[RFC PATCH v3] net/bonding: restrict secondary control operations
Stephen Hemminger
stephen at networkplumber.org
Wed Aug 26 19:53:25 CEST 2026
On Wed, 26 Aug 2026 11:10:09 -0500
Weijun Pan <wpan3636 at 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 at 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.
More information about the dev
mailing list