xref: /illumos-gate/usr/src/uts/common/io/mac/mac_test.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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 2023 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 
27 static uint32_t
28 mt_pseudo_sum(const uint8_t proto, ipha_t *ip)
29 {
30 	const uint32_t ip_hdr_sz = IPH_HDR_LENGTH(ip);
31 	const ipaddr_t src = ip->ipha_src;
32 	const ipaddr_t dst = ip->ipha_dst;
33 	uint16_t len;
34 	uint32_t sum = 0;
35 
36 	switch (proto) {
37 	case IPPROTO_TCP:
38 		sum = IP_TCP_CSUM_COMP;
39 		break;
40 
41 	case IPPROTO_UDP:
42 		sum = IP_UDP_CSUM_COMP;
43 		break;
44 	}
45 
46 	len = ntohs(ip->ipha_length) - ip_hdr_sz;
47 	sum += (dst >> 16) + (dst & 0xFFFF) + (src >> 16) + (src & 0xFFFF);
48 	sum += htons(len);
49 	return (sum);
50 }
51 
52 /*
53  * An implementation of the internet checksum inspired by RFC 1071.
54  * This implementation is as naive as possible. It serves as the
55  * reference point for testing the optimized versions in the rest of
56  * our stack. This is no place for optimization or cleverness.
57  *
58  * Arguments
59  *
60  *     initial: The initial sum value.
61  *
62  *     addr: Pointer to the beginning of the byte stream to sum.
63  *
64  *     len: The number of bytes to sum.
65  *
66  * Return
67  *
68  *     The resulting internet checksum.
69  */
70 static uint32_t
71 mt_rfc1071_sum(uint32_t initial, uint16_t *addr, size_t len)
72 {
73 	uint32_t sum = initial;
74 
75 	while (len > 1) {
76 		sum += *addr;
77 		addr++;
78 		len -= 2;
79 	}
80 
81 	if (len == 1) {
82 		sum += *((uint8_t *)addr);
83 	}
84 
85 	while ((sum >> 16) != 0) {
86 		sum = (sum >> 16) + (sum & 0xFFFF);
87 	}
88 
89 	return (~sum & 0xFFFF);
90 }
91 
92 typedef boolean_t (*mac_sw_cksum_ipv4_t)(mblk_t *, uint32_t, ipha_t *,
93     const char **);
94 
95 /*
96  * Fill out a basic TCP header in the given mblk at the given offset.
97  * A TCP header should never straddle an mblk boundary.
98  */
99 static tcpha_t *
100 mt_tcp_basic_hdr(mblk_t *mp, uint16_t offset, uint16_t lport, uint16_t fport,
101     uint32_t seq, uint32_t ack, uint8_t flags, uint16_t win)
102 {
103 	tcpha_t *tcp = (tcpha_t *)(mp->b_rptr + offset);
104 
105 	VERIFY3U((uintptr_t)tcp + sizeof (*tcp), <=, mp->b_wptr);
106 	tcp->tha_lport = htons(lport);
107 	tcp->tha_fport = htons(fport);
108 	tcp->tha_seq = htonl(seq);
109 	tcp->tha_ack = htonl(ack);
110 	tcp->tha_offset_and_reserved = 0x5 << 4;
111 	tcp->tha_flags = flags;
112 	tcp->tha_win = htons(win);
113 	tcp->tha_sum = 0x0;
114 	tcp->tha_urp = 0x0;
115 
116 	return (tcp);
117 }
118 
119 static ipha_t *
120 mt_ipv4_simple_hdr(mblk_t *mp, uint16_t offset, uint16_t datum_length,
121     uint16_t ident, uint8_t proto, char *src, char *dst)
122 {
123 	uint32_t srcaddr, dstaddr;
124 	ipha_t *ip = (ipha_t *)(mp->b_rptr + offset);
125 
126 	VERIFY3U((uintptr_t)ip + sizeof (*ip), <=, mp->b_wptr);
127 
128 	VERIFY(inet_pton(AF_INET, src, &srcaddr));
129 	VERIFY(inet_pton(AF_INET, dst, &dstaddr));
130 	ip->ipha_version_and_hdr_length = IP_SIMPLE_HDR_VERSION;
131 	ip->ipha_type_of_service = 0x0;
132 	ip->ipha_length = htons(sizeof (*ip) + datum_length);
133 	ip->ipha_ident = htons(ident);
134 	ip->ipha_fragment_offset_and_flags = IPH_DF_HTONS;
135 	ip->ipha_ttl = 255;
136 	ip->ipha_protocol = proto;
137 	ip->ipha_hdr_checksum = 0x0;
138 	ip->ipha_src = srcaddr;
139 	ip->ipha_dst = dstaddr;
140 
141 	return (ip);
142 }
143 
144 static struct ether_header *
145 mt_ether_hdr(mblk_t *mp, uint16_t offset, char *dst, char *src, uint16_t etype)
146 {
147 	char *byte = dst;
148 	unsigned long tmp;
149 	struct ether_header *eh = (struct ether_header *)(mp->b_rptr + offset);
150 
151 	VERIFY3U((uintptr_t)eh + sizeof (*eh), <=, mp->b_wptr);
152 
153 	/* No strtok in these here parts. */
154 	for (uint_t i = 0; i < 6; i++) {
155 		char *end = strchr(dst, ':');
156 		VERIFY3P(end, !=, NULL);
157 		VERIFY0(ddi_strtoul(byte, NULL, 16, &tmp));
158 		VERIFY3U(tmp, <=, 255);
159 		eh->ether_dhost.ether_addr_octet[i] = tmp;
160 		byte = end + 1;
161 	}
162 
163 	byte = src;
164 	for (uint_t i = 0; i < 6; i++) {
165 		char *end = strchr(dst, ':');
166 		VERIFY3P(end, !=, NULL);
167 		VERIFY0(ddi_strtoul(byte, NULL, 16, &tmp));
168 		VERIFY3U(tmp, <=, 255);
169 		eh->ether_shost.ether_addr_octet[i] = tmp;
170 		byte = end + 1;
171 	}
172 
173 	eh->ether_type = etype;
174 	return (eh);
175 }
176 
177 void
178 mac_sw_cksum_ipv4_tcp_test(ktest_ctx_hdl_t *ctx)
179 {
180 	ddi_modhandle_t hdl = NULL;
181 	mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
182 	tcpha_t *tcp;
183 	ipha_t *ip;
184 	struct ether_header *eh;
185 	mblk_t *mp = NULL;
186 	char *msg = "...when it's not your turn";
187 	size_t msglen = strlen(msg) + 1;
188 	size_t mplen;
189 	const char *err = "";
190 	uint32_t sum;
191 	size_t ehsz = sizeof (*eh);
192 	size_t ipsz = sizeof (*ip);
193 	size_t tcpsz = sizeof (*tcp);
194 
195 	if (ktest_hold_mod("mac", &hdl) != 0) {
196 		KT_ERROR(ctx, "failed to hold 'mac' module");
197 		return;
198 	}
199 
200 	if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
201 	    (void **)&mac_sw_cksum_ipv4) != 0) {
202 		KT_ERROR(ctx, "failed to resolve symbol %s`%s", "mac",
203 		    "mac_sw_cksum_ipv4");
204 		goto cleanup;
205 	}
206 
207 	mplen = ehsz + ipsz + tcpsz + msglen;
208 	mp = allocb(mplen, 0);
209 	KT_EASSERT3P(mp, !=, NULL, ctx);
210 	mp->b_wptr = mp->b_rptr + mplen;
211 	tcp = mt_tcp_basic_hdr(mp, ehsz + ipsz, 2002, 2008, 1, 166, 0, 32000);
212 	ip = mt_ipv4_simple_hdr(mp, ehsz, tcpsz + msglen, 410, IPPROTO_TCP,
213 	    "192.168.2.4", "192.168.2.5");
214 	eh = mt_ether_hdr(mp, 0, "f2:35:c2:72:26:57", "92:ce:5a:29:46:9d",
215 	    ETHERTYPE_IP);
216 
217 	bcopy(msg, mp->b_rptr + ehsz + ipsz + tcpsz, msglen);
218 
219 	/*
220 	 * It's important that we calculate the reference checksum
221 	 * first, because mac_sw_cksum_ipv4() populates the checksum
222 	 * field.
223 	 */
224 	sum = mt_pseudo_sum(IPPROTO_TCP, ip);
225 	sum = mt_rfc1071_sum(sum, (uint16_t *)(mp->b_rptr + ehsz + ipsz),
226 	    tcpsz + msglen);
227 
228 	/*
229 	 * The internet checksum can never be 0xFFFF, as that would
230 	 * indicate an input of all zeros.
231 	 */
232 	KT_ASSERT3UG(sum, !=, 0xFFFF, ctx, cleanup);
233 	KT_ASSERTG(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
234 	KT_ASSERT3UG(tcp->tha_sum, !=, 0xFFFF, ctx, cleanup);
235 	KT_ASSERT3UG(sum, ==, tcp->tha_sum, ctx, cleanup);
236 	KT_PASS(ctx);
237 
238 cleanup:
239 	if (hdl != NULL) {
240 		ktest_release_mod(hdl);
241 	}
242 
243 	if (mp != NULL) {
244 		freeb(mp);
245 	}
246 }
247 
248 /*
249  * Verify that an unexpected IP protocol results in the expect
250  * failure.
251  */
252 void
253 mac_sw_cksum_ipv4_bad_proto_test(ktest_ctx_hdl_t *ctx)
254 {
255 	ddi_modhandle_t hdl = NULL;
256 	mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
257 	tcpha_t *tcp;
258 	ipha_t *ip;
259 	struct ether_header *eh;
260 	mblk_t *mp = NULL;
261 	char *msg = "...when it's not your turn";
262 	size_t msglen = strlen(msg) + 1;
263 	size_t mplen;
264 	const char *err = "";
265 	size_t ehsz = sizeof (*eh);
266 	size_t ipsz = sizeof (*ip);
267 	size_t tcpsz = sizeof (*tcp);
268 
269 	if (ktest_hold_mod("mac", &hdl) != 0) {
270 		KT_ERROR(ctx, "failed to hold 'mac' module");
271 		return;
272 	}
273 
274 	if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
275 	    (void **)&mac_sw_cksum_ipv4) != 0) {
276 		KT_ERROR(ctx, "failed to resolve symbol mac`mac_sw_cksum_ipv4");
277 		goto cleanup;
278 	}
279 
280 	mplen = ehsz + ipsz + tcpsz + msglen;
281 	mp = allocb(mplen, 0);
282 	KT_EASSERT3P(mp, !=, NULL, ctx);
283 	mp->b_wptr = mp->b_rptr + mplen;
284 	tcp = mt_tcp_basic_hdr(mp, ehsz + ipsz, 2002, 2008, 1, 166, 0, 32000);
285 	ip = mt_ipv4_simple_hdr(mp, ehsz, tcpsz + msglen, 410, IPPROTO_ENCAP,
286 	    "192.168.2.4", "192.168.2.5");
287 	eh = mt_ether_hdr(mp, 0, "f2:35:c2:72:26:57", "92:ce:5a:29:46:9d",
288 	    ETHERTYPE_IP);
289 	bcopy(msg, mp->b_rptr + ehsz + ipsz + tcpsz, msglen);
290 	KT_ASSERT0G(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
291 	KT_PASS(ctx);
292 
293 cleanup:
294 	if (hdl != NULL) {
295 		ktest_release_mod(hdl);
296 	}
297 
298 	if (mp != NULL) {
299 		freeb(mp);
300 	}
301 }
302 
303 typedef struct snoop_pkt_record_hdr {
304 	uint32_t	spr_orig_len;
305 	uint32_t	spr_include_len;
306 	uint32_t	spr_record_len;
307 	uint32_t	spr_cumulative_drops;
308 	uint32_t	spr_ts_secs;
309 	uint32_t	spr_ts_micros;
310 } snoop_pkt_record_hdr_t;
311 
312 typedef struct snoop_pkt {
313 	uchar_t *sp_bytes;
314 	uint16_t sp_len;
315 } snoop_pkt_t;
316 
317 typedef struct snoop_iter {
318 	uchar_t *sic_input;	/* beginning of stream */
319 	uintptr_t sic_end;	/* end of stream */
320 	uchar_t *sic_pos;	/* current position in stream */
321 	uint_t sic_pkt_num;	/* current packet number, 1-based */
322 	snoop_pkt_record_hdr_t *sic_pkt_hdr; /* current packet record header */
323 } snoop_iter_t;
324 
325 #define	PAST_END(itr, len)	\
326 	(((uintptr_t)(itr)->sic_pos + len) > itr->sic_end)
327 
328 /*
329  * Get the next packet in the snoop stream iterator returned by
330  * mt_snoop_iter_get(). A copy of the packet is returned via the pkt
331  * pointer. The caller provides the snoop_pkt_t, and this function
332  * allocates a new buffer inside it to hold a copy of the packet's
333  * bytes. It is the responsibility of the caller to free the copy. It
334  * is recommended the caller make use of desballoc(9F) along with the
335  * snoop_pkt_free() callback. When all the packets in the stream have
336  * been read all subsequent calls to this function will set sp_bytes
337  * to NULL and sp_len to 0.
338  *
339  * The caller may optionally specify an rhdr argument in order to
340  * receive a pointer to the packet record header (unlike the packet
341  * bytes this is a pointer into the stream, not a copy).
342  */
343 static int
344 mt_snoop_iter_next(ktest_ctx_hdl_t *ctx, snoop_iter_t *itr, snoop_pkt_t *pkt,
345     snoop_pkt_record_hdr_t **rhdr)
346 {
347 	uchar_t *pkt_start;
348 
349 	/*
350 	 * We've read exactly the number of bytes expected, this is
351 	 * the end.
352 	 */
353 	if ((uintptr_t)(itr->sic_pos) == itr->sic_end) {
354 		pkt->sp_bytes = NULL;
355 		pkt->sp_len = 0;
356 
357 		if (rhdr != NULL)
358 			*rhdr = NULL;
359 
360 		return (0);
361 	}
362 
363 	/*
364 	 * A corrupted record or truncated stream could point us past
365 	 * the end of the stream.
366 	 */
367 	if (PAST_END(itr, sizeof (snoop_pkt_record_hdr_t))) {
368 		KT_ERROR(ctx, "record corrupted or stream truncated, read past "
369 		    "end of stream for record header #%d: 0x%p + %u > 0x%p",
370 		    itr->sic_pkt_num, itr->sic_pos,
371 		    sizeof (snoop_pkt_record_hdr_t), itr->sic_end);
372 		return (EIO);
373 	}
374 
375 	itr->sic_pkt_hdr = (snoop_pkt_record_hdr_t *)itr->sic_pos;
376 	pkt_start = itr->sic_pos + sizeof (snoop_pkt_record_hdr_t);
377 
378 	/*
379 	 * A corrupted record or truncated stream could point us past
380 	 * the end of the stream.
381 	 */
382 	if (PAST_END(itr, ntohl(itr->sic_pkt_hdr->spr_record_len))) {
383 		KT_ERROR(ctx, "record corrupted or stream truncated, read past "
384 		    "end of stream for record #%d: 0x%p + %u > 0x%p",
385 		    itr->sic_pkt_num, itr->sic_pos,
386 		    ntohl(itr->sic_pkt_hdr->spr_record_len), itr->sic_end);
387 		return (EIO);
388 	}
389 
390 	pkt->sp_len = ntohl(itr->sic_pkt_hdr->spr_include_len);
391 	pkt->sp_bytes = kmem_zalloc(pkt->sp_len, KM_SLEEP);
392 	bcopy(pkt_start, pkt->sp_bytes, pkt->sp_len);
393 	itr->sic_pos += ntohl(itr->sic_pkt_hdr->spr_record_len);
394 	itr->sic_pkt_num++;
395 
396 	if (rhdr != NULL) {
397 		*rhdr = itr->sic_pkt_hdr;
398 	}
399 
400 	return (0);
401 }
402 
403 /*
404  * Parse a snoop data stream (RFC 1761) provided by input and return
405  * a packet iterator to be used by mt_snoop_iter_next().
406  */
407 static int
408 mt_snoop_iter_get(ktest_ctx_hdl_t *ctx, uchar_t *input, const uint_t input_len,
409     snoop_iter_t **itr_out)
410 {
411 	const uchar_t id[8] = { 's', 'n', 'o', 'o', 'p', '\0', '\0', '\0' };
412 	uint32_t version;
413 	uint32_t datalink;
414 	snoop_iter_t *itr;
415 
416 	*itr_out = NULL;
417 
418 	if (input_len < 16) {
419 		KT_ERROR(ctx, "snoop stream truncated at file header: %u < %u ",
420 		    input_len, 16);
421 		return (ENOBUFS);
422 	}
423 
424 	if (memcmp(input, &id, sizeof (id)) != 0) {
425 		KT_ERROR(ctx, "snoop stream malformed identification: %x %x %x "
426 		    "%x %x %x %x %x", input[0], input[1], input[2], input[3],
427 		    input[4], input[5], input[6], input[7]);
428 		return (EINVAL);
429 	}
430 
431 	itr = kmem_zalloc(sizeof (*itr), KM_SLEEP);
432 	itr->sic_input = input;
433 	itr->sic_end = (uintptr_t)input + input_len;
434 	itr->sic_pos = input + sizeof (id);
435 	itr->sic_pkt_num = 1;
436 	itr->sic_pkt_hdr = NULL;
437 	version = ntohl(*(uint32_t *)itr->sic_pos);
438 
439 	if (version != 2) {
440 		KT_ERROR(ctx, "snoop stream bad version: %u != %u", version, 2);
441 		return (EINVAL);
442 	}
443 
444 	itr->sic_pos += sizeof (version);
445 	datalink = ntohl(*(uint32_t *)itr->sic_pos);
446 
447 	/* We expect only Ethernet. */
448 	if (datalink != DL_ETHER) {
449 		KT_ERROR(ctx, "snoop stream bad datalink type: %u != %u",
450 		    datalink, DL_ETHER);
451 		kmem_free(itr, sizeof (*itr));
452 		return (EINVAL);
453 	}
454 
455 	itr->sic_pos += sizeof (datalink);
456 	*itr_out = itr;
457 	return (0);
458 }
459 
460 static void
461 snoop_pkt_free(snoop_pkt_t *pkt)
462 {
463 	kmem_free(pkt->sp_bytes, pkt->sp_len);
464 }
465 
466 /*
467  * Verify mac_sw_cksum_ipv4() against an arbitrary TCP stream read
468  * from the snoop capture given as input. In order to verify the
469  * checksum all TCP/IPv4 packets must be captured in full. The snoop
470  * capture may contain non-TCP/IPv4 packets, which will be skipped
471  * over. If not a single TCP/IPv4 packet is found, the test will
472  * report an error.
473  */
474 void
475 mac_sw_cksum_ipv4_snoop_test(ktest_ctx_hdl_t *ctx)
476 {
477 	ddi_modhandle_t hdl = NULL;
478 	mac_sw_cksum_ipv4_t mac_sw_cksum_ipv4 = NULL;
479 	uchar_t *bytes;
480 	size_t num_bytes = 0;
481 	uint_t pkt_num = 0;
482 	tcpha_t *tcp;
483 	ipha_t *ip;
484 	struct ether_header *eh;
485 	mblk_t *mp = NULL;
486 	const char *err = "";
487 	uint32_t csum;
488 	size_t ehsz, ipsz, tcpsz, msglen;
489 	snoop_iter_t *itr = NULL;
490 	snoop_pkt_record_hdr_t *hdr = NULL;
491 	boolean_t at_least_one = B_FALSE;
492 	snoop_pkt_t pkt;
493 	int ret;
494 
495 	if (ktest_hold_mod("mac", &hdl) != 0) {
496 		KT_ERROR(ctx, "failed to hold 'mac' module");
497 		return;
498 	}
499 
500 	if (ktest_get_fn(hdl, "mac_sw_cksum_ipv4",
501 	    (void **)&mac_sw_cksum_ipv4) != 0) {
502 		KT_ERROR(ctx, "failed to resolve symbol mac`mac_sw_cksum_ipv4");
503 		return;
504 	}
505 
506 	ktest_get_input(ctx, &bytes, &num_bytes);
507 	ret = mt_snoop_iter_get(ctx, bytes, num_bytes, &itr);
508 	if (ret != 0) {
509 		/* mt_snoop_iter_get() already set error context. */
510 		goto cleanup;
511 	}
512 
513 	bzero(&pkt, sizeof (pkt));
514 
515 	while ((ret = mt_snoop_iter_next(ctx, itr, &pkt, &hdr)) == 0) {
516 		frtn_t frtn;
517 
518 		if (pkt.sp_len == 0) {
519 			break;
520 		}
521 
522 		pkt_num++;
523 
524 		/*
525 		 * Prepend the packet record number to any
526 		 * fail/skip/error message so the user knows which
527 		 * record in the snoop stream to inspect.
528 		 */
529 		ktest_msg_prepend(ctx, "pkt #%u: ", pkt_num);
530 
531 		/* IPv4 only */
532 		if (hdr->spr_include_len < (sizeof (*eh) + sizeof (*ip))) {
533 			continue;
534 		}
535 
536 		/* fully recorded packets only */
537 		if (hdr->spr_include_len != hdr->spr_orig_len) {
538 			continue;
539 		}
540 
541 		frtn.free_func = snoop_pkt_free;
542 		frtn.free_arg = (caddr_t)&pkt;
543 		mp = desballoc(pkt.sp_bytes, pkt.sp_len, 0, &frtn);
544 		KT_EASSERT3PG(mp, !=, NULL, ctx, cleanup);
545 		mp->b_wptr += pkt.sp_len;
546 		eh = (struct ether_header *)mp->b_rptr;
547 		ehsz = sizeof (*eh);
548 
549 		/* IPv4 only */
550 		if (ntohs(eh->ether_type) != ETHERTYPE_IP) {
551 			freeb(mp);
552 			mp = NULL;
553 			continue;
554 		}
555 
556 		ip = (ipha_t *)(mp->b_rptr + ehsz);
557 		ipsz = sizeof (*ip);
558 
559 		if (ip->ipha_protocol == IPPROTO_TCP) {
560 			tcp = (tcpha_t *)(mp->b_rptr + sizeof (*eh) +
561 			    sizeof (*ip));
562 			tcpsz = TCP_HDR_LENGTH(tcp);
563 			msglen = ntohs(ip->ipha_length) - (ipsz + tcpsz);
564 
565 			/* Let's make sure we don't run off into space. */
566 			if ((tcpsz + msglen) > (pkt.sp_len - (ehsz + ipsz))) {
567 				KT_ERROR(ctx, "(tcpsz=%lu + msglen=%lu) > "
568 				    "(pkt_len=%lu - (ehsz=%lu + ipsz=%lu))",
569 				    tcpsz, msglen, pkt.sp_len, ehsz, ipsz);
570 				goto cleanup;
571 			}
572 
573 			/*
574 			 * As we are reading a snoop input stream we
575 			 * need to make sure to zero out any existing
576 			 * checksum.
577 			 */
578 			tcp->tha_sum = 0;
579 			csum = mt_pseudo_sum(IPPROTO_TCP, ip);
580 			csum = mt_rfc1071_sum(csum,
581 			    (uint16_t *)(mp->b_rptr + ehsz + ipsz),
582 			    tcpsz + msglen);
583 		} else {
584 			freeb(mp);
585 			mp = NULL;
586 			continue;
587 		}
588 
589 		/*
590 		 * The internet checksum can never be 0xFFFF, as that
591 		 * would indicate an input of all zeros.
592 		 */
593 		KT_ASSERT3UG(csum, !=, 0xFFFF, ctx, cleanup);
594 		KT_ASSERTG(mac_sw_cksum_ipv4(mp, ehsz, ip, &err), ctx, cleanup);
595 		KT_ASSERT3UG(tcp->tha_sum, !=, 0xFFFF, ctx, cleanup);
596 		KT_ASSERT3UG(tcp->tha_sum, ==, csum, ctx, cleanup);
597 		at_least_one = B_TRUE;
598 		freeb(mp);
599 		mp = NULL;
600 
601 		/*
602 		 * Clear the prepended message for the iterator call
603 		 * as it already includes the current record number
604 		 * (and pkt_num is not incremented, thus incorrect,
605 		 * until after a successful call).
606 		 */
607 		ktest_msg_clear(ctx);
608 	}
609 
610 	if (ret != 0) {
611 		/* mt_snoop_next() already set error context. */
612 		goto cleanup;
613 	}
614 
615 	if (at_least_one) {
616 		KT_PASS(ctx);
617 	} else {
618 		ktest_msg_clear(ctx);
619 		KT_ERROR(ctx, "at least one TCP/IPv4 packet expected");
620 	}
621 
622 cleanup:
623 	if (hdl != NULL) {
624 		ktest_release_mod(hdl);
625 	}
626 
627 	if (mp != NULL) {
628 		freeb(mp);
629 	}
630 
631 	if (itr != NULL) {
632 		kmem_free(itr, sizeof (*itr));
633 	}
634 }
635 
636 static struct modlmisc mac_test_modlmisc = {
637 	.misc_modops = &mod_miscops,
638 	.misc_linkinfo = "mac ktest module"
639 };
640 
641 static struct modlinkage mac_test_modlinkage = {
642 	.ml_rev = MODREV_1,
643 	.ml_linkage = { &mac_test_modlmisc, NULL }
644 };
645 
646 int
647 _init()
648 {
649 	int ret;
650 	ktest_module_hdl_t *km = NULL;
651 	ktest_suite_hdl_t *ks = NULL;
652 
653 	VERIFY0(ktest_create_module("mac", &km));
654 	VERIFY0(ktest_add_suite(km, "checksum", &ks));
655 	VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_tcp_test",
656 	    mac_sw_cksum_ipv4_tcp_test, KTEST_FLAG_NONE));
657 	VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_bad_proto_test",
658 	    mac_sw_cksum_ipv4_bad_proto_test, KTEST_FLAG_NONE));
659 	VERIFY0(ktest_add_test(ks, "mac_sw_cksum_ipv4_snoop_test",
660 	    mac_sw_cksum_ipv4_snoop_test, KTEST_FLAG_INPUT));
661 
662 	if ((ret = ktest_register_module(km)) != 0) {
663 		ktest_free_module(km);
664 		return (ret);
665 	}
666 
667 	if ((ret = mod_install(&mac_test_modlinkage)) != 0) {
668 		ktest_unregister_module("mac");
669 		return (ret);
670 	}
671 
672 	return (0);
673 }
674 
675 int
676 _fini(void)
677 {
678 	ktest_unregister_module("mac");
679 	return (mod_remove(&mac_test_modlinkage));
680 }
681 
682 int
683 _info(struct modinfo *modinfop)
684 {
685 	return (mod_info(&mac_test_modlinkage, modinfop));
686 }
687