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