app/dumpcap: add new packet capture application

This is a new packet capture application to replace existing pdump.
The new application works like Wireshark dumpcap program and supports
the pdump API features.

It is not complete yet some features such as filtering are not implemented.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
This commit is contained in:
Stephen Hemminger 2021-10-20 14:42:31 -07:00 committed by Thomas Monjalon
parent 10f726efe2
commit cbb44143be
10 changed files with 1045 additions and 77 deletions

View File

@ -1440,6 +1440,8 @@ F: doc/guides/prog_guide/pcapng_lib.rst
F: app/test/test_pcapng.c
F: app/pdump/
F: doc/guides/tools/pdump.rst
F: app/dumpcap/
F: doc/guides/tools/dumpcap.rst
Packet Framework

844
app/dumpcap/main.c Normal file
View File

@ -0,0 +1,844 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2019-2020 Microsoft Corporation
*
* DPDK application to dump network traffic
* This is designed to look and act like the Wireshark
* dumpcap program.
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
#include <rte_alarm.h>
#include <rte_bpf.h>
#include <rte_config.h>
#include <rte_debug.h>
#include <rte_eal.h>
#include <rte_errno.h>
#include <rte_ethdev.h>
#include <rte_lcore.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
#include <rte_mempool.h>
#include <rte_pcapng.h>
#include <rte_pdump.h>
#include <rte_ring.h>
#include <rte_string_fns.h>
#include <rte_time.h>
#include <rte_version.h>
#include <pcap/pcap.h>
#include <pcap/bpf.h>
#define RING_NAME "capture-ring"
#define MONITOR_INTERVAL (500 * 1000)
#define MBUF_POOL_CACHE_SIZE 32
#define BURST_SIZE 32
#define SLEEP_THRESHOLD 1000
/* command line flags */
static const char *progname;
static bool quit_signal;
static bool group_read;
static bool quiet;
static bool promiscuous_mode = true;
static bool use_pcapng = true;
static char *output_name;
static const char *filter_str;
static unsigned int ring_size = 2048;
static const char *capture_comment;
static uint32_t snaplen = RTE_MBUF_DEFAULT_BUF_SIZE;
static bool dump_bpf;
static struct {
uint64_t duration; /* nanoseconds */
unsigned long packets; /* number of packets in file */
size_t size; /* file size (bytes) */
} stop;
/* Running state */
static struct rte_bpf_prm *bpf_prm;
static uint64_t start_time, end_time;
static uint64_t packets_received;
static size_t file_size;
struct interface {
TAILQ_ENTRY(interface) next;
uint16_t port;
char name[RTE_ETH_NAME_MAX_LEN];
struct rte_rxtx_callback *rx_cb[RTE_MAX_QUEUES_PER_PORT];
};
TAILQ_HEAD(interface_list, interface);
static struct interface_list interfaces = TAILQ_HEAD_INITIALIZER(interfaces);
static struct interface *port2intf[RTE_MAX_ETHPORTS];
/* Can do either pcap or pcapng format output */
typedef union {
rte_pcapng_t *pcapng;
pcap_dumper_t *dumper;
} dumpcap_out_t;
static void usage(void)
{
printf("Usage: %s [options] ...\n\n", progname);
printf("Capture Interface:\n"
" -i <interface> name or port index of interface\n"
" -f <capture filter> packet filter in libpcap filter syntax\n");
printf(" -s <snaplen>, --snapshot-length <snaplen>\n"
" packet snapshot length (def: %u)\n",
RTE_MBUF_DEFAULT_BUF_SIZE);
printf(" -p, --no-promiscuous-mode\n"
" don't capture in promiscuous mode\n"
" -D, --list-interfaces print list of interfaces and exit\n"
" -d print generated BPF code for capture filter\n"
"\n"
"Stop conditions:\n"
" -c <packet count> stop after n packets (def: infinite)\n"
" -a <autostop cond.> ..., --autostop <autostop cond.> ...\n"
" duration:NUM - stop after NUM seconds\n"
" filesize:NUM - stop this file after NUM kB\n"
" packets:NUM - stop after NUM packets\n"
"Output (files):\n"
" -w <filename> name of file to save (def: tempfile)\n"
" -g enable group read access on the output file(s)\n"
" -n use pcapng format instead of pcap (default)\n"
" -P use libpcap format instead of pcapng\n"
" --capture-comment <comment>\n"
" add a capture comment to the output file\n"
"\n"
"Miscellaneous:\n"
" -q don't report packet capture counts\n"
" -v, --version print version information and exit\n"
" -h, --help display this help and exit\n"
"\n"
"Use Ctrl-C to stop capturing at any time.\n");
}
static const char *version(void)
{
static char str[128];
snprintf(str, sizeof(str),
"%s 1.0 (%s)\n", progname, rte_version());
return str;
}
/* Parse numeric argument from command line */
static unsigned long get_uint(const char *arg, const char *name,
unsigned int limit)
{
unsigned long u;
char *endp;
u = strtoul(arg, &endp, 0);
if (*arg == '\0' || *endp != '\0')
rte_exit(EXIT_FAILURE,
"Specified %s \"%s\" is not a valid number\n",
name, arg);
if (limit && u > limit)
rte_exit(EXIT_FAILURE,
"Specified %s \"%s\" is too large (greater than %u)\n",
name, arg, limit);
return u;
}
/* Set auto stop values */
static void auto_stop(char *opt)
{
char *value, *endp;
value = strchr(opt, ':');
if (value == NULL)
rte_exit(EXIT_FAILURE,
"Missing colon in auto stop parameter\n");
*value++ = '\0';
if (strcmp(opt, "duration") == 0) {
double interval = strtod(value, &endp);
if (*value == '\0' || *endp != '\0' || interval <= 0)
rte_exit(EXIT_FAILURE,
"Invalid duration \"%s\"\n", value);
stop.duration = NSEC_PER_SEC * interval;
} else if (strcmp(opt, "filesize") == 0) {
stop.size = get_uint(value, "filesize", 0) * 1024;
} else if (strcmp(opt, "packets") == 0) {
stop.packets = get_uint(value, "packets", 0);
} else {
rte_exit(EXIT_FAILURE,
"Unknown autostop parameter \"%s\"\n", opt);
}
}
/* Add interface to list of interfaces to capture */
static void add_interface(uint16_t port, const char *name)
{
struct interface *intf;
intf = malloc(sizeof(*intf));
if (!intf)
rte_exit(EXIT_FAILURE, "no memory for interface\n");
memset(intf, 0, sizeof(*intf));
rte_strscpy(intf->name, name, sizeof(intf->name));
printf("Capturing on '%s'\n", name);
port2intf[port] = intf;
TAILQ_INSERT_TAIL(&interfaces, intf, next);
}
/* Select all valid DPDK interfaces */
static void select_all_interfaces(void)
{
char name[RTE_ETH_NAME_MAX_LEN];
uint16_t p;
RTE_ETH_FOREACH_DEV(p) {
if (rte_eth_dev_get_name_by_port(p, name) < 0)
continue;
add_interface(p, name);
}
}
/*
* Choose interface to capture if no -i option given.
* Select the first DPDK port, this matches what dumpcap does.
*/
static void set_default_interface(void)
{
char name[RTE_ETH_NAME_MAX_LEN];
uint16_t p;
RTE_ETH_FOREACH_DEV(p) {
if (rte_eth_dev_get_name_by_port(p, name) < 0)
continue;
add_interface(p, name);
return;
}
rte_exit(EXIT_FAILURE, "No usable interfaces found\n");
}
/* Lookup interface by name or port and add it to the list */
static void select_interface(const char *arg)
{
uint16_t port;
if (strcmp(arg, "*"))
select_all_interfaces();
else if (rte_eth_dev_get_port_by_name(arg, &port) == 0)
add_interface(port, arg);
else {
char name[RTE_ETH_NAME_MAX_LEN];
port = get_uint(arg, "port_number", UINT16_MAX);
if (rte_eth_dev_get_name_by_port(port, name) < 0)
rte_exit(EXIT_FAILURE, "Invalid port number %u\n",
port);
add_interface(port, name);
}
}
/* Display list of possible interfaces that can be used. */
static void show_interfaces(void)
{
char name[RTE_ETH_NAME_MAX_LEN];
uint16_t p;
RTE_ETH_FOREACH_DEV(p) {
if (rte_eth_dev_get_name_by_port(p, name) < 0)
continue;
printf("%u. %s\n", p, name);
}
}
static void compile_filter(void)
{
struct bpf_program bf;
pcap_t *pcap;
pcap = pcap_open_dead(DLT_EN10MB, snaplen);
if (!pcap)
rte_exit(EXIT_FAILURE, "can not open pcap\n");
if (pcap_compile(pcap, &bf, filter_str,
1, PCAP_NETMASK_UNKNOWN) != 0)
rte_exit(EXIT_FAILURE, "pcap filter string not valid (%s)\n",
pcap_geterr(pcap));
bpf_prm = rte_bpf_convert(&bf);
if (bpf_prm == NULL)
rte_exit(EXIT_FAILURE,
"bpf convert failed\n");
if (dump_bpf) {
printf("cBPF program (%u insns)\n", bf.bf_len);
bpf_dump(&bf, 1);
printf("\neBPF program (%u insns)\n", bpf_prm->nb_ins);
rte_bpf_dump(stdout, bpf_prm->ins, bpf_prm->nb_ins);
exit(0);
}
/* Don't care about original program any more */
pcap_freecode(&bf);
pcap_close(pcap);
}
/*
* Parse command line options.
* These are chosen to be similar to dumpcap command.
*/
static void parse_opts(int argc, char **argv)
{
static const struct option long_options[] = {
{ "autostop", required_argument, NULL, 'a' },
{ "capture-comment", required_argument, NULL, 0 },
{ "help", no_argument, NULL, 'h' },
{ "interface", required_argument, NULL, 'i' },
{ "list-interfaces", no_argument, NULL, 'D' },
{ "no-promiscuous-mode", no_argument, NULL, 'p' },
{ "output-file", required_argument, NULL, 'w' },
{ "ring-buffer", required_argument, NULL, 'b' },
{ "snapshot-length", required_argument, NULL, 's' },
{ "version", no_argument, NULL, 'v' },
{ NULL },
};
int option_index, c;
for (;;) {
c = getopt_long(argc, argv, "a:b:c:dDf:ghi:nN:pPqs:vw:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
switch (option_index) {
case 0:
capture_comment = optarg;
break;
default:
usage();
exit(1);
}
break;
case 'a':
auto_stop(optarg);
break;
case 'b':
rte_exit(EXIT_FAILURE,
"multiple files not implemented\n");
break;
case 'c':
stop.packets = get_uint(optarg, "packet_count", 0);
break;
case 'd':
dump_bpf = true;
break;
case 'D':
show_interfaces();
exit(0);
case 'f':
filter_str = optarg;
break;
case 'g':
group_read = true;
break;
case 'h':
printf("%s\n\n", version());
usage();
exit(0);
case 'i':
select_interface(optarg);
break;
case 'n':
use_pcapng = true;
break;
case 'N':
ring_size = get_uint(optarg, "packet_limit", 0);
break;
case 'p':
promiscuous_mode = false;
break;
case 'P':
use_pcapng = false;
break;
case 'q':
quiet = true;
break;
case 's':
snaplen = get_uint(optarg, "snap_len", 0);
break;
case 'w':
output_name = optarg;
break;
case 'v':
printf("%s\n", version());
exit(0);
default:
fprintf(stderr, "Invalid option: %s\n",
argv[optind - 1]);
usage();
exit(1);
}
}
}
static void
signal_handler(int sig_num __rte_unused)
{
__atomic_store_n(&quit_signal, true, __ATOMIC_RELAXED);
}
/* Return the time since 1/1/1970 in nanoseconds */
static uint64_t create_timestamp(void)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return rte_timespec_to_ns(&now);
}
static void
cleanup_pdump_resources(void)
{
struct interface *intf;
TAILQ_FOREACH(intf, &interfaces, next) {
rte_pdump_disable(intf->port,
RTE_PDUMP_ALL_QUEUES, RTE_PDUMP_FLAG_RXTX);
if (promiscuous_mode)
rte_eth_promiscuous_disable(intf->port);
}
}
/* Alarm signal handler, used to check that primary process */
static void
monitor_primary(void *arg __rte_unused)
{
if (__atomic_load_n(&quit_signal, __ATOMIC_RELAXED))
return;
if (rte_eal_primary_proc_alive(NULL)) {
rte_eal_alarm_set(MONITOR_INTERVAL, monitor_primary, NULL);
} else {
fprintf(stderr,
"Primary process is no longer active, exiting...\n");
__atomic_store_n(&quit_signal, true, __ATOMIC_RELAXED);
}
}
/* Setup handler to check when primary exits. */
static void
enable_primary_monitor(void)
{
int ret;
/* Once primary exits, so will pdump. */
ret = rte_eal_alarm_set(MONITOR_INTERVAL, monitor_primary, NULL);
if (ret < 0)
fprintf(stderr, "Fail to enable monitor:%d\n", ret);
}
static void
disable_primary_monitor(void)
{
int ret;
ret = rte_eal_alarm_cancel(monitor_primary, NULL);
if (ret < 0)
fprintf(stderr, "Fail to disable monitor:%d\n", ret);
}
static void
report_packet_stats(dumpcap_out_t out)
{
struct rte_pdump_stats pdump_stats;
struct interface *intf;
uint64_t ifrecv, ifdrop;
double percent;
fputc('\n', stderr);
TAILQ_FOREACH(intf, &interfaces, next) {
if (rte_pdump_stats(intf->port, &pdump_stats) < 0)
continue;
/* do what Wiretap does */
ifrecv = pdump_stats.accepted + pdump_stats.filtered;
ifdrop = pdump_stats.nombuf + pdump_stats.ringfull;
if (use_pcapng)
rte_pcapng_write_stats(out.pcapng, intf->port, NULL,
start_time, end_time,
ifrecv, ifdrop);
if (ifrecv == 0)
percent = 0;
else
percent = 100. * ifrecv / (ifrecv + ifdrop);
fprintf(stderr,
"Packets received/dropped on interface '%s': "
"%"PRIu64 "/%" PRIu64 " (%.1f)\n",
intf->name, ifrecv, ifdrop, percent);
}
}
/*
* Start DPDK EAL with arguments.
* Unlike most DPDK programs, this application does not use the
* typical EAL command line arguments.
* We don't want to expose all the DPDK internals to the user.
*/
static void dpdk_init(void)
{
static const char * const args[] = {
"dumpcap", "--proc-type", "secondary",
"--log-level", "notice"
};
const int eal_argc = RTE_DIM(args);
char **eal_argv;
unsigned int i;
/* DPDK API requires mutable versions of command line arguments. */
eal_argv = calloc(eal_argc + 1, sizeof(char *));
if (eal_argv == NULL)
rte_panic("No memory\n");
eal_argv[0] = strdup(progname);
for (i = 1; i < RTE_DIM(args); i++)
eal_argv[i] = strdup(args[i]);
if (rte_eal_init(eal_argc, eal_argv) < 0)
rte_exit(EXIT_FAILURE, "EAL init failed: is primary process running?\n");
if (rte_eth_dev_count_avail() == 0)
rte_exit(EXIT_FAILURE, "No Ethernet ports found\n");
}
/* Create packet ring shared between callbacks and process */
static struct rte_ring *create_ring(void)
{
struct rte_ring *ring;
size_t size, log2;
/* Find next power of 2 >= size. */
size = ring_size;
log2 = sizeof(size) * 8 - __builtin_clzl(size - 1);
size = 1u << log2;
if (size != ring_size) {
fprintf(stderr, "Ring size %u rounded up to %zu\n",
ring_size, size);
ring_size = size;
}
ring = rte_ring_lookup(RING_NAME);
if (ring == NULL) {
ring = rte_ring_create(RING_NAME, ring_size,
rte_socket_id(), 0);
if (ring == NULL)
rte_exit(EXIT_FAILURE, "Could not create ring :%s\n",
rte_strerror(rte_errno));
}
return ring;
}
static struct rte_mempool *create_mempool(void)
{
static const char pool_name[] = "capture_mbufs";
size_t num_mbufs = 2 * ring_size;
struct rte_mempool *mp;
mp = rte_mempool_lookup(pool_name);
if (mp)
return mp;
mp = rte_pktmbuf_pool_create_by_ops(pool_name, num_mbufs,
MBUF_POOL_CACHE_SIZE, 0,
rte_pcapng_mbuf_size(snaplen),
rte_socket_id(), "ring_mp_sc");
if (mp == NULL)
rte_exit(EXIT_FAILURE,
"Mempool (%s) creation failed: %s\n", pool_name,
rte_strerror(rte_errno));
return mp;
}
/*
* Get Operating System information.
* Returns an string allocated via malloc().
*/
static char *get_os_info(void)
{
struct utsname uts;
char *osname = NULL;
if (uname(&uts) < 0)
return NULL;
if (asprintf(&osname, "%s %s",
uts.sysname, uts.release) == -1)
return NULL;
return osname;
}
static dumpcap_out_t create_output(void)
{
dumpcap_out_t ret;
static char tmp_path[PATH_MAX];
int fd;
/* If no filename specified make a tempfile name */
if (output_name == NULL) {
struct interface *intf;
struct tm *tm;
time_t now;
char ts[32];
intf = TAILQ_FIRST(&interfaces);
now = time(NULL);
tm = localtime(&now);
if (!tm)
rte_panic("localtime failed\n");
strftime(ts, sizeof(ts), "%Y%m%d%H%M%S", tm);
snprintf(tmp_path, sizeof(tmp_path),
"/tmp/%s_%u_%s_%s.%s",
progname, intf->port, intf->name, ts,
use_pcapng ? "pcapng" : "pcap");
output_name = tmp_path;
}
if (strcmp(output_name, "-") == 0)
fd = STDOUT_FILENO;
else {
mode_t mode = group_read ? 0640 : 0600;
fd = open(output_name, O_WRONLY | O_CREAT, mode);
if (fd < 0)
rte_exit(EXIT_FAILURE, "Can not open \"%s\": %s\n",
output_name, strerror(errno));
}
if (use_pcapng) {
char *os = get_os_info();
ret.pcapng = rte_pcapng_fdopen(fd, os, NULL,
version(), capture_comment);
if (ret.pcapng == NULL)
rte_exit(EXIT_FAILURE, "pcapng_fdopen failed: %s\n",
strerror(rte_errno));
free(os);
} else {
pcap_t *pcap;
pcap = pcap_open_dead_with_tstamp_precision(DLT_EN10MB, snaplen,
PCAP_TSTAMP_PRECISION_NANO);
if (pcap == NULL)
rte_exit(EXIT_FAILURE, "pcap_open_dead failed\n");
ret.dumper = pcap_dump_fopen(pcap, fdopen(fd, "w"));
if (ret.dumper == NULL)
rte_exit(EXIT_FAILURE, "pcap_dump_fopen failed: %s\n",
pcap_geterr(pcap));
}
return ret;
}
static void enable_pdump(struct rte_ring *r, struct rte_mempool *mp)
{
struct interface *intf;
uint32_t flags;
int ret;
flags = RTE_PDUMP_FLAG_RXTX;
if (use_pcapng)
flags |= RTE_PDUMP_FLAG_PCAPNG;
TAILQ_FOREACH(intf, &interfaces, next) {
if (promiscuous_mode)
rte_eth_promiscuous_enable(intf->port);
ret = rte_pdump_enable_bpf(intf->port, RTE_PDUMP_ALL_QUEUES,
flags, snaplen,
r, mp, bpf_prm);
if (ret < 0)
rte_exit(EXIT_FAILURE,
"Packet dump enable failed: %s\n",
rte_strerror(-ret));
}
}
/*
* Show current count of captured packets
* with backspaces to overwrite last value.
*/
static void show_count(uint64_t count)
{
unsigned int i;
static unsigned int bt;
for (i = 0; i < bt; i++)
fputc('\b', stderr);
bt = fprintf(stderr, "%"PRIu64" ", count);
}
/* Write multiple packets in older pcap format */
static ssize_t
pcap_write_packets(pcap_dumper_t *dumper,
struct rte_mbuf *pkts[], uint16_t n)
{
uint8_t temp_data[snaplen];
struct pcap_pkthdr header;
uint16_t i;
size_t total = 0;
gettimeofday(&header.ts, NULL);
for (i = 0; i < n; i++) {
struct rte_mbuf *m = pkts[i];
header.len = rte_pktmbuf_pkt_len(m);
header.caplen = RTE_MIN(header.len, snaplen);
pcap_dump((u_char *)dumper, &header,
rte_pktmbuf_read(m, 0, header.caplen, temp_data));
total += sizeof(header) + header.len;
}
return total;
}
/* Process all packets in ring and dump to capture file */
static int process_ring(dumpcap_out_t out, struct rte_ring *r)
{
struct rte_mbuf *pkts[BURST_SIZE];
unsigned int avail, n;
static unsigned int empty_count;
ssize_t written;
n = rte_ring_sc_dequeue_burst(r, (void **) pkts, BURST_SIZE,
&avail);
if (n == 0) {
/* don't consume endless amounts of cpu if idle */
if (empty_count < SLEEP_THRESHOLD)
++empty_count;
else
usleep(10);
return 0;
}
empty_count = (avail == 0);
if (use_pcapng)
written = rte_pcapng_write_packets(out.pcapng, pkts, n);
else
written = pcap_write_packets(out.dumper, pkts, n);
rte_pktmbuf_free_bulk(pkts, n);
if (written < 0)
return -1;
file_size += written;
packets_received += n;
if (!quiet)
show_count(packets_received);
return 0;
}
int main(int argc, char **argv)
{
struct rte_ring *r;
struct rte_mempool *mp;
dumpcap_out_t out;
progname = argv[0];
dpdk_init();
parse_opts(argc, argv);
if (filter_str)
compile_filter();
if (TAILQ_EMPTY(&interfaces))
set_default_interface();
r = create_ring();
mp = create_mempool();
out = create_output();
start_time = create_timestamp();
enable_pdump(r, mp);
signal(SIGINT, signal_handler);
signal(SIGPIPE, SIG_IGN);
enable_primary_monitor();
if (!quiet) {
fprintf(stderr, "Packets captured: ");
show_count(0);
}
while (!__atomic_load_n(&quit_signal, __ATOMIC_RELAXED)) {
if (process_ring(out, r) < 0) {
fprintf(stderr, "pcapng file write failed; %s\n",
strerror(errno));
break;
}
if (stop.size && file_size >= stop.size)
break;
if (stop.packets && packets_received >= stop.packets)
break;
if (stop.duration != 0 &&
create_timestamp() - start_time > stop.duration)
break;
}
end_time = create_timestamp();
disable_primary_monitor();
if (rte_eal_primary_proc_alive(NULL))
report_packet_stats(out);
if (use_pcapng)
rte_pcapng_close(out.pcapng);
else
pcap_dump_close(out.dumper);
cleanup_pdump_resources();
rte_free(bpf_filter);
rte_ring_free(r);
rte_mempool_free(mp);
return rte_eal_cleanup() ? EXIT_FAILURE : 0;
}

