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 Compnay
14 */
15
16 /*
17 * Executor for mac_sw_cksum() ktests.
18 *
19 * This program builds up the packed nvlist payloads expected by the ktest for
20 * mac_sw_cksum(). The caller provides a snoop(1) with one or more packets
21 * bearing valid checksums. Along with the checksum types selected (via option
22 * flags), it is passed into the ktest, where it is stripped of its checksums
23 * and then run through mac_sw_cksum(). The resulting mblk is compared
24 * byte-for-byte with the original input to determine if the emulation generated
25 * the correct checksums.
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <strings.h>
35 #include <netinet/in.h>
36 #include <sys/mman.h>
37 #include <sys/dlpi.h>
38 #include <sys/types.h>
39 #include <sys/types32.h>
40 #include <sys/stat.h>
41 #include <sys/sysmacros.h>
42 #include <sys/ethernet.h>
43
44 #include <libnvpair.h>
45 #include <libktest.h>
46
47 typedef struct snoop_pkt_hdr {
48 uint_t sph_origlen;
49 uint_t sph_msglen;
50 uint_t sph_totlen;
51 uint_t sph_drops;
52 #if defined(_LP64)
53 struct timeval32 sph_timestamp;
54 #else
55 #error "ktest is expected to be 64-bit for now"
56 #endif
57 } snoop_pkt_hdr_t;
58
59 typedef struct snoop_file_hdr {
60 char sfh_magic[8];
61 uint32_t sfh_vers;
62 uint32_t sfh_mac_type;
63 } snoop_file_hdr_t;
64
65 static const char snoop_magic[8] = "snoop\0\0\0";
66 static const uint_t snoop_acceptable_vers = 2;
67
68 typedef struct pkt_cap_iter {
69 int pci_fd;
70 const char *pci_base;
71 size_t pci_map_sz;
72 size_t pci_sz;
73 size_t pci_offset;
74 } pkt_cap_iter_t;
75
76 static pkt_cap_iter_t *
pkt_cap_open(int fd)77 pkt_cap_open(int fd)
78 {
79 struct stat info;
80 if (fstat(fd, &info) != 0) {
81 return (NULL);
82 }
83 if (info.st_size < sizeof (snoop_file_hdr_t)) {
84 errno = EINVAL;
85 return (NULL);
86 }
87
88 const size_t page_sz = (size_t)sysconf(_SC_PAGESIZE);
89 const size_t map_sz = P2ROUNDUP(info.st_size, page_sz);
90 void *map = mmap(NULL, map_sz, PROT_READ, MAP_PRIVATE, fd, 0);
91 if (map == NULL) {
92 return (NULL);
93 }
94
95 const snoop_file_hdr_t *hdr = (const snoop_file_hdr_t *)map;
96 if (bcmp(&hdr->sfh_magic, snoop_magic, sizeof (hdr->sfh_magic)) != 0 ||
97 ntohl(hdr->sfh_vers) != snoop_acceptable_vers ||
98 ntohl(hdr->sfh_mac_type) != DL_ETHER) {
99 (void) munmap(map, map_sz);
100 errno = EINVAL;
101 return (NULL);
102 }
103
104 struct pkt_cap_iter *iter = malloc(sizeof (struct pkt_cap_iter));
105 if (iter == NULL) {
106 (void) munmap(map, map_sz);
107 errno = ENOMEM;
108 return (NULL);
109 }
110
111 iter->pci_fd = fd;
112 iter->pci_base = (const char *)map;
113 iter->pci_map_sz = map_sz;
114 iter->pci_sz = info.st_size;
115 iter->pci_offset = sizeof (*hdr);
116
117 return (iter);
118 }
119
120 static void
pkt_cap_close(pkt_cap_iter_t * iter)121 pkt_cap_close(pkt_cap_iter_t *iter)
122 {
123 (void) munmap((void *)iter->pci_base, iter->pci_map_sz);
124 (void) close(iter->pci_fd);
125 free(iter);
126 }
127
128 static bool
pkt_cap_next(pkt_cap_iter_t * iter,const void ** pkt_buf,uint_t * sizep)129 pkt_cap_next(pkt_cap_iter_t *iter, const void **pkt_buf, uint_t *sizep)
130 {
131 size_t remain = iter->pci_sz - iter->pci_offset;
132
133 if (remain < sizeof (snoop_pkt_hdr_t)) {
134 return (false);
135 }
136
137 const snoop_pkt_hdr_t *hdr =
138 (const snoop_pkt_hdr_t *)&iter->pci_base[iter->pci_offset];
139
140 const uint_t msg_sz = ntohl(hdr->sph_msglen);
141 const uint_t total_sz = ntohl(hdr->sph_totlen);
142 if (remain < total_sz || remain < msg_sz) {
143 return (false);
144 }
145
146 *pkt_buf = (const void *)&hdr[1];
147 *sizep = msg_sz;
148 iter->pci_offset += total_sz;
149 return (true);
150 }
151
152 static ktest_hdl_t *kthdl = NULL;
153 const char *mac_cksum_cmd = "";
154
155 struct payload_opts {
156 uint_t po_padding;
157 boolean_t po_cksum_partial;
158 boolean_t po_cksum_full;
159 boolean_t po_cksum_ipv4;
160 boolean_t po_split_ether;
161 uint_t po_split_manual;
162 };
163
164 static char *
build_payload(const void * pkt_buf,uint_t pkt_sz,const struct payload_opts * popts,size_t * payload_sz)165 build_payload(const void *pkt_buf, uint_t pkt_sz,
166 const struct payload_opts *popts, size_t *payload_sz)
167 {
168 nvlist_t *payload = fnvlist_alloc();
169 fnvlist_add_byte_array(payload, "pkt_bytes",
170 (uchar_t *)pkt_buf, pkt_sz);
171 if (popts->po_padding) {
172 fnvlist_add_uint32(payload, "padding", popts->po_padding);
173 }
174 if (popts->po_cksum_partial) {
175 fnvlist_add_boolean(payload, "cksum_partial");
176 }
177 if (popts->po_cksum_full) {
178 fnvlist_add_boolean(payload, "cksum_full");
179 }
180 if (popts->po_cksum_ipv4) {
181 fnvlist_add_boolean(payload, "cksum_ipv4");
182 }
183
184 uint_t nsplit = 0;
185 uint32_t splits[2];
186 if (popts->po_split_ether) {
187 splits[nsplit++] = sizeof (struct ether_header);
188 }
189 if (popts->po_split_manual != 0) {
190 splits[nsplit++] = popts->po_split_manual;
191 }
192 if (nsplit > 0) {
193 fnvlist_add_uint32_array(payload, "cksum_splits", splits,
194 nsplit);
195 }
196
197 char *packed = fnvlist_pack(payload, payload_sz);
198 nvlist_free(payload);
199
200 return (packed);
201 }
202
203 static void
mac_cksum_usage(void)204 mac_cksum_usage(void)
205 {
206 (void) fprintf(stderr, "usage: %s [flags] [opts] <cap_file>\n\n"
207 "Flags:\n"
208 "\t-4\temulate HCK_IPV4_HDRCKSUM\n"
209 "\t-f\temulate HCK_FULLCKSUM\t(cannot be used with -p)\n"
210 "\t-p\temulate HCK_PARTIALCKSUM\t(cannot be used with -f)\n"
211 "\t-e\tsplit mblk after Ethernet header\n"
212 "Options:\n"
213 "\t-b <len>\tpad mblk with <len> bytes (must be even)\n"
214 "\t-s <len>\tsplit mblk after len bytes (must be even)\n"
215 "\t\t\tif -e is specified, will be applied after that split\n"
216 "Arguments:\n"
217 "\t<cap_file> is a snoop capture of packets to test.\n"
218 "\tAny TCP or UDP packets (or plain IPv4) are expected to have\n"
219 "\tcorrect checksums. The emulated results will be compared\n"
220 "\tagainst those sums in the packet (assuming them proper)\n",
221 mac_cksum_cmd);
222 exit(EXIT_FAILURE);
223 }
224
225 int
main(int argc,char * argv[])226 main(int argc, char *argv[])
227 {
228 /* Peel off command name for usage */
229 mac_cksum_cmd = argv[0];
230 argc--;
231 argv++;
232 optind = 0;
233
234 struct payload_opts popts = { 0 };
235 int c;
236 while ((c = getopt(argc, argv, "4fpeb:s:")) != -1) {
237 switch (c) {
238 case 'p':
239 popts.po_cksum_partial = B_TRUE;
240 break;
241 case 'f':
242 popts.po_cksum_full = B_TRUE;
243 break;
244 case '4':
245 popts.po_cksum_ipv4 = B_TRUE;
246 break;
247 case 'b':
248 errno = 0;
249 popts.po_padding = strtoul(optarg, NULL, 0);
250 if (errno != 0) {
251 err(EXIT_FAILURE,
252 "invalid padding value %s", optarg);
253 }
254 break;
255 case 'e':
256 popts.po_split_ether = B_TRUE;
257 break;
258 case 's':
259 errno = 0;
260 popts.po_split_manual = strtoul(optarg, NULL, 0);
261 if (errno != 0) {
262 err(EXIT_FAILURE,
263 "invalid split value %s", optarg);
264 }
265 break;
266
267 case '?':
268 warnx("unknown run option: -%c", optopt);
269 mac_cksum_usage();
270 }
271 }
272 argc -= optind;
273 argv += optind;
274
275 if (argc != 1) {
276 mac_cksum_usage();
277 }
278
279 int fd = open(argv[0], O_RDONLY, 0);
280 if (fd < 0) {
281 err(EXIT_FAILURE, "could not open cap file %s", argv[0]);
282 }
283
284 pkt_cap_iter_t *iter = pkt_cap_open(fd);
285 if (iter == NULL) {
286 err(EXIT_FAILURE, "unrecognized cap file %s", argv[0]);
287 }
288
289 if (!ktest_mod_load("mac")) {
290 err(EXIT_FAILURE, "could not load mac ktest module");
291 }
292 if ((kthdl = ktest_init()) == NULL) {
293 err(EXIT_FAILURE, "could not initialize libktest");
294 }
295
296 const void *pkt_buf;
297 uint_t pkt_sz;
298 uint_t count_pass = 0, count_fail = 0, count_skip = 0, idx = 0;
299 while (pkt_cap_next(iter, &pkt_buf, &pkt_sz)) {
300 ktest_run_req_t req = {
301 .krq_module = "mac",
302 .krq_suite = "checksum",
303 .krq_test = "mac_sw_cksum_test",
304 };
305 size_t payload_sz;
306 char *payload =
307 build_payload(pkt_buf, pkt_sz, &popts, &payload_sz);
308 req.krq_input = (uchar_t *)payload;
309 req.krq_input_len = (uint_t)payload_sz;
310
311 ktest_run_result_t result = { 0 };
312 if (!ktest_run(kthdl, &req, &result)) {
313 err(EXIT_FAILURE, "failure while attempting ktest run");
314 }
315 free(payload);
316
317 const char *code_name = ktest_code_name(result.krr_code);
318 switch (result.krr_code) {
319 case KTEST_CODE_PASS:
320 count_pass++;
321 break;
322 case KTEST_CODE_SKIP:
323 count_skip++;
324 break;
325 default:
326 count_fail++;
327 break;
328 }
329 (void) printf("%4u\t%s\t(len: %u)\n", idx, code_name, pkt_sz);
330 if (result.krr_msg != NULL) {
331 if (result.krr_code != KTEST_CODE_PASS) {
332 (void) printf("MSG: %s\n", result.krr_msg);
333 }
334 free(result.krr_msg);
335 }
336 idx++;
337 }
338 if (idx == 0) {
339 errx(EXIT_FAILURE, "No valid packets found");
340 } else if (idx != 1) {
341 /* Summarize for > 1 packet */
342 (void) printf("SUMMARY: %u PASS, %u SKIP, %u FAIL\n",
343 count_pass, count_skip, count_fail);
344 }
345
346 pkt_cap_close(iter);
347 ktest_fini(kthdl);
348
349 return (idx == count_pass ? EXIT_SUCCESS : EXIT_FAILURE);
350 }
351