diff options
author | Todd Short <tshort@akamai.com> | 2021-09-08 16:23:04 -0400 |
---|---|---|
committer | Todd Short <todd.short@me.com> | 2022-03-10 10:42:43 -0500 |
commit | a3e53d56831adb60d6875297b3339a4251f735d2 (patch) | |
tree | c931c5b2cc9a63f80e4f3ae3a366b70064b897ae /crypto/bio | |
parent | 97896f744d9ee4f2e821e3383caac8e8c5f226cf (diff) |
Add TFO support to socket BIO and s_client/s_server
Supports Linux, MacOS and FreeBSD
Disabled by default, enabled via `enabled-tfo`
Some tests
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/8692)
Diffstat (limited to 'crypto/bio')
-rw-r--r-- | crypto/bio/bio_addr.c | 12 | ||||
-rw-r--r-- | crypto/bio/bio_err.c | 4 | ||||
-rw-r--r-- | crypto/bio/bio_sock2.c | 113 | ||||
-rw-r--r-- | crypto/bio/bss_acpt.c | 4 | ||||
-rw-r--r-- | crypto/bio/bss_conn.c | 28 | ||||
-rw-r--r-- | crypto/bio/bss_sock.c | 61 |
6 files changed, 218 insertions, 4 deletions
diff --git a/crypto/bio/bio_addr.c b/crypto/bio/bio_addr.c index 8c7139691b..5f335c14d8 100644 --- a/crypto/bio/bio_addr.c +++ b/crypto/bio/bio_addr.c @@ -67,6 +67,18 @@ void BIO_ADDR_free(BIO_ADDR *ap) OPENSSL_free(ap); } +BIO_ADDR *BIO_ADDR_dup(const BIO_ADDR *ap) +{ + BIO_ADDR *ret = NULL; + + if (ap != NULL) { + ret = BIO_ADDR_new(); + if (ret != NULL) + memcpy(ret, ap, sizeof(BIO_ADDR)); + } + return ret; +} + void BIO_ADDR_clear(BIO_ADDR *ap) { memset(ap, 0, sizeof(*ap)); diff --git a/crypto/bio/bio_err.c b/crypto/bio/bio_err.c index 7a36c61148..cbe9d30785 100644 --- a/crypto/bio/bio_err.c +++ b/crypto/bio/bio_err.c @@ -46,6 +46,9 @@ static const ERR_STRING_DATA BIO_str_reasons[] = { "no hostname or service specified"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NO_PORT_DEFINED), "no port defined"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NO_SUCH_FILE), "no such file"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TFO_DISABLED), "tfo disabled"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TFO_NO_KERNEL_SUPPORT), + "tfo no kernel support"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TRANSFER_ERROR), "transfer error"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_TRANSFER_TIMEOUT), "transfer timeout"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_BIND_SOCKET), @@ -59,6 +62,7 @@ static const ERR_STRING_DATA BIO_str_reasons[] = { {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_NODELAY), "unable to nodelay"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_REUSEADDR), "unable to reuseaddr"}, + {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNABLE_TO_TFO), "unable to tfo"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNAVAILABLE_IP_FAMILY), "unavailable ip family"}, {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_UNINITIALIZED), "uninitialized"}, diff --git a/crypto/bio/bio_sock2.c b/crypto/bio/bio_sock2.c index b6c95913ce..36b071c8e1 100644 --- a/crypto/bio/bio_sock2.c +++ b/crypto/bio/bio_sock2.c @@ -13,6 +13,7 @@ #include "bio_local.h" #include "internal/ktls.h" +#include "internal/bio_tfo.h" #include <openssl/err.h> @@ -79,6 +80,7 @@ int BIO_socket(int domain, int socktype, int protocol, int options) * - BIO_SOCK_KEEPALIVE: enable regularly sending keep-alive messages. * - BIO_SOCK_NONBLOCK: Make the socket non-blocking. * - BIO_SOCK_NODELAY: don't delay small messages. + * - BIO_SOCK_TFO: use TCP Fast Open * * options holds BIO socket options that can be used * You should call this for every address returned by BIO_lookup @@ -118,6 +120,68 @@ int BIO_connect(int sock, const BIO_ADDR *addr, int options) return 0; } } + if (options & BIO_SOCK_TFO) { +# if defined(OSSL_TFO_CLIENT_FLAG) +# if defined(OSSL_TFO_SYSCTL_CLIENT) + int enabled = 0; + size_t enabledlen = sizeof(enabled); + + /* Later FreeBSD */ + if (sysctlbyname(OSSL_TFO_SYSCTL_CLIENT, &enabled, &enabledlen, NULL, 0) < 0) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_NO_KERNEL_SUPPORT); + return 0; + } + /* Need to check for client flag */ + if (!(enabled & OSSL_TFO_CLIENT_FLAG)) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_DISABLED); + return 0; + } +# elif defined(OSSL_TFO_SYSCTL) + int enabled = 0; + size_t enabledlen = sizeof(enabled); + + /* macOS */ + if (sysctlbyname(OSSL_TFO_SYSCTL, &enabled, &enabledlen, NULL, 0) < 0) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_NO_KERNEL_SUPPORT); + return 0; + } + /* Need to check for client flag */ + if (!(enabled & OSSL_TFO_CLIENT_FLAG)) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_DISABLED); + return 0; + } +# endif +# endif +# if defined(OSSL_TFO_CONNECTX) + sa_endpoints_t sae; + + memset(&sae, 0, sizeof(sae)); + sae.sae_dstaddr = BIO_ADDR_sockaddr(addr); + sae.sae_dstaddrlen = BIO_ADDR_sockaddr_size(addr); + if (connectx(sock, &sae, SAE_ASSOCID_ANY, + CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE, + NULL, 0, NULL, NULL) == -1) { + if (!BIO_sock_should_retry(-1)) { + ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(), + "calling connectx()"); + ERR_raise(ERR_LIB_BIO, BIO_R_CONNECT_ERROR); + } + return 0; + } +# endif +# if defined(OSSL_TFO_CLIENT_SOCKOPT) + if (setsockopt(sock, IPPROTO_TCP, OSSL_TFO_CLIENT_SOCKOPT, + (const void *)&on, sizeof(on)) != 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(), + "calling setsockopt()"); + ERR_raise(ERR_LIB_BIO, BIO_R_UNABLE_TO_TFO); + return 0; + } +# endif +# if defined(OSSL_TFO_DO_NOT_CONNECT) + return 1; +# endif + } if (connect(sock, BIO_ADDR_sockaddr(addr), BIO_ADDR_sockaddr_size(addr)) == -1) { @@ -201,6 +265,7 @@ int BIO_bind(int sock, const BIO_ADDR *addr, int options) * for a recently closed port. * - BIO_SOCK_V6_ONLY: When creating an IPv6 socket, make it listen only * for IPv6 addresses and not IPv4 addresses mapped to IPv6. + * - BIO_SOCK_TFO: accept TCP fast open (set TCP_FASTOPEN) * * It's recommended that you set up both an IPv6 and IPv4 listen socket, and * then check both for new clients that connect to it. You want to set up @@ -292,6 +357,54 @@ int BIO_listen(int sock, const BIO_ADDR *addr, int options) return 0; } +# if defined(OSSL_TFO_SERVER_SOCKOPT) + /* + * Must do it explicitly after listen() for macOS, still + * works fine on other OS's + */ + if ((options & BIO_SOCK_TFO) && socktype != SOCK_DGRAM) { + int q = OSSL_TFO_SERVER_SOCKOPT_VALUE; +# if defined(OSSL_TFO_CLIENT_FLAG) +# if defined(OSSL_TFO_SYSCTL_SERVER) + int enabled = 0; + size_t enabledlen = sizeof(enabled); + + /* Later FreeBSD */ + if (sysctlbyname(OSSL_TFO_SYSCTL_SERVER, &enabled, &enabledlen, NULL, 0) < 0) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_NO_KERNEL_SUPPORT); + return 0; + } + /* Need to check for server flag */ + if (!(enabled & OSSL_TFO_SERVER_FLAG)) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_DISABLED); + return 0; + } +# elif defined(OSSL_TFO_SYSCTL) + int enabled = 0; + size_t enabledlen = sizeof(enabled); + + /* Early FreeBSD, macOS */ + if (sysctlbyname(OSSL_TFO_SYSCTL, &enabled, &enabledlen, NULL, 0) < 0) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_NO_KERNEL_SUPPORT); + return 0; + } + /* Need to check for server flag */ + if (!(enabled & OSSL_TFO_SERVER_FLAG)) { + ERR_raise(ERR_LIB_BIO, BIO_R_TFO_DISABLED); + return 0; + } +# endif +# endif + if (setsockopt(sock, IPPROTO_TCP, OSSL_TFO_SERVER_SOCKOPT, + (void *)&q, sizeof(q)) < 0) { + ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(), + "calling setsockopt()"); + ERR_raise(ERR_LIB_BIO, BIO_R_UNABLE_TO_TFO); + return 0; + } + } +# endif + return 1; } diff --git a/crypto/bio/bss_acpt.c b/crypto/bio/bss_acpt.c index 1cda967335..eeac7ca4e1 100644 --- a/crypto/bio/bss_acpt.c +++ b/crypto/bio/bss_acpt.c @@ -452,10 +452,14 @@ static long acpt_ctrl(BIO *b, int cmd, long num, void *ptr) data->bio_chain = (BIO *)ptr; } else if (num == 4) { data->accept_family = *(int *)ptr; + } else if (num == 5) { + data->bind_mode |= BIO_SOCK_TFO; } } else { if (num == 2) { data->bind_mode &= ~BIO_SOCK_NONBLOCK; + } else if (num == 5) { + data->bind_mode &= ~BIO_SOCK_TFO; } } break; diff --git a/crypto/bio/bss_conn.c b/crypto/bio/bss_conn.c index 8bc53548ca..3c61bc91c5 100644 --- a/crypto/bio/bss_conn.c +++ b/crypto/bio/bss_conn.c @@ -11,6 +11,7 @@ #include <errno.h> #include "bio_local.h" +#include "internal/bio_tfo.h" #include "internal/ktls.h" #ifndef OPENSSL_NO_SOCK @@ -24,6 +25,7 @@ typedef struct bio_connect_st { # ifndef OPENSSL_NO_KTLS unsigned char record_type; # endif + int tfo_first; BIO_ADDRINFO *addr_first; const BIO_ADDRINFO *addr_iter; @@ -361,6 +363,15 @@ static int conn_write(BIO *b, const char *in, int inl) } } else # endif +# if defined(OSSL_TFO_SENDTO) + if (data->tfo_first) { + int peerlen = BIO_ADDRINFO_sockaddr_size(data->addr_iter); + + ret = sendto(b->num, in, inl, OSSL_TFO_SENDTO, + BIO_ADDRINFO_sockaddr(data->addr_iter), peerlen); + data->tfo_first = 0; + } else +# endif ret = writesocket(b->num, in, inl); BIO_clear_retry_flags(b); if (ret <= 0) { @@ -425,6 +436,8 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr) ret = -1; break; } + } else if (num == 4) { + ret = data->connect_mode; } else { ret = 0; } @@ -485,8 +498,23 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr) else data->connect_mode &= ~BIO_SOCK_NONBLOCK; break; +#if defined(TCP_FASTOPEN) && !defined(OPENSSL_NO_TFO) + case BIO_C_SET_TFO: + if (num != 0) { + data->connect_mode |= BIO_SOCK_TFO; + data->tfo_first = 1; + } else { + data->connect_mode &= ~BIO_SOCK_TFO; + data->tfo_first = 0; + } + break; +#endif case BIO_C_SET_CONNECT_MODE: data->connect_mode = (int)num; + if (num & BIO_SOCK_TFO) + data->tfo_first = 1; + else + data->tfo_first = 0; break; case BIO_C_GET_FD: if (b->init) { diff --git a/crypto/bio/bss_sock.c b/crypto/bio/bss_sock.c index f5d8810230..201bc9df2b 100644 --- a/crypto/bio/bss_sock.c +++ b/crypto/bio/bss_sock.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <errno.h> #include "bio_local.h" +#include "internal/bio_tfo.h" #include "internal/cryptlib.h" #include "internal/ktls.h" @@ -27,6 +28,14 @@ # define sock_puts SockPuts # endif +struct bss_sock_st { + BIO_ADDR tfo_peer; + int tfo_first; +#ifndef OPENSSL_NO_KTLS + unsigned char ktls_record_type; +#endif +}; + static int sock_write(BIO *h, const char *buf, int num); static int sock_read(BIO *h, char *buf, int size); static int sock_puts(BIO *h, const char *str); @@ -81,8 +90,10 @@ static int sock_new(BIO *bi) { bi->init = 0; bi->num = 0; - bi->ptr = NULL; bi->flags = 0; + bi->ptr = OPENSSL_zalloc(sizeof(struct bss_sock_st)); + if (bi->ptr == NULL) + return 0; return 1; } @@ -97,6 +108,8 @@ static int sock_free(BIO *a) a->init = 0; a->flags = 0; } + OPENSSL_free(a->ptr); + a->ptr = NULL; return 1; } @@ -126,11 +139,14 @@ static int sock_read(BIO *b, char *out, int outl) static int sock_write(BIO *b, const char *in, int inl) { int ret = 0; +# if !defined(OPENSSL_NO_KTLS) || defined(OSSL_TFO_SENDTO) + struct bss_sock_st *data = (struct bss_sock_st *)b->ptr; +# endif clear_socket_error(); # ifndef OPENSSL_NO_KTLS if (BIO_should_ktls_ctrl_msg_flag(b)) { - unsigned char record_type = (intptr_t)b->ptr; + unsigned char record_type = data->ktls_record_type; ret = ktls_send_ctrl_message(b->num, record_type, in, inl); if (ret >= 0) { ret = inl; @@ -138,6 +154,16 @@ static int sock_write(BIO *b, const char *in, int inl) } } else # endif +# if defined(OSSL_TFO_SENDTO) + if (data->tfo_first) { + struct bss_sock_st *data = (struct bss_sock_st *)b->ptr; + socklen_t peerlen = BIO_ADDR_sockaddr_size(&data->tfo_peer); + + ret = sendto(b->num, in, inl, OSSL_TFO_SENDTO, + BIO_ADDR_sockaddr(&data->tfo_peer), peerlen); + data->tfo_first = 0; + } else +# endif ret = writesocket(b->num, in, inl); BIO_clear_retry_flags(b); if (ret <= 0) { @@ -151,16 +177,24 @@ static long sock_ctrl(BIO *b, int cmd, long num, void *ptr) { long ret = 1; int *ip; + struct bss_sock_st *data = (struct bss_sock_st *)b->ptr; # ifndef OPENSSL_NO_KTLS ktls_crypto_info_t *crypto_info; # endif switch (cmd) { case BIO_C_SET_FD: - sock_free(b); + /* minimal sock_free() */ + if (b->shutdown) { + if (b->init) + BIO_closesocket(b->num); + b->flags = 0; + } b->num = *((int *)ptr); b->shutdown = (int)num; b->init = 1; + data->tfo_first = 0; + memset(&data->tfo_peer, 0, sizeof(data->tfo_peer)); break; case BIO_C_GET_FD: if (b->init) { @@ -194,7 +228,7 @@ static long sock_ctrl(BIO *b, int cmd, long num, void *ptr) return BIO_should_ktls_flag(b, 0) != 0; case BIO_CTRL_SET_KTLS_TX_SEND_CTRL_MSG: BIO_set_ktls_ctrl_msg_flag(b); - b->ptr = (void *)num; + data->ktls_record_type = (unsigned char)num; ret = 0; break; case BIO_CTRL_CLEAR_KTLS_TX_CTRL_MSG: @@ -205,6 +239,25 @@ static long sock_ctrl(BIO *b, int cmd, long num, void *ptr) case BIO_CTRL_EOF: ret = (b->flags & BIO_FLAGS_IN_EOF) != 0; break; + case BIO_C_GET_CONNECT: + if (ptr != NULL && num == 2) { + const char **pptr = (const char **)ptr; + + *pptr = (const char *)&data->tfo_peer; + } else { + ret = 0; + } + break; + case BIO_C_SET_CONNECT: + if (ptr != NULL && num == 2) { + ret = BIO_ADDR_make(&data->tfo_peer, + BIO_ADDR_sockaddr((const BIO_ADDR *)ptr)); + if (ret) + data->tfo_first = 1; + } else { + ret = 0; + } + break; default: ret = 0; break; |