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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox