Netdev List
 help / color / mirror / Atom feed
* [PATCH net] bonding: 3ad: select a port when the TX array is empty
@ 2026-09-15  9:02 Yun Lu
  2026-09-17 21:02 ` netdev-bot+sashiko
  0 siblings, 1 reply; 3+ messages in thread
From: Yun Lu @ 2026-09-15  9:02 UTC (permalink / raw)
  To: jv, andrew+netdev, davem, edumazet, kuba, pabeni; +Cc: razor, maheshb, netdev

From: Yun Lu <luyun@kylinos.cn>

During 802.3ad bond bring-up, userspace can observe carrier up while
ARP requests still fail to transmit. This can happen even though the
active aggregator already has ports eligible for transmission.

Commit 23de0d7b6f0e ("bonding: pair enable_port with
slave_arr_updates") made port enablement request an array rebuild, but
the rebuild remains asynchronous. The 802.3ad state machine enables a
port before queueing slave_arr_work. Until the worker publishes the new
usable_slaves array, the transmit path drops packets. RTNL contention
can prolong this window because the worker retries later.

When ordinary 802.3ad skb transmission finds an empty usable array,
select a port directly from the active aggregator. Hold mode_lock across
the counting and selection passes so aggregator reselection cannot expose
the intermediate state where all is_active flags are clear. The caller's
RCU read lock protects the slave lifetime.

Use the same bond_slave_can_tx() eligibility and list order as the array
path, so hash selection remains equivalent when the eligible set is
stable. Skip the fallback in netpoll context because TX may be re-entered
while mode_lock is already held.

Fixes: ee6377147409 ("bonding: Simplify the xmit function for modes that use xmit_hash")
Signed-off-by: Yun Lu <luyun@kylinos.cn>
---
 drivers/net/bonding/bond_main.c | 66 +++++++++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 4 deletions(-)

diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
index a9bff7663eec..2cf280bf134f 100644
--- a/drivers/net/bonding/bond_main.c
+++ b/drivers/net/bonding/bond_main.c
@@ -5233,11 +5233,11 @@ static struct slave *bond_xmit_3ad_xor_slave_get(struct bonding *bond,
 	unsigned int count;
 	u32 hash;
 
-	hash = bond_xmit_hash(bond, skb);
 	count = slaves ? READ_ONCE(slaves->count) : 0;
 	if (unlikely(!count))
 		return NULL;
 
+	hash = bond_xmit_hash(bond, skb);
 	slave = slaves->arr[hash % count];
 	return slave;
 }
@@ -5289,9 +5289,65 @@ static bool bond_should_broadcast_neighbor(struct sk_buff *skb,
 	return false;
 }
 
-/* Use this Xmit function for 3AD as well as XOR modes. The current
- * usable slave array is formed in the control path. The xmit function
- * just calculates hash and sends the packet out.
+/* Called with RCU and bond->mode_lock held. */
+static bool bond_3ad_slave_is_eligible(struct slave *slave)
+{
+	const struct aggregator *agg;
+
+	agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator);
+	return agg && agg->is_active && bond_slave_can_tx(slave);
+}
+
+/* Called with RCU held when the transmit array has not caught up with
+ * the 802.3ad state machine. Do not enable ports here: use the same
+ * eligibility checks as bond_update_slave_arr().
+ */
+static struct slave *bond_3ad_xmit_fallback(struct bonding *bond, u32 hash)
+{
+	struct slave *selected = NULL;
+	unsigned int eligible = 0;
+	unsigned int target;
+	struct list_head *iter;
+	struct slave *slave;
+
+	/* Netpoll can re-enter TX while the state machine holds mode_lock.
+	 * Keep the existing empty-array drop behavior in that context.
+	 */
+	if (unlikely(netpoll_tx_running(bond->dev)))
+		return NULL;
+
+	/* Aggregator selection temporarily clears all is_active flags. Keep
+	 * both loops under mode_lock to avoid observing that intermediate
+	 * state. The caller's RCU read lock protects the selected slave.
+	 */
+	spin_lock_bh(&bond->mode_lock);
+	bond_for_each_slave_rcu(bond, slave, iter)
+		if (bond_3ad_slave_is_eligible(slave))
+			eligible++;
+
+	if (!eligible)
+		goto out;
+
+	target = hash % eligible;
+	bond_for_each_slave_rcu(bond, slave, iter) {
+		if (!bond_3ad_slave_is_eligible(slave))
+			continue;
+
+		if (!target) {
+			selected = slave;
+			break;
+		}
+		target--;
+	}
+
+out:
+	spin_unlock_bh(&bond->mode_lock);
+	return selected;
+}
+
+/* Use this Xmit function for 3AD as well as XOR modes. The usable slave
+ * array is formed in the control path.  In 3AD mode, fall back to the
+ * current port state while an empty array awaits an update.
  */
 static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
 				     struct net_device *dev)
@@ -5302,6 +5358,8 @@ static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
 
 	slaves = rcu_dereference(bond->usable_slaves);
 	slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
+	if (unlikely(!slave) && BOND_MODE(bond) == BOND_MODE_8023AD)
+		slave = bond_3ad_xmit_fallback(bond, bond_xmit_hash(bond, skb));
 	if (likely(slave))
 		return bond_dev_queue_xmit(bond, skb, slave->dev);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH net] bonding: 3ad: select a port when the TX array is empty
  2026-09-15  9:02 [PATCH net] bonding: 3ad: select a port when the TX array is empty Yun Lu
@ 2026-09-17 21:02 ` netdev-bot+sashiko
  2026-09-18  9:37   ` luyun
  0 siblings, 1 reply; 3+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:02 UTC (permalink / raw)
  To: luyun_611
  Cc: jv, andrew+netdev, davem, edumazet, kuba, pabeni, razor, maheshb,
	netdev

Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 3 · Low: 2

- [High] Lock re-entrancy: bond_3ad_xmit_fallback() adds
  spin_lock_bh(&bond->mode_lock) to the ndo_start_xmit path…
- [Medium] New per-packet mode_lock acquisition plus O(nr_slaves) list
  walks on the 802.3ad transmit path whenever usable_slaves is empty.
- [Medium] Divergent semantics for 'usable_slaves is empty in 802.3ad
  mode' across peer call sites.
- [Medium] The patch's stated motivation is that 'ARP requests still fail
  to transmit' during 802.3ad bring-up, but with the broadcast_neighbor…
- [Low] bond_3ad_xmit_fallback() counts eligible ports in one pass and
  re-walks the list in a second pass to pick the target-th eligible…
- [Low] Undescribed hunk: the patch also reorders bond_xmit_hash() to
  after the count check inside bond_xmit_3ad_xor_slave_get()…

--

bonding: 3ad: select a port when the TX array is empty

This adds a fallback in bond_3ad_xor_xmit() so that, in 802.3ad mode, an
empty usable_slaves array no longer means an automatic drop: a port is
picked directly from the active aggregator under bond->mode_lock.  A few
questions below about the new locking on the transmit path and about the
peer call sites that were left with the old semantics.

No Link: tags in the commit header.

> diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
> index a9bff7663eeca..2cf280bf134f2 100644
> --- a/drivers/net/bonding/bond_main.c
> +++ b/drivers/net/bonding/bond_main.c
> @@ -5233,11 +5233,11 @@ static struct slave *bond_xmit_3ad_xor_slave_get(struct bonding *bond,
>  	unsigned int count;
>  	u32 hash;
>  
> -	hash = bond_xmit_hash(bond, skb);
>  	count = slaves ? READ_ONCE(slaves->count) : 0;
>  	if (unlikely(!count))
>  		return NULL;
>  
> +	hash = bond_xmit_hash(bond, skb);
>  	slave = slaves->arr[hash % count];
>  	return slave;
>  }

[Severity: Low]
This isn't a bug, but could the changelog mention this hunk?  It moves
bond_xmit_hash() below the count check in bond_xmit_3ad_xor_slave_get(),
which is also called from bond_xmit_get_slave().

The reorder looks like a prerequisite of the new design, so the hash is
computed once (in the helper for the array path, in bond_3ad_xor_xmit()
for the fallback path), and it appears functionally neutral because
bond_xmit_hash()/__bond_xmit_hash() only read the skb.  Right now a
reader has to work that out on their own.

> @@ -5289,9 +5289,65 @@ static bool bond_should_broadcast_neighbor(struct sk_buff *skb,
>  	return false;
>  }
>  
> -/* Use this Xmit function for 3AD as well as XOR modes. The current
> - * usable slave array is formed in the control path. The xmit function
> - * just calculates hash and sends the packet out.
> +/* Called with RCU and bond->mode_lock held. */
> +static bool bond_3ad_slave_is_eligible(struct slave *slave)
> +{
> +	const struct aggregator *agg;
> +
> +	agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator);
> +	return agg && agg->is_active && bond_slave_can_tx(slave);
> +}
> +
> +/* Called with RCU held when the transmit array has not caught up with
> + * the 802.3ad state machine. Do not enable ports here: use the same
> + * eligibility checks as bond_update_slave_arr().
> + */
> +static struct slave *bond_3ad_xmit_fallback(struct bonding *bond, u32 hash)
> +{
> +	struct slave *selected = NULL;
> +	unsigned int eligible = 0;
> +	unsigned int target;
> +	struct list_head *iter;
> +	struct slave *slave;
> +
> +	/* Netpoll can re-enter TX while the state machine holds mode_lock.
> +	 * Keep the existing empty-array drop behavior in that context.
> +	 */
> +	if (unlikely(netpoll_tx_running(bond->dev)))
> +		return NULL;
> +
> +	/* Aggregator selection temporarily clears all is_active flags. Keep
> +	 * both loops under mode_lock to avoid observing that intermediate
> +	 * state. The caller's RCU read lock protects the selected slave.
> +	 */
> +	spin_lock_bh(&bond->mode_lock);

[Severity: High]
Is the netpoll check enough to cover all the ways TX can be entered with
a mode_lock already held?  The 802.3ad state machine holds mode_lock
across its whole sweep, and LACPDU transmission happens inside that
section:

bond_3ad_state_machine_handler()
    spin_lock_bh(&bond->mode_lock);
    ...
    ad_tx_machine(port);
        ad_lacpdu_send(port);
            skb->dev = slave->dev;
            dev_queue_xmit(skb);

bond_3ad_unbind_slave() does the same thing, calling ad_lacpdu_send()
with mode_lock held.

Bond devices set IFF_NO_QUEUE, so dev_queue_xmit() calls the lower
device's ndo_start_xmit synchronously in the same context.  If the lower
device is itself an 802.3ad bond (bond_enslave() only rejects non-bond
masters, so bond-over-bond is allowed) whose usable array is still empty
- the very bring-up window this patch targets - then:

bond_3ad_state_machine_handler()      /* outer bond mode_lock held */
  -> ad_lacpdu_send() -> dev_queue_xmit()
    -> inner bond bond_start_xmit() -> __bond_start_xmit()
      -> bond_3ad_xor_xmit()         /* empty usable_slaves */
        -> bond_3ad_xmit_fallback()
          -> spin_lock_bh(&inner->mode_lock);

Since mode_lock has a single lockdep class (one spin_lock_init() in
bond_setup(), no subclass or nest_lock annotation), wouldn't lockdep
report possible recursive locking here and then disable itself?

There is a worse variant: if egress processing on the lower device
redirects the frame back into the same bond (tc mirred egress redirect or
a bpf redirect run from sch_handle_egress in the same call), doesn't the
same CPU re-acquire the same non-recursive mode_lock with softirqs
disabled and spin forever?  netpoll_tx_running() does not cover that
case, since these paths run with interrupts enabled.

Would it be possible to avoid taking mode_lock on the transmit path
altogether, or to move LACPDU transmission out of the mode_lock section?

> +	bond_for_each_slave_rcu(bond, slave, iter)
> +		if (bond_3ad_slave_is_eligible(slave))
> +			eligible++;
> +
> +	if (!eligible)
> +		goto out;

[Severity: Medium]
How expensive does this become when the array is empty for a long time?
Every skb that finds an empty array now takes a bond-wide spinlock and
walks the full lower-device list (once when nothing is eligible, twice
otherwise), and is then dropped anyway.

The empty-array state is not only the transient rebuild window described
in the changelog.  bond_update_slave_arr() publishes an empty array
whenever bond_3ad_get_active_agg_info() fails:

	/* No active aggragator means it's not safe to use
	 * the previous array.
	 */
	bond_reset_slave_arr(bond);

That covers the whole duration of LACP non-convergence (peer switch not
running LACP, all members down), and bond devices are IFF_NO_QUEUE so
skbs still reach ndo_start_xmit with carrier down.

The same mode_lock is held by bond_3ad_state_machine_handler() across its
entire sweep and by bond_3ad_rx_indication() on LACPDU receive, so
sustained TX bounces the lock against the very state machine that would
end the fallback state.  Would an "update pending" latch, or making the
fallback one-shot, bound this?

> +
> +	target = hash % eligible;
> +	bond_for_each_slave_rcu(bond, slave, iter) {
> +		if (!bond_3ad_slave_is_eligible(slave))
> +			continue;
> +
> +		if (!target) {
> +			selected = slave;
> +			break;
> +		}
> +		target--;
> +	}

[Severity: Low]
Does mode_lock really keep the two passes consistent?  It serialises the
802.3ad state (agg->is_active), but the other inputs of
bond_3ad_slave_is_eligible() are not written under it:

include/net/bonding.h:
static inline bool bond_slave_can_tx(struct slave *slave)
{
	return bond_slave_is_up(slave) && slave->link == BOND_LINK_UP &&
	       bond_is_active_slave(slave);
}

bond_slave_is_up() reads netif_running()/netif_carrier_ok(), which the
lower driver can flip via netif_carrier_off() with no bond lock at all,
and slave->link / slave->backup are written by the monitors
(bond_miimon_commit()) under RTNL only.

So if the hash picked the last eligible index (target == eligible - 1)
and one slave becomes ineligible between the two passes, doesn't the
second walk decrement target for each remaining eligible slave, never
reach target == 0, and return NULL - dropping the packet the fallback was
supposed to send?

In the opposite direction, a slave becoming eligible between the passes
skews the hash distribution relative to the array path, which does its
single walk under RTNL.  Could the changelog's claim that "hash selection
remains equivalent" be narrowed to agg->is_active?

> +
> +out:
> +	spin_unlock_bh(&bond->mode_lock);
> +	return selected;
> +}
> +
> +/* Use this Xmit function for 3AD as well as XOR modes. The usable slave
> + * array is formed in the control path.  In 3AD mode, fall back to the
> + * current port state while an empty array awaits an update.
>   */
>  static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>  				     struct net_device *dev)
> @@ -5302,6 +5358,8 @@ static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>  
>  	slaves = rcu_dereference(bond->usable_slaves);
>  	slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
> +	if (unlikely(!slave) && BOND_MODE(bond) == BOND_MODE_8023AD)
> +		slave = bond_3ad_xmit_fallback(bond, bond_xmit_hash(bond, skb));

[Severity: Medium]
Should the fallback live in the shared helper
bond_xmit_3ad_xor_slave_get() instead?  Several other readers of the same
"usable_slaves is empty in 802.3ad mode" condition were left with the old
meaning:

bond_xdp_xmit_3ad_xor_slave_get() open-codes the same lookup and still
returns NULL:

	count = slaves ? READ_ONCE(slaves->count) : 0;
	if (unlikely(!count))
		return NULL;

so XDP_TX/XDP_REDIRECT frames keep getting dropped in exactly the window
this patch closes for skbs.

bond_xmit_get_slave(), the ndo_get_xmit_slave callback, also still
returns NULL.  Its documented contract in include/linux/netdevice.h is to
return the slave the master will use for that skb, and
netdev_get_xmit_slave() is consumed by drivers/infiniband/core/lag.c.
Doesn't that now tell an offload consumer "no egress slave" for an skb
the bond does in fact transmit?

Two control-path readers still encode the old invariant as well:

bond_should_notify_peers():
	usable = rcu_dereference_rtnl(bond->usable_slaves);
	if (!usable || !READ_ONCE(usable->count))
		return false;

bond_miimon_inspect():
	if (usable_slaves && usable_slaves->count == 0)
		ignore_updelay = true;

Is peer notification meant to stay suppressed, and updelay ignored, for a
bond that can now transmit?

[Severity: Medium]
The changelog says "ARP requests still fail to transmit".  With the
broadcast_neighbor option enabled, do ARP and IPv6 neighbour-discovery
packets ever reach this fallback?

__bond_start_xmit() short-circuits before bond_3ad_xor_xmit():

	case BOND_MODE_8023AD:
		if (bond_should_broadcast_neighbor(skb, dev))
			return bond_xmit_broadcast(skb, dev, false);

and bond_xmit_broadcast() reads the same asynchronously updated array:

	slaves_count = slaves ? READ_ONCE(slaves->count) : 0;
	for (i = 0; i < slaves_count; i++) {

With an empty or NULL array the loop body never runs, the skb is freed
and NET_XMIT_DROP is returned, so the symptom described in the commit
message survives for bonds configured with broadcast_neighbor=1 even
though the active aggregator has eligible ports.  Should that path get
the same treatment, or is the residual gap intentional?

>  	if (likely(slave))
>  		return bond_dev_queue_xmit(bond, skb, slave->dev);
>

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915090207.110987-1-luyun_611%40163.com

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH net] bonding: 3ad: select a port when the TX array is empty
  2026-09-17 21:02 ` netdev-bot+sashiko
