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