Thread (5 messages) 5 messages, 2 authors, 8d ago

Re: [PATCH nf v3 2/2] ipvs: use bitops for destination overload state

From: Julian Anastasov <ja@ssi.bg>
Date: 2026-07-14 11:17:03
Also in: lkml, lvs-devel, netfilter-devel, stable

	Hi Yizhou,

On Mon, 13 Jul 2026, Yizhou Zhao wrote:
quoted hunk ↗ jump to hunk
IPVS destination schedulers read the overload state from packet processing
paths, while connection accounting and destination updates can change it
concurrently. IP_VS_DEST_F_OVERLOAD currently shares dest->flags with
IP_VS_DEST_F_AVAILABLE, so plain read-modify-write operations on the two
independent states can race and lose either update.

KCSAN reports the race with the SH scheduler and an upper connection
threshold configured:

  BUG: KCSAN: data-race in __ip_vs_update_dest / ip_vs_sh_schedule

IP_VS_DEST_F_AVAILABLE is changed under service_mutex. Keep it in the
existing flags word, but move the overload state to a separate unsigned
long and access it with bitops. Use test_bit() in scheduler paths and
set_bit()/clear_bit() in ip_vs_dest_update_overload(). This serializes the
overload bit accesses and prevents updates to the available and overload
states from clobbering each other.

The destination flags are not exposed by the IPVS sockopt or netlink
interfaces, so move their definitions out of the UAPI header. Place the
new overload word next to weight, which keeps the existing flags,
conn_flags and weight offsets unchanged. On x86-64 this grows struct
ip_vs_dest from 472 to 480 bytes.

test_bit() does not add reader-side ordering. Schedulers can still observe
stale destination state, as they could before this change; this does not
provide a fresh cross-field snapshot.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <redacted>
Reported-by: Yuxiang Yang <redacted>
Reported-by: Ao Wang <redacted>
Reported-by: Xuewei Feng <redacted>
Reported-by: Qi Li <redacted>
Reported-by: Ke Xu <redacted>
Assisted-by: Claude-Code:GLM-5.2
Suggested-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Yizhou Zhao <redacted>
---
 include/net/ip_vs.h              | 8 ++++++++
 include/uapi/linux/ip_vs.h       | 6 ------
 net/netfilter/ipvs/ip_vs_conn.c  | 7 ++++---
 net/netfilter/ipvs/ip_vs_dh.c    | 4 ++--
 net/netfilter/ipvs/ip_vs_fo.c    | 2 +-
 net/netfilter/ipvs/ip_vs_lblc.c  | 4 ++--
 net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++++----
 net/netfilter/ipvs/ip_vs_lc.c    | 2 +-
 net/netfilter/ipvs/ip_vs_mh.c    | 2 +-
 net/netfilter/ipvs/ip_vs_nq.c    | 2 +-
 net/netfilter/ipvs/ip_vs_ovf.c   | 2 +-
 net/netfilter/ipvs/ip_vs_rr.c    | 2 +-
 net/netfilter/ipvs/ip_vs_sed.c   | 4 ++--
 net/netfilter/ipvs/ip_vs_sh.c    | 2 +-
 net/netfilter/ipvs/ip_vs_twos.c  | 4 ++--
 net/netfilter/ipvs/ip_vs_wlc.c   | 4 ++--
 net/netfilter/ipvs/ip_vs_wrr.c   | 2 +-
 17 files changed, 34 insertions(+), 31 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 3fc864a320fb..5e8e55f82b04 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -36,6 +36,13 @@
 #define IP_VS_HDR_INVERSE	1
 #define IP_VS_HDR_ICMP		2
 
+/* Destination Server Flags */
+#define IP_VS_DEST_F_AVAILABLE	0x0001		/* server is available */
+
+enum {
+	IP_VS_DEST_FL_OVERLOAD,
+};
+
 /* conn_tab limits (as per Kconfig) */
 #define IP_VS_CONN_TAB_MIN_BITS	8
 #if BITS_PER_LONG > 32
@@ -976,6 +983,7 @@ struct ip_vs_dest {
 	volatile unsigned int	flags;		/* dest status flags */
	Sashiko has some comments that we should fix somehow:

https://sashiko.dev/#/patchset/cover.1783931964.git.zhaoyz24%40mails.tsinghua.edu.cn

	One option is IP_VS_DEST_F_AVAILABLE to become
IP_VS_DEST_CF_AVAILABLE (CF=Config Flag)
 	atomic_t		conn_flags;	/* flags to copy to conn */
 	atomic_t		weight;		/* server weight */
+	unsigned long		flags2;		/* dest status flags */
	unsigned long		cfg_flags;

	We then put IP_VS_DEST_CF_AVAILABLE in this new cache line
that most of the schedulers will not read until dest is selected.
DH even should not check the IP_VS_DEST_F_AVAILABLE flag,
only lblc/lblcr should use this flag.

	We can preserve IP_VS_DEST_F_OVERLOAD in 'flags',
even we may not need to use bitops if we start to use
spin_lock_bh(&dest->dst_lock), as this lock is already
present in the dest structure. See below...
 	atomic_t		last_weight;	/* server latest weight */
 	__u16			tun_type;	/* tunnel type */
 	__be16			tun_port;	/* tunnel port */
quoted hunk ↗ jump to hunk
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index fa3fbd597f3f..2591f4e143f8 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1006,7 +1006,7 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
	We can add new arg 'bool locked'. Also, we will
return false if caller should retry under lock.
It will happen when we change IP_VS_DEST_F_OVERLOAD and
require its changes to be synchronized with the
thresholds and the number of connections.
 		goto unset;
 	conns = ip_vs_dest_totalconns(dest);
 	if (conns >= u) {
-		dest->flags |= IP_VS_DEST_F_OVERLOAD;
+		set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);

	if (conns >= u) {
		if (!locked)
			return false;
		dest->flags |= IP_VS_DEST_F_OVERLOAD;
		return true;
	}
quoted hunk ↗ jump to hunk
 		return;
 	}
 	/* Low threshold defaults to 75% of upper threshold */
@@ -1015,7 +1015,8 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
 		return;
 
 unset:
-	dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
+	if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
+		clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
	if (dest->flags & IP_VS_DEST_F_OVERLOAD) {
		if (!locked)
			return false;
		dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
	}
	return true;
quoted hunk ↗ jump to hunk
 }
 
 /*
@@ -1174,7 +1175,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
 		atomic_dec(&dest->persistconns);
 	}
 
-	if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+	if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
 		ip_vs_dest_update_overload(dest);
	if (dest->flags & IP_VS_DEST_F_OVERLOAD) {
		if (!ip_vs_dest_update_overload(dest, false)) {
			spin_lock_bh(&dest->dst_lock);
			ip_vs_dest_update_overload(dest, true);
			spin_unlock_bh(&dest->dst_lock);
		}
	}

	In __ip_vs_update_dest() we will always use lock:

		spin_lock_bh(&dest->dst_lock);
		WRITE_ONCE(dest->u_threshold, udest->u_threshold);
		WRITE_ONCE(dest->l_threshold, udest->l_threshold);
		ip_vs_dest_update_overload(dest, true);
		spin_unlock_bh(&dest->dst_lock);

	The goal is to avoid the lock for the common case
when flag does not change. What do you think?

Regards

--
Julian Anastasov [off-list ref]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help