npftest.c revision 1.31 1 1.31 mlelstv /* $NetBSD: npftest.c,v 1.31 2025/08/10 09:01:28 mlelstv Exp $ */
2 1.1 rmind
3 1.1 rmind /*
4 1.1 rmind * NPF testing framework.
5 1.1 rmind *
6 1.1 rmind * Public Domain.
7 1.1 rmind */
8 1.1 rmind
9 1.1 rmind #include <stdio.h>
10 1.1 rmind #include <stdlib.h>
11 1.1 rmind #include <stdbool.h>
12 1.2 rmind #include <string.h>
13 1.1 rmind #include <unistd.h>
14 1.1 rmind #include <assert.h>
15 1.2 rmind #include <fcntl.h>
16 1.2 rmind #include <err.h>
17 1.2 rmind
18 1.16 rmind #include <sys/mman.h>
19 1.20 christos #include <sys/stat.h>
20 1.20 christos #if !defined(_NPF_STANDALONE)
21 1.2 rmind #include <sys/ioctl.h>
22 1.2 rmind #include <net/if.h>
23 1.2 rmind #include <arpa/inet.h>
24 1.1 rmind
25 1.22 rmind #include <dnv.h>
26 1.22 rmind #include <nv.h>
27 1.19 pooka
28 1.1 rmind #include <rump/rump.h>
29 1.2 rmind #include <rump/rump_syscalls.h>
30 1.20 christos #endif
31 1.1 rmind
32 1.16 rmind #include <cdbw.h>
33 1.16 rmind
34 1.1 rmind #include "npftest.h"
35 1.1 rmind
36 1.2 rmind static bool verbose, quiet;
37 1.1 rmind
38 1.8 joerg __dead static void
39 1.20 christos usage(const char *progname)
40 1.1 rmind {
41 1.7 martin printf("usage:\n"
42 1.7 martin " %s [ -q | -v ] [ -c <config> ] "
43 1.31 mlelstv "[ -i <interface> -s <file> ] -b <benchmark> -p <ncpu>\n"
44 1.31 mlelstv " %s [ -q | -v ] [ -i <interface> -s <file> ] -t\n"
45 1.7 martin " %s -T <testname> -c <config>\n"
46 1.7 martin " %s -L\n"
47 1.7 martin "where:\n"
48 1.31 mlelstv "\t-b benchmark\n"
49 1.31 mlelstv "\t-t regression test\n"
50 1.7 martin "\t-T <testname>: specific test\n"
51 1.4 rmind "\t-s <file>: pcap stream\n"
52 1.2 rmind "\t-c <config>: NPF configuration file\n"
53 1.4 rmind "\t-i <interface>: primary interface\n"
54 1.31 mlelstv "\t-L list testnames and description for -T\n"
55 1.31 mlelstv "\t-q quiet mode\n"
56 1.31 mlelstv "\t-v verbose mode\n",
57 1.31 mlelstv "\t-p set RUMP_NCPU\n",
58 1.20 christos progname, progname, progname);
59 1.2 rmind exit(EXIT_FAILURE);
60 1.1 rmind }
61 1.1 rmind
62 1.8 joerg __dead static void
63 1.7 martin describe_tests(void)
64 1.7 martin {
65 1.7 martin printf( "nbuf\tbasic npf mbuf handling\n"
66 1.10 rmind "bpf\tBPF coprocessor\n"
67 1.7 martin "table\ttable handling\n"
68 1.7 martin "state\tstate handling and processing\n"
69 1.25 rmind "gc\tconnection G/C\n"
70 1.7 martin "rule\trule processing\n"
71 1.28 joe "nat\tNAT rule processing\n"
72 1.29 joe "guid\tUser/group filtering\n"
73 1.30 joe "ether\tlayer 2 rule processing\n"
74 1.30 joe "l2defpass\tlayer 2 default pass on layer 3 rules\n");
75 1.7 martin exit(EXIT_SUCCESS);
76 1.7 martin }
77 1.7 martin
78 1.6 rmind static bool
79 1.2 rmind result(const char *testcase, bool ok)
80 1.1 rmind {
81 1.1 rmind if (!quiet) {
82 1.2 rmind printf("NPF %-10s\t%s\n", testcase, ok ? "OK" : "fail");
83 1.1 rmind }
84 1.1 rmind if (verbose) {
85 1.1 rmind puts("-----");
86 1.1 rmind }
87 1.6 rmind return !ok;
88 1.1 rmind }
89 1.1 rmind
90 1.2 rmind static void
91 1.22 rmind load_npf_config(const char *fpath)
92 1.2 rmind {
93 1.22 rmind struct stat sb;
94 1.22 rmind int error, fd;
95 1.22 rmind size_t len;
96 1.22 rmind void *buf;
97 1.4 rmind
98 1.22 rmind /*
99 1.22 rmind * Read the configuration from the specified file.
100 1.22 rmind */
101 1.22 rmind if ((fd = open(fpath, O_RDONLY)) == -1) {
102 1.22 rmind err(EXIT_FAILURE, "open");
103 1.22 rmind }
104 1.22 rmind if (fstat(fd, &sb) == -1) {
105 1.22 rmind err(EXIT_FAILURE, "fstat");
106 1.2 rmind }
107 1.22 rmind len = sb.st_size;
108 1.22 rmind buf = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
109 1.22 rmind if (buf == MAP_FAILED) {
110 1.22 rmind err(EXIT_FAILURE, "mmap");
111 1.4 rmind }
112 1.22 rmind close(fd);
113 1.2 rmind
114 1.22 rmind /*
115 1.22 rmind * Load the NPF configuration.
116 1.22 rmind */
117 1.22 rmind error = rumpns_npf_test_load(buf, len, verbose);
118 1.2 rmind if (error) {
119 1.20 christos errx(EXIT_FAILURE, "npf_test_load: %s\n", strerror(error));
120 1.2 rmind }
121 1.22 rmind munmap(buf, len);
122 1.2 rmind
123 1.2 rmind if (verbose) {
124 1.22 rmind printf("Loaded NPF config at '%s'\n", fpath);
125 1.2 rmind }
126 1.2 rmind }
127 1.2 rmind
128 1.16 rmind static void *
129 1.16 rmind generate_test_cdb(size_t *size)
130 1.16 rmind {
131 1.16 rmind in_addr_t addr;
132 1.16 rmind struct cdbw *cdbw;
133 1.16 rmind struct stat sb;
134 1.16 rmind char sfn[32];
135 1.16 rmind int alen, fd;
136 1.16 rmind void *cdb;
137 1.16 rmind
138 1.16 rmind if ((cdbw = cdbw_open()) == NULL) {
139 1.16 rmind err(EXIT_FAILURE, "cdbw_open");
140 1.16 rmind }
141 1.16 rmind strlcpy(sfn, "/tmp/npftest_cdb.XXXXXX", sizeof(sfn));
142 1.16 rmind if ((fd = mkstemp(sfn)) == -1) {
143 1.16 rmind err(EXIT_FAILURE, "mkstemp");
144 1.16 rmind }
145 1.16 rmind unlink(sfn);
146 1.16 rmind
147 1.16 rmind addr = inet_addr("192.168.1.1"), alen = sizeof(struct in_addr);
148 1.16 rmind if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1)
149 1.16 rmind err(EXIT_FAILURE, "cdbw_put");
150 1.16 rmind
151 1.16 rmind addr = inet_addr("10.0.0.2"), alen = sizeof(struct in_addr);
152 1.16 rmind if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1)
153 1.16 rmind err(EXIT_FAILURE, "cdbw_put");
154 1.16 rmind
155 1.27 riastrad if (cdbw_output(cdbw, fd, "npf-table-cdb", NULL) == -1) {
156 1.16 rmind err(EXIT_FAILURE, "cdbw_output");
157 1.16 rmind }
158 1.16 rmind cdbw_close(cdbw);
159 1.16 rmind
160 1.16 rmind if (fstat(fd, &sb) == -1) {
161 1.16 rmind err(EXIT_FAILURE, "fstat");
162 1.16 rmind }
163 1.16 rmind if ((cdb = mmap(NULL, sb.st_size, PROT_READ,
164 1.16 rmind MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
165 1.16 rmind err(EXIT_FAILURE, "mmap");
166 1.16 rmind }
167 1.16 rmind close(fd);
168 1.16 rmind
169 1.16 rmind *size = sb.st_size;
170 1.16 rmind return cdb;
171 1.16 rmind }
172 1.16 rmind
173 1.20 christos static void
174 1.20 christos npf_kern_init(void)
175 1.20 christos {
176 1.20 christos #if !defined(_NPF_STANDALONE)
177 1.20 christos /* XXX rn_init */
178 1.20 christos extern int rumpns_max_keylen;
179 1.20 christos rumpns_max_keylen = 1;
180 1.20 christos
181 1.20 christos rump_init();
182 1.20 christos rump_schedule();
183 1.20 christos #endif
184 1.20 christos }
185 1.20 christos
186 1.20 christos static void
187 1.20 christos npf_kern_fini(void)
188 1.20 christos {
189 1.20 christos #if !defined(_NPF_STANDALONE)
190 1.20 christos rump_unschedule();
191 1.20 christos #endif
192 1.20 christos }
193 1.20 christos
194 1.1 rmind int
195 1.1 rmind main(int argc, char **argv)
196 1.1 rmind {
197 1.12 rmind bool test, ok, fail, tname_matched;
198 1.12 rmind char *benchmark, *config, *interface, *stream, *testname;
199 1.11 rmind unsigned nthreads = 0;
200 1.13 rmind ifnet_t *ifp = NULL;
201 1.13 rmind int ch;
202 1.1 rmind
203 1.12 rmind benchmark = NULL;
204 1.2 rmind test = false;
205 1.2 rmind
206 1.7 martin tname_matched = false;
207 1.7 martin testname = NULL;
208 1.2 rmind config = NULL;
209 1.4 rmind interface = NULL;
210 1.2 rmind stream = NULL;
211 1.2 rmind
212 1.1 rmind verbose = false;
213 1.1 rmind quiet = false;
214 1.1 rmind
215 1.12 rmind while ((ch = getopt(argc, argv, "b:qvc:i:s:tT:Lp:")) != -1) {
216 1.1 rmind switch (ch) {
217 1.1 rmind case 'b':
218 1.12 rmind benchmark = optarg;
219 1.1 rmind break;
220 1.1 rmind case 'q':
221 1.1 rmind quiet = true;
222 1.1 rmind break;
223 1.1 rmind case 'v':
224 1.1 rmind verbose = true;
225 1.1 rmind break;
226 1.2 rmind case 'c':
227 1.2 rmind config = optarg;
228 1.2 rmind break;
229 1.2 rmind case 'i':
230 1.4 rmind interface = optarg;
231 1.2 rmind break;
232 1.2 rmind case 's':
233 1.2 rmind stream = optarg;
234 1.2 rmind break;
235 1.2 rmind case 't':
236 1.2 rmind test = true;
237 1.2 rmind break;
238 1.7 martin case 'T':
239 1.7 martin test = true;
240 1.7 martin testname = optarg;
241 1.7 martin break;
242 1.7 martin case 'L':
243 1.7 martin describe_tests();
244 1.11 rmind break;
245 1.11 rmind case 'p':
246 1.11 rmind /* Note: RUMP_NCPU must be high enough. */
247 1.11 rmind if ((nthreads = atoi(optarg)) > 0 &&
248 1.11 rmind getenv("RUMP_NCPU") == NULL) {
249 1.20 christos static char nthr[64];
250 1.20 christos sprintf(nthr, "%u", nthreads + 1);
251 1.20 christos setenv("RUMP_NCPU", nthr, 1);
252 1.11 rmind }
253 1.11 rmind break;
254 1.1 rmind default:
255 1.20 christos usage(argv[0]);
256 1.1 rmind }
257 1.1 rmind }
258 1.1 rmind
259 1.4 rmind /*
260 1.11 rmind * Either benchmark or test. If stream analysis, then the
261 1.11 rmind * interface should be specified. If benchmark, then the
262 1.11 rmind * config should be loaded.
263 1.4 rmind */
264 1.12 rmind if ((benchmark != NULL) == test && (stream && !interface)) {
265 1.20 christos usage(argv[0]);
266 1.2 rmind }
267 1.11 rmind if (benchmark && (!config || !nthreads)) {
268 1.11 rmind errx(EXIT_FAILURE, "missing config for the benchmark or "
269 1.11 rmind "invalid thread count");
270 1.11 rmind }
271 1.2 rmind
272 1.20 christos /*
273 1.20 christos * Initialise the NPF kernel component.
274 1.20 christos */
275 1.20 christos npf_kern_init();
276 1.17 rmind rumpns_npf_test_init(inet_pton, inet_ntop, random);
277 1.5 rmind
278 1.2 rmind if (config) {
279 1.2 rmind load_npf_config(config);
280 1.2 rmind }
281 1.13 rmind if (interface && (ifp = rumpns_npf_test_getif(interface)) == 0) {
282 1.4 rmind errx(EXIT_FAILURE, "failed to find the interface");
283 1.4 rmind }
284 1.4 rmind
285 1.6 rmind fail = false;
286 1.2 rmind
287 1.2 rmind if (test) {
288 1.7 martin if (!testname || strcmp("nbuf", testname) == 0) {
289 1.7 martin ok = rumpns_npf_nbuf_test(verbose);
290 1.7 martin fail |= result("nbuf", ok);
291 1.7 martin tname_matched = true;
292 1.7 martin }
293 1.1 rmind
294 1.9 rmind if (!testname || strcmp("bpf", testname) == 0) {
295 1.9 rmind ok = rumpns_npf_bpf_test(verbose);
296 1.9 rmind fail |= result("bpf", ok);
297 1.9 rmind tname_matched = true;
298 1.9 rmind }
299 1.9 rmind
300 1.7 martin if (!testname || strcmp("table", testname) == 0) {
301 1.16 rmind void *cdb;
302 1.16 rmind size_t len;
303 1.16 rmind
304 1.16 rmind cdb = generate_test_cdb(&len);
305 1.16 rmind ok = rumpns_npf_table_test(verbose, cdb, len);
306 1.7 martin fail |= result("table", ok);
307 1.7 martin tname_matched = true;
308 1.16 rmind munmap(cdb, len);
309 1.7 martin }
310 1.3 rmind
311 1.7 martin if (!testname || strcmp("state", testname) == 0) {
312 1.7 martin ok = rumpns_npf_state_test(verbose);
313 1.7 martin fail |= result("state", ok);
314 1.7 martin tname_matched = true;
315 1.7 martin }
316 1.23 rmind
317 1.25 rmind if (!testname || strcmp("gc", testname) == 0) {
318 1.25 rmind ok = rumpns_npf_gc_test(verbose);
319 1.25 rmind fail |= result("gc", ok);
320 1.23 rmind tname_matched = true;
321 1.23 rmind }
322 1.2 rmind }
323 1.2 rmind
324 1.4 rmind if (test && config) {
325 1.7 martin if (!testname || strcmp("rule", testname) == 0) {
326 1.7 martin ok = rumpns_npf_rule_test(verbose);
327 1.29 joe fail |= result("rules - layer 3", ok);
328 1.29 joe tname_matched = true;
329 1.29 joe }
330 1.29 joe
331 1.29 joe if (!testname || strcmp("ether", testname) == 0) {
332 1.29 joe ok = rumpns_npf_layer2_rule_test(verbose);
333 1.29 joe fail |= result("rules - layer 2", ok);
334 1.7 martin tname_matched = true;
335 1.7 martin }
336 1.4 rmind
337 1.30 joe if (!testname || strcmp("l2defpass", testname) == 0) {
338 1.30 joe ok = rumpns_npf_layer2only_test(verbose);
339 1.30 joe fail |= result(" layer 2 default pass", ok);
340 1.30 joe tname_matched = true;
341 1.30 joe }
342 1.30 joe
343 1.28 joe if (!testname || strcmp("guid", testname) == 0) {
344 1.28 joe ok = rumpns_npf_guid_test(verbose);
345 1.28 joe fail |= result("guid", ok);
346 1.28 joe tname_matched = true;
347 1.28 joe }
348 1.28 joe
349 1.7 martin if (!testname || strcmp("nat", testname) == 0) {
350 1.20 christos srandom(1);
351 1.7 martin ok = rumpns_npf_nat_test(verbose);
352 1.7 martin fail |= result("nat", ok);
353 1.7 martin tname_matched = true;
354 1.7 martin }
355 1.4 rmind }
356 1.4 rmind
357 1.2 rmind if (stream) {
358 1.13 rmind process_stream(stream, NULL, ifp);
359 1.2 rmind }
360 1.1 rmind
361 1.11 rmind if (benchmark) {
362 1.12 rmind if (strcmp("rule", benchmark) == 0) {
363 1.12 rmind rumpns_npf_test_conc(false, nthreads);
364 1.12 rmind }
365 1.12 rmind if (strcmp("state", benchmark) == 0) {
366 1.12 rmind rumpns_npf_test_conc(true, nthreads);
367 1.12 rmind }
368 1.11 rmind }
369 1.11 rmind
370 1.20 christos rumpns_npf_test_fini();
371 1.20 christos npf_kern_fini();
372 1.1 rmind
373 1.7 martin if (testname && !tname_matched)
374 1.7 martin errx(EXIT_FAILURE, "test \"%s\" unknown", testname);
375 1.7 martin
376 1.6 rmind return fail ? EXIT_FAILURE : EXIT_SUCCESS;
377 1.1 rmind }
378