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