[PATCH nf] ipvs: make destination flags atomic
From: Yizhou Zhao <hidden>
Date: 2026-07-07 08:57:50
Also in:
lkml, lvs-devel, netfilter-devel, stable
Subsystem:
ipvs, netfilter, networking [general], networking [ipv4/ipv6], the rest · Maintainers:
Simon Horman, Julian Anastasov, Pablo Neira Ayuso, Florian Westphal, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
is_unavailable() in the SH scheduler reads dest->flags from the packet
scheduling path while holding only the RCU read lock. The same word is
updated by read-modify-write operations from connection accounting and
destination update paths, for example ip_vs_bind_dest(),
ip_vs_unbind_dest(), and __ip_vs_update_dest().
The RCU read lock only protects the destination lifetime; it does not
serialize accesses to dest->flags. A racing plain load or RMW update can
therefore observe stale state or lose an AVAILABLE/OVERLOAD bit update,
which can make the scheduler choose an overloaded destination or report no
available destination even though one should be usable.
KCSAN reports the race with a standard IPVS configuration using the SH
scheduler and a destination with u_threshold set:
BUG: KCSAN: data-race in __ip_vs_update_dest / ip_vs_sh_schedule
write to ... of 4 bytes by task ipvs_cfg:
__ip_vs_update_dest
ip_vs_edit_dest
do_ip_vs_set_ctl
__x64_sys_setsockopt
read to ... of 4 bytes by task ipvs_churn:
ip_vs_sh_schedule
ip_vs_schedule
tcp_conn_schedule
ip_vs_in_hook
tcp_connect
__x64_sys_connect
value changed: 0x00000003 -> 0x00000001
Convert dest->flags to atomic_t and use atomic_read(), atomic_or(), and
atomic_and() for all destination flag tests and updates. This preserves
the existing 32-bit field size while making the flag updates atomic RMW
operations and making readers use atomic accesses. Valid minimum-sized
IPVS configuration and scheduling paths are unchanged; only the
synchronization of the destination status flags changes.
Fixes: eba3b5a78799d ("ipvs: SH fallback and L4 hashing")
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
Signed-off-by: Yizhou Zhao <redacted>
---diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 49297fec448a..bb969738ed73 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h@@ -972,7 +972,7 @@ struct ip_vs_dest { u16 af; /* address family */ __be16 port; /* port number of the server */ union nf_inet_addr addr; /* IP address of the server */ - volatile unsigned int flags; /* dest status flags */ + atomic_t flags; /* dest status flags */ atomic_t conn_flags; /* flags to copy to conn */ atomic_t weight; /* server weight */ atomic_t last_weight; /* server latest weight */
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index cb36641f8d1c..539f603f38b7 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c@@ -1055,7 +1055,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) if (dest->u_threshold != 0 && ip_vs_dest_totalconns(dest) >= dest->u_threshold) - dest->flags |= IP_VS_DEST_F_OVERLOAD; + atomic_or(IP_VS_DEST_F_OVERLOAD, &dest->flags); }
@@ -1151,13 +1151,13 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp) if (dest->l_threshold != 0) { if (ip_vs_dest_totalconns(dest) < dest->l_threshold) - dest->flags &= ~IP_VS_DEST_F_OVERLOAD; + atomic_and(~IP_VS_DEST_F_OVERLOAD, &dest->flags); } else if (dest->u_threshold != 0) { if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3) - dest->flags &= ~IP_VS_DEST_F_OVERLOAD; + atomic_and(~IP_VS_DEST_F_OVERLOAD, &dest->flags); } else { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) - dest->flags &= ~IP_VS_DEST_F_OVERLOAD; + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) + atomic_and(~IP_VS_DEST_F_OVERLOAD, &dest->flags); } ip_vs_dest_put(dest);
@@ -1188,7 +1188,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) || + !(atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE) || expire_quiescent_template(ipvs, dest) || (cdest && (dest != cdest))) { IP_VS_DBG_BUF(9, "check_template: dest not available for "
@@ -1929,7 +1929,7 @@ void ip_vs_expire_nodest_conn_flush(struct netns_ipvs *ipvs) cp = ip_vs_hn0_to_conn(hn); resched_score++; dest = cp->dest; - if (!dest || (dest->flags & IP_VS_DEST_F_AVAILABLE)) + if (!dest || (atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE)) continue; if (atomic_read(&cp->n_control))
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index d40b404c1bf6..ca778937facf 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c@@ -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 && (atomic_read(&dest->flags) & IP_VS_DEST_F_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 && (atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE)) { struct ip_vs_cpu_stats *s; struct ip_vs_service *svc;
@@ -2204,7 +2204,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 && !(atomic_read(&cp->dest->flags) & IP_VS_DEST_F_AVAILABLE)) { /* the destination server is not available */ if (sysctl_expire_nodest_conn(ipvs)) { bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb);
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index bcf40b8c41cf..685b2675b6e0 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c@@ -1368,10 +1368,10 @@ __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; + atomic_or(IP_VS_DEST_F_AVAILABLE, &dest->flags); if (udest->u_threshold == 0 || udest->u_threshold > dest->u_threshold) - dest->flags &= ~IP_VS_DEST_F_OVERLOAD; + atomic_and(~IP_VS_DEST_F_OVERLOAD, &dest->flags); dest->u_threshold = udest->u_threshold; dest->l_threshold = udest->l_threshold;
@@ -1613,7 +1613,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; + atomic_and(~IP_VS_DEST_F_AVAILABLE, &dest->flags); spin_lock_bh(&dest->dst_lock); __ip_vs_dst_cache_reset(dest);
diff --git a/net/netfilter/ipvs/ip_vs_dh.c b/net/netfilter/ipvs/ip_vs_dh.c
index e1f62f6b25e2..82492e824f02 100644
--- a/net/netfilter/ipvs/ip_vs_dh.c
+++ b/net/netfilter/ipvs/ip_vs_dh.c@@ -201,7 +201,7 @@ static int ip_vs_dh_dest_changed(struct ip_vs_service *svc, */ static inline int is_overloaded(struct ip_vs_dest *dest) { - return dest->flags & IP_VS_DEST_F_OVERLOAD; + return atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD; }
@@ -219,10 +219,10 @@ 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) - || atomic_read(&dest->weight) <= 0 - || is_overloaded(dest)) { + if (!dest + || !(atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE) + || atomic_read(&dest->weight) <= 0 + || is_overloaded(dest)) { ip_vs_scheduler_err(svc, "no destination available"); return NULL; }
diff --git a/net/netfilter/ipvs/ip_vs_fo.c b/net/netfilter/ipvs/ip_vs_fo.c
index d657b47c6511..5231e518c07c 100644
--- a/net/netfilter/ipvs/ip_vs_fo.c
+++ b/net/netfilter/ipvs/ip_vs_fo.c@@ -29,7 +29,7 @@ ip_vs_fo_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, * Find virtual server with highest weight and send it traffic */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) > hw) { hweight = dest; hw = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index 15ccb2b2fa1f..d2eb5dda5b68 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c@@ -414,7 +414,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc) * new connection. */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; if (atomic_read(&dest->weight) > 0) { least = dest;
@@ -429,7 +429,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc) */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; doh = ip_vs_dest_conn_overhead(dest);
@@ -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 ((atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE) && atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc)) goto out; }
diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c
index c90ea897c3f7..48f02453a5be 100644
--- a/net/netfilter/ipvs/ip_vs_lblcr.c
+++ b/net/netfilter/ipvs/ip_vs_lblcr.c@@ -166,11 +166,11 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) /* select the first destination server, whose weight > 0 */ list_for_each_entry_rcu(e, &set->list, list) { least = e->dest; - if (least->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&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) + && (atomic_read(&least->flags) & IP_VS_DEST_F_AVAILABLE)) { loh = ip_vs_dest_conn_overhead(least); goto nextstage; }
@@ -181,13 +181,13 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) nextstage: list_for_each_entry_continue_rcu(e, &set->list, list) { dest = e->dest; - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; 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)) + && (atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE)) { least = dest; loh = doh; }
@@ -577,7 +577,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc) * new connection. */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; if (atomic_read(&dest->weight) > 0) {
@@ -593,7 +593,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc) */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_lc.c b/net/netfilter/ipvs/ip_vs_lc.c
index 38cc38c5d8bb..6acb3c904af5 100644
--- a/net/netfilter/ipvs/ip_vs_lc.c
+++ b/net/netfilter/ipvs/ip_vs_lc.c@@ -38,7 +38,7 @@ ip_vs_lc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if ((dest->flags & IP_VS_DEST_F_OVERLOAD) || + if ((atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) || atomic_read(&dest->weight) == 0) continue; doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_mh.c b/net/netfilter/ipvs/ip_vs_mh.c
index 020863047562..c322ed1754b7 100644
--- a/net/netfilter/ipvs/ip_vs_mh.c
+++ b/net/netfilter/ipvs/ip_vs_mh.c@@ -80,7 +80,7 @@ static inline void generate_hash_secret(hsiphash_key_t *hash1, static inline bool is_unavailable(struct ip_vs_dest *dest) { return atomic_read(&dest->weight) <= 0 || - dest->flags & IP_VS_DEST_F_OVERLOAD; + atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD; } /* Returns hash value for IPVS MH entry */
diff --git a/net/netfilter/ipvs/ip_vs_nq.c b/net/netfilter/ipvs/ip_vs_nq.c
index ada158c610ce..ffa4bfeb21d9 100644
--- a/net/netfilter/ipvs/ip_vs_nq.c
+++ b/net/netfilter/ipvs/ip_vs_nq.c@@ -72,7 +72,7 @@ ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD || + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD || !atomic_read(&dest->weight)) continue;
diff --git a/net/netfilter/ipvs/ip_vs_ovf.c b/net/netfilter/ipvs/ip_vs_ovf.c
index c5c67df80a0b..f7f17dddbb05 100644
--- a/net/netfilter/ipvs/ip_vs_ovf.c
+++ b/net/netfilter/ipvs/ip_vs_ovf.c@@ -33,7 +33,7 @@ ip_vs_ovf_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { w = atomic_read(&dest->weight); - if ((dest->flags & IP_VS_DEST_F_OVERLOAD) || + if ((atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) || atomic_read(&dest->activeconns) > w || w == 0) continue;
diff --git a/net/netfilter/ipvs/ip_vs_rr.c b/net/netfilter/ipvs/ip_vs_rr.c
index 4125ee561cdc..98453d205d6f 100644
--- a/net/netfilter/ipvs/ip_vs_rr.c
+++ b/net/netfilter/ipvs/ip_vs_rr.c@@ -66,7 +66,7 @@ ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) > 0) /* HIT */ goto out;
diff --git a/net/netfilter/ipvs/ip_vs_sed.c b/net/netfilter/ipvs/ip_vs_sed.c
index 245a323c84cd..0249062d1360 100644
--- a/net/netfilter/ipvs/ip_vs_sed.c
+++ b/net/netfilter/ipvs/ip_vs_sed.c@@ -75,7 +75,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) > 0) { least = dest; loh = ip_vs_sed_dest_overhead(least);
@@ -90,7 +90,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; doh = ip_vs_sed_dest_overhead(dest); if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_sh.c b/net/netfilter/ipvs/ip_vs_sh.c
index cd67066e3b26..343780b82c95 100644
--- a/net/netfilter/ipvs/ip_vs_sh.c
+++ b/net/netfilter/ipvs/ip_vs_sh.c@@ -73,7 +73,7 @@ struct ip_vs_sh_state { static inline bool is_unavailable(struct ip_vs_dest *dest) { return atomic_read(&dest->weight) <= 0 || - dest->flags & IP_VS_DEST_F_OVERLOAD; + atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD; } /*
diff --git a/net/netfilter/ipvs/ip_vs_twos.c b/net/netfilter/ipvs/ip_vs_twos.c
index dbb7f5fd4688..35fa4c6dc5cf 100644
--- a/net/netfilter/ipvs/ip_vs_twos.c
+++ b/net/netfilter/ipvs/ip_vs_twos.c@@ -52,7 +52,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc, /* Generate a random weight between [0,sum of all weights) */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD)) { + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD)) { weight = atomic_read(&dest->weight); if (weight > 0) { total_weight += weight;
@@ -75,7 +75,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc, /* Pick two weighted servers */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; weight = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_wlc.c b/net/netfilter/ipvs/ip_vs_wlc.c
index 9da445ca09a1..c2d09ac96fe8 100644
--- a/net/netfilter/ipvs/ip_vs_wlc.c
+++ b/net/netfilter/ipvs/ip_vs_wlc.c@@ -47,7 +47,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) > 0) { least = dest; loh = ip_vs_dest_conn_overhead(least);
@@ -62,7 +62,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) continue; doh = ip_vs_dest_conn_overhead(dest); if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_wrr.c b/net/netfilter/ipvs/ip_vs_wrr.c
index 2dcff1040da5..f21a75284971 100644
--- a/net/netfilter/ipvs/ip_vs_wrr.c
+++ b/net/netfilter/ipvs/ip_vs_wrr.c@@ -176,7 +176,7 @@ ip_vs_wrr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!(atomic_read(&dest->flags) & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) >= mark->cw) goto found; if (dest == stop)
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index ce542ed4b013..37b9671e4960 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c@@ -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) + atomic_read(&dest->flags) & IP_VS_DEST_F_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) + atomic_read(&dest->flags) & IP_VS_DEST_F_AVAILABLE) __ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie); else noref = 0;