xref: /illumos-gate/usr/src/test/os-tests/tests/tcpsig/tcpsig.c (revision 851677fbe21ab9cad62cea9e299167ea661fd1c8)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * Basic set of tests for TCP_MD5SIG. The main design of this is to spin up
18  * connections on localhost that walk through different options and confirm
19  * that traffic either flows or is dropped according to the configuration.
20  */
21 
22 #include <err.h>
23 #include <port.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <netinet/tcp.h>
29 #include <arpa/inet.h>
30 #include <sys/sysmacros.h>
31 #include <stdbool.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/debug.h>
36 
37 static hrtime_t sock_to = MSEC2NSEC(100); /* ms in ns */
38 static const uint32_t msgdata = 0x7777;
39 
40 /*
41  * Port setup - see tcpsig_init
42  */
43 
44 /* No SAs are configured */
45 #define	PORT_NOSA	24134
46 /* SAs exist in both directions, and the authentication keys match */
47 #define	PORT_BIDIR	24135
48 /* SAs exist in both directions, but the authentication keys don't match */
49 #define	PORT_MISMATCH	24136
50 /* A single SA exists in the outbound direction, none for inbound */
51 #define	PORT_OBSA	24137
52 /* A single SA exists in the inbound direction, none for outbound */
53 #define	PORT_IBSA	24138
54 
55 typedef enum {
56 	TCPSIG_SENDRECV,
57 	TCPSIG_NOCONNECT,
58 	TCPSIG_CONNREFUSED,
59 	TCPSIG_NODATA
60 } tcpsig_pass_t;
61 
62 typedef struct {
63 	const char		*tt_desc;
64 	const int		tt_domain;
65 	const uint16_t		tt_port;
66 	const bool		tt_enable_src;
67 	const bool		tt_enable_dst;
68 	const tcpsig_pass_t	tt_pass;
69 } tcpsig_test_t;
70 
71 static const tcpsig_test_t tcpsig_tests[] = {
72 	/* Tests using the port that (hopefully) has no SAs configured */
73 	{
74 		.tt_desc = "IPv4 NOSA with MD5 enabled on both sides",
75 		.tt_domain = PF_INET,
76 		.tt_port = PORT_NOSA,
77 		.tt_enable_src = true,
78 		.tt_enable_dst = true,
79 		.tt_pass = TCPSIG_CONNREFUSED
80 	}, {
81 		.tt_desc = "IPv4 NOSA with MD5 disabled on both sides",
82 		.tt_domain = PF_INET,
83 		.tt_port = PORT_NOSA,
84 		.tt_enable_src = false,
85 		.tt_enable_dst = false,
86 		.tt_pass = TCPSIG_SENDRECV
87 	}, {
88 		.tt_desc = "IPv4 NOSA with MD5 enabled on src only",
89 		.tt_domain = PF_INET,
90 		.tt_port = PORT_NOSA,
91 		.tt_enable_src = true,
92 		.tt_enable_dst = false,
93 		.tt_pass = TCPSIG_CONNREFUSED
94 	}, {
95 		.tt_desc = "IPv4 NOSA with MD5 enabled on dst only",
96 		.tt_domain = PF_INET,
97 		.tt_port = PORT_NOSA,
98 		.tt_enable_src = false,
99 		.tt_enable_dst = true,
100 		.tt_pass = TCPSIG_NOCONNECT
101 	},
102 	{
103 		.tt_desc = "IPv6 NOSA with MD5 enabled on both sides",
104 		.tt_domain = PF_INET6,
105 		.tt_port = PORT_NOSA,
106 		.tt_enable_src = true,
107 		.tt_enable_dst = true,
108 		.tt_pass = TCPSIG_CONNREFUSED
109 	}, {
110 		.tt_desc = "IPv6 NOSA with MD5 disabled on both sides",
111 		.tt_domain = PF_INET6,
112 		.tt_port = PORT_NOSA,
113 		.tt_enable_src = false,
114 		.tt_enable_dst = false,
115 		.tt_pass = TCPSIG_SENDRECV
116 	}, {
117 		.tt_desc = "IPv6 NOSA with MD5 enabled on src only",
118 		.tt_domain = PF_INET6,
119 		.tt_port = PORT_NOSA,
120 		.tt_enable_src = true,
121 		.tt_enable_dst = false,
122 		.tt_pass = TCPSIG_CONNREFUSED
123 	}, {
124 		.tt_desc = "IPv6 NOSA with MD5 enabled on dst only",
125 		.tt_domain = PF_INET6,
126 		.tt_port = PORT_NOSA,
127 		.tt_enable_src = false,
128 		.tt_enable_dst = true,
129 		.tt_pass = TCPSIG_NOCONNECT
130 	},
131 	/* Tests using the port that has bi-directional SAs configured */
132 	{
133 		.tt_desc = "IPv4 BIDIR with MD5 enabled on both sides",
134 		.tt_domain = PF_INET,
135 		.tt_port = PORT_BIDIR,
136 		.tt_enable_src = true,
137 		.tt_enable_dst = true,
138 		.tt_pass = TCPSIG_SENDRECV
139 	}, {
140 		.tt_desc = "IPv4 BIDIR with MD5 disabled on both sides",
141 		.tt_domain = PF_INET,
142 		.tt_port = PORT_BIDIR,
143 		.tt_enable_src = false,
144 		.tt_enable_dst = false,
145 		.tt_pass = TCPSIG_SENDRECV
146 	}, {
147 		.tt_desc = "IPv4 BIDIR with MD5 enabled on src only",
148 		.tt_domain = PF_INET,
149 		.tt_port = PORT_BIDIR,
150 		.tt_enable_src = true,
151 		.tt_enable_dst = false,
152 		.tt_pass = TCPSIG_NOCONNECT
153 	}, {
154 		.tt_desc = "IPv4 BIDIR with MD5 enabled on dst only",
155 		.tt_domain = PF_INET,
156 		.tt_port = PORT_BIDIR,
157 		.tt_enable_src = false,
158 		.tt_enable_dst = true,
159 		.tt_pass = TCPSIG_NOCONNECT
160 	}, {
161 		.tt_desc = "IPv6 BIDIR with MD5 enabled on both sides",
162 		.tt_domain = PF_INET6,
163 		.tt_port = PORT_BIDIR,
164 		.tt_enable_src = true,
165 		.tt_enable_dst = true,
166 		.tt_pass = TCPSIG_SENDRECV
167 	}, {
168 		.tt_desc = "IPv6 BIDIR with MD5 disabled on both sides",
169 		.tt_domain = PF_INET6,
170 		.tt_port = PORT_BIDIR,
171 		.tt_enable_src = false,
172 		.tt_enable_dst = false,
173 		.tt_pass = TCPSIG_SENDRECV
174 	}, {
175 		.tt_desc = "IPv6 BIDIR with MD5 enabled on src only",
176 		.tt_domain = PF_INET6,
177 		.tt_port = PORT_BIDIR,
178 		.tt_enable_src = true,
179 		.tt_enable_dst = false,
180 		.tt_pass = TCPSIG_NOCONNECT
181 	}, {
182 		.tt_desc = "IPv6 BIDIR with MD5 enabled on dst only",
183 		.tt_domain = PF_INET6,
184 		.tt_port = PORT_BIDIR,
185 		.tt_enable_src = false,
186 		.tt_enable_dst = true,
187 		.tt_pass = TCPSIG_NOCONNECT
188 	},
189 	/* Tests using the port with mismatching SA keys */
190 	{
191 		/*
192 		 * Both sides of the connection have access to the two
193 		 * SAs and will use the correct key depending on the direction
194 		 * of the traffic. We therefore expect this to succeed.
195 		 * `tcpdump -M` can be used to verify that a different key is
196 		 * being used in each direction.
197 		 */
198 		.tt_desc = "IPv4 MISMATCH with MD5 enabled on both sides",
199 		.tt_domain = PF_INET,
200 		.tt_port = PORT_MISMATCH,
201 		.tt_enable_src = true,
202 		.tt_enable_dst = true,
203 		.tt_pass = TCPSIG_SENDRECV
204 	}, {
205 		.tt_desc = "IPv4 MISMATCH with MD5 disabled on both sides",
206 		.tt_domain = PF_INET,
207 		.tt_port = PORT_MISMATCH,
208 		.tt_enable_src = false,
209 		.tt_enable_dst = false,
210 		.tt_pass = TCPSIG_SENDRECV
211 	}, {
212 		.tt_desc = "IPv4 MISMATCH with MD5 enabled on src only",
213 		.tt_domain = PF_INET,
214 		.tt_port = PORT_MISMATCH,
215 		.tt_enable_src = true,
216 		.tt_enable_dst = false,
217 		.tt_pass = TCPSIG_NOCONNECT
218 	}, {
219 		.tt_desc = "IPv4 MISMATCH with MD5 enabled on dst only",
220 		.tt_domain = PF_INET,
221 		.tt_port = PORT_MISMATCH,
222 		.tt_enable_src = false,
223 		.tt_enable_dst = true,
224 		.tt_pass = TCPSIG_NOCONNECT
225 	}, {
226 		.tt_desc = "IPv6 MISMATCH with MD5 enabled on both sides",
227 		.tt_domain = PF_INET6,
228 		.tt_port = PORT_MISMATCH,
229 		.tt_enable_src = true,
230 		.tt_enable_dst = true,
231 		.tt_pass = TCPSIG_SENDRECV
232 	}, {
233 		.tt_desc = "IPv6 MISMATCH with MD5 disabled on both sides",
234 		.tt_domain = PF_INET6,
235 		.tt_port = PORT_MISMATCH,
236 		.tt_enable_src = false,
237 		.tt_enable_dst = false,
238 		.tt_pass = TCPSIG_SENDRECV
239 	}, {
240 		.tt_desc = "IPv6 MISMATCH with MD5 enabled on src only",
241 		.tt_domain = PF_INET6,
242 		.tt_port = PORT_MISMATCH,
243 		.tt_enable_src = true,
244 		.tt_enable_dst = false,
245 		.tt_pass = TCPSIG_NOCONNECT
246 	}, {
247 		.tt_desc = "IPv6 MISMATCH with MD5 enabled on dst only",
248 		.tt_domain = PF_INET6,
249 		.tt_port = PORT_MISMATCH,
250 		.tt_enable_src = false,
251 		.tt_enable_dst = true,
252 		.tt_pass = TCPSIG_NOCONNECT
253 	},
254 	/* Tests using the port with only an outbound SA */
255 	{
256 		.tt_desc = "IPv4 OBSA with MD5 enabled on both sides",
257 		.tt_domain = PF_INET,
258 		.tt_port = PORT_OBSA,
259 		.tt_enable_src = true,
260 		.tt_enable_dst = true,
261 		.tt_pass = TCPSIG_NOCONNECT
262 	}, {
263 		.tt_desc = "IPv4 OBSA with MD5 disabled on both sides",
264 		.tt_domain = PF_INET,
265 		.tt_port = PORT_OBSA,
266 		.tt_enable_src = false,
267 		.tt_enable_dst = false,
268 		.tt_pass = TCPSIG_SENDRECV
269 	}, {
270 		.tt_desc = "IPv4 OBSA with MD5 enabled on src only",
271 		.tt_domain = PF_INET,
272 		.tt_port = PORT_OBSA,
273 		.tt_enable_src = true,
274 		.tt_enable_dst = false,
275 		.tt_pass = TCPSIG_NOCONNECT
276 	}, {
277 		.tt_desc = "IPv4 OBSA with MD5 enabled on dst only",
278 		.tt_domain = PF_INET,
279 		.tt_port = PORT_OBSA,
280 		.tt_enable_src = false,
281 		.tt_enable_dst = true,
282 		.tt_pass = TCPSIG_NOCONNECT
283 	}, {
284 		.tt_desc = "IPv6 OBSA with MD5 enabled on both sides",
285 		.tt_domain = PF_INET6,
286 		.tt_port = PORT_OBSA,
287 		.tt_enable_src = true,
288 		.tt_enable_dst = true,
289 		.tt_pass = TCPSIG_NOCONNECT
290 	}, {
291 		.tt_desc = "IPv6 OBSA with MD5 disabled on both sides",
292 		.tt_domain = PF_INET6,
293 		.tt_port = PORT_OBSA,
294 		.tt_enable_src = false,
295 		.tt_enable_dst = false,
296 		.tt_pass = TCPSIG_SENDRECV
297 	}, {
298 		.tt_desc = "IPv6 OBSA with MD5 enabled on src only",
299 		.tt_domain = PF_INET6,
300 		.tt_port = PORT_OBSA,
301 		.tt_enable_src = true,
302 		.tt_enable_dst = false,
303 		.tt_pass = TCPSIG_NOCONNECT
304 	}, {
305 		.tt_desc = "IPv6 OBSA with MD5 enabled on dst only",
306 		.tt_domain = PF_INET6,
307 		.tt_port = PORT_OBSA,
308 		.tt_enable_src = false,
309 		.tt_enable_dst = true,
310 		.tt_pass = TCPSIG_NOCONNECT
311 	},
312 	/* Tests using the port with only an inbound SA */
313 	{
314 		.tt_desc = "IPv4 IBSA with MD5 enabled on both sides",
315 		.tt_domain = PF_INET,
316 		.tt_port = PORT_IBSA,
317 		.tt_enable_src = true,
318 		.tt_enable_dst = true,
319 		.tt_pass = TCPSIG_CONNREFUSED
320 	}, {
321 		.tt_desc = "IPv4 IBSA with MD5 disabled on both sides",
322 		.tt_domain = PF_INET,
323 		.tt_port = PORT_IBSA,
324 		.tt_enable_src = false,
325 		.tt_enable_dst = false,
326 		.tt_pass = TCPSIG_SENDRECV
327 	}, {
328 		.tt_desc = "IPv4 IBSA with MD5 enabled on src only",
329 		.tt_domain = PF_INET,
330 		.tt_port = PORT_IBSA,
331 		.tt_enable_src = true,
332 		.tt_enable_dst = false,
333 		.tt_pass = TCPSIG_CONNREFUSED
334 	}, {
335 		.tt_desc = "IPv4 IBSA with MD5 enabled on dst only",
336 		.tt_domain = PF_INET,
337 		.tt_port = PORT_IBSA,
338 		.tt_enable_src = false,
339 		.tt_enable_dst = true,
340 		.tt_pass = TCPSIG_NOCONNECT
341 	}, {
342 		.tt_desc = "IPv6 IBSA with MD5 enabled on both sides",
343 		.tt_domain = PF_INET6,
344 		.tt_port = PORT_IBSA,
345 		.tt_enable_src = true,
346 		.tt_enable_dst = true,
347 		.tt_pass = TCPSIG_CONNREFUSED
348 	}, {
349 		.tt_desc = "IPv6 IBSA with MD5 disabled on both sides",
350 		.tt_domain = PF_INET6,
351 		.tt_port = PORT_IBSA,
352 		.tt_enable_src = false,
353 		.tt_enable_dst = false,
354 		.tt_pass = TCPSIG_SENDRECV
355 	}, {
356 		.tt_desc = "IPv6 IBSA with MD5 enabled on src only",
357 		.tt_domain = PF_INET6,
358 		.tt_port = PORT_IBSA,
359 		.tt_enable_src = true,
360 		.tt_enable_dst = false,
361 		.tt_pass = TCPSIG_CONNREFUSED
362 	}, {
363 		.tt_desc = "IPv6 IBSA with MD5 enabled on dst only",
364 		.tt_domain = PF_INET6,
365 		.tt_port = PORT_IBSA,
366 		.tt_enable_src = false,
367 		.tt_enable_dst = true,
368 		.tt_pass = TCPSIG_NOCONNECT
369 	}
370 };
371 
372 static bool
373 tcpsig_bind_dest(const tcpsig_test_t *test, int sock,
374     struct sockaddr_storage *dst)
375 {
376 	socklen_t len;
377 	struct sockaddr_storage addr;
378 
379 	(void) memset(&addr, 0, sizeof (struct sockaddr_storage));
380 
381 	if (test->tt_domain == PF_INET) {
382 		struct sockaddr_in *in = (struct sockaddr_in *)&addr;
383 		in->sin_family = AF_INET;
384 		in->sin_port = htons(test->tt_port);
385 		if (inet_pton(AF_INET, "127.0.0.1", &in->sin_addr) != 1) {
386 			warnx("TEST FAILED: %s: failed to convert 127.0.0.1 "
387 			    "to an IPv4 address", test->tt_desc);
388 			return (false);
389 		}
390 		len = sizeof (struct sockaddr_in);
391 	} else {
392 		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
393 		in6->sin6_family = AF_INET6;
394 		in6->sin6_port = htons(test->tt_port);
395 		if (inet_pton(AF_INET6, "::1", &in6->sin6_addr) != 1) {
396 			warnx("TEST FAILED: %s: failed to convert ::1 "
397 			    "to an IPv6 address", test->tt_desc);
398 			return (false);
399 		}
400 		len = sizeof (struct sockaddr_in6);
401 	}
402 
403 	if (bind(sock, (struct sockaddr *)&addr, len) != 0) {
404 		warn("TEST FAILED: %s: failed to bind listen socket",
405 		    test->tt_desc);
406 		return (false);
407 	}
408 
409 	len = sizeof (struct sockaddr_storage);
410 	if (getsockname(sock, (struct sockaddr *)dst, &len) != 0) {
411 		warn("TEST FAILED: %s: failed to retrieve socket address ",
412 		    test->tt_desc);
413 		return (false);
414 	}
415 
416 	return (true);
417 }
418 
419 /*
420  * Our job is to attempt to connect to the other end with our current settings.
421  * This may not work, so we use our port to get things ready just in case.
422  */
423 static bool
424 tcpsig_connect(const tcpsig_test_t *test, int port, int src, int dst, int *cfd,
425     const struct sockaddr *addr)
426 {
427 	struct timespec to = { .tv_nsec = sock_to };
428 	int namelen = test->tt_domain == PF_INET ? sizeof (struct sockaddr_in) :
429 	    sizeof (struct sockaddr_in6);
430 	int conn;
431 	port_event_t pe;
432 
433 	if (listen(dst, 5) != 0) {
434 		warn("TEST FAILED: %s: failed to listen", test->tt_desc);
435 		return (false);
436 	}
437 
438 	if (connect(src, addr, namelen) != 0 && errno != EINPROGRESS) {
439 		if (errno == ECONNREFUSED &&
440 		    test->tt_pass == TCPSIG_CONNREFUSED) {
441 			(void) printf("TEST PASSED: %s: connection refused\n",
442 			    test->tt_desc);
443 			return (true);
444 		}
445 		warn("TEST FAILED: %s: failed to connect", test->tt_desc);
446 		return (false);
447 	}
448 
449 	if (port_associate(port, PORT_SOURCE_FD, src, POLLOUT, NULL) != 0) {
450 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
451 		    "associate to watch connect", test->tt_desc);
452 	}
453 
454 	if (port_get(port, &pe, &to) != 0) {
455 		if (test->tt_pass == TCPSIG_NOCONNECT) {
456 			(void) printf(
457 			    "TEST PASSED: %s: correctly failed to connect\n",
458 			    test->tt_desc);
459 			return (true);
460 		} else {
461 			warn("TEST FAILED: %s: timed out waiting to connect",
462 			    test->tt_desc);
463 			return (false);
464 		}
465 	}
466 
467 	if ((pe.portev_events & POLLOUT) == 0) {
468 		warnx("TEST FAILED: %s: connect port event doesn't contain "
469 		    "POLLOUT, found 0x%x", test->tt_desc, pe.portev_events);
470 		return (false);
471 	}
472 
473 	/*
474 	 * Now make sure the listen socket is ready.
475 	 */
476 	if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
477 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
478 		    "associate to watch accept", test->tt_desc);
479 	}
480 
481 	if (port_get(port, &pe, &to) != 0) {
482 		warn("TEST FAILED: %s: timed out waiting to accept",
483 		    test->tt_desc);
484 		return (false);
485 	}
486 
487 	if ((pe.portev_events & POLLIN) == 0) {
488 		warnx("TEST FAILED: %s: accept port event doesn't contain "
489 		    "POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
490 		return (false);
491 	}
492 
493 	conn = accept4(dst, NULL, NULL, SOCK_NONBLOCK);
494 	if (conn < 0) {
495 		warn("TEST FAILED: %s: failed to get client connection",
496 		    test->tt_desc);
497 		return (false);
498 	}
499 
500 	if (test->tt_pass != TCPSIG_SENDRECV &&
501 	    test->tt_pass != TCPSIG_NODATA) {
502 		warnx("TEST FAILED: %s: expected connect to fail, but passed",
503 		    test->tt_desc);
504 		return (false);
505 	}
506 
507 	*cfd = conn;
508 	return (true);
509 }
510 
511 /*
512  * Attempt to send data with the tcpsigs set up appropriately. This might fail,
513  * hence our port_associate dance and unfortunately regrettable timeout.
514  */
515 static bool
516 tcpsig_sendrecv(const tcpsig_test_t *test, int port, int src, int dst)
517 {
518 	struct timespec to = { .tv_nsec = sock_to };
519 	port_event_t pe;
520 	uint32_t data;
521 	ssize_t sret;
522 
523 	if (send(src, &msgdata, sizeof (msgdata), MSG_NOSIGNAL) !=
524 	    sizeof (msgdata)) {
525 		warn("TEST FAILED: %s: failed to write message to socket",
526 		    test->tt_desc);
527 	}
528 
529 	if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
530 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
531 		    "associate to watch recv", test->tt_desc);
532 	}
533 
534 	if (port_get(port, &pe, &to) != 0) {
535 		if (test->tt_pass == TCPSIG_NODATA) {
536 			(void) printf("TEST PASSED: %s: timed out waiting "
537 			    "for data\n", test->tt_desc);
538 			return (true);
539 		} else {
540 			warn("TEST FAILED: %s: timed out waiting to recv",
541 			    test->tt_desc);
542 			return (false);
543 		}
544 	}
545 
546 	if ((pe.portev_events & POLLIN) == 0) {
547 		warnx("TEST FAILED: %s: receive port event doesn't contain "
548 		    "POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
549 		return (false);
550 	}
551 
552 	sret = recv(dst, &data, sizeof (data), MSG_DONTWAIT);
553 	if (sret != (ssize_t)sizeof (data)) {
554 		warnx("TEST FAILED: %s: failed to receive data: %zx",
555 		    test->tt_desc, sret);
556 		return (false);
557 	}
558 
559 	if (test->tt_pass != TCPSIG_SENDRECV) {
560 		warnx("TEST FAILED: %s: found data, despite expecting not to",
561 		    test->tt_desc);
562 		return (false);
563 	}
564 
565 	if (data != msgdata) {
566 		warnx("TEST FAILED: %s: data mismatch: expected 0x%x, found "
567 		    "0x%x", test->tt_desc, msgdata, data);
568 		return (false);
569 	}
570 
571 	(void) printf("TEST PASSED: %s: successfully received data\n",
572 	    test->tt_desc);
573 	return (true);
574 }
575 
576 static bool
577 tcpsig_test_one(const tcpsig_test_t *test)
578 {
579 	int src = -1, dst = -1, cfd = -1, port = -1, tdst;
580 	int x;
581 	bool ret = true;
582 	struct sockaddr_storage dst_addr;
583 
584 	if ((port = port_create()) < 0)
585 		err(EXIT_FAILURE, "TEST FAILED: failed to create event port");
586 
587 	src = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
588 	if (src < 0) {
589 		warn("TEST FAILED: %s: failed to create source socket",
590 		    test->tt_desc);
591 		ret = false;
592 		goto cleanup;
593 	}
594 
595 	x = test->tt_enable_src ? 1 : 0;
596 	if (setsockopt(src, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
597 		warn("TEST FAILED: %s: failed to configure src MD5SIG option",
598 		    test->tt_desc);
599 		ret = false;
600 		goto cleanup;
601 	}
602 
603 	dst = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
604 	if (dst < 0) {
605 		warn("TEST FAILED: %s: failed to create destination socket",
606 		    test->tt_desc);
607 		ret = false;
608 		goto cleanup;
609 	}
610 
611 	x = test->tt_enable_dst ? 1 : 0;
612 	if (setsockopt(dst, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
613 		warn("TEST FAILED: %s: failed to configure dst MD5SIG option",
614 		    test->tt_desc);
615 		ret = false;
616 		goto cleanup;
617 	}
618 
619 	if (!tcpsig_bind_dest(test, dst, &dst_addr)) {
620 		ret = false;
621 		goto cleanup;
622 	}
623 
624 	if (!tcpsig_connect(test, port, src, dst, &cfd,
625 	    (struct sockaddr *)&dst_addr)) {
626 		ret = false;
627 		goto cleanup;
628 	}
629 
630 	if (test->tt_pass != TCPSIG_SENDRECV && test->tt_pass != TCPSIG_NODATA)
631 		goto cleanup;
632 
633 	tdst = cfd;
634 
635 	if (!tcpsig_sendrecv(test, port, src, tdst)) {
636 		ret = false;
637 		goto cleanup;
638 	}
639 
640 cleanup:
641 	if (port > -1)
642 		(void) close(port);
643 	if (src > -1) {
644 		(void) shutdown(src, SHUT_RDWR);
645 		(void) close(src);
646 	}
647 	if (dst > -1)
648 		(void) close(dst);
649 	if (cfd > -1)
650 		(void) close(cfd);
651 	return (ret);
652 }
653 
654 int
655 main(int argc, char **argv)
656 {
657 	size_t max = ARRAY_SIZE(tcpsig_tests) - 1;
658 	int ret = EXIT_SUCCESS;
659 
660 	if (argc == 2) {
661 		const char *errstr;
662 		size_t idx;
663 
664 		idx = (size_t)strtonumx(argv[1], 0, max, &errstr, 0);
665 		if (errstr != NULL) {
666 			(void) fprintf(stderr, "Syntax: %s [test number]\n",
667 			    getprogname());
668 			(void) fprintf(stderr,
669 			    "Test number is in the range [0-%u]\n", max);
670 			(void) fprintf(stderr, "\nAvailable tests:\n");
671 			for (size_t i = 0; i <= max; i++) {
672 				(void) fprintf(stderr, "    %5d - %s\n", i,
673 				    tcpsig_tests[i].tt_desc);
674 			}
675 			return (EXIT_FAILURE);
676 		}
677 
678 		if (!tcpsig_test_one(&tcpsig_tests[idx]))
679 			ret = EXIT_FAILURE;
680 	} else {
681 		for (size_t i = 0; i <= max; i++) {
682 			if (!tcpsig_test_one(&tcpsig_tests[i]))
683 				ret = EXIT_FAILURE;
684 		}
685 		if (ret == EXIT_SUCCESS)
686 			(void) printf("All tests passed successfully\n");
687 	}
688 
689 	return (ret);
690 }
691