1 | /*
|
---|
2 | * Copyright 2022 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 |
|
---|
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 | || !TEST_true(BIO_set_nbio_accept(abio, 1))
|
---|
85 | || !TEST_true(BIO_set_tfo_accept(abio, server_tfo))
|
---|
86 | || !TEST_int_gt(BIO_do_accept(abio), 0)
|
---|
87 | || !TEST_ptr(port = BIO_get_accept_port(abio))) {
|
---|
88 | sockerr = get_last_socket_error();
|
---|
89 | goto err;
|
---|
90 | }
|
---|
91 |
|
---|
92 | /* Note: first BIO_do_accept will basically do the bind/listen */
|
---|
93 |
|
---|
94 | /* CLIENT SOCKET */
|
---|
95 | if (!TEST_ptr(cbio = BIO_new_connect("localhost"))
|
---|
96 | || !TEST_long_gt(BIO_set_conn_port(cbio, port), 0)
|
---|
97 | || !TEST_long_gt(BIO_set_nbio(cbio, 1), 0)
|
---|
98 | || !TEST_long_gt(BIO_set_tfo(cbio, client_tfo), 0)) {
|
---|
99 | sockerr = get_last_socket_error();
|
---|
100 | goto err;
|
---|
101 | }
|
---|
102 |
|
---|
103 | /* FIRST ACCEPT: no connection should be established */
|
---|
104 | if (BIO_do_accept(abio) <= 0) {
|
---|
105 | if (!BIO_should_retry(abio)) {
|
---|
106 | sockerr = get_last_socket_error();
|
---|
107 | BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
---|
108 | goto err;
|
---|
109 | }
|
---|
110 | } else {
|
---|
111 | sbio = BIO_pop(abio);
|
---|
112 | BIO_printf(bio_err, "Error: accepted unknown connection\n");
|
---|
113 | goto err;
|
---|
114 | }
|
---|
115 |
|
---|
116 | /* CONNECT ATTEMPT: different behavior based on TFO support */
|
---|
117 | if (BIO_do_connect(cbio) <= 0) {
|
---|
118 | sockerr = get_last_socket_error();
|
---|
119 | if (sockerr == EOPNOTSUPP) {
|
---|
120 | BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
|
---|
121 | goto success;
|
---|
122 | } else if (sockerr != EINPROGRESS) {
|
---|
123 | BIO_printf(bio_err, "Error: failed without EINPROGRESSn");
|
---|
124 | goto err;
|
---|
125 | }
|
---|
126 | }
|
---|
127 |
|
---|
128 | /* macOS needs some time for this to happen, so put in a select */
|
---|
129 | if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
|
---|
130 | sockerr = get_last_socket_error();
|
---|
131 | BIO_printf(bio_err, "Error: socket wait failed\n");
|
---|
132 | goto err;
|
---|
133 | }
|
---|
134 |
|
---|
135 | /* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
|
---|
136 | if (BIO_do_accept(abio) <= 0) {
|
---|
137 | if (!BIO_should_retry(abio)) {
|
---|
138 | sockerr = get_last_socket_error();
|
---|
139 | BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
---|
140 | goto err;
|
---|
141 | }
|
---|
142 | } else {
|
---|
143 | if (idx == 0)
|
---|
144 | BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
|
---|
145 | else if (idx == 1)
|
---|
146 | BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
|
---|
147 | else if (idx == 4)
|
---|
148 | BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
|
---|
149 | else
|
---|
150 | BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
|
---|
151 | sbio = BIO_pop(abio);
|
---|
152 | goto success;
|
---|
153 | }
|
---|
154 |
|
---|
155 | /* SEND DATA: this should establish the actual TFO connection */
|
---|
156 | if (!TEST_true(BIO_write_ex(cbio, SOCKET_DATA, SOCKET_DATA_LEN, &bytes))) {
|
---|
157 | sockerr = get_last_socket_error();
|
---|
158 | goto err;
|
---|
159 | }
|
---|
160 |
|
---|
161 | /* macOS needs some time for this to happen, so put in a select */
|
---|
162 | if (!TEST_int_ge(BIO_wait(abio, time(NULL) + 2, 0), 0)) {
|
---|
163 | sockerr = get_last_socket_error();
|
---|
164 | BIO_printf(bio_err, "Error: socket wait failed\n");
|
---|
165 | goto err;
|
---|
166 | }
|
---|
167 |
|
---|
168 | /* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
|
---|
169 | if (BIO_do_accept(abio) <= 0) {
|
---|
170 | sockerr = get_last_socket_error();
|
---|
171 | BIO_printf(bio_err, "Error: socket not accepted\n");
|
---|
172 | goto err;
|
---|
173 | }
|
---|
174 | BIO_printf(bio_err, "Success: Server accepted socket after write\n");
|
---|
175 | if (!TEST_ptr(sbio = BIO_pop(abio))
|
---|
176 | || !TEST_true(BIO_read_ex(sbio, read_buffer, sizeof(read_buffer), &bytes))
|
---|
177 | || !TEST_size_t_eq(bytes, SOCKET_DATA_LEN)
|
---|
178 | || !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
|
---|
179 | sockerr = get_last_socket_error();
|
---|
180 | goto err;
|
---|
181 | }
|
---|
182 |
|
---|
183 | success:
|
---|
184 | sockerr = 0;
|
---|
185 | ret = 1;
|
---|
186 |
|
---|
187 | err:
|
---|
188 | if (sockerr != 0) {
|
---|
189 | const char *errstr = strerror(sockerr);
|
---|
190 |
|
---|
191 | if (errstr != NULL)
|
---|
192 | BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
|
---|
193 | }
|
---|
194 | BIO_free(cbio);
|
---|
195 | BIO_free(abio);
|
---|
196 | BIO_free(sbio);
|
---|
197 | return ret;
|
---|
198 | }
|
---|
199 |
|
---|
200 | static int test_fd_tfo(int idx)
|
---|
201 | {
|
---|
202 | struct sockaddr_storage sstorage;
|
---|
203 | socklen_t slen;
|
---|
204 | struct addrinfo *ai = NULL;
|
---|
205 | struct addrinfo hints;
|
---|
206 | int ret = 0;
|
---|
207 | int cfd = -1; /* client socket */
|
---|
208 | int afd = -1; /* accept socket */
|
---|
209 | int sfd = -1; /* server accepted socket */
|
---|
210 | BIO_ADDR *baddr = NULL;
|
---|
211 | char read_buffer[20];
|
---|
212 | int bytes_read;
|
---|
213 | int server_flags = BIO_SOCK_NONBLOCK;
|
---|
214 | int client_flags = BIO_SOCK_NONBLOCK;
|
---|
215 | int sockerr = 0;
|
---|
216 | unsigned short port;
|
---|
217 | void *addr;
|
---|
218 | size_t addrlen;
|
---|
219 |
|
---|
220 | switch (idx) {
|
---|
221 | default:
|
---|
222 | case 0:
|
---|
223 | break;
|
---|
224 | case 1:
|
---|
225 | case 2:
|
---|
226 | server_flags |= BIO_SOCK_TFO;
|
---|
227 | client_flags |= BIO_SOCK_TFO;
|
---|
228 | break;
|
---|
229 | case 3:
|
---|
230 | client_flags |= BIO_SOCK_TFO;
|
---|
231 | break;
|
---|
232 | case 4:
|
---|
233 | server_flags |= BIO_SOCK_TFO;
|
---|
234 | break;
|
---|
235 | }
|
---|
236 |
|
---|
237 | /* ADDRESS SETUP */
|
---|
238 | memset(&hints, 0, sizeof(hints));
|
---|
239 | hints.ai_family = AF_UNSPEC;
|
---|
240 | hints.ai_socktype = SOCK_STREAM;
|
---|
241 | if (!TEST_int_eq(getaddrinfo(NULL, "0", &hints, &ai), 0))
|
---|
242 | goto err;
|
---|
243 |
|
---|
244 | switch (ai->ai_family) {
|
---|
245 | case AF_INET:
|
---|
246 | port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
|
---|
247 | addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr;
|
---|
248 | addrlen = sizeof(((struct sockaddr_in *)ai->ai_addr)->sin_addr);
|
---|
249 | BIO_printf(bio_err, "Using IPv4\n");
|
---|
250 | break;
|
---|
251 | case AF_INET6:
|
---|
252 | port = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port;
|
---|
253 | addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
|
---|
254 | addrlen = sizeof(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
|
---|
255 | BIO_printf(bio_err, "Using IPv6\n");
|
---|
256 | break;
|
---|
257 | default:
|
---|
258 | BIO_printf(bio_err, "Unknown address family %d\n", ai->ai_family);
|
---|
259 | goto err;
|
---|
260 | }
|
---|
261 |
|
---|
262 | if (!TEST_ptr(baddr = BIO_ADDR_new())
|
---|
263 | || !TEST_true(BIO_ADDR_rawmake(baddr, ai->ai_family, addr, addrlen, port)))
|
---|
264 | goto err;
|
---|
265 |
|
---|
266 | /* ACCEPT SOCKET */
|
---|
267 |
|
---|
268 | if (!TEST_int_ge(afd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0)
|
---|
269 | || !TEST_true(BIO_listen(afd, baddr, server_flags)))
|
---|
270 | goto err;
|
---|
271 |
|
---|
272 | /* UPDATE ADDRESS WITH PORT */
|
---|
273 | slen = sizeof(sstorage);
|
---|
274 | if (!TEST_int_ge(getsockname(afd, (struct sockaddr *)&sstorage, &slen), 0))
|
---|
275 | goto err;
|
---|
276 |
|
---|
277 | switch (sstorage.ss_family) {
|
---|
278 | case AF_INET:
|
---|
279 | port = ((struct sockaddr_in *)&sstorage)->sin_port;
|
---|
280 | addr = &((struct sockaddr_in *)&sstorage)->sin_addr;
|
---|
281 | addrlen = sizeof(((struct sockaddr_in *)&sstorage)->sin_addr);
|
---|
282 | break;
|
---|
283 | case AF_INET6:
|
---|
284 | port = ((struct sockaddr_in6 *)&sstorage)->sin6_port;
|
---|
285 | addr = &((struct sockaddr_in6 *)&sstorage)->sin6_addr;
|
---|
286 | addrlen = sizeof(((struct sockaddr_in6 *)&sstorage)->sin6_addr);
|
---|
287 | break;
|
---|
288 | default:
|
---|
289 | goto err;
|
---|
290 | }
|
---|
291 |
|
---|
292 | if(!TEST_true(BIO_ADDR_rawmake(baddr, sstorage.ss_family, addr, addrlen, port)))
|
---|
293 | goto err;
|
---|
294 |
|
---|
295 | /* CLIENT SOCKET */
|
---|
296 | if (!TEST_int_ge(cfd = BIO_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, 0), 0))
|
---|
297 | goto err;
|
---|
298 |
|
---|
299 | /* FIRST ACCEPT: no connection should be established */
|
---|
300 | sfd = BIO_accept_ex(afd, NULL, 0);
|
---|
301 | if (sfd == -1) {
|
---|
302 | sockerr = get_last_socket_error();
|
---|
303 | /* Note: Windows would hit WSAEWOULDBLOCK */
|
---|
304 | if (sockerr != EAGAIN) {
|
---|
305 | BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
---|
306 | goto err;
|
---|
307 | }
|
---|
308 | } else {
|
---|
309 | BIO_printf(bio_err, "Error: accepted unknown connection\n");
|
---|
310 | goto err;
|
---|
311 | }
|
---|
312 |
|
---|
313 | /* CONNECT ATTEMPT: different behavior based on TFO support */
|
---|
314 | if (!BIO_connect(cfd, baddr, client_flags)) {
|
---|
315 | sockerr = get_last_socket_error();
|
---|
316 | if (sockerr == EOPNOTSUPP) {
|
---|
317 | BIO_printf(bio_err, "Skip: TFO not enabled/supported for client\n");
|
---|
318 | goto success;
|
---|
319 | } else {
|
---|
320 | /* Note: Windows would hit WSAEWOULDBLOCK */
|
---|
321 | if (sockerr != EINPROGRESS) {
|
---|
322 | BIO_printf(bio_err, "Error: failed without EINPROGRESS\n");
|
---|
323 | goto err;
|
---|
324 | }
|
---|
325 | }
|
---|
326 | }
|
---|
327 |
|
---|
328 | /* macOS needs some time for this to happen, so put in a select */
|
---|
329 | if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
|
---|
330 | sockerr = get_last_socket_error();
|
---|
331 | BIO_printf(bio_err, "Error: socket wait failed\n");
|
---|
332 | goto err;
|
---|
333 | }
|
---|
334 |
|
---|
335 | /* SECOND ACCEPT: if TFO is supported, this will still fail until data is sent */
|
---|
336 | sfd = BIO_accept_ex(afd, NULL, 0);
|
---|
337 | if (sfd == -1) {
|
---|
338 | sockerr = get_last_socket_error();
|
---|
339 | /* Note: Windows would hit WSAEWOULDBLOCK */
|
---|
340 | if (sockerr != EAGAIN) {
|
---|
341 | BIO_printf(bio_err, "Error: failed without EAGAIN\n");
|
---|
342 | goto err;
|
---|
343 | }
|
---|
344 | } else {
|
---|
345 | if (idx == 0)
|
---|
346 | BIO_printf(bio_err, "Success: non-TFO connection accepted without data\n");
|
---|
347 | else if (idx == 1)
|
---|
348 | BIO_printf(bio_err, "Ignore: connection accepted before data, possibly no TFO cookie, or TFO may not be enabled\n");
|
---|
349 | else if (idx == 4)
|
---|
350 | BIO_printf(bio_err, "Success: connection accepted before data, client TFO is disabled\n");
|
---|
351 | else
|
---|
352 | BIO_printf(bio_err, "Warning: connection accepted before data, TFO may not be enabled\n");
|
---|
353 | goto success;
|
---|
354 | }
|
---|
355 |
|
---|
356 | /* SEND DATA: this should establish the actual TFO connection */
|
---|
357 | #ifdef OSSL_TFO_SENDTO
|
---|
358 | if (!TEST_int_ge(sendto(cfd, SOCKET_DATA, SOCKET_DATA_LEN, OSSL_TFO_SENDTO,
|
---|
359 | (struct sockaddr *)&sstorage, slen), 0)) {
|
---|
360 | sockerr = get_last_socket_error();
|
---|
361 | goto err;
|
---|
362 | }
|
---|
363 | #else
|
---|
364 | if (!TEST_int_ge(writesocket(cfd, SOCKET_DATA, SOCKET_DATA_LEN), 0)) {
|
---|
365 | sockerr = get_last_socket_error();
|
---|
366 | goto err;
|
---|
367 | }
|
---|
368 | #endif
|
---|
369 |
|
---|
370 | /* macOS needs some time for this to happen, so put in a select */
|
---|
371 | if (!TEST_int_ge(BIO_socket_wait(afd, 1, time(NULL) + 2), 0)) {
|
---|
372 | sockerr = get_last_socket_error();
|
---|
373 | BIO_printf(bio_err, "Error: socket wait failed\n");
|
---|
374 | goto err;
|
---|
375 | }
|
---|
376 |
|
---|
377 | /* FINAL ACCEPT: if TFO is enabled, socket should be accepted at *this* point */
|
---|
378 | sfd = BIO_accept_ex(afd, NULL, 0);
|
---|
379 | if (sfd == -1) {
|
---|
380 | sockerr = get_last_socket_error();
|
---|
381 | BIO_printf(bio_err, "Error: socket not accepted\n");
|
---|
382 | goto err;
|
---|
383 | }
|
---|
384 | BIO_printf(bio_err, "Success: Server accepted socket after write\n");
|
---|
385 | bytes_read = readsocket(sfd, read_buffer, sizeof(read_buffer));
|
---|
386 | if (!TEST_int_eq(bytes_read, SOCKET_DATA_LEN)
|
---|
387 | || !TEST_strn_eq(read_buffer, SOCKET_DATA, SOCKET_DATA_LEN)) {
|
---|
388 | sockerr = get_last_socket_error();
|
---|
389 | goto err;
|
---|
390 | }
|
---|
391 |
|
---|
392 | success:
|
---|
393 | sockerr = 0;
|
---|
394 | ret = 1;
|
---|
395 |
|
---|
396 | err:
|
---|
397 | if (sockerr != 0) {
|
---|
398 | const char *errstr = strerror(sockerr);
|
---|
399 |
|
---|
400 | if (errstr != NULL)
|
---|
401 | BIO_printf(bio_err, "last errno: %d=%s\n", sockerr, errstr);
|
---|
402 | }
|
---|
403 | if (ai != NULL)
|
---|
404 | freeaddrinfo(ai);
|
---|
405 | BIO_ADDR_free(baddr);
|
---|
406 | BIO_closesocket(cfd);
|
---|
407 | BIO_closesocket(sfd);
|
---|
408 | BIO_closesocket(afd);
|
---|
409 | return ret;
|
---|
410 | }
|
---|
411 | #endif
|
---|
412 |
|
---|
413 | int setup_tests(void)
|
---|
414 | {
|
---|
415 | #if !defined(OPENSSL_NO_TFO) && defined(GOOD_OS)
|
---|
416 | ADD_ALL_TESTS(test_bio_tfo, 5);
|
---|
417 | ADD_ALL_TESTS(test_fd_tfo, 5);
|
---|
418 | #endif
|
---|
419 | return 1;
|
---|
420 | }
|
---|