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 <[email protected]>
---
 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

Reply via email to