xref: /illumos-gate/usr/src/test/os-tests/tests/tcpsig/tcpsig.c (revision 9d9f94326341c9595a2be9de21b1b5bb335155f8)
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_SENDRECV
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_SENDRECV
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_SENDRECV
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_SENDRECV
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, opt;
431 	unsigned int optlen;
432 	port_event_t pe;
433 
434 	if (listen(dst, 5) != 0) {
435 		warn("TEST FAILED: %s: failed to listen", test->tt_desc);
436 		return (false);
437 	}
438 
439 	if (connect(src, addr, namelen) != 0 && errno != EINPROGRESS) {
440 		if (errno == ECONNREFUSED &&
441 		    test->tt_pass == TCPSIG_CONNREFUSED) {
442 			(void) printf("TEST PASSED: %s: connection refused\n",
443 			    test->tt_desc);
444 			return (true);
445 		}
446 		warn("TEST FAILED: %s: failed to connect", test->tt_desc);
447 		return (false);
448 	}
449 
450 	if (port_associate(port, PORT_SOURCE_FD, src, POLLOUT, NULL) != 0) {
451 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
452 		    "associate to watch connect", test->tt_desc);
453 	}
454 
455 	if (port_get(port, &pe, &to) != 0) {
456 		if (test->tt_pass == TCPSIG_NOCONNECT) {
457 			(void) printf(
458 			    "TEST PASSED: %s: correctly failed to connect\n",
459 			    test->tt_desc);
460 			return (true);
461 		} else {
462 			warn("TEST FAILED: %s: timed out waiting to connect",
463 			    test->tt_desc);
464 			return (false);
465 		}
466 	}
467 
468 	if ((pe.portev_events & POLLOUT) == 0) {
469 		warnx("TEST FAILED: %s: connect port event doesn't contain "
470 		    "POLLOUT, found 0x%x", test->tt_desc, pe.portev_events);
471 		return (false);
472 	}
473 
474 	/*
475 	 * Now make sure the listen socket is ready.
476 	 */
477 	if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
478 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
479 		    "associate to watch accept", test->tt_desc);
480 	}
481 
482 	if (port_get(port, &pe, &to) != 0) {
483 		warn("TEST FAILED: %s: timed out waiting to accept",
484 		    test->tt_desc);
485 		return (false);
486 	}
487 
488 	if ((pe.portev_events & POLLIN) == 0) {
489 		warnx("TEST FAILED: %s: accept port event doesn't contain "
490 		    "POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
491 		return (false);
492 	}
493 
494 	conn = accept4(dst, NULL, NULL, SOCK_NONBLOCK);
495 	if (conn < 0) {
496 		warn("TEST FAILED: %s: failed to get client connection",
497 		    test->tt_desc);
498 		return (false);
499 	}
500 
501 	optlen = sizeof (opt);
502 	if (getsockopt(conn, IPPROTO_TCP, TCP_MD5SIG, &opt, &optlen) != 0) {
503 		warn("TEST FAILED: %s: failed to retrieve accepted socket "
504 		    "TCP_MD5SIG option", test->tt_desc);
505 		return (false);
506 	}
507 
508 	if (optlen != sizeof (opt)) {
509 		warn("TEST FAILED: %s: TCP_MD5SIG option has wrong length %d "
510 		    "(expected %ld).", test->tt_desc, optlen, sizeof (opt));
511 		return (false);
512 	}
513 
514 	/*
515 	 * For tests where the TCP MD5 option is not enabled on the source, but
516 	 * is on the destination, and where we expect the connection to
517 	 * succeed, we also expect that the socket option has been disabled on
518 	 * accept(). Check.
519 	 */
520 	if (test->tt_enable_dst && !test->tt_enable_src &&
521 	    test->tt_pass == TCPSIG_SENDRECV && opt != 0) {
522 		warnx("TEST FAILED: %s: TCP_MD5SIG is set and should not be",
523 		    test->tt_desc);
524 		return (false);
525 	} else if (test->tt_enable_src && opt == 0) {
526 		warnx("TEST FAILED: %s: TCP_MD5SIG is not set and should be",
527 		    test->tt_desc);
528 		return (false);
529 	}
530 
531 	if (test->tt_pass != TCPSIG_SENDRECV &&
532 	    test->tt_pass != TCPSIG_NODATA) {
533 		warnx("TEST FAILED: %s: expected connect to fail, but passed",
534 		    test->tt_desc);
535 		return (false);
536 	}
537 
538 	*cfd = conn;
539 	return (true);
540 }
541 
542 /*
543  * Attempt to send data with the tcpsigs set up appropriately. This might fail,
544  * hence our port_associate dance and unfortunately regrettable timeout.
545  */
546 static bool
547 tcpsig_sendrecv(const tcpsig_test_t *test, int port, int src, int dst)
548 {
549 	struct timespec to = { .tv_nsec = sock_to };
550 	port_event_t pe;
551 	uint32_t data;
552 	ssize_t sret;
553 
554 	if (send(src, &msgdata, sizeof (msgdata), MSG_NOSIGNAL) !=
555 	    sizeof (msgdata)) {
556 		warn("TEST FAILED: %s: failed to write message to socket",
557 		    test->tt_desc);
558 	}
559 
560 	if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
561 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
562 		    "associate to watch recv", test->tt_desc);
563 	}
564 
565 	if (port_get(port, &pe, &to) != 0) {
566 		if (test->tt_pass == TCPSIG_NODATA) {
567 			(void) printf("TEST PASSED: %s: timed out waiting "
568 			    "for data\n", test->tt_desc);
569 			return (true);
570 		} else {
571 			warn("TEST FAILED: %s: timed out waiting to recv",
572 			    test->tt_desc);
573 			return (false);
574 		}
575 	}
576 
577 	if ((pe.portev_events & POLLIN) == 0) {
578 		warnx("TEST FAILED: %s: receive port event doesn't contain "
579 		    "POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
580 		return (false);
581 	}
582 
583 	sret = recv(dst, &data, sizeof (data), MSG_DONTWAIT);
584 	if (sret != (ssize_t)sizeof (data)) {
585 		warnx("TEST FAILED: %s: failed to receive data: %zx",
586 		    test->tt_desc, sret);
587 		return (false);
588 	}
589 
590 	if (test->tt_pass != TCPSIG_SENDRECV) {
591 		warnx("TEST FAILED: %s: found data, despite expecting not to",
592 		    test->tt_desc);
593 		return (false);
594 	}
595 
596 	if (data != msgdata) {
597 		warnx("TEST FAILED: %s: data mismatch: expected 0x%x, found "
598 		    "0x%x", test->tt_desc, msgdata, data);
599 		return (false);
600 	}
601 
602 	(void) printf("TEST PASSED: %s: successfully received data\n",
603 	    test->tt_desc);
604 	return (true);
605 }
606 
607 static bool
608 tcpsig_test_one(const tcpsig_test_t *test)
609 {
610 	int src = -1, dst = -1, cfd = -1, port = -1, tdst;
611 	int x;
612 	bool ret = true;
613 	struct sockaddr_storage dst_addr;
614 
615 	if ((port = port_create()) < 0)
616 		err(EXIT_FAILURE, "TEST FAILED: failed to create event port");
617 
618 	src = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
619 	if (src < 0) {
620 		warn("TEST FAILED: %s: failed to create source socket",
621 		    test->tt_desc);
622 		ret = false;
623 		goto cleanup;
624 	}
625 
626 	x = test->tt_enable_src ? 1 : 0;
627 	if (setsockopt(src, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
628 		warn("TEST FAILED: %s: failed to configure src MD5SIG option",
629 		    test->tt_desc);
630 		ret = false;
631 		goto cleanup;
632 	}
633 
634 	dst = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
635 	if (dst < 0) {
636 		warn("TEST FAILED: %s: failed to create destination socket",
637 		    test->tt_desc);
638 		ret = false;
639 		goto cleanup;
640 	}
641 
642 	x = test->tt_enable_dst ? 1 : 0;
643 	if (setsockopt(dst, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
644 		warn("TEST FAILED: %s: failed to configure dst MD5SIG option",
645 		    test->tt_desc);
646 		ret = false;
647 		goto cleanup;
648 	}
649 
650 	if (!tcpsig_bind_dest(test, dst, &dst_addr)) {
651 		ret = false;
652 		goto cleanup;
653 	}
654 
655 	if (!tcpsig_connect(test, port, src, dst, &cfd,
656 	    (struct sockaddr *)&dst_addr)) {
657 		ret = false;
658 		goto cleanup;
659 	}
660 
661 	if (test->tt_pass != TCPSIG_SENDRECV && test->tt_pass != TCPSIG_NODATA)
662 		goto cleanup;
663 
664 	tdst = cfd;
665 
666 	if (!tcpsig_sendrecv(test, port, src, tdst)) {
667 		ret = false;
668 		goto cleanup;
669 	}
670 
671 cleanup:
672 	if (port > -1)
673 		(void) close(port);
674 	if (src > -1) {
675 		(void) shutdown(src, SHUT_RDWR);
676 		(void) close(src);
677 	}
678 	if (dst > -1)
679 		(void) close(dst);
680 	if (cfd > -1)
681 		(void) close(cfd);
682 	return (ret);
683 }
684 
685 int
686 main(int argc, char **argv)
687 {
688 	size_t max = ARRAY_SIZE(tcpsig_tests) - 1;
689 	int ret = EXIT_SUCCESS;
690 
691 	if (argc == 2) {
692 		const char *errstr;
693 		size_t idx;
694 
695 		idx = (size_t)strtonumx(argv[1], 0, max, &errstr, 0);
696 		if (errstr != NULL) {
697 			(void) fprintf(stderr, "Syntax: %s [test number]\n",
698 			    getprogname());
699 			(void) fprintf(stderr,
700 			    "Test number is in the range [0-%u]\n", max);
701 			(void) fprintf(stderr, "\nAvailable tests:\n");
702 			for (size_t i = 0; i <= max; i++) {
703 				(void) fprintf(stderr, "    %5d - %s\n", i,
704 				    tcpsig_tests[i].tt_desc);
705 			}
706 			return (EXIT_FAILURE);
707 		}
708 
709 		if (!tcpsig_test_one(&tcpsig_tests[idx]))
710 			ret = EXIT_FAILURE;
711 	} else {
712 		for (size_t i = 0; i <= max; i++) {
713 			if (!tcpsig_test_one(&tcpsig_tests[i]))
714 				ret = EXIT_FAILURE;
715 		}
716 		if (ret == EXIT_SUCCESS)
717 			(void) printf("All tests passed successfully\n");
718 	}
719 
720 	return (ret);
721 }
722