npftest.c revision 1.21 1 /* $NetBSD: npftest.c,v 1.21 2017/05/17 18:55:13 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 extern int rumpns_npfctl_testing;
199
200 int
201 main(int argc, char **argv)
202 {
203 bool test, ok, fail, tname_matched;
204 char *benchmark, *config, *interface, *stream, *testname;
205 unsigned nthreads = 0;
206 ifnet_t *ifp = NULL;
207 int ch;
208
209 benchmark = NULL;
210 test = false;
211
212 tname_matched = false;
213 testname = NULL;
214 config = NULL;
215 interface = NULL;
216 stream = NULL;
217
218 verbose = false;
219 quiet = false;
220
221 while ((ch = getopt(argc, argv, "b:qvc:i:s:tT:Lp:")) != -1) {
222 switch (ch) {
223 case 'b':
224 benchmark = optarg;
225 break;
226 case 'q':
227 quiet = true;
228 break;
229 case 'v':
230 verbose = true;
231 break;
232 case 'c':
233 config = optarg;
234 break;
235 case 'i':
236 interface = optarg;
237 break;
238 case 's':
239 stream = optarg;
240 break;
241 case 't':
242 test = true;
243 break;
244 case 'T':
245 test = true;
246 testname = optarg;
247 break;
248 case 'L':
249 describe_tests();
250 break;
251 case 'p':
252 /* Note: RUMP_NCPU must be high enough. */
253 if ((nthreads = atoi(optarg)) > 0 &&
254 getenv("RUMP_NCPU") == NULL) {
255 static char nthr[64];
256 sprintf(nthr, "%u", nthreads + 1);
257 setenv("RUMP_NCPU", nthr, 1);
258 }
259 break;
260 default:
261 usage(argv[0]);
262 }
263 }
264
265 /*
266 * Either benchmark or test. If stream analysis, then the
267 * interface should be specified. If benchmark, then the
268 * config should be loaded.
269 */
270 if ((benchmark != NULL) == test && (stream && !interface)) {
271 usage(argv[0]);
272 }
273 if (benchmark && (!config || !nthreads)) {
274 errx(EXIT_FAILURE, "missing config for the benchmark or "
275 "invalid thread count");
276 }
277
278 /*
279 * Initialise the NPF kernel component.
280 */
281 rumpns_npfctl_testing = 1;
282 npf_kern_init();
283 rumpns_npf_test_init(inet_pton, inet_ntop, random);
284
285 if (config) {
286 load_npf_config(config);
287 }
288 if (interface && (ifp = rumpns_npf_test_getif(interface)) == 0) {
289 errx(EXIT_FAILURE, "failed to find the interface");
290 }
291
292 srandom(1);
293 fail = false;
294
295 if (test) {
296 if (!testname || strcmp("nbuf", testname) == 0) {
297 ok = rumpns_npf_nbuf_test(verbose);
298 fail |= result("nbuf", ok);
299 tname_matched = true;
300 }
301
302 if (!testname || strcmp("bpf", testname) == 0) {
303 ok = rumpns_npf_bpf_test(verbose);
304 fail |= result("bpf", ok);
305 tname_matched = true;
306 }
307
308 if (!testname || strcmp("table", testname) == 0) {
309 void *cdb;
310 size_t len;
311
312 cdb = generate_test_cdb(&len);
313 ok = rumpns_npf_table_test(verbose, cdb, len);
314 fail |= result("table", ok);
315 tname_matched = true;
316 munmap(cdb, len);
317 }
318
319 if (!testname || strcmp("state", testname) == 0) {
320 ok = rumpns_npf_state_test(verbose);
321 fail |= result("state", ok);
322 tname_matched = true;
323 }
324 }
325
326 if (test && config) {
327 if (!testname || strcmp("rule", testname) == 0) {
328 ok = rumpns_npf_rule_test(verbose);
329 fail |= result("rule", ok);
330 tname_matched = true;
331 }
332
333 if (!testname || strcmp("nat", testname) == 0) {
334 srandom(1);
335 ok = rumpns_npf_nat_test(verbose);
336 fail |= result("nat", ok);
337 tname_matched = true;
338 }
339 }
340
341 if (stream) {
342 process_stream(stream, NULL, ifp);
343 }
344
345 if (benchmark) {
346 if (strcmp("rule", benchmark) == 0) {
347 rumpns_npf_test_conc(false, nthreads);
348 }
349 if (strcmp("state", benchmark) == 0) {
350 rumpns_npf_test_conc(true, nthreads);
351 }
352 }
353
354 rumpns_npf_test_fini();
355 npf_kern_fini();
356
357 if (testname && !tname_matched)
358 errx(EXIT_FAILURE, "test \"%s\" unknown", testname);
359
360 return fail ? EXIT_FAILURE : EXIT_SUCCESS;
361 }
362