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

