[PATCH v6 07/13] ethdev: add port mirroring feature

Ivan Malov ivan.malov at arknetworks.am
Wed Jul 23 04:13:27 CEST 2025


Hi Stephen,

(please see below)

On Tue, 22 Jul 2025, Stephen Hemminger wrote:

> This adds new feature port mirroring to the ethdev layer.
>
> Signed-off-by: Stephen Hemminger <stephen at networkplumber.org>
> ---
> 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           |   1 +
> lib/ethdev/rte_ethdev.c          |  17 +-
> lib/ethdev/rte_ethdev.h          |  23 +-
> lib/ethdev/rte_ethdev_core.h     |  12 +-
> lib/ethdev/rte_mirror.c          | 375 +++++++++++++++++++++++++++++++
> lib/ethdev/rte_mirror.h          |  99 ++++++++
> 12 files changed, 600 insertions(+), 18 deletions(-)
> create mode 100644 lib/ethdev/rte_mirror.c
> create mode 100644 lib/ethdev/rte_mirror.h
>
> 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 fffffe2625..dd144db993 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 f1d2586591..3672b6a35b 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',
> diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
> index 41363af2c3..6602a771bd 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..4101402266 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
> +	{
> +		const 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
> +	{
> +		const 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..b72050d41b 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,
> +			  const 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..9140306199
> --- /dev/null
> +++ b/lib/ethdev/rte_mirror.c
> @@ -0,0 +1,375 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Stephen Hemminger <stephen at networkplumber.org>
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include <rte_config.h>
> +#include <rte_bitops.h>
> +#include <rte_eal.h>
> +#include <rte_log.h>
> +#include <rte_common.h>
> +#include <rte_cycles.h>
> +#include <rte_alarm.h>
> +#include <rte_ether.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
> +
> +/* 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;
> +
> +#ifdef RTE_ETHDEV_MIRROR
> +
> +/* 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;
> +}
> +
> +/**
> + * 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_eth_mirror_conf conf;
> +};
> +
> +/* 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)
> +{
> +	struct rte_eth_mirror *mirror;
> +
> +	/* Don't allow multiple mirrors from same source to target */
> +	while ((mirror = *top) != NULL) {
> +		if (mirror->conf.target == conf->target) {
> +			RTE_ETHDEV_LOG_LINE(ERR,
> +				    "Mirror to port %u already exists", conf->target);
> +			return -EEXIST;
> +		}
> +	}
> +
> +	mirror = rte_zmalloc(NULL, sizeof(*mirror), 0);
> +	if (mirror == NULL)
> +		return -ENOMEM;
> +
> +	mirror->conf = *conf;
> +	/* specifying 0 implies the full packet */
> +	if (conf->snaplen == 0)
> +		mirror->conf.snaplen = UINT32_MAX;
> +
> +	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)
> +{
> +	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->direction == 0 || conf->direction >
> +	    (RTE_ETH_MIRROR_DIRECTION_INGRESS | RTE_ETH_MIRROR_DIRECTION_EGRESS)) {
> +		RTE_ETHDEV_LOG_LINE(ERR, "Invalid direction %#x", conf->direction);
> +		return -EINVAL;
> +	}
> +
> +	if (conf->mp == NULL) {
> +		RTE_ETHDEV_LOG_LINE(ERR, "not a valid mempool");
> +		return -EINVAL;
> +	}
> +
> +	if (conf->flags & ~RTE_ETH_MIRROR_FLAG_MASK) {
> +		RTE_ETHDEV_LOG_LINE(ERR, "unsupported flags");
> +		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;
> +	}
> +
> +	/* Because multiple directions and multiple queues will all going to the mirror port

Perhaps use
/*
  * comment
  */
style (up to you).

Also, 'will all BE going'?

> +	 * need the transmit path to be lockfree.
> +	 */
> +	if (!(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE)) {
> +		RTE_ETHDEV_LOG_LINE(ERR, "Mirror needs lockfree transmit");
> +		return -ENOTSUP;
> +	}
> +
> +	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->direction & RTE_ETH_MIRROR_DIRECTION_INGRESS)
> +			ret = ethdev_insert_mirror(&dev->rx_mirror, conf);
> +		if (ret == 0 && (conf->direction & RTE_ETH_MIRROR_DIRECTION_EGRESS))
> +			ret = ethdev_insert_mirror(&dev->tx_mirror, conf);
> +		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 bool
> +ethdev_delete_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top, uint16_t target_id)
> +{
> +	struct rte_eth_mirror *mirror;
> +
> +	while ((mirror = *top) != NULL) {
> +		if (mirror->conf.target == target_id)
> +			goto found;
> +		top = &mirror->next;
> +	}
> +	/* not found in list */
> +	return false;
> +
> +found:
> +	/* unlink from list */
> +	rte_atomic_store_explicit(top, mirror->next, rte_memory_order_relaxed);
> +
> +	/* Defer freeing the mirror until after one second

Perhaps use
/*
  * comment
  */
style (up to you).

> +	 * 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.
> +	 */
> +	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)
> +{
> +	int ret = 0;
> +
> +	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> +	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
> +
> +	RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);
> +
> +	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,
> +	     const struct rte_eth_mirror_conf *conf)
> +{
> +	struct rte_mbuf *tosend[RTE_MIRROR_BURST_SIZE];
> +	unsigned int count = 0;
> +	unsigned int i;
> +
> +	for (i = 0; i < nb_pkts; i++) {
> +		struct rte_mbuf *m = pkts[i];
> +		struct rte_mbuf *mc;
> +
> +		if (conf->flags & RTE_ETH_MIRROR_INDIRECT_FLAG) {
> +			mc = rte_pktmbuf_alloc(conf->mp);

Can 'rte_pktmbuf_alloc_bulk' be used prior to the 'for' loop? I do not insist.

> +			if (unlikely(mc == NULL))
> +				continue;
> +
> +			/* Make both mbuf's point to the same data */
> +			rte_pktmbuf_attach(mc, m);
> +		} else {
> +			mc = rte_pktmbuf_copy(m, conf->mp, 0, conf->snaplen);
> +			/* TODO: dropped stats? */
> +			if (unlikely(mc == NULL))
> +				continue;
> +		}
> +
> +		/* Put info about origin of the packet */
> +		if (conf->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 (conf->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;
> +	}
> +
> +	uint16_t nsent = rte_eth_tx_burst(conf->target, 0, tosend, count);
> +	if (unlikely(nsent < count)) {
> +		uint16_t drop = count - nsent;
> +
> +		/* TODO: need some stats here? */
> +		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,
> +		     const struct rte_eth_mirror *mirror)
> +{
> +	unsigned int i;

Perhaps move this to the inside of 'while' below.

> +
> +	while (mirror != NULL) {
> +		for (i = 0; i < nb_pkts; i += RTE_MIRROR_BURST_SIZE) {
> +			uint16_t left  = nb_pkts - i;
> +			uint16_t burst = RTE_MIN(left, RTE_MIRROR_BURST_SIZE);
> +
> +			eth_dev_mirror(port_id, queue_id, direction,
> +				       pkts + i, burst, &mirror->conf);
> +		}
> +
> +		mirror = rte_atomic_load_explicit(&mirror->next, rte_memory_order_relaxed);
> +	}
> +}
> +
> +#else
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_add_mirror, 25.11)
> +int
> +rte_eth_add_mirror(uint16_t port_id __rte_unused,
> +		   const struct rte_eth_mirror_conf *conf __rte_unused)
> +{
> +	return -ENOTSUP;
> +}
> +
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_remove_mirror, 25.11)
> +int
> +rte_eth_remove_mirror(uint16_t port_id __rte_unused, uint16_t target_id __rte_unused)
> +{
> +	return -ENOTSUP;
> +
> +}
> +#endif /* RTE_ETHDEV_MIRROR */
> diff --git a/lib/ethdev/rte_mirror.h b/lib/ethdev/rte_mirror.h
> new file mode 100644
> index 0000000000..9369309958
> --- /dev/null
> +++ b/lib/ethdev/rte_mirror.h
> @@ -0,0 +1,99 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Stephen Hemminger <stephen at networkplumber.org>
> + */
> +
> +#ifndef RTE_MIRROR_H_
> +#define RTE_MIRROR_H_
> +
> +#include <stdint.h>
> +
> +#include <rte_compat.h>
> +#include <rte_mbuf.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/**
> + * @file
> + * Ethdev port mirroring
> + *
> + * This interface provides the ability to duplicate packets to another port.
> + */
> +
> +/* Definitions for ethdev analyzer direction */
> +#define RTE_ETH_MIRROR_DIRECTION_INGRESS 1
> +#define RTE_ETH_MIRROR_DIRECTION_EGRESS 2
> +
> +/**
> + * @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_eth_mirror_conf {
> +	struct rte_mempool *mp;	/**< Memory pool for copies, If NULL then cloned. */
> +	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 */
> +	uint8_t direction;	/**< bitmask of RTE_ETH_MIRROR_DIRECTION_XXX */
> +};
> +
> +#define RTE_ETH_MIRROR_TIMESTAMP_FLAG 1	/**< insert timestamp into mirrored packet */
> +#define RTE_ETH_MIRROR_ORIGIN_FLAG 2	/**< insert rte_mbuf_origin into mirrored packet */
> +#define RTE_ETH_MIRROR_INDIRECT_FLAG 4  /**< use rte_mbuf_attach rather than copy */
> +
> +#define RTE_ETH_MIRROR_FLAG_MASK 7
> +
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
> + *
> + * Create a Switched Port Analyzer (MIRROR) instance.
> + *
> + * @param port_id
> + *   The port identifier of the source Ethernet device.
> + * @param conf
> + *   Settings for this MIRROR instance..

Redundant 'period' sign at the end.

> + * @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 mirrorning.

Could be a typo: mirror'n'ing.

> + * After this call no more packets will be sent the target port.

Perhaps 'to the target port'? It's up to you.

Thank you.

> + *
> + * @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);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* RTE_MIRROR_H_ */
> -- 
> 2.47.2
>
>


More information about the dev mailing list