xref: /illumos-gate/usr/src/uts/common/io/mac/mac_ktest.c (revision 10597944279b73141546abca67a8e947810e5bb2)
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 Company
14  * Copyright 2024 Ryan Zezeski
15  */
16 
17 /*
18  * A test module for various mac routines.
19  */
20 #include <inet/ip.h>
21 #include <inet/ip_impl.h>
22 #include <sys/dlpi.h>
23 #include <sys/ethernet.h>
24 #include <sys/ktest.h>
25 #include <sys/mac_client.h>
26 #include <sys/mac_provider.h>
27 #include <sys/pattr.h>
28 #include <sys/strsubr.h>
29 #include <sys/strsun.h>
30 
31 /* Arbitrary limits for cksum tests */
32 #define	PADDING_MAX	32
33 #define	SPLITS_MAX	8
34 
35 typedef struct emul_test_params {
36 	mblk_t		*etp_mp;
37 	uchar_t		*etp_raw;
38 	uint_t		etp_raw_sz;
39 	uchar_t		*etp_outputs;
40 	uint_t		etp_outputs_sz;
41 	boolean_t	etp_do_partial;
42 	boolean_t	etp_do_full;
43 	boolean_t	etp_do_ipv4;
44 	boolean_t	etp_do_lso;
45 	uint_t		etp_mss;
46 	uint_t		etp_splits[SPLITS_MAX];
47 } emul_test_params_t;
48 
49 static void
etp_free(const emul_test_params_t * etp)50 etp_free(const emul_test_params_t *etp)
51 {
52 	if (etp->etp_mp != NULL) {
53 		freemsgchain(etp->etp_mp);
54 	}
55 	if (etp->etp_raw != NULL) {
56 		kmem_free(etp->etp_raw, etp->etp_raw_sz);
57 	}
58 	if (etp->etp_outputs != NULL) {
59 		kmem_free(etp->etp_outputs, etp->etp_outputs_sz);
60 	}
61 }
62 
63 static mblk_t *
cksum_alloc_pkt(const emul_test_params_t * etp,uint32_t padding)64 cksum_alloc_pkt(const emul_test_params_t *etp, uint32_t padding)
65 {
66 	uint32_t remain = etp->etp_raw_sz;
67 	uint_t split_idx = 0;
68 	const uint8_t *pkt_bytes = etp->etp_raw;
69 
70 	mblk_t *head = NULL, *tail = NULL;
71 	while (remain > 0) {
72 		const boolean_t has_split = etp->etp_splits[split_idx] != 0;
73 		const uint32_t to_copy = has_split ?
74 		    MIN(remain, etp->etp_splits[split_idx]) : remain;
75 		const uint32_t to_alloc = padding + to_copy;
76 
77 		mblk_t *mp = allocb(to_alloc, 0);
78 		if (mp == NULL) {
79 			freemsg(head);
80 			return (NULL);
81 		}
82 		if (head == NULL) {
83 			head = mp;
84 		}
85 		if (tail != NULL) {
86 			tail->b_cont = mp;
87 		}
88 		tail = mp;
89 
90 		/* Pad the first mblk with zeros, if requested */
91 		if (padding != 0) {
92 			bzero(mp->b_rptr, padding);
93 			mp->b_rptr += padding;
94 			mp->b_wptr += padding;
95 			padding = 0;
96 		}
97 
98 		bcopy(pkt_bytes, mp->b_rptr, to_copy);
99 		mp->b_wptr += to_copy;
100 		pkt_bytes += to_copy;
101 		remain -= to_copy;
102 		if (has_split) {
103 			split_idx++;
104 		}
105 	}
106 	return (head);
107 }
108 
109 static boolean_t
emul_test_parse_input(ktest_ctx_hdl_t * ctx,emul_test_params_t * etp)110 emul_test_parse_input(ktest_ctx_hdl_t *ctx, emul_test_params_t *etp)
111 {
112 	uchar_t *bytes;
113 	size_t num_bytes = 0;
114 
115 	ktest_get_input(ctx, &bytes, &num_bytes);
116 	bzero(etp, sizeof (*etp));
117 
118 	nvlist_t *params = NULL;
119 	if (nvlist_unpack((char *)bytes, num_bytes, &params, KM_SLEEP) != 0) {
120 		KT_ERROR(ctx, "Invalid nvlist input");
121 		return (B_FALSE);
122 	}
123 
124 	uchar_t *pkt_bytes, *out_pkt_bytes;
125 	uint_t pkt_sz, out_pkt_sz;
126 
127 	if (nvlist_lookup_byte_array(params, "pkt_bytes", &pkt_bytes,
128 	    &pkt_sz) != 0) {
129 		KT_ERROR(ctx, "Input missing pkt_bytes field");
130 		goto bail;
131 	}
132 	if (pkt_sz == 0) {
133 		KT_ERROR(ctx, "Packet must not be 0-length");
134 		goto bail;
135 	}
136 
137 	if (nvlist_lookup_byte_array(params, "out_pkt_bytes", &out_pkt_bytes,
138 	    &out_pkt_sz) == 0) {
139 		if (out_pkt_sz < sizeof (uint32_t)) {
140 			KT_ERROR(ctx, "Serialized packets need a u32 length");
141 			goto bail;
142 		}
143 		etp->etp_outputs = kmem_alloc(out_pkt_sz, KM_SLEEP);
144 		bcopy(out_pkt_bytes, etp->etp_outputs, out_pkt_sz);
145 		etp->etp_outputs_sz = out_pkt_sz;
146 	}
147 
148 	(void) nvlist_lookup_uint32(params, "mss", &etp->etp_mss);
149 
150 	uint32_t padding = 0;
151 	(void) nvlist_lookup_uint32(params, "padding", &padding);
152 	if (padding & 1) {
153 		KT_ERROR(ctx, "padding must be even");
154 		goto bail;
155 	} else if (padding > PADDING_MAX) {
156 		KT_ERROR(ctx, "padding greater than max of %u", PADDING_MAX);
157 		goto bail;
158 	}
159 
160 	etp->etp_do_ipv4 = fnvlist_lookup_boolean(params, "cksum_ipv4");
161 	etp->etp_do_partial = fnvlist_lookup_boolean(params, "cksum_partial");
162 	etp->etp_do_full = fnvlist_lookup_boolean(params, "cksum_full");
163 
164 	uint32_t *splits;
165 	uint_t nsplits;
166 	if (nvlist_lookup_uint32_array(params, "cksum_splits", &splits,
167 	    &nsplits) == 0) {
168 		if (nsplits > SPLITS_MAX) {
169 			KT_ERROR(ctx, "Too many splits requested");
170 			goto bail;
171 		}
172 		for (uint_t i = 0; i < nsplits; i++) {
173 			if (splits[i] == 0) {
174 				KT_ERROR(ctx, "Splits should not be 0");
175 				goto bail;
176 			} else if (splits[i] & 1) {
177 				KT_ERROR(ctx, "Splits must be 2-byte aligned");
178 				goto bail;
179 			}
180 			etp->etp_splits[i] = splits[i];
181 		}
182 	}
183 
184 	if (etp->etp_do_partial && etp->etp_do_full) {
185 		KT_ERROR(ctx, "Cannot request full and partial cksum");
186 		goto bail;
187 	}
188 
189 	etp->etp_raw = kmem_alloc(pkt_sz, KM_SLEEP);
190 	bcopy(pkt_bytes, etp->etp_raw, pkt_sz);
191 	etp->etp_raw_sz = pkt_sz;
192 
193 	etp->etp_mp = cksum_alloc_pkt(etp, padding);
194 	if (etp->etp_mp == NULL) {
195 		KT_ERROR(ctx, "Could not allocate mblk");
196 		goto bail;
197 	}
198 
199 	nvlist_free(params);
200 	return (B_TRUE);
201 
202 bail:
203 	etp_free(etp);
204 
205 	if (params != NULL) {
206 		nvlist_free(params);
207 	}
208 	return (B_FALSE);
209 }
210 
211 /* Calculate pseudo-header checksum for a packet */
212 static uint16_t
cksum_calc_pseudo(ktest_ctx_hdl_t * ctx,const uint8_t * pkt_data,const mac_ether_offload_info_t * meoi,boolean_t exclude_len)213 cksum_calc_pseudo(ktest_ctx_hdl_t *ctx, const uint8_t *pkt_data,
214     const mac_ether_offload_info_t *meoi, boolean_t exclude_len)
215 {
216 	if ((meoi->meoi_flags & MEOI_L4INFO_SET) == 0) {
217 		KT_ERROR(ctx, "MEOI lacks L4 info");
218 		return (0);
219 	}
220 
221 	const uint16_t *iphs = (const uint16_t *)(pkt_data + meoi->meoi_l2hlen);
222 	uint32_t cksum = 0;
223 
224 	/* Copied from ip_input_cksum_pseudo_v[46]() */
225 	if (meoi->meoi_l3proto == ETHERTYPE_IP) {
226 		cksum += iphs[6] + iphs[7] + iphs[8] + iphs[9];
227 	} else if (meoi->meoi_l3proto == ETHERTYPE_IPV6) {
228 		cksum += iphs[4] + iphs[5] + iphs[6] + iphs[7] +
229 		    iphs[8] + iphs[9] + iphs[10] + iphs[11] +
230 		    iphs[12] + iphs[13] + iphs[14] + iphs[15] +
231 		    iphs[16] + iphs[17] + iphs[18] + iphs[19];
232 	} else {
233 		KT_ERROR(ctx, "unexpected proto %u", meoi->meoi_l3proto);
234 		return (0);
235 	}
236 
237 	switch (meoi->meoi_l4proto) {
238 	case IPPROTO_TCP:
239 		cksum += IP_TCP_CSUM_COMP;
240 		break;
241 	case IPPROTO_UDP:
242 		cksum += IP_UDP_CSUM_COMP;
243 		break;
244 	case IPPROTO_ICMPV6:
245 		cksum += IP_ICMPV6_CSUM_COMP;
246 		break;
247 	default:
248 		KT_ERROR(ctx, "unexpected L4 proto %u", meoi->meoi_l4proto);
249 		return (0);
250 	}
251 
252 	uint16_t ulp_len =
253 	    meoi->meoi_len - ((uint16_t)meoi->meoi_l2hlen + meoi->meoi_l3hlen);
254 	if (meoi->meoi_l3proto == ETHERTYPE_IP) {
255 		/*
256 		 * IPv4 packets can fall below the 60-byte minimum for ethernet,
257 		 * resulting in padding which makes the "easy" means of
258 		 * determining ULP length potentially inaccurate.
259 		 *
260 		 * Reach into the v4 header to make that calculation.
261 		 */
262 		const ipha_t *ipha =
263 		    (const ipha_t *)(pkt_data + meoi->meoi_l2hlen);
264 		ulp_len = ntohs(ipha->ipha_length) - meoi->meoi_l3hlen;
265 	}
266 
267 	/* LSO packets omit ULP length from cksum since it may be changing */
268 	if (!exclude_len) {
269 		cksum += htons(ulp_len);
270 	}
271 
272 	cksum = (cksum >> 16) + (cksum & 0xffff);
273 	cksum = (cksum >> 16) + (cksum & 0xffff);
274 	return (cksum);
275 }
276 
277 /*
278  * Overwrite 2 bytes in mblk at given offset.
279  *
280  * Assumes:
281  * - offset is 2-byte aligned
282  * - mblk(s) in chain reference memory which is 2-byte aligned
283  * - offset is within mblk chain
284  */
285 static void
mblk_write16(mblk_t * mp,uint_t off,uint16_t val)286 mblk_write16(mblk_t *mp, uint_t off, uint16_t val)
287 {
288 	VERIFY(mp != NULL);
289 	VERIFY3U(off & 1, ==, 0);
290 	VERIFY3U(off + 2, <=, msgdsize(mp));
291 
292 	while (off >= MBLKL(mp)) {
293 		off -= MBLKL(mp);
294 		mp = mp->b_cont;
295 		VERIFY(mp != NULL);
296 	}
297 
298 	uint16_t *datap = (uint16_t *)(mp->b_rptr + off);
299 	*datap = val;
300 }
301 
302 /* Compare an individual mblk with known good value in test parameters.  */
303 static boolean_t
pkt_compare(ktest_ctx_hdl_t * ctx,const uchar_t * buf,const uint_t len,mblk_t * mp)304 pkt_compare(ktest_ctx_hdl_t *ctx, const uchar_t *buf, const uint_t len,
305     mblk_t *mp)
306 {
307 	if (msgdsize(mp) != len) {
308 		KT_FAIL(ctx, "mp size %u != %u", msgdsize(mp), len);
309 		return (B_FALSE);
310 	}
311 
312 	uint32_t fail_val = 0, good_val = 0;
313 	uint_t mp_off = 0, fail_len = 0, i;
314 	for (i = 0; i < len; i++) {
315 		/*
316 		 * If we encounter a mismatch, collect up to 4 bytes of context
317 		 * to print with the failure.
318 		 */
319 		if (mp->b_rptr[mp_off] != buf[i] || fail_len != 0) {
320 			fail_val |= mp->b_rptr[mp_off] << (fail_len * 8);
321 			good_val |= buf[i] << (fail_len * 8);
322 
323 			fail_len++;
324 			if (fail_len == 4) {
325 				break;
326 			}
327 		}
328 
329 		mp_off++;
330 		if (mp_off == MBLKL(mp)) {
331 			mp = mp->b_cont;
332 			mp_off = 0;
333 		}
334 	}
335 
336 	if (fail_len != 0) {
337 		KT_FAIL(ctx, "mp[%02X] %08X != %08X", (i - fail_len),
338 		    fail_val, good_val);
339 		return (B_FALSE);
340 	}
341 
342 	return (B_TRUE);
343 }
344 
345 /* Compare resulting mblk chain with known good values in test parameters. */
346 static boolean_t
pkt_result_compare_chain(ktest_ctx_hdl_t * ctx,const emul_test_params_t * etp,mblk_t * mp)347 pkt_result_compare_chain(ktest_ctx_hdl_t *ctx, const emul_test_params_t *etp,
348     mblk_t *mp)
349 {
350 	uint_t remaining = etp->etp_outputs_sz;
351 	const uchar_t *raw_cur = etp->etp_outputs;
352 
353 	uint_t idx = 0;
354 	while (remaining != 0 && mp != NULL) {
355 		uint32_t inner_pkt_len;
356 		if (remaining < sizeof (inner_pkt_len)) {
357 			KT_ERROR(ctx, "insufficient bytes to read packet len");
358 			return (B_FALSE);
359 		}
360 		bcopy(raw_cur, &inner_pkt_len, sizeof (inner_pkt_len));
361 		remaining -= sizeof (inner_pkt_len);
362 		raw_cur += sizeof (inner_pkt_len);
363 
364 		if (remaining < inner_pkt_len) {
365 			KT_ERROR(ctx, "wanted %u bytes to read packet, had %u",
366 			    inner_pkt_len, remaining);
367 			return (B_FALSE);
368 		}
369 
370 		if (!pkt_compare(ctx, raw_cur, inner_pkt_len, mp)) {
371 			ktest_msg_prepend(ctx, "packet %u: ", idx);
372 			return (B_FALSE);
373 		}
374 
375 		remaining -= inner_pkt_len;
376 		raw_cur += inner_pkt_len;
377 		idx++;
378 		mp = mp->b_next;
379 	}
380 
381 	if (remaining != 0) {
382 		KT_FAIL(ctx, "fewer packets returned than expected");
383 		return (B_FALSE);
384 	}
385 
386 	if (mp != NULL) {
387 		KT_FAIL(ctx, "more packets returned than expected");
388 		return (B_FALSE);
389 	}
390 
391 	return (B_TRUE);
392 }
393 
394 static void
mac_hw_emul_test(ktest_ctx_hdl_t * ctx,emul_test_params_t * etp)395 mac_hw_emul_test(ktest_ctx_hdl_t *ctx, emul_test_params_t *etp)
396 {
397 	mblk_t *mp = etp->etp_mp;
398 
399 	mac_ether_offload_info_t meoi;
400 	mac_ether_offload_info(mp, &meoi);
401 
402 	if ((meoi.meoi_flags & MEOI_L3INFO_SET) == 0 ||
403 	    (meoi.meoi_l3proto != ETHERTYPE_IP &&
404 	    meoi.meoi_l3proto != ETHERTYPE_IPV6)) {
405 		KT_SKIP(ctx, "l3 protocol not recognized/supported");
406 		return;
407 	}
408 
409 	mac_emul_t emul_flags = 0;
410 	uint_t hck_flags = 0, hck_start = 0, hck_stuff = 0, hck_end = 0;
411 
412 	if (etp->etp_do_lso) {
413 		emul_flags |= MAC_LSO_EMUL;
414 		hck_flags |= HW_LSO;
415 		if (etp->etp_mss == 0) {
416 			KT_ERROR(ctx, "invalid MSS for LSO");
417 			return;
418 		}
419 	}
420 
421 	if (meoi.meoi_l3proto == ETHERTYPE_IP && etp->etp_do_ipv4) {
422 		mblk_write16(mp,
423 		    meoi.meoi_l2hlen + offsetof(ipha_t, ipha_hdr_checksum), 0);
424 		emul_flags |= MAC_IPCKSUM_EMUL;
425 		hck_flags |= HCK_IPV4_HDRCKSUM;
426 	}
427 
428 	const boolean_t do_l4 = etp->etp_do_partial || etp->etp_do_full;
429 	if ((meoi.meoi_flags & MEOI_L4INFO_SET) != 0 && do_l4) {
430 		boolean_t skip_pseudo = B_FALSE;
431 		hck_start = meoi.meoi_l2hlen + meoi.meoi_l3hlen;
432 		hck_stuff = hck_start;
433 		hck_end = meoi.meoi_len;
434 
435 		switch (meoi.meoi_l4proto) {
436 		case IPPROTO_TCP:
437 			hck_stuff += TCP_CHECKSUM_OFFSET;
438 			break;
439 		case IPPROTO_UDP:
440 			hck_stuff += UDP_CHECKSUM_OFFSET;
441 			break;
442 		case IPPROTO_ICMP:
443 			hck_stuff += ICMP_CHECKSUM_OFFSET;
444 			/*
445 			 * ICMP does not include the pseudo-header content in
446 			 * its checksum, but we can still do a partial with that
447 			 * field cleared.
448 			 */
449 			skip_pseudo = B_TRUE;
450 			break;
451 		case IPPROTO_ICMPV6:
452 			hck_stuff += ICMPV6_CHECKSUM_OFFSET;
453 			break;
454 		case IPPROTO_SCTP:
455 			/*
456 			 * Only full checksums are supported for SCTP, and the
457 			 * test logic for clearing the existing sum needs to
458 			 * account for its increased width.
459 			 */
460 			hck_stuff += SCTP_CHECKSUM_OFFSET;
461 			if (etp->etp_do_full) {
462 				mblk_write16(mp, hck_stuff, 0);
463 				mblk_write16(mp, hck_stuff + 2, 0);
464 			} else {
465 				KT_SKIP(ctx,
466 				    "Partial L4 cksum not supported for SCTP");
467 				return;
468 			}
469 			break;
470 		default:
471 			KT_SKIP(ctx,
472 			    "Partial L4 cksum not supported for proto");
473 			return;
474 		}
475 
476 		emul_flags |= MAC_HWCKSUM_EMUL;
477 		if (etp->etp_do_partial) {
478 			hck_flags |= HCK_PARTIALCKSUM;
479 			if (!skip_pseudo) {
480 				/* Populate L4 pseudo-header cksum */
481 				const uint16_t pcksum = cksum_calc_pseudo(ctx,
482 				    etp->etp_raw, &meoi, etp->etp_do_lso);
483 				mblk_write16(mp, hck_stuff, pcksum);
484 			} else {
485 				mblk_write16(mp, hck_stuff, 0);
486 			}
487 		} else {
488 			hck_flags |= HCK_FULLCKSUM;
489 			/* Zero out the L4 cksum */
490 			mblk_write16(mp, hck_stuff, 0);
491 		}
492 	}
493 	if (do_l4 && (hck_flags & (HCK_FULLCKSUM|HCK_PARTIALCKSUM)) == 0) {
494 		KT_SKIP(ctx, "L4 checksum not supported for packet");
495 		return;
496 	}
497 
498 	if (emul_flags != 0) {
499 		if ((hck_flags & HCK_PARTIALCKSUM) == 0) {
500 			hck_start = hck_stuff = hck_end = 0;
501 		} else {
502 			/*
503 			 * The offsets for mac_hcksum_set are all relative to
504 			 * the start of the L3 header.  Prior to here, these
505 			 * values were relative to the start of the packet.
506 			 */
507 			hck_start -= meoi.meoi_l2hlen;
508 			hck_stuff -= meoi.meoi_l2hlen;
509 			hck_end -= meoi.meoi_l2hlen;
510 		}
511 		/* Set hcksum information on all mblks in chain */
512 		for (mblk_t *cmp = mp; cmp != NULL; cmp = cmp->b_cont) {
513 			mac_hcksum_set(cmp, hck_start, hck_stuff, hck_end, 0,
514 			    hck_flags & HCK_FLAGS);
515 			lso_info_set(cmp, etp->etp_mss,
516 			    hck_flags & HW_LSO_FLAGS);
517 		}
518 
519 		mac_hw_emul(&mp, NULL, NULL, emul_flags);
520 		KT_ASSERT3P(mp, !=, NULL, ctx);
521 		etp->etp_mp = mp;
522 
523 		boolean_t success = (etp->etp_outputs == NULL) ?
524 		    pkt_compare(ctx, etp->etp_raw, etp->etp_raw_sz, mp) :
525 		    pkt_result_compare_chain(ctx, etp, mp);
526 		if (!success) {
527 			return;
528 		}
529 	} else {
530 		KT_SKIP(ctx, "offloads unsupported for packet");
531 		return;
532 	}
533 
534 	KT_PASS(ctx);
535 }
536 
537 /*
538  * Verify checksum emulation against an arbitrary chain of packets.  If the
539  * packet is of a supported protocol, any L3 and L4 checksums are cleared, and
540  * then mac_hw_emul() is called to perform the offload emulation.  Afterwards,
541  * the packet is compared to see if it equals the input, which is assumed to
542  * have correct checksums.
543  */
544 static void
mac_sw_cksum_test(ktest_ctx_hdl_t * ctx)545 mac_sw_cksum_test(ktest_ctx_hdl_t *ctx)
546 {
547 	emul_test_params_t etp;
548 	if (!emul_test_parse_input(ctx, &etp)) {
549 		goto cleanup;
550 	}
551 
552 	mac_hw_emul_test(ctx, &etp);
553 
554 cleanup:
555 	etp_free(&etp);
556 }
557 
558 /*
559  * Verify mac_sw_lso() (and checksum) emulation against an arbitrary input
560  * packet.  This test functions like mac_sw_cksum_test insofar as checksums can
561  * be customised, but also sets HW_LSO on any input packet, and compares the
562  * outputs against a mandatory chain of packets provided by the caller.
563  */
564 static void
mac_sw_lso_test(ktest_ctx_hdl_t * ctx)565 mac_sw_lso_test(ktest_ctx_hdl_t *ctx)
566 {
567 	emul_test_params_t etp;
568 	if (!emul_test_parse_input(ctx, &etp)) {
569 		goto cleanup;
570 	}
571 
572 	if (etp.etp_mss == 0) {
573 		KT_ERROR(ctx, "invalid MSS for LSO");
574 		goto cleanup;
575 	}
576 
577 	if (etp.etp_outputs == NULL) {
578 		KT_ERROR(ctx, "LSO tests require explicit packet list");
579 		goto cleanup;
580 	}
581 
582 	etp.etp_do_lso = B_TRUE;
583 
584 	mac_hw_emul_test(ctx, &etp);
585 
586 cleanup:
587 	etp_free(&etp);
588 }
589 
590 typedef struct meoi_test_params {
591 	mblk_t				*mtp_mp;
592 	mac_ether_offload_info_t	mtp_partial;
593 	mac_ether_offload_info_t	mtp_results;
594 	uint_t				mtp_offset;
595 } meoi_test_params_t;
596 
597 static void
nvlist_to_meoi(nvlist_t * results,mac_ether_offload_info_t * meoi)598 nvlist_to_meoi(nvlist_t *results, mac_ether_offload_info_t *meoi)
599 {
600 	uint64_t u64_val;
601 	int int_val;
602 	uint16_t u16_val;
603 	uint8_t u8_val;
604 
605 	bzero(meoi, sizeof (*meoi));
606 	if (nvlist_lookup_int32(results, "meoi_flags", &int_val) == 0) {
607 		meoi->meoi_flags = int_val;
608 	}
609 	if (nvlist_lookup_uint64(results, "meoi_len", &u64_val) == 0) {
610 		meoi->meoi_len = u64_val;
611 	}
612 	if (nvlist_lookup_uint8(results, "meoi_l2hlen", &u8_val) == 0) {
613 		meoi->meoi_l2hlen = u8_val;
614 	}
615 	if (nvlist_lookup_uint16(results, "meoi_l3proto", &u16_val) == 0) {
616 		meoi->meoi_l3proto = u16_val;
617 	}
618 	if (nvlist_lookup_uint16(results, "meoi_l3hlen", &u16_val) == 0) {
619 		meoi->meoi_l3hlen = u16_val;
620 	}
621 	if (nvlist_lookup_uint8(results, "meoi_l4proto", &u8_val) == 0) {
622 		meoi->meoi_l4proto = u8_val;
623 	}
624 	if (nvlist_lookup_uint8(results, "meoi_l4hlen", &u8_val) == 0) {
625 		meoi->meoi_l4hlen = u8_val;
626 	}
627 }
628 
629 static mblk_t *
alloc_split_pkt(ktest_ctx_hdl_t * ctx,nvlist_t * nvl,const char * pkt_field)630 alloc_split_pkt(ktest_ctx_hdl_t *ctx, nvlist_t *nvl, const char *pkt_field)
631 {
632 	uchar_t *pkt_bytes;
633 	uint_t pkt_sz;
634 
635 	if (nvlist_lookup_byte_array(nvl, pkt_field, &pkt_bytes,
636 	    &pkt_sz) != 0) {
637 		KT_ERROR(ctx, "Input missing %s field", pkt_field);
638 		return (NULL);
639 	}
640 
641 	const uint32_t *splits = NULL;
642 	uint_t num_splits = 0;
643 	(void) nvlist_lookup_uint32_array(nvl, "splits", (uint32_t **)&splits,
644 	    &num_splits);
645 
646 	uint_t split_idx = 0;
647 	mblk_t *result = NULL, *tail = NULL;
648 
649 	do {
650 		uint_t block_sz = pkt_sz;
651 		if (split_idx < num_splits) {
652 			block_sz = MIN(block_sz, splits[split_idx]);
653 		}
654 
655 		mblk_t *mp = allocb(block_sz, 0);
656 		if (mp == NULL) {
657 			KT_ERROR(ctx, "mblk alloc failure");
658 			freemsg(result);
659 			return (NULL);
660 		}
661 
662 		if (result == NULL) {
663 			result = mp;
664 		} else {
665 			tail->b_cont = mp;
666 		}
667 		tail = mp;
668 
669 		if (block_sz != 0) {
670 			bcopy(pkt_bytes, mp->b_wptr, block_sz);
671 			mp->b_wptr += block_sz;
672 		}
673 		pkt_sz -= block_sz;
674 		pkt_bytes += block_sz;
675 		split_idx++;
676 	} while (pkt_sz > 0);
677 
678 	return (result);
679 }
680 
681 /*
682  * mac_ether_offload_info tests expect the following as input (via packed
683  * nvlist)
684  *
685  * - pkt_bytes (byte array): packet bytes to parse
686  * - splits (uint32 array, optional): byte sizes to split packet into mblks
687  * - results (nvlist): mac_ether_offload_info result struct to compare
688  *   - Field names and types should match those in the mac_ether_offload_info
689  *     struct. Any fields not specified will be assumed to be zero.
690  *
691  * For mac_partial_offload_info tests, two additional fields are parsed:
692  *
693  * - offset (uint32, optional): offset into the packet at which the parsing
694  *   should begin
695  * - partial (nvlist): mac_ether_offload_info input struct to be used as
696  *   starting point for partial parsing
697  */
698 static boolean_t
meoi_test_parse_input(ktest_ctx_hdl_t * ctx,meoi_test_params_t * mtp,boolean_t test_partial)699 meoi_test_parse_input(ktest_ctx_hdl_t *ctx, meoi_test_params_t *mtp,
700     boolean_t test_partial)
701 {
702 	uchar_t *bytes;
703 	size_t num_bytes = 0;
704 
705 	ktest_get_input(ctx, &bytes, &num_bytes);
706 	bzero(mtp, sizeof (*mtp));
707 
708 	nvlist_t *params = NULL;
709 	if (nvlist_unpack((char *)bytes, num_bytes, &params, KM_SLEEP) != 0) {
710 		KT_ERROR(ctx, "Invalid nvlist input");
711 		return (B_FALSE);
712 	}
713 
714 	nvlist_t *results;
715 	if (nvlist_lookup_nvlist(params, "results", &results) != 0) {
716 		KT_ERROR(ctx, "Input missing results field");
717 		nvlist_free(params);
718 		return (B_FALSE);
719 	}
720 
721 	if (test_partial) {
722 		nvlist_t *partial;
723 		if (nvlist_lookup_nvlist(params, "partial", &partial) != 0) {
724 			KT_ERROR(ctx, "Input missing partial field");
725 			nvlist_free(params);
726 			return (B_FALSE);
727 		} else {
728 			nvlist_to_meoi(partial, &mtp->mtp_partial);
729 		}
730 
731 		(void) nvlist_lookup_uint32(params, "offset", &mtp->mtp_offset);
732 	}
733 
734 	mtp->mtp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
735 	if (mtp->mtp_mp == NULL) {
736 		nvlist_free(params);
737 		return (B_FALSE);
738 	}
739 
740 	nvlist_to_meoi(results, &mtp->mtp_results);
741 
742 	nvlist_free(params);
743 	return (B_TRUE);
744 }
745 
746 void
mac_ether_offload_info_test(ktest_ctx_hdl_t * ctx)747 mac_ether_offload_info_test(ktest_ctx_hdl_t *ctx)
748 {
749 	meoi_test_params_t mtp = { 0 };
750 
751 	if (!meoi_test_parse_input(ctx, &mtp, B_FALSE)) {
752 		return;
753 	}
754 
755 	mac_ether_offload_info_t result;
756 	mac_ether_offload_info(mtp.mtp_mp, &result);
757 
758 	const mac_ether_offload_info_t *expect = &mtp.mtp_results;
759 	KT_ASSERT3UG(result.meoi_flags, ==, expect->meoi_flags, ctx, done);
760 	KT_ASSERT3UG(result.meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
761 	KT_ASSERT3UG(result.meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
762 	KT_ASSERT3UG(result.meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
763 	KT_ASSERT3UG(result.meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
764 	KT_ASSERT3UG(result.meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
765 
766 	KT_PASS(ctx);
767 
768 done:
769 	freemsg(mtp.mtp_mp);
770 }
771 
772 void
mac_partial_offload_info_test(ktest_ctx_hdl_t * ctx)773 mac_partial_offload_info_test(ktest_ctx_hdl_t *ctx)
774 {
775 	meoi_test_params_t mtp = { 0 };
776 
777 	if (!meoi_test_parse_input(ctx, &mtp, B_TRUE)) {
778 		return;
779 	}
780 
781 	mac_ether_offload_info_t *result = &mtp.mtp_partial;
782 	mac_partial_offload_info(mtp.mtp_mp, mtp.mtp_offset, result);
783 
784 	const mac_ether_offload_info_t *expect = &mtp.mtp_results;
785 	KT_ASSERT3UG(result->meoi_flags, ==, expect->meoi_flags, ctx, done);
786 	KT_ASSERT3UG(result->meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
787 	KT_ASSERT3UG(result->meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
788 	KT_ASSERT3UG(result->meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
789 	KT_ASSERT3UG(result->meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
790 	KT_ASSERT3UG(result->meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
791 
792 	KT_PASS(ctx);
793 
794 done:
795 	freemsg(mtp.mtp_mp);
796 }
797 
798 typedef struct ether_test_params {
799 	mblk_t		*etp_mp;
800 	uint32_t	etp_tci;
801 	uint8_t		etp_dstaddr[ETHERADDRL];
802 	boolean_t	etp_is_err;
803 } ether_test_params_t;
804 
805 /*
806  * mac_ether_l2_info tests expect the following as input (via packed nvlist)
807  *
808  * - pkt_bytes (byte array): packet bytes to parse
809  * - splits (uint32 array, optional): byte sizes to split packet into mblks
810  * - tci (uint32): VLAN TCI result value to compare
811  * - dstaddr (byte array): MAC addr result value to compare
812  * - is_err (boolean): if test function should return error
813  */
814 static boolean_t
ether_parse_input(ktest_ctx_hdl_t * ctx,ether_test_params_t * etp)815 ether_parse_input(ktest_ctx_hdl_t *ctx, ether_test_params_t *etp)
816 {
817 	uchar_t *bytes;
818 	size_t num_bytes = 0;
819 
820 	ktest_get_input(ctx, &bytes, &num_bytes);
821 	bzero(etp, sizeof (*etp));
822 
823 	nvlist_t *params = NULL;
824 	if (nvlist_unpack((char *)bytes, num_bytes, &params, KM_SLEEP) != 0) {
825 		KT_ERROR(ctx, "Invalid nvlist input");
826 		return (B_FALSE);
827 	}
828 
829 	etp->etp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
830 	if (etp->etp_mp == NULL) {
831 		nvlist_free(params);
832 		return (B_FALSE);
833 	}
834 
835 	if (nvlist_lookup_uint32(params, "tci", &etp->etp_tci) != 0) {
836 		KT_ERROR(ctx, "Input missing tci field");
837 		nvlist_free(params);
838 		return (B_FALSE);
839 	}
840 
841 	uchar_t *dstaddr;
842 	uint_t dstaddr_sz;
843 	if (nvlist_lookup_byte_array(params, "dstaddr", &dstaddr,
844 	    &dstaddr_sz) != 0) {
845 		KT_ERROR(ctx, "Input missing dstaddr field");
846 		nvlist_free(params);
847 		return (B_FALSE);
848 	} else if (dstaddr_sz != ETHERADDRL) {
849 		KT_ERROR(ctx, "bad dstaddr size %u != %u", dstaddr_sz,
850 		    ETHERADDRL);
851 		nvlist_free(params);
852 		return (B_FALSE);
853 	}
854 	bcopy(dstaddr, &etp->etp_dstaddr, ETHERADDRL);
855 
856 	etp->etp_is_err = nvlist_lookup_boolean(params, "is_err") == 0;
857 
858 	nvlist_free(params);
859 	return (B_TRUE);
860 }
861 
862 void
mac_ether_l2_info_test(ktest_ctx_hdl_t * ctx)863 mac_ether_l2_info_test(ktest_ctx_hdl_t *ctx)
864 {
865 	ether_test_params_t etp = { 0 };
866 
867 	if (!ether_parse_input(ctx, &etp)) {
868 		return;
869 	}
870 
871 	uint8_t dstaddr[ETHERADDRL];
872 	uint32_t vlan_tci = 0;
873 	const boolean_t is_err =
874 	    !mac_ether_l2_info(etp.etp_mp, dstaddr, &vlan_tci);
875 
876 	KT_ASSERTG(is_err == etp.etp_is_err, ctx, done);
877 	KT_ASSERTG(bcmp(dstaddr, etp.etp_dstaddr, ETHERADDRL) == 0, ctx,
878 	    done);
879 	KT_ASSERT3UG(vlan_tci, ==, etp.etp_tci, ctx, done);
880 
881 	KT_PASS(ctx);
882 
883 done:
884 	freemsg(etp.etp_mp);
885 }
886 
887 
888 static struct modlmisc mac_ktest_modlmisc = {
889 	.misc_modops = &mod_miscops,
890 	.misc_linkinfo = "mac ktest module"
891 };
892 
893 static struct modlinkage mac_ktest_modlinkage = {
894 	.ml_rev = MODREV_1,
895 	.ml_linkage = { &mac_ktest_modlmisc, NULL }
896 };
897 
898 int
_init()899 _init()
900 {
901 	int ret;
902 	ktest_module_hdl_t *km = NULL;
903 	ktest_suite_hdl_t *ks = NULL;
904 
905 	VERIFY0(ktest_create_module("mac", &km));
906 	VERIFY0(ktest_add_suite(km, "checksum", &ks));
907 	VERIFY0(ktest_add_test(ks, "mac_sw_cksum_test",
908 	    mac_sw_cksum_test, KTEST_FLAG_INPUT));
909 
910 	ks = NULL;
911 	VERIFY0(ktest_add_suite(km, "lso", &ks));
912 	VERIFY0(ktest_add_test(ks, "mac_sw_lso_test",
913 	    mac_sw_lso_test, KTEST_FLAG_INPUT));
914 
915 	ks = NULL;
916 	VERIFY0(ktest_add_suite(km, "parsing", &ks));
917 	VERIFY0(ktest_add_test(ks, "mac_ether_offload_info_test",
918 	    mac_ether_offload_info_test, KTEST_FLAG_INPUT));
919 	VERIFY0(ktest_add_test(ks, "mac_partial_offload_info_test",
920 	    mac_partial_offload_info_test, KTEST_FLAG_INPUT));
921 	VERIFY0(ktest_add_test(ks, "mac_ether_l2_info_test",
922 	    mac_ether_l2_info_test, KTEST_FLAG_INPUT));
923 
924 	if ((ret = ktest_register_module(km)) != 0) {
925 		ktest_free_module(km);
926 		return (ret);
927 	}
928 
929 	if ((ret = mod_install(&mac_ktest_modlinkage)) != 0) {
930 		ktest_unregister_module("mac");
931 		return (ret);
932 	}
933 
934 	return (0);
935 }
936 
937 int
_fini(void)938 _fini(void)
939 {
940 	ktest_unregister_module("mac");
941 	return (mod_remove(&mac_ktest_modlinkage));
942 }
943 
944 int
_info(struct modinfo * modinfop)945 _info(struct modinfo *modinfop)
946 {
947 	return (mod_info(&mac_ktest_modlinkage, modinfop));
948 }
949