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

Yizhou Zhao <[email protected]> Sat, 11 Jul 2026 22:09:39 +0800
Newsgroups org.kernel.vger.lvs-devel,org.kernel.vger.linux-kernel,org.kernel.vger.netdev,org.kernel.vger.netfilter-devel,org.kernel.vger.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:
>=20
>=20
> Hello,
>=20
> On Wed, 8 Jul 2026, Yizhou Zhao wrote:
>=20
>>> On Jul 8, 2026, at 03:18, Julian Anastasov <[email protected]> wrote:
>>>=20
>>> On Tue, 7 Jul 2026, Yizhou Zhao wrote:
>>>=20
>>=20
>> We have posted a v2 patch at:
>> =
https://lore.kernel.org/netfilter-devel/20260708060454.20534-1-zhaoyz24@ma=
ils.tsinghua.edu.cn/
>>=20
>> The v2 patch updates the commit message with more conservative
>> wording, and fixes the checkpatch logical-continuation warnings.
>=20
> After looking again at the code, I think we can
> do it in different way:
>=20
> - 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=20
> include/net/ip_vs.h, see below...
>=20
> - IP_VS_DEST_F_AVAILABLE is changed only under service_mutex,
> so we can keep its usage
>=20
> - 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):
>=20
> - test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
> - set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
>=20
> Sometimes if (test_bit()) clear_bit() can avoid
> full memory barrier in ip_vs_dest_update_overload()
>=20
> - clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
> test_bit() guard can help here too
>=20
> 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:
>=20
> 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);
> }
>=20
> +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);
> }
>=20
> +__always_inline void ip_vs_dest_update_overload(struct ip_vs_dest =
*dest)
> +{
> + int conns, l, u;
> +
> + u =3D READ_ONCE(dest->u_threshold);
> + if (!u)
> + goto unset;
> + conns =3D ip_vs_dest_totalconns(dest);
> + if (conns >=3D u) {
> + dest->flags |=3D IP_VS_DEST_F_OVERLOAD;
> + return;
> + }
> + l =3D READ_ONCE(dest->l_threshold) ? : (u * 3 / 4);
> + if (conns >=3D 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 =3D u * 3 / 4;
if (conns >=3D l && l)
return;

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

l =3D u - u / 4;

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

> +unset:
> + dest->flags &=3D ~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);
> }
>=20
> - if (dest->u_threshold !=3D 0 &&
> -    ip_vs_dest_totalconns(dest) >=3D dest->u_threshold)
> - dest->flags |=3D IP_VS_DEST_F_OVERLOAD;
> + ip_vs_dest_update_overload(dest);
> }
>=20
>=20
> @@ -1257,16 +1275,8 @@ static inline void ip_vs_unbind_dest(struct =
ip_vs_conn *cp)
> atomic_dec(&dest->persistconns);
> }
>=20
> - if (dest->l_threshold !=3D 0) {
> - if (ip_vs_dest_totalconns(dest) < dest->l_threshold)
> - dest->flags &=3D ~IP_VS_DEST_F_OVERLOAD;
> - } else if (dest->u_threshold !=3D 0) {
> - if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3)
> - dest->flags &=3D ~IP_VS_DEST_F_OVERLOAD;
> - } else {
> - if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> - dest->flags &=3D ~IP_VS_DEST_F_OVERLOAD;
> - }
> + if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> + ip_vs_dest_update_overload(dest);
>=20
> 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;
>=20
> /* We cannot modify an address and change the address family */
> BUG_ON(!add && udest->af !=3D 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 |=3D IP_VS_DEST_F_AVAILABLE;
>=20
> - if (udest->u_threshold =3D=3D 0 || udest->u_threshold > =
dest->u_threshold)
> - dest->flags &=3D ~IP_VS_DEST_F_OVERLOAD;
> - dest->u_threshold =3D udest->u_threshold;
> - dest->l_threshold =3D udest->l_threshold;
> + upd_thresh =3D READ_ONCE(dest->u_threshold) !=3D udest->u_threshold =
||
> +     READ_ONCE(dest->l_threshold) !=3D 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);
>=20
> dest->af =3D udest->af;
>=20
> @@ -3667,8 +3670,8 @@ __ip_vs_get_dest_entries(struct netns_ipvs =
*ipvs, const struct ip_vs_get_dests *
> entry.port =3D dest->port;
> entry.conn_flags =3D atomic_read(&dest->conn_flags);
> entry.weight =3D atomic_read(&dest->weight);
> - entry.u_threshold =3D dest->u_threshold;
> - entry.l_threshold =3D dest->l_threshold;
> + entry.u_threshold =3D READ_ONCE(dest->u_threshold);
> + entry.l_threshold =3D READ_ONCE(dest->l_threshold);
> entry.activeconns =3D atomic_read(&dest->activeconns);
> entry.inactconns =3D atomic_read(&dest->inactconns);
> entry.persistconns =3D 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,
>=20
> Regards
>=20
> --
> 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