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 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 * 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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 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 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 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 938 _fini(void) 939 { 940 ktest_unregister_module("mac"); 941 return (mod_remove(&mac_ktest_modlinkage)); 942 } 943 944 int 945 _info(struct modinfo *modinfop) 946 { 947 return (mod_info(&mac_ktest_modlinkage, modinfop)); 948 } 949