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