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