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