Home | History | Annotate | Line # | Download | only in npftest
      1 /*	$NetBSD: npftest.c,v 1.32 2026/10/30 23:36:16 joe 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
     39 usage(const char *progname)
     40 {
     41 	printf("usage:\n"
     42 	    "  %s [ -q | -v ] [ -c <config> ] "
     43 	        "[ -i <interface> -s <file> ] -b <benchmark> -p <ncpu>\n"
     44 	    "  %s [ -q | -v ] [ -i <interface> -s <file> ] -t\n"
     45 	    "  %s -T <testname> -c <config>\n"
     46 	    "  %s -L\n"
     47 	    "where:\n"
     48 	    "\t-b benchmark\n"
     49 	    "\t-t regression test\n"
     50 	    "\t-T <testname>: specific test\n"
     51 	    "\t-s <file>: pcap stream\n"
     52 	    "\t-c <config>: NPF configuration file\n"
     53 	    "\t-i <interface>: primary interface\n"
     54 	    "\t-L list testnames and description for -T\n"
     55 	    "\t-q quiet mode\n"
     56 	    "\t-v verbose mode\n",
     57 	    "\t-p set RUMP_NCPU\n",
     58 	    progname, progname, progname);
     59 	exit(EXIT_FAILURE);
     60 }
     61 
     62 __dead static void
     63 describe_tests(void)
     64 {
     65 	printf(	"nbuf\tbasic npf mbuf handling\n"
     66 		"bpf\tBPF coprocessor\n"
     67 		"table\ttable handling\n"
     68 		"state\tstate handling and processing\n"
     69 		"gc\tconnection G/C\n"
     70 		"rule\trule processing\n"
     71 		"nat\tNAT rule processing\n"
     72 		"guid\tUser/group filtering\n"
     73 		"ether\tlayer 2 rule processing\n"
     74 		"l2defpass\tlayer 2 default pass on layer 3 rules\n"
     75 		"proc\trule procedure tests\n");
     76 	exit(EXIT_SUCCESS);
     77 }
     78 
     79 static bool
     80 result(const char *testcase, bool ok)
     81 {
     82 	if (!quiet) {
     83 		printf("NPF %-10s\t%s\n", testcase, ok ? "OK" : "fail");
     84 	}
     85 	if (verbose) {
     86 		puts("-----");
     87 	}
     88 	return !ok;
     89 }
     90 
     91 static void
     92 load_npf_config(const char *fpath)
     93 {
     94 	struct stat sb;
     95 	int error, fd;
     96 	size_t len;
     97 	void *buf;
     98 
     99 	/*
    100 	 * Read the configuration from the specified file.
    101 	 */
    102 	if ((fd = open(fpath, O_RDONLY)) == -1) {
    103 		err(EXIT_FAILURE, "open");
    104 	}
    105 	if (fstat(fd, &sb) == -1) {
    106 		err(EXIT_FAILURE, "fstat");
    107 	}
    108 	len = sb.st_size;
    109 	buf = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
    110 	if (buf == MAP_FAILED) {
    111 		err(EXIT_FAILURE, "mmap");
    112 	}
    113 	close(fd);
    114 
    115 	/*
    116 	 * Load the NPF configuration.
    117 	 */
    118 	error = rumpns_npf_test_load(buf, len, verbose);
    119 	if (error) {
    120 		errx(EXIT_FAILURE, "npf_test_load: %s\n", strerror(error));
    121 	}
    122 	munmap(buf, len);
    123 
    124 	if (verbose) {
    125 		printf("Loaded NPF config at '%s'\n", fpath);
    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 static void
    175 npf_kern_init(void)
    176 {
    177 #if !defined(_NPF_STANDALONE)
    178 	/* XXX rn_init */
    179 	extern int rumpns_max_keylen;
    180 	rumpns_max_keylen = 1;
    181 
    182 	rump_init();
    183 	rump_schedule();
    184 #endif
    185 }
    186 
    187 static void
    188 npf_kern_fini(void)
    189 {
    190 #if !defined(_NPF_STANDALONE)
    191 	rump_unschedule();
    192 #endif
    193 }
    194 
    195 int
    196 main(int argc, char **argv)
    197 {
    198 	bool test, ok, fail, tname_matched;
    199 	char *benchmark, *config, *interface, *stream, *testname;
    200 	unsigned nthreads = 0;
    201 	ifnet_t *ifp = NULL;
    202 	int ch;
    203 
    204 	benchmark = NULL;
    205 	test = false;
    206 
    207 	tname_matched = false;
    208 	testname = NULL;
    209 	config = NULL;
    210 	interface = NULL;
    211 	stream = NULL;
    212 
    213 	verbose = false;
    214 	quiet = false;
    215 
    216 	while ((ch = getopt(argc, argv, "b:qvc:i:s:tT:Lp:")) != -1) {
    217 		switch (ch) {
    218 		case 'b':
    219 			benchmark = optarg;
    220 			break;
    221 		case 'q':
    222 			quiet = true;
    223 			break;
    224 		case 'v':
    225 			verbose = true;
    226 			break;
    227 		case 'c':
    228 			config = optarg;
    229 			break;
    230 		case 'i':
    231 			interface = optarg;
    232 			break;
    233 		case 's':
    234 			stream = optarg;
    235 			break;
    236 		case 't':
    237 			test = true;
    238 			break;
    239 		case 'T':
    240 			test = true;
    241 			testname = optarg;
    242 			break;
    243 		case 'L':
    244 			describe_tests();
    245 			break;
    246 		case 'p':
    247 			/* Note: RUMP_NCPU must be high enough. */
    248 			if ((nthreads = atoi(optarg)) > 0 &&
    249 			    getenv("RUMP_NCPU") == NULL) {
    250 				static char nthr[64];
    251 				sprintf(nthr, "%u", nthreads + 1);
    252 				setenv("RUMP_NCPU", nthr, 1);
    253 			}
    254 			break;
    255 		default:
    256 			usage(argv[0]);
    257 		}
    258 	}
    259 
    260 	/*
    261 	 * Either benchmark or test.  If stream analysis, then the
    262 	 * interface should be specified.  If benchmark, then the
    263 	 * config should be loaded.
    264 	 */
    265 	if ((benchmark != NULL) == test && (stream && !interface)) {
    266 		usage(argv[0]);
    267 	}
    268 	if (benchmark && (!config || !nthreads)) {
    269 		errx(EXIT_FAILURE, "missing config for the benchmark or "
    270 		    "invalid thread count");
    271 	}
    272 
    273 	/*
    274 	 * Initialise the NPF kernel component.
    275 	 */
    276 	npf_kern_init();
    277 	rumpns_npf_test_init(inet_pton, inet_ntop, random);
    278 
    279 	if (config) {
    280 		load_npf_config(config);
    281 	}
    282 	if (interface && (ifp = rumpns_npf_test_getif(interface)) == 0) {
    283 		errx(EXIT_FAILURE, "failed to find the interface");
    284 	}
    285 
    286 	fail = false;
    287 
    288 	if (test) {
    289 		if (!testname || strcmp("nbuf", testname) == 0) {
    290 			ok = rumpns_npf_nbuf_test(verbose);
    291 			fail |= result("nbuf", ok);
    292 			tname_matched = true;
    293 		}
    294 
    295 		if (!testname || strcmp("bpf", testname) == 0) {
    296 			ok = rumpns_npf_bpf_test(verbose);
    297 			fail |= result("bpf", ok);
    298 			tname_matched = true;
    299 		}
    300 
    301 		if (!testname || strcmp("table", testname) == 0) {
    302 			void *cdb;
    303 			size_t len;
    304 
    305 			cdb = generate_test_cdb(&len);
    306 			ok = rumpns_npf_table_test(verbose, cdb, len);
    307 			fail |= result("table", ok);
    308 			tname_matched = true;
    309 			munmap(cdb, len);
    310 		}
    311 
    312 		if (!testname || strcmp("state", testname) == 0) {
    313 			ok = rumpns_npf_state_test(verbose);
    314 			fail |= result("state", ok);
    315 			tname_matched = true;
    316 		}
    317 
    318 		if (!testname || strcmp("gc", testname) == 0) {
    319 			ok = rumpns_npf_gc_test(verbose);
    320 			fail |= result("gc", ok);
    321 			tname_matched = true;
    322 		}
    323 	}
    324 
    325 	if (test && config) {
    326 		if (!testname || strcmp("rule", testname) == 0) {
    327 			ok = rumpns_npf_rule_test(verbose);
    328 			fail |= result("rules - layer 3", ok);
    329 			tname_matched = true;
    330 		}
    331 
    332 		if (!testname || strcmp("ether", testname) == 0) {
    333 			ok = rumpns_npf_layer2_rule_test(verbose);
    334 			fail |= result("rules - layer 2", ok);
    335 			tname_matched = true;
    336 		}
    337 
    338 		if (strcmp("l2defpass", testname) == 0) {
    339 			ok = rumpns_npf_layer2only_test(verbose);
    340 			fail |= result(" layer 2 default pass", ok);
    341 			tname_matched = true;
    342 		}
    343 
    344 		if (!testname || strcmp("guid", testname) == 0) {
    345 			ok = rumpns_npf_guid_test(verbose);
    346 			fail |= result("guid", ok);
    347 			tname_matched = true;
    348 		}
    349 
    350 		if (!testname || strcmp("nat", testname) == 0) {
    351 			srandom(1);
    352 			ok = rumpns_npf_nat_test(verbose);
    353 			fail |= result("nat", ok);
    354 			tname_matched = true;
    355 		}
    356 
    357 		if (!testname || strcmp("proc", testname) == 0) {
    358 			ok = rumpns_npf_procedure_test(verbose);
    359 			fail |= result("proc", ok);
    360 			tname_matched = true;
    361 		}
    362 	}
    363 
    364 	if (stream) {
    365 		process_stream(stream, NULL, ifp);
    366 	}
    367 
    368 	if (benchmark) {
    369 		if (strcmp("rule", benchmark) == 0) {
    370 			rumpns_npf_test_conc(false, nthreads);
    371 		}
    372 		if (strcmp("state", benchmark) == 0) {
    373 			rumpns_npf_test_conc(true, nthreads);
    374 		}
    375 	}
    376 
    377 	rumpns_npf_test_fini();
    378 	npf_kern_fini();
    379 
    380 	if (testname && !tname_matched)
    381 		errx(EXIT_FAILURE, "test \"%s\" unknown", testname);
    382 
    383 	return fail ? EXIT_FAILURE : EXIT_SUCCESS;
    384 }
    385