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


Reply via email to