]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipvs: separate destination availability state
authorYizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Fri, 31 Jul 2026 14:27:43 +0000 (22:27 +0800)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 10 Aug 2026 18:23:35 +0000 (20:23 +0200)
IPVS configuration paths update destination availability while connection
accounting updates destination overload state. The two independent states
share dest->flags, so their read-modify-write updates can race and lose one
another.

Keep OVERLOAD in flags, where the preceding patch serializes its updates
with dst_lock, and move AVAILABLE to cflags. This keeps configuration-
controlled availability out of the scheduler hot cacheline until a
scheduler needs to check it. It also prevents availability updates from
clobbering overload state.

The destination status bits are not exposed through the IPVS sockopt or
netlink interfaces, so keep their definitions in the internal IPVS header.

Readers can still observe stale destination state; this does not provide a
cross-field snapshot.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Link: https://lore.kernel.org/all/8913381c-1e02-35c7-0ec4-61de5a12fd35@ssi.bg/
Assisted-by: Claude-Code:GLM-5.2
Suggested-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/ip_vs.h
include/uapi/linux/ip_vs.h
net/netfilter/ipvs/ip_vs_conn.c
net/netfilter/ipvs/ip_vs_core.c
net/netfilter/ipvs/ip_vs_ctl.c
net/netfilter/ipvs/ip_vs_dh.c
net/netfilter/ipvs/ip_vs_lblc.c
net/netfilter/ipvs/ip_vs_lblcr.c
net/netfilter/ipvs/ip_vs_xmit.c

index e99382930617a023d39aeffc1654ed54a2d27fd9..fc2ef5ef31a6918c6b13c6d9ff47d53fe68302df 100644 (file)
 #define IP_VS_HDR_INVERSE      1
 #define IP_VS_HDR_ICMP         2
 
+/* Destination Server Flags */
+#define IP_VS_DEST_F_OVERLOAD  0x0002          /* server is overloaded */
+
+/* Destination Server Config Flags */
+#define IP_VS_DEST_CF_AVAILABLE        0x0001          /* server is available */
+
 /* conn_tab limits (as per Kconfig) */
 #define IP_VS_CONN_TAB_MIN_BITS        8
 #if BITS_PER_LONG > 32
