npftest.c revision 1.23 1 /* $NetBSD: npftest.c,v 1.23 2019/01/19 21:19:32 rmind 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 "conn\tconnection processing\n"
68 "rule\trule processing\n"
69 "nat\tNAT rule processing\n");
70 exit(EXIT_SUCCESS);
71 }
72
73 static bool
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
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 *
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
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
182 npf_kern_fini(void)
183 {
184 #if !defined(_NPF_STANDALONE)
185 rump_unschedule();
186 #endif
187 }
188
189 int
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 srandom(1);
281 fail = false;
282
283 if (test) {
284 if (!testname || strcmp("nbuf", testname) == 0) {
285 ok = rumpns_npf_nbuf_test(verbose);
286 fail |= result("nbuf", ok);
287 tname_matched = true;
288 }
289
290 if (!testname || strcmp("bpf", testname) == 0) {
291 ok = rumpns_npf_bpf_test(verbose);
292 fail |= result("bpf", ok);
293 tname_matched = true;
294 }
295
296 if (!testname || strcmp("table", testname) == 0) {
297 void *cdb;
298 size_t len;
299
300 cdb = generate_test_cdb(&len);
301 ok = rumpns_npf_table_test(verbose, cdb, len);
302 fail |= result("table", ok);
303 tname_matched = true;
304 munmap(cdb, len);
305 }
306
307 if (!testname || strcmp("state", testname) == 0) {
308 ok = rumpns_npf_state_test(verbose);
309 fail |= result("state", ok);
310 tname_matched = true;
311 }
312
313 if (!testname || strcmp("conn", testname) == 0) {
314 srandom(1);
315 ok = rumpns_npf_conn_test(verbose);
316 fail |= result("conn", 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("rule", ok);
325 tname_matched = true;
326 }
327
328 if (!testname || strcmp("nat", testname) == 0) {
329 srandom(1);
330 ok = rumpns_npf_nat_test(verbose);
331 fail |= result("nat", ok);
332 tname_matched = true;
333 }
334 }
335
336 if (stream) {
337 process_stream(stream, NULL, ifp);
338 }
339
340 if (benchmark) {
341 if (strcmp("rule", benchmark) == 0) {
342 rumpns_npf_test_conc(false, nthreads);
343 }
344 if (strcmp("state", benchmark) == 0) {
345 rumpns_npf_test_conc(true, nthreads);
346 }
347 }
348
349 rumpns_npf_test_fini();
350 npf_kern_fini();
351
352 if (testname && !tname_matched)
353 errx(EXIT_FAILURE, "test \"%s\" unknown", testname);
354
355 return fail ? EXIT_FAILURE : EXIT_SUCCESS;
356 }
357