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, ¶ms, 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, ¶ms, 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, ¶ms, 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