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 = "\xff\xff\xff\xff\xff\xff", 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 107 static int 108 test_setup(void) 109 { 110 port_id = rte_eth_dev_count_avail(); 111 112 /* Make a dummy null device to snoop on */ 113 if (rte_vdev_init(null_dev, NULL) != 0) { 114 fprintf(stderr, "Failed to create vdev '%s'\n", null_dev); 115 goto fail; 116 } 117 118 /* Make a pool for cloned packets */ 119 mp = rte_pktmbuf_pool_create_by_ops("pcapng_test_pool", 120 MAX_BURST, 0, 0, 121 rte_pcapng_mbuf_size(pkt_len) + 128, 122 SOCKET_ID_ANY, "ring_mp_sc"); 123 if (mp == NULL) { 124 fprintf(stderr, "Cannot create mempool\n"); 125 goto fail; 126 } 127 128 return 0; 129 130 fail: 131 rte_vdev_uninit(null_dev); 132 rte_mempool_free(mp); 133 return -1; 134 } 135 136 static int 137 fill_pcapng_file(rte_pcapng_t *pcapng, unsigned int num_packets) 138 { 139 struct dummy_mbuf mbfs; 140 struct rte_mbuf *orig; 141 unsigned int burst_size; 142 unsigned int count; 143 ssize_t len; 144 145 /* make a dummy packet */ 146 mbuf1_prepare(&mbfs, pkt_len); 147 orig = &mbfs.mb[0]; 148 149 for (count = 0; count < num_packets; count += burst_size) { 150 struct rte_mbuf *clones[MAX_BURST]; 151 unsigned int i; 152 153 /* put 1 .. MAX_BURST packets in one write call */ 154 burst_size = rte_rand_max(MAX_BURST) + 1; 155 for (i = 0; i < burst_size; i++) { 156 struct rte_mbuf *mc; 157 158 mc = rte_pcapng_copy(port_id, 0, orig, mp, pkt_len, 159 RTE_PCAPNG_DIRECTION_IN, NULL); 160 if (mc == NULL) { 161 fprintf(stderr, "Cannot copy packet\n"); 162 return -1; 163 } 164 clones[i] = mc; 165 } 166 167 /* write it to capture file */ 168 len = rte_pcapng_write_packets(pcapng, clones, burst_size); 169 rte_pktmbuf_free_bulk(clones, burst_size); 170 171 if (len <= 0) { 172 fprintf(stderr, "Write of packets failed: %s\n", 173 rte_strerror(rte_errno)); 174 return -1; 175 } 176 177 /* Leave a small gap between packets to test for time wrap */ 178 usleep(rte_rand_max(MAX_GAP_US)); 179 } 180 181 return count; 182 } 183 184 static char * 185 fmt_time(char *buf, size_t size, uint64_t ts_ns) 186 { 187 time_t sec; 188 size_t len; 189 190 sec = ts_ns / NS_PER_S; 191 len = strftime(buf, size, "%X", localtime(&sec)); 192 snprintf(buf + len, size - len, ".%09lu", 193 (unsigned long)(ts_ns % NS_PER_S)); 194 195 return buf; 196 } 197 198 /* Context for the pcap_loop callback */ 199 struct pkt_print_ctx { 200 pcap_t *pcap; 201 unsigned int count; 202 uint64_t start_ns; 203 uint64_t end_ns; 204 }; 205 206 static void 207 print_packet(uint64_t ts_ns, const struct rte_ether_hdr *eh, size_t len) 208 { 209 char tbuf[128], src[64], dst[64]; 210 211 fmt_time(tbuf, sizeof(tbuf), ts_ns); 212 rte_ether_format_addr(dst, sizeof(dst), &eh->dst_addr); 213 rte_ether_format_addr(src, sizeof(src), &eh->src_addr); 214 printf("%s: %s -> %s type %x length %zu\n", 215 tbuf, src, dst, rte_be_to_cpu_16(eh->ether_type), len); 216 } 217 218 /* Callback from pcap_loop used to validate packets in the file */ 219 static void 220 parse_pcap_packet(u_char *user, const struct pcap_pkthdr *h, 221 const u_char *bytes) 222 { 223 struct pkt_print_ctx *ctx = (struct pkt_print_ctx *)user; 224 const struct rte_ether_hdr *eh; 225 const struct rte_ipv4_hdr *ip; 226 uint64_t ns; 227 228 eh = (const struct rte_ether_hdr *)bytes; 229 ip = (const struct rte_ipv4_hdr *)(eh + 1); 230 231 ctx->count += 1; 232 233 /* The pcap library is misleading in reporting timestamp. 234 * packet header struct gives timestamp as a timeval (ie. usec); 235 * but the file is open in nanonsecond mode therefore 236 * the timestamp is really in timespec (ie. nanoseconds). 237 */ 238 ns = h->ts.tv_sec * NS_PER_S + h->ts.tv_usec; 239 if (ns < ctx->start_ns || ns > ctx->end_ns) { 240 char tstart[128], tend[128]; 241 242 fmt_time(tstart, sizeof(tstart), ctx->start_ns); 243 fmt_time(tend, sizeof(tend), ctx->end_ns); 244 fprintf(stderr, "Timestamp out of range [%s .. %s]\n", 245 tstart, tend); 246 goto error; 247 } 248 249 if (!rte_is_broadcast_ether_addr(&eh->dst_addr)) { 250 fprintf(stderr, "Destination is not broadcast\n"); 251 goto error; 252 } 253 254 if (rte_ipv4_cksum(ip) != 0) { 255 fprintf(stderr, "Bad IPv4 checksum\n"); 256 goto error; 257 } 258 259 return; /* packet is normal */ 260 261 error: 262 print_packet(ns, eh, h->len); 263 264 /* Stop parsing at first error */ 265 pcap_breakloop(ctx->pcap); 266 } 267 268 static uint64_t 269 current_timestamp(void) 270 { 271 struct timespec ts; 272 273 clock_gettime(CLOCK_REALTIME, &ts); 274 return rte_timespec_to_ns(&ts); 275 } 276 277 /* 278 * Open the resulting pcapng file with libpcap 279 * Would be better to use capinfos from wireshark 280 * but that creates an unwanted dependency. 281 */ 282 static int 283 valid_pcapng_file(const char *file_name, uint64_t started, unsigned int expected) 284 { 285 char errbuf[PCAP_ERRBUF_SIZE]; 286 struct pkt_print_ctx ctx = { }; 287 int ret; 288 289 ctx.start_ns = started; 290 ctx.end_ns = current_timestamp(); 291 292 ctx.pcap = pcap_open_offline_with_tstamp_precision(file_name, 293 PCAP_TSTAMP_PRECISION_NANO, 294 errbuf); 295 if (ctx.pcap == NULL) { 296 fprintf(stderr, "pcap_open_offline('%s') failed: %s\n", 297 file_name, errbuf); 298 return -1; 299 } 300 301 ret = pcap_loop(ctx.pcap, 0, parse_pcap_packet, (u_char *)&ctx); 302 if (ret != 0) { 303 fprintf(stderr, "pcap_dispatch: failed: %s\n", 304 pcap_geterr(ctx.pcap)); 305 } else if (ctx.count != expected) { 306 printf("Only %u packets, expected %u\n", 307 ctx.count, expected); 308 ret = -1; 309 } 310 311 pcap_close(ctx.pcap); 312 313 return ret; 314 } 315 316 static int 317 test_add_interface(void) 318 { 319 char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; 320 static rte_pcapng_t *pcapng; 321 int ret, tmp_fd; 322 uint64_t now = current_timestamp(); 323 324 tmp_fd = mkstemps(file_name, strlen(".pcapng")); 325 if (tmp_fd == -1) { 326 perror("mkstemps() failure"); 327 goto fail; 328 } 329 printf("pcapng: output file %s\n", file_name); 330 331 /* open a test capture file */ 332 pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_addif", NULL); 333 if (pcapng == NULL) { 334 fprintf(stderr, "rte_pcapng_fdopen failed\n"); 335 close(tmp_fd); 336 goto fail; 337 } 338 339 /* Add interface to the file */ 340 ret = rte_pcapng_add_interface(pcapng, port_id, 341 NULL, NULL, NULL); 342 if (ret < 0) { 343 fprintf(stderr, "can not add port %u\n", port_id); 344 goto fail; 345 } 346 347 /* Add interface with ifname and ifdescr */ 348 ret = rte_pcapng_add_interface(pcapng, port_id, 349 "myeth", "Some long description", NULL); 350 if (ret < 0) { 351 fprintf(stderr, "can not add port %u with ifname\n", port_id); 352 goto fail; 353 } 354 355 /* Add interface with filter */ 356 ret = rte_pcapng_add_interface(pcapng, port_id, 357 NULL, NULL, "tcp port 8080"); 358 if (ret < 0) { 359 fprintf(stderr, "can not add port %u with filter\n", port_id); 360 goto fail; 361 } 362 363 rte_pcapng_close(pcapng); 364 365 ret = valid_pcapng_file(file_name, now, 0); 366 /* if test fails want to investigate the file */ 367 if (ret == 0) 368 unlink(file_name); 369 370 return ret; 371 372 fail: 373 rte_pcapng_close(pcapng); 374 return -1; 375 } 376 377 static int 378 test_write_packets(void) 379 { 380 char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; 381 static rte_pcapng_t *pcapng; 382 int ret, tmp_fd, count; 383 uint64_t now = current_timestamp(); 384 385 tmp_fd = mkstemps(file_name, strlen(".pcapng")); 386 if (tmp_fd == -1) { 387 perror("mkstemps() failure"); 388 goto fail; 389 } 390 printf("pcapng: output file %s\n", file_name); 391 392 /* open a test capture file */ 393 pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_test", NULL); 394 if (pcapng == NULL) { 395 fprintf(stderr, "rte_pcapng_fdopen failed\n"); 396 close(tmp_fd); 397 goto fail; 398 } 399 400 /* Add interface to the file */ 401 ret = rte_pcapng_add_interface(pcapng, port_id, 402 NULL, NULL, NULL); 403 if (ret < 0) { 404 fprintf(stderr, "can not add port %u\n", port_id); 405 goto fail; 406 } 407 408 count = fill_pcapng_file(pcapng, TOTAL_PACKETS); 409 if (count < 0) 410 goto fail; 411 412 /* write a statistics block */ 413 ret = rte_pcapng_write_stats(pcapng, port_id, 414 count, 0, "end of test"); 415 if (ret <= 0) { 416 fprintf(stderr, "Write of statistics failed\n"); 417 goto fail; 418 } 419 420 rte_pcapng_close(pcapng); 421 422 ret = valid_pcapng_file(file_name, now, count); 423 /* if test fails want to investigate the file */ 424 if (ret == 0) 425 unlink(file_name); 426 427 return ret; 428 429 fail: 430 rte_pcapng_close(pcapng); 431 return -1; 432 } 433 434 static void 435 test_cleanup(void) 436 { 437 rte_mempool_free(mp); 438 rte_vdev_uninit(null_dev); 439 } 440 441 static struct 442 unit_test_suite test_pcapng_suite = { 443 .setup = test_setup, 444 .teardown = test_cleanup, 445 .suite_name = "Test Pcapng Unit Test Suite", 446 .unit_test_cases = { 447 TEST_CASE(test_add_interface), 448 TEST_CASE(test_write_packets), 449 TEST_CASES_END() 450 } 451 }; 452 453 static int 454 test_pcapng(void) 455 { 456 return unit_test_suite_runner(&test_pcapng_suite); 457 } 458 459 REGISTER_FAST_TEST(pcapng_autotest, true, true, test_pcapng); 460