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