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