From: Stephen Hemminger <stephen@networkplumber.org>
To: dev@dpdk.org
Cc: Stephen Hemminger <stephen@networkplumber.org>,
Reshma Pattan <reshma.pattan@intel.com>
Subject: [PATCH v4 1/4] pcapng: add API to read back capture mbuf header
Date: Wed, 30 Sep 2026 19:35:26 -0700 [thread overview]
Message-ID: <20261001025853.319860-2-stephen@networkplumber.org> (raw)
In-Reply-To: <20261001025853.319860-1-stephen@networkplumber.org>
An mbuf from rte_pcapng_copy() starts with an enhanced packet block
holding the capture time, the length before truncation and the port.
The only way to get at that was to write the mbuf to a file, which
is no use to something forwarding captured packets elsewhere.
Add rte_pcapng_pkt_info() to decode that header in place, and check
it is well formed before trusting the lengths in it.
The capture time is reported as the raw TSC value: there is no
capture file here to take a reference point from, so converting it
to a time of day is left to the caller.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/test_pcapng.c | 133 +++++++++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 5 +
lib/pcapng/rte_pcapng.c | 40 ++++++++
lib/pcapng/rte_pcapng.h | 46 +++++++++
4 files changed, 224 insertions(+)
diff --git a/app/test/test_pcapng.c b/app/test/test_pcapng.c
index d14ea84f0d..cb36ea1d54 100644
--- a/app/test/test_pcapng.c
+++ b/app/test/test_pcapng.c
@@ -672,6 +672,138 @@ test_write_before_open(void)
return -1;
}
+/*
+ * Check that rte_pcapng_pkt_info() reads back what rte_pcapng_copy()
+ * recorded. The length before truncation and the capture time are
+ * only in the block header, so this is the only way a consumer that
+ * does not write a file can get at them.
+ */
+static int
+test_pkt_info(void)
+{
+ struct dummy_mbuf mbfs;
+ struct rte_mbuf *mc;
+ struct rte_pcapng_pkt pkt;
+ uint32_t pkt_len, snaplen, saved;
+ uint64_t before, after;
+ const uint8_t *data;
+ int ret;
+
+ mbuf1_prepare(&mbfs);
+ mbuf1_resize(&mbfs, 512);
+ pkt_len = rte_pktmbuf_pkt_len(&mbfs.mb[0]);
+
+ /* An untruncated copy reports the length it came in with. */
+ before = rte_get_tsc_cycles();
+ mc = rte_pcapng_copy(port_id, 0, &mbfs.mb[0], mp, pkt_len,
+ RTE_PCAPNG_DIRECTION_IN, NULL);
+ TEST_ASSERT(mc != NULL, "rte_pcapng_copy failed");
+ after = rte_get_tsc_cycles();
+
+ ret = rte_pcapng_pkt_info(mc, &pkt);
+ TEST_ASSERT(ret == 0, "rte_pcapng_pkt_info failed: %d", ret);
+
+ TEST_ASSERT(pkt.original_len == pkt_len,
+ "original_len is %u, expected %u", pkt.original_len, pkt_len);
+ TEST_ASSERT(pkt.captured_len == pkt_len,
+ "captured_len is %u, expected %u", pkt.captured_len, pkt_len);
+ TEST_ASSERT(pkt.port == port_id,
+ "port is %u, expected %u", pkt.port, port_id);
+
+ /* The copy was made between the two readings, so the recorded
+ * cycle count has to fall between them.
+ */
+ TEST_ASSERT(pkt.cycles >= before && pkt.cycles <= after,
+ "cycles %"PRIu64" is outside [%"PRIu64", %"PRIu64"]",
+ pkt.cycles, before, after);
+
+ /* data_offset points at the packet itself, not the block header. */
+ data = rte_pktmbuf_mtod_offset(mc, const uint8_t *, pkt.data_offset);
+ TEST_ASSERT(memcmp(data, rte_pktmbuf_mtod(&mbfs.mb[0], const void *),
+ rte_pktmbuf_data_len(&mbfs.mb[0])) == 0,
+ "packet data is not at data_offset");
+
+ /* A corrupt block is rejected rather than believed. */
+ {
+ struct pcapng_test_epb {
+ uint32_t block_type;
+ uint32_t block_length;
+ } *epb = rte_pktmbuf_mtod(mc, struct pcapng_test_epb *);
+
+ saved = epb->block_type;
+ epb->block_type = ~saved;
+ TEST_ASSERT(rte_pcapng_pkt_info(mc, &pkt) == -EINVAL,
+ "bad block_type was accepted");
+ epb->block_type = saved;
+
+ saved = epb->block_length;
+ epb->block_length = saved + 1;
+ TEST_ASSERT(rte_pcapng_pkt_info(mc, &pkt) == -EINVAL,
+ "bad block_length was accepted");
+ epb->block_length = saved;
+
+ /* and is fine again once put back */
+ TEST_ASSERT(rte_pcapng_pkt_info(mc, &pkt) == 0,
+ "restored block was rejected");
+ }
+
+ TEST_ASSERT(rte_pcapng_pkt_info(NULL, &pkt) == -EINVAL,
+ "NULL mbuf was accepted");
+ TEST_ASSERT(rte_pcapng_pkt_info(mc, NULL) == -EINVAL,
+ "NULL result was accepted");
+
+ rte_pktmbuf_free(mc);
+
+ /*
+ * Truncated copy. This is the case that cannot be recovered
+ * from the mbuf alone: captured_len shrinks to the snaplen
+ * while original_len still describes the packet on the wire.
+ */
+ snaplen = pkt_len / 2;
+ mc = rte_pcapng_copy(port_id, 0, &mbfs.mb[0], mp, snaplen,
+ RTE_PCAPNG_DIRECTION_IN, NULL);
+ TEST_ASSERT(mc != NULL, "truncated rte_pcapng_copy failed");
+
+ ret = rte_pcapng_pkt_info(mc, &pkt);
+ TEST_ASSERT(ret == 0, "rte_pcapng_pkt_info failed on truncated: %d", ret);
+
+ TEST_ASSERT(pkt.captured_len == snaplen,
+ "captured_len is %u, expected %u", pkt.captured_len, snaplen);
+ TEST_ASSERT(pkt.original_len == pkt_len,
+ "original_len is %u, expected %u, truncation lost it",
+ pkt.original_len, pkt_len);
+
+ rte_pktmbuf_free(mc);
+
+ /*
+ * A stripped VLAN tag is put back by the copy, but is not
+ * counted in the length reported by the hardware. So the
+ * captured packet is larger than the original, and a consumer
+ * has to cope with that rather than assume it cannot happen.
+ */
+ mbfs.mb[0].ol_flags |= RTE_MBUF_F_RX_VLAN_STRIPPED;
+ mbfs.mb[0].vlan_tci = 42;
+
+ mc = rte_pcapng_copy(port_id, 0, &mbfs.mb[0], mp, pkt_len,
+ RTE_PCAPNG_DIRECTION_IN, NULL);
+ TEST_ASSERT(mc != NULL, "VLAN rte_pcapng_copy failed");
+
+ ret = rte_pcapng_pkt_info(mc, &pkt);
+ TEST_ASSERT(ret == 0, "rte_pcapng_pkt_info failed on VLAN: %d", ret);
+
+ TEST_ASSERT(pkt.captured_len == pkt_len + sizeof(struct rte_vlan_hdr),
+ "captured_len is %u, expected %zu with the tag restored",
+ pkt.captured_len, pkt_len + sizeof(struct rte_vlan_hdr));
+ TEST_ASSERT(pkt.original_len == pkt_len,
+ "original_len is %u, expected %u", pkt.original_len, pkt_len);
+ TEST_ASSERT(pkt.captured_len > pkt.original_len,
+ "restored VLAN tag did not make the capture longer");
+
+ rte_pktmbuf_free(mc);
+
+ return 0;
+}
+
static void
test_cleanup(void)
{
@@ -688,6 +820,7 @@ unit_test_suite test_pcapng_suite = {
TEST_CASE(test_add_interface),
TEST_CASE(test_write_packets),
TEST_CASE(test_write_before_open),
+ TEST_CASE(test_pkt_info),
TEST_CASES_END()
}
};
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e027c7a27f..5b5a9f006e 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added pcapng API to read back a captured packet header.**
+
+ Added the experimental ``rte_pcapng_pkt_info()`` function to read back what
+ ``rte_pcapng_copy()`` records in a captured packet.
+
* **Added API to get CPU socket ID.**
Added the experimental ``rte_cpu_socket_id()`` function
diff --git a/lib/pcapng/rte_pcapng.c b/lib/pcapng/rte_pcapng.c
index b5d1026891..54a0aa1342 100644
--- a/lib/pcapng/rte_pcapng.c
+++ b/lib/pcapng/rte_pcapng.c
@@ -707,6 +707,46 @@ rte_pcapng_copy(uint16_t port_id, uint32_t queue,
return NULL;
}
+/* Read back the block header put there by rte_pcapng_copy() */
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pcapng_pkt_info, 26.11)
+int
+rte_pcapng_pkt_info(const struct rte_mbuf *m, struct rte_pcapng_pkt *pkt)
+{
+ const struct pcapng_enhance_packet_block *epb;
+ struct pcapng_enhance_packet_block ebuf;
+
+ if (unlikely(m == NULL || pkt == NULL))
+ return -EINVAL;
+
+ epb = rte_pktmbuf_read(m, 0, sizeof(*epb), &ebuf);
+ if (unlikely(epb == NULL))
+ return -EINVAL;
+
+ if (unlikely(epb->block_type != PCAPNG_ENHANCED_PACKET_BLOCK))
+ return -EINVAL;
+
+ /*
+ * rte_pcapng_copy() sets block_length to the whole mbuf length, and
+ * the packet data has to fit in what is left after the header.
+ */
+ if (unlikely(epb->block_length != rte_pktmbuf_pkt_len(m)))
+ return -EINVAL;
+
+ if (unlikely(epb->capture_length >
+ epb->block_length - sizeof(*epb)))
+ return -EINVAL;
+
+ pkt->cycles = (uint64_t)epb->timestamp_hi << 32;
+ pkt->cycles += epb->timestamp_lo;
+
+ pkt->captured_len = epb->capture_length;
+ pkt->original_len = epb->original_length;
+ pkt->data_offset = sizeof(*epb);
+ pkt->port = m->port;
+
+ return 0;
+}
+
/* Write pre-formatted packets to file. */
RTE_EXPORT_SYMBOL(rte_pcapng_write_packets)
ssize_t
diff --git a/lib/pcapng/rte_pcapng.h b/lib/pcapng/rte_pcapng.h
index d8d328f710..055075e921 100644
--- a/lib/pcapng/rte_pcapng.h
+++ b/lib/pcapng/rte_pcapng.h
@@ -22,6 +22,8 @@
#include <stdint.h>
#include <sys/types.h>
+#include <rte_compat.h>
+#include <rte_mbuf.h>
#include <rte_mempool.h>
#ifdef __cplusplus
@@ -140,6 +142,50 @@ rte_pcapng_copy(uint16_t port_id, uint32_t queue,
uint32_t length,
enum rte_pcapng_direction direction, const char *comment);
+/**
+ * Decoded header of an mbuf produced by rte_pcapng_copy().
+ *
+ * @warning
+ * @b EXPERIMENTAL: this structure may change without prior notice.
+ */
+struct rte_pcapng_pkt {
+ uint64_t cycles; /**< TSC value when the packet was captured */
+ uint32_t captured_len; /**< bytes of packet data present */
+ uint32_t original_len; /**< length of the packet on the wire */
+ uint32_t data_offset; /**< offset of packet data in the mbuf */
+ uint16_t port; /**< port recorded by rte_pcapng_copy() */
+};
+
+/**
+ * Extract info from mbuf created by rte_pcapng_copy().
+ *
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Only valid for packets created by rte_pcapng_copy().
+ * The mbuf is not modified.
+ * To reach the packet data, read *captured_len* bytes starting at *data_offset*.
+ *
+ * The capture time is reported as the raw TSC value recorded by
+ * rte_pcapng_copy(), since this has no capture file to take a reference
+ * point from. To turn it into a time of day, sample rte_get_tsc_cycles()
+ * and the system clock together once, then scale the difference by
+ * rte_get_tsc_hz().
+ *
+ * @param m
+ * An mbuf returned by rte_pcapng_copy().
+ * @param pkt
+ * Filled in on success.
+ * @return
+ * 0 on success, -EINVAL if the mbuf is not a well formed enhanced
+ * packet block.
+ *
+ * @note
+ * Length may vary from the original because rte_pcapng_copy() inserts VLAN.
+ */
+__rte_experimental
+int
+rte_pcapng_pkt_info(const struct rte_mbuf *m, struct rte_pcapng_pkt *pkt);
/**
* Determine optimum mbuf data size.
--
2.53.0
next prev parent reply other threads:[~2026-10-01 2:59 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 21:07 [PATCH] examples/rpcapd: demo version of packet capture daemon Stephen Hemminger
2026-09-20 18:59 ` [PATCH v2] " Stephen Hemminger
2026-09-21 9:51 ` Marat Khalili
2026-09-21 15:57 ` Stephen Hemminger
2026-09-21 15:58 ` Stephen Hemminger
2026-09-21 16:43 ` Marat Khalili
2026-09-21 17:31 ` Stephen Hemminger
2026-09-21 17:53 ` Marat Khalili
2026-09-21 16:17 ` Stephen Hemminger
2026-09-22 18:45 ` Stephen Hemminger
2026-09-22 21:31 ` [PATCH v3] " Stephen Hemminger
2026-09-28 16:18 ` Marat Khalili
2026-09-28 17:24 ` Stephen Hemminger
2026-10-01 2:35 ` [PATCH v4 0/4] add rpcap remote " Stephen Hemminger
2026-10-01 2:35 ` Stephen Hemminger [this message]
2026-10-01 2:35 ` [PATCH v4 2/4] app/rpcapd: remote pcap daemon Stephen Hemminger
2026-10-01 18:50 ` Marat Khalili
2026-10-01 2:35 ` [PATCH v4 3/4] app/rpcapd: add TLS support Stephen Hemminger
2026-10-01 2:35 ` [PATCH v4 4/4] app/rpcapd: add host list option Stephen Hemminger
2026-10-01 18:50 ` [PATCH v4 0/4] add rpcap remote capture daemon Marat Khalili
2026-10-01 23:00 ` Stephen Hemminger
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261001025853.319860-2-stephen@networkplumber.org \
--to=stephen@networkplumber.org \
--cc=dev@dpdk.org \
--cc=reshma.pattan@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.