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