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