diff options
author | Tim Ruehsen <tim.ruehsen@gmx.de> | 2016-07-25 13:00:12 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2016-07-26 14:23:51 +0200 |
commit | 0f67ccecddbe6533b51e74c955a4629654e79ae3 (patch) | |
tree | a5a33b5478f294f13f23f261ba4ec26c00960aa7 /lib | |
parent | e340e7a65d9a255121de2190d194af20e8e7779e (diff) | |
download | gnutls-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.am | 2 | ||||
-rw-r--r-- | lib/buffers.c | 3 | ||||
-rw-r--r-- | lib/gnutls_int.h | 12 | ||||
-rw-r--r-- | lib/includes/Makefile.am | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/socket.h | 50 | ||||
-rw-r--r-- | lib/libgnutls.map | 1 | ||||
-rw-r--r-- | lib/state.c | 3 | ||||
-rw-r--r-- | lib/system.c | 27 | ||||
-rw-r--r-- | lib/system.h | 4 | ||||
-rw-r--r-- | lib/system/fastopen.c | 204 |
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 +} + |