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