[PATCH v6 1/1] pcapng: add user-supplied timestamp support
Dawid Wesierski
dawid.wesierski at intel.com
Mon Jun 29 11:37:33 CEST 2026
Add a timestamp parameter to rte_pcapng_copy() so that callers with a
hardware PTP or pre-captured timestamp can inject an exact epoch-ns value
directly into the packet record.
Timestamp handling:
- ts != 0: caller-supplied nanoseconds since the Unix epoch, stored as-is.
- ts == 0: TSC captured at copy time with bit 63 set as a sentinel.
rte_pcapng_write_packets() detects the sentinel and converts the TSC to
epoch ns using the file's calibrated clock. The TSC will not reach
bit 63 for centuries, and epoch-ns values stay below bit 63 until 2554,
so the bit is safe to use as a disambiguation flag.
Adding the parameter changes the ABI, so rte_pcapng_copy() is versioned.
rte_pcapng_tsc_to_ns() is added as a new experimental helper. It exposes the
same calibrated, drift-compensated, divide-free TSC-to-epoch-ns conversion
used internally by rte_pcapng_write_packets(), allows callers to convert
a TSC captured at packet arrival time before passing it to rte_pcapng_copy().
Signed-off-by: Marek Kasiewicz <marek.kasiewicz at intel.com>
Signed-off-by: Dawid Wesierski <dawid.wesierski at intel.com>
---
.mailmap | 2 +
app/test/test_pcapng.c | 112 +++++++++++++++++++++++++++++++++++++++-
lib/graph/graph_pcap.c | 2 +-
lib/pcapng/meson.build | 1 +
lib/pcapng/rte_pcapng.c | 59 +++++++++++++++++----
lib/pcapng/rte_pcapng.h | 22 +++++++-
lib/pdump/rte_pdump.c | 2 +-
7 files changed, 185 insertions(+), 15 deletions(-)
diff --git a/.mailmap b/.mailmap
index 4001e5fb0e..a7d97a631e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -366,6 +366,7 @@ David Zeng <zengxhsh at cn.ibm.com>
Davide Caratti <dcaratti at redhat.com>
Dawid Gorecki <dgr at semihalf.com>
Dawid Jurczak <dawid_jurek at vp.pl>
+Dawid Wesierski <dawid.wesierski at intel.com> Wesierski, Dawid <dawid.wesierski at intel.com>
Dawid Zielinski <dawid.zielinski at intel.com>
Dawid Łukwiński <dawid.lukwinski at intel.com>
Daxue Gao <daxuex.gao at intel.com>
@@ -1014,6 +1015,7 @@ Marcin Wilk <marcin.wilk at caviumnetworks.com>
Marcin Wojtas <mw at semihalf.com>
Marcin Zapolski <marcinx.a.zapolski at intel.com>
Marco Varlese <mvarlese at suse.de>
+Marek Kasiewicz <marek.kasiewicz at intel.com>
Marek Mical <marekx.mical at intel.com>
Marek Zalfresso-jundzillo <marekx.zalfresso-jundzillo at intel.com>
Maria Lingemark <maria.lingemark at ericsson.com>
diff --git a/app/test/test_pcapng.c b/app/test/test_pcapng.c
index 298bcbd31f..1dd2ae6cdd 100644
--- a/app/test/test_pcapng.c
+++ b/app/test/test_pcapng.c
@@ -302,7 +302,7 @@ fill_pcapng_file(rte_pcapng_t *pcapng)
mbuf1_resize(&mbfs, rte_rand_max(MAX_DATA_SIZE));
mc = rte_pcapng_copy(port_id, 0, orig, mp, rte_pktmbuf_pkt_len(orig),
- RTE_PCAPNG_DIRECTION_IN, comment);
+ RTE_PCAPNG_DIRECTION_IN, comment, 0);
if (mc == NULL) {
printf("Cannot copy packet\n");
return -1;
@@ -612,7 +612,7 @@ test_write_before_open(void)
for (i = 0; i < (int)count; i++) {
clones[i] = rte_pcapng_copy(port_id, 0, &mbfs.mb[0], mp,
rte_pktmbuf_pkt_len(&mbfs.mb[0]),
- RTE_PCAPNG_DIRECTION_IN, NULL);
+ RTE_PCAPNG_DIRECTION_IN, NULL, 0);
if (clones[i] == NULL) {
fprintf(stderr, "Cannot copy packet before open\n");
rte_pktmbuf_free_bulk(clones, i);
@@ -672,6 +672,113 @@ test_write_before_open(void)
return -1;
}
+static int
+test_pcapng_timestamp(void)
+{
+ char file_name[PATH_MAX] = "/tmp/pcapng_test_XXXXXX.pcapng";
+ rte_pcapng_t *pcapng = NULL;
+ int ret, tmp_fd;
+ struct dummy_mbuf mbfs;
+ struct rte_mbuf *orig, *mc;
+ uint64_t now_ns, tsc, ns_from_tsc, pcap_ts;
+
+ tmp_fd = mkstemps(file_name, strlen(".pcapng"));
+ if (tmp_fd == -1) {
+ perror("mkstemps() failure");
+ goto fail;
+ }
+
+ pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_ts_test", NULL);
+ if (pcapng == NULL) {
+ printf("rte_pcapng_fdopen failed\n");
+ close(tmp_fd);
+ goto fail;
+ }
+
+ ret = rte_pcapng_add_interface(pcapng, port_id, DLT_EN10MB, NULL, NULL, NULL);
+ if (ret < 0) {
+ printf("can not add port %u\n", port_id);
+ goto fail;
+ }
+
+ /* Test 1: rte_pcapng_tsc_to_ns */
+ tsc = rte_get_tsc_cycles();
+ now_ns = current_timestamp();
+ ns_from_tsc = rte_pcapng_tsc_to_ns(pcapng, tsc);
+
+ /* Check if TSC-derived NS is reasonably close to wall clock NS (within 100ms) */
+ if (ns_from_tsc > now_ns + 100000000 || ns_from_tsc < now_ns - 100000000) {
+ printf("TSC to NS conversion failed: tsc=%"PRIu64
+ " ns_from_tsc=%"PRIu64" now_ns=%"PRIu64"\n",
+ tsc, ns_from_tsc, now_ns);
+ goto fail;
+ }
+
+ /* Test 2: rte_pcapng_copy with explicit timestamp */
+ mbuf1_prepare(&mbfs);
+ orig = &mbfs.mb[0];
+ pcap_ts = now_ns + 1000000000; /* 1 second in future to be distinct */
+
+ mc = rte_pcapng_copy(port_id, 0, orig, mp, rte_pktmbuf_pkt_len(orig),
+ RTE_PCAPNG_DIRECTION_IN, "custom_ts", pcap_ts);
+ if (mc == NULL) {
+ printf("rte_pcapng_copy failed\n");
+ goto fail;
+ }
+
+ /* Write it */
+ ret = rte_pcapng_write_packets(pcapng, &mc, 1);
+ rte_pktmbuf_free(mc);
+ if (ret <= 0) {
+ printf("Write of custom timestamp packet failed\n");
+ goto fail;
+ }
+
+ rte_pcapng_close(pcapng);
+
+ /* Validate the file using libpcap */
+ /* We expect 1 packet with timestamp exactly pcap_ts */
+ {
+ char errbuf[PCAP_ERRBUF_SIZE];
+ pcap_t *pcap;
+ struct pcap_pkthdr h;
+ const u_char *bytes;
+ uint64_t ns;
+
+ pcap = pcap_open_offline_with_tstamp_precision(file_name,
+ PCAP_TSTAMP_PRECISION_NANO,
+ errbuf);
+ if (pcap == NULL) {
+ printf("pcap_open_offline failed: %s\n", errbuf);
+ goto fail;
+ }
+
+ bytes = pcap_next(pcap, &h);
+ if (bytes == NULL) {
+ printf("No packets in file\n");
+ pcap_close(pcap);
+ goto fail;
+ }
+
+ ns = (uint64_t)h.ts.tv_sec * NS_PER_S + h.ts.tv_usec;
+ if (ns != pcap_ts) {
+ printf("Timestamp mismatch: expected %"PRIu64" got %"PRIu64"\n",
+ pcap_ts, ns);
+ pcap_close(pcap);
+ goto fail;
+ }
+ pcap_close(pcap);
+ }
+
+ remove(file_name);
+ return 0;
+
+fail:
+ if (pcapng)
+ rte_pcapng_close(pcapng);
+ return -1;
+}
+
static void
test_cleanup(void)
{
@@ -688,6 +795,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_pcapng_timestamp),
TEST_CASES_END()
}
};
diff --git a/lib/graph/graph_pcap.c b/lib/graph/graph_pcap.c
index 78859ec2d4..4d907c78aa 100644
--- a/lib/graph/graph_pcap.c
+++ b/lib/graph/graph_pcap.c
@@ -216,7 +216,7 @@ graph_pcap_dispatch(struct rte_graph *graph,
struct rte_mbuf *mc;
mbuf = (struct rte_mbuf *)objs[i];
- mc = rte_pcapng_copy(mbuf->port, 0, mbuf, pkt_mp, mbuf->pkt_len, 0, comment);
+ mc = rte_pcapng_copy(mbuf->port, 0, mbuf, pkt_mp, mbuf->pkt_len, 0, comment, 0);
if (mc == NULL)
break;
diff --git a/lib/pcapng/meson.build b/lib/pcapng/meson.build
index 4549925d41..3cfaddbd6e 100644
--- a/lib/pcapng/meson.build
+++ b/lib/pcapng/meson.build
@@ -3,5 +3,6 @@
sources = files('rte_pcapng.c')
headers = files('rte_pcapng.h')
+use_function_versioning = true
deps += ['ethdev']
diff --git a/lib/pcapng/rte_pcapng.c b/lib/pcapng/rte_pcapng.c
index b5d1026891..b8ebb0c288 100644
--- a/lib/pcapng/rte_pcapng.c
+++ b/lib/pcapng/rte_pcapng.c
@@ -37,6 +37,9 @@
/* upper bound for strings in pcapng option data */
#define PCAPNG_STR_MAX UINT16_MAX
+/* Flag to indicate timestamp is in TSC cycles (bit 63) */
+#define PCAPNG_TSC_FLAG (1ULL << 63)
+
/*
* Converter from TSC values to nanoseconds since Unix epoch.
* Uses reciprocal multiply to avoid runtime division.
@@ -480,6 +483,13 @@ rte_pcapng_mbuf_size(uint32_t length)
+ sizeof(uint32_t); /* length */
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pcapng_tsc_to_ns, 26.07)
+uint64_t
+rte_pcapng_tsc_to_ns(const rte_pcapng_t *self, uint64_t tsc)
+{
+ return tsc_to_ns_epoch(&self->clock, tsc);
+}
+
/* More generalized version rte_vlan_insert() */
static int
pcapng_vlan_insert(struct rte_mbuf *m, uint16_t ether_type, uint16_t tci)
@@ -546,19 +556,18 @@ pcapng_vlan_insert(struct rte_mbuf *m, uint16_t ether_type, uint16_t tci)
*/
/* Make a copy of original mbuf with pcapng header and options */
-RTE_EXPORT_SYMBOL(rte_pcapng_copy)
-struct rte_mbuf *
-rte_pcapng_copy(uint16_t port_id, uint32_t queue,
+RTE_DEFAULT_SYMBOL(26, struct rte_mbuf *, rte_pcapng_copy,
+ (uint16_t port_id, uint32_t queue,
const struct rte_mbuf *md,
struct rte_mempool *mp,
uint32_t length,
enum rte_pcapng_direction direction,
- const char *comment)
+ const char *comment,
+ uint64_t timestamp))
{
struct pcapng_enhance_packet_block *epb;
uint32_t orig_len, pkt_len, padding, flags;
struct pcapng_option *opt;
- uint64_t timestamp;
uint16_t optlen;
struct rte_mbuf *mc;
bool rss_hash;
@@ -690,8 +699,20 @@ rte_pcapng_copy(uint16_t port_id, uint32_t queue,
/* Interface index is filled in later during write */
mc->port = port_id;
- /* Put timestamp in cycles here - adjust in packet write */
- timestamp = rte_get_tsc_cycles();
+ /*
+ * Use caller-provided timestamp.
+ * If none provided, use current TSC and set flag.
+ * Timestamps shouldn't naturally have the PCAPNG_TSC_FLAG set for
+ * centuries, so if it is set, treat it as an error.
+ */
+ if (timestamp != 0 && (timestamp & PCAPNG_TSC_FLAG)) {
+ rte_errno = EINVAL;
+ goto fail;
+ }
+
+ if (timestamp == 0)
+ timestamp = rte_get_tsc_cycles() | PCAPNG_TSC_FLAG;
+
epb->timestamp_hi = timestamp >> 32;
epb->timestamp_lo = (uint32_t)timestamp;
epb->capture_length = pkt_len;
@@ -707,6 +728,22 @@ rte_pcapng_copy(uint16_t port_id, uint32_t queue,
return NULL;
}
+/*
+ * Original ABI: no caller-supplied timestamp. Capture the current TSC
+ * (default path) and forward to the timestamped implementation.
+ */
+RTE_VERSION_SYMBOL(25, struct rte_mbuf *, rte_pcapng_copy,
+ (uint16_t port_id, uint32_t queue,
+ const struct rte_mbuf *md,
+ struct rte_mempool *mp,
+ uint32_t length,
+ enum rte_pcapng_direction direction,
+ const char *comment))
+{
+ return rte_pcapng_copy(port_id, queue, md, mp, length, direction,
+ comment, 0);
+}
+
/* Write pre-formatted packets to file. */
RTE_EXPORT_SYMBOL(rte_pcapng_write_packets)
ssize_t
@@ -743,9 +780,11 @@ rte_pcapng_write_packets(rte_pcapng_t *self,
*/
cycles = (uint64_t)epb->timestamp_hi << 32;
cycles += epb->timestamp_lo;
- timestamp = tsc_to_ns_epoch(&self->clock, cycles);
- epb->timestamp_hi = timestamp >> 32;
- epb->timestamp_lo = (uint32_t)timestamp;
+ if (cycles & PCAPNG_TSC_FLAG) {
+ timestamp = tsc_to_ns_epoch(&self->clock, cycles & ~PCAPNG_TSC_FLAG);
+ epb->timestamp_hi = timestamp >> 32;
+ epb->timestamp_lo = (uint32_t)timestamp;
+ }
/*
* Handle case of highly fragmented and large burst size
diff --git a/lib/pcapng/rte_pcapng.h b/lib/pcapng/rte_pcapng.h
index d8d328f710..97f83c5a88 100644
--- a/lib/pcapng/rte_pcapng.h
+++ b/lib/pcapng/rte_pcapng.h
@@ -129,6 +129,9 @@ enum rte_pcapng_direction {
* @param comment
* Optional per packet comment.
* Truncated to UINT16_MAX characters.
+ * @param timestamp
+ * Nanoseconds since the Unix epoch. If zero, TSC is captured and
+ * converted at write time.
*
* @return
* - The pointer to the new mbuf formatted for pcapng_write
@@ -138,7 +141,24 @@ struct rte_mbuf *
rte_pcapng_copy(uint16_t port_id, uint32_t queue,
const struct rte_mbuf *m, struct rte_mempool *mp,
uint32_t length,
- enum rte_pcapng_direction direction, const char *comment);
+ enum rte_pcapng_direction direction,
+ const char *comment, uint64_t timestamp);
+
+/**
+ * Convert a TSC value to nanoseconds since the Unix epoch.
+ *
+ * Uses the calibrated clock of the capture file.
+ *
+ * @param self
+ * The handle to the packet capture file
+ * @param tsc
+ * The TSC value to convert
+ * @return
+ * Nanoseconds since Unix epoch
+ */
+__rte_experimental
+uint64_t
+rte_pcapng_tsc_to_ns(const rte_pcapng_t *self, uint64_t tsc);
/**
diff --git a/lib/pdump/rte_pdump.c b/lib/pdump/rte_pdump.c
index ac94efe7ff..a85a95a808 100644
--- a/lib/pdump/rte_pdump.c
+++ b/lib/pdump/rte_pdump.c
@@ -176,7 +176,7 @@ pdump_copy_burst(uint16_t port_id, uint16_t queue_id,
*/
if (cbs->ver == V2)
p = rte_pcapng_copy(port_id, queue_id, pkts[i], mp, cbs->snaplen,
- direction, NULL);
+ direction, NULL, 0);
else
p = rte_pktmbuf_copy(pkts[i], mp, 0, cbs->snaplen);
--
2.47.3
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
More information about the dev
mailing list