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