* [PATCH] examples/rpcapd: demo version of packet capture daemon
@ 2026-09-08 21:07 Stephen Hemminger
0 siblings, 0 replies; only message in thread
From: Stephen Hemminger @ 2026-09-08 21:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Reshma Pattan
This example adds RPCAP support over localhost TCP
integrated with DPDK. It uses a secondary process that allows
connections from using tcpdump defacto protocol rpcap.
See: doc/guides/sample_app_ug/rpcap.rst for more info.
This does not preclude using pdump, dumpcap, or the wireshark
extcap integration.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/rel_notes/release_26_11.rst | 4 +
doc/guides/sample_app_ug/index.rst | 1 +
doc/guides/sample_app_ug/rpcapd.rst | 206 +++++
examples/meson.build | 1 +
examples/rpcapd/main.c | 1014 ++++++++++++++++++++++++
examples/rpcapd/meson.build | 11 +
examples/rpcapd/rpcap-protocol.h | 96 +++
7 files changed, 1333 insertions(+)
create mode 100644 doc/guides/sample_app_ug/rpcapd.rst
create mode 100644 examples/rpcapd/main.c
create mode 100644 examples/rpcapd/meson.build
create mode 100644 examples/rpcapd/rpcap-protocol.h
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 87c7e81bde..9dabd80c56 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,10 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added an example of tcpdump remote pcap daemon.**
+
+ Added an example that implements rpcap to allow live capture in tcpdump.
+
Removed Items
-------------
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index f12623bb66..61ed870318 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -31,6 +31,7 @@ Sample Applications User Guides
l3_forward_graph
l3_forward_power_man
link_status_intr
+ rpcapd
server_node_efd
service_cores
multi_process
diff --git a/doc/guides/sample_app_ug/rpcapd.rst b/doc/guides/sample_app_ug/rpcapd.rst
new file mode 100644
index 0000000000..e899a4d991
--- /dev/null
+++ b/doc/guides/sample_app_ug/rpcapd.rst
@@ -0,0 +1,206 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026
+
+.. _rpcapd_app:
+
+dpdk-rpcapd Sample Application
+==============================
+
+The ``dpdk-rpcapd`` sample application is a Data Plane Development Kit
+(DPDK) implementation of the remote packet capture daemon protocol
+(``rpcap``) used by libpcap. It runs as a DPDK secondary process and
+allows libpcap-aware tools such as ``tcpdump`` and Wireshark to capture
+packets from a DPDK primary process live, without writing to an
+intermediate file.
+
+The ``dpdk-rpcapd`` tool implements a subset of the protocol spoken by
+the libpcap project's ``rpcapd``. See
+https://github.com/the-tcpdump-group/libpcap/tree/master/rpcapd for the
+reference implementation. Clients connect to ``dpdk-rpcapd`` using a
+``rpcap://`` URL, request the list of available interfaces (which are
+the ports of the DPDK primary), open one, and stream packets from it.
+
+The intended workflow is one-step capture: start the primary, start
+``dpdk-rpcapd``, and point a familiar tool at it. No intermediate files,
+no separate post-processing step.
+
+.. warning::
+
+ ``dpdk-rpcapd`` listens on an unauthenticated, unencrypted TCP port
+ (default 2002, bound to ``127.0.0.1``). Any local user able to
+ reach the port can list DPDK ports and capture all traffic flowing
+ through them. This is a sample application intended for
+ development, debugging, and demonstration use only. **Do not run
+ ``dpdk-rpcapd`` on a production system.**
+
+ The default bind address is ``127.0.0.1`` so the listener is not
+ reachable from other hosts. An operator may override this with
+ ``--bind <addr>`` but should expect that the resulting deployment
+ exposes captured traffic to anyone who can reach that address; do
+ not do this on an untrusted network.
+
+
+.. note::
+
+ * The ``dpdk-rpcapd`` tool can only be used in conjunction with a
+ primary application that has the packet capture framework
+ initialized already. In DPDK, only ``dpdk-testpmd`` is modified to
+ initialize the packet capture framework; other applications must
+ be modified to call ``rte_pdump_init()`` if they are to be
+ capturable.
+
+ * ``dpdk-rpcapd`` does not replace ``dpdk-dumpcap``. ``dpdk-dumpcap``
+ produces pcapng files; ``dpdk-rpcapd`` produces a live rpcap
+ stream. The two tools serve different workflows and may be used
+ in parallel.
+
+ * For Wireshark users specifically, the Wireshark ``extcap`` plugin
+ interface is the preferred live-capture path; see :doc:`extcap`.
+ ``extcap`` integrates directly with Wireshark and is simpler to
+ deploy. ``dpdk-rpcapd`` is intended for users who want to use
+ ``tcpdump`` or other rpcap-aware libpcap clients, where ``extcap``
+ does not apply.
+
+
+Running the Application
+-----------------------
+
+The application has a small set of command-line options:
+
+* ``-p <port>``, ``--port <port>``
+
+ TCP port to listen on. Default is 2002, the IANA-assigned rpcap
+ port.
+
+* ``-b <addr>``, ``--bind <addr>``
+
+ IPv4 address to bind the listener to. Default is ``127.0.0.1``
+ (loopback only). Setting any other address exposes captured
+ traffic to the network and should not be done on untrusted
+ networks.
+
+* ``-N <ring_size>``
+
+ Size of the per-session capture ring in packets. Default is 2048.
+
+* ``-h``, ``--help``
+
+ Print usage and exit.
+
+EAL options are supplied automatically; the application runs as a
+secondary process and does not need EAL options on its command line for
+typical use.
+
+
+Client Setup
+------------
+
+Most Linux distributions ship libpcap built without ``rpcap`` support
+because the libpcap project leaves ``--enable-remote`` off by default.
+To use ``dpdk-rpcapd`` from ``tcpdump`` or Wireshark on Linux, libpcap
+must be rebuilt with remote support enabled. Approximate steps:
+
+.. code-block:: console
+
+ wget https://www.tcpdump.org/release/libpcap-1.10.6.tar.xz
+ tar xf libpcap-1.10.6.tar.xz
+ cd libpcap-1.10.6
+ ./configure --enable-remote
+ make
+ sudo make install
+ sudo ldconfig
+
+To verify that the resulting library has rpcap support:
+
+.. code-block:: console
+
+ nm -D /usr/local/lib/libpcap.so | grep ' T pcap_open$'
+
+The symbol ``pcap_open`` should be present. If not, the ``--enable-remote``
+flag did not take effect.
+
+``tcpdump`` rebuilt against this libpcap can be used as a client without
+further changes. Wireshark on Windows and macOS ships with rpcap support
+enabled by default.
+
+
+Example
+-------
+
+Start a primary application with the packet capture framework
+initialized. ``dpdk-testpmd`` is the simplest:
+
+.. code-block:: console
+
+ sudo ./<build_dir>/app/dpdk-testpmd -- --no-mlockall --vdev=net_tap0
+
+In another window, start ``dpdk-rpcapd``:
+
+.. code-block:: console
+
+ sudo ./<build_dir>/app/dpdk-rpcapd
+ RPCAPD: listening on TCP port 2002
+
+In a third window, list available interfaces using a libpcap-based
+``tcpdump`` rebuilt with remote support:
+
+.. code-block:: console
+
+ sudo /usr/local/sbin/tcpdump --list-remote-interfaces=rpcap://localhost:2002/
+ rpcap://localhost:2002/net_tap0 Network adapter 'DPDK port' on remote node localhost
+
+Capture live from a port:
+
+.. code-block:: console
+
+ sudo /usr/local/sbin/tcpdump -i rpcap://localhost:2002/net_tap0 -nn -c 20
+
+Or save to a file readable by any pcap consumer:
+
+.. code-block:: console
+
+ sudo /usr/local/sbin/tcpdump -i rpcap://localhost:2002/net_tap0 -w /tmp/capture.pcap
+
+
+Limitations
+-----------
+
+The following limitations apply to this initial version of
+``dpdk-rpcapd`` and are expected to be addressed in subsequent patches:
+
+* **Single client.** Only one client may be connected at a time.
+ Subsequent clients are queued by the listening socket but not
+ serviced until the first disconnects. Multi-client support
+ requires an event-driven main loop (planned).
+
+* **No BPF filter support.** ``UPDATEFILTER`` requests are
+ acknowledged and ignored. Capture-side filtering requires an
+ extension to ``rte_pdump`` to support filter updates on an active
+ callback.
+
+* **No authentication.** ``AUTH`` requests are acknowledged with an
+ empty reply (libpcap "version 0, null auth" semantics). This
+ sample application does not implement password authentication.
+
+* **TCP transport only; not for production use.** The rpcap protocol
+ over TCP is unauthenticated and unencrypted; any client that can
+ reach the listening port has full access to captured traffic.
+ Binding to ``127.0.0.1`` by default mitigates remote exposure but
+ does not address local users on a shared host. See the warning at
+ the top of this document.
+
+* **Microsecond timestamp resolution.** The rpcap protocol carries
+ timestamps at microsecond resolution.
+
+
+See Also
+--------
+
+* :doc:`extcap` -- Wireshark ``extcap`` plugin for direct integration
+ with Wireshark, without going through the rpcap protocol.
+
+* :doc:`../tools/dumpcap` -- file-based capture writing pcapng
+ output.
+
+* The libpcap project's ``rpcapd`` reference implementation:
+ https://github.com/the-tcpdump-group/libpcap/tree/master/rpcapd
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..24b6184353 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -45,6 +45,7 @@ all_examples = [
'ptpclient',
'qos_meter',
'qos_sched',
+ 'rpcapd',
'rxtx_callbacks',
'server_node_efd/efd_node',
'server_node_efd/efd_server',
diff --git a/examples/rpcapd/main.c b/examples/rpcapd/main.c
new file mode 100644
index 0000000000..e16f5aa3e0
--- /dev/null
+++ b/examples/rpcapd/main.c
@@ -0,0 +1,1014 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ *
+ * Proof-of-concept DPDK rpcapd: the libpcap remote packet capture
+ * daemon, implemented on top of DPDK pdump. A libpcap client (e.g.
+ * Wireshark or tcpdump using "rpcap://host[:port]/portname") can
+ * connect, list DPDK ports, open one, and stream live packets from it.
+ *
+ * Based on the DPDK dumpcap application and on rpcapd from libpcap:
+ * https://github.com/the-tcpdump-group/libpcap/tree/master/rpcapd
+ *
+ * Only the bits of the RPCAP protocol that are needed for an
+ * unauthenticated, passive-mode capture session are implemented.
+ * Configuration files, BPF filters, active mode, statistics, sampling,
+ * IPv6 and concurrent clients are intentionally omitted to keep the
+ * example small.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <rte_alarm.h>
+#include <rte_common.h>
+#include <rte_debug.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_lcore.h>
+#include <rte_log.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_pdump.h>
+#include <rte_stdatomic.h>
+#include <rte_ring.h>
+#include <rte_version.h>
+
+#include "rpcap-protocol.h"
+
+#define BURST_SIZE 32
+#define MBUF_CACHE_SIZE 32
+#define DEFAULT_RING_SIZE 2048
+#define DEFAULT_SNAPLEN RTE_MBUF_DEFAULT_BUF_SIZE
+#define PRIMARY_MONITOR_INTERVAL_US (500 * 1000)
+
+/* Logging. Use --log-level=rpcapd:debug to enable debug output. */
+RTE_LOG_REGISTER(rpcapd_logtype, rpcapd, INFO);
+#define RTE_LOGTYPE_RPCAPD rpcapd_logtype
+
+/* Per-client capture session state. */
+struct session {
+ int ctrl_fd;
+ int data_fd;
+ uint16_t port; /* DPDK ethdev port being captured */
+ char name[RTE_ETH_NAME_MAX_LEN];
+ uint32_t snaplen;
+ uint32_t npkt; /* packet sequence for rpcap_pkthdr */
+ bool capture_on;
+ struct rte_ring *ring;
+ struct rte_mempool *mp;
+};
+
+/* Command-line options */
+static uint16_t listen_port = RPCAP_DEFAULT_NETPORT;
+static uint32_t ring_size = DEFAULT_RING_SIZE;
+static const char *lcore_arg;
+static const char *file_prefix;
+static const char *bind_arg; /* -b argument, resolved after option parsing */
+static const char *debug_file; /* --debug-file argument */
+static bool ipv4_only; /* -4: restrict to IPv4 */
+static bool debug_log; /* -D: enable RPCAPD debug logging */
+
+/* Bind address for the listener and the per-session data port.
+ * Defaults to IPv4 loopback because the rpcap protocol is insecure.
+ * It exposes captured traffic to anyone who can reach the port.
+ * An operator who knowingly accepts that risk can override with
+ * --bind <addr>. IPv4 and IPv6 numeric addresses are both accepted.
+ */
+static struct sockaddr_storage listen_addr;
+static socklen_t listen_addrlen;
+
+static void stop_capture(struct session *s);
+
+static void
+set_sockaddr_port(struct sockaddr_storage *ss, uint16_t port)
+{
+ if (ss->ss_family == AF_INET6)
+ ((struct sockaddr_in6 *)ss)->sin6_port = htons(port);
+ else
+ ((struct sockaddr_in *)ss)->sin_port = htons(port);
+}
+
+static uint16_t
+get_sockaddr_port(const struct sockaddr_storage *ss)
+{
+ if (ss->ss_family == AF_INET6)
+ return ntohs(((const struct sockaddr_in6 *)ss)->sin6_port);
+ return ntohs(((const struct sockaddr_in *)ss)->sin_port);
+}
+
+static bool
+is_loopback(const struct sockaddr_storage *ss)
+{
+ if (ss->ss_family == AF_INET) {
+ const struct sockaddr_in *sin = (const void *)ss;
+
+ return (ntohl(sin->sin_addr.s_addr) >> 24) == 127;
+ }
+ if (ss->ss_family == AF_INET6) {
+ const struct sockaddr_in6 *sin6 = (const void *)ss;
+
+ return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr);
+ }
+ return false;
+}
+
+static void
+parse_bind_addr(const char *str, int family)
+{
+ struct addrinfo hints = {
+ .ai_family = family,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_NUMERICHOST | AI_PASSIVE,
+ };
+ struct addrinfo *res;
+ int rc;
+
+ rc = getaddrinfo(str, NULL, &hints, &res);
+ if (rc != 0)
+ rte_exit(EXIT_FAILURE, "Invalid bind address '%s': %s\n",
+ str, gai_strerror(rc));
+ memcpy(&listen_addr, res->ai_addr, res->ai_addrlen);
+ listen_addrlen = res->ai_addrlen;
+ freeaddrinfo(res);
+}
+
+static RTE_ATOMIC(bool) quit_signal;
+
+static void
+signal_handler(int sig __rte_unused)
+{
+ rte_atomic_store_explicit(&quit_signal, true, rte_memory_order_relaxed);
+}
+
+/* Read exactly len bytes; return 0 on success, -1 on error or EOF. */
+static int
+recv_full(int fd, void *buf, size_t len)
+{
+ uint8_t *p = buf;
+
+ while (len > 0) {
+ ssize_t n = recv(fd, p, len, 0);
+ if (rte_atomic_load_explicit(&quit_signal, rte_memory_order_relaxed))
+ return -1;
+
+ if (n < 0 && errno == EINTR)
+ continue;
+
+ if (n <= 0)
+ return -1;
+
+ p += n;
+ len -= n;
+ }
+ return 0;
+}
+
+static int
+send_iov_full(int fd, struct iovec *iov, int iovcnt, int flags)
+{
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = iovcnt,
+ };
+
+ while (sendmsg(fd, &msg, flags | MSG_NOSIGNAL) < 0) {
+ if (errno != EINTR)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+rpcap_send_msg(int fd, uint8_t type, uint16_t value, const void *payload, uint32_t plen)
+{
+ struct rpcap_header hdr = {
+ .ver = RPCAP_VERSION,
+ .type = type,
+ .value = htons(value),
+ .plen = htonl(plen),
+ };
+ struct iovec iov[2] = {
+ { .iov_base = &hdr, .iov_len = sizeof(hdr) },
+ { .iov_base = (void *)(uintptr_t)payload, .iov_len = plen },
+ };
+
+ return send_iov_full(fd, iov, plen > 0 ? 2 : 1, 0);
+}
+
+static int
+rpcap_send_error(int fd, uint16_t errcode, const char *msg)
+{
+ RTE_LOG(WARNING, RPCAPD, "sending error to client: %s\n", msg);
+ return rpcap_send_msg(fd, RPCAP_MSG_ERROR, errcode, msg, strlen(msg));
+}
+
+static int
+rpcap_recv_header(int fd, struct rpcap_header *hdr)
+{
+ if (recv_full(fd, hdr, sizeof(*hdr)) < 0)
+ return -1;
+ hdr->value = ntohs(hdr->value);
+ hdr->plen = ntohl(hdr->plen);
+ return 0;
+}
+
+/* Throw away plen bytes of payload we don't care about. */
+static int
+rpcap_discard(int fd, uint32_t plen)
+{
+ uint8_t buf[256];
+
+ while (plen > 0) {
+ size_t chunk = plen > sizeof(buf) ? sizeof(buf) : plen;
+
+ if (recv_full(fd, buf, chunk) < 0)
+ return -1;
+ plen -= chunk;
+ }
+ return 0;
+}
+
+/* Build and send the list of available DPDK ports. */
+static int
+handle_findallif(int fd)
+{
+ uint8_t *buf = NULL;
+ size_t buflen = 0;
+ uint16_t nif = 0;
+ uint16_t p;
+ int rc;
+
+ RTE_ETH_FOREACH_DEV(p) {
+ static const char desc[] = "DPDK port";
+ char name[RTE_ETH_NAME_MAX_LEN];
+ size_t namelen, desclen, entry;
+ uint8_t *nb;
+
+ if (rte_eth_dev_get_name_by_port(p, name) < 0) {
+ RTE_LOG(INFO, RPCAPD, "can not find name for port %u\n", p);
+ continue;
+ }
+
+ RTE_LOG(INFO, RPCAPD, "findallif: port %u -> '%s'\n", p, name);
+ namelen = strlen(name);
+ desclen = strlen(desc);
+ entry = sizeof(struct rpcap_findalldevs_if) + namelen + desclen;
+
+ nb = realloc(buf, buflen + entry);
+ if (nb == NULL) {
+ RTE_LOG(ERR, RPCAPD, "out of memory in findallif\n");
+ free(buf);
+ return rpcap_send_error(fd, 0, "out of memory");
+ }
+ buf = nb;
+
+ struct rpcap_findalldevs_if iface = {
+ .namelen = htons(namelen),
+ .desclen = htons(desclen),
+ .flags = htonl(PCAP_IF_UP | PCAP_IF_RUNNING),
+ };
+ memcpy(buf + buflen, &iface, sizeof(iface));
+ memcpy(buf + buflen + sizeof(iface), name, namelen);
+ memcpy(buf + buflen + sizeof(iface) + namelen, desc, desclen);
+ buflen += entry;
+ nif++;
+ }
+
+ RTE_LOG(INFO, RPCAPD, "findallif: %u interface(s)\n", nif);
+ rc = rpcap_send_msg(fd, RPCAP_MSG_FINDALLIF_REPLY, nif, buf, buflen);
+ free(buf);
+ return rc;
+}
+
+/* OPEN_REQ: payload is the interface name (no NUL). */
+static int
+handle_open(int fd, uint32_t plen, struct session *s)
+{
+ struct rpcap_openreply reply = {
+ .linktype = htonl(DLT_EN10MB),
+ };
+ uint16_t port;
+
+ if (s->capture_on)
+ stop_capture(s);
+
+ if (plen >= sizeof(s->name)) {
+ rpcap_discard(fd, plen);
+ return rpcap_send_error(fd, 0, "interface name too long");
+ }
+ if (recv_full(fd, s->name, plen) < 0)
+ return -1;
+ s->name[plen] = '\0';
+
+ if (rte_eth_dev_get_port_by_name(s->name, &port) < 0) {
+ RTE_LOG(WARNING, RPCAPD, "open: no such port '%s'\n", s->name);
+ return rpcap_send_error(fd, 0, "unknown interface");
+ }
+ s->port = port;
+
+ RTE_LOG(DEBUG, RPCAPD, "open: '%s' -> dpdk port %u\n", s->name, port);
+ return rpcap_send_msg(fd, RPCAP_MSG_OPEN_REPLY, 0, &reply, sizeof(reply));
+}
+
+/* Open an ephemeral TCP listening socket; return fd, set *port_out. */
+static int
+open_data_listener(uint16_t *port_out)
+{
+ struct sockaddr_storage addr = listen_addr;
+ socklen_t alen;
+ int fd;
+
+ set_sockaddr_port(&addr, 0);
+
+ fd = socket(addr.ss_family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ RTE_LOG(ERR, RPCAPD, "data socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ alen = listen_addrlen;
+ if (bind(fd, (struct sockaddr *)&addr, alen) < 0 ||
+ listen(fd, 1) < 0 ||
+ getsockname(fd, (struct sockaddr *)&addr, &alen) < 0) {
+ RTE_LOG(ERR, RPCAPD, "data port bind/listen: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ *port_out = get_sockaddr_port(&addr);
+ return fd;
+}
+
+static struct rte_ring *
+create_capture_ring(uint16_t port)
+{
+ char name[RTE_RING_NAMESIZE];
+
+ snprintf(name, sizeof(name), "rpcapd_r_%u_%d", port, getpid());
+ return rte_ring_create(name, ring_size, rte_socket_id(), 0);
+}
+
+static struct rte_mempool *
+create_capture_mempool(uint16_t port, uint32_t snaplen)
+{
+ char name[RTE_MEMPOOL_NAMESIZE];
+ uint32_t mbuf_size = RTE_PKTMBUF_HEADROOM + snaplen;
+
+ snprintf(name, sizeof(name), "rpcapd_p_%u_%d", port, getpid());
+ return rte_pktmbuf_pool_create(name, ring_size * 2, MBUF_CACHE_SIZE, 0,
+ mbuf_size, rte_socket_id());
+}
+
+/* Tear down anything that handle_startcap brought up. Safe to call
+ * after partial setup as well as after a successful capture.
+ */
+static void
+stop_capture(struct session *s)
+{
+ struct rte_mbuf *pkts[BURST_SIZE];
+ unsigned int n;
+
+ if (s->capture_on) {
+ rte_pdump_disable(s->port, RTE_PDUMP_ALL_QUEUES, RTE_PDUMP_FLAG_RXTX);
+ RTE_LOG(INFO, RPCAPD, "capture stopped on %s (%u packets)\n",
+ s->name, s->npkt);
+ }
+ s->capture_on = false;
+
+ if (s->ring != NULL) {
+ while ((n = rte_ring_sc_dequeue_burst(s->ring, (void **)pkts,
+ BURST_SIZE, NULL)) > 0)
+ rte_pktmbuf_free_bulk(pkts, n);
+ rte_ring_free(s->ring);
+ s->ring = NULL;
+ }
+ if (s->mp != NULL) {
+ rte_mempool_free(s->mp);
+ s->mp = NULL;
+ }
+ if (s->data_fd >= 0) {
+ close(s->data_fd);
+ s->data_fd = -1;
+ }
+}
+
+/*
+ * STARTCAP_REQ: open the data connection and arm the pdump callback.
+ * We use passive mode with the server-allocated data port:
+ * - the server picks an ephemeral port and listens on it
+ * - the server returns that port in startcapreply.portdata
+ * - the client connects back to that port for the packet stream
+ */
+static int
+handle_startcap(int fd, uint32_t plen, struct session *s)
+{
+ struct rpcap_startcapreq req;
+ uint16_t data_port;
+ int data_listen;
+ int data_fd;
+
+ if (s->capture_on)
+ stop_capture(s);
+
+ if (plen < sizeof(req)) {
+ rpcap_discard(fd, plen);
+ return rpcap_send_error(fd, 0, "short startcap request");
+ }
+ if (recv_full(fd, &req, sizeof(req)) < 0)
+ return -1;
+ /* Skip any embedded BPF filter; not supported here. */
+ if (rpcap_discard(fd, plen - sizeof(req)) < 0)
+ return -1;
+
+ s->snaplen = ntohl(req.snaplen);
+ if (s->snaplen == 0 || s->snaplen > DEFAULT_SNAPLEN)
+ s->snaplen = DEFAULT_SNAPLEN;
+
+ s->ring = create_capture_ring(s->port);
+ s->mp = create_capture_mempool(s->port, s->snaplen);
+ if (s->ring == NULL || s->mp == NULL) {
+ RTE_LOG(ERR, RPCAPD, "ring/mempool alloc failed: %s\n",
+ rte_strerror(rte_errno));
+ stop_capture(s);
+ return rpcap_send_error(fd, 0, "DPDK alloc failed");
+ }
+
+ data_listen = open_data_listener(&data_port);
+ if (data_listen < 0) {
+ stop_capture(s);
+ return rpcap_send_error(fd, 0, "data port setup failed");
+ }
+
+ struct rpcap_startcapreply reply = {
+ .bufsize = htonl(s->snaplen * BURST_SIZE),
+ .portdata = htons(data_port),
+ };
+ if (rpcap_send_msg(fd, RPCAP_MSG_STARTCAP_REPLY, 0, &reply, sizeof(reply)) < 0) {
+ close(data_listen);
+ stop_capture(s);
+ return -1;
+ }
+
+ RTE_LOG(INFO, RPCAPD, "awaiting connection\n");
+
+ data_fd = accept(data_listen, NULL, NULL);
+ close(data_listen);
+ if (data_fd < 0) {
+ RTE_LOG(ERR, RPCAPD, "accept on data port: %s\n", strerror(errno));
+ stop_capture(s);
+ return -1;
+ }
+
+ s->data_fd = data_fd;
+
+ if (rte_pdump_enable(s->port, RTE_PDUMP_ALL_QUEUES, RTE_PDUMP_FLAG_RXTX,
+ s->ring, s->mp, NULL) < 0) {
+ RTE_LOG(ERR, RPCAPD, "rte_pdump_enable port %u failed: %s\n",
+ s->port, rte_strerror(rte_errno));
+ stop_capture(s);
+ return -1;
+ }
+ s->capture_on = true;
+ s->npkt = 0;
+
+ RTE_LOG(INFO, RPCAPD,
+ "capture started on %s (snaplen %u, data port %u)\n",
+ s->name, s->snaplen, data_port);
+ return 0;
+}
+
+/*
+ * Pull a burst from the ring, frame each packet into an RPCAP_MSG_PACKET
+ * message, and send it on the data connection. MSG_MORE on all but the
+ * last send tells the kernel to coalesce the burst into full segments.
+ */
+static int
+process_ring(struct session *s)
+{
+ struct rte_mbuf *pkts[BURST_SIZE];
+ unsigned int i, n;
+
+ n = rte_ring_sc_dequeue_burst(s->ring, (void **)pkts, BURST_SIZE, NULL);
+ if (n == 0)
+ return 0;
+
+ for (i = 0; i < n; i++) {
+ struct rte_mbuf *m = pkts[i];
+ uint8_t buf[RTE_ETHER_MAX_JUMBO_FRAME_LEN];
+ uint32_t pktlen = rte_pktmbuf_pkt_len(m);
+ uint32_t caplen = pktlen < s->snaplen ? pktlen : s->snaplen;
+ const void *data;
+ struct timeval tv;
+
+ s->npkt++;
+
+ struct rpcap_header hdr = {
+ .ver = RPCAP_VERSION,
+ .type = RPCAP_MSG_PACKET,
+ .plen = htonl(sizeof(struct rpcap_pkthdr) + caplen),
+ };
+
+ gettimeofday(&tv, NULL);
+
+ struct rpcap_pkthdr pkthdr = {
+ .timestamp_sec = htonl((uint32_t)tv.tv_sec),
+ .timestamp_usec = htonl((uint32_t)tv.tv_usec),
+ .caplen = htonl(caplen),
+ .len = htonl(pktlen),
+ .npkt = htonl(s->npkt),
+ };
+
+ data = rte_pktmbuf_read(m, 0, caplen, buf);
+
+ struct iovec iov[3] = {
+ { .iov_base = &hdr, .iov_len = sizeof(hdr) },
+ { .iov_base = &pkthdr, .iov_len = sizeof(pkthdr) },
+ { .iov_base = (void *)(uintptr_t)data, .iov_len = caplen },
+ };
+ if (send_iov_full(s->data_fd, iov, 3,
+ i + 1 < n ? MSG_MORE : 0) < 0) {
+ RTE_LOG(NOTICE, RPCAPD, "data connection closed: %s\n", strerror(errno));
+ goto error;
+ }
+ rte_pktmbuf_free(m);
+ }
+
+ return (int)n;
+
+error:
+ rte_pktmbuf_free_bulk(pkts + i, n - i);
+ return -1;
+}
+
+/*
+ * Stay in the capture loop until either:
+ * - a control message arrives (typically ENDCAP),
+ * - the data connection breaks, or
+ * - a quit signal is delivered.
+ */
+static int
+capture_loop(int ctrl_fd, struct session *s)
+{
+ struct pollfd pfd = { .fd = ctrl_fd, .events = POLLIN };
+ unsigned int idle = 0;
+
+ while (!rte_atomic_load_explicit(&quit_signal, rte_memory_order_relaxed)) {
+ int n;
+
+ if (poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN))
+ return 0;
+
+ n = process_ring(s);
+ if (n < 0)
+ return -1;
+ if (n == 0) {
+ if (idle++ < 1000)
+ continue;
+ usleep(1000);
+ idle = 0;
+ } else {
+ idle = 0;
+ }
+ }
+ return 0;
+}
+
+static int
+handle_endcap(int fd, uint32_t plen, struct session *s)
+{
+ if (rpcap_discard(fd, plen) < 0)
+ return -1;
+ stop_capture(s);
+ return rpcap_send_msg(fd, RPCAP_MSG_ENDCAP_REPLY, 0, NULL, 0);
+}
+
+static int
+handle_stats(int fd, uint32_t plen, const struct session *s)
+{
+ struct rte_eth_stats es = { 0 };
+
+ if (rpcap_discard(fd, plen) < 0)
+ return -1;
+
+ if (s->capture_on)
+ rte_eth_stats_get(s->port, &es);
+
+ struct rpcap_stats reply = {
+ .ifrecv = htonl((uint32_t)es.ipackets),
+ .ifdrop = htonl((uint32_t)es.ierrors),
+ .krnldrop = 0,
+ .svrcapt = htonl(s->npkt),
+ };
+ return rpcap_send_msg(fd, RPCAP_MSG_STATS_REPLY, 0, &reply, sizeof(reply));
+}
+
+/* Service a single client until it disconnects. */
+static void
+handle_client(int ctrl_fd)
+{
+ struct sockaddr_storage peer;
+ socklen_t plen = sizeof(peer);
+ char host[NI_MAXHOST] = "?";
+ struct session s = { .ctrl_fd = ctrl_fd, .data_fd = -1 };
+
+ if (getpeername(ctrl_fd, (struct sockaddr *)&peer, &plen) == 0)
+ getnameinfo((struct sockaddr *)&peer, plen,
+ host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+ RTE_LOG(INFO, RPCAPD, "client %s connected\n", host);
+
+ while (!rte_atomic_load_explicit(&quit_signal, rte_memory_order_relaxed)) {
+ struct rpcap_header hdr;
+
+ if (rpcap_recv_header(ctrl_fd, &hdr) < 0)
+ break;
+
+ switch (hdr.type) {
+ case RPCAP_MSG_AUTH_REQ:
+ /* No auth: discard credentials, ack with empty reply.
+ * libpcap treats a zero-length AUTH_REPLY as "version
+ * 0 only, same byte order".
+ */
+ if (rpcap_discard(ctrl_fd, hdr.plen) < 0 ||
+ rpcap_send_msg(ctrl_fd, RPCAP_MSG_AUTH_REPLY, 0, NULL, 0) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_FINDALLIF_REQ:
+ if (rpcap_discard(ctrl_fd, hdr.plen) < 0 || handle_findallif(ctrl_fd) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_OPEN_REQ:
+ if (handle_open(ctrl_fd, hdr.plen, &s) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_STARTCAP_REQ:
+ if (handle_startcap(ctrl_fd, hdr.plen, &s) < 0)
+ goto done;
+ if (capture_loop(ctrl_fd, &s) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_UPDATEFILTER_REQ:
+ /* Filters not implemented; ack and ignore. */
+ if (rpcap_discard(ctrl_fd, hdr.plen) < 0 ||
+ rpcap_send_msg(ctrl_fd, RPCAP_MSG_UPDATEFILTER_REPLY,
+ 0, NULL, 0) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_ENDCAP_REQ:
+ if (handle_endcap(ctrl_fd, hdr.plen, &s) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_STATS_REQ:
+ if (handle_stats(ctrl_fd, hdr.plen, &s) < 0)
+ goto done;
+ break;
+ case RPCAP_MSG_CLOSE:
+ rpcap_discard(ctrl_fd, hdr.plen);
+ goto done;
+ default:
+ RTE_LOG(WARNING, RPCAPD, "unsupported request type 0x%02x\n", hdr.type);
+ rpcap_discard(ctrl_fd, hdr.plen);
+ rpcap_send_error(ctrl_fd, 0, "unsupported request");
+ break;
+ }
+ }
+done:
+ stop_capture(&s);
+ close(ctrl_fd);
+ RTE_LOG(INFO, RPCAPD, "client %s disconnected\n", host);
+}
+
+static int
+open_listen_socket(uint16_t port)
+{
+ struct sockaddr_storage addr = listen_addr;
+ char host[NI_MAXHOST];
+ int fd, one = 1;
+
+ set_sockaddr_port(&addr, port);
+
+ fd = socket(addr.ss_family, SOCK_STREAM, 0);
+ if (fd < 0)
+ rte_exit(EXIT_FAILURE, "socket: %s\n", strerror(errno));
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if (bind(fd, (struct sockaddr *)&addr, listen_addrlen) < 0)
+ rte_exit(EXIT_FAILURE, "bind(%u): %s\n", port, strerror(errno));
+
+ int err = getnameinfo((struct sockaddr *)&listen_addr, listen_addrlen,
+ host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+ if (err != 0)
+ rte_exit(EXIT_FAILURE, "Listen address lookup failed: %s\n",
+ gai_strerror(err));
+
+ RTE_LOG(INFO, RPCAPD, "listening on %s port %u\n", host, listen_port);
+
+ if (!is_loopback(&listen_addr))
+ RTE_LOG(WARNING, RPCAPD,
+ "bound to non-loopback address %s; "
+ "rpcap is unauthenticated and unencrypted, "
+ "captured traffic is exposed to the network\n",
+ host);
+
+ if (listen(fd, 1) < 0)
+ rte_exit(EXIT_FAILURE, "listen: %s\n", strerror(errno));
+
+ return fd;
+}
+
+static void
+usage(FILE *f, const char *progname)
+{
+ fprintf(f, "Usage: %s [options]\n", progname);
+ fprintf(f,
+ " -p, --port <port> listen port (default %u)\n"
+ " -b, --bind <addr> bind address (default 127.0.0.1)\n"
+ " -4 use only IPv4 (reject IPv6 bind addresses)\n"
+ " -N <ring size> ring size in packets (default %u)\n"
+ " -D, --debug enable rpcapd debug log messages\n"
+ " --debug-file <f> redirect log output to file <f> (append mode)\n"
+ " --version print version and exit\n"
+ " -h, --help print this help and exit\n"
+ " --lcore=<core> CPU core to run on (default: any)\n"
+ " --file-prefix=<p> prefix to use for multi-process\n"
+ "\n"
+ "WARNING: rpcap is unauthenticated and unencrypted. Binding to\n"
+ "any non-loopback address exposes captured traffic to the\n"
+ "network. Sample application; not for production use.\n",
+ RPCAP_DEFAULT_NETPORT, DEFAULT_RING_SIZE);
+}
+
+static void
+print_version(void)
+{
+ printf("rpcapd, a remote packet capture daemon (DPDK pdump backend)\n"
+ "Built against %s\n", rte_version());
+}
+
+static void
+parse_opts(int argc, char **argv)
+{
+ enum {
+ OPT_LONG_ONLY = 0x100,
+ OPT_DEBUG_FILE,
+ OPT_VERSION,
+ };
+ static const struct option long_options[] = {
+ { "port", required_argument, NULL, 'p' },
+ { "bind", required_argument, NULL, 'b' },
+ { "debug", no_argument, NULL, 'D' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, OPT_VERSION },
+ { "debug-file", required_argument, NULL, OPT_DEBUG_FILE },
+ { "file-prefix", required_argument, NULL, 0 },
+ { "lcore", required_argument, NULL, 0 },
+ { NULL, 0, NULL, 0 },
+ };
+ int option_index, c;
+
+ while ((c = getopt_long(argc, argv, "hD4p:b:N:",
+ long_options, &option_index)) != -1) {
+ switch (c) {
+ case 'p': {
+ unsigned long u = strtoul(optarg, NULL, 0);
+
+ if (u == 0 || u > UINT16_MAX)
+ rte_exit(EXIT_FAILURE, "Invalid port: %s\n", optarg);
+ listen_port = (uint16_t)u;
+ break;
+ }
+ case 'b':
+ bind_arg = optarg;
+ break;
+ case '4':
+ ipv4_only = true;
+ break;
+ case 'N':
+ ring_size = strtoul(optarg, NULL, 0);
+ if (ring_size < 64)
+ rte_exit(EXIT_FAILURE, "Ring size too small\n");
+ break;
+ case 'D':
+ debug_log = true;
+ break;
+ case 'h':
+ usage(stdout, argv[0]);
+ exit(0);
+ case OPT_VERSION:
+ print_version();
+ exit(0);
+ case OPT_DEBUG_FILE:
+ debug_file = optarg;
+ break;
+ case 0: {
+ const char *longopt = long_options[option_index].name;
+
+ if (!strcmp(longopt, "lcore")) {
+ lcore_arg = optarg;
+ break;
+ } else if (!strcmp(longopt, "file-prefix")) {
+ file_prefix = optarg;
+ break;
+ }
+ }
+ /* fallthrough */
+ default:
+ usage(stderr, argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Resolve the bind address now that -4 has been seen. */
+ parse_bind_addr(bind_arg ? bind_arg : "127.0.0.1",
+ ipv4_only ? AF_INET : AF_UNSPEC);
+}
+
+/*
+ * Periodic check that the DPDK primary process is still alive.
+ * If it dies our shared-memory state (rings, mempools, pdump) becomes
+ * unsafe to touch, so we set quit_signal and let the main loop tear
+ * down cleanly on its next iteration. The callback runs on the EAL
+ * interrupt thread; quit_signal is atomic so the read in the main
+ * loop is well-defined.
+ */
+static void
+monitor_primary(void *arg __rte_unused)
+{
+ if (rte_atomic_load_explicit(&quit_signal, rte_memory_order_relaxed))
+ return;
+
+ if (rte_eal_primary_proc_alive(NULL)) {
+ rte_eal_alarm_set(PRIMARY_MONITOR_INTERVAL_US, monitor_primary, NULL);
+ return;
+ }
+
+ RTE_LOG(NOTICE, RPCAPD, "primary process exited, shutting down\n");
+ rte_atomic_store_explicit(&quit_signal, true, rte_memory_order_relaxed);
+}
+
+static void
+enable_primary_monitor(void)
+{
+ if (rte_eal_alarm_set(PRIMARY_MONITOR_INTERVAL_US, monitor_primary, NULL) < 0)
+ RTE_LOG(WARNING, RPCAPD, "failed to install primary process monitor\n");
+}
+
+static void
+disable_primary_monitor(void)
+{
+ rte_eal_alarm_cancel(monitor_primary, NULL);
+}
+
+/*
+ * Bring up EAL as a secondary process so that pdump can attach to a
+ * running primary DPDK application. Mirrors dumpcap's approach: the
+ * RPCAP user sees a small set of options (port, ring size) rather
+ * than the full DPDK EAL command line.
+ */
+static int
+dpdk_init(void)
+{
+ static const char * const args[] = {
+ "rpcapd",
+ "--proc-type", "secondary",
+ "--log-level", "info", /* EAL stays quiet */
+ };
+ int eal_argc = RTE_DIM(args);
+ rte_cpuset_t cpuset = { };
+ char **eal_argv;
+ unsigned int i;
+
+ if (file_prefix != NULL)
+ eal_argc += 2;
+
+ if (lcore_arg != NULL)
+ eal_argc += 2;
+
+ eal_argv = calloc(eal_argc + 1, sizeof(char *));
+ if (eal_argv == NULL)
+ return -1;
+
+ for (i = 0; i < RTE_DIM(args); i++) {
+ eal_argv[i] = strdup(args[i]);
+ if (eal_argv[i] == NULL)
+ return -1;
+ }
+
+ if (file_prefix != NULL && *file_prefix != '\0') {
+ eal_argv[i++] = strdup("--file-prefix");
+ eal_argv[i++] = strdup(file_prefix);
+ if (eal_argv[i - 1] == NULL || eal_argv[i - 2] == NULL)
+ return -1;
+ }
+
+ if (lcore_arg != NULL) {
+ eal_argv[i++] = strdup("--lcores");
+ eal_argv[i++] = strdup(lcore_arg);
+ if (eal_argv[i - 1] == NULL || eal_argv[i - 2] == NULL)
+ return -1;
+ }
+ eal_argc = i;
+
+ /*
+ * Need to get the original cpuset, before EAL init changes
+ * the affinity of this thread (main lcore).
+ */
+ if (lcore_arg == NULL &&
+ rte_thread_get_affinity_by_id(rte_thread_self(), &cpuset) != 0)
+ rte_panic("rte_thread_getaffinity failed\n");
+
+ if (rte_eal_init(eal_argc, eal_argv) < 0)
+ rte_exit(EXIT_FAILURE, "EAL init failed: is the primary process running?\n");
+
+ /*
+ * If no lcore argument was specified,
+ * then run this program as a normal process
+ * which can be scheduled on any non-isolated CPU.
+ */
+ if (lcore_arg == NULL &&
+ rte_thread_set_affinity_by_id(rte_thread_self(), &cpuset) != 0)
+ RTE_LOG(INFO, RPCAPD,
+ "Can not restore original CPU affinity\n");
+
+ if (rte_pdump_init() < 0)
+ rte_exit(EXIT_FAILURE, "rte_pdump_init failed\n");
+
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct sigaction action = {
+ .sa_handler = signal_handler,
+ };
+ int srv_fd;
+
+ parse_opts(argc, argv);
+
+ /*
+ * Redirect log output before EAL init so EAL's own messages are
+ * captured too. The FILE handle is intentionally never closed:
+ * the kernel reclaims it at process exit.
+ */
+ if (debug_file != NULL) {
+ FILE *fp = fopen(debug_file, "a");
+
+ if (fp == NULL)
+ rte_exit(EXIT_FAILURE, "Cannot open debug file '%s': %s\n",
+ debug_file, strerror(errno));
+ setvbuf(fp, NULL, _IOLBF, 0);
+ rte_openlog_stream(fp);
+ }
+
+ if (dpdk_init() < 0)
+ rte_exit(EXIT_FAILURE, "EAL init failure\n");
+
+ if (debug_log)
+ rte_log_set_level(rpcapd_logtype, RTE_LOG_DEBUG);
+
+ if (rte_eth_dev_count_avail() == 0)
+ rte_exit(EXIT_FAILURE, "No Ethernet ports found\n");
+
+ sigaction(SIGTERM, &action, NULL);
+ sigaction(SIGINT, &action, NULL);
+
+ /* If peer closes, this detected in next recv() */
+ signal(SIGPIPE, SIG_IGN);
+
+ srv_fd = open_listen_socket(listen_port);
+
+ enable_primary_monitor();
+
+ while (!rte_atomic_load_explicit(&quit_signal, rte_memory_order_relaxed)) {
+ int cfd = accept(srv_fd, NULL, NULL);
+ if (cfd < 0) {
+ if (errno == EINTR)
+ continue;
+ RTE_LOG(ERR, RPCAPD, "accept: %s\n", strerror(errno));
+ break;
+ }
+ handle_client(cfd);
+ }
+
+ disable_primary_monitor();
+ RTE_LOG(INFO, RPCAPD, "shutting down\n");
+ close(srv_fd);
+ rte_pdump_uninit();
+ return rte_eal_cleanup() ? EXIT_FAILURE : 0;
+}
diff --git a/examples/rpcapd/meson.build b/examples/rpcapd/meson.build
new file mode 100644
index 0000000000..5eb1a8487c
--- /dev/null
+++ b/examples/rpcapd/meson.build
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+if is_windows
+ build = false
+ reason = 'not supported on Windows'
+ subdir_done()
+endif
+
+sources = files('main.c')
+deps += ['ethdev', 'pdump']
diff --git a/examples/rpcapd/rpcap-protocol.h b/examples/rpcapd/rpcap-protocol.h
new file mode 100644
index 0000000000..46d87928aa
--- /dev/null
+++ b/examples/rpcapd/rpcap-protocol.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ *
+ * On-the-wire RPCAP protocol definitions, transcribed from libpcap's
+ * rpcap-protocol.h. See:
+ * https://github.com/the-tcpdump-group/libpcap/blob/master/rpcap-protocol.h
+ *
+ * Only the subset needed by the DPDK rpcd POC is included here. All
+ * multi-byte fields in the structures below are big-endian on the wire.
+ */
+
+#ifndef _RPCAP_PROTOCOL_H_
+#define _RPCAP_PROTOCOL_H_
+
+#include <stdint.h>
+
+#define RPCAP_VERSION 0
+#define RPCAP_DEFAULT_NETPORT 2002
+
+/* Message types */
+#define RPCAP_MSG_ERROR 0x01
+#define RPCAP_MSG_FINDALLIF_REQ 0x02
+#define RPCAP_MSG_OPEN_REQ 0x03
+#define RPCAP_MSG_STARTCAP_REQ 0x04
+#define RPCAP_MSG_UPDATEFILTER_REQ 0x05
+#define RPCAP_MSG_CLOSE 0x06
+#define RPCAP_MSG_PACKET 0x07
+#define RPCAP_MSG_AUTH_REQ 0x08
+#define RPCAP_MSG_STATS_REQ 0x09
+#define RPCAP_MSG_ENDCAP_REQ 0x0a
+#define RPCAP_MSG_IS_REPLY 0x80
+
+#define RPCAP_MSG_FINDALLIF_REPLY (RPCAP_MSG_FINDALLIF_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_OPEN_REPLY (RPCAP_MSG_OPEN_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_STARTCAP_REPLY (RPCAP_MSG_STARTCAP_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_UPDATEFILTER_REPLY (RPCAP_MSG_UPDATEFILTER_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_AUTH_REPLY (RPCAP_MSG_AUTH_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_ENDCAP_REPLY (RPCAP_MSG_ENDCAP_REQ | RPCAP_MSG_IS_REPLY)
+#define RPCAP_MSG_STATS_REPLY (RPCAP_MSG_STATS_REQ | RPCAP_MSG_IS_REPLY)
+
+/* Subset of pcap interface flags (pcap.h) */
+#define PCAP_IF_UP 0x00000002
+#define PCAP_IF_RUNNING 0x00000004
+
+/* DLT_EN10MB - ethernet, the only link type we report */
+#define DLT_EN10MB 1
+
+struct rpcap_header {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t value;
+ uint32_t plen;
+};
+
+struct rpcap_findalldevs_if {
+ uint16_t namelen;
+ uint16_t desclen;
+ uint32_t flags;
+ uint16_t naddr;
+ uint16_t dummy;
+};
+
+struct rpcap_openreply {
+ int32_t linktype;
+ int32_t tzoff;
+};
+
+struct rpcap_startcapreq {
+ uint32_t snaplen;
+ uint32_t read_timeout;
+ uint16_t flags;
+ uint16_t portdata;
+};
+
+struct rpcap_startcapreply {
+ int32_t bufsize;
+ uint16_t portdata;
+ uint16_t dummy;
+};
+
+struct rpcap_stats {
+ uint32_t ifrecv;
+ uint32_t ifdrop;
+ uint32_t krnldrop;
+ uint32_t svrcapt;
+};
+
+struct rpcap_pkthdr {
+ uint32_t timestamp_sec;
+ uint32_t timestamp_usec;
+ uint32_t caplen;
+ uint32_t len;
+ uint32_t npkt;
+};
+
+#endif /* _RPCAP_PROTOCOL_H_ */
--
2.53.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-08 21:08 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 21:07 [PATCH] examples/rpcapd: demo version of packet capture daemon Stephen Hemminger
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.