@ 2026-09-18  9:37   ` luyun
  0 siblings, 0 replies; 3+ messages in thread
From: luyun @ 2026-09-18  9:37 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: jv, andrew+netdev, davem, edumazet, kuba, pabeni, razor, maheshb,
	netdev


在 2026/9/18 05:02, netdev-bot+sashiko@kernel.org 写道:
> Thank you for your contribution! Sashiko AI review found 6 potential
> issue(s) to consider:
>
> Critical: 0 · High: 1 · Medium: 3 · Low: 2
>
> - [High] Lock re-entrancy: bond_3ad_xmit_fallback() adds
>    spin_lock_bh(&bond->mode_lock) to the ndo_start_xmit path…
> - [Medium] New per-packet mode_lock acquisition plus O(nr_slaves) list
>    walks on the 802.3ad transmit path whenever usable_slaves is empty.
> - [Medium] Divergent semantics for 'usable_slaves is empty in 802.3ad
>    mode' across peer call sites.
> - [Medium] The patch's stated motivation is that 'ARP requests still fail
>    to transmit' during 802.3ad bring-up, but with the broadcast_neighbor…
> - [Low] bond_3ad_xmit_fallback() counts eligible ports in one pass and
>    re-walks the list in a second pass to pick the target-th eligible…
> - [Low] Undescribed hunk: the patch also reorders bond_xmit_hash() to
>    after the count check inside bond_xmit_3ad_xor_slave_get()…


Thanks for the review.

I have addressed the comments in v2:

- use spin_trylock_bh() in the fallback path to avoid recursive
   mode_lock acquisition;
- limit the fallback to the interval in which an array update has
   not yet been published;
- retain the first eligible port if eligibility changes between the
   two passes;
- apply the fallback to ndo_get_xmit_slave and the
   broadcast_neighbor path;
- document why the hash calculation is deferred until the array count
   is known.

The XDP path is intentionally left unchanged and will be handled in a
separate patch later. The peer-notification and miimon readers continue
to use the published array because they are control-path consumers.

v2:
https://lore.kernel.org/all/20260918090137.66515-1-luyun_611@163.com/

---

Thanks,

Yun Lu

>
> --
>
> bonding: 3ad: select a port when the TX array is empty
>
> This adds a fallback in bond_3ad_xor_xmit() so that, in 802.3ad mode, an
> empty usable_slaves array no longer means an automatic drop: a port is
> picked directly from the active aggregator under bond->mode_lock.  A few
> questions below about the new locking on the transmit path and about the
> peer call sites that were left with the old semantics.
>
> No Link: tags in the commit header.
>
>> diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
>> index a9bff7663eeca..2cf280bf134f2 100644
>> --- a/drivers/net/bonding/bond_main.c
>> +++ b/drivers/net/bonding/bond_main.c
>> @@ -5233,11 +5233,11 @@ static struct slave *bond_xmit_3ad_xor_slave_get(struct bonding *bond,
>>   	unsigned int count;
>>   	u32 hash;
>>   
>> -	hash = bond_xmit_hash(bond, skb);
>>   	count = slaves ? READ_ONCE(slaves->count) : 0;
>>   	if (unlikely(!count))
>>   		return NULL;
>>   
>> +	hash = bond_xmit_hash(bond, skb);
>>   	slave = slaves->arr[hash % count];
>>   	return slave;
>>   }
> [Severity: Low]
> This isn't a bug, but could the changelog mention this hunk?  It moves
> bond_xmit_hash() below the count check in bond_xmit_3ad_xor_slave_get(),
> which is also called from bond_xmit_get_slave().
>
> The reorder looks like a prerequisite of the new design, so the hash is
> computed once (in the helper for the array path, in bond_3ad_xor_xmit()
> for the fallback path), and it appears functionally neutral because
> bond_xmit_hash()/__bond_xmit_hash() only read the skb.  Right now a
> reader has to work that out on their own.
>
>> @@ -5289,9 +5289,65 @@ static bool bond_should_broadcast_neighbor(struct sk_buff *skb,
>>   	return false;
>>   }
>>   
>> -/* Use this Xmit function for 3AD as well as XOR modes. The current
>> - * usable slave array is formed in the control path. The xmit function
>> - * just calculates hash and sends the packet out.
>> +/* Called with RCU and bond->mode_lock held. */
>> +static bool bond_3ad_slave_is_eligible(struct slave *slave)
>> +{
>> +	const struct aggregator *agg;
>> +
>> +	agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator);
>> +	return agg && agg->is_active && bond_slave_can_tx(slave);
>> +}
>> +
>> +/* Called with RCU held when the transmit array has not caught up with
>> + * the 802.3ad state machine. Do not enable ports here: use the same
>> + * eligibility checks as bond_update_slave_arr().
>> + */
>> +static struct slave *bond_3ad_xmit_fallback(struct bonding *bond, u32 hash)
>> +{
>> +	struct slave *selected = NULL;
>> +	unsigned int eligible = 0;
>> +	unsigned int target;
>> +	struct list_head *iter;
>> +	struct slave *slave;
>> +
>> +	/* Netpoll can re-enter TX while the state machine holds mode_lock.
>> +	 * Keep the existing empty-array drop behavior in that context.
>> +	 */
>> +	if (unlikely(netpoll_tx_running(bond->dev)))
>> +		return NULL;
>> +
>> +	/* Aggregator selection temporarily clears all is_active flags. Keep
>> +	 * both loops under mode_lock to avoid observing that intermediate
>> +	 * state. The caller's RCU read lock protects the selected slave.
>> +	 */
>> +	spin_lock_bh(&bond->mode_lock);
> [Severity: High]
> Is the netpoll check enough to cover all the ways TX can be entered with
> a mode_lock already held?  The 802.3ad state machine holds mode_lock
> across its whole sweep, and LACPDU transmission happens inside that
> section:
>
> bond_3ad_state_machine_handler()
>      spin_lock_bh(&bond->mode_lock);
>      ...
>      ad_tx_machine(port);
>          ad_lacpdu_send(port);
>              skb->dev = slave->dev;
>              dev_queue_xmit(skb);
>
> bond_3ad_unbind_slave() does the same thing, calling ad_lacpdu_send()
> with mode_lock held.
>
> Bond devices set IFF_NO_QUEUE, so dev_queue_xmit() calls the lower
> device's ndo_start_xmit synchronously in the same context.  If the lower
> device is itself an 802.3ad bond (bond_enslave() only rejects non-bond
> masters, so bond-over-bond is allowed) whose usable array is still empty
> - the very bring-up window this patch targets - then:
>
> bond_3ad_state_machine_handler()      /* outer bond mode_lock held */
>    -> ad_lacpdu_send() -> dev_queue_xmit()
>      -> inner bond bond_start_xmit() -> __bond_start_xmit()
>        -> bond_3ad_xor_xmit()         /* empty usable_slaves */
>          -> bond_3ad_xmit_fallback()
>            -> spin_lock_bh(&inner->mode_lock);
>
> Since mode_lock has a single lockdep class (one spin_lock_init() in
> bond_setup(), no subclass or nest_lock annotation), wouldn't lockdep
> report possible recursive locking here and then disable itself?
>
> There is a worse variant: if egress processing on the lower device
> redirects the frame back into the same bond (tc mirred egress redirect or
> a bpf redirect run from sch_handle_egress in the same call), doesn't the
> same CPU re-acquire the same non-recursive mode_lock with softirqs
> disabled and spin forever?  netpoll_tx_running() does not cover that
> case, since these paths run with interrupts enabled.
>
> Would it be possible to avoid taking mode_lock on the transmit path
> altogether, or to move LACPDU transmission out of the mode_lock section?
>
>> +	bond_for_each_slave_rcu(bond, slave, iter)
>> +		if (bond_3ad_slave_is_eligible(slave))
>> +			eligible++;
>> +
>> +	if (!eligible)
>> +		goto out;
> [Severity: Medium]
> How expensive does this become when the array is empty for a long time?
> Every skb that finds an empty array now takes a bond-wide spinlock and
> walks the full lower-device list (once when nothing is eligible, twice
> otherwise), and is then dropped anyway.
>
> The empty-array state is not only the transient rebuild window described
> in the changelog.  bond_update_slave_arr() publishes an empty array
> whenever bond_3ad_get_active_agg_info() fails:
>
> 	/* No active aggragator means it's not safe to use
> 	 * the previous array.
> 	 */
> 	bond_reset_slave_arr(bond);
>
> That covers the whole duration of LACP non-convergence (peer switch not
> running LACP, all members down), and bond devices are IFF_NO_QUEUE so
> skbs still reach ndo_start_xmit with carrier down.
>
> The same mode_lock is held by bond_3ad_state_machine_handler() across its
> entire sweep and by bond_3ad_rx_indication() on LACPDU receive, so
> sustained TX bounces the lock against the very state machine that would
> end the fallback state.  Would an "update pending" latch, or making the
> fallback one-shot, bound this?
>
>> +
>> +	target = hash % eligible;
>> +	bond_for_each_slave_rcu(bond, slave, iter) {
>> +		if (!bond_3ad_slave_is_eligible(slave))
>> +			continue;
>> +
>> +		if (!target) {
>> +			selected = slave;
>> +			break;
>> +		}
>> +		target--;
>> +	}
> [Severity: Low]
> Does mode_lock really keep the two passes consistent?  It serialises the
> 802.3ad state (agg->is_active), but the other inputs of
> bond_3ad_slave_is_eligible() are not written under it:
>
> include/net/bonding.h:
> static inline bool bond_slave_can_tx(struct slave *slave)
> {
> 	return bond_slave_is_up(slave) && slave->link == BOND_LINK_UP &&
> 	       bond_is_active_slave(slave);
> }
>
> bond_slave_is_up() reads netif_running()/netif_carrier_ok(), which the
> lower driver can flip via netif_carrier_off() with no bond lock at all,
> and slave->link / slave->backup are written by the monitors
> (bond_miimon_commit()) under RTNL only.
>
> So if the hash picked the last eligible index (target == eligible - 1)
> and one slave becomes ineligible between the two passes, doesn't the
> second walk decrement target for each remaining eligible slave, never
> reach target == 0, and return NULL - dropping the packet the fallback was
> supposed to send?
>
> In the opposite direction, a slave becoming eligible between the passes
> skews the hash distribution relative to the array path, which does its
> single walk under RTNL.  Could the changelog's claim that "hash selection
> remains equivalent" be narrowed to agg->is_active?
>
>> +
>> +out:
>> +	spin_unlock_bh(&bond->mode_lock);
>> +	return selected;
>> +}
>> +
>> +/* Use this Xmit function for 3AD as well as XOR modes. The usable slave
>> + * array is formed in the control path.  In 3AD mode, fall back to the
>> + * current port state while an empty array awaits an update.
>>    */
>>   static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>>   				     struct net_device *dev)
>> @@ -5302,6 +5358,8 @@ static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>>   
>>   	slaves = rcu_dereference(bond->usable_slaves);
>>   	slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
>> +	if (unlikely(!slave) && BOND_MODE(bond) == BOND_MODE_8023AD)
>> +		slave = bond_3ad_xmit_fallback(bond, bond_xmit_hash(bond, skb));
> [Severity: Medium]
> Should the fallback live in the shared helper
> bond_xmit_3ad_xor_slave_get() instead?  Several other readers of the same
> "usable_slaves is empty in 802.3ad mode" condition were left with the old
> meaning:
>
> bond_xdp_xmit_3ad_xor_slave_get() open-codes the same lookup and still
> returns NULL:
>
> 	count = slaves ? READ_ONCE(slaves->count) : 0;
> 	if (unlikely(!count))
> 		return NULL;
>
> so XDP_TX/XDP_REDIRECT frames keep getting dropped in exactly the window
> this patch closes for skbs.
>
> bond_xmit_get_slave(), the ndo_get_xmit_slave callback, also still
> returns NULL.  Its documented contract in include/linux/netdevice.h is to
> return the slave the master will use for that skb, and
> netdev_get_xmit_slave() is consumed by drivers/infiniband/core/lag.c.
> Doesn't that now tell an offload consumer "no egress slave" for an skb
> the bond does in fact transmit?
>
> Two control-path readers still encode the old invariant as well:
>
> bond_should_notify_peers():
> 	usable = rcu_dereference_rtnl(bond->usable_slaves);
> 	if (!usable || !READ_ONCE(usable->count))
> 		return false;
>
> bond_miimon_inspect():
> 	if (usable_slaves && usable_slaves->count == 0)
> 		ignore_updelay = true;
>
> Is peer notification meant to stay suppressed, and updelay ignored, for a
> bond that can now transmit?
>
> [Severity: Medium]
> The changelog says "ARP requests still fail to transmit".  With the
> broadcast_neighbor option enabled, do ARP and IPv6 neighbour-discovery
> packets ever reach this fallback?
>
> __bond_start_xmit() short-circuits before bond_3ad_xor_xmit():
>
> 	case BOND_MODE_8023AD:
> 		if (bond_should_broadcast_neighbor(skb, dev))
> 			return bond_xmit_broadcast(skb, dev, false);
>
> and bond_xmit_broadcast() reads the same asynchronously updated array:
>
> 	slaves_count = slaves ? READ_ONCE(slaves->count) : 0;
> 	for (i = 0; i < slaves_count; i++) {
>
> With an empty or NULL array the loop body never runs, the skb is freed
> and NET_XMIT_DROP is returned, so the symptom described in the commit
> message survives for bonds configured with broadcast_neighbor=1 even
> though the active aggregator has eligible ports.  Should that path get
> the same treatment, or is the residual gap intentional?
>
>>   	if (likely(slave))
>>   		return bond_dev_queue_xmit(bond, skb, slave->dev);
>>


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-18  9:38 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-15  9:02 [PATCH net] bonding: 3ad: select a port when the TX array is empty Yun Lu
2026-09-17 21:02 ` netdev-bot+sashiko
2026-09-18  9:37   ` luyun

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox