123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- /*
- * 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;
- }
|