16
app/dumpcap/meson.build Normal file
View File

@ -0,0 +1,16 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2019 Microsoft Corporation
if not dpdk_conf.has('RTE_PORT_PCAP')
build = false
reason = 'missing dependency, "libpcap"'
endif
if is_windows
build = false
reason = 'not supported on Windows'
subdir_done()
endif
sources = files('main.c')
deps += ['ethdev', 'pdump', 'pcapng', 'bpf']

View File

@ -2,6 +2,7 @@
# Copyright(c) 2017-2019 Intel Corporation
apps = [
'dumpcap',
'pdump',
'proc-info',
'test-acl',

View File

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
@ -16,8 +14,8 @@
viewBox="0 0 425.19685 283.46457"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="drawing-pcap.svg">
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
sodipodi:docname="packet_capture_framework.svg">
<defs
id="defs4">
<marker
@ -228,7 +226,7 @@
x2="487.64606"
y2="258.38232"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-84.916417,744.90779)" />
gradientTransform="matrix(1.1457977,0,0,0.99944907,-151.97019,745.05014)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5784"
@ -277,17 +275,18 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.57434918"
inkscape:cx="215.17857"
inkscape:cy="285.26445"
inkscape:zoom="1"
inkscape:cx="226.77165"
inkscape:cy="78.124511"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1874"
inkscape:window-height="971"
inkscape:window-x="2"
inkscape:window-y="24"
inkscape:window-maximized="0" />
inkscape:window-width="2560"
inkscape:window-height="1414"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:document-rotation="0" />
<metadata
id="metadata7">
<rdf:RDF>
@ -296,7 +295,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -321,15 +320,15 @@
y="790.82452" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="61.050636"
y="807.3205"
id="text4152"
sodipodi:linespacing="125%"><tspan
id="text4152"><tspan
sodipodi:role="line"
id="tspan4154"
x="61.050636"
y="807.3205">DPDK Primary Application</tspan></text>
y="807.3205"
style="font-size:12.5px;line-height:1.25">DPDK Primary Application</tspan></text>
<rect
style="fill:#000000;fill-opacity:0;stroke:#257cdc;stroke-width:2;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6"
@ -339,19 +338,20 @@
y="827.01843" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="350.68585"
y="841.16058"
id="text4189"
sodipodi:linespacing="125%"><tspan
id="text4189"><tspan
sodipodi:role="line"
id="tspan4191"
x="350.68585"
y="841.16058">dpdk-pdump</tspan><tspan
y="841.16058"
style="font-size:12.5px;line-height:1.25">dpdk-dumpcap</tspan><tspan
sodipodi:role="line"
x="350.68585"
y="856.78558"
id="tspan4193">tool</tspan></text>
id="tspan4193"
style="font-size:12.5px;line-height:1.25">tool</tspan></text>
<rect
style="fill:#000000;fill-opacity:0;stroke:#257cdc;stroke-width:2;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6-4"
@ -361,15 +361,15 @@
y="891.16315" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="352.70612"
y="905.3053"
id="text4189-1"
sodipodi:linespacing="125%"><tspan
id="text4189-1"><tspan
sodipodi:role="line"
x="352.70612"
y="905.3053"
id="tspan4193-3">PCAP PMD</tspan></text>
id="tspan4193-3"
style="font-size:12.5px;line-height:1.25">librte_pcapng</tspan></text>
<rect
style="fill:url(#linearGradient5745);fill-opacity:1;stroke:#257cdc;stroke-width:2;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6-6"
@ -379,15 +379,15 @@
y="923.9931" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="136.02846"
y="938.13525"
id="text4189-0"
sodipodi:linespacing="125%"><tspan
id="text4189-0"><tspan
sodipodi:role="line"
x="136.02846"
y="938.13525"
id="tspan4193-6">dpdk_port0</tspan></text>
id="tspan4193-6"
style="font-size:12.5px;line-height:1.25">dpdk_port0</tspan></text>
<rect
style="fill:#000000;fill-opacity:0;stroke:#257cdc;stroke-width:2;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6-5"
@ -397,33 +397,33 @@
y="824.99817" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="137.54369"
y="839.14026"
id="text4189-4"
sodipodi:linespacing="125%"><tspan
id="text4189-4"><tspan
sodipodi:role="line"
x="137.54369"
y="839.14026"
id="tspan4193-2">librte_pdump</tspan></text>
id="tspan4193-2"
style="font-size:12.5px;line-height:1.25">librte_pdump</tspan></text>
<rect
style="fill:url(#linearGradient5788);fill-opacity:1;stroke:#257cdc;stroke-width:1;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:url(#linearGradient5788);fill-opacity:1;stroke:#257cdc;stroke-width:1.07013;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6-4-5"
width="94.449265"
height="35.355339"
x="307.7804"
y="985.61243" />
width="108.21974"
height="35.335861"
x="297.9809"
y="985.62219" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="352.70618"
y="999.75458"
id="text4189-1-8"
sodipodi:linespacing="125%"><tspan
id="text4189-1-8"><tspan
sodipodi:role="line"
x="352.70618"
y="999.75458"
id="tspan4193-3-2">capture.pcap</tspan></text>
id="tspan4193-3-2"
style="font-size:12.5px;line-height:1.25">capture.pcapng</tspan></text>
<rect
style="fill:url(#linearGradient5788-1);fill-opacity:1;stroke:#257cdc;stroke-width:1.12555885;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4156-6-4-5-1"
@ -433,15 +433,15 @@
y="983.14984" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="136.53352"
y="1002.785"
id="text4189-1-8-4"
sodipodi:linespacing="125%"><tspan
id="text4189-1-8-4"><tspan
sodipodi:role="line"
x="136.53352"
y="1002.785"
id="tspan4193-3-2-7">Traffic Generator</tspan></text>
id="tspan4193-3-2-7"
style="font-size:12.5px;line-height:1.25">Traffic Generator</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker7331)"
d="m 351.46948,927.02357 c 0,57.5787 0,57.5787 0,57.5787"

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -14,7 +14,7 @@ and enhanced in 21.11.
The DPDK packet capture framework consists of the libraries
for collecting packets ``librte_pdump``
and writing packets to a file ``librte_pcapng``.
There is an application: ``dpdk-pdump``.
There are two sample applications: ``dpdk-dumpcap`` and older ``dpdk-pdump``.
Introduction
------------
@ -28,37 +28,48 @@ and its usage is recommended for debugging purposes.
The :doc:`librte_pcapng <../prog_guide/pcapng_lib>` library provides the API
to format packets and write them to a file in Pcapng format.
The application which initializes the packet capture framework will be a primary process
and the application that enables or disables the packet capture will
be a secondary process. The primary process sends the Rx and Tx packets from the DPDK ports
to the secondary process.
The :doc:`dpdk-dumpcap <../tools/dumpcap>` is a tool that captures packets in
like Wireshark dumpcap does for Linux.
It runs as a DPDK secondary process and captures packets
from one or more interfaces and writes them to a file in Pcapng format.
The ``dpdk-dumpcap`` tool is designed to take
most of the same options as the Wireshark ``dumpcap`` command.
In DPDK the ``testpmd`` application can be used to initialize the packet
capture framework and acts as a server, and the ``dpdk-pdump`` tool acts as a
client. To view Rx or Tx packets of ``testpmd``, the application should be
launched first, and then the ``dpdk-pdump`` tool. Packets from ``testpmd``
will be sent to the tool, which then sends them on to the Pcap PMD device and
that device writes them to the Pcap file or to an external interface depending
on the command-line option used.
Without any options it will use the packet capture framework
to capture traffic from the first available DPDK port.
The ``dpdk-testpmd`` application can be used to initialize
the packet capture framework and acts as a server,
and the ``dpdk-dumpcap`` tool acts as a client.
To view Rx or Tx packets of ``dpdk-testpmd``,
the application should be launched first,
and then the ``dpdk-dumpcap`` tool.
Packets from ``dpdk-testpmd`` will be sent to the tool,
and then to the Pcapng file.
Some things to note:
* The ``dpdk-pdump`` tool can only be used in conjunction with a primary
* All tools using ``librte_pdump`` can only be used in conjunction with a primary
application which has the packet capture framework initialized already. In
dpdk, only ``testpmd`` is modified to initialize packet capture framework,
other applications remain untouched. So, if the ``dpdk-pdump`` tool has to
other applications remain untouched. So, if the ``dpdk-dumpcap`` tool has to
be used with any application other than the testpmd, the user needs to
explicitly modify that application to call the packet capture framework
initialization code. Refer to the ``app/test-pmd/testpmd.c`` code and look
for ``pdump`` keyword to see how this is done.
* The ``dpdk-pdump`` tool depends on the libpcap based PMD.
* The ``dpdk-pdump`` tool is an older tool
created as demonstration of ``librte_pdump`` library.
The ``dpdk-pdump`` tool provides more limited functionality
and depends on the Pcap PMD.
It is retained only for compatibility reasons;
users should use ``dpdk-dumpcap`` instead.
Test Environment
----------------
The overview of using the Packet Capture Framework and the ``dpdk-pdump`` tool
The overview of using the Packet Capture Framework and the ``dpdk-dumpcap`` utility
for packet capturing on the DPDK port in
:numref:`figure_packet_capture_framework`.
@ -66,13 +77,13 @@ for packet capturing on the DPDK port in
.. figure:: img/packet_capture_framework.*
Packet capturing on a DPDK port using the dpdk-pdump tool.
Packet capturing on a DPDK port using the dpdk-dumpcap utility.
Running the Application
-----------------------
The following steps demonstrate how to run the ``dpdk-pdump`` tool to capture
The following steps demonstrate how to run the ``dpdk-dumpcap`` tool to capture
Rx side packets on dpdk_port0 in :numref:`figure_packet_capture_framework` and
inspect them using ``tcpdump``.
@ -80,16 +91,15 @@ inspect them using ``tcpdump``.
sudo <build_dir>/app/dpdk-testpmd -c 0xf0 -n 4 -- -i --port-topology=chained
#. Launch the pdump tool as follows::
#. Launch the dpdk-dumpcap as follows::
sudo <build_dir>/app/dpdk-pdump -- \
--pdump 'port=0,queue=*,rx-dev=/tmp/capture.pcap'
sudo <build_dir>/app/dpdk-dumpcap -w /tmp/capture.pcapng
#. Send traffic to dpdk_port0 from traffic generator.
Inspect packets captured in the file capture.pcap using a tool
that can interpret Pcap files, for example tcpdump::
Inspect packets captured in the file capture.pcapng using a tool
such as tcpdump or tshark that can interpret Pcapng files::
$tcpdump -nr /tmp/capture.pcap
$ tcpdump -nr /tmp/capture.pcapng
reading from file /tmp/capture.pcap, link-type EN10MB (Ethernet)
11:11:36.891404 IP 4.4.4.4.whois++ > 3.3.3.3.whois++: UDP, length 18
11:11:36.891442 IP 4.4.4.4.whois++ > 3.3.3.3.whois++: UDP, length 18

