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