Home | History | Annotate | Line # | Download | only in npftest
npftest.c revision 1.23
      1 /*	$NetBSD: npftest.c,v 1.23 2019/01/19 21:19:32 rmind 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> ] < -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
     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 		"conn\tconnection processing\n"
     68 		"rule\trule processing\n"
     69 		"nat\tNAT rule processing\n");
     70 	exit(EXIT_SUCCESS);
     71 }
     72 
     73 static bool
     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
     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 *
    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
    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
    182 npf_kern_fini(void)
    183 {
    184 #if !defined(_NPF_STANDALONE)
    185 	rump_unschedule();
    186 #endif
    187 }
    188 
    189 int
    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 	srandom(1);
    281 	fail = false;
    282 
    283 	if (test) {
    284 		if (!testname || strcmp("nbuf", testname) == 0) {
    285 			ok = rumpns_npf_nbuf_test(verbose);
    286 			fail |= result("nbuf", ok);
    287 			tname_matched = true;
    288 		}
    289 
    290 		if (!testname || strcmp("bpf", testname) == 0) {
    291 			ok = rumpns_npf_bpf_test(verbose);
    292 			fail |= result("bpf", ok);
    293 			tname_matched = true;
    294 		}
    295 
    296 		if (!testname || strcmp("table", testname) == 0) {
    297 			void *cdb;
    298 			size_t len;
    299 
    300 			cdb = generate_test_cdb(&len);
    301 			ok = rumpns_npf_table_test(verbose, cdb, len);
    302 			fail |= result("table", ok);
    303 			tname_matched = true;
    304 			munmap(cdb, len);
    305 		}
    306 
    307 		if (!testname || strcmp("state", testname) == 0) {
    308 			ok = rumpns_npf_state_test(verbose);
    309 			fail |= result("state", ok);
    310 			tname_matched = true;
    311 		}
    312 
    313 		if (!testname || strcmp("conn", testname) == 0) {
    314 			srandom(1);
    315 			ok = rumpns_npf_conn_test(verbose);
    316 			fail |= result("conn", ok);
    317 			tname_matched = true;
    318 		}
    319 	}
    320 
    321 	if (test && config) {
    322 		if (!testname || strcmp("rule", testname) == 0) {
    323 			ok = rumpns_npf_rule_test(verbose);
    324 			fail |= result("rule", ok);
    325 			tname_matched = true;
    326 		}
    327 
    328 		if (!testname || strcmp("nat", testname) == 0) {
    329 			srandom(1);
    330 			ok = rumpns_npf_nat_test(verbose);
    331 			fail |= result("nat", ok);
    332 			tname_matched = true;
    333 		}
    334 	}
    335 
    336 	if (stream) {
    337 		process_stream(stream, NULL, ifp);
    338 	}
    339 
    340 	if (benchmark) {
    341 		if (strcmp("rule", benchmark) == 0) {
    342 			rumpns_npf_test_conc(false, nthreads);
    343 		}
    344 		if (strcmp("state", benchmark) == 0) {
    345 			rumpns_npf_test_conc(true, nthreads);
    346 		}
    347 	}
    348 
    349 	rumpns_npf_test_fini();
    350 	npf_kern_fini();
    351 
    352 	if (testname && !tname_matched)
    353 		errx(EXIT_FAILURE, "test \"%s\" unknown", testname);
    354 
    355 	return fail ? EXIT_FAILURE : EXIT_SUCCESS;
    356 }
    357