From: Patrick Robb <probb@iol.unh.edu>
To: Stephen Hemminger <stephen@networkplumber.org>
Cc: dev <dev@dpdk.org>
Subject: Re: [PATCH v11 09/14] ethdev: add port mirroring feature
Date: Fri, 15 Aug 2025 11:29:57 -0400 [thread overview]
Message-ID: <CAJvnSUAqYff4N5Z7M3GhAXUxRJutN16T-1-PhS7NtphhfHqsKQ@mail.gmail.com> (raw)
In-Reply-To: <20250808165843.39075-10-stephen@networkplumber.org>
[-- Attachment #1: Type: text/plain, Size: 51594 bytes --]
Sending a CI testing retest for this series because of a suspected false
failure on the vlan testsuite.
On Fri, Aug 8, 2025 at 12:59 PM Stephen Hemminger <
stephen@networkplumber.org> wrote:
> This adds new feature port mirroring to the ethdev layer.
> And standalone tests for those features.
>
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
> app/test/meson.build | 1 +
> app/test/test_ethdev_mirror.c | 325 ++++++++++++++++++++
> config/rte_config.h | 1 +
> lib/ethdev/ethdev_driver.h | 6 +
> lib/ethdev/ethdev_private.c | 58 +++-
> lib/ethdev/ethdev_private.h | 3 +
> lib/ethdev/ethdev_trace.h | 17 ++
> lib/ethdev/ethdev_trace_points.c | 6 +
> lib/ethdev/meson.build | 2 +
> lib/ethdev/rte_ethdev.c | 17 +-
> lib/ethdev/rte_ethdev.h | 23 +-
> lib/ethdev/rte_ethdev_core.h | 12 +-
> lib/ethdev/rte_mirror.c | 508 +++++++++++++++++++++++++++++++
> lib/ethdev/rte_mirror.h | 147 +++++++++
> 14 files changed, 1108 insertions(+), 18 deletions(-)
> create mode 100644 app/test/test_ethdev_mirror.c
> create mode 100644 lib/ethdev/rte_mirror.c
> create mode 100644 lib/ethdev/rte_mirror.h
>
> diff --git a/app/test/meson.build b/app/test/meson.build
> index f3ea3e2b74..87219b869e 100644
> --- a/app/test/meson.build
> +++ b/app/test/meson.build
> @@ -73,6 +73,7 @@ source_file_deps = {
> 'test_errno.c': [],
> 'test_ethdev_api.c': ['ethdev'],
> 'test_ethdev_link.c': ['ethdev'],
> + 'test_ethdev_mirror.c': ['net_ring', 'ethdev', 'bus_vdev'],
> 'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
> 'test_event_dma_adapter.c': ['dmadev', 'eventdev', 'bus_vdev'],
> 'test_event_eth_rx_adapter.c': ['ethdev', 'eventdev', 'bus_vdev'],
> diff --git a/app/test/test_ethdev_mirror.c b/app/test/test_ethdev_mirror.c
> new file mode 100644
> index 0000000000..bbff1ff6a7
> --- /dev/null
> +++ b/app/test/test_ethdev_mirror.c
> @@ -0,0 +1,325 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Stephen Hemminger <stephen@networkplumber.org>
> + */
> +#include "test.h"
> +
> +#include <stdio.h>
> +
> +#include <rte_eth_ring.h>
> +#include <rte_ethdev.h>
> +#include <rte_bus_vdev.h>
> +
> +#define SOCKET0 0
> +#define RING_SIZE 256
> +#define BURST_SZ 64
> +#define NB_MBUF 512
> +
> +static struct rte_mempool *mirror_pool;
> +static const uint32_t pkt_len = 200;
> +
> +static struct rte_ring *rxtx_ring;
> +static const char ring_dev[] = "net_ring0";
> +static uint16_t ring_port;
> +static const char null_dev[] = "net_null0";
> +static uint16_t null_port;
> +
> +static int
> +configure_port(uint16_t port, const char *name)
> +{
> + struct rte_eth_conf eth_conf = { 0 };
> +
> + if (rte_eth_dev_configure(port, 1, 1, ð_conf) < 0) {
> + fprintf(stderr, "Configure failed for port %u: %s\n",
> port, name);
> + return -1;
> + }
> +
> + /* only single queue */
> + if (rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL) < 0)
> {
> + fprintf(stderr, "TX queue setup failed port %u: %s\n",
> port, name);
> + return -1;
> + }
> +
> + if (rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL,
> mirror_pool) < 0) {
> + fprintf(stderr, "RX queue setup failed port %u: %s\n",
> port, name);
> + return -1;
> + }
> +
> + if (rte_eth_dev_start(port) < 0) {
> + fprintf(stderr, "Error starting port %u:%s\n", port, name);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static void
> +test_cleanup(void)
> +{
> + rte_ring_free(rxtx_ring);
> +
> + rte_vdev_uninit("net_ring");
> + rte_vdev_uninit("net_null");
> +
> + rte_mempool_free(mirror_pool);
> +}
> +
> +/* Make two virtual devices ring and null */
> +static int
> +test_setup(void)
> +{
> + char null_name[] = "net_null0";
> + int ret;
> +
> + /* ring must support multiple enqueue for mirror to work */
> + rxtx_ring = rte_ring_create("R0", RING_SIZE, SOCKET0,
> RING_F_MP_RTS_ENQ | RING_F_SC_DEQ);
> + if (rxtx_ring == NULL) {
> + fprintf(stderr, "rte_ring_create R0 failed\n");
> + goto fail;
> + }
> + ret = rte_eth_from_rings(ring_dev, &rxtx_ring, 1, &rxtx_ring, 1,
> SOCKET0);
> + if (ret < 0) {
> + fprintf(stderr, "rte_eth_from_rings failed\n");
> + goto fail;
> + }
> + ring_port = ret;
> +
> + /* Make a dummy null device to snoop on */
> + if (rte_vdev_init(null_dev, NULL) != 0) {
> + fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
> + goto fail;
> + }
> + if (rte_eth_dev_get_port_by_name(null_dev, &null_port) != 0) {
> + fprintf(stderr, "cannot find added vdev %s\n", null_name);
> + goto fail;
> + }
> +
> + mirror_pool = rte_pktmbuf_pool_create("mirror_pool", NB_MBUF, 32,
> + 0,
> RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
> + if (mirror_pool == NULL) {
> + fprintf(stderr, "rte_pktmbuf_pool_create failed\n");
> + goto fail;
> + }
> +
> + ret = configure_port(ring_port, ring_dev);
> + if (ret < 0)
> + goto fail;
> +
> + ret = configure_port(null_port, null_dev);
> + if (ret < 0)
> + goto fail;
> +
> + return 0;
> +fail:
> + test_cleanup();
> + return -1;
> +}
> +
> +/* Make sure mirror API checks args */
> +static int32_t
> +ethdev_mirror_api(void)
> +{
> + struct rte_eth_mirror_conf conf = {
> + .mp = mirror_pool,
> + .flags = RTE_ETH_MIRROR_DIRECTION_EGRESS,
> + };
> + struct rte_eth_mirror_stats stats;
> + uint16_t invalid_port = RTE_MAX_ETHPORTS;
> + int ret;
> +
> + conf.target = null_port;
> + ret = rte_eth_add_mirror(invalid_port, &conf);
> + TEST_ASSERT(ret != 0, "Created mirror from invalid port");
> +
> + conf.target = invalid_port;
> + ret = rte_eth_add_mirror(null_port, &conf);
> + TEST_ASSERT(ret != 0, "Created mirror to invalid port");
> +
> + conf.flags = 0;
> + conf.target = ring_port;
> + ret = rte_eth_add_mirror(null_port, &conf);
> + TEST_ASSERT(ret != 0, "Created mirror with invalid flags");
> +
> + conf.flags = RTE_ETH_MIRROR_DIRECTION_INGRESS;
> + conf.target = ring_port;
> + ret = rte_eth_add_mirror(ring_port, &conf);
> + TEST_ASSERT(ret != 0, "Created mirror to self");
> +
> + ret = rte_eth_mirror_stats_get(invalid_port, null_port, &stats);
> + TEST_ASSERT(ret != 0, "Able to gets mirror stats from invalid
> port");
> +
> + ret = rte_eth_mirror_stats_get(null_port, invalid_port, &stats);
> + TEST_ASSERT(ret != 0, "Able to gets mirror stats from invalid
> target");
> +
> + conf.target = null_port;
> + ret = rte_eth_add_mirror(ring_port, &conf);
> + TEST_ASSERT(ret == 0, "Could not create mirror from ring to null");
> +
> + ret = rte_eth_add_mirror(ring_port, &conf);
> + TEST_ASSERT(ret != 0, "Able to create duplicate mirror");
> +
> + ret = rte_eth_mirror_stats_get(ring_port, null_port, NULL);
> + TEST_ASSERT(ret == -EINVAL, "Able to get status with NULL");
> +
> + ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);
> + TEST_ASSERT(ret != 0, "Able to get stats with swapped ports");
> +
> + ret = rte_eth_mirror_stats_get(ring_port, null_port, &stats);
> + TEST_ASSERT(ret == 0, "Could not get stats");
> +
> + ret = rte_eth_remove_mirror(ring_port, null_port);
> + TEST_ASSERT(ret == 0, "Unable to delete mirror");
> +
> + ret = rte_eth_remove_mirror(ring_port, null_port);
> + TEST_ASSERT(ret != 0, "Able to delete port without mirror");
> +
> + return 0;
> +}
> +
> +static int
> +init_mbuf(struct rte_mbuf *m, uint16_t id)
> +{
> + struct {
> + struct rte_ether_hdr eth;
> + struct rte_ipv4_hdr ip;
> + struct rte_udp_hdr udp;
> + } hdrs = {
> + .eth = {
> + .dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff,
> 0xff, 0xff },
> + .ether_type =
> rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
> + },
> + .ip = {
> + .version_ihl = RTE_IPV4_VHL_DEF,
> + .time_to_live = 1,
> + .next_proto_id = IPPROTO_UDP,
> + .src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
> + .dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
> + },
> + .udp = {
> + .dst_port = rte_cpu_to_be_16(9), /* Discard port */
> + },
> + };
> +
> + rte_eth_random_addr(hdrs.eth.src_addr.addr_bytes);
> + uint16_t plen = pkt_len - sizeof(struct rte_ether_hdr);
> +
> + hdrs.ip.packet_id = rte_cpu_to_be_16(id);
> + hdrs.ip.total_length = rte_cpu_to_be_16(plen);
> + hdrs.ip.hdr_checksum = rte_ipv4_cksum(&hdrs.ip);
> +
> + plen -= sizeof(struct rte_ipv4_hdr);
> + hdrs.udp.src_port = rte_rand();
> + hdrs.udp.dgram_len = rte_cpu_to_be_16(plen);
> +
> + void *data = rte_pktmbuf_append(m, pkt_len);
> + TEST_ASSERT(data != NULL, "could not add header");
> +
> + memcpy(data, &hdrs, sizeof(hdrs));
> + return 0;
> +}
> +
> +static int
> +init_burst(struct rte_mbuf *pkts[], unsigned int n)
> +{
> + for (unsigned int i = 0; i < n; i++) {
> + if (init_mbuf(pkts[i], i) < 0)
> + return -1;
> + }
> + return 0;
> +}
> +
> +static int
> +validate_burst(struct rte_mbuf *pkts[], unsigned int n)
> +{
> + for (unsigned int i = 0; i < n; i++) {
> + struct rte_mbuf *m = pkts[i];
> +
> + rte_mbuf_sanity_check(m, 1);
> + TEST_ASSERT(m->pkt_len == pkt_len,
> + "mirror packet len %u not right len %u",
> + m->pkt_len, pkt_len);
> +
> + const struct rte_ether_hdr *eh
> + = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
> + TEST_ASSERT(rte_is_broadcast_ether_addr(&eh->dst_addr),
> + "mirrored packet is not broadcast");
> +
> + }
> + return 0;
> +}
> +
> +static int32_t
> +ethdev_mirror_packets(void)
> +{
> + struct rte_eth_mirror_conf conf = {
> + .mp = mirror_pool,
> + .target = ring_port,
> + .flags = RTE_ETH_MIRROR_DIRECTION_EGRESS,
> + };
> + struct rte_eth_mirror_stats stats;
> + struct rte_mbuf *tx_pkts[BURST_SZ], *rx_pkts[BURST_SZ];
> + uint16_t nb_tx, nb_rx;
> + int ret;
> +
> + ret = rte_pktmbuf_alloc_bulk(mirror_pool, tx_pkts, BURST_SZ);
> + TEST_ASSERT(ret == 0, "Could not allocate mbufs");
> +
> + ret = init_burst(tx_pkts, BURST_SZ);
> + TEST_ASSERT(ret == 0, "Init mbufs failed");
> +
> + ret = rte_eth_add_mirror(null_port, &conf);
> + TEST_ASSERT(ret == 0, "Could not create mirror from ring to null");
> +
> + nb_tx = rte_eth_tx_burst(null_port, 0, tx_pkts, BURST_SZ);
> + TEST_ASSERT(nb_tx == BURST_SZ, "Only sent %u burst to null (vs
> %u)",
> + nb_tx, BURST_SZ);
> +
> + nb_rx = rte_eth_rx_burst(ring_port, 0, rx_pkts, BURST_SZ);
> + TEST_ASSERT(nb_rx == BURST_SZ, "Only received %u of %u packets",
> + nb_rx, BURST_SZ);
> +
> + validate_burst(rx_pkts, nb_rx);
> + rte_pktmbuf_free_bulk(rx_pkts, nb_rx);
> +
> + ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);
> + TEST_ASSERT(ret == 0, "Could not get stats: %d", ret);
> +
> + TEST_ASSERT(stats.packets == BURST_SZ, "Stats reports %"PRIu64" of
> %u packets",
> + stats.packets, BURST_SZ);
> + TEST_ASSERT(stats.nombuf == 0, "Stats: no mbufs %"PRIu64,
> stats.nombuf);
> + TEST_ASSERT(stats.full == 0, "Stats: transmit was full %"PRIu64,
> stats.full);
> +
> + ret = rte_eth_mirror_stats_reset(null_port, ring_port);
> + TEST_ASSERT(ret == 0, "Could not get stats: %d", ret);
> +
> + ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);
> + TEST_ASSERT(ret == 0, "Could not get stats after reset: %d", ret);
> +
> + TEST_ASSERT(stats.packets == 0, "Stats reports %"PRIu64" after
> reset", stats.packets);
> +
> + ret = rte_eth_remove_mirror(null_port, ring_port);
> + TEST_ASSERT(ret == 0, "Could not remove mirror");
> +
> + return 0;
> +}
> +
> +static struct unit_test_suite ethdev_mirror_suite = {
> + .suite_name = "port mirroring",
> + .setup = test_setup,
> + .teardown = test_cleanup,
> + .unit_test_cases = {
> + TEST_CASE(ethdev_mirror_api),
> + TEST_CASE(ethdev_mirror_packets),
> + TEST_CASES_END()
> + }
> +};
> +
> +static int
> +test_ethdev_mirror(void)
> +{
> +#ifndef RTE_ETHDEV_MIRROR
> + return TEST_SKIPPED;
> +#endif
> + return unit_test_suite_runner(ðdev_mirror_suite);
> +}
> +
> +REGISTER_FAST_TEST(ethdev_mirror, true, true, test_ethdev_mirror);
> diff --git a/config/rte_config.h b/config/rte_config.h
> index 05344e2619..76eb2aa417 100644
> --- a/config/rte_config.h
> +++ b/config/rte_config.h
> @@ -69,6 +69,7 @@
> #define RTE_MAX_QUEUES_PER_PORT 1024
> #define RTE_ETHDEV_QUEUE_STAT_CNTRS 16 /* max 256 */
> #define RTE_ETHDEV_RXTX_CALLBACKS 1
> +#define RTE_ETHDEV_MIRROR 1
> #define RTE_MAX_MULTI_HOST_CTRLS 4
>
> /* cryptodev defines */
> diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
> index 2b4d2ae9c3..e6aad6d30a 100644
> --- a/lib/ethdev/ethdev_driver.h
> +++ b/lib/ethdev/ethdev_driver.h
> @@ -91,6 +91,12 @@ struct __rte_cache_aligned rte_eth_dev {
> */
> RTE_ATOMIC(struct rte_eth_rxtx_callback *)
> pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT];
>
> + /** Receive mirrors */
> + RTE_ATOMIC(struct rte_eth_mirror *) rx_mirror;
> +
> + /** Transmit mirrors */
> + RTE_ATOMIC(struct rte_eth_mirror *) tx_mirror;
> +
> enum rte_eth_dev_state state; /**< Flag indicating the port state
> */
> void *security_ctx; /**< Context for security ops */
> };
> diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c
> index 000b8372d8..e702acc874 100644
> --- a/lib/ethdev/ethdev_private.c
> +++ b/lib/ethdev/ethdev_private.c
> @@ -289,6 +289,8 @@ eth_dev_fp_ops_setup(struct rte_eth_fp_ops *fpo,
> fpo->tx_descriptor_status = dev->tx_descriptor_status;
> fpo->recycle_tx_mbufs_reuse = dev->recycle_tx_mbufs_reuse;
> fpo->recycle_rx_descriptors_refill =
> dev->recycle_rx_descriptors_refill;
> + fpo->rx_mirror = (struct rte_eth_mirror * __rte_atomic
> *)(uintptr_t)&dev->rx_mirror;
> + fpo->tx_mirror = (struct rte_eth_mirror * __rte_atomic
> *)(uintptr_t)&dev->tx_mirror;
>
> fpo->rxq.data = dev->data->rx_queues;
> fpo->rxq.clbk = (void * __rte_atomic
> *)(uintptr_t)dev->post_rx_burst_cbs;
> @@ -490,17 +492,53 @@ eth_dev_tx_queue_config(struct rte_eth_dev *dev,
> uint16_t nb_queues)
> }
>
> static int
> -ethdev_handle_request(const struct ethdev_mp_request *req)
> +ethdev_handle_request(const struct ethdev_mp_request *req, size_t len)
> {
> + len -= sizeof(*req);
> +
> switch (req->operation) {
> case ETH_REQ_START:
> + if (len != 0)
> + return -EINVAL;
> +
> return rte_eth_dev_start(req->port_id);
>
> case ETH_REQ_STOP:
> + if (len != 0)
> + return -EINVAL;
> return rte_eth_dev_stop(req->port_id);
>
> + case ETH_REQ_RESET:
> + if (len != 0)
> + return -EINVAL;
> + return rte_eth_dev_reset(req->port_id);
> +
> + case ETH_REQ_ADD_MIRROR:
> + if (len != sizeof(struct rte_eth_mirror_conf)) {
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "add mirror conf wrong size %zu", len);
> + return -EINVAL;
> + }
> +
> + const struct rte_eth_mirror_conf *conf
> + = (const struct rte_eth_mirror_conf *) req->config;
> +
> + return rte_eth_add_mirror(req->port_id, conf);
> +
> + case ETH_REQ_REMOVE_MIRROR:
> + if (len != sizeof(uint16_t)) {
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "mirror remove wrong size %zu", len);
> + return -EINVAL;
> + }
> +
> + uint16_t target = *(const uint16_t *) req->config;
> + return rte_eth_remove_mirror(req->port_id, target);
> +
> default:
> - return -EINVAL;
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "Unknown mp request operation %u",
> req->operation);
> + return -ENOTSUP;
> }
> }
>
> @@ -513,23 +551,25 @@ static_assert(sizeof(struct ethdev_mp_response) <=
> RTE_MP_MAX_PARAM_LEN,
> int
> ethdev_server(const struct rte_mp_msg *mp_msg, const void *peer)
> {
> - const struct ethdev_mp_request *req
> - = (const struct ethdev_mp_request *)mp_msg->param;
> -
> struct rte_mp_msg mp_resp = {
> .name = ETHDEV_MP,
> };
> struct ethdev_mp_response *resp;
> + const struct ethdev_mp_request *req;
>
> resp = (struct ethdev_mp_response *)mp_resp.param;
> mp_resp.len_param = sizeof(*resp);
> - resp->res_op = req->operation;
>
> /* recv client requests */
> - if (mp_msg->len_param != sizeof(*req))
> + if (mp_msg->len_param < (int)sizeof(*req)) {
> + RTE_ETHDEV_LOG_LINE(ERR, "invalid request from secondary");
> resp->err_value = -EINVAL;
> - else
> - resp->err_value = ethdev_handle_request(req);
> + } else {
> + req = (const struct ethdev_mp_request *)mp_msg->param;
> + resp->res_op = req->operation;
> + resp->err_value = ethdev_handle_request(req,
> mp_msg->len_param);
> +
> + }
>
> return rte_mp_reply(&mp_resp, peer);
> }
> diff --git a/lib/ethdev/ethdev_private.h b/lib/ethdev/ethdev_private.h
> index f58f161871..d2fdc20057 100644
> --- a/lib/ethdev/ethdev_private.h
> +++ b/lib/ethdev/ethdev_private.h
> @@ -85,6 +85,9 @@ int eth_dev_tx_queue_config(struct rte_eth_dev *dev,
> uint16_t nb_queues);
> enum ethdev_mp_operation {
> ETH_REQ_START,
> ETH_REQ_STOP,
> + ETH_REQ_RESET,
> + ETH_REQ_ADD_MIRROR,
> + ETH_REQ_REMOVE_MIRROR,
> };
>
> struct ethdev_mp_request {
> diff --git a/lib/ethdev/ethdev_trace.h b/lib/ethdev/ethdev_trace.h
> index 482befc209..e137afcbf7 100644
> --- a/lib/ethdev/ethdev_trace.h
> +++ b/lib/ethdev/ethdev_trace.h
> @@ -1035,6 +1035,23 @@ RTE_TRACE_POINT(
> rte_trace_point_emit_int(ret);
> )
>
> +RTE_TRACE_POINT(
> + rte_eth_trace_add_mirror,
> + RTE_TRACE_POINT_ARGS(uint16_t port_id,
> + const struct rte_eth_mirror_conf *conf, int
> ret),
> + rte_trace_point_emit_u16(port_id);
> + rte_trace_point_emit_u16(conf->target);
> + rte_trace_point_emit_int(ret);
> +)
> +
> +RTE_TRACE_POINT(
> + rte_eth_trace_remove_mirror,
> + RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t target_id, int
> ret),
> + rte_trace_point_emit_u16(port_id);
> + rte_trace_point_emit_u16(target_id);
> + rte_trace_point_emit_int(ret);
> +)
> +
> RTE_TRACE_POINT(
> rte_eth_trace_rx_queue_info_get,
> RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t queue_id,
> diff --git a/lib/ethdev/ethdev_trace_points.c
> b/lib/ethdev/ethdev_trace_points.c
> index 071c508327..fa1fd21809 100644
> --- a/lib/ethdev/ethdev_trace_points.c
> +++ b/lib/ethdev/ethdev_trace_points.c
> @@ -389,6 +389,12 @@
> RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_rx_callback,
> RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_tx_callback,
> lib.ethdev.remove_tx_callback)
>
> +RTE_TRACE_POINT_REGISTER(rte_eth_trace_add_mirror,
> + lib.ethdev.add_mirror)
> +
> +RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_mirror,
> + lib.ethdev.remove_mirror)
> +
> RTE_TRACE_POINT_REGISTER(rte_eth_trace_rx_queue_info_get,
> lib.ethdev.rx_queue_info_get)
>
> diff --git a/lib/ethdev/meson.build b/lib/ethdev/meson.build
> index 1d68d5348c..eb624bd579 100644
> --- a/lib/ethdev/meson.build
> +++ b/lib/ethdev/meson.build
> @@ -11,6 +11,7 @@ sources = files(
> 'rte_ethdev_cman.c',
> 'rte_ethdev_telemetry.c',
> 'rte_flow.c',
> + 'rte_mirror.c',
> 'rte_mtr.c',
> 'rte_tm.c',
> 'sff_telemetry.c',
> @@ -27,6 +28,7 @@ headers = files(
> 'rte_dev_info.h',
> 'rte_flow.h',
> 'rte_flow_driver.h',
> + 'rte_mirror.h',
> 'rte_mtr.h',
> 'rte_mtr_driver.h',
> 'rte_tm.h',
> diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
> index adeec575be..ac889c220a 100644
> --- a/lib/ethdev/rte_ethdev.c
> +++ b/lib/ethdev/rte_ethdev.c
> @@ -14,6 +14,8 @@
> #include <bus_driver.h>
> #include <eal_export.h>
> #include <rte_log.h>
> +#include <rte_cycles.h>
> +#include <rte_alarm.h>
> #include <rte_interrupts.h>
> #include <rte_kvargs.h>
> #include <rte_memcpy.h>
> @@ -2041,13 +2043,16 @@ rte_eth_dev_reset(uint16_t port_id)
> if (dev->dev_ops->dev_reset == NULL)
> return -ENOTSUP;
>
> - ret = rte_eth_dev_stop(port_id);
> - if (ret != 0) {
> - RTE_ETHDEV_LOG_LINE(ERR,
> - "Failed to stop device (port %u) before reset: %s
> - ignore",
> - port_id, rte_strerror(-ret));
> + if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
> + ret = rte_eth_dev_stop(port_id);
> + if (ret != 0)
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "Failed to stop device (port %u)
> before reset: %s - ignore",
> + port_id, rte_strerror(-ret));
> + ret = eth_err(port_id, dev->dev_ops->dev_reset(dev));
> + } else {
> + ret = ethdev_request(port_id, ETH_REQ_STOP, NULL, 0);
> }
> - ret = eth_err(port_id, dev->dev_ops->dev_reset(dev));
>
> rte_ethdev_trace_reset(port_id, ret);
>
> diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
> index f9fb6ae549..84801a1123 100644
> --- a/lib/ethdev/rte_ethdev.h
> +++ b/lib/ethdev/rte_ethdev.h
> @@ -170,6 +170,7 @@
>
> #include "rte_ethdev_trace_fp.h"
> #include "rte_dev_info.h"
> +#include "rte_mirror.h"
>
> #ifdef __cplusplus
> extern "C" {
> @@ -1466,7 +1467,6 @@ enum rte_eth_tunnel_type {
> RTE_ETH_TUNNEL_TYPE_ECPRI,
> RTE_ETH_TUNNEL_TYPE_MAX,
> };
> -
> #ifdef __cplusplus
> }
> #endif
> @@ -6334,6 +6334,17 @@ rte_eth_rx_burst(uint16_t port_id, uint16_t
> queue_id,
>
> nb_rx = p->rx_pkt_burst(qd, rx_pkts, nb_pkts);
>
> +#ifdef RTE_ETHDEV_MIRROR
> + {
> + struct rte_eth_mirror *mirror
> + = rte_atomic_load_explicit(p->rx_mirror,
> rte_memory_order_relaxed);
> +
> + if (unlikely(mirror != NULL))
> + rte_eth_mirror_burst(port_id, queue_id,
> RTE_ETH_MIRROR_DIRECTION_INGRESS,
> + rx_pkts, nb_rx, mirror);
> + }
> +#endif
> +
> #ifdef RTE_ETHDEV_RXTX_CALLBACKS
> {
> void *cb;
> @@ -6692,6 +6703,16 @@ rte_eth_tx_burst(uint16_t port_id, uint16_t
> queue_id,
> }
> #endif
>
> +#ifdef RTE_ETHDEV_MIRROR
> + {
> + struct rte_eth_mirror *mirror;
> +
> + mirror = rte_atomic_load_explicit(p->tx_mirror,
> rte_memory_order_relaxed);
> + if (unlikely(mirror != NULL))
> + rte_eth_mirror_burst(port_id, queue_id,
> RTE_ETH_MIRROR_DIRECTION_EGRESS,
> + tx_pkts, nb_pkts, mirror);
> + }
> +#endif
> nb_pkts = p->tx_pkt_burst(qd, tx_pkts, nb_pkts);
>
> rte_ethdev_trace_tx_burst(port_id, queue_id, (void **)tx_pkts,
> nb_pkts);
> diff --git a/lib/ethdev/rte_ethdev_core.h b/lib/ethdev/rte_ethdev_core.h
> index e55fb42996..2ca1beb239 100644
> --- a/lib/ethdev/rte_ethdev_core.h
> +++ b/lib/ethdev/rte_ethdev_core.h
> @@ -22,6 +22,12 @@ RTE_TAILQ_HEAD(rte_eth_dev_cb_list,
> rte_eth_dev_callback);
>
> struct rte_eth_dev;
>
> +struct rte_eth_mirror;
> +
> +void rte_eth_mirror_burst(uint16_t port_id, uint16_t quque_id, uint8_t
> dir,
> + struct rte_mbuf **pkts, uint16_t nb_pkts,
> + struct rte_eth_mirror *mirror);
> +
> /**
> * @internal Retrieve input packets from a receive queue of an Ethernet
> device.
> */
> @@ -101,7 +107,8 @@ struct __rte_cache_aligned rte_eth_fp_ops {
> eth_rx_descriptor_status_t rx_descriptor_status;
> /** Refill Rx descriptors with the recycling mbufs. */
> eth_recycle_rx_descriptors_refill_t recycle_rx_descriptors_refill;
> - uintptr_t reserved1[2];
> + uintptr_t reserved1;
> + RTE_ATOMIC(struct rte_eth_mirror *) *rx_mirror;
> /**@}*/
>
> /**@{*/
> @@ -121,7 +128,8 @@ struct __rte_cache_aligned rte_eth_fp_ops {
> eth_recycle_tx_mbufs_reuse_t recycle_tx_mbufs_reuse;
> /** Get the number of used Tx descriptors. */
> eth_tx_queue_count_t tx_queue_count;
> - uintptr_t reserved2[1];
> + RTE_ATOMIC(struct rte_eth_mirror *) *tx_mirror;
> + uintptr_t reserved2;
> /**@}*/
>
> };
> diff --git a/lib/ethdev/rte_mirror.c b/lib/ethdev/rte_mirror.c
> new file mode 100644
> index 0000000000..27b613b8ff
> --- /dev/null
> +++ b/lib/ethdev/rte_mirror.c
> @@ -0,0 +1,508 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Stephen Hemminger <stephen@networkplumber.org>
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include <rte_alarm.h>
> +#include <rte_bitops.h>
> +#include <rte_common.h>
> +#include <rte_config.h>
> +#include <rte_cycles.h>
> +#include <rte_eal.h>
> +#include <rte_ether.h>
> +#include <rte_log.h>
> +#include <rte_malloc.h>
> +#include <rte_mbuf.h>
> +#include <rte_spinlock.h>
> +#include <rte_stdatomic.h>
> +#include <eal_export.h>
> +
> +#include "rte_ethdev.h"
> +#include "rte_mirror.h"
> +#include "ethdev_driver.h"
> +#include "ethdev_private.h"
> +#include "ethdev_trace.h"
> +
> +/* Upper bound of packet bursts redirected */
> +#define RTE_MIRROR_BURST_SIZE 64
> +
> +#ifdef RTE_LIB_BPF
> +#include <rte_bpf.h>
> +#endif
> +
> +/**
> + * Structure used to hold information mirror port mirrors for a
> + * queue on Rx and Tx.
> + */
> +struct rte_eth_mirror {
> + RTE_ATOMIC(struct rte_eth_mirror *) next;
> + struct rte_mempool *mp;
> + struct rte_bpf *bpf;
> + uint32_t snaplen;
> + uint32_t flags;
> + uint16_t target;
> + uint16_t tx_queues;
> + bool need_lock;
> + rte_spinlock_t lock;
> + struct rte_eth_mirror_stats stats;
> +};
> +
> +/* spinlock for setting up mirror ports */
> +static rte_spinlock_t mirror_port_lock = RTE_SPINLOCK_INITIALIZER;
> +
> +/* dynamically assigned offload flag to indicate ingress vs egress */
> +static uint64_t mirror_origin_flag;
> +static int mirror_origin_offset = -1;
> +static uint64_t mirror_ingress_flag;
> +static uint64_t mirror_egress_flag;
> +
> +static uint64_t mbuf_timestamp_dynflag;
> +static int mbuf_timestamp_offset = -1;
> +
> +/* register dynamic mbuf fields, done on first mirror creation */
> +static int
> +ethdev_dyn_mirror_register(void)
> +{
> + const struct rte_mbuf_dynfield field_desc = {
> + .name = RTE_MBUF_DYNFIELD_MIRROR_ORIGIN,
> + .size = sizeof(rte_mbuf_origin_t),
> + .align = sizeof(rte_mbuf_origin_t),
> + };
> + struct rte_mbuf_dynflag flag_desc = {
> + .name = RTE_MBUF_DYNFLAG_MIRROR_ORIGIN,
> + };
> + int offset;
> +
> + if (rte_mbuf_dyn_tx_timestamp_register(&mbuf_timestamp_offset,
> + &mbuf_timestamp_dynflag) <
> 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register timestamp
> flag");
> + return -1;
> + }
> +
> + offset = rte_mbuf_dynfield_register(&field_desc);
> + if (offset < 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin
> field");
> + return -1;
> + }
> + mirror_origin_offset = offset;
> +
> + offset = rte_mbuf_dynflag_register(&flag_desc);
> + if (offset < 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin
> flag");
> + return -1;
> + }
> + mirror_origin_flag = RTE_BIT64(offset);
> +
> + strlcpy(flag_desc.name, RTE_MBUF_DYNFLAG_MIRROR_INGRESS, sizeof(
> flag_desc.name));
> + offset = rte_mbuf_dynflag_register(&flag_desc);
> + if (offset < 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf ingress
> flag");
> + return -1;
> + }
> + mirror_ingress_flag = RTE_BIT64(offset);
> +
> + strlcpy(flag_desc.name, RTE_MBUF_DYNFLAG_MIRROR_EGRESS,
> + sizeof(flag_desc.name));
> + offset = rte_mbuf_dynflag_register(&flag_desc);
> + if (offset < 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf egress
> flag");
> + return -1;
> + }
> + mirror_egress_flag = RTE_BIT64(offset);
> +
> + return 0;
> +}
> +
> +/* Add a new mirror entry to the list. */
> +static int
> +ethdev_insert_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top,
> + const struct rte_eth_mirror_conf *conf,
> + const struct rte_eth_dev_info *info)
> +{
> + struct rte_eth_mirror *mirror;
> + ssize_t filter_len = 0;
> +
> + /* Don't allow multiple mirrors from same source to target */
> + while ((mirror = *top) != NULL) {
> + if (mirror->target == conf->target) {
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "Mirror to port %u already exists",
> conf->target);
> + return -EEXIST;
> + }
> + }
> +
> + if (conf->filter) {
> +#ifdef RTE_LIB_BPF
> + filter_len = rte_bpf_buf_size(conf->filter);
> + if (filter_len < 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Invalid BPF filter: %s",
> + rte_strerror(rte_errno));
> + return -EINVAL;
> + }
> +#else
> + RTE_ETHDEV_LOG_LINE(ERR, "BPF filter not supported");
> + return -ENOTSUP;
> +#endif
> + }
> +
> + /*
> + * Allocate space for both fast path mirror structure
> + * and filter bpf code (if any).
> + */
> + mirror = rte_zmalloc(NULL, sizeof(*mirror) + filter_len, 0);
> + if (mirror == NULL)
> + return -ENOMEM;
> +
> + mirror->mp = conf->mp;
> + mirror->target = conf->target;
> + mirror->flags = conf->flags;
> + rte_spinlock_init(&mirror->lock);
> + mirror->need_lock = !(info->tx_offload_capa &
> RTE_ETH_TX_OFFLOAD_MT_LOCKFREE);
> + mirror->tx_queues = info->nb_tx_queues;
> +
> + if (conf->snaplen == 0) /* specifying 0 implies the full packet */
> + mirror->snaplen = UINT32_MAX;
> + else
> + mirror->snaplen = conf->snaplen;
> +
> +#ifdef RTE_LIB_BPF
> + if (filter_len > 0) {
> + /* reserved space for BPF is after mirror structure */
> + void *buf = (uint8_t *)mirror + sizeof(*mirror);
> +
> + /*
> + * Copy filter internal representation into space
> + * allocated in huge pages to allow access from any
> process.
> + */
> + mirror->bpf = rte_bpf_buf_load(conf->filter, buf,
> filter_len);
> + if (mirror->bpf == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Failed to load BPF
> filter: %s",
> + rte_strerror(rte_errno));
> + rte_free(mirror);
> + return -EINVAL;
> + }
> +
> + }
> +#endif
> +
> + mirror->next = *top;
> +
> + rte_atomic_store_explicit(top, mirror, rte_memory_order_relaxed);
> + return 0;
> +}
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_add_mirror, 25.11)
> +int
> +rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf
> *conf)
> +{
> +#ifndef RTE_ETHDEV_MIRROR
> + return -ENOTSUP;
> +#endif
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> +
> + struct rte_eth_dev *dev = &rte_eth_devices[port_id];
> + struct rte_eth_dev_info dev_info;
> +
> + if (conf == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Missing configuration
> information");
> + return -EINVAL;
> + }
> +
> + if (conf->mp == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR, "not a valid mempool");
> + return -EINVAL;
> + }
> +
> + if (conf->flags & ~(RTE_ETH_MIRROR_DIRECTION_MASK |
> RTE_ETH_MIRROR_FLAG_MASK)) {
> + RTE_ETHDEV_LOG_LINE(ERR, "unsupported flags");
> + return -EINVAL;
> + }
> +
> + if ((conf->flags & RTE_ETH_MIRROR_DIRECTION_MASK) == 0) {
> + RTE_ETHDEV_LOG_LINE(ERR, "missing direction ingress or
> egress");
> + return -EINVAL;
> + }
> +
> + /* Checks that target exists */
> + int ret = rte_eth_dev_info_get(conf->target, &dev_info);
> + if (ret != 0)
> + return ret;
> +
> + /* Loopback mirror could create packet storm */
> + if (conf->target == port_id) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Cannot mirror port to self");
> + return -EINVAL;
> + }
> +
> + if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
> + /* Register dynamic fields once */
> + if (mirror_origin_offset < 0) {
> + ret = ethdev_dyn_mirror_register();
> + if (ret < 0)
> + return ret;
> + }
> +
> + rte_spinlock_lock(&mirror_port_lock);
> + ret = 0;
> +
> + if (conf->flags & RTE_ETH_MIRROR_DIRECTION_INGRESS)
> + ret = ethdev_insert_mirror(&dev->rx_mirror, conf,
> &dev_info);
> + if (ret == 0 && (conf->flags &
> RTE_ETH_MIRROR_DIRECTION_EGRESS))
> + ret = ethdev_insert_mirror(&dev->tx_mirror, conf,
> &dev_info);
> + rte_spinlock_unlock(&mirror_port_lock);
> + } else {
> + /* in secondary, proxy to primary */
> + ret = ethdev_request(port_id, ETH_REQ_ADD_MIRROR, conf,
> sizeof(*conf));
> + if (ret != 0)
> + return ret;
> + }
> +
> + rte_eth_trace_add_mirror(port_id, conf, ret);
> + return ret;
> +}
> +
> +static struct rte_eth_mirror *
> +ethdev_find_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *head, uint16_t
> target_id)
> +{
> +
> + for (;;) {
> + struct rte_eth_mirror *mirror
> + = rte_atomic_load_explicit(head,
> rte_memory_order_relaxed);
> + if (mirror == NULL)
> + return NULL; /* reached end of list */
> +
> + if (mirror->target == target_id)
> + return mirror;
> +
> + head = &mirror->next;
> + }
> +}
> +
> +static bool
> +ethdev_delete_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top, uint16_t
> target_id)
> +{
> + struct rte_eth_mirror *mirror;
> +
> + mirror = ethdev_find_mirror(top, target_id);
> + if (mirror == NULL)
> + return false;
> +
> + /* unlink from list */
> + rte_atomic_store_explicit(top, mirror->next,
> rte_memory_order_relaxed);
> +
> + /*
> + * Defer freeing the mirror until after one second to allow for
> active threads
> + * that are using it. Assumes no PMD takes more than one second to
> transmit a burst.
> + * Alternative would be RCU, but RCU in DPDK is optional and
> requires application changes.
> + */
> + rte_eal_alarm_set(US_PER_S, rte_free, mirror);
> + return true;
> +}
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_remove_mirror, 25.11)
> +int
> +rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id)
> +{
> +#ifndef RTE_ETHDEV_MIRROR
> + return -ENOTSUP;
> +#endif
> + int ret = 0;
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);
> +
> + struct rte_eth_dev *dev = &rte_eth_devices[port_id];
> +
> + if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
> + bool found;
> +
> + rte_spinlock_lock(&mirror_port_lock);
> + found = ethdev_delete_mirror(&dev->rx_mirror, target_id);
> + found |= ethdev_delete_mirror(&dev->tx_mirror, target_id);
> + rte_spinlock_unlock(&mirror_port_lock);
> + if (!found)
> + ret = -ENOENT; /* no mirror present */
> + } else {
> + ret = ethdev_request(port_id, ETH_REQ_REMOVE_MIRROR,
> + &target_id, sizeof(target_id));
> + }
> +
> + rte_eth_trace_remove_mirror(port_id, target_id, ret);
> + return ret;
> +}
> +
> +static inline void
> +eth_dev_mirror(uint16_t port_id, uint16_t queue_id, uint8_t direction,
> + struct rte_mbuf **pkts, uint16_t nb_pkts,
> + struct rte_eth_mirror *mirror)
> +{
> + struct rte_mbuf *tosend[RTE_MIRROR_BURST_SIZE];
> + unsigned int count = 0;
> + unsigned int nsent;
> +
> +#ifdef RTE_LIB_BPF
> + uint64_t rcs[RTE_MIRROR_BURST_SIZE];
> + if (mirror->bpf)
> + rte_bpf_exec_burst(mirror->bpf, (void **)pkts, rcs,
> nb_pkts);
> +#endif
> +
> + for (unsigned int i = 0; i < nb_pkts; i++) {
> +#ifdef RTE_LIB_BPF
> + /*
> + * This uses same BPF return value convention as socket
> filter
> + * and pcap_offline_filter. If program returns zero
> + * then packet doesn't match the filter (will be ignored).
> + */
> + if (mirror->bpf && rcs[i] == 0) {
> + ++mirror->stats.filtered;
> + continue;
> + }
> +#endif
> +
> + struct rte_mbuf *m = pkts[i];
> + struct rte_mbuf *mc = rte_pktmbuf_copy(m, mirror->mp, 0,
> mirror->snaplen);
> + if (unlikely(mc == NULL)) {
> + ++mirror->stats.nombuf;
> + continue;
> + }
> +
> + /* Put info about origin of the packet */
> + if (mirror->flags & RTE_ETH_MIRROR_ORIGIN_FLAG) {
> + struct rte_mbuf_origin *origin
> + = RTE_MBUF_DYNFIELD(mc,
> mirror_origin_offset, rte_mbuf_origin_t *);
> + origin->original_len = m->pkt_len;
> + origin->port_id = port_id;
> + origin->queue_id = queue_id;
> + mc->ol_flags |= mirror_origin_flag;
> + }
> +
> + /* Insert timestamp into packet */
> + if (mirror->flags & RTE_ETH_MIRROR_TIMESTAMP_FLAG) {
> + *RTE_MBUF_DYNFIELD(m, mbuf_timestamp_offset,
> rte_mbuf_timestamp_t *)
> + = rte_get_tsc_cycles();
> + mc->ol_flags |= mbuf_timestamp_dynflag;
> + }
> +
> + mc->ol_flags &= ~(mirror_ingress_flag |
> mirror_egress_flag);
> + if (direction & RTE_ETH_MIRROR_DIRECTION_INGRESS)
> + mc->ol_flags |= mirror_ingress_flag;
> + else if (direction & RTE_ETH_MIRROR_DIRECTION_EGRESS)
> + mc->ol_flags |= mirror_egress_flag;
> +
> + tosend[count++] = mc;
> + }
> +
> + if (mirror->need_lock) {
> + uint16_t txq = queue_id % mirror->tx_queues;
> +
> + rte_spinlock_lock(&mirror->lock);
> + nsent = rte_eth_tx_burst(mirror->target, txq, tosend,
> count);
> + rte_spinlock_unlock(&mirror->lock);
> + } else {
> + nsent = rte_eth_tx_burst(mirror->target, 0, tosend, count);
> + }
> +
> + mirror->stats.packets += nsent;
> +
> + if (unlikely(nsent < count)) {
> + uint16_t drop = count - nsent;
> +
> + mirror->stats.full += drop;
> + rte_pktmbuf_free_bulk(pkts + nsent, drop);
> + }
> +}
> +
> +/* This function is really internal but used from inline */
> +RTE_EXPORT_SYMBOL(rte_eth_mirror_burst)
> +void
> +rte_eth_mirror_burst(uint16_t port_id, uint16_t queue_id, uint8_t
> direction,
> + struct rte_mbuf **pkts, uint16_t nb_pkts,
> + struct rte_eth_mirror *mirror)
> +{
> +
> + while (mirror != NULL) {
> + for (uint16_t i = 0; i < nb_pkts; i +=
> RTE_MIRROR_BURST_SIZE) {
> + uint16_t burst = RTE_MIN(nb_pkts - i,
> RTE_MIRROR_BURST_SIZE);
> +
> + eth_dev_mirror(port_id, queue_id, direction,
> + pkts + i, burst, mirror);
> + }
> +
> + mirror = rte_atomic_load_explicit(&mirror->next,
> rte_memory_order_relaxed);
> + }
> +}
> +
> +static int
> +ethdev_mirror_stats_get(RTE_ATOMIC(struct rte_eth_mirror *) *head,
> uint16_t target_id,
> + struct rte_eth_mirror_stats *stats)
> +{
> + const struct rte_eth_mirror *mirror;
> +
> + mirror = ethdev_find_mirror(head, target_id);
> + if (mirror == NULL)
> + return -1;
> +
> + stats->packets += mirror->stats.packets;
> + stats->nombuf += mirror->stats.nombuf;
> + stats->full += mirror->stats.full;
> + return 0;
> +}
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_mirror_stats_get, 25.11)
> +int
> +rte_eth_mirror_stats_get(uint16_t port_id, uint16_t target_id,
> + struct rte_eth_mirror_stats *stats)
> +{
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);
> +
> + if (stats == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR, "Mirror port stats is NULL");
> + return -EINVAL;
> + }
> +
> + memset(stats, 0, sizeof(*stats));
> +
> + struct rte_eth_dev *dev = &rte_eth_devices[port_id];
> + int rx_ret = ethdev_mirror_stats_get(&dev->rx_mirror, target_id,
> stats);
> + int tx_ret = ethdev_mirror_stats_get(&dev->tx_mirror, target_id,
> stats);
> +
> + /* if rx or tx mirror is valid return 0 */
> + return (tx_ret == 0 || rx_ret == 0) ? 0 : -ENOENT;
> +}
> +
> +static int
> +ethdev_mirror_stats_reset(RTE_ATOMIC(struct rte_eth_mirror *) *head,
> uint16_t target_id)
> +{
> + struct rte_eth_mirror *mirror;
> +
> + mirror = ethdev_find_mirror(head, target_id);
> + if (mirror == NULL)
> + return -1;
> +
> + memset(&mirror->stats, 0, sizeof(struct rte_eth_mirror_stats));
> + return 0;
> +}
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_mirror_stats_reset, 25.11)
> +int
> +rte_eth_mirror_stats_reset(uint16_t port_id, uint16_t target_id)
> +{
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);
> +
> + struct rte_eth_dev *dev = &rte_eth_devices[port_id];
> + int rx_ret = ethdev_mirror_stats_reset(&dev->rx_mirror, target_id);
> + int tx_ret = ethdev_mirror_stats_reset(&dev->tx_mirror, target_id);
> +
> + /* if rx or tx mirror is valid return 0 */
> + return (tx_ret == 0 || rx_ret == 0) ? 0 : -ENOENT;
> +
> +}
> diff --git a/lib/ethdev/rte_mirror.h b/lib/ethdev/rte_mirror.h
> new file mode 100644
> index 0000000000..593ef477b6
> --- /dev/null
> +++ b/lib/ethdev/rte_mirror.h
> @@ -0,0 +1,147 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Stephen Hemminger <stephen@networkplumber.org>
> + */
> +
> +#ifndef RTE_MIRROR_H_
> +#define RTE_MIRROR_H_
> +
> +/**
> + * @file
> + * Ethdev port mirroring
> + *
> + * This interface provides the ability to duplicate packets to another
> port.
> + */
> +
> +#include <stdint.h>
> +
> +#include <rte_compat.h>
> +#include <rte_mbuf.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/* Definitions for ethdev mirror flags */
> +#define RTE_ETH_MIRROR_DIRECTION_INGRESS 1
> +#define RTE_ETH_MIRROR_DIRECTION_EGRESS 2
> +#define RTE_ETH_MIRROR_DIRECTION_MASK (RTE_ETH_MIRROR_DIRECTION_INGRESS |
> \
> + RTE_ETH_MIRROR_DIRECTION_EGRESS)
> +
> +#define RTE_ETH_MIRROR_TIMESTAMP_FLAG 4 /**< insert timestamp into
> mirrored packet */
> +#define RTE_ETH_MIRROR_ORIGIN_FLAG 8 /**< insert
> rte_mbuf_origin into mirrored packet */
> +
> +#define RTE_ETH_MIRROR_FLAG_MASK (RTE_ETH_MIRROR_TIMESTAMP_FLAG | \
> + RTE_ETH_MIRROR_ORIGIN_FLAG)
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this structure may change without prior notice.
> + *
> + * This dynamic field is added to mbuf's when they are copied to
> + * the port mirror.
> + */
> +typedef struct rte_mbuf_origin {
> + uint32_t original_len; /**< Packet length before copy */
> + uint16_t port_id; /**< Port where packet originated */
> + uint16_t queue_id; /**< Queue used for Tx or Rx */
> +} rte_mbuf_origin_t;
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this structure may change without prior notice.
> + *
> + * Structure used to configure ethdev Switched Port Analyzer (MIRROR)
> + */
> +struct rte_bpf_prm;
> +struct rte_eth_mirror_conf {
> + struct rte_mempool *mp; /**< Memory pool for copies, If NULL then
> cloned. */
> + struct rte_bpf_prm *filter; /**< Optional packet filter */
> + uint32_t snaplen; /**< Upper limit on number of bytes to
> copy */
> + uint32_t flags; /**< bitmask of RTE_ETH_MIRROR_XXX_FLAG's
> */
> + uint16_t target; /**< Destination port */
> +};
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this structure may change without prior notice.
> + *
> + * Structure returned by rte_mirror_stats.
> + */
> +struct rte_eth_mirror_stats {
> + uint64_t packets; /**< Number of mirrored packets. */
> + uint64_t filtered; /**< Packets filtered by BPF program */
> + uint64_t nombuf; /**< Rx mbuf allocation failures. */
> + uint64_t full; /**< Target port transmit full. */
> +};
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change, or be removed, without prior
> notice
> + *
> + * Create a port mirror instance.
> + *
> + * @param port_id
> + * The port identifier of the source Ethernet device.
> + * @param conf
> + * Settings for this MIRROR instance.
> + * @return
> + * Negative errno value on error, 0 on success.
> + */
> +__rte_experimental
> +int
> +rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf
> *conf);
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change, or be removed, without prior
> notice
> + *
> + * Break port existing port mirroring.
> + * After this call no more packets will be sent from origin port to the
> target port.
> + *
> + * @param port_id
> + * The port identifier of the source Ethernet device.
> + * @param target_id
> + * The identifier of the destination port.
> + * @return
> + * Negative errno value on error, 0 on success.
> + */
> +__rte_experimental
> +int rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id);
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change, or be removed, without prior
> notice
> + *
> + * Query statistics for a mirror.
> + *
> + * @param port_id
> + * The port identifier of the source Ethernet device.
> + * @param target_id
> + * The identifier of the destination port.
> + * @param stats
> + * A pointer to a structure of type *rte_eth_mirror_stats* to be filled.
> + *
> + * @return
> + * Negative errno value on error, 0 on success.
> + */
> +__rte_experimental
> +int rte_eth_mirror_stats_get(uint16_t port_id, uint16_t target_id,
> + struct rte_eth_mirror_stats *stats);
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change, or be removed, without prior
> notice
> + *
> + * Reset statistics for mirror.
> + *
> + * @param port_id
> + * The port identifier of the source Ethernet device.
> + * @param target_id
> + * The identifier of the destination port.
> + */
> +__rte_experimental
> +int rte_eth_mirror_stats_reset(uint16_t port_id, uint16_t target_id);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* RTE_MIRROR_H_ */
> --
> 2.47.2
>
>
[-- Attachment #2: Type: text/html, Size: 61624 bytes --]
next prev parent reply other threads:[~2025-08-15 15:36 UTC|newest]
Thread overview: 159+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-11 23:44 [RFC 00/13] Packet capture using port mirroring Stephen Hemminger
2025-04-11 23:44 ` [RFC 01/13] app/testpmd: revert auto attach/detach Stephen Hemminger
2025-04-15 13:28 ` lihuisong (C)
2025-04-16 0:06 ` Stephen Hemminger
2025-04-17 3:14 ` lihuisong (C)
2025-04-11 23:44 ` [RFC 02/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-04-15 0:19 ` Stephen Hemminger
2025-04-11 23:44 ` [RFC 03/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-04-11 23:44 ` [RFC 04/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-04-11 23:44 ` [RFC 05/13] net/ring: add argument to attach existing ring Stephen Hemminger
2025-04-11 23:44 ` [RFC 06/13] net/ring: add timestamp devargs Stephen Hemminger
2025-04-11 23:44 ` [RFC 07/13] net/null: all lockfree transmit Stephen Hemminger
2025-04-11 23:44 ` [RFC 08/13] mbuf: add fields for mirroring Stephen Hemminger
2025-04-12 9:59 ` Morten Brørup
2025-04-12 16:56 ` Stephen Hemminger
2025-04-13 7:00 ` Morten Brørup
2025-04-13 14:31 ` Stephen Hemminger
2025-04-13 14:44 ` Morten Brørup
2025-04-11 23:44 ` [RFC 09/13] ethdev: add port mirror capability Stephen Hemminger
2025-04-11 23:44 ` [RFC 10/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-04-11 23:44 ` [RFC 11/13] test: add tests for ethdev mirror Stephen Hemminger
2025-04-11 23:44 ` [RFC 12/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-04-11 23:44 ` [RFC 13/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-04-12 11:06 ` [RFC 00/13] Packet capture using port mirroring Morten Brørup
2025-04-12 17:04 ` Stephen Hemminger
2025-04-13 9:26 ` Morten Brørup
2025-07-15 16:15 ` [PATCH v4 " Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 01/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 02/13] ethdev: make sure all necessary headers included Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 03/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 04/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 05/13] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 06/13] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 07/13] ethdev: add port mirroring feature Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 08/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 09/13] pcapng: make queue optional Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 10/13] test: add tests for ethdev mirror Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 11/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 12/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-07-15 16:15 ` [PATCH v4 13/13] pdump: mark API and application as deprecated Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 00/13] Packet capture using port mirroring Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 01/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 02/13] ethdev: make sure all necessary headers included Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 03/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 04/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 05/13] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 06/13] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 07/13] ethdev: add port mirroring feature Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 08/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 09/13] pcapng: make queue optional Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 10/13] test: add tests for ethdev mirror Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 11/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 12/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-07-18 16:28 ` [PATCH v5 13/13] pdump: mark API and application as deprecated Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 00/13] Packet capture using port mirroring Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 01/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-07-23 1:08 ` Ivan Malov
2025-07-23 14:38 ` Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 02/13] ethdev: make sure all necessary headers included Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 03/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-07-23 1:31 ` Ivan Malov
2025-07-23 15:09 ` Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 04/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 05/13] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 06/13] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 07/13] ethdev: add port mirroring feature Stephen Hemminger
2025-07-23 2:13 ` Ivan Malov
2025-07-23 15:26 ` Stephen Hemminger
2025-07-23 15:30 ` Ivan Malov
2025-07-22 17:34 ` [PATCH v6 08/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-07-23 2:32 ` Ivan Malov
2025-07-23 3:18 ` Ivan Malov
2025-07-23 19:20 ` Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 09/13] pcapng: make queue optional Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 10/13] test: add tests for ethdev mirror Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 11/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 12/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-07-23 3:16 ` Ivan Malov
2025-07-23 3:27 ` Ivan Malov
2025-07-23 19:26 ` Stephen Hemminger
2025-07-22 17:34 ` [PATCH v6 13/13] pdump: mark API and application as deprecated Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 00/12] Port mirroring for packet capture Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 01/12] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 02/12] ethdev: make sure all necessary headers included Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 03/12] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 04/12] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 05/12] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 06/12] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 07/12] ethdev: add port mirroring feature Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 08/12] test: add tests for ethdev mirror Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 09/12] pcapng: split packet copy from header insertion Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 10/12] pcapng: make queue optional Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 11/12] app/testpmd: support for port mirroring Stephen Hemminger
2025-07-28 22:08 ` [PATCH v7 12/12] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 00/13] Packet capture using port mirroring Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 01/13] bpf: add ability to load bpf into a buffer Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 02/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 03/13] ethdev: make sure all necessary headers included Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 04/13] ethdev: swap bpf and ethdev dependency Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 05/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 06/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 07/13] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 08/13] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 09/13] ethdev: add port mirroring feature Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 10/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 11/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 12/13] pcapng: make queue optional Stephen Hemminger
2025-08-04 16:27 ` [PATCH v8 13/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 00/13] Packet capture using port mirroring Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 01/13] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 02/13] ethdev: make sure all necessary headers included Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 03/13] ethdev: reorder bpf and ethdev dependency Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 04/13] bpf: add ability to load bpf into a buffer Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 05/13] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 06/13] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 07/13] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 08/13] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 09/13] ethdev: add port mirroring feature Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 10/13] app/testpmd: support for port mirroring Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 11/13] pcapng: split packet copy from header insertion Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 12/13] pcapng: make queue optional Stephen Hemminger
2025-08-06 21:40 ` [PATCH v9 13/13] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 00/14] Port mirroring feature Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 01/14] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 02/14] ethdev: make sure all necessary headers included Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 03/14] ethdev: reorder bpf and ethdev dependency Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 04/14] bpf: add ability to load bpf into a buffer Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 05/14] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 06/14] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 07/14] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 08/14] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 09/14] ethdev: add port mirroring feature Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 10/14] app/testpmd: support for port mirroring Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 11/14] pcapng: split packet copy from header insertion Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 12/14] pcapng: make queue optional Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 13/14] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-08-07 17:33 ` [PATCH v10 14/14] doc: add documentation for port mirroring Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 00/14] Port mirroring for packet capture Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 01/14] ethdev: allow start/stop from secondary process Stephen Hemminger
2025-08-09 6:31 ` Khadem Ullah
2025-08-11 10:10 ` Andrew Rybchenko
2025-08-08 16:55 ` [PATCH v11 02/14] ethdev: make sure all necessary headers included Stephen Hemminger
2025-08-11 10:10 ` Andrew Rybchenko
2025-08-08 16:55 ` [PATCH v11 03/14] ethdev: reorder bpf and ethdev dependency Stephen Hemminger
2025-08-11 10:14 ` Andrew Rybchenko
2025-08-08 16:55 ` [PATCH v11 04/14] bpf: add ability to load bpf into a buffer Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 05/14] test: add test for hotplug and secondary process operations Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 06/14] net/ring: allow lockfree transmit if ring supports it Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 07/14] net/ring: add new devarg to create vdev from existing ring Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 08/14] mbuf: add dynamic flag for use by port mirroring Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 09/14] ethdev: add port mirroring feature Stephen Hemminger
2025-08-11 11:04 ` Andrew Rybchenko
2025-08-15 15:29 ` Patrick Robb [this message]
2025-08-08 16:55 ` [PATCH v11 10/14] app/testpmd: support for port mirroring Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 11/14] pcapng: split packet copy from header insertion Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 12/14] pcapng: make queue optional Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 13/14] app/dumpcap: use port mirror instead of pdump Stephen Hemminger
2025-08-08 16:55 ` [PATCH v11 14/14] doc: add documentation for port mirroring Stephen Hemminger
2025-08-11 15:36 ` [PATCH v11 00/14] Port mirroring for packet capture 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=CAJvnSUAqYff4N5Z7M3GhAXUxRJutN16T-1-PhS7NtphhfHqsKQ@mail.gmail.com \
--to=probb@iol.unh.edu \
--cc=dev@dpdk.org \
--cc=stephen@networkplumber.org \
/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;
as well as URLs for NNTP newsgroup(s).