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