* [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 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
* 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
* [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