Re: [PATCH nf] ipvs: make destination flags atomic

Yizhou Zhao <[email protected]>
Newsgroups gmane.comp.linux.lvs.devel,gmane.linux.network,gmane.linux.kernel,gmane.comp.security.firewalls.netfilter.devel,gmane.linux.kernel.stable
Message-ID <[email protected]>
Hello Julian,

Thank you for the detailed proposal. Yes, I am happy to handle the
follow-up conversion of the overload flag to bitops.


> On Jul 8, 2026, at 23:53, Julian Anastasov <[email protected]> wrote:
> 
> 
> Hello,
> 
> On Wed, 8 Jul 2026, Yizhou Zhao wrote:
> 
>>> On Jul 8, 2026, at 03:18, Julian Anastasov <[email protected]> wrote:
>>> 
>>> On Tue, 7 Jul 2026, Yizhou Zhao wrote:
>>> 
>> 
>> We have posted a v2 patch at:
>> https://lore.kernel.org/netfilter-devel/[email protected]/
>> 
>> The v2 patch updates the commit message with more conservative
>> wording, and fixes the checkpatch logical-continuation warnings.
> 
> After looking again at the code, I think we can
> do it in different way:
> 
> - IP_VS_DEST_F_AVAILABLE and IP_VS_DEST_F_OVERLOAD are defined
> in include/uapi/linux/ip_vs.h but we never export them to user
> space. So, we are free to change them. We can move them to 
> include/net/ip_vs.h, see below...
> 
> - IP_VS_DEST_F_AVAILABLE is changed only under service_mutex,
> so we can keep its usage
> 
> - IP_VS_DEST_F_OVERLOAD needs different access methods.
> We can add 'unsigned long flags2;', may be after l_threshold.
> And to switch to such usage (F_OVERLOAD -> FL_OVERLOAD):
> 
> - test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
> - set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
> 
> Sometimes if (test_bit()) clear_bit() can avoid
> full memory barrier in ip_vs_dest_update_overload()
> 
> - clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
> test_bit() guard can help here too
> 
> As there are other races involved, something like
> this can be a starting point for such change. It tries harder
> to update the overload flag on dest edit/add but it does not
> include the proposed bitops:
> 
> diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
> index 49297fec448a..b34631270e24 100644
> --- a/include/net/ip_vs.h
> +++ b/include/net/ip_vs.h
> @@ -1906,6 +1906,8 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest)
> kfree(dest);
> }
> 
> +void ip_vs_dest_update_overload(struct ip_vs_dest *dest);
> +
> /* IPVS sync daemon data and function prototypes
>  * (from ip_vs_sync.c)
>  */
> diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
> index d19caf66afeb..3fd221996e6e 100644
> --- a/net/netfilter/ipvs/ip_vs_conn.c
> +++ b/net/netfilter/ipvs/ip_vs_conn.c
> @@ -1087,6 +1087,26 @@ static inline int ip_vs_dest_totalconns(struct ip_vs_dest *dest)
> + atomic_read(&dest->inactconns);
> }
> 
> +__always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
> +{
> + int conns, l, u;
> +
> + u = READ_ONCE(dest->u_threshold);
> + if (!u)
> + goto unset;
> + conns = ip_vs_dest_totalconns(dest);
> + if (conns >= u) {
> + dest->flags |= IP_VS_DEST_F_OVERLOAD;
> + return;
> + }
> + l = READ_ONCE(dest->l_threshold) ? : (u * 3 / 4);
> + if (conns >= l && l)
> + return;
> +

I noticed one integer-rounding detail in the proposed helper. The
existing default lower-threshold check:

ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3

clears OVERLOAD when conns is below ceil(3 * u / 4), assuming the
multiplications do not overflow. In the proposed helper:

l = u * 3 / 4;
if (conns >= l && l)
return;

the division rounds down, so the boundary is different when u is not a
multiple of four. For example, with u == 2 and conns == 1, the existing
code clears OVERLOAD while the helper keeps it set. Would using

l = u - u / 4;

for the default lower threshold preserve the existing behavior while
also avoiding the multiplication overflow?

> +unset:
> + dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> +}
> +
> /*
>  * Bind a connection entry with a virtual service destination
>  * Called just after a new connection entry is created.
> @@ -1161,9 +1181,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
> atomic_inc(&dest->persistconns);
> }
> 
> - if (dest->u_threshold != 0 &&
> -    ip_vs_dest_totalconns(dest) >= dest->u_threshold)
> - dest->flags |= IP_VS_DEST_F_OVERLOAD;
> + ip_vs_dest_update_overload(dest);
> }
> 
> 
> @@ -1257,16 +1275,8 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
> atomic_dec(&dest->persistconns);
> }
> 
> - if (dest->l_threshold != 0) {
> - if (ip_vs_dest_totalconns(dest) < dest->l_threshold)
> - dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> - } else if (dest->u_threshold != 0) {
> - if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3)
> - dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> - } else {
> - if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> - dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> - }
> + if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> + ip_vs_dest_update_overload(dest);
> 
> ip_vs_dest_put(dest);
> }
> diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
> index bcf40b8c41cf..2871116e46ec 100644
> --- a/net/netfilter/ipvs/ip_vs_ctl.c
> +++ b/net/netfilter/ipvs/ip_vs_ctl.c
> @@ -1315,6 +1315,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
> struct ip_vs_service *old_svc;
> struct ip_vs_scheduler *sched;
> int conn_flags;
> + bool upd_thresh;
> 
> /* We cannot modify an address and change the address family */
> BUG_ON(!add && udest->af != dest->af);
> @@ -1370,10 +1371,12 @@ __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;
> 
> - if (udest->u_threshold == 0 || udest->u_threshold > dest->u_threshold)
> - dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> - dest->u_threshold = udest->u_threshold;
> - dest->l_threshold = udest->l_threshold;
> + upd_thresh = READ_ONCE(dest->u_threshold) != udest->u_threshold ||
> +     READ_ONCE(dest->l_threshold) != udest->l_threshold;
> + WRITE_ONCE(dest->u_threshold, udest->u_threshold);
> + WRITE_ONCE(dest->l_threshold, udest->l_threshold);
> + if (upd_thresh)
> + ip_vs_dest_update_overload(dest);
> 
> dest->af = udest->af;
> 
> @@ -3667,8 +3670,8 @@ __ip_vs_get_dest_entries(struct netns_ipvs *ipvs, const struct ip_vs_get_dests *
> entry.port = dest->port;
> entry.conn_flags = atomic_read(&dest->conn_flags);
> entry.weight = atomic_read(&dest->weight);
> - entry.u_threshold = dest->u_threshold;
> - entry.l_threshold = dest->l_threshold;
> + entry.u_threshold = READ_ONCE(dest->u_threshold);
> + entry.l_threshold = READ_ONCE(dest->l_threshold);
> entry.activeconns = atomic_read(&dest->activeconns);
> entry.inactconns = atomic_read(&dest->inactconns);
> entry.persistconns = atomic_read(&dest->persistconns);
> @@ -4277,8 +4280,10 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest)
> dest->tun_port) ||
>    nla_put_u16(skb, IPVS_DEST_ATTR_TUN_FLAGS,
> dest->tun_flags) ||
> -    nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH, dest->u_threshold) ||
> -    nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH, dest->l_threshold) ||
> +    nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH,
> + READ_ONCE(dest->u_threshold)) ||
> +    nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH,
> + READ_ONCE(dest->l_threshold)) ||
>    nla_put_u32(skb, IPVS_DEST_ATTR_ACTIVE_CONNS,
> atomic_read(&dest->activeconns)) ||
>    nla_put_u32(skb, IPVS_DEST_ATTR_INACT_CONNS,
> 
> Regards
> 
> --
> Julian Anastasov <[email protected]>

Please go ahead with the ip_vs_dest_update_overload() patch. I will base
the follow-up on the posted version and check the resulting struct
ip_vs_dest layout as Vadim suggested.

Regards,
Yizhou
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.