summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTim Ruehsen <tim.ruehsen@gmx.de>2016-07-25 13:00:12 +0200
committerNikos Mavrogiannopoulos <nmav@redhat.com>2016-07-26 14:23:51 +0200
commit0f67ccecddbe6533b51e74c955a4629654e79ae3 (patch)
treea5a33b5478f294f13f23f261ba4ec26c00960aa7 /lib
parente340e7a65d9a255121de2190d194af20e8e7779e (diff)
downloadgnutls-0f67ccecddbe6533b51e74c955a4629654e79ae3.tar.gz
Support TCP Fast Open
This introduces a new function gnutls_transport_set_fastopen(). Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com> Signed-off-by: Tim Ruehsen <tim.ruehsen@gmx.de>
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/buffers.c3
-rw-r--r--lib/gnutls_int.h12
-rw-r--r--lib/includes/Makefile.am2
-rw-r--r--lib/includes/gnutls/socket.h50
-rw-r--r--lib/libgnutls.map1
-rw-r--r--lib/state.c3
-rw-r--r--lib/system.c27
-rw-r--r--lib/system.h4
-rw-r--r--lib/system/fastopen.c204
10 files changed, 292 insertions, 16 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index ec549e848c..6eaf9b86e2 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -72,7 +72,7 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c \
extensions.c auth.c sslv2_compat.c datum.c session_pack.c mpi.c \
pk.c cert.c global.c constate.c anon_cred.c pkix_asn1_tab.c gnutls_asn1_tab.c \
mem.c fingerprint.c vasprintf.c vasprintf.h tls-sig.c ecc.c alert.c privkey_raw.c \
- system.c inet_ntop.c str.c state.c x509.c file.c supplemental.c \
+ system.c system/fastopen.c inet_ntop.c str.c state.c x509.c file.c supplemental.c \
random.c crypto-api.c privkey.c pcert.c pubkey.c locks.c dtls.c \
system_override.c crypto-backend.c verify-tofu.c pin.c tpm.c fips.c \
safe-memfuncs.c inet_pton.c atfork.c atfork.h randomart.c \
diff --git a/lib/buffers.c b/lib/buffers.c
index 756969903e..72c48e7e04 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -496,8 +496,7 @@ _gnutls_writev(gnutls_session_t session, const giovec_t * giovec,
}
if (no_writev == 0) {
- i = session->internals.vec_push_func(fd, giovec,
- giovec_cnt);
+ i = session->internals.vec_push_func(fd, giovec, giovec_cnt);
} else {
i = _gnutls_writev_emu(session, fd, giovec, giovec_cnt, 1);
}
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index bb3739c04e..f7739aedab 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -92,6 +92,7 @@ typedef struct {
#include <gnutls/gnutls.h>
#include <gnutls/dtls.h>
#include <gnutls/abstract.h>
+#include <gnutls/socket.h>
#include <system.h>
/* in case we compile with system headers taking priority, we
@@ -751,6 +752,14 @@ typedef struct {
unsigned int packets_dropped;
} dtls_st;
+typedef struct tfo_st {
+ int fd;
+ int flags;
+ bool connect_only; /* a previous sendmsg() failed, attempting connect() */
+ struct sockaddr_storage connect_addr;
+ socklen_t connect_addrlen;
+} tfo_st;
+
typedef struct {
/* holds all the parsed data received by the record layer */
mbuffer_head_st record_buffer;
@@ -1022,6 +1031,9 @@ typedef struct {
bool false_start_used; /* non-zero if false start was used for appdata */
+ /* Needed for TCP Fast Open (TFO), set by gnutls_transport_set_fastopen() */
+ tfo_st tfo;
+
/* If you add anything here, check _gnutls_handshake_internal_state_clear().
*/
} internals_st;
diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am
index 09665afe57..6319ddbc5a 100644
--- a/lib/includes/Makefile.am
+++ b/lib/includes/Makefile.am
@@ -22,7 +22,7 @@ nobase_include_HEADERS = gnutls/x509.h gnutls/pkcs12.h gnutls/compat.h \
gnutls/openpgp.h gnutls/crypto.h gnutls/pkcs11.h \
gnutls/abstract.h gnutls/dtls.h gnutls/ocsp.h gnutls/tpm.h \
gnutls/x509-ext.h gnutls/self-test.h gnutls/system-keys.h \
- gnutls/urls.h gnutls/pkcs7.h
+ gnutls/urls.h gnutls/pkcs7.h gnutls/socket.h
if ENABLE_CXX
nobase_include_HEADERS += gnutls/gnutlsxx.h
diff --git a/lib/includes/gnutls/socket.h b/lib/includes/gnutls/socket.h
new file mode 100644
index 0000000000..dea6f2620b
--- /dev/null
+++ b/lib/includes/gnutls/socket.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * Author: Tim Ruehsen
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* This file contains socket related types, prototypes and includes.
+ */
+
+#ifndef GNUTLS_SOCKET_H
+#define GNUTLS_SOCKET_H
+
+#include <gnutls/gnutls.h>
+
+/* Get socklen_t */
+#include <sys/socket.h>
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* *INDENT-ON* */
+
+void gnutls_transport_set_fastopen(gnutls_session_t session,
+ int fd,
+ struct sockaddr *connect_addr,
+ socklen_t connect_addrlen);
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+}
+#endif
+/* *INDENT-ON* */
+#endif /* GNUTLS_SOCKET_H */
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index d73332562c..0c2b5486c4 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -1101,6 +1101,7 @@ GNUTLS_3_4
gnutls_x509_tlsfeatures_check_crt;
gnutls_x509_crq_set_extension_by_oid;
gnutls_x509_dn_set_str;
+ gnutls_transport_set_fastopen;
local:
*;
};
diff --git a/lib/state.c b/lib/state.c
index 23a80bb9fa..8b81f73cfc 100644
--- a/lib/state.c
+++ b/lib/state.c
@@ -303,6 +303,9 @@ void _gnutls_handshake_internal_state_clear(gnutls_session_t session)
session->internals.handshake_endtime = 0;
session->internals.handshake_in_progress = 0;
+
+ session->internals.tfo.connect_addrlen = 0;
+ session->internals.tfo.connect_only = 0;
}
/**
diff --git a/lib/system.c b/lib/system.c
index 5c9bc6e68f..d1e1171da6 100644
--- a/lib/system.c
+++ b/lib/system.c
@@ -111,10 +111,9 @@ int system_errno(gnutls_transport_ptr_t ptr)
return errno;
}
-#ifdef MSG_NOSIGNAL
-ssize_t
-system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
- int iovec_cnt)
+static ssize_t
+_system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
+ int iovec_cnt, int flags)
{
struct msghdr hdr;
@@ -122,22 +121,26 @@ system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
hdr.msg_iov = (struct iovec *)iovec;
hdr.msg_iovlen = iovec_cnt;
- return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, MSG_NOSIGNAL);
+ return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, flags);
}
-#endif
+#ifdef MSG_NOSIGNAL
ssize_t
-system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
+system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
int iovec_cnt)
{
- struct msghdr hdr;
+ return _system_writev(ptr, iovec, iovec_cnt, MSG_NOSIGNAL);
+}
- memset(&hdr, 0, sizeof(hdr));
- hdr.msg_iov = (struct iovec *)iovec;
- hdr.msg_iovlen = iovec_cnt;
+#endif
- return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, 0);
+ssize_t
+system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
+ int iovec_cnt)
+{
+ return _system_writev(ptr, iovec, iovec_cnt, 0);
}
+
#endif
diff --git a/lib/system.h b/lib/system.h
index dd77365a1c..11803047ea 100644
--- a/lib/system.h
+++ b/lib/system.h
@@ -50,6 +50,10 @@ ssize_t system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
int iovec_cnt);
ssize_t system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
int iovec_cnt);
+ssize_t system_writev_tfo(gnutls_session_t ptr, const giovec_t * iovec,
+ int iovec_cnt);
+ssize_t system_writev_nosignal_tfo(gnutls_session_t ptr, const giovec_t * iovec,
+ int iovec_cnt);
#endif
ssize_t system_read(gnutls_transport_ptr_t ptr, void *data,
size_t data_size);
diff --git a/lib/system/fastopen.c b/lib/system/fastopen.c
new file mode 100644
index 0000000000..c2f6caefe0
--- /dev/null
+++ b/lib/system/fastopen.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <system.h>
+#include "gnutls_int.h"
+#include "errors.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <c-ctype.h>
+
+/* Get TCP_FASTOPEN */
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+
+/* Do not use the gnulib functions for sending and receiving data.
+ * Using them makes gnutls only working with gnulib applications.
+ */
+#undef send
+#undef recv
+#undef select
+#undef connect
+
+#ifdef _WIN32
+static ssize_t
+tfo_send(gnutls_transport_ptr_t ptr, const void *buf, size_t len)
+{
+ tfo_st *p = ptr;
+ int fd = p->fd;
+
+ if (unlikely(p->connect_addrlen != 0)) {
+ int ret;
+
+ ret = connect(fd, (struct sockaddr*)&p->connect_addr, p->connect_addrlen);
+ if (errno == ENOTCONN || errno == EINPROGRESS) {
+ gnutls_assert();
+ errno = EAGAIN;
+ }
+
+ if (ret == 0 || errno != EAGAIN) {
+ p->connect_only = 0;
+ p->connect_addrlen = 0;
+ }
+
+ return ret;
+ }
+
+ return send(fd, buf, len, 0);
+}
+#else /* sendmsg */
+static ssize_t
+tfo_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec, int iovec_cnt)
+{
+ tfo_st *p = ptr;
+ int fd = p->fd;
+ struct msghdr hdr;
+ int ret, on = 1;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.msg_iov = (struct iovec *)iovec;
+ hdr.msg_iovlen = iovec_cnt;
+
+ if (likely(!p->connect_addrlen))
+ return sendmsg(fd, &hdr, p->flags);
+
+#ifdef MSG_FASTOPEN
+ if (!p->connect_only) {
+ if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)) == -1)
+ _gnutls_debug_log("Failed to set socket option FASTOPEN\n");
+
+ hdr.msg_name = &p->connect_addr;
+ hdr.msg_namelen = p->connect_addrlen;
+
+ ret = sendmsg(fd, &hdr, p->flags | MSG_FASTOPEN);
+
+ if (ret < 0) {
+ if (errno == EINPROGRESS) {
+ gnutls_assert();
+ errno = EAGAIN; // GnuTLS does not handle EINPROGRESS
+ } else if (errno == EOPNOTSUPP) {
+ // fallback from fastopen, e.g. when fastopen is disabled in system
+ _gnutls_debug_log("Fallback from TCP Fast Open... TFO is not enabled at system level\n");
+ p->connect_only = 1;
+ goto connect_only;
+ }
+ }
+ } else
+#endif
+ {
+ connect_only:
+ ret = connect(fd, (struct sockaddr*)&p->connect_addr, p->connect_addrlen);
+ if (errno == ENOTCONN || errno == EINPROGRESS) {
+ gnutls_assert();
+ errno = EAGAIN;
+ }
+
+ if (ret == 0)
+ p->connect_only = 0;
+ }
+
+ if (ret == 0 || errno != EAGAIN) {
+ /* This has to be called just once, connect info not needed any more */
+ p->connect_addrlen = 0;
+ }
+
+ return ret;
+}
+#endif
+
+static
+int tfo_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms)
+{
+ tfo_st *p = ptr;
+
+ return gnutls_system_recv_timeout((gnutls_transport_ptr_t)(long)p->fd, ms);
+}
+
+static ssize_t
+tfo_read(gnutls_transport_ptr_t ptr, void *data, size_t data_size)
+{
+ tfo_st *p = ptr;
+
+ return recv(p->fd, data, data_size, 0);
+}
+
+/**
+ * gnutls_transport_set_fastopen:
+ * @session: is a #gnutls_session_t type.
+ * @fd: is the session's socket descriptor
+ * @connect_addr: is the address we want to connect to
+ * @connect_addrlen: is the length of @connect_addr
+ *
+ * Enables TCP Fast Open (TFO) when @connect_addr and @connect_addrlen are set
+ * before the transport socket has been connected.
+ *
+ * TFO only works for TCP sockets of type AF_INET and AF_INET6.
+ * If the OS doesn't support TCP fast open this function will use
+ * connect() transparently during the first write.
+ *
+ * Note: This function overrides all transport callback functions.
+ * If this is undesirable, TCP Fast Open must be implemented on the user
+ * callback functions without calling this function. When using
+ * this function gnutls_transport_set_ptr() or gnutls_transport_set_int()
+ * must not be used.
+ *
+ * On GNU/Linux TFO has to be enabled at the system layer, that is
+ * in /proc/sys/net/ipv4/tcp_fastopen, bit 0 has to be set.
+ *
+ * Since: 3.5.3
+ **/
+void
+gnutls_transport_set_fastopen(gnutls_session_t session,
+ int fd, struct sockaddr *connect_addr, socklen_t connect_addrlen)
+{
+ if (connect_addrlen > (socklen_t)sizeof(session->internals.tfo.connect_addr)) {
+ gnutls_assert();
+ abort();
+ }
+
+ memcpy(&session->internals.tfo.connect_addr, connect_addr, connect_addrlen);
+ session->internals.tfo.connect_addrlen = connect_addrlen;
+ session->internals.tfo.fd = fd;
+
+ gnutls_transport_set_pull_function(session, tfo_read);
+ gnutls_transport_set_pull_timeout_function(session, tfo_recv_timeout);
+ gnutls_transport_set_ptr(session, &session->internals.tfo);
+
+ session->internals.tfo.flags = 0;
+#ifdef MSG_NOSIGNAL
+ if (session->internals.flags & GNUTLS_NO_SIGNAL)
+ session->internals.tfo.flags |= MSG_NOSIGNAL;
+#endif
+
+#ifdef _WIN32
+ gnutls_transport_set_vec_push_function(session, NULL);
+ gnutls_transport_set_push_function(session, tfo_send);
+#else
+ gnutls_transport_set_vec_push_function(session, tfo_writev);
+#endif
+}
+