1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (c) 2021 Microsoft Corporation 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 9 #include <rte_bus_vdev.h> 10 #include <rte_ethdev.h> 11 #include <rte_ether.h> 12 #include <rte_ip.h> 13 #include <rte_mbuf.h> 14 #include <rte_mempool.h> 15 #include <rte_net.h> 16 #include <rte_pcapng.h> 17 #include <rte_random.h> 18 #include <rte_reciprocal.h> 19 #include <rte_time.h> 20 #include <rte_udp.h> 21 22 #include <pcap/pcap.h> 23 24 #include "test.h" 25 26 #define PCAPNG_TEST_DEBUG 0 27 28 #define TOTAL_PACKETS 4096 29 #define MAX_BURST 64 30 #define MAX_GAP_US 100000 31 #define DUMMY_MBUF_NUM 3 32 33 static struct rte_mempool *mp; 34 static const uint32_t pkt_len = 200; 35 static uint16_t port_id; 36 static const char null_dev[] = "net_null0"; 37 38 /* first mbuf in the packet, should always be at offset 0 */ 39 struct dummy_mbuf { 40 struct rte_mbuf mb[DUMMY_MBUF_NUM]; 41 uint8_t buf[DUMMY_MBUF_NUM][RTE_MBUF_DEFAULT_BUF_SIZE]; 42 }; 43 44 static void 45 dummy_mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len, 46 uint32_t data_len) 47 { 48 uint32_t i; 49 uint8_t *db; 50 51 mb->buf_addr = buf; 52 rte_mbuf_iova_set(mb, (uintptr_t)buf); 53 mb->buf_len = buf_len; 54 rte_mbuf_refcnt_set(mb, 1); 55 56 /* set pool pointer to dummy value, test doesn't use it */ 57 mb->pool = (void *)buf; 58 59 rte_pktmbuf_reset(mb); 60 db = (uint8_t *)rte_pktmbuf_append(mb, data_len); 61 62 for (i = 0; i != data_len; i++) 63 db[i] = i; 64 } 65 66 /* Make an IP packet consisting of chain of one packets */ 67 static void 68 mbuf1_prepare(struct dummy_mbuf *dm, uint32_t plen) 69 { 70 struct { 71 struct rte_ether_hdr eth; 72 struct rte_ipv4_hdr ip; 73 struct rte_udp_hdr udp; 74 } pkt = { 75 .eth = { 76 .dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, 77 .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4), 78 }, 79 .ip = { 80 .version_ihl = RTE_IPV4_VHL_DEF, 81 .time_to_live = 1, 82 .next_proto_id = IPPROTO_UDP, 83 .src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK), 84 .dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST), 85 }, 86 .udp = { 87 .dst_port = rte_cpu_to_be_16(9), /* Discard port */ 88 }, 89 }; 90 91 memset(dm, 0, sizeof(*dm)); 92 dummy_mbuf_prep(&dm->mb[0], dm->buf[0], sizeof(dm->buf[0]), plen); 93 94 rte_eth_random_addr(pkt.eth.src_addr.addr_bytes); 95 plen -= sizeof(struct rte_ether_hdr); 96 97 pkt.ip.total_length = rte_cpu_to_be_16(plen); 98 pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip); 99 100 plen -= sizeof(struct rte_ipv4_hdr); 101 pkt.udp.src_port = rte_rand(); 102 pkt.udp.dgram_len = rte_cpu_to_be_16(plen); 103 104 memcpy(rte_pktmbuf_mtod(dm->mb, void *), &pkt, sizeof(pkt)); 105 106 /* Idea here is to create mbuf chain big enough that after mbuf deep copy they won't be 107 * compressed into single mbuf to properly test store of chained mbufs 108 */ 109 dummy_mbuf_prep(&dm->mb[1], dm->buf[1], sizeof(dm->buf[1]), pkt_len); 110 dummy_mbuf_prep(&dm->mb[2], dm->buf[2], sizeof(dm->buf[2]), pkt_len); 111 rte_pktmbuf_chain(&dm->mb[0], &dm->mb[1]); 112 rte_pktmbuf_chain(&dm->mb[0], &dm->mb[2]); 113 } 114 115 static int 116 test_setup(void) 117 { 118 port_id = rte_eth_dev_count_avail(); 119 120 /* Make a dummy null device to snoop on */ 121 if (rte_vdev_init(null_dev, NULL) != 0) { 122 fprintf(stderr, "Failed to create vdev '%s'\n", null_dev); 123 goto fail; 124 } 125 126 /* Make a pool for cloned packets */ 127 mp = rte_pktmbuf_pool_create_by_ops("pcapng_test_pool", 128 MAX_BURST * 32, 0, 0, 129 rte_pcapng_mbuf_size(pkt_len) + 128, 130 SOCKET_ID_ANY, "ring_mp_sc"); 131 if (mp == NULL) { 132 fprintf(stderr, "Cannot create mempool\n"); 133 goto fail; 134 } 135 136 return 0; 137 138 fail: 139 rte_vdev_uninit(null_dev); 140 rte_mempool_free(mp); 141 return -1; 142 } 143 144 static int 145 fill_pcapng_file(rte_pcapng_t *pcapng, unsigned int num_packets) 146 { 147 struct dummy_mbuf mbfs; 148 struct rte_mbuf *orig; 149 unsigned int burst_size; 150 unsigned int count; 151 ssize_t len; 152 153 /* make a dummy packet */ 154 mbuf1_prepare(&mbfs, pkt_len); 155 orig = &mbfs.mb[0]; 156 157 for (count = 0; count < num_packets; count += burst_size) { 158 struct rte_mbuf *clones[MAX_BURST]; 159 unsigned int i; 160 161 /* put 1 .. MAX_BURST packets in one write call */ 162 burst_size = rte_rand_max(MAX_BURST) + 1; 163 for (i = 0; i < burst_size; i++) { 164 struct rte_mbuf *mc; 165 166 mc = rte_pcapng_copy(port_id, 0, orig, mp, rte_pktmbuf_pkt_len(orig), 167 RTE_PCAPNG_DIRECTION_IN, NULL); 168 if (mc == NULL) { 169 fprintf(stderr, "Cannot copy packet\n"); 170 return -1; 171 } 172 clones[i] = mc; 173 } 174 175 /* write it to capture file */ 176 len = rte_pcapng_write_packets(pcapng, clones, burst_size); 177 rte_pktmbuf_free_bulk(clones, burst_size); 178 179 if (len <= 0) { 180 fprintf(stderr, "Write of packets failed: %s\n", 181 rte_strerror(rte_errno)); 182 return -1; 183 } 184 185 /* Leave a small gap between packets to test for time wrap */ 186 usleep(rte_rand_max(MAX_GAP_US)); 187 } 188 189 return count; 190 } 191 192 static char * 193 fmt_time(char *buf, size_t size, uint64_t ts_ns) 194 { 195 time_t sec; 196 size_t len; 197 198 sec = ts_ns / NS_PER_S; 199 len = strftime(buf, size, "%X", localtime(&sec)); 200 snprintf(buf + len, size - len, ".%09lu", 201 (unsigned long)(ts_ns % NS_PER_S)); 202 203 return buf; 204 } 205 206 /* Context for the pcap_loop callback */ 207 struct pkt_print_ctx { 208 pcap_t *pcap; 209 unsigned int count; 210 uint64_t start_ns; 211 uint64_t end_ns; 212 }; 213 214 static void 215 print_packet(uint64_t ts_ns, const struct rte_ether_hdr *eh, size_t len) 216 { 217 char tbuf[128], src[64], dst[64]; 218 219 fmt_time(tbuf, sizeof(tbuf), ts_ns); 220 rte_ether_format_addr(dst, sizeof(dst), &eh->dst_addr); 221 rte_ether_format_addr(src, sizeof(src), &eh->src_addr); 222 printf("%s: %s -> %s type %x length %zu\n", 223 tbuf, src, dst, rte_be_to_cpu_16(eh->ether_type), len); 224 } 225 226 /* Callback from pcap_loop used to validate packets in the file */ 227 static void 228 parse_pcap_packet(u_char *user, const struct pcap_pkthdr *h, 229 const u_char *bytes) 230 { 231 struct pkt_print_ctx *ctx = (struct pkt_print_ctx *)user; 232 const struct rte_ether_hdr *eh; 233 const struct rte_ipv4_hdr *ip; 234 uint64_t ns; 235 236 eh = (const struct rte_ether_hdr *)bytes; 237 ip = (const struct rte_ipv4_hdr *)(eh + 1); 238 239 ctx->count += 1; 240 241 /* The pcap library is misleading in reporting timestamp. 242 * packet header struct gives timestamp as a timeval (ie. usec); 243 * but the file is open in nanonsecond mode therefore 244 * the timestamp is really in timespec (ie. nanoseconds). 245 */ 246 ns = (uint64_t)h->ts.tv_sec * NS_PER_S + h->ts.tv_usec; 247 if (ns < ctx->start_ns || ns > ctx->end_ns) { 248 char tstart[128], tend[128]; 249 250 fmt_time(tstart, sizeof(tstart), ctx->start_ns); 251 fmt_time(tend, sizeof(tend), ctx->end_ns); 252 fprintf(stderr, "Timestamp out of range [%s .. %s]\n", 253 tstart, tend); 254 goto error; 255 } 256 257 if (!rte_is_broadcast_ether_addr(&eh->dst_addr)) { 258 fprintf(stderr, "Destination is not broadcast\n"); 259 goto error; 260 } 261 262 if (rte_ipv4_cksum(ip) != 0) { 263 fprintf(stderr, "Bad IPv4 checksum\n"); 264 goto error; 265 } 266 267 return; /* packet is normal */ 268 269 error: 270 print_packet(ns, eh, h->len); 271 272 /* Stop parsing at first error */ 273 pcap_breakloop(ctx->pcap); 274 } 275 276 static uint64_t 277 current_timestamp(void) 278 { 279 struct timespec ts; 280 281 clock_gettime(CLOCK_REALTIME, &ts); 282 return rte_timespec_to_ns(&ts); 283 } 284 285 /* 286 * Open the resulting pcapng file with libpcap 287 * Would be better to use capinfos from wireshark 288 * but that creates an unwanted dependency. 289 */ 290 static int 291 valid_pcapng_file(const char *file_name, uint64_t started, unsigned int expected) 292 { 293 char errbuf[PCAP_ERRBUF_SIZE]; 294 struct pkt_print_ctx ctx = { }; 295 int ret; 296 297 ctx.start_ns = started; 298 ctx.end_ns = current_timestamp(); 299 300 ctx.pcap = pcap_open_offline_with_tstamp_precision(file_name, 301 PCAP_TSTAMP_PRECISION_NANO, 302 errbuf); 303 if (ctx.pcap == NULL) { 304 fprintf(stderr, "pcap_open_offline('%s') failed: %s\n", 305 file_name, errbuf); 306 return -1; 307 } 308 309 ret = pcap_loop(ctx.pcap, 0, parse_pcap_packet, (u_char *)&ctx); 310 if (ret != 0) { 311 fprintf(stderr, "pcap_dispatch: failed: %s\n", 312 pcap_geterr(ctx.pcap)); 313 } else if (ctx.count != expected) { 314 printf("Only %u packets, expected %u\n", 315 ctx.count, expected); 316 ret = -1; 317 } 318 319 pcap_close(ctx.pcap); 320 321 return ret; 322 } 323 324 static int 325 test_add_interface(void) 326 { 327 char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; 328 static rte_pcapng_t *pcapng; 329 int ret, tmp_fd; 330 uint64_t now = current_timestamp(); 331 332 tmp_fd = mkstemps(file_name, strlen(".pcapng")); 333 if (tmp_fd == -1) { 334 perror("mkstemps() failure"); 335 goto fail; 336 } 337 printf("pcapng: output file %s\n", file_name); 338 339 /* open a test capture file */ 340 pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_addif", NULL); 341 if (pcapng == NULL) { 342 fprintf(stderr, "rte_pcapng_fdopen failed\n"); 343 close(tmp_fd); 344 goto fail; 345 } 346 347 /* Add interface to the file */ 348 ret = rte_pcapng_add_interface(pcapng, port_id, 349 NULL, NULL, NULL); 350 if (ret < 0) { 351 fprintf(stderr, "can not add port %u\n", port_id); 352 goto fail; 353 } 354 355 /* Add interface with ifname and ifdescr */ 356 ret = rte_pcapng_add_interface(pcapng, port_id, 357 "myeth", "Some long description", NULL); 358 if (ret < 0) { 359 fprintf(stderr, "can not add port %u with ifname\n", port_id); 360 goto fail; 361 } 362 363 /* Add interface with filter */ 364 ret = rte_pcapng_add_interface(pcapng, port_id, 365 NULL, NULL, "tcp port 8080"); 366 if (ret < 0) { 367 fprintf(stderr, "can not add port %u with filter\n", port_id); 368 goto fail; 369 } 370 371 rte_pcapng_close(pcapng); 372 373 ret = valid_pcapng_file(file_name, now, 0); 374 /* if test fails want to investigate the file */ 375 if (ret == 0) 376 unlink(file_name); 377 378 return ret; 379 380 fail: 381 rte_pcapng_close(pcapng); 382 return -1; 383 } 384 385 static int 386 test_write_packets(void) 387 { 388 char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; 389 static rte_pcapng_t *pcapng; 390 int ret, tmp_fd, count; 391 uint64_t now = current_timestamp(); 392 393 tmp_fd = mkstemps(file_name, strlen(".pcapng")); 394 if (tmp_fd == -1) { 395 perror("mkstemps() failure"); 396 goto fail; 397 } 398 printf("pcapng: output file %s\n", file_name); 399 400 /* open a test capture file */ 401 pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_test", NULL); 402 if (pcapng == NULL) { 403 fprintf(stderr, "rte_pcapng_fdopen failed\n"); 404 close(tmp_fd); 405 goto fail; 406 } 407 408 /* Add interface to the file */ 409 ret = rte_pcapng_add_interface(pcapng, port_id, 410 NULL, NULL, NULL); 411 if (ret < 0) { 412 fprintf(stderr, "can not add port %u\n", port_id); 413 goto fail; 414 } 415 416 count = fill_pcapng_file(pcapng, TOTAL_PACKETS); 417 if (count < 0) 418 goto fail; 419 420 /* write a statistics block */ 421 ret = rte_pcapng_write_stats(pcapng, port_id, 422 count, 0, "end of test"); 423 if (ret <= 0) { 424 fprintf(stderr, "Write of statistics failed\n"); 425 goto fail; 426 } 427 428 rte_pcapng_close(pcapng); 429 430 ret = valid_pcapng_file(file_name, now, count); 431 /* if test fails want to investigate the file */ 432 if (ret == 0) 433 unlink(file_name); 434 435 return ret; 436 437 fail: 438 rte_pcapng_close(pcapng); 439 return -1; 440 } 441 442 static void 443 test_cleanup(void) 444 { 445 rte_mempool_free(mp); 446 rte_vdev_uninit(null_dev); 447 } 448 449 static struct 450 unit_test_suite test_pcapng_suite = { 451 .setup = test_setup, 452 .teardown = test_cleanup, 453 .suite_name = "Test Pcapng Unit Test Suite", 454 .unit_test_cases = { 455 TEST_CASE(test_add_interface), 456 TEST_CASE(test_write_packets), 457 TEST_CASES_END() 458 } 459 }; 460 461 static int 462 test_pcapng(void) 463 { 464 return unit_test_suite_runner(&test_pcapng_suite); 465 } 466 467 REGISTER_FAST_TEST(pcapng_autotest, true, true, test_pcapng); 468