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