xref: /freebsd/crypto/openssl/test/bio_tfo_test.c (revision 1523ccfd9c8c254f7928143d31c305384b05fd11)
1 /*
2  * Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 #include <openssl/bio.h>
11 #include "internal/e_os.h"
12 #include "internal/sockets.h"
13 #include "internal/bio_tfo.h"
14 #include "testutil.h"
15 
16 /* If OS support is added in crypto/bio/bio_tfo.h, add it here */
17 #if defined(OPENSSL_SYS_LINUX)
18 #define GOOD_OS 1
19 #elif defined(__FreeBSD__)
20 #define GOOD_OS 1
21 #elif defined(OPENSSL_SYS_MACOSX)
22 #define GOOD_OS 1
23 #else
24 #ifdef GOOD_OS
25 #undef GOOD_OS
26 #endif
27 #endif
28 
29 #if !defined(OPENSSL_NO_TFO) && defined(GOOD_OS)
30 
31 /*
32  * This test is to ensure that if TCP Fast Open is configured, that socket
33  * connections will still work. These tests are able to detect if TCP Fast
34  * Open works, but the tests will pass as long as the socket connects.
35  *
36  * The first test function tests the socket interface as implemented as BIOs.
37  *
38  * The second test functions tests the socket interface as implemented as fds.
39  *
40  * The tests are run 5 times. The first time is without TFO.
41  * The second test will create the TCP fast open cookie,
42  * this can be seen in `ip tcp_metrics` and in /proc/net/netstat/ on Linux.
43  * e.g. on Linux 4.15.0-135-generic:
44  * $ grep '^TcpExt:' /proc/net/netstat | cut -d ' ' -f 84-90 | column -t
45  * The third attempt will use the cookie and actually do TCP fast open.
46  * The 4th time is client-TFO only, the 5th time is server-TFO only.
47  */
48 
49 #define SOCKET_DATA "FooBar"
50 #define SOCKET_DATA_LEN sizeof(SOCKET_DATA)
51 
test_bio_tfo(int idx)52 static int test_bio_tfo(int idx)
53 {
54     BIO *cbio = NULL;
55     BIO *abio = NULL;
56     BIO *sbio = NULL;
57     int ret = 0;
58     int sockerr = 0;
59     const char *port;
60     int server_tfo = 0;
61     int client_tfo = 0;
62     size_t bytes;
63     char read_buffer[20];
64 
65     switch (idx) {
66     default:
67     case 0:
68         break;
69     case 1:
70     case 2:
71         server_tfo = 1;
72         client_tfo = 1;
73         break;
74     case 3:
75         client_tfo = 1;
76         break;
77     case 4:
78         server_tfo = 1;
79         break;
80     }
81 
82     /* ACCEPT SOCKET */
83     if (!TEST_ptr(abio = BIO_new_accept("localhost:0"))
84 #if !OPENSSL_USE_IPV6
85         || !TEST_true(BIO_set_accept_ip_family(abio, BIO_FAMILY_IPV4))
86 #endif
87         || !TEST_true(BIO_set_nbio_accept(abio, 1))
88         || !TEST_true(BIO_set_tfo_accept(abio, server_tfo))
89         || !TEST_int_gt(BIO_do_accept(abio), 0)
90         || !TEST_ptr(port = BIO_get_accept_port(abio))) {
91         sockerr = get_last_socket_error();
92         goto err;
93     }
94 
95     /* Note: first BIO_do_accept will basically do the bind/listen */
96 
97     /* CLIENT SOCKET */
98     if (!TEST_ptr(cbio = BIO_new_connect("localhost"))
99 #if !OPENSSL_USE_IPV6
100         || !TEST_long_gt(BIO_set_conn_ip_family(cbio, BIO_FAMILY_IPV4), 0)
101 #endif
102         || !TEST_long_gt(BIO_set_conn_port(cbio, port), 0)
103         || !TEST_long_gt(BIO_set_nbio(cbio, 1), 0)
104         || !TEST_long_gt(BIO_set_tfo(cbio, client_tfo), 0)) {
105         sockerr = get_last_socket_error();
106         goto err;
107     }
108 
109     /* FIRST ACCEPT: no connection should be established */
110     if (BIO_do_accept(abio) <= 0) {
111         if (!BIO_should_retry(abio)) {
112             sockerr = get_last_socket_error();
113             BIO_printf(bio_err, "Error: failed without EAGAIN\n");
114             goto err;
115         }
116     } else {
117         sbio = BIO_pop(abio);
118         BIO_printf(bio_err, "Error: accepted unknown connection\n");
119         goto err;
120     }
121 
122     /* CONNECT ATTEMPT: different behavior based on TFO support */
123     if (BIO_do_connect(cbio) <= 0) {
124         sockerr = get_last_socket_error();
125         if (sockerr == EOPNOTSUPP) {
126             BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
127             goto success;
128         } else if (sockerr != EINPROGRESS) {
129             BIO_printf(bio_err, "Error: failed without EINPROGRESSn");
130             goto err;
131         }
132     }
133 
134     /* macOS needs some time for this to happen, so put in a select */
135     if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
136         sockerr = get_last_socket_error();
137         BIO_printf(bio_err, "Error: socket wait failed\n");
138         goto err;
139     }
140 
141     /* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
142     if (BIO_do_accept(abio) <= 0) {
143         if (!BIO_should_retry(abio)) {
144             sockerr = get_last_socket_error();
145             BIO_printf(bio_err, "Error: failed without EAGAIN\n");
146             goto err;
147         }
148     } else {
149         if (idx == 0)
150             BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
151         else if (idx == 1)
152             BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
153         else if (idx == 4)
154             BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
155         else
156             BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
157         sbio = BIO_pop(abio);
158         goto success;
159     }
160 
161     /* SEND DATA: this should establish the actual TFO connection */
162     if (!TEST_true(BIO_write_ex(cbio, SOCKET_DATA, SOCKET_DATA_LEN, &bytes))) {
163         sockerr = get_last_socket_error();
164         goto err;
165     }
166 
167     /* macOS needs some time for this to happen, so put in a select */
168     if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
169         sockerr = get_last_socket_error();
170         BIO_printf(bio_err, "Error: socket wait failed\n");
171         goto err;
172     }
173 
174     /* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
175     if (BIO_do_accept(abio) <= 0) {
176         sockerr = get_last_socket_error();
177         BIO_printf(bio_err, "Error: socket not accepted\n");
178         goto err;
179     }
180     BIO_printf(bio_err, "Success: Server accepted socket after write\n");
181     if (!TEST_ptr(sbio = BIO_pop(abio))
182         || !TEST_true(BIO_read_ex(sbio, read_buffer, sizeof(read_buffer), &bytes))
183         || !TEST_size_t_eq(bytes, SOCKET_DATA_LEN)
184         || !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
185         sockerr = get_last_socket_error();
186         goto err;
187     }
188 
189 success:
190     sockerr = 0;
191     ret = 1;
192 
193 err:
194     if (sockerr != 0) {
195         const char *errstr = strerror(sockerr);
196 
197         if (errstr != NULL)
198             BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
199     }
200     BIO_free(cbio);
201     BIO_free(abio);
202     BIO_free(sbio);
203     return ret;
204 }
205 
test_fd_tfo(int idx)206 static int test_fd_tfo(int idx)
207 {
208     struct sockaddr_storage sstorage;
209     socklen_t slen;
210     struct addrinfo *ai = NULL;
211     struct addrinfo hints;
212     int ret = 0;
213     int cfd = -1; /* client socket */
214     int afd = -1; /* accept socket */
215     int sfd = -1; /* server accepted socket */
216     BIO_ADDR *baddr = NULL;
217     char read_buffer[20];
218     int bytes_read;
219     int server_flags = BIO_SOCK_NONBLOCK;
220     int client_flags = BIO_SOCK_NONBLOCK;
221     int sockerr = 0;
222     unsigned short port;
223     void *addr;
224     size_t addrlen;
225 
226     switch (idx) {
227     default:
228     case 0:
229         break;
230     case 1:
231     case 2:
232         server_flags |= BIO_SOCK_TFO;
233         client_flags |= BIO_SOCK_TFO;
234         break;
235     case 3:
236         client_flags |= BIO_SOCK_TFO;
237         break;
238     case 4:
239         server_flags |= BIO_SOCK_TFO;
240         break;
241     }
242 
243     /* ADDRESS SETUP */
244     memset(&hints, 0, sizeof(hints));
245 #if OPENSSL_USE_IPV6
246     hints.ai_family = AF_UNSPEC;
247 #else
248     hints.ai_family = AF_INET;
249 #endif
250     hints.ai_socktype = SOCK_STREAM;
251     if (!TEST_int_eq(getaddrinfo(NULL, "0", &hints, &ai), 0))
252         goto err;
253 
254     switch (ai->ai_family) {
255     case AF_INET:
256         port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
257         addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr;
258         addrlen = sizeof(((struct sockaddr_in *)ai->ai_addr)->sin_addr);
259         BIO_printf(bio_err, "Using IPv4\n");
260         break;
261 #if OPENSSL_USE_IPV6
262     case AF_INET6:
263         port = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port;
264         addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
265         addrlen = sizeof(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
266         BIO_printf(bio_err, "Using IPv6\n");
267         break;
268 #endif
269     default:
270         BIO_printf(bio_err, "Unknown address family %d\n", ai->ai_family);
271         goto err;
272     }
273 
274     if (!TEST_ptr(baddr = BIO_ADDR_new())
275         || !TEST_true(BIO_ADDR_rawmake(baddr, ai->ai_family, addr, addrlen, port)))
276         goto err;
277 
278     /* ACCEPT SOCKET */
279 
280     if (!TEST_int_ge(afd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0)
281         || !TEST_true(BIO_listen(afd, baddr, server_flags)))
282         goto err;
283 
284     /* UPDATE ADDRESS WITH PORT */
285     slen = sizeof(sstorage);
286     if (!TEST_int_ge(getsockname(afd, (struct sockaddr *)&sstorage, &slen), 0))
287         goto err;
288 
289     switch (sstorage.ss_family) {
290     case AF_INET:
291         port = ((struct sockaddr_in *)&sstorage)->sin_port;
292         addr = &((struct sockaddr_in *)&sstorage)->sin_addr;
293         addrlen = sizeof(((struct sockaddr_in *)&sstorage)->sin_addr);
294         break;
295 #if OPENSSL_USE_IPV6
296     case AF_INET6:
297         port = ((struct sockaddr_in6 *)&sstorage)->sin6_port;
298         addr = &((struct sockaddr_in6 *)&sstorage)->sin6_addr;
299         addrlen = sizeof(((struct sockaddr_in6 *)&sstorage)->sin6_addr);
300         break;
301 #endif
302     default:
303         goto err;
304     }
305 
306     if (!TEST_true(BIO_ADDR_rawmake(baddr, sstorage.ss_family, addr, addrlen, port)))
307         goto err;
308 
309     /* CLIENT SOCKET */
310     if (!TEST_int_ge(cfd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0))
311         goto err;
312 
313     /* FIRST ACCEPT: no connection should be established */
314     sfd = BIO_accept_ex(afd, NULL, 0);
315     if (sfd == -1) {
316         sockerr = get_last_socket_error();
317         /* Note: Windows would hit WSAEWOULDBLOCK */
318         if (sockerr != EAGAIN) {
319             BIO_printf(bio_err, "Error: failed without EAGAIN\n");
320             goto err;
321         }
322     } else {
323         BIO_printf(bio_err, "Error: accepted unknown connection\n");
324         goto err;
325     }
326 
327     /* CONNECT ATTEMPT: different behavior based on TFO support */
328     if (!BIO_connect(cfd, baddr, client_flags)) {
329         sockerr = get_last_socket_error();
330         if (sockerr == EOPNOTSUPP) {
331             BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
332             goto success;
333         } else {
334             /* Note: Windows would hit WSAEWOULDBLOCK */
335             if (sockerr != EINPROGRESS) {
336                 BIO_printf(bio_err, "Error: failed without EINPROGRESS\n");
337                 goto err;
338             }
339         }
340     }
341 
342     /* macOS needs some time for this to happen, so put in a select */
343     if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
344         sockerr = get_last_socket_error();
345         BIO_printf(bio_err, "Error: socket wait failed\n");
346         goto err;
347     }
348 
349     /* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
350     sfd = BIO_accept_ex(afd, NULL, 0);
351     if (sfd == -1) {
352         sockerr = get_last_socket_error();
353         /* Note: Windows would hit WSAEWOULDBLOCK */
354         if (sockerr != EAGAIN) {
355             BIO_printf(bio_err, "Error: failed without EAGAIN\n");
356             goto err;
357         }
358     } else {
359         if (idx == 0)
360             BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
361         else if (idx == 1)
362             BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
363         else if (idx == 4)
364             BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
365         else
366             BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
367         goto success;
368     }
369 
370     /* SEND DATA: this should establish the actual TFO connection */
371 #ifdef OSSL_TFO_SENDTO
372     if (!TEST_int_ge(sendto(cfd, SOCKET_DATA, SOCKET_DATA_LEN, OSSL_TFO_SENDTO,
373                          (struct sockaddr *)&sstorage, slen),
374             0)) {
375         sockerr = get_last_socket_error();
376         goto err;
377     }
378 #else
379     if (!TEST_int_ge(writesocket(cfd, SOCKET_DATA, SOCKET_DATA_LEN), 0)) {
380         sockerr = get_last_socket_error();
381         goto err;
382     }
383 #endif
384 
385     /* macOS needs some time for this to happen, so put in a select */
386     if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
387         sockerr = get_last_socket_error();
388         BIO_printf(bio_err, "Error: socket wait failed\n");
389         goto err;
390     }
391 
392     /* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
393     sfd = BIO_accept_ex(afd, NULL, 0);
394     if (sfd == -1) {
395         sockerr = get_last_socket_error();
396         BIO_printf(bio_err, "Error: socket not accepted\n");
397         goto err;
398     }
399     BIO_printf(bio_err, "Success: Server accepted socket after write\n");
400     bytes_read = readsocket(sfd, read_buffer, sizeof(read_buffer));
401     if (!TEST_int_eq(bytes_read, SOCKET_DATA_LEN)
402         || !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
403         sockerr = get_last_socket_error();
404         goto err;
405     }
406 
407 success:
408     sockerr = 0;
409     ret = 1;
410 
411 err:
412     if (sockerr != 0) {
413         const char *errstr = strerror(sockerr);
414 
415         if (errstr != NULL)
416             BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
417     }
418     if (ai != NULL)
419         freeaddrinfo(ai);
420     BIO_ADDR_free(baddr);
421     BIO_closesocket(cfd);
422     BIO_closesocket(sfd);
423     BIO_closesocket(afd);
424     return ret;
425 }
426 #endif
427 
setup_tests(void)428 int setup_tests(void)
429 {
430 #if !defined(OPENSSL_NO_TFO) && defined(GOOD_OS)
431     ADD_ALL_TESTS(test_bio_tfo, 5);
432     ADD_ALL_TESTS(test_fd_tfo, 5);
433 #endif
434     return 1;
435 }
436