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