View File

@ -89,5 +89,5 @@ function.
Use Case: Packet Capturing
--------------------------
The DPDK ``app/pdump`` tool is developed based on this library to capture packets in DPDK.
Users can use this as an example to develop their own packet capturing tools.
The DPDK ``app/dpdk-dumpcap`` utility uses this library
to capture packets in DPDK.

View File

@ -242,6 +242,10 @@ New Features
* **Revised packet capture framework.**
* New dpdk-dumpcap program that has most of the features
of the wireshark dumpcap utility including:
capture of multiple interfaces, filtering,
and stopping after number of bytes, packets.
* New library for writing pcapng packet capture files.
* Enhancements to the pdump library to support:
* Packet filter with BPF.

View File

@ -0,0 +1,90 @@
.. SPDX-License-Identifier: BSD-3-Clause
Copyright(c) 2020 Microsoft Corporation.
dpdk-dumpcap Application
========================
The ``dpdk-dumpcap`` tool is a Data Plane Development Kit (DPDK)
network traffic dump tool.
The interface is similar to the dumpcap tool in Wireshark.
It runs as a secondary DPDK process and lets you capture packets
that are coming into and out of a DPDK primary process.
The ``dpdk-dumpcap`` writes files in Pcapng packet format.
Without any options set, it will use DPDK to capture traffic
from the first available DPDK interface
and write the received raw packet data,
along with timestamps into a pcapng file.
If the ``-w`` option is not specified, ``dpdk-dumpcap`` writes
to a newly created file with a name chosen
based on interface name and timestamp.
If ``-w`` option is specified, then that file is used.
.. note::
* The ``dpdk-dumpcap`` tool can only be used in conjunction with a primary
application which has the packet capture framework initialized already.
In DPDK, only the ``dpdk-testpmd`` is modified to initialize
packet capture framework, other applications remain untouched.
So, if the ``dpdk-dumpcap`` tool has to be used with any application
other than the ``dpdk-testpmd``, user needs to explicitly modify
that application to call packet capture framework initialization code.
Refer ``app/test-pmd/testpmd.c`` code to see how this is done.
* The ``dpdk-dumpcap`` tool runs as a DPDK secondary process.
It exits when the primary application exits.
Running the Application
-----------------------
To list interfaces available for capture, use ``--list-interfaces``.
To filter packets in style of *tshark*, use the ``-f`` flag.
To capture on multiple interfaces at once, use multiple ``-I`` flags.
Example
-------
.. code-block:: console
# <build_dir>/app/dpdk-dumpcap --list-interfaces
0. 000:00:03.0
1. 000:00:03.1
# <build_dir>/app/dpdk-dumpcap -I 0000:00:03.0 -c 6 -w /tmp/sample.pcapng
Packets captured: 6
Packets received/dropped on interface '0000:00:03.0' 6/0
# <build_dir>/app/dpdk-dumpcap -f 'tcp port 80'
Packets captured: 6
Packets received/dropped on interface '0000:00:03.0' 10/8
Limitations
-----------
The following option of Wireshark ``dumpcap`` is not yet implemented:
* ``-b|--ring-buffer`` -- more complex file management.
The following options do not make sense in the context of DPDK.
* ``-C <byte_limit>`` -- it's a kernel thing.
* ``-t`` -- use a thread per interface.
* Timestamp type.
* Link data types. Only EN10MB (Ethernet) is supported.
* Wireless related options: ``-I|--monitor-mode`` and ``-k <freq>``
.. note::
* The options to ``dpdk-dumpcap`` are like the Wireshark dumpcap program
and are not the same as ``dpdk-pdump`` and other DPDK applications.

View File

@ -8,11 +8,12 @@ DPDK Tools User Guides
:maxdepth: 2
:numbered:
proc_info
pdump
pmdinfo
hugepages
devbind
proc_info
pmdinfo
dumpcap
pdump
flow-perf
testbbdev
cryptoperf