* [PATCH net v5 0/2] bonding: fix TLB load-tracking overflow on high-speed NICs
@ 2026-08-25 1:01 Hangbin Liu
2026-08-25 1:01 ` [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state Hangbin Liu
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
0 siblings, 2 replies; 11+ messages in thread
From: Hangbin Liu @ 2026-08-25 1:01 UTC (permalink / raw)
To: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov
Cc: Hangbin Liu, netdev, linux-kernel, Hangbin Liu
The bonding TLB (Transmit Load Balancing) mode tracks per-slave and
per-client transmit byte counts in u32 fields. At sustained throughput
above ~3.2 Gbit/s over the 10-second rebalance interval these counters
wrap, causing compute_gap() to produce incorrect gap values and
mis-select transmit slaves. Such speeds are routine on modern NICs
under heavy traffic.
This two-patch series fixes the overflow by widening the relevant
fields to u64.
Patch 1 converts the unbalanced_load counter to per-cpu state as a
preparatory step. The counter sits in the transmit hot path, so
converting it to per-cpu before widening avoids introducing cross-CPU
synchronization overhead for a u64. Also use a prev_total_unbalanced
to store the previous total load to avoid reset per-cpu data.
Patch 2 widens tx_bytes, load_history, load, and the per-cpu
unbalanced_load tx_bytes from u32 to u64. It adds u64_stats_sync
protection for the per-cpu counter to prevent tearing on 32-bit
architectures, and reworks compute_gap() to use s64 arithmetic with
READ_ONCE() on slave->speed.
Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
---
Changes in v5:
- use s64 as the return value for compute_gap, so we can compute the
difference when NICs are overload. (Nikolay Aleksandrov)
- Link to v4: https://lore.kernel.org/r/20260820-bond_overflow-v4-0-805ba0d3efb6@kylinos.cn
Changes in v4:
- move per-cpu allocation to tlb_initialize/tlb_deinitialize (Nikolay Aleksandrov)
- use an extra prev_total_unbalanced to avoid reset per-cpu data (Nikolay Aleksandrov)
- Link to v3: https://lore.kernel.org/r/20260818-bond_overflow-v3-0-e05d4dbc2fd8@kylinos.cn
Changes in v3:
- Add a preparatory patch to convert unbalanced_load to per-cpu first
- widens tlb counters to u64 and add helpers to prevent tearing on 32-bit
- Link to v2: https://lore.kernel.org/r/20260814-bond_overflow-v2-1-d3fe588ad167@kylinos.cn
Changes in v2:
- update comment description, including AI-detected info.
- fix tx_bytes/load type detected by sashiko
- cast SPEED_UNKNOWN to 0 before shift, detected by sashiko
- Link to v1: https://lore.kernel.org/r/20260810-bond_overflow-v1-1-c9ff29d76770@kylinos.cn
---
Hangbin Liu (2):
bonding: convert unbalanced_load to per-cpu state
bonding: fix u32 overflow in compute_gap()
drivers/net/bonding/bond_alb.c | 69 +++++++++++++++++++++++++++++++++++-------
include/net/bond_alb.h | 14 ++++++---
2 files changed, 68 insertions(+), 15 deletions(-)
---
base-commit: 564973a259ec76f2dad0853420e7034cc43994c4
change-id: 20260806-bond_overflow-ac6a6a78d6a0
Best regards,
--
Hangbin Liu <liuhangbin@kylinos.cn>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state
2026-08-25 1:01 [PATCH net v5 0/2] bonding: fix TLB load-tracking overflow on high-speed NICs Hangbin Liu
@ 2026-08-25 1:01 ` Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
1 sibling, 1 reply; 11+ messages in thread
From: Hangbin Liu @ 2026-08-25 1:01 UTC (permalink / raw)
To: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov
Cc: Hangbin Liu, netdev, linux-kernel, Hangbin Liu
From: Hangbin Liu <liuhangbin@kylinos.cn>
A later patch widens the bonding TLB tx counters from u32 to u64. The
unbalanced_load counter sits in the transmit hot path, and cross-CPU
synchronization of a u64 would introduce measurable overhead. Convert
unbalanced_load to a per-cpu counter first so that the subsequent
widening only touches per-cpu data local to each CPU.
Introduce struct unbalanced_load_stats to hold the per-cpu counter,
and move the aggregation into a helper, reset_unbalanced_load(), which
sums all per-cpu instances. Use the delta of current total load vs
variable prev_total_unbalanced to calculate the loading.
Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
---
drivers/net/bonding/bond_alb.c | 37 ++++++++++++++++++++++++++++++-------
include/net/bond_alb.h | 7 ++++++-
2 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
index 839f7482dc18..0afed2c39231 100644
--- a/drivers/net/bonding/bond_alb.c
+++ b/drivers/net/bonding/bond_alb.c
@@ -133,6 +133,10 @@ static int tlb_initialize(struct bonding *bond)
if (!new_hashtbl)
return -ENOMEM;
+ bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
+ if (!bond_info->unbalanced_load)
+ goto out;
+
spin_lock_bh(&bond->mode_lock);
bond_info->tx_hashtbl = new_hashtbl;
@@ -143,6 +147,10 @@ static int tlb_initialize(struct bonding *bond)
spin_unlock_bh(&bond->mode_lock);
return 0;
+
+out:
+ kfree(new_hashtbl);
+ return -ENOMEM;
}
/* Must be called only after all slaves have been released */
@@ -154,6 +162,8 @@ static void tlb_deinitialize(struct bonding *bond)
kfree(bond_info->tx_hashtbl);
bond_info->tx_hashtbl = NULL;
+ free_percpu(bond_info->unbalanced_load);
+ bond_info->prev_total_unbalanced = 0;
spin_unlock_bh(&bond->mode_lock);
}
@@ -1345,7 +1355,7 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
/* unbalanced or unassigned, send through primary */
tx_slave = rcu_dereference(bond->curr_active_slave);
if (bond->params.tlb_dynamic_lb)
- bond_info->unbalanced_load += skb->len;
+ this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
}
if (tx_slave && bond_slave_can_tx(tx_slave)) {
@@ -1529,6 +1539,23 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
return bond_do_alb_xmit(skb, bond, tx_slave);
}
+static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
+{
+ struct unbalanced_load_stats *p;
+ u32 delta, total_bytes = 0;
+ int i;
+
+ for_each_possible_cpu(i) {
+ p = per_cpu_ptr(bond_info->unbalanced_load, i);
+ total_bytes += READ_ONCE(p->tx_bytes);
+ }
+
+ delta = total_bytes - bond_info->prev_total_unbalanced;
+ bond_info->prev_total_unbalanced = total_bytes;
+
+ return delta / BOND_TLB_REBALANCE_INTERVAL;
+}
+
void bond_alb_monitor(struct work_struct *work)
{
struct bonding *bond = container_of(work, struct bonding,
@@ -1570,12 +1597,8 @@ void bond_alb_monitor(struct work_struct *work)
if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
bond_for_each_slave_rcu(bond, slave, iter) {
tlb_clear_slave(bond, slave, 1);
- if (slave == rcu_access_pointer(bond->curr_active_slave)) {
- SLAVE_TLB_INFO(slave).load =
- bond_info->unbalanced_load /
- BOND_TLB_REBALANCE_INTERVAL;
- bond_info->unbalanced_load = 0;
- }
+ if (slave == rcu_access_pointer(bond->curr_active_slave))
+ SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
}
atomic_set(&bond_info->tx_rebalance_counter, 0);
}
diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
index e5945427f38d..6fb09b4fc7e2 100644
--- a/include/net/bond_alb.h
+++ b/include/net/bond_alb.h
@@ -123,9 +123,14 @@ struct tlb_slave_info {
*/
};
+struct unbalanced_load_stats {
+ u32 tx_bytes;
+};
+
struct alb_bond_info {
struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
- u32 unbalanced_load;
+ struct unbalanced_load_stats __percpu *unbalanced_load;
+ u32 prev_total_unbalanced;
atomic_t tx_rebalance_counter;
int lp_counter;
/* -------- rlb parameters -------- */
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-25 1:01 [PATCH net v5 0/2] bonding: fix TLB load-tracking overflow on high-speed NICs Hangbin Liu
2026-08-25 1:01 ` [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state Hangbin Liu
@ 2026-08-25 1:01 ` Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
` (2 more replies)
1 sibling, 3 replies; 11+ messages in thread
From: Hangbin Liu @ 2026-08-25 1:01 UTC (permalink / raw)
To: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov
Cc: Hangbin Liu, netdev, linux-kernel, Hangbin Liu
From: Hangbin Liu <liuhangbin@kylinos.cn>
The TLB load-tracking fields tx_bytes, load_history, load, and
unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
over the 10-second rebalance interval the byte counters wrap, causing
compute_gap() to produce incorrect gap values and mis-select slaves.
Such speeds are common on modern NICs under heavy traffic.
Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
unbalanced_load_stats against tearing on 32-bit architectures, and
div_u64() for the 64-bit divisions. The tx_bytes and load_history
are protected in spin_lock. Also protect the slave load writing in
bond_alb_monitor() with spin_lock in case of tear on 32-bit.
Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
when the speed is unknown.
Detected by AI code review.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
---
drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
include/net/bond_alb.h | 11 +++++----
2 files changed, 44 insertions(+), 19 deletions(-)
diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
index 0afed2c39231..9a43a1f47893 100644
--- a/drivers/net/bonding/bond_alb.c
+++ b/drivers/net/bonding/bond_alb.c
@@ -6,6 +6,7 @@
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
#include <linux/pkt_sched.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
@@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
{
if (save_load) {
- entry->load_history = 1 + entry->tx_bytes /
- BOND_TLB_REBALANCE_INTERVAL;
+ entry->load_history = 1 + div_u64(entry->tx_bytes,
+ BOND_TLB_REBALANCE_INTERVAL);
entry->tx_bytes = 0;
}
@@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
if (!new_hashtbl)
return -ENOMEM;
- bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
+ bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
if (!bond_info->unbalanced_load)
goto out;
@@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
static long long compute_gap(struct slave *slave)
{
- return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
- (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
+ u32 raw_speed = READ_ONCE(slave->speed);
+
+ /* It's meaningless to compare gap on unknown speed NIC */
+ if (raw_speed == (u32)SPEED_UNKNOWN)
+ return LLONG_MIN;
+
+ return ((s64)raw_speed << 20) - /* Convert to bits per sec */
+ ((s64)SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
}
static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
@@ -188,7 +195,7 @@ static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
if (bond_slave_can_tx(slave)) {
long long gap = compute_gap(slave);
- if (max_gap < gap) {
+ if (!least_loaded || max_gap < gap) {
least_loaded = slave;
max_gap = gap;
}
@@ -1354,8 +1361,14 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
if (!tx_slave) {
/* unbalanced or unassigned, send through primary */
tx_slave = rcu_dereference(bond->curr_active_slave);
- if (bond->params.tlb_dynamic_lb)
- this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
+ if (bond->params.tlb_dynamic_lb) {
+ struct unbalanced_load_stats *pcpu_load;
+
+ pcpu_load = this_cpu_ptr(bond_info->unbalanced_load);
+ u64_stats_update_begin(&pcpu_load->syncp);
+ u64_stats_add(&pcpu_load->tx_bytes, skb->len);
+ u64_stats_update_end(&pcpu_load->syncp);
+ }
}
if (tx_slave && bond_slave_can_tx(tx_slave)) {
@@ -1539,21 +1552,27 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
return bond_do_alb_xmit(skb, bond, tx_slave);
}
-static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
+static u64 reset_unbalanced_load(struct alb_bond_info *bond_info)
{
+ u64 delta, tx_bytes, total_bytes = 0;
struct unbalanced_load_stats *p;
- u32 delta, total_bytes = 0;
+ unsigned int start;
int i;
for_each_possible_cpu(i) {
p = per_cpu_ptr(bond_info->unbalanced_load, i);
- total_bytes += READ_ONCE(p->tx_bytes);
+ do {
+ start = u64_stats_fetch_begin(&p->syncp);
+ tx_bytes = u64_stats_read(&p->tx_bytes);
+ } while (u64_stats_fetch_retry(&p->syncp, start));
+
+ total_bytes += tx_bytes;
}
delta = total_bytes - bond_info->prev_total_unbalanced;
bond_info->prev_total_unbalanced = total_bytes;
- return delta / BOND_TLB_REBALANCE_INTERVAL;
+ return div_u64(delta, BOND_TLB_REBALANCE_INTERVAL);
}
void bond_alb_monitor(struct work_struct *work)
@@ -1597,8 +1616,13 @@ void bond_alb_monitor(struct work_struct *work)
if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
bond_for_each_slave_rcu(bond, slave, iter) {
tlb_clear_slave(bond, slave, 1);
- if (slave == rcu_access_pointer(bond->curr_active_slave))
- SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
+ if (slave == rcu_access_pointer(bond->curr_active_slave)) {
+ u64 new_load = reset_unbalanced_load(bond_info);
+
+ spin_lock_bh(&bond->mode_lock);
+ SLAVE_TLB_INFO(slave).load = new_load;
+ spin_unlock_bh(&bond->mode_lock);
+ }
}
atomic_set(&bond_info->tx_rebalance_counter, 0);
}
diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
index 6fb09b4fc7e2..32f1981033e4 100644
--- a/include/net/bond_alb.h
+++ b/include/net/bond_alb.h
@@ -57,12 +57,12 @@ struct tlb_client_info {
* packets to a Client that the Hash function
* gave this entry index.
*/
- u32 tx_bytes; /* Each Client accumulates the BytesTx that
+ u64 tx_bytes; /* Each Client accumulates the BytesTx that
* were transmitted to it, and after each
* CallBack the LoadHistory is divided
* by the balance interval
*/
- u32 load_history; /* This field contains the amount of Bytes
+ u64 load_history; /* This field contains the amount of Bytes
* that were transmitted to this client by
* the server on the previous balance
* interval in Bps.
@@ -118,19 +118,20 @@ struct tlb_slave_info {
* are the entries that were assigned to use this
* slave for transmit.
*/
- u32 load; /* Each slave sums the loadHistory of all clients
+ u64 load; /* Each slave sums the loadHistory of all clients
* assigned to it
*/
};
struct unbalanced_load_stats {
- u32 tx_bytes;
+ u64_stats_t tx_bytes;
+ struct u64_stats_sync syncp;
};
struct alb_bond_info {
struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
struct unbalanced_load_stats __percpu *unbalanced_load;
- u32 prev_total_unbalanced;
+ u64 prev_total_unbalanced;
atomic_t tx_rebalance_counter;
int lp_counter;
/* -------- rlb parameters -------- */
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state
2026-08-25 1:01 ` [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state Hangbin Liu
@ 2026-08-26 7:34 ` Nikolay Aleksandrov
0 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-08-26 7:34 UTC (permalink / raw)
To: Hangbin Liu, Jay Vosburgh, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netdev, linux-kernel, Hangbin Liu
On 25/08/2026 04:01, Hangbin Liu wrote:
> From: Hangbin Liu <liuhangbin@kylinos.cn>
>
> A later patch widens the bonding TLB tx counters from u32 to u64. The
> unbalanced_load counter sits in the transmit hot path, and cross-CPU
> synchronization of a u64 would introduce measurable overhead. Convert
> unbalanced_load to a per-cpu counter first so that the subsequent
> widening only touches per-cpu data local to each CPU.
>
> Introduce struct unbalanced_load_stats to hold the per-cpu counter,
> and move the aggregation into a helper, reset_unbalanced_load(), which
> sums all per-cpu instances. Use the delta of current total load vs
> variable prev_total_unbalanced to calculate the loading.
>
> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
> ---
> drivers/net/bonding/bond_alb.c | 37 ++++++++++++++++++++++++++++++-------
> include/net/bond_alb.h | 7 ++++++-
> 2 files changed, 36 insertions(+), 8 deletions(-)
>
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
@ 2026-08-26 7:34 ` Nikolay Aleksandrov
2026-08-27 13:42 ` Paolo Abeni
2026-08-27 18:20 ` David Laight
2 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-08-26 7:34 UTC (permalink / raw)
To: Hangbin Liu, Jay Vosburgh, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netdev, linux-kernel, Hangbin Liu
On 25/08/2026 04:01, Hangbin Liu wrote:
> From: Hangbin Liu <liuhangbin@kylinos.cn>
>
> The TLB load-tracking fields tx_bytes, load_history, load, and
> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
> over the 10-second rebalance interval the byte counters wrap, causing
> compute_gap() to produce incorrect gap values and mis-select slaves.
> Such speeds are common on modern NICs under heavy traffic.
>
> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
> unbalanced_load_stats against tearing on 32-bit architectures, and
> div_u64() for the 64-bit divisions. The tx_bytes and load_history
> are protected in spin_lock. Also protect the slave load writing in
> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
>
> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
> when the speed is unknown.
>
> Detected by AI code review.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
> ---
> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
> include/net/bond_alb.h | 11 +++++----
> 2 files changed, 44 insertions(+), 19 deletions(-)
>
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
@ 2026-08-27 13:42 ` Paolo Abeni
2026-08-28 1:28 ` Hangbin Liu
2026-08-27 18:20 ` David Laight
2 siblings, 1 reply; 11+ messages in thread
From: Paolo Abeni @ 2026-08-27 13:42 UTC (permalink / raw)
To: Hangbin Liu, Jay Vosburgh, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Simon Horman, Nikolay Aleksandrov
Cc: netdev, linux-kernel, Hangbin Liu
On 8/25/26 3:01 AM, Hangbin Liu wrote:
> From: Hangbin Liu <liuhangbin@kylinos.cn>
>
> The TLB load-tracking fields tx_bytes, load_history, load, and
> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
> over the 10-second rebalance interval the byte counters wrap, causing
> compute_gap() to produce incorrect gap values and mis-select slaves.
> Such speeds are common on modern NICs under heavy traffic.
>
> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
> unbalanced_load_stats against tearing on 32-bit architectures, and
> div_u64() for the 64-bit divisions. The tx_bytes and load_history
> are protected in spin_lock. Also protect the slave load writing in
> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
>
> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
> when the speed is unknown.
>
> Detected by AI code review.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
> ---
> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
> include/net/bond_alb.h | 11 +++++----
> 2 files changed, 44 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
> index 0afed2c39231..9a43a1f47893 100644
> --- a/drivers/net/bonding/bond_alb.c
> +++ b/drivers/net/bonding/bond_alb.c
> @@ -6,6 +6,7 @@
> #include <linux/skbuff.h>
> #include <linux/netdevice.h>
> #include <linux/etherdevice.h>
> +#include <linux/ethtool.h>
> #include <linux/pkt_sched.h>
> #include <linux/spinlock.h>
> #include <linux/slab.h>
> @@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
> static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
> {
> if (save_load) {
> - entry->load_history = 1 + entry->tx_bytes /
> - BOND_TLB_REBALANCE_INTERVAL;
> + entry->load_history = 1 + div_u64(entry->tx_bytes,
> + BOND_TLB_REBALANCE_INTERVAL);
> entry->tx_bytes = 0;
> }
>
> @@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
> if (!new_hashtbl)
> return -ENOMEM;
>
> - bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
> + bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
> if (!bond_info->unbalanced_load)
> goto out;
>
> @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
>
> static long long compute_gap(struct slave *slave)
> {
> - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
> - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> + u32 raw_speed = READ_ONCE(slave->speed);
> +
> + /* It's meaningless to compare gap on unknown speed NIC */
> + if (raw_speed == (u32)SPEED_UNKNOWN)
> + return LLONG_MIN;
Sashiko noted the above could entirely disable:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260825-bond_overflow-v5-0-7a800de133f1%40kylinos.cn
I think the v2 code for the above should be fine.
All other comments look noise to me.
/P
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
2026-08-27 13:42 ` Paolo Abeni
@ 2026-08-27 18:20 ` David Laight
2026-08-27 19:09 ` Nikolay Aleksandrov
2 siblings, 1 reply; 11+ messages in thread
From: David Laight @ 2026-08-27 18:20 UTC (permalink / raw)
To: Hangbin Liu
Cc: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov,
netdev, linux-kernel, Hangbin Liu
On Tue, 25 Aug 2026 09:01:30 +0800
Hangbin Liu <hangbin.liu@linux.dev> wrote:
> From: Hangbin Liu <liuhangbin@kylinos.cn>
>
> The TLB load-tracking fields tx_bytes, load_history, load, and
> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
> over the 10-second rebalance interval the byte counters wrap, causing
> compute_gap() to produce incorrect gap values and mis-select slaves.
> Such speeds are common on modern NICs under heavy traffic.
>
> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
> unbalanced_load_stats against tearing on 32-bit architectures, and
> div_u64() for the 64-bit divisions. The tx_bytes and load_history
> are protected in spin_lock. Also protect the slave load writing in
> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
How about changing the rebalance interval to either 8 or 16 seconds
to avoid the expensive divide?
Alternatively multiply the other side of the comparisons by the interval,
replacing the expensive divide with a cheap multiply.
David
>
> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
> when the speed is unknown.
>
> Detected by AI code review.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
> ---
> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
> include/net/bond_alb.h | 11 +++++----
> 2 files changed, 44 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
> index 0afed2c39231..9a43a1f47893 100644
> --- a/drivers/net/bonding/bond_alb.c
> +++ b/drivers/net/bonding/bond_alb.c
> @@ -6,6 +6,7 @@
> #include <linux/skbuff.h>
> #include <linux/netdevice.h>
> #include <linux/etherdevice.h>
> +#include <linux/ethtool.h>
> #include <linux/pkt_sched.h>
> #include <linux/spinlock.h>
> #include <linux/slab.h>
> @@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
> static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
> {
> if (save_load) {
> - entry->load_history = 1 + entry->tx_bytes /
> - BOND_TLB_REBALANCE_INTERVAL;
> + entry->load_history = 1 + div_u64(entry->tx_bytes,
> + BOND_TLB_REBALANCE_INTERVAL);
> entry->tx_bytes = 0;
> }
>
> @@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
> if (!new_hashtbl)
> return -ENOMEM;
>
> - bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
> + bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
> if (!bond_info->unbalanced_load)
> goto out;
>
> @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
>
> static long long compute_gap(struct slave *slave)
> {
> - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
> - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> + u32 raw_speed = READ_ONCE(slave->speed);
> +
> + /* It's meaningless to compare gap on unknown speed NIC */
> + if (raw_speed == (u32)SPEED_UNKNOWN)
> + return LLONG_MIN;
> +
> + return ((s64)raw_speed << 20) - /* Convert to bits per sec */
> + ((s64)SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> }
>
> static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
> @@ -188,7 +195,7 @@ static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
> if (bond_slave_can_tx(slave)) {
> long long gap = compute_gap(slave);
>
> - if (max_gap < gap) {
> + if (!least_loaded || max_gap < gap) {
> least_loaded = slave;
> max_gap = gap;
> }
> @@ -1354,8 +1361,14 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
> if (!tx_slave) {
> /* unbalanced or unassigned, send through primary */
> tx_slave = rcu_dereference(bond->curr_active_slave);
> - if (bond->params.tlb_dynamic_lb)
> - this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
> + if (bond->params.tlb_dynamic_lb) {
> + struct unbalanced_load_stats *pcpu_load;
> +
> + pcpu_load = this_cpu_ptr(bond_info->unbalanced_load);
> + u64_stats_update_begin(&pcpu_load->syncp);
> + u64_stats_add(&pcpu_load->tx_bytes, skb->len);
> + u64_stats_update_end(&pcpu_load->syncp);
> + }
> }
>
> if (tx_slave && bond_slave_can_tx(tx_slave)) {
> @@ -1539,21 +1552,27 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
> return bond_do_alb_xmit(skb, bond, tx_slave);
> }
>
> -static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
> +static u64 reset_unbalanced_load(struct alb_bond_info *bond_info)
> {
> + u64 delta, tx_bytes, total_bytes = 0;
> struct unbalanced_load_stats *p;
> - u32 delta, total_bytes = 0;
> + unsigned int start;
> int i;
>
> for_each_possible_cpu(i) {
> p = per_cpu_ptr(bond_info->unbalanced_load, i);
> - total_bytes += READ_ONCE(p->tx_bytes);
> + do {
> + start = u64_stats_fetch_begin(&p->syncp);
> + tx_bytes = u64_stats_read(&p->tx_bytes);
> + } while (u64_stats_fetch_retry(&p->syncp, start));
> +
> + total_bytes += tx_bytes;
> }
>
> delta = total_bytes - bond_info->prev_total_unbalanced;
> bond_info->prev_total_unbalanced = total_bytes;
>
> - return delta / BOND_TLB_REBALANCE_INTERVAL;
> + return div_u64(delta, BOND_TLB_REBALANCE_INTERVAL);
> }
>
> void bond_alb_monitor(struct work_struct *work)
> @@ -1597,8 +1616,13 @@ void bond_alb_monitor(struct work_struct *work)
> if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
> bond_for_each_slave_rcu(bond, slave, iter) {
> tlb_clear_slave(bond, slave, 1);
> - if (slave == rcu_access_pointer(bond->curr_active_slave))
> - SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
> + if (slave == rcu_access_pointer(bond->curr_active_slave)) {
> + u64 new_load = reset_unbalanced_load(bond_info);
> +
> + spin_lock_bh(&bond->mode_lock);
> + SLAVE_TLB_INFO(slave).load = new_load;
> + spin_unlock_bh(&bond->mode_lock);
> + }
> }
> atomic_set(&bond_info->tx_rebalance_counter, 0);
> }
> diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
> index 6fb09b4fc7e2..32f1981033e4 100644
> --- a/include/net/bond_alb.h
> +++ b/include/net/bond_alb.h
> @@ -57,12 +57,12 @@ struct tlb_client_info {
> * packets to a Client that the Hash function
> * gave this entry index.
> */
> - u32 tx_bytes; /* Each Client accumulates the BytesTx that
> + u64 tx_bytes; /* Each Client accumulates the BytesTx that
> * were transmitted to it, and after each
> * CallBack the LoadHistory is divided
> * by the balance interval
> */
> - u32 load_history; /* This field contains the amount of Bytes
> + u64 load_history; /* This field contains the amount of Bytes
> * that were transmitted to this client by
> * the server on the previous balance
> * interval in Bps.
> @@ -118,19 +118,20 @@ struct tlb_slave_info {
> * are the entries that were assigned to use this
> * slave for transmit.
> */
> - u32 load; /* Each slave sums the loadHistory of all clients
> + u64 load; /* Each slave sums the loadHistory of all clients
> * assigned to it
> */
> };
>
> struct unbalanced_load_stats {
> - u32 tx_bytes;
> + u64_stats_t tx_bytes;
> + struct u64_stats_sync syncp;
> };
>
> struct alb_bond_info {
> struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
> struct unbalanced_load_stats __percpu *unbalanced_load;
> - u32 prev_total_unbalanced;
> + u64 prev_total_unbalanced;
> atomic_t tx_rebalance_counter;
> int lp_counter;
> /* -------- rlb parameters -------- */
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-27 18:20 ` David Laight
@ 2026-08-27 19:09 ` Nikolay Aleksandrov
2026-08-27 20:56 ` David Laight
0 siblings, 1 reply; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-08-27 19:09 UTC (permalink / raw)
To: David Laight, Hangbin Liu
Cc: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
Hangbin Liu
On 27/08/2026 21:20, David Laight wrote:
> On Tue, 25 Aug 2026 09:01:30 +0800
> Hangbin Liu <hangbin.liu@linux.dev> wrote:
>
>> From: Hangbin Liu <liuhangbin@kylinos.cn>
>>
>> The TLB load-tracking fields tx_bytes, load_history, load, and
>> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
>> over the 10-second rebalance interval the byte counters wrap, causing
>> compute_gap() to produce incorrect gap values and mis-select slaves.
>> Such speeds are common on modern NICs under heavy traffic.
>>
>> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
>> unbalanced_load_stats against tearing on 32-bit architectures, and
>> div_u64() for the 64-bit divisions. The tx_bytes and load_history
>> are protected in spin_lock. Also protect the slave load writing in
>> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
>
> How about changing the rebalance interval to either 8 or 16 seconds
> to avoid the expensive divide?
> Alternatively multiply the other side of the comparisons by the interval,
> replacing the expensive divide with a cheap multiply.
>
> David
>
How is that relevant to these patches?
And how does that help at all if today that is done at about 10 second interval?
>>
>> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
>> when the speed is unknown.
>>
>> Detected by AI code review.
>>
>> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
>> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
>> ---
>> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
>> include/net/bond_alb.h | 11 +++++----
>> 2 files changed, 44 insertions(+), 19 deletions(-)
>>
>> diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
>> index 0afed2c39231..9a43a1f47893 100644
>> --- a/drivers/net/bonding/bond_alb.c
>> +++ b/drivers/net/bonding/bond_alb.c
>> @@ -6,6 +6,7 @@
>> #include <linux/skbuff.h>
>> #include <linux/netdevice.h>
>> #include <linux/etherdevice.h>
>> +#include <linux/ethtool.h>
>> #include <linux/pkt_sched.h>
>> #include <linux/spinlock.h>
>> #include <linux/slab.h>
>> @@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
>> static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
>> {
>> if (save_load) {
>> - entry->load_history = 1 + entry->tx_bytes /
>> - BOND_TLB_REBALANCE_INTERVAL;
>> + entry->load_history = 1 + div_u64(entry->tx_bytes,
>> + BOND_TLB_REBALANCE_INTERVAL);
>> entry->tx_bytes = 0;
>> }
>>
>> @@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
>> if (!new_hashtbl)
>> return -ENOMEM;
>>
>> - bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
>> + bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
>> if (!bond_info->unbalanced_load)
>> goto out;
>>
>> @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
>>
>> static long long compute_gap(struct slave *slave)
>> {
>> - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
>> - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
>> + u32 raw_speed = READ_ONCE(slave->speed);
>> +
>> + /* It's meaningless to compare gap on unknown speed NIC */
>> + if (raw_speed == (u32)SPEED_UNKNOWN)
>> + return LLONG_MIN;
>> +
>> + return ((s64)raw_speed << 20) - /* Convert to bits per sec */
>> + ((s64)SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
>> }
>>
>> static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
>> @@ -188,7 +195,7 @@ static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
>> if (bond_slave_can_tx(slave)) {
>> long long gap = compute_gap(slave);
>>
>> - if (max_gap < gap) {
>> + if (!least_loaded || max_gap < gap) {
>> least_loaded = slave;
>> max_gap = gap;
>> }
>> @@ -1354,8 +1361,14 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
>> if (!tx_slave) {
>> /* unbalanced or unassigned, send through primary */
>> tx_slave = rcu_dereference(bond->curr_active_slave);
>> - if (bond->params.tlb_dynamic_lb)
>> - this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
>> + if (bond->params.tlb_dynamic_lb) {
>> + struct unbalanced_load_stats *pcpu_load;
>> +
>> + pcpu_load = this_cpu_ptr(bond_info->unbalanced_load);
>> + u64_stats_update_begin(&pcpu_load->syncp);
>> + u64_stats_add(&pcpu_load->tx_bytes, skb->len);
>> + u64_stats_update_end(&pcpu_load->syncp);
>> + }
>> }
>>
>> if (tx_slave && bond_slave_can_tx(tx_slave)) {
>> @@ -1539,21 +1552,27 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
>> return bond_do_alb_xmit(skb, bond, tx_slave);
>> }
>>
>> -static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
>> +static u64 reset_unbalanced_load(struct alb_bond_info *bond_info)
>> {
>> + u64 delta, tx_bytes, total_bytes = 0;
>> struct unbalanced_load_stats *p;
>> - u32 delta, total_bytes = 0;
>> + unsigned int start;
>> int i;
>>
>> for_each_possible_cpu(i) {
>> p = per_cpu_ptr(bond_info->unbalanced_load, i);
>> - total_bytes += READ_ONCE(p->tx_bytes);
>> + do {
>> + start = u64_stats_fetch_begin(&p->syncp);
>> + tx_bytes = u64_stats_read(&p->tx_bytes);
>> + } while (u64_stats_fetch_retry(&p->syncp, start));
>> +
>> + total_bytes += tx_bytes;
>> }
>>
>> delta = total_bytes - bond_info->prev_total_unbalanced;
>> bond_info->prev_total_unbalanced = total_bytes;
>>
>> - return delta / BOND_TLB_REBALANCE_INTERVAL;
>> + return div_u64(delta, BOND_TLB_REBALANCE_INTERVAL);
>> }
>>
>> void bond_alb_monitor(struct work_struct *work)
>> @@ -1597,8 +1616,13 @@ void bond_alb_monitor(struct work_struct *work)
>> if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
>> bond_for_each_slave_rcu(bond, slave, iter) {
>> tlb_clear_slave(bond, slave, 1);
>> - if (slave == rcu_access_pointer(bond->curr_active_slave))
>> - SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
>> + if (slave == rcu_access_pointer(bond->curr_active_slave)) {
>> + u64 new_load = reset_unbalanced_load(bond_info);
>> +
>> + spin_lock_bh(&bond->mode_lock);
>> + SLAVE_TLB_INFO(slave).load = new_load;
>> + spin_unlock_bh(&bond->mode_lock);
>> + }
>> }
>> atomic_set(&bond_info->tx_rebalance_counter, 0);
>> }
>> diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
>> index 6fb09b4fc7e2..32f1981033e4 100644
>> --- a/include/net/bond_alb.h
>> +++ b/include/net/bond_alb.h
>> @@ -57,12 +57,12 @@ struct tlb_client_info {
>> * packets to a Client that the Hash function
>> * gave this entry index.
>> */
>> - u32 tx_bytes; /* Each Client accumulates the BytesTx that
>> + u64 tx_bytes; /* Each Client accumulates the BytesTx that
>> * were transmitted to it, and after each
>> * CallBack the LoadHistory is divided
>> * by the balance interval
>> */
>> - u32 load_history; /* This field contains the amount of Bytes
>> + u64 load_history; /* This field contains the amount of Bytes
>> * that were transmitted to this client by
>> * the server on the previous balance
>> * interval in Bps.
>> @@ -118,19 +118,20 @@ struct tlb_slave_info {
>> * are the entries that were assigned to use this
>> * slave for transmit.
>> */
>> - u32 load; /* Each slave sums the loadHistory of all clients
>> + u64 load; /* Each slave sums the loadHistory of all clients
>> * assigned to it
>> */
>> };
>>
>> struct unbalanced_load_stats {
>> - u32 tx_bytes;
>> + u64_stats_t tx_bytes;
>> + struct u64_stats_sync syncp;
>> };
>>
>> struct alb_bond_info {
>> struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
>> struct unbalanced_load_stats __percpu *unbalanced_load;
>> - u32 prev_total_unbalanced;
>> + u64 prev_total_unbalanced;
>> atomic_t tx_rebalance_counter;
>> int lp_counter;
>> /* -------- rlb parameters -------- */
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-27 19:09 ` Nikolay Aleksandrov
@ 2026-08-27 20:56 ` David Laight
2026-08-28 9:19 ` Nikolay Aleksandrov
0 siblings, 1 reply; 11+ messages in thread
From: David Laight @ 2026-08-27 20:56 UTC (permalink / raw)
To: Nikolay Aleksandrov
Cc: Hangbin Liu, Jay Vosburgh, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kernel, Hangbin Liu
On Thu, 27 Aug 2026 22:09:04 +0300
Nikolay Aleksandrov <razor@blackwall.org> wrote:
> On 27/08/2026 21:20, David Laight wrote:
> > On Tue, 25 Aug 2026 09:01:30 +0800
> > Hangbin Liu <hangbin.liu@linux.dev> wrote:
> >
> >> From: Hangbin Liu <liuhangbin@kylinos.cn>
> >>
> >> The TLB load-tracking fields tx_bytes, load_history, load, and
> >> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
> >> over the 10-second rebalance interval the byte counters wrap, causing
> >> compute_gap() to produce incorrect gap values and mis-select slaves.
> >> Such speeds are common on modern NICs under heavy traffic.
> >>
> >> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
> >> unbalanced_load_stats against tearing on 32-bit architectures, and
> >> div_u64() for the 64-bit divisions. The tx_bytes and load_history
> >> are protected in spin_lock. Also protect the slave load writing in
> >> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
> >
> > How about changing the rebalance interval to either 8 or 16 seconds
> > to avoid the expensive divide?
> > Alternatively multiply the other side of the comparisons by the interval,
> > replacing the expensive divide with a cheap multiply.
> >
> > David
> >
>
> How is that relevant to these patches?
> And how does that help at all if today that is done at about 10 second interval?
The 10 seconds is almost certainly completely arbitrary.
It is relevant because the 64bit divide is significantly expensive on 32bit.
David
>
> >>
> >> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
> >> when the speed is unknown.
> >>
> >> Detected by AI code review.
> >>
> >> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> >> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
> >> ---
> >> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
> >> include/net/bond_alb.h | 11 +++++----
> >> 2 files changed, 44 insertions(+), 19 deletions(-)
> >>
> >> diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
> >> index 0afed2c39231..9a43a1f47893 100644
> >> --- a/drivers/net/bonding/bond_alb.c
> >> +++ b/drivers/net/bonding/bond_alb.c
> >> @@ -6,6 +6,7 @@
> >> #include <linux/skbuff.h>
> >> #include <linux/netdevice.h>
> >> #include <linux/etherdevice.h>
> >> +#include <linux/ethtool.h>
> >> #include <linux/pkt_sched.h>
> >> #include <linux/spinlock.h>
> >> #include <linux/slab.h>
> >> @@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
> >> static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
> >> {
> >> if (save_load) {
> >> - entry->load_history = 1 + entry->tx_bytes /
> >> - BOND_TLB_REBALANCE_INTERVAL;
> >> + entry->load_history = 1 + div_u64(entry->tx_bytes,
> >> + BOND_TLB_REBALANCE_INTERVAL);
> >> entry->tx_bytes = 0;
> >> }
> >>
> >> @@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
> >> if (!new_hashtbl)
> >> return -ENOMEM;
> >>
> >> - bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
> >> + bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
> >> if (!bond_info->unbalanced_load)
> >> goto out;
> >>
> >> @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
> >>
> >> static long long compute_gap(struct slave *slave)
> >> {
> >> - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
> >> - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> >> + u32 raw_speed = READ_ONCE(slave->speed);
> >> +
> >> + /* It's meaningless to compare gap on unknown speed NIC */
> >> + if (raw_speed == (u32)SPEED_UNKNOWN)
> >> + return LLONG_MIN;
> >> +
> >> + return ((s64)raw_speed << 20) - /* Convert to bits per sec */
> >> + ((s64)SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> >> }
> >>
> >> static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
> >> @@ -188,7 +195,7 @@ static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
> >> if (bond_slave_can_tx(slave)) {
> >> long long gap = compute_gap(slave);
> >>
> >> - if (max_gap < gap) {
> >> + if (!least_loaded || max_gap < gap) {
> >> least_loaded = slave;
> >> max_gap = gap;
> >> }
> >> @@ -1354,8 +1361,14 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
> >> if (!tx_slave) {
> >> /* unbalanced or unassigned, send through primary */
> >> tx_slave = rcu_dereference(bond->curr_active_slave);
> >> - if (bond->params.tlb_dynamic_lb)
> >> - this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
> >> + if (bond->params.tlb_dynamic_lb) {
> >> + struct unbalanced_load_stats *pcpu_load;
> >> +
> >> + pcpu_load = this_cpu_ptr(bond_info->unbalanced_load);
> >> + u64_stats_update_begin(&pcpu_load->syncp);
> >> + u64_stats_add(&pcpu_load->tx_bytes, skb->len);
> >> + u64_stats_update_end(&pcpu_load->syncp);
> >> + }
> >> }
> >>
> >> if (tx_slave && bond_slave_can_tx(tx_slave)) {
> >> @@ -1539,21 +1552,27 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
> >> return bond_do_alb_xmit(skb, bond, tx_slave);
> >> }
> >>
> >> -static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
> >> +static u64 reset_unbalanced_load(struct alb_bond_info *bond_info)
> >> {
> >> + u64 delta, tx_bytes, total_bytes = 0;
> >> struct unbalanced_load_stats *p;
> >> - u32 delta, total_bytes = 0;
> >> + unsigned int start;
> >> int i;
> >>
> >> for_each_possible_cpu(i) {
> >> p = per_cpu_ptr(bond_info->unbalanced_load, i);
> >> - total_bytes += READ_ONCE(p->tx_bytes);
> >> + do {
> >> + start = u64_stats_fetch_begin(&p->syncp);
> >> + tx_bytes = u64_stats_read(&p->tx_bytes);
> >> + } while (u64_stats_fetch_retry(&p->syncp, start));
> >> +
> >> + total_bytes += tx_bytes;
> >> }
> >>
> >> delta = total_bytes - bond_info->prev_total_unbalanced;
> >> bond_info->prev_total_unbalanced = total_bytes;
> >>
> >> - return delta / BOND_TLB_REBALANCE_INTERVAL;
> >> + return div_u64(delta, BOND_TLB_REBALANCE_INTERVAL);
> >> }
> >>
> >> void bond_alb_monitor(struct work_struct *work)
> >> @@ -1597,8 +1616,13 @@ void bond_alb_monitor(struct work_struct *work)
> >> if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
> >> bond_for_each_slave_rcu(bond, slave, iter) {
> >> tlb_clear_slave(bond, slave, 1);
> >> - if (slave == rcu_access_pointer(bond->curr_active_slave))
> >> - SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
> >> + if (slave == rcu_access_pointer(bond->curr_active_slave)) {
> >> + u64 new_load = reset_unbalanced_load(bond_info);
> >> +
> >> + spin_lock_bh(&bond->mode_lock);
> >> + SLAVE_TLB_INFO(slave).load = new_load;
> >> + spin_unlock_bh(&bond->mode_lock);
> >> + }
> >> }
> >> atomic_set(&bond_info->tx_rebalance_counter, 0);
> >> }
> >> diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
> >> index 6fb09b4fc7e2..32f1981033e4 100644
> >> --- a/include/net/bond_alb.h
> >> +++ b/include/net/bond_alb.h
> >> @@ -57,12 +57,12 @@ struct tlb_client_info {
> >> * packets to a Client that the Hash function
> >> * gave this entry index.
> >> */
> >> - u32 tx_bytes; /* Each Client accumulates the BytesTx that
> >> + u64 tx_bytes; /* Each Client accumulates the BytesTx that
> >> * were transmitted to it, and after each
> >> * CallBack the LoadHistory is divided
> >> * by the balance interval
> >> */
> >> - u32 load_history; /* This field contains the amount of Bytes
> >> + u64 load_history; /* This field contains the amount of Bytes
> >> * that were transmitted to this client by
> >> * the server on the previous balance
> >> * interval in Bps.
> >> @@ -118,19 +118,20 @@ struct tlb_slave_info {
> >> * are the entries that were assigned to use this
> >> * slave for transmit.
> >> */
> >> - u32 load; /* Each slave sums the loadHistory of all clients
> >> + u64 load; /* Each slave sums the loadHistory of all clients
> >> * assigned to it
> >> */
> >> };
> >>
> >> struct unbalanced_load_stats {
> >> - u32 tx_bytes;
> >> + u64_stats_t tx_bytes;
> >> + struct u64_stats_sync syncp;
> >> };
> >>
> >> struct alb_bond_info {
> >> struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
> >> struct unbalanced_load_stats __percpu *unbalanced_load;
> >> - u32 prev_total_unbalanced;
> >> + u64 prev_total_unbalanced;
> >> atomic_t tx_rebalance_counter;
> >> int lp_counter;
> >> /* -------- rlb parameters -------- */
> >>
> >
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-27 13:42 ` Paolo Abeni
@ 2026-08-28 1:28 ` Hangbin Liu
0 siblings, 0 replies; 11+ messages in thread
From: Hangbin Liu @ 2026-08-28 1:28 UTC (permalink / raw)
To: Paolo Abeni
Cc: Jay Vosburgh, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Simon Horman, Nikolay Aleksandrov, netdev,
linux-kernel, Hangbin Liu
On Thu, Aug 27, 2026 at 03:42:23PM +0200, Paolo Abeni wrote:
> > @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
> >
> > static long long compute_gap(struct slave *slave)
> > {
> > - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
> > - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
> > + u32 raw_speed = READ_ONCE(slave->speed);
> > +
> > + /* It's meaningless to compare gap on unknown speed NIC */
> > + if (raw_speed == (u32)SPEED_UNKNOWN)
> > + return LLONG_MIN;
>
> Sashiko noted the above could entirely disable:
>
> https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260825-bond_overflow-v5-0-7a800de133f1%40kylinos.cn
>
> I think the v2 code for the above should be fine.
Thanks, it looks like I was over‑thinking this.
I was considering a case with two slaves: one at 1 Gbit/s and another at
10 Gbit/s. Once traffic exceeds 1 Gbit/s, `compute_gap` may still select the
1 Gbit/s slave, which leads to overload.
In any case, this behavior is still better than no balancing at all. I will fix this.
Thanks
Hangbin
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap()
2026-08-27 20:56 ` David Laight
@ 2026-08-28 9:19 ` Nikolay Aleksandrov
0 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-08-28 9:19 UTC (permalink / raw)
To: David Laight
Cc: Hangbin Liu, Jay Vosburgh, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kernel, Hangbin Liu
On 27/08/2026 23:56, David Laight wrote:
> On Thu, 27 Aug 2026 22:09:04 +0300
> Nikolay Aleksandrov <razor@blackwall.org> wrote:
>
>> On 27/08/2026 21:20, David Laight wrote:
>>> On Tue, 25 Aug 2026 09:01:30 +0800
>>> Hangbin Liu <hangbin.liu@linux.dev> wrote:
>>>
>>>> From: Hangbin Liu <liuhangbin@kylinos.cn>
>>>>
>>>> The TLB load-tracking fields tx_bytes, load_history, load, and
>>>> unbalanced_load are all u32. At sustained throughput above ~3.2 Gbit/s
>>>> over the 10-second rebalance interval the byte counters wrap, causing
>>>> compute_gap() to produce incorrect gap values and mis-select slaves.
>>>> Such speeds are common on modern NICs under heavy traffic.
>>>>
>>>> Widen these fields to u64. Use u64_stats_sync to protect the per-cpu
>>>> unbalanced_load_stats against tearing on 32-bit architectures, and
>>>> div_u64() for the 64-bit divisions. The tx_bytes and load_history
>>>> are protected in spin_lock. Also protect the slave load writing in
>>>> bond_alb_monitor() with spin_lock in case of tear on 32-bit.
>>>
>>> How about changing the rebalance interval to either 8 or 16 seconds
>>> to avoid the expensive divide?
>>> Alternatively multiply the other side of the comparisons by the interval,
>>> replacing the expensive divide with a cheap multiply.
>>>
>>> David
>>>
>>
>> How is that relevant to these patches?
>> And how does that help at all if today that is done at about 10 second interval?
>
> The 10 seconds is almost certainly completely arbitrary.
> It is relevant because the 64bit divide is significantly expensive on 32bit.
>
> David
>
Yeah, that is clear. But currently that recalculation is done once every 10
seconds, such optimizations will be noise. Regardless of that, these changes
are unrelated to the problem he is fixing with the set.
>>
>>>>
>>>> Rework compute_gap() to use s64 arithmetic throughout. Return LLONG_MIN
>>>> when the speed is unknown.
>>>>
>>>> Detected by AI code review.
>>>>
>>>> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
>>>> Signed-off-by: Hangbin Liu <liuhangbin@kylinos.cn>
>>>> ---
>>>> drivers/net/bonding/bond_alb.c | 52 ++++++++++++++++++++++++++++++------------
>>>> include/net/bond_alb.h | 11 +++++----
>>>> 2 files changed, 44 insertions(+), 19 deletions(-)
>>>>
>>>> diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c
>>>> index 0afed2c39231..9a43a1f47893 100644
>>>> --- a/drivers/net/bonding/bond_alb.c
>>>> +++ b/drivers/net/bonding/bond_alb.c
>>>> @@ -6,6 +6,7 @@
>>>> #include <linux/skbuff.h>
>>>> #include <linux/netdevice.h>
>>>> #include <linux/etherdevice.h>
>>>> +#include <linux/ethtool.h>
>>>> #include <linux/pkt_sched.h>
>>>> #include <linux/spinlock.h>
>>>> #include <linux/slab.h>
>>>> @@ -74,8 +75,8 @@ static inline u8 _simple_hash(const u8 *hash_start, int hash_size)
>>>> static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load)
>>>> {
>>>> if (save_load) {
>>>> - entry->load_history = 1 + entry->tx_bytes /
>>>> - BOND_TLB_REBALANCE_INTERVAL;
>>>> + entry->load_history = 1 + div_u64(entry->tx_bytes,
>>>> + BOND_TLB_REBALANCE_INTERVAL);
>>>> entry->tx_bytes = 0;
>>>> }
>>>>
>>>> @@ -133,7 +134,7 @@ static int tlb_initialize(struct bonding *bond)
>>>> if (!new_hashtbl)
>>>> return -ENOMEM;
>>>>
>>>> - bond_info->unbalanced_load = alloc_percpu(struct unbalanced_load_stats);
>>>> + bond_info->unbalanced_load = netdev_alloc_pcpu_stats(struct unbalanced_load_stats);
>>>> if (!bond_info->unbalanced_load)
>>>> goto out;
>>>>
>>>> @@ -170,8 +171,14 @@ static void tlb_deinitialize(struct bonding *bond)
>>>>
>>>> static long long compute_gap(struct slave *slave)
>>>> {
>>>> - return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */
>>>> - (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
>>>> + u32 raw_speed = READ_ONCE(slave->speed);
>>>> +
>>>> + /* It's meaningless to compare gap on unknown speed NIC */
>>>> + if (raw_speed == (u32)SPEED_UNKNOWN)
>>>> + return LLONG_MIN;
>>>> +
>>>> + return ((s64)raw_speed << 20) - /* Convert to bits per sec */
>>>> + ((s64)SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
>>>> }
>>>>
>>>> static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
>>>> @@ -188,7 +195,7 @@ static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
>>>> if (bond_slave_can_tx(slave)) {
>>>> long long gap = compute_gap(slave);
>>>>
>>>> - if (max_gap < gap) {
>>>> + if (!least_loaded || max_gap < gap) {
>>>> least_loaded = slave;
>>>> max_gap = gap;
>>>> }
>>>> @@ -1354,8 +1361,14 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond,
>>>> if (!tx_slave) {
>>>> /* unbalanced or unassigned, send through primary */
>>>> tx_slave = rcu_dereference(bond->curr_active_slave);
>>>> - if (bond->params.tlb_dynamic_lb)
>>>> - this_cpu_add(bond_info->unbalanced_load->tx_bytes, skb->len);
>>>> + if (bond->params.tlb_dynamic_lb) {
>>>> + struct unbalanced_load_stats *pcpu_load;
>>>> +
>>>> + pcpu_load = this_cpu_ptr(bond_info->unbalanced_load);
>>>> + u64_stats_update_begin(&pcpu_load->syncp);
>>>> + u64_stats_add(&pcpu_load->tx_bytes, skb->len);
>>>> + u64_stats_update_end(&pcpu_load->syncp);
>>>> + }
>>>> }
>>>>
>>>> if (tx_slave && bond_slave_can_tx(tx_slave)) {
>>>> @@ -1539,21 +1552,27 @@ netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
>>>> return bond_do_alb_xmit(skb, bond, tx_slave);
>>>> }
>>>>
>>>> -static u32 reset_unbalanced_load(struct alb_bond_info *bond_info)
>>>> +static u64 reset_unbalanced_load(struct alb_bond_info *bond_info)
>>>> {
>>>> + u64 delta, tx_bytes, total_bytes = 0;
>>>> struct unbalanced_load_stats *p;
>>>> - u32 delta, total_bytes = 0;
>>>> + unsigned int start;
>>>> int i;
>>>>
>>>> for_each_possible_cpu(i) {
>>>> p = per_cpu_ptr(bond_info->unbalanced_load, i);
>>>> - total_bytes += READ_ONCE(p->tx_bytes);
>>>> + do {
>>>> + start = u64_stats_fetch_begin(&p->syncp);
>>>> + tx_bytes = u64_stats_read(&p->tx_bytes);
>>>> + } while (u64_stats_fetch_retry(&p->syncp, start));
>>>> +
>>>> + total_bytes += tx_bytes;
>>>> }
>>>>
>>>> delta = total_bytes - bond_info->prev_total_unbalanced;
>>>> bond_info->prev_total_unbalanced = total_bytes;
>>>>
>>>> - return delta / BOND_TLB_REBALANCE_INTERVAL;
>>>> + return div_u64(delta, BOND_TLB_REBALANCE_INTERVAL);
>>>> }
>>>>
>>>> void bond_alb_monitor(struct work_struct *work)
>>>> @@ -1597,8 +1616,13 @@ void bond_alb_monitor(struct work_struct *work)
>>>> if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) {
>>>> bond_for_each_slave_rcu(bond, slave, iter) {
>>>> tlb_clear_slave(bond, slave, 1);
>>>> - if (slave == rcu_access_pointer(bond->curr_active_slave))
>>>> - SLAVE_TLB_INFO(slave).load = reset_unbalanced_load(bond_info);
>>>> + if (slave == rcu_access_pointer(bond->curr_active_slave)) {
>>>> + u64 new_load = reset_unbalanced_load(bond_info);
>>>> +
>>>> + spin_lock_bh(&bond->mode_lock);
>>>> + SLAVE_TLB_INFO(slave).load = new_load;
>>>> + spin_unlock_bh(&bond->mode_lock);
>>>> + }
>>>> }
>>>> atomic_set(&bond_info->tx_rebalance_counter, 0);
>>>> }
>>>> diff --git a/include/net/bond_alb.h b/include/net/bond_alb.h
>>>> index 6fb09b4fc7e2..32f1981033e4 100644
>>>> --- a/include/net/bond_alb.h
>>>> +++ b/include/net/bond_alb.h
>>>> @@ -57,12 +57,12 @@ struct tlb_client_info {
>>>> * packets to a Client that the Hash function
>>>> * gave this entry index.
>>>> */
>>>> - u32 tx_bytes; /* Each Client accumulates the BytesTx that
>>>> + u64 tx_bytes; /* Each Client accumulates the BytesTx that
>>>> * were transmitted to it, and after each
>>>> * CallBack the LoadHistory is divided
>>>> * by the balance interval
>>>> */
>>>> - u32 load_history; /* This field contains the amount of Bytes
>>>> + u64 load_history; /* This field contains the amount of Bytes
>>>> * that were transmitted to this client by
>>>> * the server on the previous balance
>>>> * interval in Bps.
>>>> @@ -118,19 +118,20 @@ struct tlb_slave_info {
>>>> * are the entries that were assigned to use this
>>>> * slave for transmit.
>>>> */
>>>> - u32 load; /* Each slave sums the loadHistory of all clients
>>>> + u64 load; /* Each slave sums the loadHistory of all clients
>>>> * assigned to it
>>>> */
>>>> };
>>>>
>>>> struct unbalanced_load_stats {
>>>> - u32 tx_bytes;
>>>> + u64_stats_t tx_bytes;
>>>> + struct u64_stats_sync syncp;
>>>> };
>>>>
>>>> struct alb_bond_info {
>>>> struct tlb_client_info *tx_hashtbl; /* Dynamically allocated */
>>>> struct unbalanced_load_stats __percpu *unbalanced_load;
>>>> - u32 prev_total_unbalanced;
>>>> + u64 prev_total_unbalanced;
>>>> atomic_t tx_rebalance_counter;
>>>> int lp_counter;
>>>> /* -------- rlb parameters -------- */
>>>>
>>>
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-28 9:19 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 1:01 [PATCH net v5 0/2] bonding: fix TLB load-tracking overflow on high-speed NICs Hangbin Liu
2026-08-25 1:01 ` [PATCH net v5 1/2] bonding: convert unbalanced_load to per-cpu state Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
2026-08-25 1:01 ` [PATCH net v5 2/2] bonding: fix u32 overflow in compute_gap() Hangbin Liu
2026-08-26 7:34 ` Nikolay Aleksandrov
2026-08-27 13:42 ` Paolo Abeni
2026-08-28 1:28 ` Hangbin Liu
2026-08-27 18:20 ` David Laight
2026-08-27 19:09 ` Nikolay Aleksandrov
2026-08-27 20:56 ` David Laight
2026-08-28 9:19 ` Nikolay Aleksandrov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox