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 <inet/tcp.h>
23 #include <sys/dlpi.h>
24 #include <sys/ethernet.h>
25 #include <sys/ktest.h>
26 #include <sys/mac_provider.h>
27
28 static uint32_t
mt_pseudo_sum(const uint8_t proto,ipha_t * ip)29 mt_pseudo_sum(const uint8_t proto, ipha_t *ip)
30 {
31 const uint32_t ip_hdr_sz = IPH_HDR_LENGTH(ip);
32 const ipaddr_t src = ip->ipha_src;
33 const ipaddr_t dst = ip->ipha_dst;
34 uint16_t len;
35 uint32_t sum = 0;
36
37 switch (proto) {
38 case IPPROTO_TCP:
39 sum = IP_TCP_CSUM_COMP;
40 break;
41
42 case IPPROTO_UDP:
43 sum = IP_UDP_CSUM_COMP;
44 break;
45 }
46
47 len = ntohs(ip->ipha_length) - ip_hdr_sz;
48 sum += (dst >> 16) + (dst & 0xFFFF) + (src >> 16) + (src & 0xFFFF);
49 sum += htons(len);
50 return (sum);
51 }
52
53 /*
54 * An implementation of the internet checksum inspired by RFC 1071.
55 * This implementation is as naive as possible. It serves as the
56 * reference point for testing the optimized versions in the rest of
57 * our stack. This is no place for optimization or cleverness.
58 *
59 * Arguments
60 *
61 * initial: The initial sum value.
62 *
63 * addr: Pointer to the beginning of the byte stream to sum.
64 *
65 * len: The number of bytes to sum.
66 *
67 * Return
68 *
69 * The resulting internet checksum.
70 */
71 static uint32_t
mt_rfc1071_sum(uint32_t initial,uint16_t * addr,size_t len)72 mt_rfc1071_sum(uint32_t initial, uint16_t *addr, size_t len)
73 {
74 uint32_t sum = initial;
75
76 while (len > 1) {
77 sum += *addr;
78 addr++;
79 len -= 2;
80 }
81
82 if (len == 1) {
83 sum += *((uint8_t *)addr);
84 }
85
86 while ((sum >> 16) != 0) {
87 sum = (sum >> 16) + (sum & 0xFFFF);
88 }
89
90 return (~sum & 0xFFFF);
91 }
92
93 typedef boolean_t (*mac_sw_cksum_ipv4_t)(mblk_t *, uint32_t, ipha_t *,
94 const char **);
95
96 /*
97 * Fill out a basic TCP header in the given mblk at the given offset.
98 * A TCP header should never straddle an mblk boundary.
99 */
100 static tcpha_t *
mt_tcp_basic_hdr(mblk_t * mp,uint16_t offset,uint16_t lport,uint16_t fport,uint32_t seq,uint32_t ack,uint8_t flags,uint16_t win)101 mt_tcp_basic_hdr(mblk_t *mp, uint16_t offset, uint16_t lport, uint16_t fport,
102 uint32_t seq, uint32_t ack, uint8_t flags, uint16_t win)
103 {
104 tcpha_t *tcp = (tcpha_t *)(mp->b_rptr + offset);
105
106 VERIFY3U((uintptr_t)tcp + sizeof (*tcp), <=, mp->b_wptr);
107 tcp->tha_lport = htons(lport);
108 tcp->tha_fport = htons(fport);
109 tcp->tha_seq = htonl(seq);
110 tcp->tha_ack = htonl(ack);
111 tcp->tha_offset_and_reserved = 0x5 << 4;
112 tcp->tha_flags = flags;
113 tcp->tha_win = htons(win);
114 tcp->tha_sum = 0x0;
115 tcp->tha_urp = 0x0;
116
117 return (tcp);
118 }
119
120 static ipha_t *
mt_ipv4_simple_hdr(mblk_t * mp,uint16_t offset,uint16_t datum_length,uint16_t ident,uint8_t proto,char * src,char * dst)121 mt_ipv4_simple_hdr(mblk_t *mp, uint16_t offset, uint16_t datum_length,
122 uint16_t ident, uint8_t proto, char *src, char *dst)
123 {
124 uint32_t srcaddr, dstaddr;
125 ipha_t *ip = (ipha_t *)(mp->b_rptr + offset);
126
127 VERIFY3U((uintptr_t)ip + sizeof (*ip), <=, mp->b_wptr);
128
129 VERIFY(inet_pton(AF_INET, src, &srcaddr));
130 VERIFY(inet_pton(AF_INET, dst, &dstaddr));
131 ip->ipha_version_and_hdr_length = IP_SIMPLE_HDR_VERSION;
132 ip->ipha_type_of_service = 0x0;
133 ip->ipha_length = htons(sizeof (*ip) + datum_length);
134 ip->ipha_ident = htons(ident);
135 ip->ipha_fragment_offset_and_flags = IPH_DF_HTONS;
136 ip->ipha_ttl = 255;
137 ip->ipha_protocol = proto;
138 ip->ipha_hdr_checksum = 0x0;
139 ip->ipha_src = srcaddr;
140 ip->ipha_dst = dstaddr;
141
142 return (ip);
143 }
144
145 static struct ether_header *
mt_ether_hdr(mblk_t * mp,uint16_t offset,char * dst,char * src,uint16_t etype)146 mt_ether_hdr(mblk_t *mp, uint16_t offset, char *dst, char *src, uint16_t etype)
147 {
148 char *byte = dst;
149 unsigned long tmp;
150 struct ether_header *eh = (struct ether_header *)(mp->b_rptr + offset);
151
152 VERIFY3U((uintptr_t)eh + sizeof (*eh), <=, mp->b_wptr);
153
154 /* No strtok in these here parts. */
155 for (uint_t i = 0; i < 6; i++) {
156 char *end = strchr(dst, ':');
157 VERIFY3P(end, !=, NULL);
158 VERIFY0(ddi_strtoul(byte, NULL, 16, &tmp));
159 VERIFY3U(tmp, <=, 255);
160 eh->ether_dhost.ether_addr_octet[i] = tmp;
161 byte = end + 1;
162 }
163
164 byte = src;
165 for (uint_t i = 0; i < 6; i++) {
166 char *end = strchr(dst, ':');
167 VERIFY3P(end, !=, NULL);
168 VERIFY0(ddi_strtoul(byte, NULL, 16, &tmp));
169 VERIFY3U(tmp, <=, 255);
170 eh->ether_shost.ether_addr_octet[i] = tmp;
171 byte = end + 1;
172 }
173
174 eh->ether_type = etype;
175 return (eh);
176 }
177
178 void
mac_sw_cksum_ipv4_tcp_test(ktest_ctx_hdl_t * ctx)179 mac_sw_cksum_ipv4_tcp_test(ktest_ctx_hdl_t *ctx)
180 {
181 ddi_modhandle_t hdl = NULL;
182 mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
183 tcpha_t *tcp;
184 ipha_t *ip;
185 struct ether_header *eh;
186 mblk_t *mp = NULL;
187 char *msg = "...when it's not your turn";
188 size_t msglen = strlen(msg) + 1;
189 size_t mplen;
190 const char *err = "";
191 uint32_t sum;
192 size_t ehsz = sizeof (*eh);
193 size_t ipsz = sizeof (*ip);
194 size_t tcpsz = sizeof (*tcp);
195
196 if (ktest_hold_mod("mac", &hdl) != 0) {
197 KT_ERROR(ctx, "failed to hold 'mac' module");
198 return;
199 }
200
201 if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
202 (void **)&mac_sw_cksum_ipv4) != 0) {
203 KT_ERROR(ctx, "failed to resolve symbol %s`%s", "mac",
204 "mac_sw_cksum_ipv4");
205 goto cleanup;
206 }
207
208 mplen = ehsz + ipsz + tcpsz + msglen;
209 mp = allocb(mplen, 0);
210 KT_EASSERT3P(mp, !=, NULL, ctx);
211 mp->b_wptr = mp->b_rptr + mplen;
212 tcp = mt_tcp_basic_hdr(mp, ehsz + ipsz, 2002, 2008, 1, 166, 0, 32000);
213 ip = mt_ipv4_simple_hdr(mp, ehsz, tcpsz + msglen, 410, IPPROTO_TCP,
214 "192.168.2.4", "192.168.2.5");
215 eh = mt_ether_hdr(mp, 0, "f2:35:c2:72:26:57", "92:ce:5a:29:46:9d",
216 ETHERTYPE_IP);
217
218 bcopy(msg, mp->b_rptr + ehsz + ipsz + tcpsz, msglen);
219
220 /*
221 * It's important that we calculate the reference checksum
222 * first, because mac_sw_cksum_ipv4() populates the checksum
223 * field.
224 */
225 sum = mt_pseudo_sum(IPPROTO_TCP, ip);
226 sum = mt_rfc1071_sum(sum, (uint16_t *)(mp->b_rptr + ehsz + ipsz),
227 tcpsz + msglen);
228
229 /*
230 * The internet checksum can never be 0xFFFF, as that would
231 * indicate an input of all zeros.
232 */
233 KT_ASSERT3UG(sum, !=, 0xFFFF, ctx, cleanup);
234 KT_ASSERTG(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
235 KT_ASSERT3UG(tcp->tha_sum, !=, 0xFFFF, ctx, cleanup);
236 KT_ASSERT3UG(sum, ==, tcp->tha_sum, ctx, cleanup);
237 KT_PASS(ctx);
238
239 cleanup:
240 if (hdl != NULL) {
241 ktest_release_mod(hdl);
242 }
243
244 if (mp != NULL) {
245 freeb(mp);
246 }
247 }
248
249 /*
250 * Verify that an unexpected IP protocol results in the expect
251 * failure.
252 */
253 void
mac_sw_cksum_ipv4_bad_proto_test(ktest_ctx_hdl_t * ctx)254 mac_sw_cksum_ipv4_bad_proto_test(ktest_ctx_hdl_t *ctx)
255 {
256 ddi_modhandle_t hdl = NULL;
257 mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
258 tcpha_t *tcp;
259 ipha_t *ip;
260 struct ether_header *eh;
261 mblk_t *mp = NULL;
262 char *msg = "...when it's not your turn";
263 size_t msglen = strlen(msg) + 1;
264 size_t mplen;
265 const char *err = "";
266 size_t ehsz = sizeof (*eh);
267 size_t ipsz = sizeof (*ip);
268 size_t tcpsz = sizeof (*tcp);
269
270 if (ktest_hold_mod("mac", &hdl) != 0) {
271 KT_ERROR(ctx, "failed to hold 'mac' module");
272 return;
273 }
274
275 if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
276 (void **)&mac_sw_cksum_ipv4) != 0) {
277 KT_ERROR(ctx, "failed to resolve symbol mac`mac_sw_cksum_ipv4");
278 goto cleanup;
279 }
280
281 mplen = ehsz + ipsz + tcpsz + msglen;
282 mp = allocb(mplen, 0);
283 KT_EASSERT3P(mp, !=, NULL, ctx);
284 mp->b_wptr = mp->b_rptr + mplen;
285 tcp = mt_tcp_basic_hdr(mp, ehsz + ipsz, 2002, 2008, 1, 166, 0, 32000);
286 ip = mt_ipv4_simple_hdr(mp, ehsz, tcpsz + msglen, 410, IPPROTO_ENCAP,
287 "192.168.2.4", "192.168.2.5");
288 eh = mt_ether_hdr(mp, 0, "f2:35:c2:72:26:57", "92:ce:5a:29:46:9d",
289 ETHERTYPE_IP);
290 bcopy(msg, mp->b_rptr + ehsz + ipsz + tcpsz, msglen);
291 KT_ASSERT0G(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
292 KT_PASS(ctx);
293
294 cleanup:
295 if (hdl != NULL) {
296 ktest_release_mod(hdl);
297 }
298
299 if (mp != NULL) {
300 freeb(mp);
301 }
302 }
303
304 typedef struct snoop_pkt_record_hdr {
305 uint32_t spr_orig_len;
306 uint32_t spr_include_len;
307 uint32_t spr_record_len;
308 uint32_t spr_cumulative_drops;
309 uint32_t spr_ts_secs;
310 uint32_t spr_ts_micros;
311 } snoop_pkt_record_hdr_t;
312
313 typedef struct snoop_pkt {
314 uchar_t *sp_bytes;
315 uint16_t sp_len;
316 } snoop_pkt_t;
317
318 typedef struct snoop_iter {
319 uchar_t *sic_input; /* beginning of stream */
320 uintptr_t sic_end; /* end of stream */
321 uchar_t *sic_pos; /* current position in stream */
322 uint_t sic_pkt_num; /* current packet number, 1-based */
323 snoop_pkt_record_hdr_t *sic_pkt_hdr; /* current packet record header */
324 } snoop_iter_t;
325
326 #define PAST_END(itr, len) \
327 (((uintptr_t)(itr)->sic_pos + len) > itr->sic_end)
328
329 /*
330 * Get the next packet in the snoop stream iterator returned by
331 * mt_snoop_iter_get(). A copy of the packet is returned via the pkt
332 * pointer. The caller provides the snoop_pkt_t, and this function
333 * allocates a new buffer inside it to hold a copy of the packet's
334 * bytes. It is the responsibility of the caller to free the copy. It
335 * is recommended the caller make use of desballoc(9F) along with the
336 * snoop_pkt_free() callback. When all the packets in the stream have
337 * been read all subsequent calls to this function will set sp_bytes
338 * to NULL and sp_len to 0.
339 *
340 * The caller may optionally specify an rhdr argument in order to
341 * receive a pointer to the packet record header (unlike the packet
342 * bytes this is a pointer into the stream, not a copy).
343 */
344 static int
mt_snoop_iter_next(ktest_ctx_hdl_t * ctx,snoop_iter_t * itr,snoop_pkt_t * pkt,snoop_pkt_record_hdr_t ** rhdr)345 mt_snoop_iter_next(ktest_ctx_hdl_t *ctx, snoop_iter_t *itr, snoop_pkt_t *pkt,
346 snoop_pkt_record_hdr_t **rhdr)
347 {
348 uchar_t *pkt_start;
349
350 /*
351 * We've read exactly the number of bytes expected, this is
352 * the end.
353 */
354 if ((uintptr_t)(itr->sic_pos) == itr->sic_end) {
355 pkt->sp_bytes = NULL;
356 pkt->sp_len = 0;
357
358 if (rhdr != NULL)
359 *rhdr = NULL;
360
361 return (0);
362 }
363
364 /*
365 * A corrupted record or truncated stream could point us past
366 * the end of the stream.
367 */
368 if (PAST_END(itr, sizeof (snoop_pkt_record_hdr_t))) {
369 KT_ERROR(ctx, "record corrupted or stream truncated, read past "
370 "end of stream for record header #%d: 0x%p + %u > 0x%p",
371 itr->sic_pkt_num, itr->sic_pos,
372 sizeof (snoop_pkt_record_hdr_t), itr->sic_end);
373 return (EIO);
374 }
375
376 itr->sic_pkt_hdr = (snoop_pkt_record_hdr_t *)itr->sic_pos;
377 pkt_start = itr->sic_pos + sizeof (snoop_pkt_record_hdr_t);
378
379 /*
380 * A corrupted record or truncated stream could point us past
381 * the end of the stream.
382 */
383 if (PAST_END(itr, ntohl(itr->sic_pkt_hdr->spr_record_len))) {
384 KT_ERROR(ctx, "record corrupted or stream truncated, read past "
385 "end of stream for record #%d: 0x%p + %u > 0x%p",
386 itr->sic_pkt_num, itr->sic_pos,
387 ntohl(itr->sic_pkt_hdr->spr_record_len), itr->sic_end);
388 return (EIO);
389 }
390
391 pkt->sp_len = ntohl(itr->sic_pkt_hdr->spr_include_len);
392 pkt->sp_bytes = kmem_zalloc(pkt->sp_len, KM_SLEEP);
393 bcopy(pkt_start, pkt->sp_bytes, pkt->sp_len);
394 itr->sic_pos += ntohl(itr->sic_pkt_hdr->spr_record_len);
395 itr->sic_pkt_num++;
396
397 if (rhdr != NULL) {
398 *rhdr = itr->sic_pkt_hdr;
399 }
400
401 return (0);
402 }
403
404 /*
405 * Parse a snoop data stream (RFC 1761) provided by input and return
406 * a packet iterator to be used by mt_snoop_iter_next().
407 */
408 static int
mt_snoop_iter_get(ktest_ctx_hdl_t * ctx,uchar_t * input,const uint_t input_len,snoop_iter_t ** itr_out)409 mt_snoop_iter_get(ktest_ctx_hdl_t *ctx, uchar_t *input, const uint_t input_len,
410 snoop_iter_t **itr_out)
411 {
412 const uchar_t id[8] = { 's', 'n', 'o', 'o', 'p', '\0', '\0', '\0' };
413 uint32_t version;
414 uint32_t datalink;
415 snoop_iter_t *itr;
416
417 *itr_out = NULL;
418
419 if (input_len < 16) {
420 KT_ERROR(ctx, "snoop stream truncated at file header: %u < %u ",
421 input_len, 16);
422 return (ENOBUFS);
423 }
424
425 if (memcmp(input, &id, sizeof (id)) != 0) {
426 KT_ERROR(ctx, "snoop stream malformed identification: %x %x %x "
427 "%x %x %x %x %x", input[0], input[1], input[2], input[3],
428 input[4], input[5], input[6], input[7]);
429 return (EINVAL);
430 }
431
432 itr = kmem_zalloc(sizeof (*itr), KM_SLEEP);
433 itr->sic_input = input;
434 itr->sic_end = (uintptr_t)input + input_len;
435 itr->sic_pos = input + sizeof (id);
436 itr->sic_pkt_num = 1;
437 itr->sic_pkt_hdr = NULL;
438 version = ntohl(*(uint32_t *)itr->sic_pos);
439
440 if (version != 2) {
441 KT_ERROR(ctx, "snoop stream bad version: %u != %u", version, 2);
442 return (EINVAL);
443 }
444
445 itr->sic_pos += sizeof (version);
446 datalink = ntohl(*(uint32_t *)itr->sic_pos);
447
448 /* We expect only Ethernet. */
449 if (datalink != DL_ETHER) {
450 KT_ERROR(ctx, "snoop stream bad datalink type: %u != %u",
451 datalink, DL_ETHER);
452 kmem_free(itr, sizeof (*itr));
453 return (EINVAL);
454 }
455
456 itr->sic_pos += sizeof (datalink);
457 *itr_out = itr;
458 return (0);
459 }
460
461 static void
snoop_pkt_free(snoop_pkt_t * pkt)462 snoop_pkt_free(snoop_pkt_t *pkt)
463 {
464 kmem_free(pkt->sp_bytes, pkt->sp_len);
465 }
466
467 /*
468 * Verify mac_sw_cksum_ipv4() against an arbitrary TCP stream read
469 * from the snoop capture given as input. In order to verify the
470 * checksum all TCP/IPv4 packets must be captured in full. The snoop
471 * capture may contain non-TCP/IPv4 packets, which will be skipped
472 * over. If not a single TCP/IPv4 packet is found, the test will
473 * report an error.
474 */
475 void
mac_sw_cksum_ipv4_snoop_test(ktest_ctx_hdl_t * ctx)476 mac_sw_cksum_ipv4_snoop_test(ktest_ctx_hdl_t *ctx)
477 {
478 ddi_modhandle_t hdl = NULL;
479 mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
480 uchar_t *bytes;
481 size_t num_bytes = 0;
482 uint_t pkt_num = 0;
483 tcpha_t *tcp;
484 ipha_t *ip;
485 struct ether_header *eh;
486 mblk_t *mp = NULL;
487 const char *err = "";
488 uint32_t csum;
489 size_t ehsz, ipsz, tcpsz, msglen;
490 snoop_iter_t *itr = NULL;
491 snoop_pkt_record_hdr_t *hdr = NULL;
492 boolean_t at_least_one = B_FALSE;
493 snoop_pkt_t pkt;
494 int ret;
495
496 if (ktest_hold_mod("mac", &hdl) != 0) {
497 KT_ERROR(ctx, "failed to hold 'mac' module");
498 return;
499 }
500
501 if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
502 (void **)&mac_sw_cksum_ipv4) != 0) {
503 KT_ERROR(ctx, "failed to resolve symbol mac`mac_sw_cksum_ipv4");
504 return;
505 }
506
507 ktest_get_input(ctx, &bytes, &num_bytes);
508 ret = mt_snoop_iter_get(ctx, bytes, num_bytes, &itr);
509 if (ret != 0) {
510 /* mt_snoop_iter_get() already set error context. */
511 goto cleanup;
512 }
513
514 bzero(&pkt, sizeof (pkt));
515
516 while ((ret = mt_snoop_iter_next(ctx, itr, &pkt, &hdr)) == 0) {
517 frtn_t frtn;
518
519 if (pkt.sp_len == 0) {
520 break;
521 }
522
523 pkt_num++;
524
525 /*
526 * Prepend the packet record number to any
527 * fail/skip/error message so the user knows which
528 * record in the snoop stream to inspect.
529 */
530 ktest_msg_prepend(ctx, "pkt #%u: ", pkt_num);
531
532 /* IPv4 only */
533 if (hdr->spr_include_len < (sizeof (*eh) + sizeof (*ip))) {
534 continue;
535 }
536
537 /* fully recorded packets only */
538 if (hdr->spr_include_len != hdr->spr_orig_len) {
539 continue;
540 }
541
542 frtn.free_func = snoop_pkt_free;
543 frtn.free_arg = (caddr_t)&pkt;
544 mp = desballoc(pkt.sp_bytes, pkt.sp_len, 0, &frtn);
545 KT_EASSERT3PG(mp, !=, NULL, ctx, cleanup);
546 mp->b_wptr += pkt.sp_len;
547 eh = (struct ether_header *)mp->b_rptr;
548 ehsz = sizeof (*eh);
549
550 /* IPv4 only */
551 if (ntohs(eh->ether_type) != ETHERTYPE_IP) {
552 freeb(mp);
553 mp = NULL;
554 continue;
555 }
556
557 ip = (ipha_t *)(mp->b_rptr + ehsz);
558 ipsz = sizeof (*ip);
559
560 if (ip->ipha_protocol == IPPROTO_TCP) {
561 tcp = (tcpha_t *)(mp->b_rptr + sizeof (*eh) +
562 sizeof (*ip));
563 tcpsz = TCP_HDR_LENGTH(tcp);
564 msglen = ntohs(ip->ipha_length) - (ipsz + tcpsz);
565
566 /* Let's make sure we don't run off into space. */
567 if ((tcpsz + msglen) > (pkt.sp_len - (ehsz + ipsz))) {
568 KT_ERROR(ctx, "(tcpsz=%lu + msglen=%lu) > "
569 "(pkt_len=%lu - (ehsz=%lu + ipsz=%lu))",
570 tcpsz, msglen, pkt.sp_len, ehsz, ipsz);
571 goto cleanup;
572 }
573
574 /*
575 * As we are reading a snoop input stream we
576 * need to make sure to zero out any existing
577 * checksum.
578 */
579 tcp->tha_sum = 0;
580 csum = mt_pseudo_sum(IPPROTO_TCP, ip);
581 csum = mt_rfc1071_sum(csum,
582 (uint16_t *)(mp->b_rptr + ehsz + ipsz),
583 tcpsz + msglen);
584 } else {
585 freeb(mp);
586 mp = NULL;
587 continue;
588 }
589
590 /*
591 * The internet checksum can never be 0xFFFF, as that
592 * would indicate an input of all zeros.
593 */
594 KT_ASSERT3UG(csum, !=, 0xFFFF, ctx, cleanup);
595 KT_ASSERTG(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
596 KT_ASSERT3UG(tcp->tha_sum, !=, 0xFFFF, ctx, cleanup);
597 KT_ASSERT3UG(tcp->tha_sum, ==, csum, ctx, cleanup);
598 at_least_one = B_TRUE;
599 freeb(mp);
600 mp = NULL;
601
602 /*
603 * Clear the prepended message for the iterator call
604 * as it already includes the current record number
605 * (and pkt_num is not incremented, thus incorrect,
606 * until after a successful call).
607 */
608 ktest_msg_clear(ctx);
609 }
610
611 if (ret != 0) {
612 /* mt_snoop_next() already set error context. */
613 goto cleanup;
614 }
615
616 if (at_least_one) {
617 KT_PASS(ctx);
618 } else {
619 ktest_msg_clear(ctx);
620 KT_ERROR(ctx, "at least one TCP/IPv4 packet expected");
621 }
622
623 cleanup:
624 if (hdl != NULL) {
625 ktest_release_mod(hdl);
626 }
627
628 if (mp != NULL) {
629 freeb(mp);
630 }
631
632 if (itr != NULL) {
633 kmem_free(itr, sizeof (*itr));
634 }
635 }
636
637 typedef struct meoi_test_params {
638 mblk_t *mtp_mp;
639 mac_ether_offload_info_t mtp_partial;
640 mac_ether_offload_info_t mtp_results;
641 uint_t mtp_offset;
642 } meoi_test_params_t;
643
644 static void
nvlist_to_meoi(nvlist_t * results,mac_ether_offload_info_t * meoi)645 nvlist_to_meoi(nvlist_t *results, mac_ether_offload_info_t *meoi)
646 {
647 uint64_t u64_val;
648 int int_val;
649 uint16_t u16_val;
650 uint8_t u8_val;
651
652 bzero(meoi, sizeof (*meoi));
653 if (nvlist_lookup_int32(results, "meoi_flags", &int_val) == 0) {
654 meoi->meoi_flags = int_val;
655 }
656 if (nvlist_lookup_uint64(results, "meoi_len", &u64_val) == 0) {
657 meoi->meoi_len = u64_val;
658 }
659 if (nvlist_lookup_uint8(results, "meoi_l2hlen", &u8_val) == 0) {
660 meoi->meoi_l2hlen = u8_val;
661 }
662 if (nvlist_lookup_uint16(results, "meoi_l3proto", &u16_val) == 0) {
663 meoi->meoi_l3proto = u16_val;
664 }
665 if (nvlist_lookup_uint16(results, "meoi_l3hlen", &u16_val) == 0) {
666 meoi->meoi_l3hlen = u16_val;
667 }
668 if (nvlist_lookup_uint8(results, "meoi_l4proto", &u8_val) == 0) {
669 meoi->meoi_l4proto = u8_val;
670 }
671 if (nvlist_lookup_uint8(results, "meoi_l4hlen", &u8_val) == 0) {
672 meoi->meoi_l4hlen = u8_val;
673 }
674 }
675
676 static mblk_t *
alloc_split_pkt(ktest_ctx_hdl_t * ctx,nvlist_t * nvl,const char * pkt_field)677 alloc_split_pkt(ktest_ctx_hdl_t *ctx, nvlist_t *nvl, const char *pkt_field)
678 {
679 uchar_t *pkt_bytes;
680 uint_t pkt_sz;
681
682 if (nvlist_lookup_byte_array(nvl, pkt_field, &pkt_bytes,
683 &pkt_sz) != 0) {
684 KT_ERROR(ctx, "Input missing %s field", pkt_field);
685 return (NULL);
686 }
687
688 const uint32_t *splits = NULL;
689 uint_t num_splits = 0;
690 (void) nvlist_lookup_uint32_array(nvl, "splits", (uint32_t **)&splits,
691 &num_splits);
692
693 uint_t split_idx = 0;
694 mblk_t *result = NULL, *tail = NULL;
695
696 do {
697 uint_t block_sz = pkt_sz;
698 if (split_idx < num_splits) {
699 block_sz = MIN(block_sz, splits[split_idx]);
700 }
701
702 mblk_t *mp = allocb(block_sz, 0);
703 if (mp == NULL) {
704 KT_ERROR(ctx, "mblk alloc failure");
705 freemsg(result);
706 return (NULL);
707 }
708
709 if (result == NULL) {
710 result = mp;
711 } else {
712 tail->b_cont = mp;
713 }
714 tail = mp;
715
716 if (block_sz != 0) {
717 bcopy(pkt_bytes, mp->b_wptr, block_sz);
718 mp->b_wptr += block_sz;
719 }
720 pkt_sz -= block_sz;
721 pkt_bytes += block_sz;
722 split_idx++;
723 } while (pkt_sz > 0);
724
725 return (result);
726 }
727
728 /*
729 * mac_ether_offload_info tests expect the following as input (via packed
730 * nvlist)
731 *
732 * - pkt_bytes (byte array): packet bytes to parse
733 * - splits (uint32 array, optional): byte sizes to split packet into mblks
734 * - results (nvlist): mac_ether_offload_info result struct to compare
735 * - Field names and types should match those in the mac_ether_offload_info
736 * struct. Any fields not specified will be assumed to be zero.
737 *
738 * For mac_partial_offload_info tests, two additional fields are parsed:
739 *
740 * - offset (uint32, optional): offset into the packet at which the parsing
741 * should begin
742 * - partial (nvlist): mac_ether_offload_info input struct to be used as
743 * starting point for partial parsing
744 */
745 static boolean_t
meoi_test_parse_input(ktest_ctx_hdl_t * ctx,meoi_test_params_t * mtp,boolean_t test_partial)746 meoi_test_parse_input(ktest_ctx_hdl_t *ctx, meoi_test_params_t *mtp,
747 boolean_t test_partial)
748 {
749 uchar_t *bytes;
750 size_t num_bytes = 0;
751
752 ktest_get_input(ctx, &bytes, &num_bytes);
753 bzero(mtp, sizeof (*mtp));
754
755 nvlist_t *params = NULL;
756 if (nvlist_unpack((char *)bytes, num_bytes, ¶ms, KM_SLEEP) != 0) {
757 KT_ERROR(ctx, "Invalid nvlist input");
758 return (B_FALSE);
759 }
760
761 nvlist_t *results;
762 if (nvlist_lookup_nvlist(params, "results", &results) != 0) {
763 KT_ERROR(ctx, "Input missing results field");
764 nvlist_free(params);
765 return (B_FALSE);
766 }
767
768 if (test_partial) {
769 nvlist_t *partial;
770 if (nvlist_lookup_nvlist(params, "partial", &partial) != 0) {
771 KT_ERROR(ctx, "Input missing partial field");
772 nvlist_free(params);
773 return (B_FALSE);
774 } else {
775 nvlist_to_meoi(partial, &mtp->mtp_partial);
776 }
777
778 (void) nvlist_lookup_uint32(params, "offset", &mtp->mtp_offset);
779 }
780
781 mtp->mtp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
782 if (mtp->mtp_mp == NULL) {
783 nvlist_free(params);
784 return (B_FALSE);
785 }
786
787 nvlist_to_meoi(results, &mtp->mtp_results);
788
789 nvlist_free(params);
790 return (B_TRUE);
791 }
792
793 void
mac_ether_offload_info_test(ktest_ctx_hdl_t * ctx)794 mac_ether_offload_info_test(ktest_ctx_hdl_t *ctx)
795 {
796 meoi_test_params_t mtp = { 0 };
797
798 if (!meoi_test_parse_input(ctx, &mtp, B_FALSE)) {
799 return;
800 }
801
802 mac_ether_offload_info_t result;
803 mac_ether_offload_info(mtp.mtp_mp, &result);
804
805 const mac_ether_offload_info_t *expect = &mtp.mtp_results;
806 KT_ASSERT3UG(result.meoi_flags, ==, expect->meoi_flags, ctx, done);
807 KT_ASSERT3UG(result.meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
808 KT_ASSERT3UG(result.meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
809 KT_ASSERT3UG(result.meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
810 KT_ASSERT3UG(result.meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
811 KT_ASSERT3UG(result.meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
812
813 KT_PASS(ctx);
814
815 done:
816 freemsg(mtp.mtp_mp);
817 }
818
819 void
mac_partial_offload_info_test(ktest_ctx_hdl_t * ctx)820 mac_partial_offload_info_test(ktest_ctx_hdl_t *ctx)
821 {
822 meoi_test_params_t mtp = { 0 };
823
824 if (!meoi_test_parse_input(ctx, &mtp, B_TRUE)) {
825 return;
826 }
827
828 mac_ether_offload_info_t *result = &mtp.mtp_partial;
829 mac_partial_offload_info(mtp.mtp_mp, mtp.mtp_offset, result);
830
831 const mac_ether_offload_info_t *expect = &mtp.mtp_results;
832 KT_ASSERT3UG(result->meoi_flags, ==, expect->meoi_flags, ctx, done);
833 KT_ASSERT3UG(result->meoi_l2hlen, ==, expect->meoi_l2hlen, ctx, done);
834 KT_ASSERT3UG(result->meoi_l3proto, ==, expect->meoi_l3proto, ctx, done);
835 KT_ASSERT3UG(result->meoi_l3hlen, ==, expect->meoi_l3hlen, ctx, done);
836 KT_ASSERT3UG(result->meoi_l4proto, ==, expect->meoi_l4proto, ctx, done);
837 KT_ASSERT3UG(result->meoi_l4hlen, ==, expect->meoi_l4hlen, ctx, done);
838
839 KT_PASS(ctx);
840
841 done:
842 freemsg(mtp.mtp_mp);
843 }
844
845 typedef struct ether_test_params {
846 mblk_t *etp_mp;
847 uint32_t etp_tci;
848 uint8_t etp_dstaddr[ETHERADDRL];
849 boolean_t etp_is_err;
850 } ether_test_params_t;
851
852 /*
853 * mac_ether_l2_info tests expect the following as input (via packed nvlist)
854 *
855 * - pkt_bytes (byte array): packet bytes to parse
856 * - splits (uint32 array, optional): byte sizes to split packet into mblks
857 * - tci (uint32): VLAN TCI result value to compare
858 * - dstaddr (byte array): MAC addr result value to compare
859 * - is_err (boolean): if test function should return error
860 */
861 static boolean_t
ether_parse_input(ktest_ctx_hdl_t * ctx,ether_test_params_t * etp)862 ether_parse_input(ktest_ctx_hdl_t *ctx, ether_test_params_t *etp)
863 {
864 uchar_t *bytes;
865 size_t num_bytes = 0;
866
867 ktest_get_input(ctx, &bytes, &num_bytes);
868 bzero(etp, sizeof (*etp));
869
870 nvlist_t *params = NULL;
871 if (nvlist_unpack((char *)bytes, num_bytes, ¶ms, KM_SLEEP) != 0) {
872 KT_ERROR(ctx, "Invalid nvlist input");
873 return (B_FALSE);
874 }
875
876 etp->etp_mp = alloc_split_pkt(ctx, params, "pkt_bytes");
877 if (etp->etp_mp == NULL) {
878 nvlist_free(params);
879 return (B_FALSE);
880 }
881
882 if (nvlist_lookup_uint32(params, "tci", &etp->etp_tci) != 0) {
883 KT_ERROR(ctx, "Input missing tci field");
884 nvlist_free(params);
885 return (B_FALSE);
886 }
887
888 uchar_t *dstaddr;
889 uint_t dstaddr_sz;
890 if (nvlist_lookup_byte_array(params, "dstaddr", &dstaddr,
891 &dstaddr_sz) != 0) {
892 KT_ERROR(ctx, "Input missing dstaddr field");
893 nvlist_free(params);
894 return (B_FALSE);
895 } else if (dstaddr_sz != ETHERADDRL) {
896 KT_ERROR(ctx, "bad dstaddr size %u != %u", dstaddr_sz,
897 ETHERADDRL);
898 nvlist_free(params);
899 return (B_FALSE);
900 }
901 bcopy(dstaddr, &etp->etp_dstaddr, ETHERADDRL);
902
903 etp->etp_is_err = nvlist_lookup_boolean(params, "is_err") == 0;
904
905 nvlist_free(params);
906 return (B_TRUE);
907 }
908
909 void
mac_ether_l2_info_test(ktest_ctx_hdl_t * ctx)910 mac_ether_l2_info_test(ktest_ctx_hdl_t *ctx)
911 {
912 ether_test_params_t etp = { 0 };
913
914 if (!ether_parse_input(ctx, &etp)) {
915 return;
916 }
917
918 uint8_t dstaddr[ETHERADDRL];
919 uint32_t vlan_tci = 0;
920 const boolean_t is_err =
921 !mac_ether_l2_info(etp.etp_mp, dstaddr, &vlan_tci);
922
923 KT_ASSERTG(is_err == etp.etp_is_err, ctx, done);
924 KT_ASSERTG(bcmp(dstaddr, etp.etp_dstaddr, ETHERADDRL) == 0, ctx,
925 done);
926 KT_ASSERT3UG(vlan_tci, ==, etp.etp_tci, ctx, done);
927
928 KT_PASS(ctx);
929
930 done:
931 freemsg(etp.etp_mp);
932 }
933
934
935 static struct modlmisc mac_ktest_modlmisc = {
936 .misc_modops = &mod_miscops,
937 .misc_linkinfo = "mac ktest module"
938 };
939
940 static struct modlinkage mac_ktest_modlinkage = {
941 .ml_rev = MODREV_1,
942 .ml_linkage = { &mac_ktest_modlmisc, NULL }
943 };
944
945 int
_init()946 _init()
947 {
948 int ret;
949 ktest_module_hdl_t *km = NULL;
950 ktest_suite_hdl_t *ks = NULL;
951
952 VERIFY0(ktest_create_module("mac", &km));
953 VERIFY0(ktest_add_suite(km, "checksum", &ks));
954 VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_tcp_test",
955 mac_sw_cksum_ipv4_tcp_test, KTEST_FLAG_NONE));
956 VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_bad_proto_test",
957 mac_sw_cksum_ipv4_bad_proto_test, KTEST_FLAG_NONE));
958 VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_snoop_test",
959 mac_sw_cksum_ipv4_snoop_test, KTEST_FLAG_INPUT));
960
961 ks = NULL;
962 VERIFY0(ktest_add_suite(km, "parsing", &ks));
963 VERIFY0(ktest_add_test(ks, "mac_ether_offload_info_test",
964 mac_ether_offload_info_test, KTEST_FLAG_INPUT));
965 VERIFY0(ktest_add_test(ks, "mac_partial_offload_info_test",
966 mac_partial_offload_info_test, KTEST_FLAG_INPUT));
967 VERIFY0(ktest_add_test(ks, "mac_ether_l2_info_test",
968 mac_ether_l2_info_test, KTEST_FLAG_INPUT));
969
970 if ((ret = ktest_register_module(km)) != 0) {
971 ktest_free_module(km);
972 return (ret);
973 }
974
975 if ((ret = mod_install(&mac_ktest_modlinkage)) != 0) {
976 ktest_unregister_module("mac");
977 return (ret);
978 }
979
980 return (0);
981 }
982
983 int
_fini(void)984 _fini(void)
985 {
986 ktest_unregister_module("mac");
987 return (mod_remove(&mac_ktest_modlinkage));
988 }
989
990 int
_info(struct modinfo * modinfop)991 _info(struct modinfo *modinfop)
992 {
993 return (mod_info(&mac_ktest_modlinkage, modinfop));
994 }
995