mirror of
https://github.com/openssl/openssl.git
synced 2024-11-23 10:03:32 +08:00
d272ef5372
Running bio_tfo_test under asan yields ==172342==ERROR: LeakSanitizer: detected memory leaks Direct leak of 380 byte(s) in 5 object(s) allocated from: #0 0x3ff89bba251 in malloc (/lib64/libasan.so.8+0xba251) #1 0x3ff88cf9fd5 in gaih_inet.constprop.0 (/lib64/libc.so.6+0xf9fd5) #2 0x3ff88cfaf6f in getaddrinfo (/lib64/libc.so.6+0xfaf6f) #3 0x3ff89ba52a9 in __interceptor_getaddrinfo.part.0 (/lib64/libasan.so.8+0xa52a9) #4 0x1004909 in test_fd_tfo test/bio_tfo_test.c:241 [...] and fails the test. Fix this by freeing the return addrinfo on exit. Signed-off-by: Juergen Christ <jchrist@linux.ibm.com> Reviewed-by: Todd Short <todd.short@me.com> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/18979)
421 lines
13 KiB
C
421 lines
13 KiB
C
/*
|
|
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include <openssl/bio.h>
|
|
#include "internal/e_os.h"
|
|
#include "internal/sockets.h"
|
|
#include "internal/bio_tfo.h"
|
|
#include "testutil.h"
|
|
|
|
/* If OS support is added in crypto/bio/bio_tfo.h, add it here */
|
|
#if defined(OPENSSL_SYS_LINUX)
|
|
# define GOOD_OS 1
|
|
#elif defined(__FreeBSD__)
|
|
# define GOOD_OS 1
|
|
#elif defined(OPENSSL_SYS_MACOSX)
|
|
# define GOOD_OS 1
|
|
#else
|
|
# ifdef GOOD_OS
|
|
# undef GOOD_OS
|
|
# endif
|
|
#endif
|
|
|
|
#if !defined(OPENSSL_NO_TFO) && defined(GOOD_OS)
|
|
|
|
/*
|
|
* This test is to ensure that if TCP Fast Open is configured, that socket
|
|
* connections will still work. These tests are able to detect if TCP Fast
|
|
* Open works, but the tests will pass as long as the socket connects.
|
|
*
|
|
* The first test function tests the socket interface as implemented as BIOs.
|
|
*
|
|
* The second test functions tests the socket interface as implemented as fds.
|
|
*
|
|
* The tests are run 5 times. The first time is without TFO.
|
|
* The second test will create the TCP fast open cookie,
|
|
* this can be seen in `ip tcp_metrics` and in /proc/net/netstat/ on Linux.
|
|
* e.g. on Linux 4.15.0-135-generic:
|
|
* $ grep '^TcpExt:' /proc/net/netstat | cut -d ' ' -f 84-90 | column -t
|
|
* The third attempt will use the cookie and actually do TCP fast open.
|
|
* The 4th time is client-TFO only, the 5th time is server-TFO only.
|
|
*/
|
|
|
|
# define SOCKET_DATA "FooBar"
|
|
# define SOCKET_DATA_LEN sizeof(SOCKET_DATA)
|
|
|
|
static int test_bio_tfo(int idx)
|
|
{
|
|
BIO *cbio = NULL;
|
|
BIO *abio = NULL;
|
|
BIO *sbio = NULL;
|
|
int ret = 0;
|
|
int sockerr = 0;
|
|
const char *port;
|
|
int server_tfo = 0;
|
|
int client_tfo = 0;
|
|
size_t bytes;
|
|
char read_buffer[20];
|
|
|
|
switch (idx) {
|
|
default:
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
server_tfo = 1;
|
|
client_tfo = 1;
|
|
break;
|
|
case 3:
|
|
client_tfo = 1;
|
|
break;
|
|
case 4:
|
|
server_tfo = 1;
|
|
break;
|
|
}
|
|
|
|
/* ACCEPT SOCKET */
|
|
if (!TEST_ptr(abio = BIO_new_accept("localhost:0"))
|
|
|| !TEST_true(BIO_set_nbio_accept(abio, 1))
|
|
|| !TEST_true(BIO_set_tfo_accept(abio, server_tfo))
|
|
|| !TEST_int_gt(BIO_do_accept(abio), 0)
|
|
|| !TEST_ptr(port = BIO_get_accept_port(abio))) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
|
|
/* Note: first BIO_do_accept will basically do the bind/listen */
|
|
|
|
/* CLIENT SOCKET */
|
|
if (!TEST_ptr(cbio = BIO_new_connect("localhost"))
|
|
|| !TEST_long_gt(BIO_set_conn_port(cbio, port), 0)
|
|
|| !TEST_long_gt(BIO_set_nbio(cbio, 1), 0)
|
|
|| !TEST_long_gt(BIO_set_tfo(cbio, client_tfo), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
|
|
/* FIRST ACCEPT: no connection should be established */
|
|
if (BIO_do_accept(abio) <= 0) {
|
|
if (!BIO_should_retry(abio)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
|
goto err;
|
|
}
|
|
} else {
|
|
sbio = BIO_pop(abio);
|
|
BIO_printf(bio_err, "Error: accepted unknown connection\n");
|
|
goto err;
|
|
}
|
|
|
|
/* CONNECT ATTEMPT: different behavior based on TFO support */
|
|
if (BIO_do_connect(cbio) <= 0) {
|
|
sockerr = get_last_socket_error();
|
|
if (sockerr == EOPNOTSUPP) {
|
|
BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
|
|
goto success;
|
|
} else if (sockerr != EINPROGRESS) {
|
|
BIO_printf(bio_err, "Error: failed without EINPROGRESSn");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
/* macOS needs some time for this to happen, so put in a select */
|
|
if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket wait failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
|
|
if (BIO_do_accept(abio) <= 0) {
|
|
if (!BIO_should_retry(abio)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
|
goto err;
|
|
}
|
|
} else {
|
|
if (idx == 0)
|
|
BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
|
|
else if (idx == 1)
|
|
BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
|
|
else if (idx == 4)
|
|
BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
|
|
else
|
|
BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
|
|
sbio = BIO_pop(abio);
|
|
goto success;
|
|
}
|
|
|
|
/* SEND DATA: this should establish the actual TFO connection */
|
|
if (!TEST_true(BIO_write_ex(cbio, SOCKET_DATA, SOCKET_DATA_LEN, &bytes))) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
|
|
/* macOS needs some time for this to happen, so put in a select */
|
|
if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket wait failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
|
|
if (BIO_do_accept(abio) <= 0) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket not accepted\n");
|
|
goto err;
|
|
}
|
|
BIO_printf(bio_err, "Success: Server accepted socket after write\n");
|
|
if (!TEST_ptr(sbio = BIO_pop(abio))
|
|
|| !TEST_true(BIO_read_ex(sbio, read_buffer, sizeof(read_buffer), &bytes))
|
|
|| !TEST_size_t_eq(bytes, SOCKET_DATA_LEN)
|
|
|| !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
|
|
success:
|
|
sockerr = 0;
|
|
ret = 1;
|
|
|
|
err:
|
|
if (sockerr != 0) {
|
|
const char *errstr = strerror(sockerr);
|
|
|
|
if (errstr != NULL)
|
|
BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
|
|
}
|
|
BIO_free(cbio);
|
|
BIO_free(abio);
|
|
BIO_free(sbio);
|
|
return ret;
|
|
}
|
|
|
|
static int test_fd_tfo(int idx)
|
|
{
|
|
struct sockaddr_storage sstorage;
|
|
socklen_t slen;
|
|
struct addrinfo *ai = NULL;
|
|
struct addrinfo hints;
|
|
int ret = 0;
|
|
int cfd = -1; /* client socket */
|
|
int afd = -1; /* accept socket */
|
|
int sfd = -1; /* server accepted socket */
|
|
BIO_ADDR *baddr = NULL;
|
|
char read_buffer[20];
|
|
int bytes_read;
|
|
int server_flags = BIO_SOCK_NONBLOCK;
|
|
int client_flags = BIO_SOCK_NONBLOCK;
|
|
int sockerr = 0;
|
|
unsigned short port;
|
|
void *addr;
|
|
size_t addrlen;
|
|
|
|
switch (idx) {
|
|
default:
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
server_flags |= BIO_SOCK_TFO;
|
|
client_flags |= BIO_SOCK_TFO;
|
|
break;
|
|
case 3:
|
|
client_flags |= BIO_SOCK_TFO;
|
|
break;
|
|
case 4:
|
|
server_flags |= BIO_SOCK_TFO;
|
|
break;
|
|
}
|
|
|
|
/* ADDRESS SETUP */
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
if (!TEST_int_eq(getaddrinfo(NULL, "0", &hints, &ai), 0))
|
|
goto err;
|
|
|
|
switch (ai->ai_family) {
|
|
case AF_INET:
|
|
port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
|
|
addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr;
|
|
addrlen = sizeof(((struct sockaddr_in *)ai->ai_addr)->sin_addr);
|
|
BIO_printf(bio_err, "Using IPv4\n");
|
|
break;
|
|
case AF_INET6:
|
|
port = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port;
|
|
addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
|
|
addrlen = sizeof(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
|
|
BIO_printf(bio_err, "Using IPv6\n");
|
|
break;
|
|
default:
|
|
BIO_printf(bio_err, "Unknown address family %d\n", ai->ai_family);
|
|
goto err;
|
|
}
|
|
|
|
if (!TEST_ptr(baddr = BIO_ADDR_new())
|
|
|| !TEST_true(BIO_ADDR_rawmake(baddr, ai->ai_family, addr, addrlen, port)))
|
|
goto err;
|
|
|
|
/* ACCEPT SOCKET */
|
|
|
|
if (!TEST_int_ge(afd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0)
|
|
|| !TEST_true(BIO_listen(afd, baddr, server_flags)))
|
|
goto err;
|
|
|
|
/* UPDATE ADDRESS WITH PORT */
|
|
slen = sizeof(sstorage);
|
|
if (!TEST_int_ge(getsockname(afd, (struct sockaddr *)&sstorage, &slen), 0))
|
|
goto err;
|
|
|
|
switch (sstorage.ss_family) {
|
|
case AF_INET:
|
|
port = ((struct sockaddr_in *)&sstorage)->sin_port;
|
|
addr = &((struct sockaddr_in *)&sstorage)->sin_addr;
|
|
addrlen = sizeof(((struct sockaddr_in *)&sstorage)->sin_addr);
|
|
break;
|
|
case AF_INET6:
|
|
port = ((struct sockaddr_in6 *)&sstorage)->sin6_port;
|
|
addr = &((struct sockaddr_in6 *)&sstorage)->sin6_addr;
|
|
addrlen = sizeof(((struct sockaddr_in6 *)&sstorage)->sin6_addr);
|
|
break;
|
|
default:
|
|
goto err;
|
|
}
|
|
|
|
if(!TEST_true(BIO_ADDR_rawmake(baddr, sstorage.ss_family, addr, addrlen, port)))
|
|
goto err;
|
|
|
|
/* CLIENT SOCKET */
|
|
if (!TEST_int_ge(cfd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0))
|
|
goto err;
|
|
|
|
/* FIRST ACCEPT: no connection should be established */
|
|
sfd = BIO_accept_ex(afd, NULL, 0);
|
|
if (sfd == -1) {
|
|
sockerr = get_last_socket_error();
|
|
/* Note: Windows would hit WSAEWOULDBLOCK */
|
|
if (sockerr != EAGAIN) {
|
|
BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
|
goto err;
|
|
}
|
|
} else {
|
|
BIO_printf(bio_err, "Error: accepted unknown connection\n");
|
|
goto err;
|
|
}
|
|
|
|
/* CONNECT ATTEMPT: different behavior based on TFO support */
|
|
if (!BIO_connect(cfd, baddr, client_flags)) {
|
|
sockerr = get_last_socket_error();
|
|
if (sockerr == EOPNOTSUPP) {
|
|
BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
|
|
goto success;
|
|
} else {
|
|
/* Note: Windows would hit WSAEWOULDBLOCK */
|
|
if (sockerr != EINPROGRESS) {
|
|
BIO_printf(bio_err, "Error: failed without EINPROGRESS\n");
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* macOS needs some time for this to happen, so put in a select */
|
|
if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket wait failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
|
|
sfd = BIO_accept_ex(afd, NULL, 0);
|
|
if (sfd == -1) {
|
|
sockerr = get_last_socket_error();
|
|
/* Note: Windows would hit WSAEWOULDBLOCK */
|
|
if (sockerr != EAGAIN) {
|
|
BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
|
goto err;
|
|
}
|
|
} else {
|
|
if (idx == 0)
|
|
BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
|
|
else if (idx == 1)
|
|
BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
|
|
else if (idx == 4)
|
|
BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
|
|
else
|
|
BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
|
|
goto success;
|
|
}
|
|
|
|
/* SEND DATA: this should establish the actual TFO connection */
|
|
#ifdef OSSL_TFO_SENDTO
|
|
if (!TEST_int_ge(sendto(cfd, SOCKET_DATA, SOCKET_DATA_LEN, OSSL_TFO_SENDTO,
|
|
(struct sockaddr *)&sstorage, slen), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
#else
|
|
if (!TEST_int_ge(writesocket(cfd, SOCKET_DATA, SOCKET_DATA_LEN), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
#endif
|
|
|
|
/* macOS needs some time for this to happen, so put in a select */
|
|
if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket wait failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
|
|
sfd = BIO_accept_ex(afd, NULL, 0);
|
|
if (sfd == -1) {
|
|
sockerr = get_last_socket_error();
|
|
BIO_printf(bio_err, "Error: socket not accepted\n");
|
|
goto err;
|
|
}
|
|
BIO_printf(bio_err, "Success: Server accepted socket after write\n");
|
|
bytes_read = readsocket(sfd, read_buffer, sizeof(read_buffer));
|
|
if (!TEST_int_eq(bytes_read, SOCKET_DATA_LEN)
|
|
|| !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
|
|
sockerr = get_last_socket_error();
|
|
goto err;
|
|
}
|
|
|
|
success:
|
|
sockerr = 0;
|
|
ret = 1;
|
|
|
|
err:
|
|
if (sockerr != 0) {
|
|
const char *errstr = strerror(sockerr);
|
|
|
|
if (errstr != NULL)
|
|
BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
|
|
}
|
|
if (ai != NULL)
|
|
freeaddrinfo(ai);
|
|
BIO_ADDR_free(baddr);
|
|
BIO_closesocket(cfd);
|
|
BIO_closesocket(sfd);
|
|
BIO_closesocket(afd);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
int setup_tests(void)
|
|
{
|
|
#if !defined(OPENSSL_NO_TFO) && defined(GOOD_OS)
|
|
ADD_ALL_TESTS(test_bio_tfo, 5);
|
|
ADD_ALL_TESTS(test_fd_tfo, 5);
|
|
#endif
|
|
return 1;
|
|
}
|