xref: /illumos-gate/usr/src/uts/common/io/mac/mac_ktest.c (revision ca28c3d8eab8b53ff145fd15cf80cdc2da3fc032)
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, &params, 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, &params, 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