From: Julian Anastasov <[email protected]>
The upper/lower connection thresholds for dest can be changed,
so use ip_vs_dest_update_overload() to properly update the
dest overload flag.
The thresholds were not limited, fit them in the 0 .. INT_MAX
range as already done in ipvsadm.
As the thresholds are also read when connections are created
and expired, use WRITE_ONCE/READ_ONCE to access them.
As the lower threshold is optional, use (u - (u >> 2)) to
calculate the 75% default value based on the upper threshold
by preserving the integer rounding, as suggested by Yizhou Zhao.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Julian Anastasov <[email protected]>
---
include/net/ip_vs.h | 2 ++
net/netfilter/ipvs/ip_vs_conn.c | 43 +++++++++++++++++++++++----------
net/netfilter/ipvs/ip_vs_ctl.c | 26 ++++++++++++++------
3 files changed, 50 insertions(+), 21 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 417ff51f62fc..3fc864a320fb 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1907,6 +1907,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 6ed2622363f0..fa3fbd597f3f 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -991,6 +991,33 @@ static inline int ip_vs_dest_totalconns(struct ip_vs_dest
*dest)
+ atomic_read(&dest->inactconns);
}
+/* Update overload flag based on number of dest conns and lower/upper
+ * connection thresholds:
+ * - conns reach u_threshold and exceed it: set the flag
+ * - conns go below l_threshold (or 75% of u_threshold): clear the flag
+ */
+__always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
+{
+ int conns;
+ u32 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;
+ }
+ /* Low threshold defaults to 75% of upper threshold */
+ l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2));
+ if (conns >= l)
+ return;
+
+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.
@@ -1053,9 +1080,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);
}
@@ -1149,16 +1174,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..62f73d892f97 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -1370,10 +1370,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;
+ if (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);
+ ip_vs_dest_update_overload(dest);
+ }
dest->af = udest->af;
@@ -1486,6 +1488,9 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct
ip_vs_dest_user_kern *udest)
return -ERANGE;
}
+ if (udest->u_threshold > INT_MAX)
+ return -EINVAL;
+
if (udest->tun_type == IP_VS_CONN_F_TUNNEL_TYPE_GUE) {
if (udest->tun_port == 0) {
pr_err("%s(): tunnel port is zero\n", __func__);
@@ -1559,6 +1564,9 @@ ip_vs_edit_dest(struct ip_vs_service *svc, struct
ip_vs_dest_user_kern *udest)
return -ERANGE;
}
+ if (udest->u_threshold > INT_MAX)
+ return -EINVAL;
+
if (udest->tun_type == IP_VS_CONN_F_TUNNEL_TYPE_GUE) {
if (udest->tun_port == 0) {
pr_err("%s(): tunnel port is zero\n", __func__);
@@ -3667,8 +3675,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 +4285,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,
--
2.34.1