Re: [PATCH net] bonding: 3ad: select a port when the TX array is empty

luyun <[email protected]>
Newsgroups org.kernel.vger.netdev
Message-ID <[email protected]>
在 2026/9/18 05:02, [email protected] 写道:
> 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/[email protected]/

---

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);
>>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.