xref: /illumos-gate/usr/src/test/os-tests/tests/mac/mac_parsing.c (revision 3d6ee46b4ddaa0ca6a00cc84d52edf88676f88ce)
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 2025 Oxide Computer Compnay
14  */
15 
16 /*
17  * Driver for mac ktests
18  *
19  * This generates input payloads for the packet-parsing tests in the mac_test
20  * module.  Prior to calling this program, that module (`mac_test`) must be
21  * loaded so we can execute those tests with our payloads.  Since that manual
22  * step of loading the module is required, this test is currently omitted from
23  * the default runfile.
24  */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdbool.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <strings.h>
32 #include <spawn.h>
33 #include <wait.h>
34 #include <errno.h>
35 #include <err.h>
36 #include <sys/debug.h>
37 #include <sys/sysmacros.h>
38 
39 #include <libnvpair.h>
40 #include <libktest.h>
41 #include <sys/ethernet.h>
42 #include <netinet/in.h>
43 #include <netinet/ip.h>
44 #include <netinet/ip6.h>
45 #include <netinet/tcp.h>
46 
47 static ktest_hdl_t *kthdl = NULL;
48 
49 /*
50  * Clones of in-kernel types to specify desired results.
51  * N.B. These must be kept in sync with those in mac_provider.h
52  */
53 typedef enum mac_ether_offload_flags {
54 	MEOI_L2INFO_SET		= 1 << 0,
55 	MEOI_L3INFO_SET		= 1 << 1,
56 	MEOI_L4INFO_SET		= 1 << 2,
57 	MEOI_VLAN_TAGGED	= 1 << 3,
58 	MEOI_L3_FRAGMENT	= 1 << 4
59 } mac_ether_offload_flags_t;
60 
61 typedef struct mac_ether_offload_info {
62 	mac_ether_offload_flags_t	meoi_flags;	/* What's valid? */
63 	size_t		meoi_len;	/* Total message length */
64 	uint8_t		meoi_l2hlen;	/* How long is the Ethernet header? */
65 	uint16_t	meoi_l3proto;	/* What's the Ethertype */
66 	uint16_t	meoi_l3hlen;	/* How long is the header? */
67 	uint8_t		meoi_l4proto;	/* What is the payload type? */
68 	uint8_t		meoi_l4hlen;	/* How long is the L4 header */
69 } mac_ether_offload_info_t;
70 
71 
72 typedef struct test_pkt {
73 	size_t tp_sz;
74 	uint8_t *tp_bytes;
75 } test_pkt_t;
76 
77 static test_pkt_t *
tp_alloc(void)78 tp_alloc(void)
79 {
80 	void *buf = calloc(1, sizeof (test_pkt_t));
81 	VERIFY(buf != NULL);
82 	return (buf);
83 }
84 
85 static void
tp_free(test_pkt_t * tp)86 tp_free(test_pkt_t *tp)
87 {
88 	if (tp->tp_bytes != NULL) {
89 		free(tp->tp_bytes);
90 	}
91 	free(tp);
92 }
93 
94 static void
tp_append(test_pkt_t * tp,const void * bytes,size_t sz)95 tp_append(test_pkt_t *tp, const void *bytes, size_t sz)
96 {
97 	if (tp->tp_bytes == NULL) {
98 		VERIFY(tp->tp_sz == 0);
99 
100 		tp->tp_bytes = malloc(sz);
101 		VERIFY(tp->tp_bytes != NULL);
102 		bcopy(bytes, tp->tp_bytes, sz);
103 		tp->tp_sz = sz;
104 	} else {
105 		const size_t new_sz = tp->tp_sz + sz;
106 
107 		tp->tp_bytes = realloc(tp->tp_bytes, new_sz);
108 		VERIFY(tp->tp_bytes != NULL);
109 		bcopy(bytes, &tp->tp_bytes[tp->tp_sz], sz);
110 		tp->tp_sz = new_sz;
111 	}
112 }
113 
114 static void
append_ether(test_pkt_t * tp,uint16_t ethertype)115 append_ether(test_pkt_t *tp, uint16_t ethertype)
116 {
117 	struct ether_header hdr_ether = {
118 		.ether_type = htons(ethertype),
119 	};
120 
121 	tp_append(tp, &hdr_ether, sizeof (hdr_ether));
122 }
123 
124 static void
append_ip4(test_pkt_t * tp,uint8_t ipproto)125 append_ip4(test_pkt_t *tp, uint8_t ipproto)
126 {
127 	struct ip hdr_ip = {
128 		.ip_v = 4,
129 		.ip_hl = 5,
130 		.ip_p = ipproto,
131 	};
132 
133 	tp_append(tp, &hdr_ip, sizeof (hdr_ip));
134 }
135 
136 static void
append_ip6(test_pkt_t * tp,uint8_t ipproto)137 append_ip6(test_pkt_t *tp, uint8_t ipproto)
138 {
139 	struct ip6_hdr hdr_ip6 = { 0 };
140 	hdr_ip6.ip6_vfc = 0x60;
141 	hdr_ip6.ip6_nxt = ipproto;
142 
143 	tp_append(tp, &hdr_ip6, sizeof (hdr_ip6));
144 }
145 
146 static void
append_tcp(test_pkt_t * tp)147 append_tcp(test_pkt_t *tp)
148 {
149 	struct tcphdr hdr_tcp = {
150 		.th_off = 5
151 	};
152 	tp_append(tp, &hdr_tcp, sizeof (hdr_tcp));
153 }
154 
155 static test_pkt_t *
build_tcp4(mac_ether_offload_info_t * meoi)156 build_tcp4(mac_ether_offload_info_t *meoi)
157 {
158 	test_pkt_t *tp = tp_alloc();
159 	append_ether(tp, ETHERTYPE_IP);
160 	append_ip4(tp, IPPROTO_TCP);
161 	append_tcp(tp);
162 
163 	mac_ether_offload_info_t expected = {
164 		.meoi_flags =
165 		    MEOI_L2INFO_SET | MEOI_L3INFO_SET | MEOI_L4INFO_SET,
166 		.meoi_len = tp->tp_sz,
167 		.meoi_l2hlen = sizeof (struct ether_header),
168 		.meoi_l3proto = ETHERTYPE_IP,
169 		.meoi_l3hlen = sizeof (struct ip),
170 		.meoi_l4proto = IPPROTO_TCP,
171 		.meoi_l4hlen = sizeof (struct tcphdr),
172 	};
173 	*meoi = expected;
174 
175 	return (tp);
176 }
177 
178 static test_pkt_t *
build_tcp6(mac_ether_offload_info_t * meoi)179 build_tcp6(mac_ether_offload_info_t *meoi)
180 {
181 	test_pkt_t *tp = tp_alloc();
182 	append_ether(tp, ETHERTYPE_IPV6);
183 	append_ip6(tp, IPPROTO_TCP);
184 	append_tcp(tp);
185 
186 	mac_ether_offload_info_t expected = {
187 		.meoi_flags =
188 		    MEOI_L2INFO_SET | MEOI_L3INFO_SET | MEOI_L4INFO_SET,
189 		.meoi_len = tp->tp_sz,
190 		.meoi_l2hlen = sizeof (struct ether_header),
191 		.meoi_l3proto = ETHERTYPE_IPV6,
192 		.meoi_l3hlen = sizeof (struct ip6_hdr),
193 		.meoi_l4proto = IPPROTO_TCP,
194 		.meoi_l4hlen = sizeof (struct tcphdr),
195 	};
196 	*meoi = expected;
197 
198 	return (tp);
199 }
200 
201 static test_pkt_t *
build_frag_v4(mac_ether_offload_info_t * meoi)202 build_frag_v4(mac_ether_offload_info_t *meoi)
203 {
204 	test_pkt_t *tp = tp_alloc();
205 	append_ether(tp, ETHERTYPE_IP);
206 
207 	struct ip hdr_ip = {
208 		.ip_v = 4,
209 		.ip_hl = 5,
210 		.ip_off = IP_MF,
211 		.ip_p = IPPROTO_TCP,
212 	};
213 	tp_append(tp, &hdr_ip, sizeof (hdr_ip));
214 
215 	append_tcp(tp);
216 
217 	mac_ether_offload_info_t expected = {
218 		.meoi_flags = MEOI_L2INFO_SET | MEOI_L3INFO_SET |
219 		    MEOI_L4INFO_SET | MEOI_L3_FRAGMENT,
220 		.meoi_l2hlen = sizeof (struct ether_header),
221 		.meoi_l3hlen = sizeof (struct ip),
222 		.meoi_l4hlen = sizeof (struct tcphdr),
223 		.meoi_l3proto = ETHERTYPE_IP,
224 		.meoi_l4proto = IPPROTO_TCP
225 	};
226 	*meoi = expected;
227 
228 	return (tp);
229 }
230 
231 static test_pkt_t *
build_frag_v6(mac_ether_offload_info_t * meoi)232 build_frag_v6(mac_ether_offload_info_t *meoi)
233 {
234 	test_pkt_t *tp = tp_alloc();
235 	append_ether(tp, ETHERTYPE_IPV6);
236 
237 	struct ip6_hdr hdr_ip6 = { 0 };
238 	hdr_ip6.ip6_vfc = 0x60;
239 	hdr_ip6.ip6_nxt = IPPROTO_ROUTING;
240 
241 	struct ip6_rthdr0 eh_route = {
242 		.ip6r0_nxt = IPPROTO_FRAGMENT,
243 		.ip6r0_len = 0,
244 		/* Has padding for len=0 8-byte boundary */
245 	};
246 	struct ip6_frag eh_frag = {
247 		.ip6f_nxt = IPPROTO_DSTOPTS,
248 	};
249 	struct ip6_dstopt {
250 		struct ip6_opt ip6dst_hdr;
251 		/* pad out to required 8-byte boundary */
252 		uint8_t ip6dst_data[6];
253 	} eh_dstopts = {
254 		.ip6dst_hdr = {
255 			.ip6o_type = IPPROTO_TCP,
256 			.ip6o_len = 0,
257 		}
258 	};
259 
260 	/*
261 	 * Mark the packet for fragmentation, but do so in the middle of the EHs
262 	 * as a more contrived case.
263 	 */
264 	VERIFY(tp->tp_sz == sizeof (struct ether_header));
265 	tp_append(tp, &hdr_ip6, sizeof (hdr_ip6));
266 	tp_append(tp, &eh_route, sizeof (eh_route));
267 	tp_append(tp, &eh_frag, sizeof (eh_frag));
268 	tp_append(tp, &eh_dstopts, sizeof (eh_dstopts));
269 	const size_t l3sz = tp->tp_sz - sizeof (struct ether_header);
270 
271 	append_tcp(tp);
272 
273 	mac_ether_offload_info_t expected = {
274 		.meoi_flags = MEOI_L2INFO_SET | MEOI_L3INFO_SET |
275 		    MEOI_L4INFO_SET | MEOI_L3_FRAGMENT,
276 		.meoi_l2hlen = sizeof (struct ether_header),
277 		.meoi_l3hlen = l3sz,
278 		.meoi_l4hlen = sizeof (struct tcphdr),
279 		.meoi_l3proto = ETHERTYPE_IPV6,
280 		.meoi_l4proto = IPPROTO_TCP
281 	};
282 	*meoi = expected;
283 
284 	return (tp);
285 }
286 
287 static nvlist_t *
meoi_to_nvlist(const mac_ether_offload_info_t * meoi)288 meoi_to_nvlist(const mac_ether_offload_info_t *meoi)
289 {
290 	nvlist_t *out = fnvlist_alloc();
291 	fnvlist_add_int32(out, "meoi_flags", meoi->meoi_flags);
292 	fnvlist_add_uint64(out, "meoi_len", meoi->meoi_len);
293 	fnvlist_add_uint8(out, "meoi_l2hlen", meoi->meoi_l2hlen);
294 	fnvlist_add_uint16(out, "meoi_l3proto", meoi->meoi_l3proto);
295 	fnvlist_add_uint16(out, "meoi_l3hlen", meoi->meoi_l3hlen);
296 	fnvlist_add_uint8(out, "meoi_l4proto", meoi->meoi_l4proto);
297 	fnvlist_add_uint8(out, "meoi_l4hlen", meoi->meoi_l4hlen);
298 
299 	return (out);
300 }
301 
302 static nvlist_t *
build_meoi_payload(test_pkt_t * tp,const mac_ether_offload_info_t * results,uint32_t * splits,uint_t num_splits)303 build_meoi_payload(test_pkt_t *tp, const mac_ether_offload_info_t *results,
304     uint32_t *splits, uint_t num_splits)
305 {
306 	nvlist_t *nvl_results = meoi_to_nvlist(results);
307 
308 	nvlist_t *payload = fnvlist_alloc();
309 	fnvlist_add_byte_array(payload, "pkt_bytes", tp->tp_bytes, tp->tp_sz);
310 	if (num_splits != 0 && splits != NULL) {
311 		fnvlist_add_uint32_array(payload, "splits", splits,
312 		    num_splits);
313 	}
314 	fnvlist_add_nvlist(payload, "results", nvl_results);
315 
316 	nvlist_free(nvl_results);
317 
318 	return (payload);
319 }
320 
321 static nvlist_t *
build_partial_payload(test_pkt_t * tp,uint_t offset,const mac_ether_offload_info_t * partial,const mac_ether_offload_info_t * results,uint32_t * splits,uint_t num_splits)322 build_partial_payload(test_pkt_t *tp, uint_t offset,
323     const mac_ether_offload_info_t *partial,
324     const mac_ether_offload_info_t *results,
325     uint32_t *splits, uint_t num_splits)
326 {
327 	nvlist_t *nvl_partial = meoi_to_nvlist(partial);
328 	nvlist_t *nvl_results = meoi_to_nvlist(results);
329 
330 	nvlist_t *payload = fnvlist_alloc();
331 	fnvlist_add_byte_array(payload, "pkt_bytes", tp->tp_bytes, tp->tp_sz);
332 	if (num_splits != 0 && splits != NULL) {
333 		fnvlist_add_uint32_array(payload, "splits", splits,
334 		    num_splits);
335 	}
336 	fnvlist_add_nvlist(payload, "results", nvl_results);
337 	fnvlist_add_nvlist(payload, "partial", nvl_partial);
338 	fnvlist_add_uint32(payload, "offset", offset);
339 
340 	nvlist_free(nvl_partial);
341 	nvlist_free(nvl_results);
342 
343 	return (payload);
344 }
345 
346 static nvlist_t *
build_ether_payload(test_pkt_t * tp,uint8_t * dstaddr,uint32_t tci,uint32_t * splits,uint_t num_splits)347 build_ether_payload(test_pkt_t *tp, uint8_t *dstaddr, uint32_t tci,
348     uint32_t *splits, uint_t num_splits)
349 {
350 	nvlist_t *payload = fnvlist_alloc();
351 	fnvlist_add_byte_array(payload, "pkt_bytes", tp->tp_bytes, tp->tp_sz);
352 	if (num_splits != 0 && splits != NULL) {
353 		fnvlist_add_uint32_array(payload, "splits", splits,
354 		    num_splits);
355 	}
356 	fnvlist_add_byte_array(payload, "dstaddr", dstaddr, ETHERADDRL);
357 	fnvlist_add_uint32(payload, "tci", tci);
358 
359 	return (payload);
360 }
361 
362 struct test_tuple {
363 	const char *tt_module;
364 	const char *tt_suite;
365 	const char *tt_test;
366 };
367 const struct test_tuple tuple_meoi = {
368 	.tt_module = "mac",
369 	.tt_suite = "parsing",
370 	.tt_test = "mac_ether_offload_info_test"
371 };
372 const struct test_tuple tuple_partial_meoi = {
373 	.tt_module = "mac",
374 	.tt_suite = "parsing",
375 	.tt_test = "mac_partial_offload_info_test"
376 };
377 const struct test_tuple tuple_l2info = {
378 	.tt_module = "mac",
379 	.tt_suite = "parsing",
380 	.tt_test = "mac_ether_l2_info_test"
381 };
382 
383 static bool
run_test(nvlist_t * payload,const struct test_tuple * tuple)384 run_test(nvlist_t *payload, const struct test_tuple *tuple)
385 {
386 	size_t payload_sz;
387 	char *payload_packed = fnvlist_pack(payload, &payload_sz);
388 	VERIFY(payload_packed != NULL);
389 	nvlist_free(payload);
390 
391 	ktest_run_req_t req = {
392 		.krq_module = tuple->tt_module,
393 		.krq_suite = tuple->tt_suite,
394 		.krq_test = tuple->tt_test,
395 		.krq_input = (uchar_t *)payload_packed,
396 		.krq_input_len = payload_sz,
397 	};
398 	ktest_run_result_t result = { 0 };
399 
400 	if (!ktest_run(kthdl, &req, &result)) {
401 		err(EXIT_FAILURE, "error while attempting ktest_run()");
402 	}
403 
404 	const char *cname = ktest_code_name(result.krr_code);
405 	if (result.krr_code == KTEST_CODE_PASS) {
406 		(void) printf("%s: %s\n", tuple->tt_test, cname);
407 		free(result.krr_msg);
408 		return (true);
409 	} else {
410 		(void) printf("%s: %s @ line %u\n",
411 		    tuple->tt_test, cname, result.krr_line);
412 		(void) printf("\tmsg: %s", result.krr_msg);
413 		free(result.krr_msg);
414 		return (false);
415 	}
416 }
417 
418 static uint32_t *
split_gen_single(uint_t num_bytes)419 split_gen_single(uint_t num_bytes)
420 {
421 	uint32_t *splits = calloc(num_bytes, sizeof (uint32_t));
422 	VERIFY(splits != NULL);
423 	for (uint_t i = 0; i < num_bytes; i++) {
424 		splits[i] = 1;
425 	}
426 	return (splits);
427 }
428 static uint32_t *
split_gen_random(uint_t num_bytes,uint_t * num_splits)429 split_gen_random(uint_t num_bytes, uint_t *num_splits)
430 {
431 	/*
432 	 * Generate split points between 0-10 bytes in size.  Assuming an
433 	 * average size of 5 when allocating a fixed buffer, with any remaining
434 	 * bytes going into one large trailing mblk.
435 	 */
436 	*num_splits = num_bytes / 5;
437 
438 	uint32_t *splits = calloc(*num_splits, sizeof (uint32_t));
439 	VERIFY(splits != NULL);
440 	for (uint_t i = 0; i < *num_splits; i++) {
441 		/*
442 		 * This uses random() rather than something like
443 		 * arc4random_uniform() so we can have deterministic splits for
444 		 * the test case.  This is achieved with a prior srand() call
445 		 * with a fixed seed.
446 		 */
447 		splits[i] = random() % 11;
448 	}
449 
450 	return (splits);
451 }
452 static void
split_print(const uint32_t * splits,uint_t num_splits)453 split_print(const uint32_t *splits, uint_t num_splits)
454 {
455 	if (num_splits == 0) {
456 		(void) printf("\tsplits: []\n");
457 	} else {
458 		(void) printf("\tsplits: [");
459 		for (uint_t i = 0; i < num_splits; i++) {
460 			(void) printf("%s%u", i == 0 ? "" : ", ", splits[i]);
461 		}
462 		(void) printf("]\n");
463 	}
464 }
465 
466 /*
467  * Run variations of mac_ether_offload_info() test against packet/meoi pair.
468  * Returns true if any variation failed.
469  */
470 static bool
run_meoi_variants(const char * prefix,test_pkt_t * tp,const mac_ether_offload_info_t * meoi)471 run_meoi_variants(const char *prefix, test_pkt_t *tp,
472     const mac_ether_offload_info_t *meoi)
473 {
474 	nvlist_t *payload;
475 	bool any_failed = false;
476 	uint32_t *splits = NULL;
477 	uint_t num_splits;
478 
479 	(void) printf("%s - simple - ", prefix);
480 	payload = build_meoi_payload(tp, meoi, NULL, 0);
481 	any_failed |= !run_test(payload, &tuple_meoi);
482 
483 	(void) printf("%s - split-single-bytes - ", prefix);
484 	splits = split_gen_single(tp->tp_sz);
485 	payload = build_meoi_payload(tp, meoi, splits, tp->tp_sz);
486 	any_failed |= !run_test(payload, &tuple_meoi);
487 	free(splits);
488 
489 	(void) printf("%s - split-random - ", prefix);
490 	splits = split_gen_random(tp->tp_sz, &num_splits);
491 	payload = build_meoi_payload(tp, meoi, splits, num_splits);
492 	any_failed |= !run_test(payload, &tuple_meoi);
493 	split_print(splits, num_splits);
494 	free(splits);
495 
496 	return (any_failed);
497 }
498 
499 /*
500  * Run variations of mac_partial_offload_info() test against packet/meoi pair.
501  * Returns true if any variation failed.
502  */
503 static bool
run_partial_variants(const char * prefix,test_pkt_t * tp,const mac_ether_offload_info_t * meoi)504 run_partial_variants(const char *prefix, test_pkt_t *tp,
505     const mac_ether_offload_info_t *meoi)
506 {
507 	nvlist_t *payload;
508 	bool any_failed = false;
509 	uint32_t *splits = NULL;
510 	uint_t num_splits;
511 
512 	/* skip over the l2 header but ask for the rest to be filled */
513 	uint32_t offset = meoi->meoi_l2hlen;
514 	mac_ether_offload_info_t partial = {
515 		.meoi_flags = MEOI_L2INFO_SET,
516 		.meoi_l3proto = meoi->meoi_l3proto,
517 	};
518 	/* And the result should reflect that ignored l2 header */
519 	mac_ether_offload_info_t result;
520 	bcopy(meoi, &result, sizeof (result));
521 	result.meoi_l2hlen = 0;
522 
523 	(void) printf("%s - simple - ", prefix);
524 	payload = build_partial_payload(tp, offset, &partial, &result, NULL, 0);
525 	any_failed |= !run_test(payload, &tuple_partial_meoi);
526 
527 	(void) printf("%s - split-single-bytes - ", prefix);
528 	splits = split_gen_single(tp->tp_sz);
529 	payload = build_partial_payload(tp, offset, &partial, &result, splits,
530 	    tp->tp_sz);
531 	any_failed |= !run_test(payload, &tuple_partial_meoi);
532 	free(splits);
533 
534 	(void) printf("%s - split-random - ", prefix);
535 	splits = split_gen_random(tp->tp_sz, &num_splits);
536 	payload = build_partial_payload(tp, offset, &partial, &result, splits,
537 	    num_splits);
538 	any_failed |= !run_test(payload, &tuple_partial_meoi);
539 	split_print(splits, num_splits);
540 	free(splits);
541 
542 	return (any_failed);
543 }
544 
545 /*
546  * Run variations of mac_ether_l2_info() test against packet/data pairing.
547  * Returns true if any variation failed.
548  */
549 static bool
run_ether_variants(const char * prefix,test_pkt_t * tp,uint8_t * dstaddr,uint32_t tci)550 run_ether_variants(const char *prefix, test_pkt_t *tp, uint8_t *dstaddr,
551     uint32_t tci)
552 {
553 	nvlist_t *payload;
554 	bool any_failed = false;
555 	uint32_t *splits = NULL;
556 
557 	(void) printf("%s - simple - ", prefix);
558 	payload = build_ether_payload(tp, dstaddr, tci, NULL, 0);
559 	any_failed |= !run_test(payload, &tuple_l2info);
560 
561 	(void) printf("%s - split-single-bytes - ", prefix);
562 	splits = split_gen_single(tp->tp_sz);
563 	payload = build_ether_payload(tp, dstaddr, tci, splits, tp->tp_sz);
564 	any_failed |= !run_test(payload, &tuple_l2info);
565 	free(splits);
566 
567 	/* intentionally split dstaddr, tpid, tci, and ethertype */
568 	uint32_t intentional_splits[] = { 4, 9, 2, 2 };
569 	(void) printf("%s - split-intentional - ", prefix);
570 	payload = build_ether_payload(tp, dstaddr, tci, intentional_splits,
571 	    ARRAY_SIZE(intentional_splits));
572 	any_failed |= !run_test(payload, &tuple_l2info);
573 	split_print(intentional_splits, ARRAY_SIZE(intentional_splits));
574 
575 	return (any_failed);
576 }
577 
578 int
main(int argc,char * argv[])579 main(int argc, char *argv[])
580 {
581 	if (!ktest_mod_load("mac")) {
582 		err(EXIT_FAILURE, "could not load mac ktest module");
583 	}
584 	if ((kthdl = ktest_init()) == NULL) {
585 		err(EXIT_FAILURE, "could not initialize libktest");
586 	}
587 
588 	bool any_failed = false;
589 
590 	/* Use fixed seed for deterministic "random" output */
591 	srandom(0x1badbeef);
592 
593 	mac_ether_offload_info_t meoi_tcp4 = { 0 };
594 	test_pkt_t *tp_tcp4 = build_tcp4(&meoi_tcp4);
595 
596 	mac_ether_offload_info_t meoi_tcp6 = { 0 };
597 	test_pkt_t *tp_tcp6 = build_tcp6(&meoi_tcp6);
598 
599 	any_failed |=
600 	    run_meoi_variants("basic tcp4", tp_tcp4, &meoi_tcp4);
601 	any_failed |=
602 	    run_meoi_variants("basic tcp6", tp_tcp6, &meoi_tcp6);
603 	any_failed |= run_partial_variants("basic tcp4", tp_tcp4, &meoi_tcp4);
604 	any_failed |= run_partial_variants("basic tcp6", tp_tcp6, &meoi_tcp6);
605 
606 	/*
607 	 * Truncate the tcp header to induce a parse failure, but expect that
608 	 * the packet info is still populated
609 	 */
610 	tp_tcp4->tp_sz -= 4;
611 	tp_tcp6->tp_sz -= 4;
612 	meoi_tcp4.meoi_flags &= ~MEOI_L4INFO_SET;
613 	meoi_tcp6.meoi_flags &= ~MEOI_L4INFO_SET;
614 
615 	any_failed |=
616 	    run_meoi_variants("truncated tcp4", tp_tcp4, &meoi_tcp4);
617 	any_failed |=
618 	    run_meoi_variants("truncated tcp6", tp_tcp6, &meoi_tcp6);
619 
620 	mac_ether_offload_info_t meoi_frag_v4 = { 0 };
621 	mac_ether_offload_info_t meoi_frag_v6 = { 0 };
622 	test_pkt_t *tp_frag_v4 = build_frag_v4(&meoi_frag_v4);
623 	test_pkt_t *tp_frag_v6 = build_frag_v6(&meoi_frag_v6);
624 
625 	any_failed |= run_meoi_variants("fragment ipv4", tp_frag_v4,
626 	    &meoi_frag_v4);
627 	any_failed |= run_meoi_variants("fragment ipv6", tp_frag_v6,
628 	    &meoi_frag_v6);
629 
630 	test_pkt_t *tp_ether_plain = tp_alloc();
631 	struct ether_header hdr_l2_plain = {
632 		.ether_dhost = { 0x86, 0x1d, 0xe0, 0x11, 0x22, 0x33},
633 		.ether_type = htons(ETHERTYPE_IP),
634 	};
635 	tp_append(tp_ether_plain, &hdr_l2_plain, sizeof (hdr_l2_plain));
636 
637 	test_pkt_t *tp_ether_vlan = tp_alloc();
638 	const uint16_t arb_vlan = 201;
639 	struct ether_vlan_header hdr_l2_vlan = {
640 		.ether_dhost = { 0x86, 0x1d, 0xe0, 0x11, 0x22, 0x33},
641 		.ether_tpid = htons(ETHERTYPE_VLAN),
642 		.ether_tci = htons(arb_vlan),
643 		.ether_type = htons(ETHERTYPE_IP),
644 	};
645 	tp_append(tp_ether_vlan, &hdr_l2_vlan, sizeof (hdr_l2_vlan));
646 
647 	any_failed |= run_ether_variants("ether plain", tp_ether_plain,
648 	    hdr_l2_plain.ether_dhost.ether_addr_octet, UINT32_MAX);
649 	any_failed |= run_ether_variants("ether vlan", tp_ether_vlan,
650 	    hdr_l2_vlan.ether_dhost.ether_addr_octet, arb_vlan);
651 
652 	tp_free(tp_tcp4);
653 	tp_free(tp_tcp6);
654 	tp_free(tp_frag_v4);
655 	tp_free(tp_frag_v6);
656 	tp_free(tp_ether_plain);
657 	tp_free(tp_ether_vlan);
658 
659 	ktest_fini(kthdl);
660 	return (any_failed ? EXIT_FAILURE : EXIT_SUCCESS);
661 }
662