DPDK patches and discussions
 help / color / mirror / Atom feed
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, &eth_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(&ethdev_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 --]

  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).