1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright(c) 2019-2020 Microsoft Corporation
4 * DPDK application to dump network traffic
5 * This is designed to look and act like the Wireshark
20 #include <sys/queue.h>
21 #include <sys/types.h>
22 #include <sys/utsname.h>
26 #include <rte_alarm.h>
28 #include <rte_config.h>
29 #include <rte_debug.h>
31 #include <rte_errno.h>
32 #include <rte_ethdev.h>
33 #include <rte_lcore.h>
34 #include <rte_malloc.h>
36 #include <rte_mempool.h>
37 #include <rte_pcapng.h>
38 #include <rte_pdump.h>
40 #include <rte_string_fns.h>
42 #include <rte_version.h>
44 #include <pcap/pcap.h>
47 #define RING_NAME "capture-ring"
48 #define MONITOR_INTERVAL (500 * 1000)
49 #define MBUF_POOL_CACHE_SIZE 32
51 #define SLEEP_THRESHOLD 1000
53 /* command line flags */
54 static const char *progname;
55 static bool quit_signal;
56 static bool group_read;
58 static bool promiscuous_mode = true;
59 static bool use_pcapng = true;
60 static char *output_name;
61 static const char *filter_str;
62 static unsigned int ring_size = 2048;
63 static const char *capture_comment;
64 static uint32_t snaplen = RTE_MBUF_DEFAULT_BUF_SIZE;
67 uint64_t duration; /* nanoseconds */
68 unsigned long packets; /* number of packets in file */
69 size_t size; /* file size (bytes) */
73 static struct rte_bpf_prm *bpf_prm;
74 static uint64_t start_time, end_time;
75 static uint64_t packets_received;
76 static size_t file_size;
79 TAILQ_ENTRY(interface) next;
81 char name[RTE_ETH_NAME_MAX_LEN];
83 struct rte_rxtx_callback *rx_cb[RTE_MAX_QUEUES_PER_PORT];
86 TAILQ_HEAD(interface_list, interface);
87 static struct interface_list interfaces = TAILQ_HEAD_INITIALIZER(interfaces);
88 static struct interface *port2intf[RTE_MAX_ETHPORTS];
90 /* Can do either pcap or pcapng format output */
93 pcap_dumper_t *dumper;
96 static void usage(void)
98 printf("Usage: %s [options] ...\n\n", progname);
99 printf("Capture Interface:\n"
100 " -i <interface> name or port index of interface\n"
101 " -f <capture filter> packet filter in libpcap filter syntax\n");
102 printf(" -s <snaplen>, --snapshot-length <snaplen>\n"
103 " packet snapshot length (def: %u)\n",
104 RTE_MBUF_DEFAULT_BUF_SIZE);
105 printf(" -p, --no-promiscuous-mode\n"
106 " don't capture in promiscuous mode\n"
107 " -D, --list-interfaces print list of interfaces and exit\n"
108 " -d print generated BPF code for capture filter\n"
111 " -c <packet count> stop after n packets (def: infinite)\n"
112 " -a <autostop cond.> ..., --autostop <autostop cond.> ...\n"
113 " duration:NUM - stop after NUM seconds\n"
114 " filesize:NUM - stop this file after NUM kB\n"
115 " packets:NUM - stop after NUM packets\n"
117 " -w <filename> name of file to save (def: tempfile)\n"
118 " -g enable group read access on the output file(s)\n"
119 " -n use pcapng format instead of pcap (default)\n"
120 " -P use libpcap format instead of pcapng\n"
121 " --capture-comment <comment>\n"
122 " add a capture comment to the output file\n"
125 " -q don't report packet capture counts\n"
126 " -v, --version print version information and exit\n"
127 " -h, --help display this help and exit\n"
129 "Use Ctrl-C to stop capturing at any time.\n");
132 static const char *version(void)
134 static char str[128];
136 snprintf(str, sizeof(str),
137 "%s 1.0 (%s)\n", progname, rte_version());
141 /* Parse numeric argument from command line */
142 static unsigned long get_uint(const char *arg, const char *name,
148 u = strtoul(arg, &endp, 0);
149 if (*arg == '\0' || *endp != '\0')
150 rte_exit(EXIT_FAILURE,
151 "Specified %s \"%s\" is not a valid number\n",
153 if (limit && u > limit)
154 rte_exit(EXIT_FAILURE,
155 "Specified %s \"%s\" is too large (greater than %u)\n",
161 /* Set auto stop values */
162 static void auto_stop(char *opt)
166 value = strchr(opt, ':');
168 rte_exit(EXIT_FAILURE,
169 "Missing colon in auto stop parameter\n");
172 if (strcmp(opt, "duration") == 0) {
173 double interval = strtod(value, &endp);
175 if (*value == '\0' || *endp != '\0' || interval <= 0)
176 rte_exit(EXIT_FAILURE,
177 "Invalid duration \"%s\"\n", value);
178 stop.duration = NSEC_PER_SEC * interval;
179 } else if (strcmp(opt, "filesize") == 0) {
180 stop.size = get_uint(value, "filesize", 0) * 1024;
181 } else if (strcmp(opt, "packets") == 0) {
182 stop.packets = get_uint(value, "packets", 0);
184 rte_exit(EXIT_FAILURE,
185 "Unknown autostop parameter \"%s\"\n", opt);
189 /* Add interface to list of interfaces to capture */
190 static void add_interface(uint16_t port, const char *name)
192 struct interface *intf;
194 intf = malloc(sizeof(*intf));
196 rte_exit(EXIT_FAILURE, "no memory for interface\n");
198 memset(intf, 0, sizeof(*intf));
199 rte_strscpy(intf->name, name, sizeof(intf->name));
201 printf("Capturing on '%s'\n", name);
203 port2intf[port] = intf;
204 TAILQ_INSERT_TAIL(&interfaces, intf, next);
207 /* Select all valid DPDK interfaces */
208 static void select_all_interfaces(void)
210 char name[RTE_ETH_NAME_MAX_LEN];
213 RTE_ETH_FOREACH_DEV(p) {
214 if (rte_eth_dev_get_name_by_port(p, name) < 0)
216 add_interface(p, name);
221 * Choose interface to capture if no -i option given.
222 * Select the first DPDK port, this matches what dumpcap does.
224 static void set_default_interface(void)
226 char name[RTE_ETH_NAME_MAX_LEN];
229 RTE_ETH_FOREACH_DEV(p) {
230 if (rte_eth_dev_get_name_by_port(p, name) < 0)
232 add_interface(p, name);
235 rte_exit(EXIT_FAILURE, "No usable interfaces found\n");
238 /* Lookup interface by name or port and add it to the list */
239 static void select_interface(const char *arg)
243 if (strcmp(arg, "*"))
244 select_all_interfaces();
245 else if (rte_eth_dev_get_port_by_name(arg, &port) == 0)
246 add_interface(port, arg);
248 char name[RTE_ETH_NAME_MAX_LEN];
250 port = get_uint(arg, "port_number", UINT16_MAX);
251 if (rte_eth_dev_get_name_by_port(port, name) < 0)
252 rte_exit(EXIT_FAILURE, "Invalid port number %u\n",
254 add_interface(port, name);
258 /* Display list of possible interfaces that can be used. */
259 static void show_interfaces(void)
261 char name[RTE_ETH_NAME_MAX_LEN];
264 RTE_ETH_FOREACH_DEV(p) {
265 if (rte_eth_dev_get_name_by_port(p, name) < 0)
267 printf("%u. %s\n", p, name);
271 static void compile_filter(void)
273 struct bpf_program bf;
276 pcap = pcap_open_dead(DLT_EN10MB, snaplen);
278 rte_exit(EXIT_FAILURE, "can not open pcap\n");
280 if (pcap_compile(pcap, &bf, filter_str,
281 1, PCAP_NETMASK_UNKNOWN) != 0)
282 rte_exit(EXIT_FAILURE, "pcap filter string not valid (%s)\n",
285 bpf_prm = rte_bpf_convert(&bf);
287 rte_exit(EXIT_FAILURE,
288 "bpf convert failed: %s(%d)\n",
289 rte_strerror(rte_errno), rte_errno);
292 printf("cBPF program (%u insns)\n", bf.bf_len);
294 printf("\neBPF program (%u insns)\n", bpf_prm->nb_ins);
295 rte_bpf_dump(stdout, bpf_prm->ins, bpf_prm->nb_ins);
299 /* Don't care about original program any more */
305 * Parse command line options.
306 * These are chosen to be similar to dumpcap command.
308 static void parse_opts(int argc, char **argv)
310 static const struct option long_options[] = {
311 { "autostop", required_argument, NULL, 'a' },
312 { "capture-comment", required_argument, NULL, 0 },
313 { "help", no_argument, NULL, 'h' },
314 { "interface", required_argument, NULL, 'i' },
315 { "list-interfaces", no_argument, NULL, 'D' },
316 { "no-promiscuous-mode", no_argument, NULL, 'p' },
317 { "output-file", required_argument, NULL, 'w' },
318 { "ring-buffer", required_argument, NULL, 'b' },
319 { "snapshot-length", required_argument, NULL, 's' },
320 { "version", no_argument, NULL, 'v' },
326 c = getopt_long(argc, argv, "a:b:c:dDf:ghi:nN:pPqs:vw:",
327 long_options, &option_index);
333 switch (option_index) {
335 capture_comment = optarg;
346 rte_exit(EXIT_FAILURE,
347 "multiple files not implemented\n");
350 stop.packets = get_uint(optarg, "packet_count", 0);
365 printf("%s\n\n", version());
369 select_interface(optarg);
375 ring_size = get_uint(optarg, "packet_limit", 0);
378 promiscuous_mode = false;
387 snaplen = get_uint(optarg, "snap_len", 0);
390 output_name = optarg;
393 printf("%s\n", version());
396 fprintf(stderr, "Invalid option: %s\n",
405 signal_handler(int sig_num __rte_unused)
407 __atomic_store_n(&quit_signal, true, __ATOMIC_RELAXED);
410 /* Return the time since 1/1/1970 in nanoseconds */
411 static uint64_t create_timestamp(void)
415 clock_gettime(CLOCK_MONOTONIC, &now);
416 return rte_timespec_to_ns(&now);
420 cleanup_pdump_resources(void)
422 struct interface *intf;
424 TAILQ_FOREACH(intf, &interfaces, next) {
425 rte_pdump_disable(intf->port,
426 RTE_PDUMP_ALL_QUEUES, RTE_PDUMP_FLAG_RXTX);
427 if (promiscuous_mode)
428 rte_eth_promiscuous_disable(intf->port);
432 /* Alarm signal handler, used to check that primary process */
434 monitor_primary(void *arg __rte_unused)
436 if (__atomic_load_n(&quit_signal, __ATOMIC_RELAXED))
439 if (rte_eal_primary_proc_alive(NULL)) {
440 rte_eal_alarm_set(MONITOR_INTERVAL, monitor_primary, NULL);
443 "Primary process is no longer active, exiting...\n");
444 __atomic_store_n(&quit_signal, true, __ATOMIC_RELAXED);
448 /* Setup handler to check when primary exits. */
450 enable_primary_monitor(void)
454 /* Once primary exits, so will pdump. */
455 ret = rte_eal_alarm_set(MONITOR_INTERVAL, monitor_primary, NULL);
457 fprintf(stderr, "Fail to enable monitor:%d\n", ret);
461 disable_primary_monitor(void)
465 ret = rte_eal_alarm_cancel(monitor_primary, NULL);
467 fprintf(stderr, "Fail to disable monitor:%d\n", ret);
471 report_packet_stats(dumpcap_out_t out)
473 struct rte_pdump_stats pdump_stats;
474 struct interface *intf;
475 uint64_t ifrecv, ifdrop;
479 TAILQ_FOREACH(intf, &interfaces, next) {
480 if (rte_pdump_stats(intf->port, &pdump_stats) < 0)
483 /* do what Wiretap does */
484 ifrecv = pdump_stats.accepted + pdump_stats.filtered;
485 ifdrop = pdump_stats.nombuf + pdump_stats.ringfull;
488 rte_pcapng_write_stats(out.pcapng, intf->port, NULL,
489 start_time, end_time,
495 percent = 100. * ifrecv / (ifrecv + ifdrop);
498 "Packets received/dropped on interface '%s': "
499 "%"PRIu64 "/%" PRIu64 " (%.1f)\n",
500 intf->name, ifrecv, ifdrop, percent);
505 * Start DPDK EAL with arguments.
506 * Unlike most DPDK programs, this application does not use the
507 * typical EAL command line arguments.
508 * We don't want to expose all the DPDK internals to the user.
510 static void dpdk_init(void)
512 static const char * const args[] = {
513 "dumpcap", "--proc-type", "secondary",
514 "--log-level", "notice"
517 const int eal_argc = RTE_DIM(args);
521 /* DPDK API requires mutable versions of command line arguments. */
522 eal_argv = calloc(eal_argc + 1, sizeof(char *));
523 if (eal_argv == NULL)
524 rte_panic("No memory\n");
526 eal_argv[0] = strdup(progname);
527 for (i = 1; i < RTE_DIM(args); i++)
528 eal_argv[i] = strdup(args[i]);
530 if (rte_eal_init(eal_argc, eal_argv) < 0)
531 rte_exit(EXIT_FAILURE, "EAL init failed: is primary process running?\n");
533 if (rte_eth_dev_count_avail() == 0)
534 rte_exit(EXIT_FAILURE, "No Ethernet ports found\n");
537 /* Create packet ring shared between callbacks and process */
538 static struct rte_ring *create_ring(void)
540 struct rte_ring *ring;
543 /* Find next power of 2 >= size. */
545 log2 = sizeof(size) * 8 - __builtin_clzl(size - 1);
548 if (size != ring_size) {
549 fprintf(stderr, "Ring size %u rounded up to %zu\n",
554 ring = rte_ring_lookup(RING_NAME);
556 ring = rte_ring_create(RING_NAME, ring_size,
559 rte_exit(EXIT_FAILURE, "Could not create ring :%s\n",
560 rte_strerror(rte_errno));
565 static struct rte_mempool *create_mempool(void)
567 static const char pool_name[] = "capture_mbufs";
568 size_t num_mbufs = 2 * ring_size;
569 struct rte_mempool *mp;
571 mp = rte_mempool_lookup(pool_name);
575 mp = rte_pktmbuf_pool_create_by_ops(pool_name, num_mbufs,
576 MBUF_POOL_CACHE_SIZE, 0,
577 rte_pcapng_mbuf_size(snaplen),
578 rte_socket_id(), "ring_mp_sc");
580 rte_exit(EXIT_FAILURE,
581 "Mempool (%s) creation failed: %s\n", pool_name,
582 rte_strerror(rte_errno));
588 * Get Operating System information.
589 * Returns an string allocated via malloc().
591 static char *get_os_info(void)
599 if (asprintf(&osname, "%s %s",
600 uts.sysname, uts.release) == -1)
606 static dumpcap_out_t create_output(void)
609 static char tmp_path[PATH_MAX];
612 /* If no filename specified make a tempfile name */
613 if (output_name == NULL) {
614 struct interface *intf;
619 intf = TAILQ_FIRST(&interfaces);
621 tm = localtime(&now);
623 rte_panic("localtime failed\n");
625 strftime(ts, sizeof(ts), "%Y%m%d%H%M%S", tm);
627 snprintf(tmp_path, sizeof(tmp_path),
628 "/tmp/%s_%u_%s_%s.%s",
629 progname, intf->port, intf->name, ts,
630 use_pcapng ? "pcapng" : "pcap");
631 output_name = tmp_path;
634 if (strcmp(output_name, "-") == 0)
637 mode_t mode = group_read ? 0640 : 0600;
639 fd = open(output_name, O_WRONLY | O_CREAT, mode);
641 rte_exit(EXIT_FAILURE, "Can not open \"%s\": %s\n",
642 output_name, strerror(errno));
646 char *os = get_os_info();
648 ret.pcapng = rte_pcapng_fdopen(fd, os, NULL,
649 version(), capture_comment);
650 if (ret.pcapng == NULL)
651 rte_exit(EXIT_FAILURE, "pcapng_fdopen failed: %s\n",
652 strerror(rte_errno));
657 pcap = pcap_open_dead_with_tstamp_precision(DLT_EN10MB, snaplen,
658 PCAP_TSTAMP_PRECISION_NANO);
660 rte_exit(EXIT_FAILURE, "pcap_open_dead failed\n");
662 ret.dumper = pcap_dump_fopen(pcap, fdopen(fd, "w"));
663 if (ret.dumper == NULL)
664 rte_exit(EXIT_FAILURE, "pcap_dump_fopen failed: %s\n",
671 static void enable_pdump(struct rte_ring *r, struct rte_mempool *mp)
673 struct interface *intf;
677 flags = RTE_PDUMP_FLAG_RXTX;
679 flags |= RTE_PDUMP_FLAG_PCAPNG;
681 TAILQ_FOREACH(intf, &interfaces, next) {
682 if (promiscuous_mode)
683 rte_eth_promiscuous_enable(intf->port);
685 ret = rte_pdump_enable_bpf(intf->port, RTE_PDUMP_ALL_QUEUES,
689 rte_exit(EXIT_FAILURE,
690 "Packet dump enable failed: %s\n",
696 * Show current count of captured packets
697 * with backspaces to overwrite last value.
699 static void show_count(uint64_t count)
702 static unsigned int bt;
704 for (i = 0; i < bt; i++)
707 bt = fprintf(stderr, "%"PRIu64" ", count);
710 /* Write multiple packets in older pcap format */
712 pcap_write_packets(pcap_dumper_t *dumper,
713 struct rte_mbuf *pkts[], uint16_t n)
715 uint8_t temp_data[snaplen];
716 struct pcap_pkthdr header;
720 gettimeofday(&header.ts, NULL);
722 for (i = 0; i < n; i++) {
723 struct rte_mbuf *m = pkts[i];
725 header.len = rte_pktmbuf_pkt_len(m);
726 header.caplen = RTE_MIN(header.len, snaplen);
728 pcap_dump((u_char *)dumper, &header,
729 rte_pktmbuf_read(m, 0, header.caplen, temp_data));
731 total += sizeof(header) + header.len;
737 /* Process all packets in ring and dump to capture file */
738 static int process_ring(dumpcap_out_t out, struct rte_ring *r)
740 struct rte_mbuf *pkts[BURST_SIZE];
741 unsigned int avail, n;
742 static unsigned int empty_count;
745 n = rte_ring_sc_dequeue_burst(r, (void **) pkts, BURST_SIZE,
748 /* don't consume endless amounts of cpu if idle */
749 if (empty_count < SLEEP_THRESHOLD)
756 empty_count = (avail == 0);
759 written = rte_pcapng_write_packets(out.pcapng, pkts, n);
761 written = pcap_write_packets(out.dumper, pkts, n);
763 rte_pktmbuf_free_bulk(pkts, n);
768 file_size += written;
769 packets_received += n;
771 show_count(packets_received);
776 int main(int argc, char **argv)
779 struct rte_mempool *mp;
785 parse_opts(argc, argv);
790 if (TAILQ_EMPTY(&interfaces))
791 set_default_interface();
794 mp = create_mempool();
795 out = create_output();
797 start_time = create_timestamp();
800 signal(SIGINT, signal_handler);
801 signal(SIGPIPE, SIG_IGN);
803 enable_primary_monitor();
806 fprintf(stderr, "Packets captured: ");
810 while (!__atomic_load_n(&quit_signal, __ATOMIC_RELAXED)) {
811 if (process_ring(out, r) < 0) {
812 fprintf(stderr, "pcapng file write failed; %s\n",
817 if (stop.size && file_size >= stop.size)
820 if (stop.packets && packets_received >= stop.packets)
823 if (stop.duration != 0 &&
824 create_timestamp() - start_time > stop.duration)
828 end_time = create_timestamp();
829 disable_primary_monitor();
831 if (rte_eal_primary_proc_alive(NULL))
832 report_packet_stats(out);
835 rte_pcapng_close(out.pcapng);
837 pcap_dump_close(out.dumper);
839 cleanup_pdump_resources();
840 rte_free(bpf_filter);
842 rte_mempool_free(mp);
844 return rte_eal_cleanup() ? EXIT_FAILURE : 0;