Commit 448af51f authored by Nikos Mavrogiannopoulos's avatar Nikos Mavrogiannopoulos
Browse files

TCP fast open code is more self-contained

parent 8969d893
......@@ -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 \
......
......@@ -496,19 +496,7 @@ _gnutls_writev(gnutls_session_t session, const giovec_t * giovec,
}
if (no_writev == 0) {
if (session->internals.connect_addr) {
#ifdef HAVE_WRITEV
if (session->internals.vec_push_func == system_writev)
i = system_writev_tfo(session, giovec, giovec_cnt);
#ifdef MSG_NOSIGNAL
else if (session->internals.vec_push_func == system_writev_nosignal)
i = system_writev_nosignal_tfo(session, giovec, giovec_cnt);
#endif
else
#endif
i = session->internals.vec_push_func(fd, giovec, giovec_cnt);
} else
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);
}
......
......@@ -752,6 +752,13 @@ typedef struct {
unsigned int packets_dropped;
} dtls_st;
typedef struct tfo_st {
int fd;
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;
......@@ -1024,8 +1031,7 @@ 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() */
struct sockaddr *connect_addr;
socklen_t connect_addrlen;
tfo_st tfo;
/* If you add anything here, check _gnutls_handshake_internal_state_clear().
*/
......
......@@ -38,6 +38,7 @@ extern "C" {
/* *INDENT-ON* */
void gnutls_transport_set_fastopen(gnutls_session_t session,
int fd,
struct sockaddr *connect_addr,
socklen_t connect_addrlen);
......
......@@ -146,47 +146,6 @@ gnutls_transport_set_int2(gnutls_session_t session,
(gnutls_transport_ptr_t) (long) recv_int;
}
/**
* gnutls_transport_set_fastopen:
* @session: is a #gnutls_session_t type.
* @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.
* Not every OS supports TFO yet.
*
* Overriding push functions via gnutls_transport_set_vec_push_function()
* or gnutls_transport_set_push_function() effectively disables the internal
* support for TFO. In this case the user's write function has to care about
* the implementation of TFO.
*
* 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,
struct sockaddr *connect_addr, socklen_t connect_addrlen)
{
gnutls_free(session->internals.connect_addr);
if (connect_addr && connect_addrlen) {
session->internals.connect_addr = gnutls_malloc(connect_addrlen);
if (session->internals.connect_addr) {
memcpy(session->internals.connect_addr, connect_addr, connect_addrlen);
session->internals.connect_addrlen = connect_addrlen;
} else
gnutls_assert();
} else {
session->internals.connect_addr = NULL;
session->internals.connect_addrlen = 0;
}
}
#if 0
/* this will be a macro */
/**
......
......@@ -304,9 +304,8 @@ void _gnutls_handshake_internal_state_clear(gnutls_session_t session)
session->internals.handshake_endtime = 0;
session->internals.handshake_in_progress = 0;
gnutls_free(session->internals.connect_addr);
session->internals.connect_addr = NULL;
session->internals.connect_addrlen = 0;
session->internals.tfo.connect_addrlen = 0;
session->internals.tfo.connect_only = 0;
}
/**
......
......@@ -31,11 +31,6 @@
#include <sys/types.h>
#include <c-ctype.h>
/* Get TCP_FASTOPEN */
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef _WIN32
# include <windows.h>
# include <wincrypt.h>
......@@ -129,52 +124,6 @@ _system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, flags);
}
static ssize_t
_system_writev_tfo(gnutls_session_t session, const giovec_t * iovec,
int iovec_cnt, int flags)
{
int fd = GNUTLS_POINTER_TO_INT(session->internals.transport_send_ptr);
struct msghdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.msg_iov = (struct iovec *)iovec;
hdr.msg_iovlen = iovec_cnt;
#ifdef MSG_FASTOPEN
int ret, on = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)) == -1)
_gnutls_debug_log("Failed to set socket option FASTOPEN\n");
hdr.msg_name = session->internals.connect_addr;
hdr.msg_namelen = session->internals.connect_addrlen;
ret = sendmsg(fd, &hdr, flags | MSG_FASTOPEN);
if (ret < 0) {
if (errno == EINPROGRESS) {
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");
ret = connect(fd, session->internals.connect_addr, session->internals.connect_addrlen);
if (errno == ENOTCONN || errno == EINPROGRESS)
errno = EAGAIN;
}
}
if (ret == 0 || errno != EAGAIN) {
/* This function has to be called just once, connect info not needed any more */
gnutls_free(session->internals.connect_addr);
session->internals.connect_addr = NULL;
session->internals.connect_addrlen = 0;
}
return ret;
#else
return sendmsg(fd, &hdr, flags);
#endif
}
#ifdef MSG_NOSIGNAL
ssize_t
system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
......@@ -183,12 +132,6 @@ system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
return _system_writev(ptr, iovec, iovec_cnt, MSG_NOSIGNAL);
}
ssize_t
system_writev_nosignal_tfo(gnutls_session_t session, const giovec_t * iovec,
int iovec_cnt)
{
return _system_writev_tfo(session, iovec, iovec_cnt, MSG_NOSIGNAL);
}
#endif
ssize_t
......@@ -198,12 +141,6 @@ system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
return _system_writev(ptr, iovec, iovec_cnt, 0);
}
ssize_t
system_writev_tfo(gnutls_session_t session, const giovec_t * iovec,
int iovec_cnt)
{
return _system_writev_tfo(session, iovec, iovec_cnt, 0);
}
#endif
......
/*
* 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
static ssize_t
_system_writev_tfo(tfo_st *p, const giovec_t * iovec, int iovec_cnt,
int flags)
{
int fd = p->fd;
struct msghdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.msg_iov = (struct iovec *)iovec;
hdr.msg_iovlen = iovec_cnt;
if (unlikely(p->connect_addrlen != 0)) {
int ret, on = 1;
#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, 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;
}
/* else */
return sendmsg(fd, &hdr, flags);
}
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_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
int iovec_cnt)
{
tfo_st *p = ptr;
return _system_writev_tfo(p, iovec, iovec_cnt, MSG_NOSIGNAL);
}
static ssize_t
tfo_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec,
int iovec_cnt)
{
tfo_st *p = ptr;
return _system_writev_tfo(p, iovec, iovec_cnt, 0);
}
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);
}
static ssize_t
tfo_send(gnutls_transport_ptr_t ptr, const void *data, size_t data_size)
{
tfo_st *p = ptr;
return send(p->fd, data, data_size, 0);
}
/**
* gnutls_transport_set_fastopen:
* @session: is a #gnutls_session_t type.
* @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.
*
* Returns: zero if TCP fast open is supported, or %GNUTLS_E_UNIMPLEMENTED_FEATURE
* otherwise.
*
* 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 > 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_push_function(session, tfo_send);
gnutls_transport_set_ptr(session, &session->internals.tfo);
#ifdef MSG_NOSIGNAL
if (session->internals.flags & GNUTLS_NO_SIGNAL)
gnutls_transport_set_vec_push_function(session, tfo_writev_nosignal);
else
#endif
gnutls_transport_set_vec_push_function(session, tfo_writev);
return;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment