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

Yun Lu <[email protected]>
Newsgroups org.kernel.vger.netdev
Message-ID <[email protected]>
From: Yun Lu <[email protected]>

During 802.3ad bond bring-up, the state machine can enable a port before
slave_arr_work publishes the updated usable_slaves array. Packets are
dropped in this window even though the active aggregator has ports that
are eligible for transmission. RTNL contention can extend the window.

Track the unpublished array update with a boolean latch. It is set before
the state machine releases mode_lock, left set while the worker retries,
and cleared after a successful update or work cancellation. The state
machine and array worker use the same ordered workqueue, so a later update
cannot race with the worker clearing the latch.

If an skb transmit or ndo_get_xmit_slave query finds an empty usable array
while the latch is set, select a port directly from the active aggregator.
Use spin_trylock_bh() so recursive TX retains the existing drop or NULL
behavior instead of deadlocking. The latch also avoids taking mode_lock and
walking the slave list during persistent empty-array states such as LACP
non-convergence.

Use the same eligibility and list order as the array path. If eligibility
changes between the counting and selection passes, use the first eligible
port found by the second pass. Cover broadcast-neighbor packets by sending
one through the fallback until normal array-based broadcasting resumes.

Defer hash calculation until after the array count check so the normal and
fallback paths each calculate it only once.

Fixes: ee6377147409 ("bonding: Simplify the xmit function for modes that use xmit_hash")
Signed-off-by: Yun Lu <[email protected]>
---
Changes in v2:
- Track unpublished array updates with a boolean latch, limiting fallback
  locking and list walks to the asynchronous update window.
- Use spin_trylock_bh() so recursive TX keeps the existing drop or NULL
  behavior instead of deadlocking on mode_lock.
- Use the first eligible port if eligibility changes between the two list
  walks.
- Apply the fallback to ndo_get_xmit_slave and broadcast-neighbor skb
  transmission. Keep control-path readers on the published array and leave
  XDP for a separate follow-up patch.
- Document why hash calculation is deferred until the array count is known.

v1: https://lore.kernel.org/netdev/[email protected]/

 drivers/net/bonding/bond_3ad.c  |  2 +
 drivers/net/bonding/bond_main.c | 85 +++++++++++++++++++++++++++++++--
 include/net/bonding.h           |  1 +
 3 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c
index b8e4b4d68dd6..1fe499ade439 100644
--- a/drivers/net/bonding/bond_3ad.c
+++ b/drivers/net/bonding/bond_3ad.c
@@ -2614,6 +2614,8 @@ void bond_3ad_state_machine_handler(struct work_struct *work)
 			break;
 		}
 	}
+	if (update_slave_arr)
+		WRITE_ONCE(bond->slave_arr_update_pending, true);
 	rcu_read_unlock();
 	spin_unlock_bh(&bond->mode_lock);
 
diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
index a9bff7663eec..1144281912d3 100644
--- a/drivers/net/bonding/bond_main.c
+++ b/drivers/net/bonding/bond_main.c
@@ -4339,6 +4339,7 @@ void bond_work_cancel_all(struct bonding *bond)
 	cancel_delayed_work_sync(&bond->ad_work);
 	cancel_delayed_work_sync(&bond->mcast_work);
 	cancel_delayed_work_sync(&bond->slave_arr_work);
+	WRITE_ONCE(bond->slave_arr_update_pending, false);
 	cancel_delayed_work_sync(&bond->peer_notify_work);
 }
 
@@ -5093,6 +5094,8 @@ static void bond_slave_arr_handler(struct work_struct *work)
 		pr_warn_ratelimited("Failed to update slave array from WT\n");
 		goto err;
 	}
+	/* bond->wq is ordered, so no newer request can race this clear. */
+	WRITE_ONCE(bond->slave_arr_update_pending, false);
 	return;
 
 err:
@@ -5233,11 +5236,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 +5292,67 @@ 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. */
+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;
+
+	/* Limit the list walk to the array-update window. */
+	if (!netif_carrier_ok(bond->dev) ||
+	    !READ_ONCE(bond->slave_arr_update_pending))
+		return NULL;
+
+	/* TX may recurse while mode_lock is already held. */
+	if (unlikely(netpoll_tx_running(bond->dev)) ||
+	    !spin_trylock_bh(&bond->mode_lock))
+		return NULL;
+
+	if (!READ_ONCE(bond->slave_arr_update_pending))
+		goto out;
+
+	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 (!selected)
+			selected = slave;
+
+		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 update is pending.
  */
 static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
 				     struct net_device *dev)
@@ -5302,6 +5363,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);
 
@@ -5327,6 +5390,16 @@ static netdev_tx_t bond_xmit_broadcast(struct sk_buff *skb,
 		slaves = rcu_dereference(bond->usable_slaves);
 
 	slaves_count = slaves ? READ_ONCE(slaves->count) : 0;
+	if (!slaves_count && !all_slaves &&
+	    BOND_MODE(bond) == BOND_MODE_8023AD) {
+		struct slave *slave;
+
+		slave = bond_3ad_xmit_fallback(bond,
+					       bond_xmit_hash(bond, skb));
+		if (slave)
+			return bond_dev_queue_xmit(bond, skb, slave->dev);
+	}
+
 	for (i = 0; i < slaves_count; i++) {
 		struct slave *slave = slaves->arr[i];
 		struct sk_buff *skb2;
@@ -5432,6 +5505,10 @@ static struct net_device *bond_xmit_get_slave(struct net_device *master_dev,
 		else
 			slaves = rcu_dereference(bond->usable_slaves);
 		slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
+		if (!slave && !all_slaves &&
+		    BOND_MODE(bond) == BOND_MODE_8023AD)
+			slave = bond_3ad_xmit_fallback(bond,
+						       bond_xmit_hash(bond, skb));
 		break;
 	case BOND_MODE_BROADCAST:
 		break;
diff --git a/include/net/bonding.h b/include/net/bonding.h
index 598d56b1bc97..3ab8ca1c57bf 100644
--- a/include/net/bonding.h
+++ b/include/net/bonding.h
@@ -220,6 +220,7 @@ struct bonding {
 	struct   slave __rcu *primary_slave;
 	struct   bond_up_slave __rcu *usable_slaves;
 	struct   bond_up_slave __rcu *all_slaves;
+	bool     slave_arr_update_pending; /* 802.3ad update not yet published */
 	bool     force_primary;
 	bool     notifier_ctx;
 	s32      slave_cnt; /* never change this value outside the attach/detach wrappers */
-- 
2.43.0
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.