@@ -976,6 +982,7 @@ struct ip_vs_dest {
        volatile unsigned int   flags;          /* dest status flags */
        atomic_t                conn_flags;     /* flags to copy to conn */
        atomic_t                weight;         /* server weight */
+       unsigned long           cflags;         /* config flags */
        atomic_t                last_weight;    /* server latest weight */
        __u16                   tun_type;       /* tunnel type */
        __be16                  tun_port;       /* tunnel port */
index 1ed234e7f251362162ed72c75c3b3d9a35d96780..2c37c6ac7525a56caedfb20107edfbe0355261f1 100644 (file)
 #define IP_VS_SVC_F_SCHED_SH_FALLBACK  IP_VS_SVC_F_SCHED1 /* SH fallback */
 #define IP_VS_SVC_F_SCHED_SH_PORT      IP_VS_SVC_F_SCHED2 /* SH use port */
 
-/*
- *      Destination Server Flags
- */
-#define IP_VS_DEST_F_AVAILABLE 0x0001          /* server is available */
-#define IP_VS_DEST_F_OVERLOAD  0x0002          /* server is overloaded */
-
 /*
  *      IPVS sync daemon states
  */
index abf52a226feed57e0491563cfe4645a3076984ba..6fa3e1dc534c3e0e1fc0a0e6966b1f0c97b0074e 100644 (file)
@@ -1279,7 +1279,7 @@ int ip_vs_check_template(struct ip_vs_conn *ct, struct ip_vs_dest *cdest)
         * Checking the dest server status.
         */
        if ((dest == NULL) ||
-           !(dest->flags & IP_VS_DEST_F_AVAILABLE) ||
+           !(dest->cflags & IP_VS_DEST_CF_AVAILABLE) ||
            expire_quiescent_template(ipvs, dest) ||
            (cdest && (dest != cdest))) {
                IP_VS_DBG_BUF(9, "check_template: dest not available for "
@@ -2020,7 +2020,7 @@ repeat:
                        cp = ip_vs_hn0_to_conn(hn);
                        resched_score++;
                        dest = cp->dest;
-                       if (!dest || (dest->flags & IP_VS_DEST_F_AVAILABLE))
+                       if (!dest || (dest->cflags & IP_VS_DEST_CF_AVAILABLE))
                                continue;
 
                        if (atomic_read(&cp->n_control))
index 0bdaeb4ed61e4b0a3d4e475dfea903ecf1feb514..95af77b68851afa1c2e6b45ddd697f1209255a98 100644 (file)
@@ -302,7 +302,7 @@ ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
        struct ip_vs_dest *dest = cp->dest;
        struct netns_ipvs *ipvs = cp->ipvs;
 
-       if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+       if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
                struct ip_vs_cpu_stats *s;
                struct ip_vs_service *svc;
 
@@ -338,7 +338,7 @@ ip_vs_out_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
        struct ip_vs_dest *dest = cp->dest;
        struct netns_ipvs *ipvs = cp->ipvs;
 
-       if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+       if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
                struct ip_vs_cpu_stats *s;
                struct ip_vs_service *svc;
 
@@ -2210,7 +2210,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
        }
 
        /* Check the server status */
-       if (cp && cp->dest && !(cp->dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+       if (cp && cp->dest && !(cp->dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
                /* the destination server is not available */
                if (sysctl_expire_nodest_conn(ipvs)) {
                        bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb);
index 974773642af86fca92ea5b007936b9f91d38bc99..8f9a8e491ad6b4260c720083c2179ad38862aabb 100644 (file)
@@ -1402,7 +1402,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
        }
 
        /* set the dest status flags */
-       dest->flags |= IP_VS_DEST_F_AVAILABLE;
+       dest->cflags |= IP_VS_DEST_CF_AVAILABLE;
 
        if (READ_ONCE(dest->u_threshold) != udest->u_threshold ||
            READ_ONCE(dest->l_threshold) != udest->l_threshold) {
@@ -1662,7 +1662,7 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc,
                                struct ip_vs_dest *dest,
                                int svcupd)
 {
-       dest->flags &= ~IP_VS_DEST_F_AVAILABLE;
+       dest->cflags &= ~IP_VS_DEST_CF_AVAILABLE;
 
        spin_lock_bh(&dest->dst_lock);
        __ip_vs_dst_cache_reset(dest);
index e1f62f6b25e2111fcec757ae085d830d120c4254..43abed7a26a6a525a27262d65aef01a65d76f057 100644 (file)
@@ -219,8 +219,8 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
 
        s = (struct ip_vs_dh_state *) svc->sched_data;
        dest = ip_vs_dh_get(svc->af, s, &iph->daddr);
-       if (!dest
-           || !(dest->flags & IP_VS_DEST_F_AVAILABLE)
+       if (!dest ||
+           !(dest->cflags & IP_VS_DEST_CF_AVAILABLE)
            || atomic_read(&dest->weight) <= 0
            || is_overloaded(dest)) {
                ip_vs_scheduler_err(svc, "no destination available");
index 15ccb2b2fa1f4a01a8ed677fffa3834a10cc8924..693bcc82ccb77b49bfa28a7facac946f0223aba2 100644 (file)
@@ -502,7 +502,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
                 */
 
                dest = en->dest;
-               if ((dest->flags & IP_VS_DEST_F_AVAILABLE) &&
+               if ((dest->cflags & IP_VS_DEST_CF_AVAILABLE) &&
                    atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc))
                        goto out;
        }
index c90ea897c3f756d1b5805c06ff5f6984494cb29f..f53f05ceea36f02d6d1d435882484005829fe6de 100644 (file)
@@ -169,8 +169,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
                if (least->flags & IP_VS_DEST_F_OVERLOAD)
                        continue;
 
-               if ((atomic_read(&least->weight) > 0)
-                   && (least->flags & IP_VS_DEST_F_AVAILABLE)) {
+               if ((atomic_read(&least->weight) > 0) &&
+                   (least->cflags & IP_VS_DEST_CF_AVAILABLE)) {
                        loh = ip_vs_dest_conn_overhead(least);
                        goto nextstage;
                }
@@ -186,8 +186,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
 
                doh = ip_vs_dest_conn_overhead(dest);
                if (((__s64)loh * atomic_read(&dest->weight) >
-                    (__s64)doh * atomic_read(&least->weight))
-                   && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+                    (__s64)doh * atomic_read(&least->weight)) &&
+                   (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
                        least = dest;
                        loh = doh;
                }
index c4508f3f43dd37e658387b4a0dc7ae1424bdc6de..fc7403186394097d27197ddfa2029704478438f9 100644 (file)
@@ -351,7 +351,7 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
                         * stored in dest_trash.
                         */
                        if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)) &&
-                           dest->flags & IP_VS_DEST_F_AVAILABLE)
+                           dest->cflags & IP_VS_DEST_CF_AVAILABLE)
                                __ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
                        else
                                noref = 0;
@@ -530,7 +530,7 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
                         * stored in dest_trash.
                         */
                        if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)) &&
-                           dest->flags & IP_VS_DEST_F_AVAILABLE)
+                           dest->cflags & IP_VS_DEST_CF_AVAILABLE)
                                __ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
                        else
                                noref = 0;