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