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 * 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 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, ¶ms, 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 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 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 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 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 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 * 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 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, ¶ms, 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 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 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 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, ¶ms, 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 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 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 816 _fini(void) 817 { 818 ktest_unregister_module("mac"); 819 return (mod_remove(&mac_ktest_modlinkage)); 820 } 821 822 int 823 _info(struct modinfo *modinfop) 824 { 825 return (mod_info(&mac_ktest_modlinkage, modinfop)); 826 } 827