xref: /illumos-gate/usr/src/uts/common/io/mac/mac_ktest.c (revision 5d086a78863b0e948d240467a191382719b8d813)
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 2026 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 		for (mblk_t *curr = mp; curr != NULL; curr = curr->b_next) {
524 			KT_ASSERT(OK_32PTR(curr->b_rptr + meoi.meoi_l2hlen),
525 			    ctx);
526 		}
527 
528 		boolean_t success = (etp->etp_outputs == NULL) ?
529 		    pkt_compare(ctx, etp->etp_raw, etp->etp_raw_sz, mp) :
530 		    pkt_result_compare_chain(ctx, etp, mp);
531 		if (!success) {
532 			return;
533 		}
534 	} else {
535 		KT_SKIP(ctx, "offloads unsupported for packet");
536 		return;
537 	}
538 
539 	KT_PASS(ctx);
540 }
541 
542 /*
543  * Verify checksum emulation against an arbitrary chain of packets.  If the
544  * packet is of a supported protocol, any L3 and L4 checksums are cleared, and
545  * then mac_hw_emul() is called to perform the offload emulation.  Afterwards,
546  * the packet is compared to see if it equals the input, which is assumed to
547  * have correct checksums.
548  */
549 static void
mac_sw_cksum_test(ktest_ctx_hdl_t * ctx)550 mac_sw_cksum_test(ktest_ctx_hdl_t *ctx)
551 {
552 	emul_test_params_t etp;
553 	if (!emul_test_parse_input(ctx, &etp)) {
554 		goto cleanup;
555 	}
556 
557 	mac_hw_emul_test(ctx, &etp);
558 
559 cleanup:
560 	etp_free(&etp);
561 }
562 
563 /*
564  * Verify mac_sw_lso() (and checksum) emulation against an arbitrary input
565  * packet.  This test functions like mac_sw_cksum_test insofar as checksums can
566  * be customised, but also sets HW_LSO on any input packet, and compares the
567  * outputs against a mandatory chain of packets provided by the caller.
568  */
569 static void
mac_sw_lso_test(ktest_ctx_hdl_t * ctx)570 mac_sw_lso_test(ktest_ctx_hdl_t *ctx)
571 {
572 	emul_test_params_t etp;
573 	if (!emul_test_parse_input(ctx, &etp)) {
574 		goto cleanup;
575 	}
576 
577 	if (etp.etp_mss == 0) {
578 		KT_ERROR(ctx, "invalid MSS for LSO");
579 		goto cleanup;
580 	}
581 
582 	if (etp.etp_outputs == NULL) {
583 		KT_ERROR(ctx, "LSO tests require explicit packet list");
584 		goto cleanup;
585 	}
586 
587 	etp.etp_do_lso = B_TRUE;
588 
589 	mac_hw_emul_test(ctx, &etp);
590 
591 cleanup:
592 	etp_free(&etp);
593 }
594 
595 typedef struct meoi_test_params {
596 	mblk_t				*mtp_mp;
597 	mac_ether_offload_info_t	mtp_partial;
598 	mac_ether_offload_info_t	mtp_results;
599 	uint_t				mtp_offset;
600 } meoi_test_params_t;
601 
602 static void
nvlist_to_meoi(nvlist_t * results,mac_ether_offload_info_t * meoi)603 nvlist_to_meoi(nvlist_t *results, mac_ether_offload_info_t *meoi)
604 {
605 	uint64_t u64_val;
606 	int int_val;
607 	uint16_t u16_val;
608 	uint8_t u8_val;
609 
610 	bzero(meoi, sizeof (*meoi));
611 	if (nvlist_lookup_int32(results, "meoi_flags", &int_val) == 0) {
612 		meoi->meoi_flags = int_val;
613 	}
614 	if (nvlist_lookup_uint64(results, "meoi_len", &u64_val) == 0) {
615 		meoi->meoi_len = u64_val;
616 	}
617 	if (nvlist_lookup_uint8(results, "meoi_l2hlen", &u8_val) == 0) {
618 		meoi->meoi_l2hlen = u8_val;
619 	}
620 	if (nvlist_lookup_uint16(results, "meoi_l3proto", &u16_val) == 0) {
621 		meoi->meoi_l3proto = u16_val;
622 	}
623 	if (nvlist_lookup_uint16(results, "meoi_l3hlen", &u16_val) == 0) {
624 		meoi->meoi_l3hlen = u16_val;
625 	}
626 	if (nvlist_lookup_uint8(results, "meoi_l4proto", &u8_val) == 0) {
627 		meoi->meoi_l4proto = u8_val;
628 	}
629 	if (nvlist_lookup_uint8(results, "meoi_l4hlen", &u8_val) == 0) {
630 		meoi->meoi_l4hlen = u8_val;
631 	}
632 }
633 
634 static mblk_t *
alloc_split_pkt(ktest_ctx_hdl_t * ctx,nvlist_t * nvl,const char * pkt_field)635 alloc_split_pkt(ktest_ctx_hdl_t *ctx, nvlist_t *nvl, const char *pkt_field)
636 {
637 	uchar_t *pkt_bytes;
638 	uint_t pkt_sz;
639 
640 	if (nvlist_lookup_byte_array(nvl, pkt_field, &pkt_bytes,
641 	    &pkt_sz) != 0) {
642 		KT_ERROR(ctx, "Input missing %s field", pkt_field);
643 		return (NULL);
644 	}
645 
646 	const uint32_t *splits = NULL;
647 	uint_t num_splits = 0;
648 	(void) nvlist_lookup_uint32_array(nvl, "splits", (uint32_t **)&splits,
649 	    &num_splits);
650 
651 	uint_t split_idx = 0;
652 	mblk_t *result = NULL, *tail = NULL;
653 
654 	do {
655 		uint_t block_sz = pkt_sz;
656 		if (split_idx < num_splits) {
657 			block_sz = MIN(block_sz, splits[split_idx]);
658 		}
659 
660 		mblk_t *mp = allocb(block_sz, 0);
661 		if (mp == NULL) {
662 			KT_ERROR(ctx, "mblk alloc failure");
663 			freemsg(result);
664 			return (NULL);
665 		}
666 
667 		if (result == NULL) {
668 			result = mp;
669 		} else {
670 			tail->b_cont = mp;
671 		}
672 		tail = mp;
673 
674 		if (block_sz != 0) {
675 			bcopy(pkt_bytes, mp->b_wptr, block_sz);
676 			mp->b_wptr += block_sz;
677 		}
678 		pkt_sz -= block_sz;
679 		pkt_bytes += block_sz;
680 		split_idx++;
681 	} while (pkt_sz > 0);
682 
683 	return (result);
684 }
685 
686 /*
687  * mac_ether_offload_info tests expect the following as input (via packed
688  * nvlist)
689  *
690  * - pkt_bytes (byte array): packet bytes to parse
691  * - splits (uint32 array, optional): byte sizes to split packet into mblks
692  * - results (nvlist): mac_ether_offload_info result struct to compare
693  *   - Field names and types should match those in the mac_ether_offload_info
694  *     struct. Any fields not specified will be assumed to be zero.
695  *
696  * For mac_partial_offload_info tests, two additional fields are parsed:
697  *
698  * - offset (uint32, optional): offset into the packet at which the parsing
699  *   should begin
700  * - partial (nvlist): mac_ether_offload_info input struct to be used as
701  *   starting point for partial parsing
702  */
703 static boolean_t
meoi_test_parse_input(ktest_ctx_hdl_t * ctx,meoi_test_params_t * mtp,boolean_t test_partial)704 meoi_test_parse_input(ktest_ctx_hdl_t *ctx, meoi_test_params_t *mtp,
705     boolean_t test_partial)
706 {
707 	uchar_t *bytes;
708 	size_t num_bytes = 0;
709 
710 	ktest_get_input(ctx, &bytes, &num_bytes);
711 	bzero(mtp, sizeof (*mtp));
712 
713 	nvlist_t *params = NULL;
714 	if (nvlist_unpack((char *)bytes, num_bytes, &params, KM_SLEEP) != 0) {
715 		KT_ERROR(ctx, "Invalid nvlist input");
716 		return (B_FALSE);
717 	}
718 
719 	nvlist_t *results;
720 	if (nvlist_lookup_nvlist(params, "results", &results) != 0) {
721 		KT_ERROR(ctx, "Input missing results field");
722 		nvlist_free(params);
723 		return (B_FALSE);
724 	}
725 
726 	if (test_partial) {
727 		nvlist_t *partial;
728 		if (nvlist_lookup_nvlist(params, "partial", &partial) != 0) {
729 			KT_ERROR(ctx, "Input missing partial field");
730 			nvlist_free(params);
731 			return (B_FALSE);
732 		} else {
733 			nvlist_to_meoi(partial, &mtp->mtp_partial);
734 		}
735 
736 		(void) nvlist_lookup_uint32(params, "offset", &mtp->mtp_offset);
737 	}
738 
739 	mtp->mtp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
740 	if (mtp->mtp_mp == NULL) {
741 		nvlist_free(params);
742 		return (B_FALSE);
743 	}
744 
745 	nvlist_to_meoi(results, &mtp->mtp_results);
746 
747 	nvlist_free(params);
748 	return (B_TRUE);
749 }
750 
751 void
mac_ether_offload_info_test(ktest_ctx_hdl_t * ctx)752 mac_ether_offload_info_test(ktest_ctx_hdl_t *ctx)
753 {
754 	meoi_test_params_t mtp = { 0 };
755 
756 	if (!meoi_test_parse_input(ctx, &mtp, B_FALSE)) {
757 		return;
758 	}
759 
760 	mac_ether_offload_info_t result;
761 	mac_ether_offload_info(mtp.mtp_mp, &result);
762 
763 	const mac_ether_offload_info_t *expect = &mtp.mtp_results;
764 	KT_ASSERT3UG(result.meoi_flags, ==, expect->meoi_flags, ctx, done);
765 	KT_ASSERT3UG(result.meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
766 	KT_ASSERT3UG(result.meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
767 	KT_ASSERT3UG(result.meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
768 	KT_ASSERT3UG(result.meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
769 	KT_ASSERT3UG(result.meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
770 
771 	KT_PASS(ctx);
772 
773 done:
774 	freemsg(mtp.mtp_mp);
775 }
776 
777 void
mac_partial_offload_info_test(ktest_ctx_hdl_t * ctx)778 mac_partial_offload_info_test(ktest_ctx_hdl_t *ctx)
779 {
780 	meoi_test_params_t mtp = { 0 };
781 
782 	if (!meoi_test_parse_input(ctx, &mtp, B_TRUE)) {
783 		return;
784 	}
785 
786 	mac_ether_offload_info_t *result = &mtp.mtp_partial;
787 	mac_partial_offload_info(mtp.mtp_mp, mtp.mtp_offset, result);
788 
789 	const mac_ether_offload_info_t *expect = &mtp.mtp_results;
790 	KT_ASSERT3UG(result->meoi_flags, ==, expect->meoi_flags, ctx, done);
791 	KT_ASSERT3UG(result->meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
792 	KT_ASSERT3UG(result->meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
793 	KT_ASSERT3UG(result->meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
794 	KT_ASSERT3UG(result->meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
795 	KT_ASSERT3UG(result->meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
796 
797 	KT_PASS(ctx);
798 
799 done:
800 	freemsg(mtp.mtp_mp);
801 }
802 
803 typedef struct ether_test_params {
804 	mblk_t		*etp_mp;
805 	uint32_t	etp_tci;
806 	uint8_t		etp_dstaddr[ETHERADDRL];
807 	boolean_t	etp_is_err;
808 } ether_test_params_t;
809 
810 /*
811  * mac_ether_l2_info tests expect the following as input (via packed nvlist)
812  *
813  * - pkt_bytes (byte array): packet bytes to parse
814  * - splits (uint32 array, optional): byte sizes to split packet into mblks
815  * - tci (uint32): VLAN TCI result value to compare
816  * - dstaddr (byte array): MAC addr result value to compare
817  * - is_err (boolean): if test function should return error
818  */
819 static boolean_t
ether_parse_input(ktest_ctx_hdl_t * ctx,ether_test_params_t * etp)820 ether_parse_input(ktest_ctx_hdl_t *ctx, ether_test_params_t *etp)
821 {
822 	uchar_t *bytes;
823 	size_t num_bytes = 0;
824 
825 	ktest_get_input(ctx, &bytes, &num_bytes);
826 	bzero(etp, sizeof (*etp));
827 
828 	nvlist_t *params = NULL;
829 	if (nvlist_unpack((char *)bytes, num_bytes, &params, KM_SLEEP) != 0) {
830 		KT_ERROR(ctx, "Invalid nvlist input");
831 		return (B_FALSE);
832 	}
833 
834 	etp->etp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
835 	if (etp->etp_mp == NULL) {
836 		nvlist_free(params);
837 		return (B_FALSE);
838 	}
839 
840 	if (nvlist_lookup_uint32(params, "tci", &etp->etp_tci) != 0) {
841 		KT_ERROR(ctx, "Input missing tci field");
842 		nvlist_free(params);
843 		return (B_FALSE);
844 	}
845 
846 	uchar_t *dstaddr;
847 	uint_t dstaddr_sz;
848 	if (nvlist_lookup_byte_array(params, "dstaddr", &dstaddr,
849 	    &dstaddr_sz) != 0) {
850 		KT_ERROR(ctx, "Input missing dstaddr field");
851 		nvlist_free(params);
852 		return (B_FALSE);
853 	} else if (dstaddr_sz != ETHERADDRL) {
854 		KT_ERROR(ctx, "bad dstaddr size %u != %u", dstaddr_sz,
855 		    ETHERADDRL);
856 		nvlist_free(params);
857 		return (B_FALSE);
858 	}
859 	bcopy(dstaddr, &etp->etp_dstaddr, ETHERADDRL);
860 
861 	etp->etp_is_err = nvlist_lookup_boolean(params, "is_err") == 0;
862 
863 	nvlist_free(params);
864 	return (B_TRUE);
865 }
866 
867 void
mac_ether_l2_info_test(ktest_ctx_hdl_t * ctx)868 mac_ether_l2_info_test(ktest_ctx_hdl_t *ctx)
869 {
870 	ether_test_params_t etp = { 0 };
871 
872 	if (!ether_parse_input(ctx, &etp)) {
873 		return;
874 	}
875 
876 	uint8_t dstaddr[ETHERADDRL];
877 	uint32_t vlan_tci = 0;
878 	const boolean_t is_err =
879 	    !mac_ether_l2_info(etp.etp_mp, dstaddr, &vlan_tci);
880 
881 	KT_ASSERTG(is_err == etp.etp_is_err, ctx, done);
882 	KT_ASSERTG(bcmp(dstaddr, etp.etp_dstaddr, ETHERADDRL) == 0, ctx,
883 	    done);
884 	KT_ASSERT3UG(vlan_tci, ==, etp.etp_tci, ctx, done);
885 
886 	KT_PASS(ctx);
887 
888 done:
889 	freemsg(etp.etp_mp);
890 }
891 
892 
893 static struct modlmisc mac_ktest_modlmisc = {
894 	.misc_modops = &mod_miscops,
895 	.misc_linkinfo = "mac ktest module"
896 };
897 
898 static struct modlinkage mac_ktest_modlinkage = {
899 	.ml_rev = MODREV_1,
900 	.ml_linkage = { &mac_ktest_modlmisc, NULL }
901 };
902 
903 int
_init()904 _init()
905 {
906 	int ret;
907 	ktest_module_hdl_t *km = NULL;
908 	ktest_suite_hdl_t *ks = NULL;
909 
910 	VERIFY0(ktest_create_module("mac", &km));
911 	VERIFY0(ktest_add_suite(km, "checksum", &ks));
912 	VERIFY0(ktest_add_test(ks, "mac_sw_cksum_test",
913 	    mac_sw_cksum_test, KTEST_FLAG_INPUT));
914 
915 	ks = NULL;
916 	VERIFY0(ktest_add_suite(km, "lso", &ks));
917 	VERIFY0(ktest_add_test(ks, "mac_sw_lso_test",
918 	    mac_sw_lso_test, KTEST_FLAG_INPUT));
919 
920 	ks = NULL;
921 	VERIFY0(ktest_add_suite(km, "parsing", &ks));
922 	VERIFY0(ktest_add_test(ks, "mac_ether_offload_info_test",
923 	    mac_ether_offload_info_test, KTEST_FLAG_INPUT));
924 	VERIFY0(ktest_add_test(ks, "mac_partial_offload_info_test",
925 	    mac_partial_offload_info_test, KTEST_FLAG_INPUT));
926 	VERIFY0(ktest_add_test(ks, "mac_ether_l2_info_test",
927 	    mac_ether_l2_info_test, KTEST_FLAG_INPUT));
928 
929 	if ((ret = ktest_register_module(km)) != 0) {
930 		ktest_free_module(km);
931 		return (ret);
932 	}
933 
934 	if ((ret = mod_install(&mac_ktest_modlinkage)) != 0) {
935 		ktest_unregister_module("mac");
936 		return (ret);
937 	}
938 
939 	return (0);
940 }
941 
942 int
_fini(void)943 _fini(void)
944 {
945 	ktest_unregister_module("mac");
946 	return (mod_remove(&mac_ktest_modlinkage));
947 }
948 
949 int
_info(struct modinfo * modinfop)950 _info(struct modinfo *modinfop)
951 {
952 	return (mod_info(&mac_ktest_modlinkage, modinfop));
953 }
954