xref: /netbsd-src/usr.sbin/npf/npftest/npftest.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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