* [RFC PATCH] net/bonding: reject control operations in secondary
@ 2026-07-08 17:42 Weijun Pan
2026-07-22 23:10 ` Stephen Hemminger
` (4 more replies)
0 siblings, 5 replies; 33+ messages in thread
From: Weijun Pan @ 2026-07-08 17:42 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor), Anatoly Burakov; +Cc: dev, Weijun Pan
The bonding PMD installs safe secondary burst functions when real
secondary datapath support is not available. This avoids crashes, but
secondary processes must not be able to change bonding control-plane
state if the primary process is the owner of that state.
Reject bonding control-plane operations from secondary processes. This
keeps bonding configuration and LACP state owned by the primary process
and is a prerequisite for future limited secondary datapath support.
Bugzilla ID: 1900
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
drivers/net/bonding/rte_eth_bond_8023ad.c | 31 ++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 47 +++++++++++++++++
drivers/net/bonding/rte_eth_bond_pmd.c | 61 +++++++++++++++++++++++
3 files changed, 139 insertions(+)
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..685255ffc3 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1428,11 +1428,24 @@ rte_eth_bond_8023ad_conf_get(uint16_t port_id,
return 0;
}
+static int
+bond_8023ad_primary_only(const char *op)
+{
+ if (rte_eal_process_type() != RTE_PROC_SECONDARY)
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in secondary process", op);
+ return -ENOTSUP;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_bond_8023ad_agg_selection_set)
int
rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
enum rte_bond_8023ad_agg_selection agg_selection)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
@@ -1506,6 +1519,9 @@ int
rte_eth_bond_8023ad_setup(uint16_t port_id,
struct rte_eth_bond_8023ad_conf *conf)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *bond_dev;
int err;
@@ -1590,6 +1606,9 @@ int
rte_eth_bond_8023ad_ext_collect(uint16_t port_id, uint16_t member_id,
int enabled)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct port *port;
int res;
@@ -1612,6 +1631,9 @@ int
rte_eth_bond_8023ad_ext_distrib(uint16_t port_id, uint16_t member_id,
int enabled)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct port *port;
int res;
@@ -1664,6 +1686,9 @@ int
rte_eth_bond_8023ad_ext_slowtx(uint16_t port_id, uint16_t member_id,
struct rte_mbuf *lacp_pkt)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct port *port;
int res;
@@ -1725,6 +1750,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_8023ad_dedicated_queues_enable)
int
rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
@@ -1754,6 +1782,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_8023ad_dedicated_queues_disable)
int
rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
{
+ if (bond_8023ad_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..3b754bf8e0 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -150,10 +150,24 @@ deactivate_member(struct rte_eth_dev *eth_dev, uint16_t port_id)
}
}
+static int
+bond_api_primary_only(const char *op)
+{
+ if (rte_eal_process_type() != RTE_PROC_SECONDARY)
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in secondary process", op);
+ return -1;
+}
+
+
RTE_EXPORT_SYMBOL(rte_eth_bond_create)
int
rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
struct rte_eth_dev *bond_dev;
char devargs[52];
@@ -193,6 +207,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_free)
int
rte_eth_bond_free(const char *name)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
return rte_vdev_uninit(name);
}
@@ -638,6 +655,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_member_add)
int
rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
@@ -777,6 +797,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_member_remove)
int
rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
int retval;
@@ -800,6 +823,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_mode_set)
int
rte_eth_bond_mode_set(uint16_t bonding_port_id, uint8_t mode)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct rte_eth_dev *bonding_eth_dev;
if (valid_bonding_port_id(bonding_port_id) != 0)
@@ -832,6 +858,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_primary_set)
int
rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
if (valid_bonding_port_id(bonding_port_id) != 0)
@@ -921,6 +950,9 @@ int
rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
struct rte_ether_addr *mac_addr)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
@@ -947,6 +979,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_mac_address_reset)
int
rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
@@ -989,6 +1024,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_xmit_policy_set)
int
rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
if (valid_bonding_port_id(bonding_port_id) != 0)
@@ -1034,6 +1072,9 @@ RTE_EXPORT_SYMBOL(rte_eth_bond_link_monitoring_set)
int
rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
if (valid_bonding_port_id(bonding_port_id) != 0)
@@ -1063,6 +1104,9 @@ rte_eth_bond_link_down_prop_delay_set(uint16_t bonding_port_id,
uint32_t delay_ms)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
if (valid_bonding_port_id(bonding_port_id) != 0)
@@ -1091,6 +1135,9 @@ int
rte_eth_bond_link_up_prop_delay_set(uint16_t bonding_port_id, uint32_t delay_ms)
{
+ if (bond_api_primary_only(__func__) != 0)
+ return -1;
+
struct bond_dev_private *internals;
if (valid_bonding_port_id(bonding_port_id) != 0)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..b6e38d4a60 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2052,9 +2052,22 @@ bond_ethdev_primary_set(struct bond_dev_private *internals,
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev);
+static int
+bond_ethdev_primary_only(const char *op)
+{
+ if (rte_eal_process_type() != RTE_PROC_SECONDARY)
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in secondary process", op);
+ return -ENOTSUP;
+}
+
static int
bond_ethdev_start(struct rte_eth_dev *eth_dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals;
int i;
@@ -2186,6 +2199,9 @@ bond_ethdev_free_queues(struct rte_eth_dev *dev)
int
bond_ethdev_stop(struct rte_eth_dev *eth_dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = eth_dev->data->dev_private;
uint16_t i;
int ret;
@@ -2400,6 +2416,9 @@ bond_ethdev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
static int
bond_ethdev_vlan_filter_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
int res;
uint16_t i;
struct bond_dev_private *internals = dev->data->dev_private;
@@ -2431,6 +2450,9 @@ bond_ethdev_rx_queue_setup(struct rte_eth_dev *dev, uint16_t rx_queue_id,
uint16_t nb_rx_desc, unsigned int socket_id __rte_unused,
const struct rte_eth_rxconf *rx_conf, struct rte_mempool *mb_pool)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_rx_queue *bd_rx_q = (struct bond_rx_queue *)
rte_zmalloc_socket(NULL, sizeof(struct bond_rx_queue),
0, dev->data->numa_node);
@@ -2455,6 +2477,9 @@ bond_ethdev_tx_queue_setup(struct rte_eth_dev *dev, uint16_t tx_queue_id,
uint16_t nb_tx_desc, unsigned int socket_id __rte_unused,
const struct rte_eth_txconf *tx_conf)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_tx_queue *bd_tx_q = (struct bond_tx_queue *)
rte_zmalloc_socket(NULL, sizeof(struct bond_tx_queue),
0, dev->data->numa_node);
@@ -2697,6 +2722,9 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
static int
bond_ethdev_stats_reset(struct rte_eth_dev *dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = dev->data->dev_private;
int i;
int err;
@@ -2714,6 +2742,9 @@ bond_ethdev_stats_reset(struct rte_eth_dev *dev)
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = eth_dev->data->dev_private;
int i;
int ret = 0;
@@ -2768,6 +2799,9 @@ bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev)
static int
bond_ethdev_promiscuous_disable(struct rte_eth_dev *dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = dev->data->dev_private;
int i;
int ret = 0;
@@ -2871,6 +2905,9 @@ bond_ethdev_promiscuous_update(struct rte_eth_dev *dev)
static int
bond_ethdev_allmulticast_enable(struct rte_eth_dev *eth_dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = eth_dev->data->dev_private;
int i;
int ret = 0;
@@ -2925,6 +2962,9 @@ bond_ethdev_allmulticast_enable(struct rte_eth_dev *eth_dev)
static int
bond_ethdev_allmulticast_disable(struct rte_eth_dev *eth_dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct bond_dev_private *internals = eth_dev->data->dev_private;
int i;
int ret = 0;
@@ -3186,6 +3226,9 @@ static int
bond_ethdev_rss_reta_update(struct rte_eth_dev *dev,
struct rte_eth_rss_reta_entry64 *reta_conf, uint16_t reta_size)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
unsigned i, j;
int result = 0;
int member_reta_size;
@@ -3246,6 +3289,9 @@ static int
bond_ethdev_rss_hash_update(struct rte_eth_dev *dev,
struct rte_eth_rss_conf *rss_conf)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
int i, result = 0;
struct bond_dev_private *internals = dev->data->dev_private;
struct rte_eth_rss_conf bond_rss_conf;
@@ -3295,6 +3341,9 @@ bond_ethdev_rss_hash_conf_get(struct rte_eth_dev *dev,
static int
bond_ethdev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *member_eth_dev;
struct bond_dev_private *internals = dev->data->dev_private;
int ret, i;
@@ -3324,6 +3373,9 @@ static int
bond_ethdev_mac_address_set(struct rte_eth_dev *dev,
struct rte_ether_addr *addr)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
if (mac_address_set(dev, addr)) {
RTE_BOND_LOG(ERR, "Failed to update MAC address");
return -EINVAL;
@@ -3345,6 +3397,9 @@ bond_ethdev_mac_addr_add(struct rte_eth_dev *dev,
struct rte_ether_addr *mac_addr,
__rte_unused uint32_t index, uint32_t vmdq)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
struct rte_eth_dev *member_eth_dev;
struct bond_dev_private *internals = dev->data->dev_private;
int ret, i;
@@ -3381,6 +3436,9 @@ bond_ethdev_mac_addr_add(struct rte_eth_dev *dev,
static void
bond_ethdev_mac_addr_remove(struct rte_eth_dev *dev, uint32_t index)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return;
+
struct rte_eth_dev *member_eth_dev;
struct bond_dev_private *internals = dev->data->dev_private;
int i;
@@ -3965,6 +4023,9 @@ bond_remove(struct rte_vdev_device *dev)
static int
bond_ethdev_configure(struct rte_eth_dev *dev)
{
+ if (bond_ethdev_primary_only(__func__) != 0)
+ return -ENOTSUP;
+
const char *name = dev->device->name;
struct bond_dev_private *internals = dev->data->dev_private;
struct rte_kvargs *kvlist = internals->kvlist;
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [RFC PATCH] net/bonding: reject control operations in secondary
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
@ 2026-07-22 23:10 ` Stephen Hemminger
2026-07-26 17:32 ` Stephen Hemminger
` (3 subsequent siblings)
4 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-07-22 23:10 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Wed, 8 Jul 2026 12:42:04 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> +static int
> +bond_ethdev_primary_only(const char *op)
> +{
> + if (rte_eal_process_type() != RTE_PROC_SECONDARY)
> + return 0;
> +
> + RTE_BOND_LOG(ERR, "%s not supported in secondary process", op);
> + return -ENOTSUP;
> +}
> +
Minor nit, the function returns 0 or -ENOTSUP but then caller
always ends up just checking for 0. It might look cleaner as boolean
function or wrap the whole thing as a macro like ethdev does.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [RFC PATCH] net/bonding: reject control operations in secondary
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
2026-07-22 23:10 ` Stephen Hemminger
@ 2026-07-26 17:32 ` Stephen Hemminger
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
` (2 subsequent siblings)
4 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-07-26 17:32 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Wed, 8 Jul 2026 12:42:04 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> The bonding PMD installs safe secondary burst functions when real
> secondary datapath support is not available. This avoids crashes, but
> secondary processes must not be able to change bonding control-plane
> state if the primary process is the owner of that state.
>
> Reject bonding control-plane operations from secondary processes. This
> keeps bonding configuration and LACP state owned by the primary process
> and is a prerequisite for future limited secondary datapath support.
>
> Bugzilla ID: 1900
>
> Signed-off-by: Weijun Pan <wpan3636@gmail.com>
> ---
This is the right path, but AI review found things that need work.
Review: [RFC PATCH] net/bonding: reject control operations in secondary
Message-Id: <20260708174204.72574-1-wpan3636@gmail.com>
Verified against DPDK main (38f72e500b3b). Applies cleanly, builds clean
with -Dwerror=true, checkpatches.sh reports no issues.
The intent is right, but the checks are placed one layer too low, which
means the stated goal -- "secondary processes must not be able to change
bonding control-plane state" -- is not actually met for dev_configure,
and one legitimate secondary path is broken.
Errors
------
1. rte_eth_bond_pmd.c: bond_ethdev_configure()
Returning -ENOTSUP from the dev_configure op does not prevent the
secondary from mutating shared state -- it makes it worse.
rte_eth_dev_configure() has already done this before it calls the PMD
(lib/ethdev/rte_ethdev.c):
1364 memcpy(&orig_conf, &dev->data->dev_conf, ...)
1587 diag = eth_dev_rx_queue_config(dev, nb_rx_q);
1596 diag = eth_dev_tx_queue_config(dev, nb_tx_q);
1606 diag = dev->dev_ops->dev_configure(dev);
1646 reset_queues:
1647 eth_dev_rx_queue_config(dev, 0);
1648 eth_dev_tx_queue_config(dev, 0);
On the -ENOTSUP the code jumps to reset_queues, and
eth_dev_rx_queue_config(dev, 0) calls dev_ops->rx_queue_release() on
every queue and rte_free()s dev->data->rx_queues, then sets
nb_rx_queues = 0 (lib/ethdev/ethdev_private.c:455). Those are the
primary's queues in shared memory. bond_ethdev_rx_queue_release() /
bond_ethdev_tx_queue_release() are not gated by this patch, so they
run.
Net effect: a stray rte_eth_dev_configure() from a secondary now
destroys the primary's Rx/Tx queue arrays instead of being rejected.
2. rte_eth_bond_api.c: rte_eth_bond_free()
This blocks the only supported way for a secondary to detach its
local port. rte_vdev_uninit() is process-local and works in a
secondary; bond_remove() has a deliberate secondary branch
(rte_eth_bond_pmd.c:4003):
if (rte_eal_process_type() != RTE_PROC_PRIMARY)
return rte_eth_dev_release_port(eth_dev);
That branch is now unreachable through the public API. Compare
bond_ethdev_close() ten lines away (2310), which returns 0 rather
than an error for non-primary, and the comment in
rte_eth_dev_close() explaining that a secondary must be able to
close to release its process-private resources. Teardown paths need
to stay callable; only reconfiguration should be rejected.
Warnings
--------
3. Structural: use a separate ops table rather than 17 in-function
checks.
bond_probe() already installs the ops table for the secondary
explicitly (rte_eth_bond_pmd.c:3889):
eth_dev->dev_ops = &default_dev_ops;
ethdev already returns -ENOTSUP for every one of these ops when the
pointer is NULL -- dev_configure (1347), dev_start (1801),
rx_queue_setup (2307), promiscuous_enable (3027), mtu_set (4400),
reta_update (5007), mac_addr_add (5415), mac_addr_remove (5479),
and so on -- and it does so *before* touching dev->data, which is
exactly what fixes finding 1. Defining
static const struct eth_dev_ops secondary_dev_ops = {
.dev_close = bond_ethdev_close,
.dev_infos_get = bond_ethdev_info,
.link_update = bond_ethdev_link_update,
.stats_get = bond_ethdev_stats_get,
.reta_query = bond_ethdev_rss_reta_query,
.rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
.eth_dev_priv_dump = bond_ethdev_priv_dump,
};
and assigning it in the secondary branch of bond_probe() gets you
identical semantics with one hunk instead of eighteen, and no
runtime check on the primary's path.
4. Wrong polarity. All three helpers test
rte_eal_process_type() != RTE_PROC_SECONDARY
The established idiom is != RTE_PROC_PRIMARY: 357 occurrences under
drivers/ against 3 of the form used here, and both existing checks
in this driver (bond_ethdev_close at 2310, bond_remove at 4003) use
the primary form. As written, RTE_PROC_AUTO and RTE_PROC_INVALID
fall through to the permissive path.
5. Three byte-identical static helpers in three files
(bond_8023ad_primary_only, bond_api_primary_only,
bond_ethdev_primary_only). One static inline in
eth_bond_private.h.
6. The helper's return value is computed and discarded:
if (bond_8023ad_primary_only(__func__) != 0)
return -ENOTSUP;
Either propagate it (ret = ...; if (ret != 0) return ret;) or make
the helper return bool and name it accordingly.
7. Coverage gaps. flow_ops_get is not gated, so rte_flow_create() /
rte_flow_destroy() from a secondary still mutate bonding state.
rx_queue_release / tx_queue_release are ungated while the
corresponding setup ops are gated -- see finding 1 for why that
combination bites.
8. No documentation. This changes the observable behaviour of exported
API, but doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
says nothing about multi-process, and
doc/guides/rel_notes/release_26_07.rst is not touched. The guide
needs a short section stating which operations are primary-only.
9. Bugzilla ID 1900 is cited with no Fixes: tag and no
Cc: stable@dpdk.org. If 1900 is a crash being fixed, both are
needed. If this is groundwork for secondary datapath support, say
so and drop the implication.
Info
----
10. Statements are inserted ahead of the declarations in every touched
function. Legal under c11, but it inverts the DPDK function layout
(declarations, blank line, statements) throughout. Folding the
check into the existing declaration block would avoid it; the ops
table in finding 3 avoids it entirely. Also, rte_eth_bond_api.c
gains a double blank line after bond_api_primary_only().
11. rte_eth_dev_stop() resets that process's fast-path ops to the dummy
functions before calling dev_stop (rte_ethdev.c:1819). With
bond_ethdev_stop() now returning -ENOTSUP, a secondary that calls
stop ends up with a dead local datapath, dev_started still 1, and
no way to restart since dev_start is also gated.
Given findings 1 and 2, no Reviewed-by on this revision.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [RFC PATCH v2] net/bonding: restrict secondary control operations
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
2026-07-22 23:10 ` Stephen Hemminger
2026-07-26 17:32 ` Stephen Hemminger
@ 2026-08-23 15:16 ` Weijun Pan
2026-08-23 15:43 ` Stephen Hemminger
` (2 more replies)
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
4 siblings, 3 replies; 33+ messages in thread
From: Weijun Pan @ 2026-08-23 15:16 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor), Anatoly Burakov; +Cc: dev, Weijun Pan
The bonding PMD currently supports secondary attach with safe fallback
burst functions, but bonding configuration and LACP state are owned by
the primary process.
Use a secondary-specific dev_ops table so unsupported operations are
rejected by ethdev before PMD callbacks can mutate shared device state.
Also reject bonding-specific control APIs from non-primary processes,
while keeping secondary detach and query paths available.
This keeps secondary process behavior safe while leaving room for future
limited datapath support.
Bugzilla ID: 1900
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
.../link_bonding_poll_mode_drv_lib.rst | 13 ++++++
doc/guides/rel_notes/release_26_11.rst | 6 +++
drivers/net/bonding/eth_bond_private.h | 8 ++++
drivers/net/bonding/rte_eth_bond_8023ad.c | 45 +++++++++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 44 ++++++++++++++++++
drivers/net/bonding/rte_eth_bond_pmd.c | 12 ++++-
6 files changed, 127 insertions(+), 1 deletion(-)
diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
index 2fa1ac4028..7ecb05d407 100644
--- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
+++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
@@ -254,6 +254,19 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
that are assumed not to be invoked in parallel on different logical cores to
work on the same target object.
+Bonding device configuration is owned by the primary process. Secondary
+processes may attach to an existing bonding device for query and detach
+operations, but must not change bonding configuration or device state.
+
+In a secondary process, bonding control operations such as configuring,
+starting or stopping the device, setting up queues, changing members,
+changing the bonding mode, updating RSS, changing MAC addresses, changing
+MTU, or configuring ``rte_flow`` rules are not supported.
+
+Secondary process datapath support is limited and bonding mode specific.
+Applications should not rely on secondary processes for bonding datapath
+operation unless support for the selected mode is explicitly documented.
+
It should also be noted that the PMD receive function should not be invoked
directly on a member devices after they have been to a bonding device since
packets read directly from the member device will no longer be available to the
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..8df012c99b 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Updated bonding PMD secondary process handling.**
+
+ Bonding devices now use a secondary-process device operations table to
+ keep query and detach paths available while rejecting unsupported control
+ operations before shared ethdev state can be modified. Bonding-specific
+ control APIs are also restricted to the primary process.
Removed Items
-------------
diff --git a/drivers/net/bonding/eth_bond_private.h b/drivers/net/bonding/eth_bond_private.h
index 378bbba4e6..750e5925df 100644
--- a/drivers/net/bonding/eth_bond_private.h
+++ b/drivers/net/bonding/eth_bond_private.h
@@ -7,12 +7,14 @@
#include <stdint.h>
#include <sys/queue.h>
+#include <stdbool.h>
#include <ethdev_driver.h>
#include <rte_flow.h>
#include <rte_spinlock.h>
#include <rte_bitmap.h>
#include <rte_flow_driver.h>
+#include <rte_eal.h>
#include "rte_eth_bond.h"
#include "eth_bond_8023ad_private.h"
@@ -212,6 +214,12 @@ find_member_by_id(uint16_t *members, uint16_t members_count, uint16_t member_id)
return pos;
}
+static inline bool
+bond_process_is_primary(void)
+{
+ return rte_eal_process_type() == RTE_PROC_PRIMARY;
+}
+
int
valid_port_id(uint16_t port_id);
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..e3d02f0539 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1428,6 +1428,16 @@ rte_eth_bond_8023ad_conf_get(uint16_t port_id,
return 0;
}
+static int
+bond_8023ad_check_primary(const char *op)
+{
+ if (bond_process_is_primary())
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in non-primary process", op);
+ return -ENOTSUP;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_bond_8023ad_agg_selection_set)
int
rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
@@ -1436,6 +1446,11 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
@@ -1508,6 +1523,11 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
{
struct rte_eth_dev *bond_dev;
int err;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
@@ -1592,6 +1612,11 @@ rte_eth_bond_8023ad_ext_collect(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1614,6 +1639,11 @@ rte_eth_bond_8023ad_ext_distrib(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1666,6 +1696,11 @@ rte_eth_bond_8023ad_ext_slowtx(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1727,6 +1762,11 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1756,6 +1796,11 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_8023ad_check_primary(__func__);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..c4f055bfa5 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -150,6 +150,17 @@ deactivate_member(struct rte_eth_dev *eth_dev, uint16_t port_id)
}
}
+static int
+bond_api_check_primary(const char *op)
+{
+ if (bond_process_is_primary())
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in non-primary process", op);
+ return -1;
+}
+
+
RTE_EXPORT_SYMBOL(rte_eth_bond_create)
int
rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
@@ -159,6 +170,9 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -643,6 +657,9 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
int retval;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +798,9 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -802,6 +822,9 @@ rte_eth_bond_mode_set(uint16_t bonding_port_id, uint8_t mode)
{
struct rte_eth_dev *bonding_eth_dev;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -834,6 +857,9 @@ rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -924,6 +950,9 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -950,6 +979,9 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -991,6 +1023,9 @@ rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1036,6 +1071,9 @@ rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1065,6 +1103,9 @@ rte_eth_bond_link_down_prop_delay_set(uint16_t bonding_port_id,
{
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1093,6 +1134,9 @@ rte_eth_bond_link_up_prop_delay_set(uint16_t bonding_port_id, uint32_t delay_ms)
{
struct bond_dev_private *internals;
+ if (bond_api_check_primary(__func__) != 0)
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..f7fb562cee 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -3644,6 +3644,16 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
return 0;
}
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+ .eth_dev_priv_dump = bond_ethdev_priv_dump,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3828,7 +3838,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v2] net/bonding: restrict secondary control operations
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
@ 2026-08-23 15:43 ` Stephen Hemminger
2026-08-24 2:39 ` Weijun Pan
2026-08-24 16:15 ` Stephen Hemminger
2026-08-26 16:10 ` [RFC PATCH v3] " Weijun Pan
2 siblings, 1 reply; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-23 15:43 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Sun, 23 Aug 2026 10:16:25 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> index 2fa1ac4028..7ecb05d407 100644
> --- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> +++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> @@ -254,6 +254,19 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
> that are assumed not to be invoked in parallel on different logical cores to
> work on the same target object.
>
> +Bonding device configuration is owned by the primary process. Secondary
> +processes may attach to an existing bonding device for query and detach
> +operations, but must not change bonding configuration or device state.
> +
> +In a secondary process, bonding control operations such as configuring,
> +starting or stopping the device, setting up queues, changing members,
> +changing the bonding mode, updating RSS, changing MAC addresses, changing
> +MTU, or configuring ``rte_flow`` rules are not supported.
> +
> +Secondary process datapath support is limited and bonding mode specific.
> +Applications should not rely on secondary processes for bonding datapath
> +operation unless support for the selected mode is explicitly documented.
> +
> It should also be noted that the PMD receive function should not be invoked
> directly on a member devices after they have been to a bonding device since
> packets read directly from the member device will no longer be available to the
> diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
> index c8cc86295d..8df012c99b 100644
> --- a/doc/guides/rel_notes/release_26_11.rst
> +++ b/doc/guides/rel_notes/release_26_11.rst
> @@ -55,6 +55,12 @@ New Features
> Also, make sure to start the actual text at the margin.
> =======================================================
>
> +* **Updated bonding PMD secondary process handling.**
> +
> + Bonding devices now use a secondary-process device operations table to
> + keep query and detach paths available while rejecting unsupported control
> + operations before shared ethdev state can be modified. Bonding-specific
> + control APIs are also restricted to the primary process.
>
> Removed Items
Mind if I shorten this text, it reads like AI autogenerated verbosity.
I.e 10x longer than needed. Release notes especially should be succinct.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v2] net/bonding: restrict secondary control operations
2026-08-23 15:43 ` Stephen Hemminger
@ 2026-08-24 2:39 ` Weijun Pan
0 siblings, 0 replies; 33+ messages in thread
From: Weijun Pan @ 2026-08-24 2:39 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
Sure, please feel free to shorten it.
Thanks,
Weijun
On Sun, Aug 23, 2026 at 10:43 AM Stephen Hemminger
<stephen@networkplumber.org> wrote:
>
> On Sun, 23 Aug 2026 10:16:25 -0500
> Weijun Pan <wpan3636@gmail.com> wrote:
>
> > diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> > index 2fa1ac4028..7ecb05d407 100644
> > --- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> > +++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> > @@ -254,6 +254,19 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
> > that are assumed not to be invoked in parallel on different logical cores to
> > work on the same target object.
> >
> > +Bonding device configuration is owned by the primary process. Secondary
> > +processes may attach to an existing bonding device for query and detach
> > +operations, but must not change bonding configuration or device state.
> > +
> > +In a secondary process, bonding control operations such as configuring,
> > +starting or stopping the device, setting up queues, changing members,
> > +changing the bonding mode, updating RSS, changing MAC addresses, changing
> > +MTU, or configuring ``rte_flow`` rules are not supported.
> > +
> > +Secondary process datapath support is limited and bonding mode specific.
> > +Applications should not rely on secondary processes for bonding datapath
> > +operation unless support for the selected mode is explicitly documented.
> > +
> > It should also be noted that the PMD receive function should not be invoked
> > directly on a member devices after they have been to a bonding device since
> > packets read directly from the member device will no longer be available to the
> > diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
> > index c8cc86295d..8df012c99b 100644
> > --- a/doc/guides/rel_notes/release_26_11.rst
> > +++ b/doc/guides/rel_notes/release_26_11.rst
> > @@ -55,6 +55,12 @@ New Features
> > Also, make sure to start the actual text at the margin.
> > =======================================================
> >
> > +* **Updated bonding PMD secondary process handling.**
> > +
> > + Bonding devices now use a secondary-process device operations table to
> > + keep query and detach paths available while rejecting unsupported control
> > + operations before shared ethdev state can be modified. Bonding-specific
> > + control APIs are also restricted to the primary process.
> >
> > Removed Items
>
> Mind if I shorten this text, it reads like AI autogenerated verbosity.
> I.e 10x longer than needed. Release notes especially should be succinct.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v2] net/bonding: restrict secondary control operations
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-23 15:43 ` Stephen Hemminger
@ 2026-08-24 16:15 ` Stephen Hemminger
2026-08-26 16:10 ` [RFC PATCH v3] " Weijun Pan
2 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-24 16:15 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Sun, 23 Aug 2026 10:16:25 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> The bonding PMD currently supports secondary attach with safe fallback
> burst functions, but bonding configuration and LACP state are owned by
> the primary process.
>
> Use a secondary-specific dev_ops table so unsupported operations are
> rejected by ethdev before PMD callbacks can mutate shared device state.
> Also reject bonding-specific control APIs from non-primary processes,
> while keeping secondary detach and query paths available.
>
> This keeps secondary process behavior safe while leaving room for future
> limited datapath support.
>
> Bugzilla ID: 1900
>
> Signed-off-by: Weijun Pan <wpan3636@gmail.com>
> ---
More indepth AI review with Fable saw some possible issues.
Review: [RFC PATCH v2] net/bonding: restrict secondary control operations
Applies cleanly to main, builds with -Dwerror=true. The secondary
dev_ops table is a good approach; everything left in it is read-only
against shared memory, and bond_ethdev_close() already returns early
for non-primary so dev_close stays safe.
Warning: prog guide describes a secondary datapath that does not exist
"Secondary process datapath support is limited and bonding mode
specific ... unless support for the selected mode is explicitly
documented."
bond_probe() installs bond_ethdev_rx_secondary() (returns 0) and
bond_ethdev_tx_secondary() (frees and returns nb_pkts) for every
mode. There is no mode with datapath support. Say that plainly:
Rx and Tx are not supported on a bonding device in a secondary
process; receive returns no packets and transmit drops them.
Warning: LACP query paths return zeroed state in a secondary process
bond_mode_8023ad_ports[] is a plain global array, populated only in
the primary. rte_eth_bond_8023ad_member_info(), _ext_collect_get()
and _ext_distrib_get() validate against shared internals (which
pass), then read actor/partner state from the secondary's untouched
copy and return all zeros with rc 0. bond_ethdev_priv_dump(), kept
in secondary_dev_ops, prints the same zeros through dump_lacp().
Since the patch's premise is that query paths are safe in a
secondary, either give these three the same bond_8023ad_check_primary()
guard (and drop eth_dev_priv_dump from secondary_dev_ops or make
dump_lacp() skip in secondary), or document that LACP per-member
state is only visible to the primary.
Warning: rte_eth_bond_api.c: two blank lines after
bond_api_check_primary(). checkpatch will flag it.
Info: three spellings of the same test
rte_eth_bond_pmd.c already open-codes rte_eal_process_type() in
bond_ethdev_mode_set(), bond_ethdev_close(), bond_probe() and
bond_remove(). This patch adds bond_process_is_primary() plus two
near-identical logging wrappers with different return values (-1
and -ENOTSUP). The return values match each file's conventions, so
not wrong, but one helper in eth_bond_private.h taking the error
code would remove the duplication.
Info: link_update writes shared state from the secondary
bond_ethdev_link_update() assigns ethdev->data->dev_link fields
directly. Keeping it in secondary_dev_ops means a secondary calling
rte_eth_link_get() races the primary on that shared struct.
Pre-existing behaviour, but worth a thought given the patch's
"must not change device state" rule.
----------------------------------------------------------------------
Suggested commit message (the Bugzilla entry has the background;
no need to restate it):
net/bonding: restrict secondary control operations
Bonding configuration and LACP state are owned by the primary
process. Install a reduced dev_ops table in secondary processes so
ethdev rejects control operations, and reject the bonding control
API when called from a non-primary process. Query and detach remain
available.
Bugzilla ID: 1900
----------------------------------------------------------------------
Suggested release note:
* **Restricted bonding device control to the primary process.**
Secondary processes can query and detach a bonding device but can no
longer change its configuration.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [RFC PATCH v3] net/bonding: restrict secondary control operations
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-23 15:43 ` Stephen Hemminger
2026-08-24 16:15 ` Stephen Hemminger
@ 2026-08-26 16:10 ` Weijun Pan
2026-08-26 17:53 ` Stephen Hemminger
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
2 siblings, 2 replies; 33+ messages in thread
From: Weijun Pan @ 2026-08-26 16:10 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor), Anatoly Burakov; +Cc: dev, Weijun Pan
Bonding configuration and LACP state are owned by the primary
process. Install a reduced dev_ops table in secondary processes so
ethdev rejects control operations, and reject the bonding control
API when called from a non-primary process. Query and detach remain
available.
Bugzilla ID: 1900
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
v3:
- Document that secondary bonding Rx/Tx are unsupported.
- Reject secondary LACP runtime state queries.
- Remove private dump from secondary dev_ops.
- Use a common primary-process helper.
- Shorten release notes.
.../link_bonding_poll_mode_drv_lib.rst | 13 +++++
doc/guides/rel_notes/release_26_11.rst | 4 ++
drivers/net/bonding/eth_bond_private.h | 12 +++++
drivers/net/bonding/rte_eth_bond_8023ad.c | 48 +++++++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 53 ++++++++++++++++++-
drivers/net/bonding/rte_eth_bond_pmd.c | 11 +++-
6 files changed, 139 insertions(+), 2 deletions(-)
diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
index 2fa1ac4028..8e602e51b1 100644
--- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
+++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
@@ -254,6 +254,19 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
that are assumed not to be invoked in parallel on different logical cores to
work on the same target object.
+Bonding device configuration and LACP runtime state are owned by the primary
+process. Secondary processes may attach to an existing bonding device for
+supported query and detach operations, but control operations are restricted
+to the primary process.
+
+In a secondary process, bonding control operations such as configuring,
+starting or stopping the device, setting up queues, changing members,
+changing the bonding mode, updating RSS, changing MAC addresses, changing
+MTU, or configuring ``rte_flow`` rules are not supported.
+
+Rx and Tx are not supported on a bonding device in a secondary process;
+receive returns no packets and transmit drops packets.
+
It should also be noted that the PMD receive function should not be invoked
directly on a member devices after they have been to a bonding device since
packets read directly from the member device will no longer be available to the
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..1d03e3ee9b 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,10 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Restricted bonding device control to the primary process.**
+
+ Supported query and detach paths remain available to secondary processes,
+ while bonding device configuration changes are rejected.
Removed Items
-------------
diff --git a/drivers/net/bonding/eth_bond_private.h b/drivers/net/bonding/eth_bond_private.h
index 378bbba4e6..526bcd0363 100644
--- a/drivers/net/bonding/eth_bond_private.h
+++ b/drivers/net/bonding/eth_bond_private.h
@@ -7,12 +7,14 @@
#include <stdint.h>
#include <sys/queue.h>
+#include <stdbool.h>
#include <ethdev_driver.h>
#include <rte_flow.h>
#include <rte_spinlock.h>
#include <rte_bitmap.h>
#include <rte_flow_driver.h>
+#include <rte_eal.h>
#include "rte_eth_bond.h"
#include "eth_bond_8023ad_private.h"
@@ -212,6 +214,16 @@ find_member_by_id(uint16_t *members, uint16_t members_count, uint16_t member_id)
return pos;
}
+static inline int
+bond_check_primary(const char *op, int err)
+{
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY)
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in non-primary process", op);
+ return err;
+}
+
int
valid_port_id(uint16_t port_id);
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..65f417a444 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1436,6 +1436,11 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
@@ -1508,6 +1513,11 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
{
struct rte_eth_dev *bond_dev;
int err;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
@@ -1531,6 +1541,11 @@ rte_eth_bond_8023ad_member_info(uint16_t port_id, uint16_t member_id,
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct port *port;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (info == NULL || valid_bonding_port_id(port_id) != 0 ||
rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
@@ -1592,6 +1607,11 @@ rte_eth_bond_8023ad_ext_collect(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1614,6 +1634,11 @@ rte_eth_bond_8023ad_ext_distrib(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1636,6 +1661,10 @@ rte_eth_bond_8023ad_ext_distrib_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ err = bond_check_primary(__func__, -ENOTSUP);
+ if (err != 0)
+ return err;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1651,6 +1680,10 @@ rte_eth_bond_8023ad_ext_collect_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ err = bond_check_primary(__func__, -ENOTSUP);
+ if (err != 0)
+ return err;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1666,6 +1699,11 @@ rte_eth_bond_8023ad_ext_slowtx(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1727,6 +1765,11 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1756,6 +1799,11 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..029e141d89 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -159,6 +159,10 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -640,9 +644,12 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
-
int retval;
+ retval = bond_check_primary(__func__, -1);
+ if (retval != 0)
+ return retval;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +788,10 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ retval = bond_check_primary(__func__, -1);
+ if (retval != 0)
+ return retval;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -801,6 +812,11 @@ int
rte_eth_bond_mode_set(uint16_t bonding_port_id, uint8_t mode)
{
struct rte_eth_dev *bonding_eth_dev;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -833,6 +849,11 @@ int
rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -923,6 +944,11 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -949,6 +975,11 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -990,6 +1021,11 @@ int
rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1035,6 +1071,11 @@ int
rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1064,6 +1105,11 @@ rte_eth_bond_link_down_prop_delay_set(uint16_t bonding_port_id,
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1092,6 +1138,11 @@ rte_eth_bond_link_up_prop_delay_set(uint16_t bonding_port_id, uint32_t delay_ms)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..b250d01445 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -3644,6 +3644,15 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
return 0;
}
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3828,7 +3837,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v3] net/bonding: restrict secondary control operations
2026-08-26 16:10 ` [RFC PATCH v3] " Weijun Pan
@ 2026-08-26 17:53 ` Stephen Hemminger
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
1 sibling, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-26 17:53 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Wed, 26 Aug 2026 11:10:09 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> Bonding configuration and LACP state are owned by the primary
> process. Install a reduced dev_ops table in secondary processes so
> ethdev rejects control operations, and reject the bonding control
> API when called from a non-primary process. Query and detach remain
> available.
>
> Bugzilla ID: 1900
>
> Signed-off-by: Weijun Pan <wpan3636@gmail.com>
> ---
I would like to see link update use rte_eth_linkstatus_set() helper.
And do another check that documentation matches reality after this patch.
Overly verbose (sorry) AI review was:
Applies cleanly to main (d55ccd4), diffstat matches, and builds with
-Dwerror=true for net/bonding. checkpatches.sh is clean.
Fixed since v2
--------------
- rte_eth_bond_8023ad_member_info(), _ext_collect_get() and
_ext_distrib_get() are now rejected in a secondary, so they no
longer return all-zero LACP state read out of the process-local
bond_mode_8023ad_ports[].
- eth_dev_priv_dump is no longer in the secondary ops table, so
dump_lacp() cannot walk that array from a secondary either.
- The two near-identical primary-process helpers are collapsed into
bond_check_primary().
- The stray double blank line in rte_eth_bond_member_add() is gone.
- The programmer's guide now states the secondary datapath is a
stub, which matches bond_ethdev_rx_secondary() and
bond_ethdev_tx_secondary().
The set of API gated in this version looks right to me. Every
mutator in rte_eth_bond_api.c and rte_eth_bond_8023ad.c is covered,
rte_eth_bond_free() is deliberately left open for detach, and the
getters that are left ungated (mode_get, primary_get, members_get,
xmit_policy_get, 8023ad_conf_get, 8023ad_agg_selection_get, ...) all
read bond_dev_private, which is in shared memory. Omitting dev_stop
from secondary_dev_ops is also correct: rte_eth_dev_stop() writes
dev->data->dev_started, and rte_eth_dev_close() has its own
secondary path so detach still works.
Warnings
--------
1. link_update is retained in secondary_dev_ops, so dev_link is now
written from two processes without using the ethdev accessors.
bond_ethdev_link_update() assigns ethdev->data->dev_link.* field by
field, and dev_link lives in the shared rte_eth_dev_data. With
.link_update present, any rte_eth_link_get() in a secondary
republishes the primary's link record:
if (dev->data->dev_conf.intr_conf.lsc && dev->data->dev_started)
rte_eth_linkstatus_get(dev, eth_link);
else {
if (dev->dev_ops->link_update == NULL)
return -ENOTSUP;
dev->dev_ops->link_update(dev, 1);
*eth_link = dev->data->dev_link;
}
Readers on the LSC path use rte_eth_linkstatus_get(), a 64-bit
atomic load, so a reader can observe a torn combination of
link_status, link_speed and link_duplex.
The driver should use rte_eth_linkstatus_set() rather than touching
dev->data->dev_link directly. That is one atomic exchange of the
whole word, which closes the window on the primary path as well and
makes the secondary's recomputation harmless -- it derives the same
value from the same shared member state. Build the result locally
and publish once:
struct rte_eth_link link;
rte_eth_linkstatus_get(ethdev, &link);
link.link_speed = RTE_ETH_SPEED_NUM_NONE;
if (ethdev->data->dev_started == 0 ||
bond_ctx->active_member_count == 0) {
link.link_status = RTE_ETH_LINK_DOWN;
goto out;
}
link.link_status = RTE_ETH_LINK_UP;
...
out:
rte_eth_linkstatus_set(ethdev, &link);
return 0;
Seeding from rte_eth_linkstatus_get() keeps link_duplex and
link_autoneg for the modes that never set them; the interior
"return 0" paths become "goto out".
The rest of the driver has the same problem and is worth a
preparatory patch: bond_ethdev_start() and bond_ethdev_stop() set
link_status directly (rte_eth_bond_pmd.c:2068, 2220), as does
bond_ethdev_lsc_event_callback() at 3101, and
bond_ethdev_member_link_status_change() writes link_autoneg and
link_duplex at 1439. Nothing in bonding currently uses the
accessors.
2. The documentation does not match what the code now rejects.
The new prog_guide paragraph lists configure, start/stop, queue
setup, member changes, mode, RSS, MAC, MTU and rte_flow. It omits
everything else the patch turns into an error in a secondary:
- all of rte_eth_bond_8023ad_* except conf_get and
agg_selection_get, including member_info(),
ext_collect_get() and ext_distrib_get(), which are queries
- dedicated queue enable/disable
- xmit policy, link monitoring interval, link up/down
propagation delays, primary member selection
- stats reset, promiscuous and allmulticast, VLAN filter,
private dump
The commit message and the release note both say "query ... remain
available", and the guide says "supported query and detach
operations", so a reader would reasonably conclude
rte_eth_bond_8023ad_member_info() still works in a secondary. It
no longer does. Please say explicitly which queries survive
(device info, stats, link, RETA and RSS hash config, bonding mode,
members, and the LACP configuration) and that LACP runtime state
queries do not.
Worth stating the supported teardown too: rte_eth_dev_stop()
returns -ENOTSUP in a secondary and rte_eth_dev_close() is the
detach call. A secondary that does the usual stop-then-close will
now see an error from the stop.
3. Release note is in the wrong section.
This changes the behaviour of already-exported functions -- calls
that previously returned 0 now return -ENOTSUP or -1 -- so it
belongs under "API Changes", not "New Features". Same comment as
on v2.
4. bond_ethdev_stats_get() ignores the rte_eth_stats_get() return.
Pre-existing, but the patch puts .stats_get in secondary_dev_ops,
which makes the failure case realistic:
struct rte_eth_stats member_stats;
...
for (i = 0; i < internals->member_count; i++) {
rte_eth_stats_get(internals->members[i].port_id,
&member_stats);
stats->ipackets += member_stats.ipackets;
eth_stats_qstats_get() runs RTE_ETH_VALID_PORTID_OR_ERR_RET before
the memset, so on -ENODEV member_stats is left untouched. On the
first iteration that is uninitialized stack; on later iterations it
is the previous member's counters, double-counted. A secondary
that attached to the bonding device but did not probe the member
ports (blocklist, or members added by the primary after the
secondary started) hits exactly this.
ret = rte_eth_stats_get(internals->members[i].port_id,
&member_stats);
if (ret != 0)
continue;
Better as a separate fix ahead of this patch, since it is not
secondary-specific.
Info
----
- Three of the 8023ad functions declare a second int purely for the
new check while an existing one is right there:
struct port *port;
int res;
int ret;
ret = bond_check_primary(__func__, -ENOTSUP);
if (ret != 0)
return ret;
ext_distrib_get() and ext_collect_get() reuse err instead, which
reads better. Reusing res in ext_collect(), ext_distrib() and
ext_slowtx() would drop three declarations and make the series
consistent.
- rte_eth_bond_api.c passes -1 and rte_eth_bond_8023ad.c passes
-ENOTSUP. Each matches its own file's existing convention, so this
is defensible, but a caller cannot distinguish "wrong process type"
from "bad port id" in the api.c cases. -ENOTSUP everywhere would
be clearer if you are willing to change those return values.
- The <stdbool.h> addition to eth_bond_private.h is unrelated to this
change; bool is already used at line 184 and was working by
transitive include. It is a real fix, just not this patch's.
- Dropping eth_dev_priv_dump entirely costs some debuggability. Only
dump_lacp() touches the process-local array; dump_basic() reads
bond_dev_private, which is shared. Keeping .eth_dev_priv_dump in
secondary_dev_ops and skipping the LACP section in a secondary
would let a secondary still dump mode, members and offloads.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats
2026-08-26 16:10 ` [RFC PATCH v3] " Weijun Pan
2026-08-26 17:53 ` Stephen Hemminger
@ 2026-08-30 1:14 ` Weijun Pan
2026-08-30 1:14 ` [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations Weijun Pan
` (2 more replies)
1 sibling, 3 replies; 33+ messages in thread
From: Weijun Pan @ 2026-08-30 1:14 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor); +Cc: dev, Weijun Pan
bond_ethdev_stats_get() accumulates statistics from each bonding
member. If rte_eth_stats_get() fails for a member, the local
member_stats structure may be left unchanged.
Skip members whose statistics cannot be read, instead of accumulating
stale or uninitialized counters.
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
v4:
- Add this preparatory fix as a separate patch.
drivers/net/bonding/rte_eth_bond_pmd.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..6f10dbb0c7 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2677,9 +2677,12 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
struct bond_dev_private *internals = dev->data->dev_private;
struct rte_eth_stats member_stats;
int i;
+ int ret;
for (i = 0; i < internals->member_count; i++) {
- rte_eth_stats_get(internals->members[i].port_id, &member_stats);
+ ret = rte_eth_stats_get(internals->members[i].port_id, &member_stats);
+ if (ret != 0)
+ continue;
stats->ipackets += member_stats.ipackets;
stats->opackets += member_stats.opackets;
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
@ 2026-08-30 1:14 ` Weijun Pan
2026-08-30 4:29 ` Stephen Hemminger
2026-08-30 4:22 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Stephen Hemminger
2026-08-30 16:35 ` [RFC PATCH v5 " Weijun Pan
2 siblings, 1 reply; 33+ messages in thread
From: Weijun Pan @ 2026-08-30 1:14 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor), Anatoly Burakov; +Cc: dev, Weijun Pan
Bonding configuration and LACP runtime state are owned by the primary
process. Install a reduced dev_ops table in secondary processes so
ethdev rejects control operations, and reject bonding control APIs
when called from a non-primary process.
Supported query and detach paths remain available, while LACP runtime
state queries are restricted to the primary process.
Bugzilla ID: 1900
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
v4:
- Use rte_eth_linkstatus_set() in link update.
- Document supported secondary query and detach paths.
- Move the release note to API Changes.
- Keep secondary private dump but skip LACP state.
.../link_bonding_poll_mode_drv_lib.rst | 30 ++++++++++
doc/guides/rel_notes/release_26_11.rst | 6 ++
drivers/net/bonding/eth_bond_private.h | 12 ++++
drivers/net/bonding/rte_eth_bond_8023ad.c | 48 +++++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 53 ++++++++++++++++-
drivers/net/bonding/rte_eth_bond_pmd.c | 58 +++++++++++--------
6 files changed, 182 insertions(+), 25 deletions(-)
diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
index 2fa1ac4028..a3f197c8b5 100644
--- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
+++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
@@ -254,6 +254,36 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
that are assumed not to be invoked in parallel on different logical cores to
work on the same target object.
+Bonding device configuration and LACP runtime state are owned by the primary
+process. Secondary processes may attach to an existing bonding device for
+detach and supported query operations only.
+
+Supported secondary-process queries include device information, statistics,
+link status, RETA query, RSS hash configuration, bonding mode, member list,
+primary member, transmit policy, link monitoring configuration, and LACP
+configuration. Private dump is limited to shared bonding information and skips
+LACP runtime state in a secondary process.
+
+Control operations are restricted to the primary process. This includes
+configuring, starting or stopping the device, setting up queues, changing
+members, changing the bonding mode, selecting the primary member, changing the
+transmit policy, changing link monitoring or propagation delays, updating RSS,
+changing MAC addresses, changing MTU, configuring VLAN filters, changing
+promiscuous or all-multicast mode, resetting statistics, configuring
+``rte_flow`` rules, and changing 802.3ad settings, including aggregation
+selection, external collect/distribute/slow-Tx controls, and dedicated queue
+enable or disable.
+
+LACP runtime state queries, including ``rte_eth_bond_8023ad_member_info()``,
+``rte_eth_bond_8023ad_ext_collect_get()``, and
+``rte_eth_bond_8023ad_ext_distrib_get()``, are also restricted to the primary
+process.
+
+Rx and Tx are not supported on a bonding device in a secondary process;
+receive returns no packets and transmit drops packets. In a secondary process,
+``rte_eth_dev_stop()`` returns ``-ENOTSUP`` and ``rte_eth_dev_close()`` is the
+detach operation.
+
It should also be noted that the PMD receive function should not be invoked
directly on a member devices after they have been to a bonding device since
packets read directly from the member device will no longer be available to the
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..60d8146d68 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -81,6 +81,12 @@ Removed Items
API Changes
-----------
+* **Restricted bonding device control to the primary process.**
+
+ Bonding device configuration and LACP runtime state operations are now
+ rejected in secondary processes. Secondary processes may detach and use
+ supported query operations only.
+
.. This section should contain API changes. Sample format:
* sample: Add a short 1-2 sentence description of the API change
diff --git a/drivers/net/bonding/eth_bond_private.h b/drivers/net/bonding/eth_bond_private.h
index 378bbba4e6..526bcd0363 100644
--- a/drivers/net/bonding/eth_bond_private.h
+++ b/drivers/net/bonding/eth_bond_private.h
@@ -7,12 +7,14 @@
#include <stdint.h>
#include <sys/queue.h>
+#include <stdbool.h>
#include <ethdev_driver.h>
#include <rte_flow.h>
#include <rte_spinlock.h>
#include <rte_bitmap.h>
#include <rte_flow_driver.h>
+#include <rte_eal.h>
#include "rte_eth_bond.h"
#include "eth_bond_8023ad_private.h"
@@ -212,6 +214,16 @@ find_member_by_id(uint16_t *members, uint16_t members_count, uint16_t member_id)
return pos;
}
+static inline int
+bond_check_primary(const char *op, int err)
+{
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY)
+ return 0;
+
+ RTE_BOND_LOG(ERR, "%s not supported in non-primary process", op);
+ return err;
+}
+
int
valid_port_id(uint16_t port_id);
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..65f417a444 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1436,6 +1436,11 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
@@ -1508,6 +1513,11 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
{
struct rte_eth_dev *bond_dev;
int err;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
@@ -1531,6 +1541,11 @@ rte_eth_bond_8023ad_member_info(uint16_t port_id, uint16_t member_id,
struct rte_eth_dev *bond_dev;
struct bond_dev_private *internals;
struct port *port;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (info == NULL || valid_bonding_port_id(port_id) != 0 ||
rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
@@ -1592,6 +1607,11 @@ rte_eth_bond_8023ad_ext_collect(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1614,6 +1634,11 @@ rte_eth_bond_8023ad_ext_distrib(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1636,6 +1661,10 @@ rte_eth_bond_8023ad_ext_distrib_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ err = bond_check_primary(__func__, -ENOTSUP);
+ if (err != 0)
+ return err;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1651,6 +1680,10 @@ rte_eth_bond_8023ad_ext_collect_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ err = bond_check_primary(__func__, -ENOTSUP);
+ if (err != 0)
+ return err;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1666,6 +1699,11 @@ rte_eth_bond_8023ad_ext_slowtx(uint16_t port_id, uint16_t member_id,
{
struct port *port;
int res;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
@@ -1727,6 +1765,11 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1756,6 +1799,11 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
{
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -ENOTSUP);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..029e141d89 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -159,6 +159,10 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -640,9 +644,12 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
-
int retval;
+ retval = bond_check_primary(__func__, -1);
+ if (retval != 0)
+ return retval;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +788,10 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ retval = bond_check_primary(__func__, -1);
+ if (retval != 0)
+ return retval;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -801,6 +812,11 @@ int
rte_eth_bond_mode_set(uint16_t bonding_port_id, uint8_t mode)
{
struct rte_eth_dev *bonding_eth_dev;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -833,6 +849,11 @@ int
rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -923,6 +944,11 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -949,6 +975,11 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -990,6 +1021,11 @@ int
rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1035,6 +1071,11 @@ int
rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1064,6 +1105,11 @@ rte_eth_bond_link_down_prop_delay_set(uint16_t bonding_port_id,
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1092,6 +1138,11 @@ rte_eth_bond_link_up_prop_delay_set(uint16_t bonding_port_id, uint32_t delay_ms)
{
struct bond_dev_private *internals;
+ int ret;
+
+ ret = bond_check_primary(__func__, -1);
+ if (ret != 0)
+ return ret;
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6f10dbb0c7..bba93a5638 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2558,22 +2558,24 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
struct bond_dev_private *bond_ctx;
struct rte_eth_link member_link;
+ struct rte_eth_link bond_link;
bool one_link_update_succeeded;
uint32_t idx;
int ret;
- bond_ctx = ethdev->data->dev_private;
+ rte_eth_linkstatus_get(ethdev, &bond_link);
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ bond_ctx = ethdev->data->dev_private;
if (ethdev->data->dev_started == 0 ||
bond_ctx->active_member_count == 0) {
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
- return 0;
+ bond_link.link_status = RTE_ETH_LINK_DOWN;
+ goto out;
}
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ bond_link.link_status = RTE_ETH_LINK_UP;
if (wait_to_complete)
link_update = rte_eth_link_get;
@@ -2586,7 +2588,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* Setting link speed to UINT32_MAX to ensure we pick up the
* value of the first active member
*/
- ethdev->data->dev_link.link_speed = UINT32_MAX;
+ bond_link.link_speed = UINT32_MAX;
/**
* link speed is minimum value of all the members link speed as
@@ -2597,19 +2599,16 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
ret = link_update(bond_ctx->active_members[idx],
&member_link);
if (ret < 0) {
- ethdev->data->dev_link.link_speed =
- RTE_ETH_SPEED_NUM_NONE;
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
RTE_BOND_LOG(ERR,
"Member (port %u) link get failed: %s",
bond_ctx->active_members[idx],
rte_strerror(-ret));
- return 0;
+ goto out;
}
- if (member_link.link_speed <
- ethdev->data->dev_link.link_speed)
- ethdev->data->dev_link.link_speed =
- member_link.link_speed;
+ if (member_link.link_speed < bond_link.link_speed)
+ bond_link.link_speed = member_link.link_speed;
}
break;
case BONDING_MODE_ACTIVE_BACKUP:
@@ -2619,15 +2618,15 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
bond_ctx->current_primary_port,
rte_strerror(-ret));
- return 0;
+ goto out;
}
- ethdev->data->dev_link.link_speed = member_link.link_speed;
+ bond_link.link_speed = member_link.link_speed;
break;
case BONDING_MODE_8023AD:
- ethdev->data->dev_link.link_autoneg =
+ bond_link.link_autoneg =
bond_ctx->mode4.member_link.link_autoneg;
- ethdev->data->dev_link.link_duplex =
+ bond_link.link_duplex =
bond_ctx->mode4.member_link.link_duplex;
/* fall through */
/* to update link speed */
@@ -2640,7 +2639,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* In theses mode the maximum theoretical link speed is the sum
* of all the members
*/
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
one_link_update_succeeded = false;
for (idx = 0; idx < bond_ctx->active_member_count; idx++) {
@@ -2655,17 +2654,17 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
}
one_link_update_succeeded = true;
- ethdev->data->dev_link.link_speed +=
- member_link.link_speed;
+ bond_link.link_speed += member_link.link_speed;
}
if (!one_link_update_succeeded) {
RTE_BOND_LOG(ERR, "All members link get failed");
- return 0;
+ goto out;
}
}
-
+out:
+ rte_eth_linkstatus_set(ethdev, &bond_link);
return 0;
}
@@ -3641,12 +3640,23 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
const struct bond_dev_private *internals = dev->data->dev_private;
dump_basic(dev, f);
- if (internals->mode == BONDING_MODE_8023AD)
+ if (internals->mode == BONDING_MODE_8023AD &&
+ rte_eal_process_type() == RTE_PROC_PRIMARY)
dump_lacp(dev->data->port_id, f);
return 0;
}
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+ .eth_dev_priv_dump = bond_ethdev_priv_dump,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3831,7 +3841,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
2026-08-30 1:14 ` [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations Weijun Pan
@ 2026-08-30 4:22 ` Stephen Hemminger
2026-08-30 16:35 ` [RFC PATCH v5 " Weijun Pan
2 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 4:22 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), dev
On Sat, 29 Aug 2026 20:14:44 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
> index 6a4f997b5a..6f10dbb0c7 100644
> --- a/drivers/net/bonding/rte_eth_bond_pmd.c
> +++ b/drivers/net/bonding/rte_eth_bond_pmd.c
> @@ -2677,9 +2677,12 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
> struct bond_dev_private *internals = dev->data->dev_private;
> struct rte_eth_stats member_stats;
> int i;
> + int ret;
>
> for (i = 0; i < internals->member_count; i++) {
> - rte_eth_stats_get(internals->members[i].port_id, &member_stats);
> + ret = rte_eth_stats_get(internals->members[i].port_id, &member_stats);
> + if (ret != 0)
> + continue;
Ok, but you really don't need the ret variable here.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations
2026-08-30 1:14 ` [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations Weijun Pan
@ 2026-08-30 4:29 ` Stephen Hemminger
0 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 4:29 UTC (permalink / raw)
To: Weijun Pan; +Cc: Chas Williams, Min Hu (Connor), Anatoly Burakov, dev
On Sat, 29 Aug 2026 20:14:45 -0500
Weijun Pan <wpan3636@gmail.com> wrote:
> diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> index 2fa1ac4028..a3f197c8b5 100644
> --- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> +++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
> @@ -254,6 +254,36 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
> that are assumed not to be invoked in parallel on different logical cores to
> work on the same target object.
>
> +Bonding device configuration and LACP runtime state are owned by the primary
> +process. Secondary processes may attach to an existing bonding device for
> +detach and supported query operations only.
> +
> +Supported secondary-process queries include device information, statistics,
> +link status, RETA query, RSS hash configuration, bonding mode, member list,
> +primary member, transmit policy, link monitoring configuration, and LACP
> +configuration. Private dump is limited to shared bonding information and skips
> +LACP runtime state in a secondary process.
> +
> +Control operations are restricted to the primary process. This includes
> +configuring, starting or stopping the device, setting up queues, changing
> +members, changing the bonding mode, selecting the primary member, changing the
> +transmit policy, changing link monitoring or propagation delays, updating RSS,
> +changing MAC addresses, changing MTU, configuring VLAN filters, changing
> +promiscuous or all-multicast mode, resetting statistics, configuring
> +``rte_flow`` rules, and changing 802.3ad settings, including aggregation
> +selection, external collect/distribute/slow-Tx controls, and dedicated queue
> +enable or disable.
> +
> +LACP runtime state queries, including ``rte_eth_bond_8023ad_member_info()``,
> +``rte_eth_bond_8023ad_ext_collect_get()``, and
> +``rte_eth_bond_8023ad_ext_distrib_get()``, are also restricted to the primary
> +process.
> +
> +Rx and Tx are not supported on a bonding device in a secondary process;
> +receive returns no packets and transmit drops packets. In a secondary process,
> +``rte_eth_dev_stop()`` returns ``-ENOTSUP`` and ``rte_eth_dev_close()`` is the
> +detach operation.
> +
That is way too long an explanation (thanks AI). Should just be short summary here.
> +* **Restricted bonding device control to the primary process.**
> +
> + Bonding device configuration and LACP runtime state operations are now
> + rejected in secondary processes. Secondary processes may detach and use
> + supported query operations only.
> +
Once again, AI is being too wordy. It was always true that bonding control
did not work for secondary. And it is not really an API change.
Should be under Added items, like "Bonding allow data operations in secondary process"
> +static inline int
> +bond_check_primary(const char *op, int err)
> +{
> + if (rte_eal_process_type() == RTE_PROC_PRIMARY)
> + return 0;
> +
> + RTE_BOND_LOG(ERR, "%s not supported in non-primary process", op);
> + return err;
> +}
When ever possible avoid using negatives in English speech.
Should just say "%s not supported in secondary process.
And returning different errors is awkward way to handle.
Just make helper that returns true/false and if false put that error code at
that location in caller.
Then you can eliminate lots of "int ret" in the calling code as well.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [RFC PATCH v5 1/2] net/bonding: skip unavailable member stats
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
2026-08-30 1:14 ` [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-30 4:22 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Stephen Hemminger
@ 2026-08-30 16:35 ` Weijun Pan
2026-08-30 16:35 ` [RFC PATCH v5 2/2] net/bonding: restrict secondary control operations Weijun Pan
2 siblings, 1 reply; 33+ messages in thread
From: Weijun Pan @ 2026-08-30 16:35 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor); +Cc: dev, Stephen Hemminger, Weijun Pan
bond_ethdev_stats_get() accumulates statistics from each bonding
member. If rte_eth_stats_get() fails for a member, the local
member_stats structure may be left unchanged.
Skip members whose statistics cannot be read, instead of accumulating
stale or uninitialized counters.
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
v5:
- Remove the temporary ret variable.
v4:
- Add this preparatory fix.
drivers/net/bonding/rte_eth_bond_pmd.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..63f21d8522 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2679,7 +2679,9 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
int i;
for (i = 0; i < internals->member_count; i++) {
- rte_eth_stats_get(internals->members[i].port_id, &member_stats);
+ if (rte_eth_stats_get(internals->members[i].port_id,
+ &member_stats) != 0)
+ continue;
stats->ipackets += member_stats.ipackets;
stats->opackets += member_stats.opackets;
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [RFC PATCH v5 2/2] net/bonding: restrict secondary control operations
2026-08-30 16:35 ` [RFC PATCH v5 " Weijun Pan
@ 2026-08-30 16:35 ` Weijun Pan
0 siblings, 0 replies; 33+ messages in thread
From: Weijun Pan @ 2026-08-30 16:35 UTC (permalink / raw)
To: Chas Williams, Min Hu (Connor), Anatoly Burakov
Cc: dev, Stephen Hemminger, Weijun Pan
Bonding configuration and LACP runtime state are owned by the primary
process. Install a reduced dev_ops table in secondary processes so
ethdev rejects control operations, and reject bonding control APIs
when called from a secondary process.
Supported query and detach paths remain available, while LACP runtime
state queries are restricted to the primary process.
Bugzilla ID: 1900
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
v5:
- Simplify the helper and release note wording.
v4:
- Address secondary link update, docs, and private dump handling.
.../link_bonding_poll_mode_drv_lib.rst | 30 ++++++++++
doc/guides/rel_notes/release_26_11.rst | 5 ++
drivers/net/bonding/eth_bond_private.h | 12 ++++
drivers/net/bonding/rte_eth_bond_8023ad.c | 30 ++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 34 ++++++++++-
drivers/net/bonding/rte_eth_bond_pmd.c | 58 +++++++++++--------
6 files changed, 144 insertions(+), 25 deletions(-)
diff --git a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
index 2fa1ac4028..a3f197c8b5 100644
--- a/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
+++ b/doc/guides/prog_guide/link_bonding_poll_mode_drv_lib.rst
@@ -254,6 +254,36 @@ Like all other PMD, all functions exported by a PMD are lock-free functions
that are assumed not to be invoked in parallel on different logical cores to
work on the same target object.
+Bonding device configuration and LACP runtime state are owned by the primary
+process. Secondary processes may attach to an existing bonding device for
+detach and supported query operations only.
+
+Supported secondary-process queries include device information, statistics,
+link status, RETA query, RSS hash configuration, bonding mode, member list,
+primary member, transmit policy, link monitoring configuration, and LACP
+configuration. Private dump is limited to shared bonding information and skips
+LACP runtime state in a secondary process.
+
+Control operations are restricted to the primary process. This includes
+configuring, starting or stopping the device, setting up queues, changing
+members, changing the bonding mode, selecting the primary member, changing the
+transmit policy, changing link monitoring or propagation delays, updating RSS,
+changing MAC addresses, changing MTU, configuring VLAN filters, changing
+promiscuous or all-multicast mode, resetting statistics, configuring
+``rte_flow`` rules, and changing 802.3ad settings, including aggregation
+selection, external collect/distribute/slow-Tx controls, and dedicated queue
+enable or disable.
+
+LACP runtime state queries, including ``rte_eth_bond_8023ad_member_info()``,
+``rte_eth_bond_8023ad_ext_collect_get()``, and
+``rte_eth_bond_8023ad_ext_distrib_get()``, are also restricted to the primary
+process.
+
+Rx and Tx are not supported on a bonding device in a secondary process;
+receive returns no packets and transmit drops packets. In a secondary process,
+``rte_eth_dev_stop()`` returns ``-ENOTSUP`` and ``rte_eth_dev_close()`` is the
+detach operation.
+
It should also be noted that the PMD receive function should not be invoked
directly on a member devices after they have been to a bonding device since
packets read directly from the member device will no longer be available to the
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..2a8e3a2d20 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -24,6 +24,11 @@ DPDK Release 26.11
New Features
------------
+* **Added bonding PMD secondary-process support.**
+
+ Added support for querying and detaching bonding devices from secondary
+ processes.
+
.. This section should contain new features added in this release.
Sample format:
diff --git a/drivers/net/bonding/eth_bond_private.h b/drivers/net/bonding/eth_bond_private.h
index 378bbba4e6..b3c2ddf24e 100644
--- a/drivers/net/bonding/eth_bond_private.h
+++ b/drivers/net/bonding/eth_bond_private.h
@@ -7,12 +7,14 @@
#include <stdint.h>
#include <sys/queue.h>
+#include <stdbool.h>
#include <ethdev_driver.h>
#include <rte_flow.h>
#include <rte_spinlock.h>
#include <rte_bitmap.h>
#include <rte_flow_driver.h>
+#include <rte_eal.h>
#include "rte_eth_bond.h"
#include "eth_bond_8023ad_private.h"
@@ -212,6 +214,16 @@ find_member_by_id(uint16_t *members, uint16_t members_count, uint16_t member_id)
return pos;
}
+static inline bool
+bond_check_primary(const char *op)
+{
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY)
+ return true;
+
+ RTE_BOND_LOG(ERR, "%s not supported in secondary process", op);
+ return false;
+}
+
int
valid_port_id(uint16_t port_id);
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..5f52cc2bcc 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1437,6 +1437,9 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
@@ -1509,6 +1512,9 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
struct rte_eth_dev *bond_dev;
int err;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
return err;
@@ -1532,6 +1538,9 @@ rte_eth_bond_8023ad_member_info(uint16_t port_id, uint16_t member_id,
struct bond_dev_private *internals;
struct port *port;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
if (info == NULL || valid_bonding_port_id(port_id) != 0 ||
rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
return -EINVAL;
@@ -1593,6 +1602,9 @@ rte_eth_bond_8023ad_ext_collect(uint16_t port_id, uint16_t member_id,
struct port *port;
int res;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
return res;
@@ -1615,6 +1627,9 @@ rte_eth_bond_8023ad_ext_distrib(uint16_t port_id, uint16_t member_id,
struct port *port;
int res;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
return res;
@@ -1636,6 +1651,9 @@ rte_eth_bond_8023ad_ext_distrib_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1651,6 +1669,9 @@ rte_eth_bond_8023ad_ext_collect_get(uint16_t port_id, uint16_t member_id)
struct port *port;
int err;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
err = bond_8023ad_ext_validate(port_id, member_id);
if (err != 0)
return err;
@@ -1667,6 +1688,9 @@ rte_eth_bond_8023ad_ext_slowtx(uint16_t port_id, uint16_t member_id,
struct port *port;
int res;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
res = bond_8023ad_ext_validate(port_id, member_id);
if (res != 0)
return res;
@@ -1728,6 +1752,9 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1757,6 +1784,9 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..91207e8573 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -159,6 +159,9 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -640,9 +643,11 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
-
int retval;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +786,9 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -802,6 +810,9 @@ rte_eth_bond_mode_set(uint16_t bonding_port_id, uint8_t mode)
{
struct rte_eth_dev *bonding_eth_dev;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -834,6 +845,9 @@ rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -924,6 +938,9 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -950,6 +967,9 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -991,6 +1011,9 @@ rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1036,6 +1059,9 @@ rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1065,6 +1091,9 @@ rte_eth_bond_link_down_prop_delay_set(uint16_t bonding_port_id,
{
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1093,6 +1122,9 @@ rte_eth_bond_link_up_prop_delay_set(uint16_t bonding_port_id, uint32_t delay_ms)
{
struct bond_dev_private *internals;
+ if (!bond_check_primary(__func__))
+ return -1;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 63f21d8522..04a2d6f1d2 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2558,22 +2558,24 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
struct bond_dev_private *bond_ctx;
struct rte_eth_link member_link;
+ struct rte_eth_link bond_link;
bool one_link_update_succeeded;
uint32_t idx;
int ret;
- bond_ctx = ethdev->data->dev_private;
+ rte_eth_linkstatus_get(ethdev, &bond_link);
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ bond_ctx = ethdev->data->dev_private;
if (ethdev->data->dev_started == 0 ||
bond_ctx->active_member_count == 0) {
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
- return 0;
+ bond_link.link_status = RTE_ETH_LINK_DOWN;
+ goto out;
}
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ bond_link.link_status = RTE_ETH_LINK_UP;
if (wait_to_complete)
link_update = rte_eth_link_get;
@@ -2586,7 +2588,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* Setting link speed to UINT32_MAX to ensure we pick up the
* value of the first active member
*/
- ethdev->data->dev_link.link_speed = UINT32_MAX;
+ bond_link.link_speed = UINT32_MAX;
/**
* link speed is minimum value of all the members link speed as
@@ -2597,19 +2599,16 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
ret = link_update(bond_ctx->active_members[idx],
&member_link);
if (ret < 0) {
- ethdev->data->dev_link.link_speed =
- RTE_ETH_SPEED_NUM_NONE;
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
RTE_BOND_LOG(ERR,
"Member (port %u) link get failed: %s",
bond_ctx->active_members[idx],
rte_strerror(-ret));
- return 0;
+ goto out;
}
- if (member_link.link_speed <
- ethdev->data->dev_link.link_speed)
- ethdev->data->dev_link.link_speed =
- member_link.link_speed;
+ if (member_link.link_speed < bond_link.link_speed)
+ bond_link.link_speed = member_link.link_speed;
}
break;
case BONDING_MODE_ACTIVE_BACKUP:
@@ -2619,15 +2618,15 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
bond_ctx->current_primary_port,
rte_strerror(-ret));
- return 0;
+ goto out;
}
- ethdev->data->dev_link.link_speed = member_link.link_speed;
+ bond_link.link_speed = member_link.link_speed;
break;
case BONDING_MODE_8023AD:
- ethdev->data->dev_link.link_autoneg =
+ bond_link.link_autoneg =
bond_ctx->mode4.member_link.link_autoneg;
- ethdev->data->dev_link.link_duplex =
+ bond_link.link_duplex =
bond_ctx->mode4.member_link.link_duplex;
/* fall through */
/* to update link speed */
@@ -2640,7 +2639,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* In theses mode the maximum theoretical link speed is the sum
* of all the members
*/
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ bond_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
one_link_update_succeeded = false;
for (idx = 0; idx < bond_ctx->active_member_count; idx++) {
@@ -2655,17 +2654,17 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
}
one_link_update_succeeded = true;
- ethdev->data->dev_link.link_speed +=
- member_link.link_speed;
+ bond_link.link_speed += member_link.link_speed;
}
if (!one_link_update_succeeded) {
RTE_BOND_LOG(ERR, "All members link get failed");
- return 0;
+ goto out;
}
}
-
+out:
+ rte_eth_linkstatus_set(ethdev, &bond_link);
return 0;
}
@@ -3640,12 +3639,23 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
const struct bond_dev_private *internals = dev->data->dev_private;
dump_basic(dev, f);
- if (internals->mode == BONDING_MODE_8023AD)
+ if (internals->mode == BONDING_MODE_8023AD &&
+ rte_eal_process_type() == RTE_PROC_PRIMARY)
dump_lacp(dev->data->port_id, f);
return 0;
}
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+ .eth_dev_priv_dump = bond_ethdev_priv_dump,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3830,7 +3840,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 0/8] net/bonding: fixes and per-member statistics
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
` (2 preceding siblings ...)
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
` (7 more replies)
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
4 siblings, 8 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
The recent RFC's to bonding prompted me to (with AI assistance) to
look at current state of bonding PMD. Several issues were found.
This incorporates the RFC patch series and modifies it to
have Fixes and more checks.
Added proper extended statistics which allows looking how
members are doing.
Weijun Pan reported the stats double counting and the secondary
process crash (Bugzilla 1900).
Stephen Hemminger (7):
net/bonding: fix TLB member ordering with unusable member
net/bonding: skip unavailable members in device info
net/bonding: use atomic link status accessors
net/bonding: restrict control operations in secondary process
net/bonding: add extended statistics
test/bonding: add extended statistics test
doc: add bonding features matrix
Weijun Pan (1):
net/bonding: skip unavailable member stats
app/test/test_link_bonding.c | 148 ++++++++++
doc/guides/nics/features/bonding.ini | 28 ++
doc/guides/rel_notes/release_26_11.rst | 6 +
drivers/net/bonding/rte_eth_bond_8023ad.c | 18 ++
drivers/net/bonding/rte_eth_bond_api.c | 24 ++
drivers/net/bonding/rte_eth_bond_pmd.c | 338 +++++++++++++++++-----
6 files changed, 483 insertions(+), 79 deletions(-)
create mode 100644 doc/guides/nics/features/bonding.ini
--
2.53.0
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 1/8] net/bonding: fix TLB member ordering with unusable member
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
` (6 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Andrew Rybchenko, Igor Romanov, Declan Doherty, Daniel Mrzyglod
The TLB rebalance callback assumed every active member could be
queried. If the link is down, has no usable speed, or stats fail,
the member was ordered on garbage bandwidth values.
Skip such members. Transmit reads active_member_count entries of
tlb_members_order but only measured members are written, so pad
the tail rather than leave stale port ids behind.
Members are not deactivated here; the link status callback owns
that and also updates the primary port and bonding link state.
The callback runs every millisecond, so an unusable member logged
1000 times per second. Log only on state change.
Fixes: fc1134c79283 ("net/bonding: check status of getting link info")
Fixes: 7c76a747e68c ("bond: add mode 5")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 99 ++++++++++++++++----------
1 file changed, 62 insertions(+), 37 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..5b4b3d6ac2 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -33,6 +33,7 @@
/* Table for statistics in mode 5 TLB */
static uint64_t tlb_last_obytets[RTE_MAX_ETHPORTS];
+static bool tlb_unusable[RTE_MAX_ETHPORTS];
static inline size_t
get_vlan_offset(struct rte_ether_hdr *eth_hdr, uint16_t *proto)
@@ -865,8 +866,10 @@ void
bond_tlb_activate_member(struct bond_dev_private *internals) {
int i;
- for (i = 0; i < internals->active_member_count; i++)
+ for (i = 0; i < internals->active_member_count; i++) {
tlb_last_obytets[internals->active_members[i]] = 0;
+ tlb_unusable[internals->active_members[i]] = false;
+ }
}
static int
@@ -890,22 +893,10 @@ bandwidth_cmp(const void *a, const void *b)
}
static void
-bandwidth_left(uint16_t port_id, uint64_t load, uint8_t update_idx,
- struct bwg_member *bwg_member)
+bandwidth_left(uint64_t load, uint64_t link_bwg, uint8_t update_idx,
+ struct bwg_member *bwg_member)
{
- struct rte_eth_link link_status;
- int ret;
-
- ret = rte_eth_link_get_nowait(port_id, &link_status);
- if (ret < 0) {
- RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
- port_id, rte_strerror(-ret));
- return;
- }
- uint64_t link_bwg = link_status.link_speed * 1000000ULL / 8;
- if (link_bwg == 0)
- return;
- link_bwg = link_bwg * (update_idx+1) * REORDER_PERIOD_MS;
+ link_bwg = link_bwg * (update_idx + 1) * REORDER_PERIOD_MS;
bwg_member->bwg_left_int = (link_bwg - 1000 * load) / link_bwg;
bwg_member->bwg_left_remainder = (link_bwg - 1000 * load) % link_bwg;
}
@@ -914,44 +905,78 @@ static void
bond_ethdev_update_tlb_member_cb(void *arg)
{
struct bond_dev_private *internals = arg;
- struct rte_eth_stats member_stats;
struct bwg_member bwg_array[RTE_MAX_ETHPORTS];
- uint16_t member_count;
- uint64_t tx_bytes;
-
- uint8_t update_stats = 0;
- uint16_t member_id;
+ uint16_t active_count = internals->active_member_count;
+ uint16_t member_count = 0;
+ bool update_stats;
uint16_t i;
internals->member_update_idx++;
+ update_stats = internals->member_update_idx >= REORDER_PERIOD_MS;
+ for (i = 0; i < active_count; i++) {
+ uint16_t member_id = internals->active_members[i];
+ struct rte_eth_link link;
+ struct rte_eth_stats stats;
+ const char *reason = NULL;
+ int ret;
- if (internals->member_update_idx >= REORDER_PERIOD_MS)
- update_stats = 1;
+ ret = rte_eth_link_get_nowait(member_id, &link);
+ if (ret == 0)
+ ret = rte_eth_stats_get(member_id, &stats);
- for (i = 0; i < internals->active_member_count; i++) {
- member_id = internals->active_members[i];
- rte_eth_stats_get(member_id, &member_stats);
- tx_bytes = member_stats.obytes - tlb_last_obytets[member_id];
- bandwidth_left(member_id, tx_bytes,
- internals->member_update_idx, &bwg_array[i]);
- bwg_array[i].member = member_id;
-
- if (update_stats) {
- tlb_last_obytets[member_id] = member_stats.obytes;
+ if (ret < 0)
+ reason = rte_strerror(-ret);
+ else if (link.link_status == RTE_ETH_LINK_DOWN)
+ reason = "link down";
+ else if (link.link_speed == RTE_ETH_SPEED_NUM_NONE ||
+ link.link_speed == RTE_ETH_SPEED_NUM_UNKNOWN)
+ reason = "link speed unknown";
+
+ /*
+ * Skip the member rather than treat it as idle, which would
+ * sort it first and attract traffic. Deactivating it is the
+ * link status callback's job. Log only on state change,
+ * this runs every millisecond.
+ */
+ if (reason != NULL) {
+ if (!tlb_unusable[member_id]) {
+ tlb_unusable[member_id] = true;
+ RTE_BOND_LOG(ERR, "Member (port %u) excluded from TLB ordering: %s",
+ member_id, reason);
+ }
+ continue;
}
+ if (tlb_unusable[member_id]) {
+ tlb_unusable[member_id] = false;
+ RTE_BOND_LOG(INFO, "Member (port %u) usable for TLB ordering",
+ member_id);
+ }
+
+ bandwidth_left(stats.obytes - tlb_last_obytets[member_id],
+ link.link_speed * 1000000ULL / 8,
+ internals->member_update_idx,
+ &bwg_array[member_count]);
+ bwg_array[member_count++].member = member_id;
+
+ if (update_stats)
+ tlb_last_obytets[member_id] = stats.obytes;
}
- if (update_stats == 1)
+ if (update_stats)
internals->member_update_idx = 0;
- member_count = i;
qsort(bwg_array, member_count, sizeof(bwg_array[0]), bandwidth_cmp);
for (i = 0; i < member_count; i++)
internals->tlb_members_order[i] = bwg_array[i].member;
+ /* Transmit reads active_member_count entries, don't leave stale ones. */
+ for (i = member_count; i < active_count; i++)
+ internals->tlb_members_order[i] = member_count > 0 ?
+ bwg_array[0].member : internals->active_members[i];
+
rte_eal_alarm_set(REORDER_PERIOD_MS * 1000, bond_ethdev_update_tlb_member_cb,
- (struct bond_dev_private *)internals);
+ internals);
}
static uint16_t
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 2/8] net/bonding: skip unavailable member stats
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
2026-08-30 20:23 ` [PATCH 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
` (5 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev
Cc: Weijun Pan, stable, Chas Williams, Min Hu (Connor), Pablo de Lara,
Declan Doherty, Robert Sanford
From: Weijun Pan <wpan3636@gmail.com>
bond_ethdev_stats_get() accumulates statistics from each bonding
member. If rte_eth_stats_get() fails for a member, the local
member_stats structure may be left unchanged.
Skip members whose statistics cannot be read, instead of accumulating
stale or uninitialized counters.
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 5b4b3d6ac2..6235c07679 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2704,7 +2704,9 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
int i;
for (i = 0; i < internals->member_count; i++) {
- rte_eth_stats_get(internals->members[i].port_id, &member_stats);
+ if (rte_eth_stats_get(internals->members[i].port_id,
+ &member_stats) != 0)
+ continue;
stats->ipackets += member_stats.ipackets;
stats->opackets += member_stats.opackets;
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 3/8] net/bonding: skip unavailable members in device info
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
2026-08-30 20:23 ` [PATCH 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
2026-08-30 20:23 ` [PATCH 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
` (4 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Declan Doherty
bond_ethdev_info() queries every member to derive the maximum number of
Rx and Tx queues the bonding device can support. A single failing
member aborted the whole call.
Skip members that cannot be queried and derive the queue limits from
the rest.
A bonding device with no members at all keeps reporting UINT16_MAX, as
it did before: no member has constrained it yet.
Fixes: acfb51e2fe96 ("net/bonding: fix number of bonding Tx/Rx queues")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6235c07679..7579b97b06 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2360,28 +2360,40 @@ bond_ethdev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
/* Max number of tx/rx queues that the bonding device can support is the
* minimum values of the bonding members, as all members must be capable
* of supporting the same number of tx/rx queues.
+ *
+ * A member may be unqueryable here: it can be removed concurrently, or
+ * simply not be probed in this process. Skip those, but do not report
+ * the UINT16_MAX default if no member could be queried at all, since
+ * that would let any queue count pass rte_eth_dev_configure().
*/
if (internals->member_count > 0) {
struct rte_eth_dev_info member_info;
+ uint16_t queried = 0;
uint16_t idx;
for (idx = 0; idx < internals->member_count; idx++) {
member = internals->members[idx];
ret = rte_eth_dev_info_get(member.port_id, &member_info);
if (ret != 0) {
- RTE_BOND_LOG(ERR,
- "Error getting device (port %u) info: %s",
+ RTE_BOND_LOG(WARNING,
+ "Skipping device (port %u) info: %s",
member.port_id, strerror(-ret));
-
- return ret;
+ continue;
}
+ queried++;
+
if (member_info.max_rx_queues < max_nb_rx_queues)
max_nb_rx_queues = member_info.max_rx_queues;
if (member_info.max_tx_queues < max_nb_tx_queues)
max_nb_tx_queues = member_info.max_tx_queues;
}
+
+ if (queried == 0) {
+ RTE_BOND_LOG(ERR, "No member device info available");
+ return -ENODEV;
+ }
}
dev_info->max_rx_queues = max_nb_rx_queues;
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 4/8] net/bonding: use atomic link status accessors
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
` (2 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 5/8] net/bonding: restrict control operations in secondary process Stephen Hemminger
` (3 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Declan Doherty, Pablo de Lara, Robert Sanford
The bonding PMD read and wrote rte_eth_dev_data.dev_link directly.
Since a struct rte_eth_link is a 64 bit value that may be updated concurrently
it needs to be done atomically. Use existing helpers.
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 84 ++++++++++++++++----------
1 file changed, 51 insertions(+), 33 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 7579b97b06..6f3c13d6fb 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -1461,8 +1461,12 @@ link_properties_set(struct rte_eth_dev *ethdev, struct rte_eth_link *member_link
* In any other mode the link properties are set to default
* values of AUTONEG/DUPLEX
*/
- ethdev->data->dev_link.link_autoneg = RTE_ETH_LINK_AUTONEG;
- ethdev->data->dev_link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+ struct rte_eth_link link;
+
+ rte_eth_linkstatus_get(ethdev, &link);
+ link.link_autoneg = RTE_ETH_LINK_AUTONEG;
+ link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+ rte_eth_linkstatus_set(ethdev, &link);
}
}
@@ -2077,6 +2081,16 @@ bond_ethdev_primary_set(struct bond_dev_private *internals,
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev);
+static void
+bond_ethdev_link_down(struct rte_eth_dev *eth_dev)
+{
+ struct rte_eth_link link;
+
+ rte_eth_linkstatus_get(eth_dev, &link);
+ link.link_status = RTE_ETH_LINK_DOWN;
+ rte_eth_linkstatus_set(eth_dev, &link);
+}
+
static int
bond_ethdev_start(struct rte_eth_dev *eth_dev)
{
@@ -2090,7 +2104,7 @@ bond_ethdev_start(struct rte_eth_dev *eth_dev)
return -1;
}
- eth_dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ bond_ethdev_link_down(eth_dev);
eth_dev->data->dev_started = 1;
internals = eth_dev->data->dev_private;
@@ -2242,7 +2256,7 @@ bond_ethdev_stop(struct rte_eth_dev *eth_dev)
tlb_last_obytets[internals->active_members[i]] = 0;
}
- eth_dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ bond_ethdev_link_down(eth_dev);
eth_dev->data->dev_started = 0;
if (internals->link_status_polling_enabled) {
@@ -2537,6 +2551,7 @@ bond_ethdev_member_link_status_change_monitor(void *cb_arg)
{
struct rte_eth_dev *bonding_ethdev, *member_ethdev;
struct bond_dev_private *internals;
+ struct rte_eth_link member_link;
/* Default value for polling member found is true as we don't want to
* disable the polling thread if we cannot get the lock */
@@ -2569,9 +2584,11 @@ bond_ethdev_member_link_status_change_monitor(void *cb_arg)
member_ethdev->dev_ops->link_update(member_ethdev,
internals->members[i].link_status_wait_to_complete);
+ rte_eth_linkstatus_get(member_ethdev, &member_link);
+
/* if link status has changed since last checked then call lsc
* event callback */
- if (member_ethdev->data->dev_link.link_status !=
+ if (member_link.link_status !=
internals->members[i].last_link_status) {
bond_ethdev_lsc_event_callback(internals->members[i].port_id,
RTE_ETH_EVENT_INTR_LSC,
@@ -2595,6 +2612,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
struct bond_dev_private *bond_ctx;
struct rte_eth_link member_link;
+ struct rte_eth_link link;
bool one_link_update_succeeded;
uint32_t idx;
@@ -2602,15 +2620,17 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
bond_ctx = ethdev->data->dev_private;
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ rte_eth_linkstatus_get(ethdev, &link);
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
if (ethdev->data->dev_started == 0 ||
bond_ctx->active_member_count == 0) {
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ link.link_status = RTE_ETH_LINK_DOWN;
+ rte_eth_linkstatus_set(ethdev, &link);
return 0;
}
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ link.link_status = RTE_ETH_LINK_UP;
if (wait_to_complete)
link_update = rte_eth_link_get;
@@ -2623,7 +2643,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* Setting link speed to UINT32_MAX to ensure we pick up the
* value of the first active member
*/
- ethdev->data->dev_link.link_speed = UINT32_MAX;
+ link.link_speed = UINT32_MAX;
/**
* link speed is minimum value of all the members link speed as
@@ -2634,19 +2654,16 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
ret = link_update(bond_ctx->active_members[idx],
&member_link);
if (ret < 0) {
- ethdev->data->dev_link.link_speed =
- RTE_ETH_SPEED_NUM_NONE;
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
RTE_BOND_LOG(ERR,
"Member (port %u) link get failed: %s",
bond_ctx->active_members[idx],
rte_strerror(-ret));
- return 0;
+ goto done;
}
- if (member_link.link_speed <
- ethdev->data->dev_link.link_speed)
- ethdev->data->dev_link.link_speed =
- member_link.link_speed;
+ if (member_link.link_speed < link.link_speed)
+ link.link_speed = member_link.link_speed;
}
break;
case BONDING_MODE_ACTIVE_BACKUP:
@@ -2656,16 +2673,14 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
bond_ctx->current_primary_port,
rte_strerror(-ret));
- return 0;
+ goto done;
}
- ethdev->data->dev_link.link_speed = member_link.link_speed;
+ link.link_speed = member_link.link_speed;
break;
case BONDING_MODE_8023AD:
- ethdev->data->dev_link.link_autoneg =
- bond_ctx->mode4.member_link.link_autoneg;
- ethdev->data->dev_link.link_duplex =
- bond_ctx->mode4.member_link.link_duplex;
+ link.link_autoneg = bond_ctx->mode4.member_link.link_autoneg;
+ link.link_duplex = bond_ctx->mode4.member_link.link_duplex;
/* fall through */
/* to update link speed */
case BONDING_MODE_ROUND_ROBIN:
@@ -2677,7 +2692,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* In theses mode the maximum theoretical link speed is the sum
* of all the members
*/
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
one_link_update_succeeded = false;
for (idx = 0; idx < bond_ctx->active_member_count; idx++) {
@@ -2692,16 +2707,15 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
}
one_link_update_succeeded = true;
- ethdev->data->dev_link.link_speed +=
- member_link.link_speed;
+ link.link_speed += member_link.link_speed;
}
- if (!one_link_update_succeeded) {
+ if (!one_link_update_succeeded)
RTE_BOND_LOG(ERR, "All members link get failed");
- return 0;
- }
}
+done:
+ rte_eth_linkstatus_set(ethdev, &link);
return 0;
}
@@ -3069,7 +3083,7 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
- struct rte_eth_link link;
+ struct rte_eth_link link, bond_link;
int rc = -1;
int ret;
@@ -3122,7 +3136,8 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
goto link_update;
/* check link state properties if bonding link is up*/
- if (bonding_eth_dev->data->dev_link.link_status == RTE_ETH_LINK_UP) {
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ if (bond_link.link_status == RTE_ETH_LINK_UP) {
if (link_properties_valid(bonding_eth_dev, &link) != 0)
RTE_BOND_LOG(ERR, "Invalid link properties "
"for member %d in bonding mode %d",
@@ -3137,8 +3152,10 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
*/
if (internals->active_member_count < 1) {
/* If first active member, then change link status */
- bonding_eth_dev->data->dev_link.link_status =
- RTE_ETH_LINK_UP;
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ bond_link.link_status = RTE_ETH_LINK_UP;
+ rte_eth_linkstatus_set(bonding_eth_dev, &bond_link);
+
internals->current_primary_port = port_id;
lsc_flag = 1;
@@ -3194,7 +3211,8 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
rte_eal_alarm_cancel(bond_ethdev_delayed_lsc_propagation,
bonding_eth_dev);
- if (bonding_eth_dev->data->dev_link.link_status) {
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ if (bond_link.link_status) {
if (internals->link_up_delay_ms > 0)
rte_eal_alarm_set(internals->link_up_delay_ms * 1000,
bond_ethdev_delayed_lsc_propagation,
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 5/8] net/bonding: restrict control operations in secondary process
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
` (3 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 6/8] net/bonding: add extended statistics Stephen Hemminger
` (2 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Weijun Pan, Chas Williams,
Min Hu (Connor), Anatoly Burakov, Robert Sanford, Pablo de Lara,
Declan Doherty
Secondary process control operations could reach memory
not shared by primary process. Restrict the API to only
those things that should work by reading shared state.
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Bugzilla ID: 1900
Reported-by: Weijun Pan <wpan3636@gmail.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_8023ad.c | 18 +++++++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 24 +++++++++++++++++++++++
drivers/net/bonding/rte_eth_bond_pmd.c | 18 +++++++++++++++--
3 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..29d4de0e1d 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1440,6 +1440,9 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
bond_dev = &rte_eth_devices[port_id];
internals = bond_dev->data->dev_private;
@@ -1509,6 +1512,9 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
struct rte_eth_dev *bond_dev;
int err;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
return err;
@@ -1532,6 +1538,9 @@ rte_eth_bond_8023ad_member_info(uint16_t port_id, uint16_t member_id,
struct bond_dev_private *internals;
struct port *port;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (info == NULL || valid_bonding_port_id(port_id) != 0 ||
rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
return -EINVAL;
@@ -1564,6 +1573,9 @@ bond_8023ad_ext_validate(uint16_t port_id, uint16_t member_id)
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
return -EINVAL;
@@ -1728,6 +1740,9 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1757,6 +1772,9 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..505dd86dd8 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -159,6 +159,9 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -643,6 +646,9 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
int retval;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +787,9 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -834,6 +843,9 @@ rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -924,6 +936,9 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -950,6 +965,9 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -991,6 +1009,9 @@ rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1036,6 +1057,9 @@ rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6f3c13d6fb..0e18ded4a5 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -3695,12 +3695,26 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
const struct bond_dev_private *internals = dev->data->dev_private;
dump_basic(dev, f);
- if (internals->mode == BONDING_MODE_8023AD)
+
+ /* LACP state machine data is private to the primary process. */
+ if (internals->mode == BONDING_MODE_8023AD &&
+ rte_eal_process_type() == RTE_PROC_PRIMARY)
dump_lacp(dev->data->port_id, f);
return 0;
}
+/* Restricted set of ops allowed in secondary process. */
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+ .eth_dev_priv_dump = bond_ethdev_priv_dump,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3885,7 +3899,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 6/8] net/bonding: add extended statistics
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
` (4 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 5/8] net/bonding: restrict control operations in secondary process Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 7/8] test/bonding: add extended statistics test Stephen Hemminger
2026-08-30 20:23 ` [PATCH 8/8] doc: add bonding features matrix Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Chas Williams, Min Hu (Connor)
The bonding device reported only the sum of its members, so there was
no way to see how traffic was distributed across them.
This patch adds xstats which reports per-member packets/bytes/errors.
Also drop RTE_ETH_DEV_AUTOFILL_QUEUE_XSTATS since driver never
implemented per-queue stats.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/rel_notes/release_26_11.rst | 6 ++
drivers/net/bonding/rte_eth_bond_pmd.c | 113 ++++++++++++++++++++++++-
2 files changed, 117 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 87c7e81bde..83d70872af 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added extended statistics to bonding PMD.**
+
+ Extended statistics now report the packets, bytes and errors
+ of each member as ``rx_memberN_*`` and ``tx_memberN_*``.
+ The per-queue entries, which were always zero, are no longer reported.
+
Removed Items
-------------
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 0e18ded4a5..46bc1db120 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2764,6 +2764,111 @@ bond_ethdev_stats_reset(struct rte_eth_dev *dev)
return err;
}
+#define BOND_MEMBER_STAT_PREFIX_LEN (sizeof("rx_member") - 1 + 5 + 1)
+
+struct bond_member_stats_name_off {
+ char name[RTE_ETH_XSTATS_NAME_SIZE - BOND_MEMBER_STAT_PREFIX_LEN];
+ size_t offset;
+};
+
+static const struct bond_member_stats_name_off bond_member_rxq_stats_strings[] = {
+ { "packets", offsetof(struct rte_eth_stats, ipackets) },
+ { "bytes", offsetof(struct rte_eth_stats, ibytes) },
+ { "errors", offsetof(struct rte_eth_stats, ierrors) },
+};
+
+#define BOND_NB_MEMBER_RX_STATS RTE_DIM(bond_member_rxq_stats_strings)
+
+static const struct bond_member_stats_name_off bond_member_txq_stats_strings[] = {
+ { "packets", offsetof(struct rte_eth_stats, opackets) },
+ { "bytes", offsetof(struct rte_eth_stats, obytes) },
+ { "errors", offsetof(struct rte_eth_stats, oerrors) },
+};
+
+#define BOND_NB_MEMBER_TX_STATS RTE_DIM(bond_member_txq_stats_strings)
+
+#define BOND_NB_MEMBER_STATS (BOND_NB_MEMBER_RX_STATS + BOND_NB_MEMBER_TX_STATS)
+
+static int
+bond_ethdev_xstats_get_names(struct rte_eth_dev *dev,
+ struct rte_eth_xstat_name *xstats_names,
+ unsigned int limit)
+{
+ struct bond_dev_private *internals = dev->data->dev_private;
+ unsigned int count = internals->member_count * BOND_NB_MEMBER_STATS;
+ unsigned int i, j;
+
+ if (xstats_names == NULL || limit < count)
+ return count;
+
+ count = 0;
+ for (i = 0; i < internals->member_count; i++) {
+ uint16_t member_id = internals->members[i].port_id;
+
+ for (j = 0; j < BOND_NB_MEMBER_RX_STATS; j++)
+ snprintf(xstats_names[count++].name,
+ RTE_ETH_XSTATS_NAME_SIZE, "rx_member%u_%s",
+ member_id, bond_member_rxq_stats_strings[j].name);
+
+ for (j = 0; j < BOND_NB_MEMBER_TX_STATS; j++)
+ snprintf(xstats_names[count++].name,
+ RTE_ETH_XSTATS_NAME_SIZE, "tx_member%u_%s",
+ member_id, bond_member_txq_stats_strings[j].name);
+ }
+
+ return count;
+}
+
+static int
+bond_ethdev_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
+ unsigned int n)
+{
+ const struct bond_dev_private *internals = dev->data->dev_private;
+ unsigned int count = internals->member_count * BOND_NB_MEMBER_STATS;
+ unsigned int i, j;
+
+ if (xstats == NULL || n < count)
+ return count;
+
+ count = 0;
+ for (i = 0; i < internals->member_count; i++) {
+ struct rte_eth_stats member_stats;
+ uint16_t member_id = internals->members[i].port_id;
+
+ /* If member query fails just report zero. */
+ if (rte_eth_stats_get(member_id, &member_stats) < 0)
+ memset(&member_stats, 0, sizeof(member_stats));
+
+ for (j = 0; j < BOND_NB_MEMBER_RX_STATS; j++) {
+ xstats[count].id = count;
+ xstats[count].value = *(const uint64_t *)((const char *)&member_stats +
+ bond_member_rxq_stats_strings[j].offset);
+ count++;
+ }
+
+ for (j = 0; j < BOND_NB_MEMBER_TX_STATS; j++) {
+ xstats[count].id = count;
+ xstats[count].value = *(const uint64_t *)((const char *)&member_stats +
+ bond_member_txq_stats_strings[j].offset);
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static int
+bond_ethdev_xstats_reset(struct rte_eth_dev *dev)
+{
+ const struct bond_dev_private *internals = dev->data->dev_private;
+ uint16_t i;
+
+ for (i = 0; i < internals->member_count; i++)
+ rte_eth_stats_reset(internals->members[i].port_id);
+
+ return 0;
+}
+
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev)
{
@@ -3710,6 +3815,8 @@ static const struct eth_dev_ops secondary_dev_ops = {
.dev_infos_get = bond_ethdev_info,
.link_update = bond_ethdev_link_update,
.stats_get = bond_ethdev_stats_get,
+ .xstats_get = bond_ethdev_xstats_get,
+ .xstats_get_names = bond_ethdev_xstats_get_names,
.reta_query = bond_ethdev_rss_reta_query,
.rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
.eth_dev_priv_dump = bond_ethdev_priv_dump,
@@ -3729,6 +3836,9 @@ const struct eth_dev_ops default_dev_ops = {
.link_update = bond_ethdev_link_update,
.stats_get = bond_ethdev_stats_get,
.stats_reset = bond_ethdev_stats_reset,
+ .xstats_get = bond_ethdev_xstats_get,
+ .xstats_get_names = bond_ethdev_xstats_get_names,
+ .xstats_reset = bond_ethdev_xstats_reset,
.promiscuous_enable = bond_ethdev_promiscuous_enable,
.promiscuous_disable = bond_ethdev_promiscuous_disable,
.allmulticast_enable = bond_ethdev_allmulticast_enable,
@@ -3780,8 +3890,7 @@ bond_alloc(struct rte_vdev_device *dev, uint8_t mode)
}
eth_dev->dev_ops = &default_dev_ops;
- eth_dev->data->dev_flags = RTE_ETH_DEV_INTR_LSC |
- RTE_ETH_DEV_AUTOFILL_QUEUE_XSTATS;
+ eth_dev->data->dev_flags = RTE_ETH_DEV_INTR_LSC;
rte_spinlock_init(&internals->lock);
rte_spinlock_init(&internals->lsc_lock);
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 7/8] test/bonding: add extended statistics test
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
` (5 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 6/8] net/bonding: add extended statistics Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
2026-08-30 20:23 ` [PATCH 8/8] doc: add bonding features matrix Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Chas Williams, Min Hu (Connor)
The bonding extended statistics had no coverage. Add a test that
exercises the contract of the three new dev_ops.
The test checks that the name and value queries agree on the count,
that a NULL or undersized table returns the required size rather than
filling it, that the per-member names are generated from the member
port ids in member order, and that a burst received on one member is
attributed to that member alone and to no other. It then resets and
confirms the member counters are cleared.
ethdev prepends its own basic statistics to the driver's, so the
offset of the first member entry is discovered at runtime rather than
assumed to be zero.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/test_link_bonding.c | 148 +++++++++++++++++++++++++++++++++++
1 file changed, 148 insertions(+)
diff --git a/app/test/test_link_bonding.c b/app/test/test_link_bonding.c
index 19b064771a..33652948be 100644
--- a/app/test/test_link_bonding.c
+++ b/app/test/test_link_bonding.c
@@ -1663,6 +1663,153 @@ test_roundrobin_rx_burst_on_single_member(void)
return remove_members_and_stop_bonding_device();
}
+/* The bonding PMD reports rx and tx packets, bytes and errors per member.
+ * ethdev prepends its own basic statistics to the driver's, so the member
+ * entries start at an offset that has to be discovered at runtime.
+ */
+#define TEST_XSTATS_PER_MEMBER 6
+
+#define TEST_XSTATS_MEMBER_COUNT 4
+
+#define TEST_XSTATS_DRIVER_COUNT \
+ (TEST_XSTATS_MEMBER_COUNT * TEST_XSTATS_PER_MEMBER)
+
+/* Enough room for the driver entries plus any basic statistics. */
+#define TEST_XSTATS_MAX 64
+
+static int
+test_xstats(void)
+{
+ struct rte_mbuf *gen_pkt_burst[MAX_PKT_BURST];
+ struct rte_mbuf *rx_pkt_burst[MAX_PKT_BURST] = { NULL };
+ struct rte_eth_xstat_name names[TEST_XSTATS_MAX];
+ struct rte_eth_xstat xstats[TEST_XSTATS_MAX];
+ uint16_t bonding_port_id = test_params->bonding_port_id;
+ unsigned int count;
+ int burst_size = 17;
+ int basic, total;
+ int i, j;
+ char name[RTE_ETH_XSTATS_NAME_SIZE];
+
+ TEST_ASSERT_SUCCESS(initialize_bonding_device_with_members(
+ BONDING_MODE_ROUND_ROBIN, 0,
+ TEST_XSTATS_MEMBER_COUNT, 1),
+ "Failed to initialize bonding device with members");
+
+ /* A NULL table is a query for the number of xstats. The bonding
+ * driver contributes six per member on top of the basic statistics.
+ */
+ total = rte_eth_xstats_get_names(bonding_port_id, NULL, 0);
+ TEST_ASSERT(total > TEST_XSTATS_DRIVER_COUNT,
+ "Expected more than %d xstats names, got %d",
+ TEST_XSTATS_DRIVER_COUNT, total);
+ TEST_ASSERT(total <= TEST_XSTATS_MAX,
+ "xstats count %d exceeds test table size %d",
+ total, TEST_XSTATS_MAX);
+
+ basic = total - TEST_XSTATS_DRIVER_COUNT;
+
+ count = rte_eth_xstats_get(bonding_port_id, NULL, 0);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "xstats count %u differs from names count %d",
+ count, total);
+
+ /* A table smaller than the xstats count must be rejected, and the
+ * required size returned instead.
+ */
+ count = rte_eth_xstats_get_names(bonding_port_id, names, total - 1);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Undersized names query returned %u, expected %d",
+ count, total);
+
+ count = rte_eth_xstats_get_names(bonding_port_id, names, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats names, got %u", total, count);
+
+ /* Driver names follow the basic ones, per member port id in member
+ * order, rx before tx.
+ */
+ for (i = 0; i < TEST_XSTATS_MEMBER_COUNT; i++) {
+ uint16_t member_id = test_params->member_port_ids[i];
+ static const char * const dir[] = { "rx", "tx" };
+ static const char * const stat[] = {
+ "packets", "bytes", "errors"
+ };
+ unsigned int base = basic + i * TEST_XSTATS_PER_MEMBER;
+ unsigned int d, s, idx = 0;
+
+ for (d = 0; d < RTE_DIM(dir); d++) {
+ for (s = 0; s < RTE_DIM(stat); s++) {
+ snprintf(name, sizeof(name), "%s_member%u_%s",
+ dir[d], member_id, stat[s]);
+ TEST_ASSERT_SUCCESS(strcmp(
+ names[base + idx].name, name),
+ "xstats name %u is \"%s\", expected \"%s\"",
+ base + idx,
+ names[base + idx].name, name);
+ idx++;
+ }
+ }
+ }
+
+ /* Receive a burst on a single member, so that the per-member
+ * counters can be told apart.
+ */
+ TEST_ASSERT_EQUAL(generate_test_burst(gen_pkt_burst, burst_size,
+ 0, 1, 0, 0, 0), burst_size, "burst generation failed");
+
+ virtual_ethdev_add_mbufs_to_rx_queue(test_params->member_port_ids[0],
+ gen_pkt_burst, burst_size);
+
+ TEST_ASSERT_EQUAL(rte_eth_rx_burst(bonding_port_id, 0,
+ rx_pkt_burst, MAX_PKT_BURST), burst_size,
+ "rx burst failed");
+
+ count = rte_eth_xstats_get(bonding_port_id, xstats, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats, got %u", total, count);
+
+ /* Only the member that received the burst has a non-zero rx packet
+ * count. Ids are the index into the table.
+ */
+ for (i = 0; i < TEST_XSTATS_MEMBER_COUNT; i++) {
+ unsigned int base = basic + i * TEST_XSTATS_PER_MEMBER;
+ uint64_t rx_packets = xstats[base].value;
+
+ TEST_ASSERT_EQUAL(xstats[base].id, (uint64_t)base,
+ "xstat %u has id %"PRIu64, base, xstats[base].id);
+
+ if (i == 0)
+ TEST_ASSERT_EQUAL(rx_packets, (uint64_t)burst_size,
+ "Member %u rx packets is %"PRIu64", expected %d",
+ test_params->member_port_ids[i],
+ rx_packets, burst_size);
+ else
+ TEST_ASSERT_EQUAL(rx_packets, 0,
+ "Member %u rx packets is %"PRIu64", expected 0",
+ test_params->member_port_ids[i], rx_packets);
+ }
+
+ /* Reset clears the underlying member statistics. */
+ TEST_ASSERT_SUCCESS(rte_eth_xstats_reset(bonding_port_id),
+ "Failed to reset xstats");
+
+ count = rte_eth_xstats_get(bonding_port_id, xstats, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats after reset, got %u",
+ total, count);
+
+ for (i = basic; i < total; i++)
+ TEST_ASSERT_EQUAL(xstats[i].value, 0,
+ "xstat \"%s\" is %"PRIu64" after reset, expected 0",
+ names[i].name, xstats[i].value);
+
+ for (j = 0; j < burst_size; j++)
+ rte_pktmbuf_free(rx_pkt_burst[j]);
+
+ return remove_members_and_stop_bonding_device();
+}
+
#define TEST_ROUNDROBIN_TX_BURST_MEMBER_COUNT (3)
static int
@@ -5160,6 +5307,7 @@ static struct unit_test_suite link_bonding_test_suite = {
TEST_CASE(test_set_bonding_port_initialization_mac_assignment),
TEST_CASE(test_status_interrupt),
TEST_CASE(test_adding_member_after_bonding_device_started),
+ TEST_CASE(test_xstats),
TEST_CASE(test_roundrobin_tx_burst),
TEST_CASE(test_roundrobin_tx_burst_member_tx_fail),
TEST_CASE(test_roundrobin_rx_burst_on_single_member),
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH 8/8] doc: add bonding features matrix
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
` (6 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 7/8] test/bonding: add extended statistics test Stephen Hemminger
@ 2026-08-30 20:23 ` Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-30 20:23 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
Bonding was the only in-tree net PMD without an entry in
doc/guides/nics/features, so it did not appear in the driver
overview table.
Add bonding.ini covering the features the driver implements
itself. Offloads that are only intersected from the members are
left unset, since those describe the members rather than the
bonding device.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/nics/features/bonding.ini | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 doc/guides/nics/features/bonding.ini
diff --git a/doc/guides/nics/features/bonding.ini b/doc/guides/nics/features/bonding.ini
new file mode 100644
index 0000000000..79bd47d3f2
--- /dev/null
+++ b/doc/guides/nics/features/bonding.ini
@@ -0,0 +1,28 @@
+;
+; Supported features of the 'bonding' network poll mode driver.
+;
+; Refer to default.ini for the full list of available PMD features.
+;
+[Features]
+Speed capabilities = Y
+Link speed configuration = Y
+Link status = Y
+Link status event = Y
+Fast mbuf free = Y
+MTU update = Y
+Promiscuous mode = Y
+Allmulticast mode = Y
+Unicast MAC filter = Y
+RSS hash = Y
+RSS key update = Y
+RSS reta update = Y
+VLAN filter = Y
+Basic stats = Y
+Extended stats = Y
+FreeBSD = Y
+Linux = Y
+ARMv8 = Y
+Power8 = Y
+x86-32 = Y
+x86-64 = Y
+Usage doc = Y
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 0/8] net/bonding: fixes and per-member stats
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
` (3 preceding siblings ...)
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
` (7 more replies)
4 siblings, 8 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
The recent RFC's to bonding prompted me to (with AI assistance) to
look at current state of bonding PMD. Several issues were found.
This incorporates the RFC patch series and modifies it to
have Fixes and more checks.
Added proper extended statistics which allows looking how
members are doing.
Weijun Pan reported the stats double counting and the secondary
process crash (Bugzilla 1900).
v2 - fix bonding.ini flow items
Stephen Hemminger (7):
net/bonding: fix TLB member ordering with unusable member
net/bonding: skip unavailable members in device info
net/bonding: use atomic link status accessors
net/bonding: restrict control ops in secondary process
net/bonding: add extended statistics
test/bonding: add extended statistics test
doc: add bonding features matrix
Weijun Pan (1):
net/bonding: skip unavailable member stats
app/test/test_link_bonding.c | 148 ++++++++++
doc/guides/nics/features/bonding.ini | 40 +++
doc/guides/rel_notes/release_26_11.rst | 6 +
drivers/net/bonding/rte_eth_bond_8023ad.c | 18 ++
drivers/net/bonding/rte_eth_bond_api.c | 24 ++
drivers/net/bonding/rte_eth_bond_pmd.c | 338 +++++++++++++++++-----
6 files changed, 495 insertions(+), 79 deletions(-)
create mode 100644 doc/guides/nics/features/bonding.ini
--
2.53.0
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 1/8] net/bonding: fix TLB member ordering with unusable member
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
` (6 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Andrew Rybchenko, Igor Romanov, Declan Doherty, Daniel Mrzyglod
The TLB rebalance callback assumed every active member could be
queried. If the link is down, has no usable speed, or stats fail,
the member was ordered on garbage bandwidth values.
Skip such members. Transmit reads active_member_count entries of
tlb_members_order but only measured members are written, so pad
the tail rather than leave stale port ids behind.
Members are not deactivated here; the link status callback owns
that and also updates the primary port and bonding link state.
The callback runs every millisecond, so an unusable member logged
1000 times per second. Log only on state change.
Fixes: fc1134c79283 ("net/bonding: check status of getting link info")
Fixes: 7c76a747e68c ("bond: add mode 5")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 99 ++++++++++++++++----------
1 file changed, 62 insertions(+), 37 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6a4f997b5a..5b4b3d6ac2 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -33,6 +33,7 @@
/* Table for statistics in mode 5 TLB */
static uint64_t tlb_last_obytets[RTE_MAX_ETHPORTS];
+static bool tlb_unusable[RTE_MAX_ETHPORTS];
static inline size_t
get_vlan_offset(struct rte_ether_hdr *eth_hdr, uint16_t *proto)
@@ -865,8 +866,10 @@ void
bond_tlb_activate_member(struct bond_dev_private *internals) {
int i;
- for (i = 0; i < internals->active_member_count; i++)
+ for (i = 0; i < internals->active_member_count; i++) {
tlb_last_obytets[internals->active_members[i]] = 0;
+ tlb_unusable[internals->active_members[i]] = false;
+ }
}
static int
@@ -890,22 +893,10 @@ bandwidth_cmp(const void *a, const void *b)
}
static void
-bandwidth_left(uint16_t port_id, uint64_t load, uint8_t update_idx,
- struct bwg_member *bwg_member)
+bandwidth_left(uint64_t load, uint64_t link_bwg, uint8_t update_idx,
+ struct bwg_member *bwg_member)
{
- struct rte_eth_link link_status;
- int ret;
-
- ret = rte_eth_link_get_nowait(port_id, &link_status);
- if (ret < 0) {
- RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
- port_id, rte_strerror(-ret));
- return;
- }
- uint64_t link_bwg = link_status.link_speed * 1000000ULL / 8;
- if (link_bwg == 0)
- return;
- link_bwg = link_bwg * (update_idx+1) * REORDER_PERIOD_MS;
+ link_bwg = link_bwg * (update_idx + 1) * REORDER_PERIOD_MS;
bwg_member->bwg_left_int = (link_bwg - 1000 * load) / link_bwg;
bwg_member->bwg_left_remainder = (link_bwg - 1000 * load) % link_bwg;
}
@@ -914,44 +905,78 @@ static void
bond_ethdev_update_tlb_member_cb(void *arg)
{
struct bond_dev_private *internals = arg;
- struct rte_eth_stats member_stats;
struct bwg_member bwg_array[RTE_MAX_ETHPORTS];
- uint16_t member_count;
- uint64_t tx_bytes;
-
- uint8_t update_stats = 0;
- uint16_t member_id;
+ uint16_t active_count = internals->active_member_count;
+ uint16_t member_count = 0;
+ bool update_stats;
uint16_t i;
internals->member_update_idx++;
+ update_stats = internals->member_update_idx >= REORDER_PERIOD_MS;
+ for (i = 0; i < active_count; i++) {
+ uint16_t member_id = internals->active_members[i];
+ struct rte_eth_link link;
+ struct rte_eth_stats stats;
+ const char *reason = NULL;
+ int ret;
- if (internals->member_update_idx >= REORDER_PERIOD_MS)
- update_stats = 1;
+ ret = rte_eth_link_get_nowait(member_id, &link);
+ if (ret == 0)
+ ret = rte_eth_stats_get(member_id, &stats);
- for (i = 0; i < internals->active_member_count; i++) {
- member_id = internals->active_members[i];
- rte_eth_stats_get(member_id, &member_stats);
- tx_bytes = member_stats.obytes - tlb_last_obytets[member_id];
- bandwidth_left(member_id, tx_bytes,
- internals->member_update_idx, &bwg_array[i]);
- bwg_array[i].member = member_id;
-
- if (update_stats) {
- tlb_last_obytets[member_id] = member_stats.obytes;
+ if (ret < 0)
+ reason = rte_strerror(-ret);
+ else if (link.link_status == RTE_ETH_LINK_DOWN)
+ reason = "link down";
+ else if (link.link_speed == RTE_ETH_SPEED_NUM_NONE ||
+ link.link_speed == RTE_ETH_SPEED_NUM_UNKNOWN)
+ reason = "link speed unknown";
+
+ /*
+ * Skip the member rather than treat it as idle, which would
+ * sort it first and attract traffic. Deactivating it is the
+ * link status callback's job. Log only on state change,
+ * this runs every millisecond.
+ */
+ if (reason != NULL) {
+ if (!tlb_unusable[member_id]) {
+ tlb_unusable[member_id] = true;
+ RTE_BOND_LOG(ERR, "Member (port %u) excluded from TLB ordering: %s",
+ member_id, reason);
+ }
+ continue;
}
+ if (tlb_unusable[member_id]) {
+ tlb_unusable[member_id] = false;
+ RTE_BOND_LOG(INFO, "Member (port %u) usable for TLB ordering",
+ member_id);
+ }
+
+ bandwidth_left(stats.obytes - tlb_last_obytets[member_id],
+ link.link_speed * 1000000ULL / 8,
+ internals->member_update_idx,
+ &bwg_array[member_count]);
+ bwg_array[member_count++].member = member_id;
+
+ if (update_stats)
+ tlb_last_obytets[member_id] = stats.obytes;
}
- if (update_stats == 1)
+ if (update_stats)
internals->member_update_idx = 0;
- member_count = i;
qsort(bwg_array, member_count, sizeof(bwg_array[0]), bandwidth_cmp);
for (i = 0; i < member_count; i++)
internals->tlb_members_order[i] = bwg_array[i].member;
+ /* Transmit reads active_member_count entries, don't leave stale ones. */
+ for (i = member_count; i < active_count; i++)
+ internals->tlb_members_order[i] = member_count > 0 ?
+ bwg_array[0].member : internals->active_members[i];
+
rte_eal_alarm_set(REORDER_PERIOD_MS * 1000, bond_ethdev_update_tlb_member_cb,
- (struct bond_dev_private *)internals);
+ internals);
}
static uint16_t
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 2/8] net/bonding: skip unavailable member stats
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
` (5 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev
Cc: Weijun Pan, stable, Chas Williams, Min Hu (Connor), Pablo de Lara,
Declan Doherty, Robert Sanford
From: Weijun Pan <wpan3636@gmail.com>
bond_ethdev_stats_get() accumulates statistics from each bonding
member. If rte_eth_stats_get() fails for a member, the local
member_stats structure may be left unchanged.
Skip members whose statistics cannot be read, instead of accumulating
stale or uninitialized counters.
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Signed-off-by: Weijun Pan <wpan3636@gmail.com>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 5b4b3d6ac2..6235c07679 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2704,7 +2704,9 @@ bond_ethdev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
int i;
for (i = 0; i < internals->member_count; i++) {
- rte_eth_stats_get(internals->members[i].port_id, &member_stats);
+ if (rte_eth_stats_get(internals->members[i].port_id,
+ &member_stats) != 0)
+ continue;
stats->ipackets += member_stats.ipackets;
stats->opackets += member_stats.opackets;
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 3/8] net/bonding: skip unavailable members in device info
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
` (4 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Declan Doherty
bond_ethdev_info() queries every member to derive the maximum number of
Rx and Tx queues the bonding device can support. A single failing
member aborted the whole call.
Skip members that cannot be queried and derive the queue limits from
the rest.
A bonding device with no members at all keeps reporting UINT16_MAX, as
it did before: no member has constrained it yet.
Fixes: acfb51e2fe96 ("net/bonding: fix number of bonding Tx/Rx queues")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6235c07679..7579b97b06 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2360,28 +2360,40 @@ bond_ethdev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
/* Max number of tx/rx queues that the bonding device can support is the
* minimum values of the bonding members, as all members must be capable
* of supporting the same number of tx/rx queues.
+ *
+ * A member may be unqueryable here: it can be removed concurrently, or
+ * simply not be probed in this process. Skip those, but do not report
+ * the UINT16_MAX default if no member could be queried at all, since
+ * that would let any queue count pass rte_eth_dev_configure().
*/
if (internals->member_count > 0) {
struct rte_eth_dev_info member_info;
+ uint16_t queried = 0;
uint16_t idx;
for (idx = 0; idx < internals->member_count; idx++) {
member = internals->members[idx];
ret = rte_eth_dev_info_get(member.port_id, &member_info);
if (ret != 0) {
- RTE_BOND_LOG(ERR,
- "Error getting device (port %u) info: %s",
+ RTE_BOND_LOG(WARNING,
+ "Skipping device (port %u) info: %s",
member.port_id, strerror(-ret));
-
- return ret;
+ continue;
}
+ queried++;
+
if (member_info.max_rx_queues < max_nb_rx_queues)
max_nb_rx_queues = member_info.max_rx_queues;
if (member_info.max_tx_queues < max_nb_tx_queues)
max_nb_tx_queues = member_info.max_tx_queues;
}
+
+ if (queried == 0) {
+ RTE_BOND_LOG(ERR, "No member device info available");
+ return -ENODEV;
+ }
}
dev_info->max_rx_queues = max_nb_rx_queues;
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 4/8] net/bonding: use atomic link status accessors
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
` (2 preceding siblings ...)
2026-08-31 16:06 ` [PATCH v2 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 5/8] net/bonding: restrict control ops in secondary process Stephen Hemminger
` (3 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Chas Williams, Min Hu (Connor),
Declan Doherty, Pablo de Lara, Robert Sanford
The bonding PMD read and wrote rte_eth_dev_data.dev_link directly.
Since a struct rte_eth_link is a 64 bit value updated concurrently
it needs to be done atomically. Use existing helpers.
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_pmd.c | 84 ++++++++++++++++----------
1 file changed, 51 insertions(+), 33 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 7579b97b06..6f3c13d6fb 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -1461,8 +1461,12 @@ link_properties_set(struct rte_eth_dev *ethdev, struct rte_eth_link *member_link
* In any other mode the link properties are set to default
* values of AUTONEG/DUPLEX
*/
- ethdev->data->dev_link.link_autoneg = RTE_ETH_LINK_AUTONEG;
- ethdev->data->dev_link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+ struct rte_eth_link link;
+
+ rte_eth_linkstatus_get(ethdev, &link);
+ link.link_autoneg = RTE_ETH_LINK_AUTONEG;
+ link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+ rte_eth_linkstatus_set(ethdev, &link);
}
}
@@ -2077,6 +2081,16 @@ bond_ethdev_primary_set(struct bond_dev_private *internals,
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev);
+static void
+bond_ethdev_link_down(struct rte_eth_dev *eth_dev)
+{
+ struct rte_eth_link link;
+
+ rte_eth_linkstatus_get(eth_dev, &link);
+ link.link_status = RTE_ETH_LINK_DOWN;
+ rte_eth_linkstatus_set(eth_dev, &link);
+}
+
static int
bond_ethdev_start(struct rte_eth_dev *eth_dev)
{
@@ -2090,7 +2104,7 @@ bond_ethdev_start(struct rte_eth_dev *eth_dev)
return -1;
}
- eth_dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ bond_ethdev_link_down(eth_dev);
eth_dev->data->dev_started = 1;
internals = eth_dev->data->dev_private;
@@ -2242,7 +2256,7 @@ bond_ethdev_stop(struct rte_eth_dev *eth_dev)
tlb_last_obytets[internals->active_members[i]] = 0;
}
- eth_dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ bond_ethdev_link_down(eth_dev);
eth_dev->data->dev_started = 0;
if (internals->link_status_polling_enabled) {
@@ -2537,6 +2551,7 @@ bond_ethdev_member_link_status_change_monitor(void *cb_arg)
{
struct rte_eth_dev *bonding_ethdev, *member_ethdev;
struct bond_dev_private *internals;
+ struct rte_eth_link member_link;
/* Default value for polling member found is true as we don't want to
* disable the polling thread if we cannot get the lock */
@@ -2569,9 +2584,11 @@ bond_ethdev_member_link_status_change_monitor(void *cb_arg)
member_ethdev->dev_ops->link_update(member_ethdev,
internals->members[i].link_status_wait_to_complete);
+ rte_eth_linkstatus_get(member_ethdev, &member_link);
+
/* if link status has changed since last checked then call lsc
* event callback */
- if (member_ethdev->data->dev_link.link_status !=
+ if (member_link.link_status !=
internals->members[i].last_link_status) {
bond_ethdev_lsc_event_callback(internals->members[i].port_id,
RTE_ETH_EVENT_INTR_LSC,
@@ -2595,6 +2612,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
struct bond_dev_private *bond_ctx;
struct rte_eth_link member_link;
+ struct rte_eth_link link;
bool one_link_update_succeeded;
uint32_t idx;
@@ -2602,15 +2620,17 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
bond_ctx = ethdev->data->dev_private;
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ rte_eth_linkstatus_get(ethdev, &link);
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
if (ethdev->data->dev_started == 0 ||
bond_ctx->active_member_count == 0) {
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ link.link_status = RTE_ETH_LINK_DOWN;
+ rte_eth_linkstatus_set(ethdev, &link);
return 0;
}
- ethdev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ link.link_status = RTE_ETH_LINK_UP;
if (wait_to_complete)
link_update = rte_eth_link_get;
@@ -2623,7 +2643,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* Setting link speed to UINT32_MAX to ensure we pick up the
* value of the first active member
*/
- ethdev->data->dev_link.link_speed = UINT32_MAX;
+ link.link_speed = UINT32_MAX;
/**
* link speed is minimum value of all the members link speed as
@@ -2634,19 +2654,16 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
ret = link_update(bond_ctx->active_members[idx],
&member_link);
if (ret < 0) {
- ethdev->data->dev_link.link_speed =
- RTE_ETH_SPEED_NUM_NONE;
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
RTE_BOND_LOG(ERR,
"Member (port %u) link get failed: %s",
bond_ctx->active_members[idx],
rte_strerror(-ret));
- return 0;
+ goto done;
}
- if (member_link.link_speed <
- ethdev->data->dev_link.link_speed)
- ethdev->data->dev_link.link_speed =
- member_link.link_speed;
+ if (member_link.link_speed < link.link_speed)
+ link.link_speed = member_link.link_speed;
}
break;
case BONDING_MODE_ACTIVE_BACKUP:
@@ -2656,16 +2673,14 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
RTE_BOND_LOG(ERR, "Member (port %u) link get failed: %s",
bond_ctx->current_primary_port,
rte_strerror(-ret));
- return 0;
+ goto done;
}
- ethdev->data->dev_link.link_speed = member_link.link_speed;
+ link.link_speed = member_link.link_speed;
break;
case BONDING_MODE_8023AD:
- ethdev->data->dev_link.link_autoneg =
- bond_ctx->mode4.member_link.link_autoneg;
- ethdev->data->dev_link.link_duplex =
- bond_ctx->mode4.member_link.link_duplex;
+ link.link_autoneg = bond_ctx->mode4.member_link.link_autoneg;
+ link.link_duplex = bond_ctx->mode4.member_link.link_duplex;
/* fall through */
/* to update link speed */
case BONDING_MODE_ROUND_ROBIN:
@@ -2677,7 +2692,7 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
* In theses mode the maximum theoretical link speed is the sum
* of all the members
*/
- ethdev->data->dev_link.link_speed = RTE_ETH_SPEED_NUM_NONE;
+ link.link_speed = RTE_ETH_SPEED_NUM_NONE;
one_link_update_succeeded = false;
for (idx = 0; idx < bond_ctx->active_member_count; idx++) {
@@ -2692,16 +2707,15 @@ bond_ethdev_link_update(struct rte_eth_dev *ethdev, int wait_to_complete)
}
one_link_update_succeeded = true;
- ethdev->data->dev_link.link_speed +=
- member_link.link_speed;
+ link.link_speed += member_link.link_speed;
}
- if (!one_link_update_succeeded) {
+ if (!one_link_update_succeeded)
RTE_BOND_LOG(ERR, "All members link get failed");
- return 0;
- }
}
+done:
+ rte_eth_linkstatus_set(ethdev, &link);
return 0;
}
@@ -3069,7 +3083,7 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
{
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
- struct rte_eth_link link;
+ struct rte_eth_link link, bond_link;
int rc = -1;
int ret;
@@ -3122,7 +3136,8 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
goto link_update;
/* check link state properties if bonding link is up*/
- if (bonding_eth_dev->data->dev_link.link_status == RTE_ETH_LINK_UP) {
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ if (bond_link.link_status == RTE_ETH_LINK_UP) {
if (link_properties_valid(bonding_eth_dev, &link) != 0)
RTE_BOND_LOG(ERR, "Invalid link properties "
"for member %d in bonding mode %d",
@@ -3137,8 +3152,10 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
*/
if (internals->active_member_count < 1) {
/* If first active member, then change link status */
- bonding_eth_dev->data->dev_link.link_status =
- RTE_ETH_LINK_UP;
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ bond_link.link_status = RTE_ETH_LINK_UP;
+ rte_eth_linkstatus_set(bonding_eth_dev, &bond_link);
+
internals->current_primary_port = port_id;
lsc_flag = 1;
@@ -3194,7 +3211,8 @@ bond_ethdev_lsc_event_callback(uint16_t port_id, enum rte_eth_event_type type,
rte_eal_alarm_cancel(bond_ethdev_delayed_lsc_propagation,
bonding_eth_dev);
- if (bonding_eth_dev->data->dev_link.link_status) {
+ rte_eth_linkstatus_get(bonding_eth_dev, &bond_link);
+ if (bond_link.link_status) {
if (internals->link_up_delay_ms > 0)
rte_eal_alarm_set(internals->link_up_delay_ms * 1000,
bond_ethdev_delayed_lsc_propagation,
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 5/8] net/bonding: restrict control ops in secondary process
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
` (3 preceding siblings ...)
2026-08-31 16:06 ` [PATCH v2 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 6/8] net/bonding: add extended statistics Stephen Hemminger
` (2 subsequent siblings)
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, stable, Weijun Pan, Chas Williams,
Min Hu (Connor), Anatoly Burakov, Robert Sanford, Declan Doherty,
Pablo de Lara
Secondary process control operations could reach memory not shared
by primary process. Restrict the API to only those things that
should work by reading shared state.
Bugzilla ID: 1900
Fixes: 2efb58cbab6e ("bond: new link bonding library")
Cc: stable@dpdk.org
Reported-by: Weijun Pan <wpan3636@gmail.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
drivers/net/bonding/rte_eth_bond_8023ad.c | 18 +++++++++++++++++
drivers/net/bonding/rte_eth_bond_api.c | 24 +++++++++++++++++++++++
drivers/net/bonding/rte_eth_bond_pmd.c | 18 +++++++++++++++--
3 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/drivers/net/bonding/rte_eth_bond_8023ad.c b/drivers/net/bonding/rte_eth_bond_8023ad.c
index d1f30229d0..29d4de0e1d 100644
--- a/drivers/net/bonding/rte_eth_bond_8023ad.c
+++ b/drivers/net/bonding/rte_eth_bond_8023ad.c
@@ -1440,6 +1440,9 @@ rte_eth_bond_8023ad_agg_selection_set(uint16_t port_id,
if (valid_bonding_port_id(port_id) != 0)
return -EINVAL;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
bond_dev = &rte_eth_devices[port_id];
internals = bond_dev->data->dev_private;
@@ -1509,6 +1512,9 @@ rte_eth_bond_8023ad_setup(uint16_t port_id,
struct rte_eth_dev *bond_dev;
int err;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
err = bond_8023ad_setup_validate(port_id, conf);
if (err != 0)
return err;
@@ -1532,6 +1538,9 @@ rte_eth_bond_8023ad_member_info(uint16_t port_id, uint16_t member_id,
struct bond_dev_private *internals;
struct port *port;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (info == NULL || valid_bonding_port_id(port_id) != 0 ||
rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
return -EINVAL;
@@ -1564,6 +1573,9 @@ bond_8023ad_ext_validate(uint16_t port_id, uint16_t member_id)
struct bond_dev_private *internals;
struct mode8023ad_private *mode4;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (rte_eth_bond_mode_get(port_id) != BONDING_MODE_8023AD)
return -EINVAL;
@@ -1728,6 +1740,9 @@ rte_eth_bond_8023ad_dedicated_queues_enable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
@@ -1757,6 +1772,9 @@ rte_eth_bond_8023ad_dedicated_queues_disable(uint16_t port)
struct rte_eth_dev *dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(port) != 0)
return -EINVAL;
diff --git a/drivers/net/bonding/rte_eth_bond_api.c b/drivers/net/bonding/rte_eth_bond_api.c
index d9b6f1c417..505dd86dd8 100644
--- a/drivers/net/bonding/rte_eth_bond_api.c
+++ b/drivers/net/bonding/rte_eth_bond_api.c
@@ -159,6 +159,9 @@ rte_eth_bond_create(const char *name, uint8_t mode, uint8_t socket_id)
char devargs[52];
int ret;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (name == NULL) {
RTE_BOND_LOG(ERR, "Invalid name specified");
return -EINVAL;
@@ -643,6 +646,9 @@ rte_eth_bond_member_add(uint16_t bonding_port_id, uint16_t member_port_id)
int retval;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -781,6 +787,9 @@ rte_eth_bond_member_remove(uint16_t bonding_port_id, uint16_t member_port_id)
struct bond_dev_private *internals;
int retval;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -834,6 +843,9 @@ rte_eth_bond_primary_set(uint16_t bonding_port_id, uint16_t member_port_id)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -924,6 +936,9 @@ rte_eth_bond_mac_address_set(uint16_t bonding_port_id,
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -950,6 +965,9 @@ rte_eth_bond_mac_address_reset(uint16_t bonding_port_id)
struct rte_eth_dev *bonding_eth_dev;
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -991,6 +1009,9 @@ rte_eth_bond_xmit_policy_set(uint16_t bonding_port_id, uint8_t policy)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
@@ -1036,6 +1057,9 @@ rte_eth_bond_link_monitoring_set(uint16_t bonding_port_id, uint32_t internal_ms)
{
struct bond_dev_private *internals;
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return -ENOTSUP;
+
if (valid_bonding_port_id(bonding_port_id) != 0)
return -1;
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 6f3c13d6fb..0e18ded4a5 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -3695,12 +3695,26 @@ bond_ethdev_priv_dump(struct rte_eth_dev *dev, FILE *f)
const struct bond_dev_private *internals = dev->data->dev_private;
dump_basic(dev, f);
- if (internals->mode == BONDING_MODE_8023AD)
+
+ /* LACP state machine data is private to the primary process. */
+ if (internals->mode == BONDING_MODE_8023AD &&
+ rte_eal_process_type() == RTE_PROC_PRIMARY)
dump_lacp(dev->data->port_id, f);
return 0;
}
+/* Restricted set of ops allowed in secondary process. */
+static const struct eth_dev_ops secondary_dev_ops = {
+ .dev_close = bond_ethdev_close,
+ .dev_infos_get = bond_ethdev_info,
+ .link_update = bond_ethdev_link_update,
+ .stats_get = bond_ethdev_stats_get,
+ .reta_query = bond_ethdev_rss_reta_query,
+ .rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
+ .eth_dev_priv_dump = bond_ethdev_priv_dump,
+};
+
const struct eth_dev_ops default_dev_ops = {
.dev_start = bond_ethdev_start,
.dev_stop = bond_ethdev_stop,
@@ -3885,7 +3899,7 @@ bond_probe(struct rte_vdev_device *dev)
return -1;
}
- eth_dev->dev_ops = &default_dev_ops;
+ eth_dev->dev_ops = &secondary_dev_ops;
eth_dev->device = &dev->device;
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 6/8] net/bonding: add extended statistics
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
` (4 preceding siblings ...)
2026-08-31 16:06 ` [PATCH v2 5/8] net/bonding: restrict control ops in secondary process Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 7/8] test/bonding: add extended statistics test Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 8/8] doc: add bonding features matrix Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Chas Williams, Min Hu (Connor)
The bonding device reported only the sum of its members, so there was
no way to see how traffic was distributed across them.
This patch adds xstats which reports per-member packets/bytes/errors.
Also drop RTE_ETH_DEV_AUTOFILL_QUEUE_XSTATS since driver never
implemented per-queue stats.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/rel_notes/release_26_11.rst | 6 ++
drivers/net/bonding/rte_eth_bond_pmd.c | 113 ++++++++++++++++++++++++-
2 files changed, 117 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 87c7e81bde..83d70872af 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added extended statistics to bonding PMD.**
+
+ Extended statistics now report the packets, bytes and errors
+ of each member as ``rx_memberN_*`` and ``tx_memberN_*``.
+ The per-queue entries, which were always zero, are no longer reported.
+
Removed Items
-------------
diff --git a/drivers/net/bonding/rte_eth_bond_pmd.c b/drivers/net/bonding/rte_eth_bond_pmd.c
index 0e18ded4a5..46bc1db120 100644
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -2764,6 +2764,111 @@ bond_ethdev_stats_reset(struct rte_eth_dev *dev)
return err;
}
+#define BOND_MEMBER_STAT_PREFIX_LEN (sizeof("rx_member") - 1 + 5 + 1)
+
+struct bond_member_stats_name_off {
+ char name[RTE_ETH_XSTATS_NAME_SIZE - BOND_MEMBER_STAT_PREFIX_LEN];
+ size_t offset;
+};
+
+static const struct bond_member_stats_name_off bond_member_rxq_stats_strings[] = {
+ { "packets", offsetof(struct rte_eth_stats, ipackets) },
+ { "bytes", offsetof(struct rte_eth_stats, ibytes) },
+ { "errors", offsetof(struct rte_eth_stats, ierrors) },
+};
+
+#define BOND_NB_MEMBER_RX_STATS RTE_DIM(bond_member_rxq_stats_strings)
+
+static const struct bond_member_stats_name_off bond_member_txq_stats_strings[] = {
+ { "packets", offsetof(struct rte_eth_stats, opackets) },
+ { "bytes", offsetof(struct rte_eth_stats, obytes) },
+ { "errors", offsetof(struct rte_eth_stats, oerrors) },
+};
+
+#define BOND_NB_MEMBER_TX_STATS RTE_DIM(bond_member_txq_stats_strings)
+
+#define BOND_NB_MEMBER_STATS (BOND_NB_MEMBER_RX_STATS + BOND_NB_MEMBER_TX_STATS)
+
+static int
+bond_ethdev_xstats_get_names(struct rte_eth_dev *dev,
+ struct rte_eth_xstat_name *xstats_names,
+ unsigned int limit)
+{
+ struct bond_dev_private *internals = dev->data->dev_private;
+ unsigned int count = internals->member_count * BOND_NB_MEMBER_STATS;
+ unsigned int i, j;
+
+ if (xstats_names == NULL || limit < count)
+ return count;
+
+ count = 0;
+ for (i = 0; i < internals->member_count; i++) {
+ uint16_t member_id = internals->members[i].port_id;
+
+ for (j = 0; j < BOND_NB_MEMBER_RX_STATS; j++)
+ snprintf(xstats_names[count++].name,
+ RTE_ETH_XSTATS_NAME_SIZE, "rx_member%u_%s",
+ member_id, bond_member_rxq_stats_strings[j].name);
+
+ for (j = 0; j < BOND_NB_MEMBER_TX_STATS; j++)
+ snprintf(xstats_names[count++].name,
+ RTE_ETH_XSTATS_NAME_SIZE, "tx_member%u_%s",
+ member_id, bond_member_txq_stats_strings[j].name);
+ }
+
+ return count;
+}
+
+static int
+bond_ethdev_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
+ unsigned int n)
+{
+ const struct bond_dev_private *internals = dev->data->dev_private;
+ unsigned int count = internals->member_count * BOND_NB_MEMBER_STATS;
+ unsigned int i, j;
+
+ if (xstats == NULL || n < count)
+ return count;
+
+ count = 0;
+ for (i = 0; i < internals->member_count; i++) {
+ struct rte_eth_stats member_stats;
+ uint16_t member_id = internals->members[i].port_id;
+
+ /* If member query fails just report zero. */
+ if (rte_eth_stats_get(member_id, &member_stats) < 0)
+ memset(&member_stats, 0, sizeof(member_stats));
+
+ for (j = 0; j < BOND_NB_MEMBER_RX_STATS; j++) {
+ xstats[count].id = count;
+ xstats[count].value = *(const uint64_t *)((const char *)&member_stats +
+ bond_member_rxq_stats_strings[j].offset);
+ count++;
+ }
+
+ for (j = 0; j < BOND_NB_MEMBER_TX_STATS; j++) {
+ xstats[count].id = count;
+ xstats[count].value = *(const uint64_t *)((const char *)&member_stats +
+ bond_member_txq_stats_strings[j].offset);
+ count++;
+ }
+ }
+
+ return count;
+}
+
+static int
+bond_ethdev_xstats_reset(struct rte_eth_dev *dev)
+{
+ const struct bond_dev_private *internals = dev->data->dev_private;
+ uint16_t i;
+
+ for (i = 0; i < internals->member_count; i++)
+ rte_eth_stats_reset(internals->members[i].port_id);
+
+ return 0;
+}
+
static int
bond_ethdev_promiscuous_enable(struct rte_eth_dev *eth_dev)
{
@@ -3710,6 +3815,8 @@ static const struct eth_dev_ops secondary_dev_ops = {
.dev_infos_get = bond_ethdev_info,
.link_update = bond_ethdev_link_update,
.stats_get = bond_ethdev_stats_get,
+ .xstats_get = bond_ethdev_xstats_get,
+ .xstats_get_names = bond_ethdev_xstats_get_names,
.reta_query = bond_ethdev_rss_reta_query,
.rss_hash_conf_get = bond_ethdev_rss_hash_conf_get,
.eth_dev_priv_dump = bond_ethdev_priv_dump,
@@ -3729,6 +3836,9 @@ const struct eth_dev_ops default_dev_ops = {
.link_update = bond_ethdev_link_update,
.stats_get = bond_ethdev_stats_get,
.stats_reset = bond_ethdev_stats_reset,
+ .xstats_get = bond_ethdev_xstats_get,
+ .xstats_get_names = bond_ethdev_xstats_get_names,
+ .xstats_reset = bond_ethdev_xstats_reset,
.promiscuous_enable = bond_ethdev_promiscuous_enable,
.promiscuous_disable = bond_ethdev_promiscuous_disable,
.allmulticast_enable = bond_ethdev_allmulticast_enable,
@@ -3780,8 +3890,7 @@ bond_alloc(struct rte_vdev_device *dev, uint8_t mode)
}
eth_dev->dev_ops = &default_dev_ops;
- eth_dev->data->dev_flags = RTE_ETH_DEV_INTR_LSC |
- RTE_ETH_DEV_AUTOFILL_QUEUE_XSTATS;
+ eth_dev->data->dev_flags = RTE_ETH_DEV_INTR_LSC;
rte_spinlock_init(&internals->lock);
rte_spinlock_init(&internals->lsc_lock);
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 7/8] test/bonding: add extended statistics test
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
` (5 preceding siblings ...)
2026-08-31 16:06 ` [PATCH v2 6/8] net/bonding: add extended statistics Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 8/8] doc: add bonding features matrix Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Chas Williams, Min Hu (Connor)
The bonding extended statistics had no coverage. Add a test that
exercises the contract of the three new dev_ops.
The test checks that the name and value queries agree on the count,
that a NULL or undersized table returns the required size rather than
filling it, that the per-member names are generated from the member
port ids in member order, and that a burst received on one member is
attributed to that member alone and to no other. It then resets and
confirms the member counters are cleared.
ethdev prepends its own basic statistics to the driver's, so the
offset of the first member entry is discovered at runtime rather than
assumed to be zero.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/test_link_bonding.c | 148 +++++++++++++++++++++++++++++++++++
1 file changed, 148 insertions(+)
diff --git a/app/test/test_link_bonding.c b/app/test/test_link_bonding.c
index 19b064771a..33652948be 100644
--- a/app/test/test_link_bonding.c
+++ b/app/test/test_link_bonding.c
@@ -1663,6 +1663,153 @@ test_roundrobin_rx_burst_on_single_member(void)
return remove_members_and_stop_bonding_device();
}
+/* The bonding PMD reports rx and tx packets, bytes and errors per member.
+ * ethdev prepends its own basic statistics to the driver's, so the member
+ * entries start at an offset that has to be discovered at runtime.
+ */
+#define TEST_XSTATS_PER_MEMBER 6
+
+#define TEST_XSTATS_MEMBER_COUNT 4
+
+#define TEST_XSTATS_DRIVER_COUNT \
+ (TEST_XSTATS_MEMBER_COUNT * TEST_XSTATS_PER_MEMBER)
+
+/* Enough room for the driver entries plus any basic statistics. */
+#define TEST_XSTATS_MAX 64
+
+static int
+test_xstats(void)
+{
+ struct rte_mbuf *gen_pkt_burst[MAX_PKT_BURST];
+ struct rte_mbuf *rx_pkt_burst[MAX_PKT_BURST] = { NULL };
+ struct rte_eth_xstat_name names[TEST_XSTATS_MAX];
+ struct rte_eth_xstat xstats[TEST_XSTATS_MAX];
+ uint16_t bonding_port_id = test_params->bonding_port_id;
+ unsigned int count;
+ int burst_size = 17;
+ int basic, total;
+ int i, j;
+ char name[RTE_ETH_XSTATS_NAME_SIZE];
+
+ TEST_ASSERT_SUCCESS(initialize_bonding_device_with_members(
+ BONDING_MODE_ROUND_ROBIN, 0,
+ TEST_XSTATS_MEMBER_COUNT, 1),
+ "Failed to initialize bonding device with members");
+
+ /* A NULL table is a query for the number of xstats. The bonding
+ * driver contributes six per member on top of the basic statistics.
+ */
+ total = rte_eth_xstats_get_names(bonding_port_id, NULL, 0);
+ TEST_ASSERT(total > TEST_XSTATS_DRIVER_COUNT,
+ "Expected more than %d xstats names, got %d",
+ TEST_XSTATS_DRIVER_COUNT, total);
+ TEST_ASSERT(total <= TEST_XSTATS_MAX,
+ "xstats count %d exceeds test table size %d",
+ total, TEST_XSTATS_MAX);
+
+ basic = total - TEST_XSTATS_DRIVER_COUNT;
+
+ count = rte_eth_xstats_get(bonding_port_id, NULL, 0);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "xstats count %u differs from names count %d",
+ count, total);
+
+ /* A table smaller than the xstats count must be rejected, and the
+ * required size returned instead.
+ */
+ count = rte_eth_xstats_get_names(bonding_port_id, names, total - 1);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Undersized names query returned %u, expected %d",
+ count, total);
+
+ count = rte_eth_xstats_get_names(bonding_port_id, names, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats names, got %u", total, count);
+
+ /* Driver names follow the basic ones, per member port id in member
+ * order, rx before tx.
+ */
+ for (i = 0; i < TEST_XSTATS_MEMBER_COUNT; i++) {
+ uint16_t member_id = test_params->member_port_ids[i];
+ static const char * const dir[] = { "rx", "tx" };
+ static const char * const stat[] = {
+ "packets", "bytes", "errors"
+ };
+ unsigned int base = basic + i * TEST_XSTATS_PER_MEMBER;
+ unsigned int d, s, idx = 0;
+
+ for (d = 0; d < RTE_DIM(dir); d++) {
+ for (s = 0; s < RTE_DIM(stat); s++) {
+ snprintf(name, sizeof(name), "%s_member%u_%s",
+ dir[d], member_id, stat[s]);
+ TEST_ASSERT_SUCCESS(strcmp(
+ names[base + idx].name, name),
+ "xstats name %u is \"%s\", expected \"%s\"",
+ base + idx,
+ names[base + idx].name, name);
+ idx++;
+ }
+ }
+ }
+
+ /* Receive a burst on a single member, so that the per-member
+ * counters can be told apart.
+ */
+ TEST_ASSERT_EQUAL(generate_test_burst(gen_pkt_burst, burst_size,
+ 0, 1, 0, 0, 0), burst_size, "burst generation failed");
+
+ virtual_ethdev_add_mbufs_to_rx_queue(test_params->member_port_ids[0],
+ gen_pkt_burst, burst_size);
+
+ TEST_ASSERT_EQUAL(rte_eth_rx_burst(bonding_port_id, 0,
+ rx_pkt_burst, MAX_PKT_BURST), burst_size,
+ "rx burst failed");
+
+ count = rte_eth_xstats_get(bonding_port_id, xstats, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats, got %u", total, count);
+
+ /* Only the member that received the burst has a non-zero rx packet
+ * count. Ids are the index into the table.
+ */
+ for (i = 0; i < TEST_XSTATS_MEMBER_COUNT; i++) {
+ unsigned int base = basic + i * TEST_XSTATS_PER_MEMBER;
+ uint64_t rx_packets = xstats[base].value;
+
+ TEST_ASSERT_EQUAL(xstats[base].id, (uint64_t)base,
+ "xstat %u has id %"PRIu64, base, xstats[base].id);
+
+ if (i == 0)
+ TEST_ASSERT_EQUAL(rx_packets, (uint64_t)burst_size,
+ "Member %u rx packets is %"PRIu64", expected %d",
+ test_params->member_port_ids[i],
+ rx_packets, burst_size);
+ else
+ TEST_ASSERT_EQUAL(rx_packets, 0,
+ "Member %u rx packets is %"PRIu64", expected 0",
+ test_params->member_port_ids[i], rx_packets);
+ }
+
+ /* Reset clears the underlying member statistics. */
+ TEST_ASSERT_SUCCESS(rte_eth_xstats_reset(bonding_port_id),
+ "Failed to reset xstats");
+
+ count = rte_eth_xstats_get(bonding_port_id, xstats, total);
+ TEST_ASSERT_EQUAL(count, (unsigned int)total,
+ "Failed to get %d xstats after reset, got %u",
+ total, count);
+
+ for (i = basic; i < total; i++)
+ TEST_ASSERT_EQUAL(xstats[i].value, 0,
+ "xstat \"%s\" is %"PRIu64" after reset, expected 0",
+ names[i].name, xstats[i].value);
+
+ for (j = 0; j < burst_size; j++)
+ rte_pktmbuf_free(rx_pkt_burst[j]);
+
+ return remove_members_and_stop_bonding_device();
+}
+
#define TEST_ROUNDROBIN_TX_BURST_MEMBER_COUNT (3)
static int
@@ -5160,6 +5307,7 @@ static struct unit_test_suite link_bonding_test_suite = {
TEST_CASE(test_set_bonding_port_initialization_mac_assignment),
TEST_CASE(test_status_interrupt),
TEST_CASE(test_adding_member_after_bonding_device_started),
+ TEST_CASE(test_xstats),
TEST_CASE(test_roundrobin_tx_burst),
TEST_CASE(test_roundrobin_tx_burst_member_tx_fail),
TEST_CASE(test_roundrobin_rx_burst_on_single_member),
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 8/8] doc: add bonding features matrix
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
` (6 preceding siblings ...)
2026-08-31 16:06 ` [PATCH v2 7/8] test/bonding: add extended statistics test Stephen Hemminger
@ 2026-08-31 16:06 ` Stephen Hemminger
7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-08-31 16:06 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
Bonding was the only in-tree net PMD without an entry in
doc/guides/nics/features, so it did not appear in the driver
overview table.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/nics/features/bonding.ini | 40 ++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 doc/guides/nics/features/bonding.ini
diff --git a/doc/guides/nics/features/bonding.ini b/doc/guides/nics/features/bonding.ini
new file mode 100644
index 0000000000..27560f63c7
--- /dev/null
+++ b/doc/guides/nics/features/bonding.ini
@@ -0,0 +1,40 @@
+;
+; Supported features of the 'bonding' network poll mode driver.
+;
+; Refer to default.ini for the full list of available PMD features.
+;
+[Features]
+Speed capabilities = Y
+Link speed configuration = Y
+Link status = Y
+Link status event = Y
+Fast mbuf free = Y
+MTU update = Y
+Promiscuous mode = Y
+Allmulticast mode = Y
+Unicast MAC filter = Y
+RSS hash = Y
+RSS key update = Y
+RSS reta update = Y
+VLAN filter = Y
+Basic stats = Y
+Extended stats = Y
+FreeBSD = Y
+Linux = Y
+ARMv8 = Y
+Power8 = Y
+x86-32 = Y
+x86-64 = Y
+Usage doc = Y
+
+; Flow rules are passed through to the members, so the usable items and
+; actions are those common to all of them. Only the types named by the
+; bonding driver itself are listed here: eth and queue are used for the
+; mode 4 dedicated LACP queue, and count is the one action that
+; bonding aggregates across members on query.
+[rte_flow items]
+eth = P
+
+[rte_flow actions]
+count = P
+queue = P
--
2.53.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
end of thread, other threads:[~2026-08-31 16:12 UTC | newest]
Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-08 17:42 [RFC PATCH] net/bonding: reject control operations in secondary Weijun Pan
2026-07-22 23:10 ` Stephen Hemminger
2026-07-26 17:32 ` Stephen Hemminger
2026-08-23 15:16 ` [RFC PATCH v2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-23 15:43 ` Stephen Hemminger
2026-08-24 2:39 ` Weijun Pan
2026-08-24 16:15 ` Stephen Hemminger
2026-08-26 16:10 ` [RFC PATCH v3] " Weijun Pan
2026-08-26 17:53 ` Stephen Hemminger
2026-08-30 1:14 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Weijun Pan
2026-08-30 1:14 ` [RFC PATCH v4 2/2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-30 4:29 ` Stephen Hemminger
2026-08-30 4:22 ` [RFC PATCH v4 1/2] net/bonding: skip unavailable member stats Stephen Hemminger
2026-08-30 16:35 ` [RFC PATCH v5 " Weijun Pan
2026-08-30 16:35 ` [RFC PATCH v5 2/2] net/bonding: restrict secondary control operations Weijun Pan
2026-08-30 20:23 ` [PATCH 0/8] net/bonding: fixes and per-member statistics Stephen Hemminger
2026-08-30 20:23 ` [PATCH 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
2026-08-30 20:23 ` [PATCH 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
2026-08-30 20:23 ` [PATCH 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
2026-08-30 20:23 ` [PATCH 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
2026-08-30 20:23 ` [PATCH 5/8] net/bonding: restrict control operations in secondary process Stephen Hemminger
2026-08-30 20:23 ` [PATCH 6/8] net/bonding: add extended statistics Stephen Hemminger
2026-08-30 20:23 ` [PATCH 7/8] test/bonding: add extended statistics test Stephen Hemminger
2026-08-30 20:23 ` [PATCH 8/8] doc: add bonding features matrix Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 0/8] net/bonding: fixes and per-member stats Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 1/8] net/bonding: fix TLB member ordering with unusable member Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 2/8] net/bonding: skip unavailable member stats Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 3/8] net/bonding: skip unavailable members in device info Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 4/8] net/bonding: use atomic link status accessors Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 5/8] net/bonding: restrict control ops in secondary process Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 6/8] net/bonding: add extended statistics Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 7/8] test/bonding: add extended statistics test Stephen Hemminger
2026-08-31 16:06 ` [PATCH v2 8/8] doc: add bonding features matrix Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox