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