1 1.31 mlelstv /* $NetBSD: npftest.c,v 1.31 2025/08/10 09:01:28 mlelstv Exp $ */ 2 1.1 rmind 3 1.1 rmind /* 4 1.1 rmind * NPF testing framework. 5 1.1 rmind * 6 1.1 rmind * Public Domain. 7 1.1 rmind */ 8 1.1 rmind 9 1.1 rmind #include <stdio.h> 10 1.1 rmind #include <stdlib.h> 11 1.1 rmind #include <stdbool.h> 12 1.2 rmind #include <string.h> 13 1.1 rmind #include <unistd.h> 14 1.1 rmind #include <assert.h> 15 1.2 rmind #include <fcntl.h> 16 1.2 rmind #include <err.h> 17 1.2 rmind 18 1.16 rmind #include <sys/mman.h> 19 1.20 christos #include <sys/stat.h> 20 1.20 christos #if !defined(_NPF_STANDALONE) 21 1.2 rmind #include <sys/ioctl.h> 22 1.2 rmind #include <net/if.h> 23 1.2 rmind #include <arpa/inet.h> 24 1.1 rmind 25 1.22 rmind #include <dnv.h> 26 1.22 rmind #include <nv.h> 27 1.19 pooka 28 1.1 rmind #include <rump/rump.h> 29 1.2 rmind #include <rump/rump_syscalls.h> 30 1.20 christos #endif 31 1.1 rmind 32 1.16 rmind #include <cdbw.h> 33 1.16 rmind 34 1.1 rmind #include "npftest.h" 35 1.1 rmind 36 1.2 rmind static bool verbose, quiet; 37 1.1 rmind 38 1.8 joerg __dead static void 39 1.20 christos usage(const char *progname) 40 1.1 rmind { 41 1.7 martin printf("usage:\n" 42 1.7 martin " %s [ -q | -v ] [ -c <config> ] " 43 1.31 mlelstv "[ -i <interface> -s <file> ] -b <benchmark> -p <ncpu>\n" 44 1.31 mlelstv " %s [ -q | -v ] [ -i <interface> -s <file> ] -t\n" 45 1.7 martin " %s -T <testname> -c <config>\n" 46 1.7 martin " %s -L\n" 47 1.7 martin "where:\n" 48 1.31 mlelstv "\t-b benchmark\n" 49 1.31 mlelstv "\t-t regression test\n" 50 1.7 martin "\t-T <testname>: specific test\n" 51 1.4 rmind "\t-s <file>: pcap stream\n" 52 1.2 rmind "\t-c <config>: NPF configuration file\n" 53 1.4 rmind "\t-i <interface>: primary interface\n" 54 1.31 mlelstv "\t-L list testnames and description for -T\n" 55 1.31 mlelstv "\t-q quiet mode\n" 56 1.31 mlelstv "\t-v verbose mode\n", 57 1.31 mlelstv "\t-p set RUMP_NCPU\n", 58 1.20 christos progname, progname, progname); 59 1.2 rmind exit(EXIT_FAILURE); 60 1.1 rmind } 61 1.1 rmind 62 1.8 joerg __dead static void 63 1.7 martin describe_tests(void) 64 1.7 martin { 65 1.7 martin printf( "nbuf\tbasic npf mbuf handling\n" 66 1.10 rmind "bpf\tBPF coprocessor\n" 67 1.7 martin "table\ttable handling\n" 68 1.7 martin "state\tstate handling and processing\n" 69 1.25 rmind "gc\tconnection G/C\n" 70 1.7 martin "rule\trule processing\n" 71 1.28 joe "nat\tNAT rule processing\n" 72 1.29 joe "guid\tUser/group filtering\n" 73 1.30 joe "ether\tlayer 2 rule processing\n" 74 1.30 joe "l2defpass\tlayer 2 default pass on layer 3 rules\n"); 75 1.7 martin exit(EXIT_SUCCESS); 76 1.7 martin } 77 1.7 martin 78 1.6 rmind static bool 79 1.2 rmind result(const char *testcase, bool ok) 80 1.1 rmind { 81 1.1 rmind if (!quiet) { 82 1.2 rmind printf("NPF %-10s\t%s\n", testcase, ok ? "OK" : "fail"); 83 1.1 rmind } 84 1.1 rmind if (verbose) { 85 1.1 rmind puts("-----"); 86 1.1 rmind } 87 1.6 rmind return !ok; 88 1.1 rmind } 89 1.1 rmind 90 1.2 rmind static void 91 1.22 rmind load_npf_config(const char *fpath) 92 1.2 rmind { 93 1.22 rmind struct stat sb; 94 1.22 rmind int error, fd; 95 1.22 rmind size_t len; 96 1.22 rmind void *buf; 97 1.4 rmind 98 1.22 rmind /* 99 1.22 rmind * Read the configuration from the specified file. 100 1.22 rmind */ 101 1.22 rmind if ((fd = open(fpath, O_RDONLY)) == -1) { 102 1.22 rmind err(EXIT_FAILURE, "open"); 103 1.22 rmind } 104 1.22 rmind if (fstat(fd, &sb) == -1) { 105 1.22 rmind err(EXIT_FAILURE, "fstat"); 106 1.2 rmind } 107 1.22 rmind len = sb.st_size; 108 1.22 rmind buf = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); 109 1.22 rmind if (buf == MAP_FAILED) { 110 1.22 rmind err(EXIT_FAILURE, "mmap"); 111 1.4 rmind } 112 1.22 rmind close(fd); 113 1.2 rmind 114 1.22 rmind /* 115 1.22 rmind * Load the NPF configuration. 116 1.22 rmind */ 117 1.22 rmind error = rumpns_npf_test_load(buf, len, verbose); 118 1.2 rmind if (error) { 119 1.20 christos errx(EXIT_FAILURE, "npf_test_load: %s\n", strerror(error)); 120 1.2 rmind } 121 1.22 rmind munmap(buf, len); 122 1.2 rmind 123 1.2 rmind if (verbose) { 124 1.22 rmind printf("Loaded NPF config at '%s'\n", fpath); 125 1.2 rmind } 126 1.2 rmind } 127 1.2 rmind 128 1.16 rmind static void * 129 1.16 rmind generate_test_cdb(size_t *size) 130 1.16 rmind { 131 1.16 rmind in_addr_t addr; 132 1.16 rmind struct cdbw *cdbw; 133 1.16 rmind struct stat sb; 134 1.16 rmind char sfn[32]; 135 1.16 rmind int alen, fd; 136 1.16 rmind void *cdb; 137 1.16 rmind 138 1.16 rmind if ((cdbw = cdbw_open()) == NULL) { 139 1.16 rmind err(EXIT_FAILURE, "cdbw_open"); 140 1.16 rmind } 141 1.16 rmind strlcpy(sfn, "/tmp/npftest_cdb.XXXXXX", sizeof(sfn)); 142 1.16 rmind if ((fd = mkstemp(sfn)) == -1) { 143 1.16 rmind err(EXIT_FAILURE, "mkstemp"); 144 1.16 rmind } 145 1.16 rmind unlink(sfn); 146 1.16 rmind 147 1.16 rmind addr = inet_addr("192.168.1.1"), alen = sizeof(struct in_addr); 148 1.16 rmind if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1) 149 1.16 rmind err(EXIT_FAILURE, "cdbw_put"); 150 1.16 rmind 151 1.16 rmind addr = inet_addr("10.0.0.2"), alen = sizeof(struct in_addr); 152 1.16 rmind if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1) 153 1.16 rmind err(EXIT_FAILURE, "cdbw_put"); 154 1.16 rmind 155 1.27 riastrad if (cdbw_output(cdbw, fd, "npf-table-cdb", NULL) == -1) { 156 1.16 rmind err(EXIT_FAILURE, "cdbw_output"); 157 1.16 rmind } 158 1.16 rmind cdbw_close(cdbw); 159 1.16 rmind 160 1.16 rmind if (fstat(fd, &sb) == -1) { 161 1.16 rmind err(EXIT_FAILURE, "fstat"); 162 1.16 rmind } 163 1.16 rmind if ((cdb = mmap(NULL, sb.st_size, PROT_READ, 164 1.16 rmind MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) { 165 1.16 rmind err(EXIT_FAILURE, "mmap"); 166 1.16 rmind } 167 1.16 rmind close(fd); 168 1.16 rmind 169 1.16 rmind *size = sb.st_size; 170 1.16 rmind return cdb; 171 1.16 rmind } 172 1.16 rmind 173 1.20 christos static void 174 1.20 christos npf_kern_init(void) 175 1.20 christos { 176 1.20 christos #if !defined(_NPF_STANDALONE) 177 1.20 christos /* XXX rn_init */ 178 1.20 christos extern int rumpns_max_keylen; 179 1.20 christos rumpns_max_keylen = 1; 180 1.20 christos 181 1.20 christos rump_init(); 182 1.20 christos rump_schedule(); 183 1.20 christos #endif 184 1.20 christos } 185 1.20 christos 186 1.20 christos static void 187 1.20 christos npf_kern_fini(void) 188 1.20 christos { 189 1.20 christos #if !defined(_NPF_STANDALONE) 190 1.20 christos rump_unschedule(); 191 1.20 christos #endif 192 1.20 christos } 193 1.20 christos 194 1.1 rmind int 195 1.1 rmind main(int argc, char **argv) 196 1.1 rmind { 197 1.12 rmind bool test, ok, fail, tname_matched; 198 1.12 rmind char *benchmark, *config, *interface, *stream, *testname; 199 1.11 rmind unsigned nthreads = 0; 200 1.13 rmind ifnet_t *ifp = NULL; 201 1.13 rmind int ch; 202 1.1 rmind 203 1.12 rmind benchmark = NULL; 204 1.2 rmind test = false; 205 1.2 rmind 206 1.7 martin tname_matched = false; 207 1.7 martin testname = NULL; 208 1.2 rmind config = NULL; 209 1.4 rmind interface = NULL; 210 1.2 rmind stream = NULL; 211 1.2 rmind 212 1.1 rmind verbose = false; 213 1.1 rmind quiet = false; 214 1.1 rmind 215 1.12 rmind while ((ch = getopt(argc, argv, "b:qvc:i:s:tT:Lp:")) != -1) { 216 1.1 rmind switch (ch) { 217 1.1 rmind case 'b': 218 1.12 rmind benchmark = optarg; 219 1.1 rmind break; 220 1.1 rmind case 'q': 221 1.1 rmind quiet = true; 222 1.1 rmind break; 223 1.1 rmind case 'v': 224 1.1 rmind verbose = true; 225 1.1 rmind break; 226 1.2 rmind case 'c': 227 1.2 rmind config = optarg; 228 1.2 rmind break; 229 1.2 rmind case 'i': 230 1.4 rmind interface = optarg; 231 1.2 rmind break; 232 1.2 rmind case 's': 233 1.2 rmind stream = optarg; 234 1.2 rmind break; 235 1.2 rmind case 't': 236 1.2 rmind test = true; 237 1.2 rmind break; 238 1.7 martin case 'T': 239 1.7 martin test = true; 240 1.7 martin testname = optarg; 241 1.7 martin break; 242 1.7 martin case 'L': 243 1.7 martin describe_tests(); 244 1.11 rmind break; 245 1.11 rmind case 'p': 246 1.11 rmind /* Note: RUMP_NCPU must be high enough. */ 247 1.11 rmind if ((nthreads = atoi(optarg)) > 0 && 248 1.11 rmind getenv("RUMP_NCPU") == NULL) { 249 1.20 christos static char nthr[64]; 250 1.20 christos sprintf(nthr, "%u", nthreads + 1); 251 1.20 christos setenv("RUMP_NCPU", nthr, 1); 252 1.11 rmind } 253 1.11 rmind break; 254 1.1 rmind default: 255 1.20 christos usage(argv[0]); 256 1.1 rmind } 257 1.1 rmind } 258 1.1 rmind 259 1.4 rmind /* 260 1.11 rmind * Either benchmark or test. If stream analysis, then the 261 1.11 rmind * interface should be specified. If benchmark, then the 262 1.11 rmind * config should be loaded. 263 1.4 rmind */ 264 1.12 rmind if ((benchmark != NULL) == test && (stream && !interface)) { 265 1.20 christos usage(argv[0]); 266 1.2 rmind } 267 1.11 rmind if (benchmark && (!config || !nthreads)) { 268 1.11 rmind errx(EXIT_FAILURE, "missing config for the benchmark or " 269 1.11 rmind "invalid thread count"); 270 1.11 rmind } 271 1.2 rmind 272 1.20 christos /* 273 1.20 christos * Initialise the NPF kernel component. 274 1.20 christos */ 275 1.20 christos npf_kern_init(); 276 1.17 rmind rumpns_npf_test_init(inet_pton, inet_ntop, random); 277 1.5 rmind 278 1.2 rmind if (config) { 279 1.2 rmind load_npf_config(config); 280 1.2 rmind } 281 1.13 rmind if (interface && (ifp = rumpns_npf_test_getif(interface)) == 0) { 282 1.4 rmind errx(EXIT_FAILURE, "failed to find the interface"); 283 1.4 rmind } 284 1.4 rmind 285 1.6 rmind fail = false; 286 1.2 rmind 287 1.2 rmind if (test) { 288 1.7 martin if (!testname || strcmp("nbuf", testname) == 0) { 289 1.7 martin ok = rumpns_npf_nbuf_test(verbose); 290 1.7 martin fail |= result("nbuf", ok); 291 1.7 martin tname_matched = true; 292 1.7 martin } 293 1.1 rmind 294 1.9 rmind if (!testname || strcmp("bpf", testname) == 0) { 295 1.9 rmind ok = rumpns_npf_bpf_test(verbose); 296 1.9 rmind fail |= result("bpf", ok); 297 1.9 rmind tname_matched = true; 298 1.9 rmind } 299 1.9 rmind 300 1.7 martin if (!testname || strcmp("table", testname) == 0) { 301 1.16 rmind void *cdb; 302 1.16 rmind size_t len; 303 1.16 rmind 304 1.16 rmind cdb = generate_test_cdb(&len); 305 1.16 rmind ok = rumpns_npf_table_test(verbose, cdb, len); 306 1.7 martin fail |= result("table", ok); 307 1.7 martin tname_matched = true; 308 1.16 rmind munmap(cdb, len); 309 1.7 martin } 310 1.3 rmind 311 1.7 martin if (!testname || strcmp("state", testname) == 0) { 312 1.7 martin ok = rumpns_npf_state_test(verbose); 313 1.7 martin fail |= result("state", ok); 314 1.7 martin tname_matched = true; 315 1.7 martin } 316 1.23 rmind 317 1.25 rmind if (!testname || strcmp("gc", testname) == 0) { 318 1.25 rmind ok = rumpns_npf_gc_test(verbose); 319 1.25 rmind fail |= result("gc", ok); 320 1.23 rmind tname_matched = true; 321 1.23 rmind } 322 1.2 rmind } 323 1.2 rmind 324 1.4 rmind if (test && config) { 325 1.7 martin if (!testname || strcmp("rule", testname) == 0) { 326 1.7 martin ok = rumpns_npf_rule_test(verbose); 327 1.29 joe fail |= result("rules - layer 3", ok); 328 1.29 joe tname_matched = true; 329 1.29 joe } 330 1.29 joe 331 1.29 joe if (!testname || strcmp("ether", testname) == 0) { 332 1.29 joe ok = rumpns_npf_layer2_rule_test(verbose); 333 1.29 joe fail |= result("rules - layer 2", ok); 334 1.7 martin tname_matched = true; 335 1.7 martin } 336 1.4 rmind 337 1.30 joe if (!testname || strcmp("l2defpass", testname) == 0) { 338 1.30 joe ok = rumpns_npf_layer2only_test(verbose); 339 1.30 joe fail |= result(" layer 2 default pass", ok); 340 1.30 joe tname_matched = true; 341 1.30 joe } 342 1.30 joe 343 1.28 joe if (!testname || strcmp("guid", testname) == 0) { 344 1.28 joe ok = rumpns_npf_guid_test(verbose); 345 1.28 joe fail |= result("guid", ok); 346 1.28 joe tname_matched = true; 347 1.28 joe } 348 1.28 joe 349 1.7 martin if (!testname || strcmp("nat", testname) == 0) { 350 1.20 christos srandom(1); 351 1.7 martin ok = rumpns_npf_nat_test(verbose); 352 1.7 martin fail |= result("nat", ok); 353 1.7 martin tname_matched = true; 354 1.7 martin } 355 1.4 rmind } 356 1.4 rmind 357 1.2 rmind if (stream) { 358 1.13 rmind process_stream(stream, NULL, ifp); 359 1.2 rmind } 360 1.1 rmind 361 1.11 rmind if (benchmark) { 362 1.12 rmind if (strcmp("rule", benchmark) == 0) { 363 1.12 rmind rumpns_npf_test_conc(false, nthreads); 364 1.12 rmind } 365 1.12 rmind if (strcmp("state", benchmark) == 0) { 366 1.12 rmind rumpns_npf_test_conc(true, nthreads); 367 1.12 rmind } 368 1.11 rmind } 369 1.11 rmind 370 1.20 christos rumpns_npf_test_fini(); 371 1.20 christos npf_kern_fini(); 372 1.1 rmind 373 1.7 martin if (testname && !tname_matched) 374 1.7 martin errx(EXIT_FAILURE, "test \"%s\" unknown", testname); 375 1.7 martin 376 1.6 rmind return fail ? EXIT_FAILURE : EXIT_SUCCESS; 377 1.1 rmind } 378