1 /* $NetBSD: npftest.c,v 1.24 2019/07/23 00:52:02 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 fail = false; 281 282 if (test) { 283 if (!testname || strcmp("nbuf", testname) == 0) { 284 ok = rumpns_npf_nbuf_test(verbose); 285 fail |= result("nbuf", ok); 286 tname_matched = true; 287 } 288 289 if (!testname || strcmp("bpf", testname) == 0) { 290 ok = rumpns_npf_bpf_test(verbose); 291 fail |= result("bpf", ok); 292 tname_matched = true; 293 } 294 295 if (!testname || strcmp("table", testname) == 0) { 296 void *cdb; 297 size_t len; 298 299 cdb = generate_test_cdb(&len); 300 ok = rumpns_npf_table_test(verbose, cdb, len); 301 fail |= result("table", ok); 302 tname_matched = true; 303 munmap(cdb, len); 304 } 305 306 if (!testname || strcmp("state", testname) == 0) { 307 ok = rumpns_npf_state_test(verbose); 308 fail |= result("state", ok); 309 tname_matched = true; 310 } 311 312 if (!testname || strcmp("conn", testname) == 0) { 313 ok = rumpns_npf_conn_test(verbose); 314 fail |= result("conn", ok); 315 tname_matched = true; 316 } 317 } 318 319 if (test && config) { 320 if (!testname || strcmp("rule", testname) == 0) { 321 ok = rumpns_npf_rule_test(verbose); 322 fail |= result("rule", ok); 323 tname_matched = true; 324 } 325 326 if (!testname || strcmp("nat", testname) == 0) { 327 srandom(1); 328 ok = rumpns_npf_nat_test(verbose); 329 fail |= result("nat", ok); 330 tname_matched = true; 331 } 332 } 333 334 if (stream) { 335 process_stream(stream, NULL, ifp); 336 } 337 338 if (benchmark) { 339 if (strcmp("rule", benchmark) == 0) { 340 rumpns_npf_test_conc(false, nthreads); 341 } 342 if (strcmp("state", benchmark) == 0) { 343 rumpns_npf_test_conc(true, nthreads); 344 } 345 } 346 347 rumpns_npf_test_fini(); 348 npf_kern_fini(); 349 350 if (testname && !tname_matched) 351 errx(EXIT_FAILURE, "test \"%s\" unknown", testname); 352 353 return fail ? EXIT_FAILURE : EXIT_SUCCESS; 354 } 355