1 /* $NetBSD: npftest.c,v 1.20 2016/12/26 23:05:05 christos 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 <prop/proplib.h> 26 27 #include <rump/rump.h> 28 #include <rump/rump_syscalls.h> 29 #endif 30 31 #include <cdbw.h> 32 33 #include "npftest.h" 34 35 static bool verbose, quiet; 36 37 __dead static void 38 usage(const char *progname) 39 { 40 printf("usage:\n" 41 " %s [ -q | -v ] [ -c <config> ] " 42 "[ -i <interface> ] < -b | -t | -s file >\n" 43 " %s -T <testname> -c <config>\n" 44 " %s -L\n" 45 "where:\n" 46 "\t-b: benchmark\n" 47 "\t-t: regression test\n" 48 "\t-T <testname>: specific test\n" 49 "\t-s <file>: pcap stream\n" 50 "\t-c <config>: NPF configuration file\n" 51 "\t-i <interface>: primary interface\n" 52 "\t-L: list testnames and description for -T\n" 53 "\t-q: quiet mode\n" 54 "\t-v: verbose mode\n", 55 progname, progname, progname); 56 exit(EXIT_FAILURE); 57 } 58 59 __dead static void 60 describe_tests(void) 61 { 62 printf( "nbuf\tbasic npf mbuf handling\n" 63 "bpf\tBPF coprocessor\n" 64 "table\ttable handling\n" 65 "state\tstate handling and processing\n" 66 "rule\trule processing\n" 67 "nat\tNAT rule processing\n"); 68 exit(EXIT_SUCCESS); 69 } 70 71 static bool 72 result(const char *testcase, bool ok) 73 { 74 if (!quiet) { 75 printf("NPF %-10s\t%s\n", testcase, ok ? "OK" : "fail"); 76 } 77 if (verbose) { 78 puts("-----"); 79 } 80 return !ok; 81 } 82 83 static void 84 load_npf_config_ifs(prop_dictionary_t dbg_dict) 85 { 86 prop_array_t iflist = prop_dictionary_get(dbg_dict, "interfaces"); 87 prop_object_iterator_t it = prop_array_iterator(iflist); 88 prop_dictionary_t ifdict; 89 90 while ((ifdict = prop_object_iterator_next(it)) != NULL) { 91 const char *ifname = NULL; 92 93 prop_dictionary_get_cstring_nocopy(ifdict, "name", &ifname); 94 (void)rumpns_npf_test_addif(ifname, true, verbose); 95 } 96 prop_object_iterator_release(it); 97 } 98 99 static void 100 load_npf_config(const char *config) 101 { 102 prop_dictionary_t npf_dict, dbg_dict; 103 void *xml; 104 int error; 105 106 /* Read the configuration from the specified file. */ 107 npf_dict = prop_dictionary_internalize_from_file(config); 108 if (!npf_dict) { 109 err(EXIT_FAILURE, "prop_dictionary_internalize_from_file"); 110 } 111 xml = prop_dictionary_externalize(npf_dict); 112 113 /* Inspect the debug data. Create the interfaces, if any. */ 114 dbg_dict = prop_dictionary_get(npf_dict, "debug"); 115 if (dbg_dict) { 116 load_npf_config_ifs(dbg_dict); 117 } 118 prop_object_release(npf_dict); 119 120 /* Pass the XML configuration for NPF kernel component to load. */ 121 error = rumpns_npf_test_load(xml); 122 if (error) { 123 errx(EXIT_FAILURE, "npf_test_load: %s\n", strerror(error)); 124 } 125 free(xml); 126 127 if (verbose) { 128 printf("Loaded NPF config at '%s'\n", config); 129 } 130 } 131 132 static void * 133 generate_test_cdb(size_t *size) 134 { 135 in_addr_t addr; 136 struct cdbw *cdbw; 137 struct stat sb; 138 char sfn[32]; 139 int alen, fd; 140 void *cdb; 141 142 if ((cdbw = cdbw_open()) == NULL) { 143 err(EXIT_FAILURE, "cdbw_open"); 144 } 145 strlcpy(sfn, "/tmp/npftest_cdb.XXXXXX", sizeof(sfn)); 146 if ((fd = mkstemp(sfn)) == -1) { 147 err(EXIT_FAILURE, "mkstemp"); 148 } 149 unlink(sfn); 150 151 addr = inet_addr("192.168.1.1"), alen = sizeof(struct in_addr); 152 if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1) 153 err(EXIT_FAILURE, "cdbw_put"); 154 155 addr = inet_addr("10.0.0.2"), alen = sizeof(struct in_addr); 156 if (cdbw_put(cdbw, &addr, alen, &addr, alen) == -1) 157 err(EXIT_FAILURE, "cdbw_put"); 158 159 if (cdbw_output(cdbw, fd, "npf-table-cdb", NULL) == -1) { 160 err(EXIT_FAILURE, "cdbw_output"); 161 } 162 cdbw_close(cdbw); 163 164 if (fstat(fd, &sb) == -1) { 165 err(EXIT_FAILURE, "fstat"); 166 } 167 if ((cdb = mmap(NULL, sb.st_size, PROT_READ, 168 MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) { 169 err(EXIT_FAILURE, "mmap"); 170 } 171 close(fd); 172 173 *size = sb.st_size; 174 return cdb; 175 } 176 177 static void 178 npf_kern_init(void) 179 { 180 #if !defined(_NPF_STANDALONE) 181 /* XXX rn_init */ 182 extern int rumpns_max_keylen; 183 rumpns_max_keylen = 1; 184 185 rump_init(); 186 rump_schedule(); 187 #endif 188 } 189 190 static void 191 npf_kern_fini(void) 192 { 193 #if !defined(_NPF_STANDALONE) 194 rump_unschedule(); 195 #endif 196 } 197 198 int 199 main(int argc, char **argv) 200 { 201 bool test, ok, fail, tname_matched; 202 char *benchmark, *config, *interface, *stream, *testname; 203 unsigned nthreads = 0; 204 ifnet_t *ifp = NULL; 205 int ch; 206 207 benchmark = NULL; 208 test = false; 209 210 tname_matched = false; 211 testname = NULL; 212 config = NULL; 213 interface = NULL; 214 stream = NULL; 215 216 verbose = false; 217 quiet = false; 218 219 while ((ch = getopt(argc, argv, "b:qvc:i:s:tT:Lp:")) != -1) { 220 switch (ch) { 221 case 'b': 222 benchmark = optarg; 223 break; 224 case 'q': 225 quiet = true; 226 break; 227 case 'v': 228 verbose = true; 229 break; 230 case 'c': 231 config = optarg; 232 break; 233 case 'i': 234 interface = optarg; 235 break; 236 case 's': 237 stream = optarg; 238 break; 239 case 't': 240 test = true; 241 break; 242 case 'T': 243 test = true; 244 testname = optarg; 245 break; 246 case 'L': 247 describe_tests(); 248 break; 249 case 'p': 250 /* Note: RUMP_NCPU must be high enough. */ 251 if ((nthreads = atoi(optarg)) > 0 && 252 getenv("RUMP_NCPU") == NULL) { 253 static char nthr[64]; 254 sprintf(nthr, "%u", nthreads + 1); 255 setenv("RUMP_NCPU", nthr, 1); 256 } 257 break; 258 default: 259 usage(argv[0]); 260 } 261 } 262 263 /* 264 * Either benchmark or test. If stream analysis, then the 265 * interface should be specified. If benchmark, then the 266 * config should be loaded. 267 */ 268 if ((benchmark != NULL) == test && (stream && !interface)) { 269 usage(argv[0]); 270 } 271 if (benchmark && (!config || !nthreads)) { 272 errx(EXIT_FAILURE, "missing config for the benchmark or " 273 "invalid thread count"); 274 } 275 276 /* 277 * Initialise the NPF kernel component. 278 */ 279 npf_kern_init(); 280 rumpns_npf_test_init(inet_pton, inet_ntop, random); 281 282 if (config) { 283 load_npf_config(config); 284 } 285 if (interface && (ifp = rumpns_npf_test_getif(interface)) == 0) { 286 errx(EXIT_FAILURE, "failed to find the interface"); 287 } 288 289 srandom(1); 290 fail = false; 291 292 if (test) { 293 if (!testname || strcmp("nbuf", testname) == 0) { 294 ok = rumpns_npf_nbuf_test(verbose); 295 fail |= result("nbuf", ok); 296 tname_matched = true; 297 } 298 299 if (!testname || strcmp("bpf", testname) == 0) { 300 ok = rumpns_npf_bpf_test(verbose); 301 fail |= result("bpf", ok); 302 tname_matched = true; 303 } 304 305 if (!testname || strcmp("table", testname) == 0) { 306 void *cdb; 307 size_t len; 308 309 cdb = generate_test_cdb(&len); 310 ok = rumpns_npf_table_test(verbose, cdb, len); 311 fail |= result("table", ok); 312 tname_matched = true; 313 munmap(cdb, len); 314 } 315 316 if (!testname || strcmp("state", testname) == 0) { 317 ok = rumpns_npf_state_test(verbose); 318 fail |= result("state", ok); 319 tname_matched = true; 320 } 321 } 322 323 if (test && config) { 324 if (!testname || strcmp("rule", testname) == 0) { 325 ok = rumpns_npf_rule_test(verbose); 326 fail |= result("rule", ok); 327 tname_matched = true; 328 } 329 330 if (!testname || strcmp("nat", testname) == 0) { 331 srandom(1); 332 ok = rumpns_npf_nat_test(verbose); 333 fail |= result("nat", ok); 334 tname_matched = true; 335 } 336 } 337 338 if (stream) { 339 process_stream(stream, NULL, ifp); 340 } 341 342 if (benchmark) { 343 if (strcmp("rule", benchmark) == 0) { 344 rumpns_npf_test_conc(false, nthreads); 345 } 346 if (strcmp("state", benchmark) == 0) { 347 rumpns_npf_test_conc(true, nthreads); 348 } 349 } 350 351 rumpns_npf_test_fini(); 352 npf_kern_fini(); 353 354 if (testname && !tname_matched) 355 errx(EXIT_FAILURE, "test \"%s\" unknown", testname); 356 357 return fail ? EXIT_FAILURE : EXIT_SUCCESS; 358 } 359