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