[PATCH nf 2/2] ipvs: return the per-proto csum validations

Julian Anastasov <[email protected]> Mon, 27 Jul 2026 21:50:24 +0300
Newsgroups org.kernel.vger.lvs-devel,org.kernel.vger.netfilter-devel
Message-ID <[email protected]>
Sashiko notes that playing games with the skb dst and rt
flags instead of providing hooknum is not a good idea
when validating the checksums.

Also, skipping checksum validation for FORWARD packets
risk silent data corruption, even if the only user is
the FTP-CMD packets coming from the real server.

So, return the per-proto checksum validations, do not
use any hooknum-specific checks and convert the common
helper to be ICMP-only (ip_vs_checksum_icmp_check).

Fixes: e876b75b9020 ("ipvs: fix the checksum validations")
Link: https://sashiko.dev/#/patchset/20260722211420.153933-1-pablo%40netfilter.org
Signed-off-by: Julian Anastasov <[email protected]>
---
 include/net/ip_vs.h                   | 29 -----------------
 net/netfilter/ipvs/ip_vs_core.c       | 35 +++++++++++++++++---
 net/netfilter/ipvs/ip_vs_proto_sctp.c |  2 +-
 net/netfilter/ipvs/ip_vs_proto_tcp.c  | 40 ++++++++++++++++++++---
 net/netfilter/ipvs/ip_vs_proto_udp.c  | 47 +++++++++++++++++++++++----
 5 files changed, 107 insertions(+), 46 deletions(-)

diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index d80785485b92..b49c75929d03 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -25,9 +25,7 @@
 #include <linux/netfilter.h>		/* for union nf_inet_addr */
 #include <linux/ip.h>
 #include <linux/ipv6.h>			/* for struct ipv6hdr */
-#include <net/route.h>
 #include <net/ipv6.h>
-#include <net/ip6_fib.h>
 #if IS_ENABLED(CONFIG_NF_CONNTRACK)
 #include <net/netfilter/nf_conntrack.h>
 #endif
@@ -2097,33 +2095,6 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum)
 	return csum_partial(diff, sizeof(diff), oldsum);
 }
 
-static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af)
-{
-	/* Checksum unnecessary or already validated? */
-	if (skb_csum_unnecessary(skb))
-		return false;
-	/* LOCAL_OUT ? */
-	if (!skb->dev || skb->dev->flags & IFF_LOOPBACK)
-		return false;
-	/* !LOCAL_IN (FORWARD) ? */
-	if (af == AF_INET6) {
-		if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL))
-			return false;
-	} else {
-		if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL))
-			return false;
-	}
-	return true;
-}
-
-static inline bool ip_vs_checksum_common_check(struct sk_buff *skb,
-					       int offset, int proto, int af)
-{
-	if (!ip_vs_checksum_needed(skb, af))
-		return true;
-	return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af);
-}
-
 /* Forget current conntrack (unconfirmed) and attach notrack entry */
 static inline void ip_vs_notrack(struct sk_buff *skb)
 {
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 0bdaeb4ed61e..ec4a56205e98 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -872,6 +872,33 @@ static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
 	return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0));
 }
 
+static bool ip_vs_checksum_icmp_check(struct sk_buff *skb, int offset, int af)
+{
+	__wsum csum;
+
+	if (skb_csum_unnecessary(skb))
+		return true;
+	if (skb->ip_summed == CHECKSUM_NONE)
+		csum = skb_checksum(skb, offset, skb->len - offset, 0);
+	else if (skb->ip_summed == CHECKSUM_COMPLETE)
+		csum = csum_sub(skb->csum, skb_checksum(skb, 0, offset, 0));
+	else
+		return true;
+
+#ifdef CONFIG_IP_VS_IPV6
+	if (af == AF_INET6) {
+		if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+				    &ipv6_hdr(skb)->daddr,
+				    skb->len - offset,
+				    IPPROTO_ICMPV6, csum))
+			return false;
+	} else
+#endif
+		if (csum_fold(csum))
+			return false;
+	return true;
+}
+
 static inline enum ip_defrag_users ip_vs_defrag_user(unsigned int hooknum)
 {
 	if (NF_INET_LOCAL_IN == hooknum)
@@ -1026,7 +1053,6 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
 				struct ip_vs_iphdr *ciph,
 				unsigned int toff, unsigned int hooknum)
 {
-	int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
 	unsigned int verdict = NF_DROP;
 	unsigned int ctoff = ciph->len;
 	bool has_ports = false;
@@ -1035,7 +1061,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
 		goto after_nat;
 
 	/* Ensure the checksum is correct */
-	if (!ip_vs_checksum_common_check(skb, toff, iproto, af)) {
+	if (!ip_vs_checksum_icmp_check(skb, toff, af)) {
 		/* Failed checksum! */
 		IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
 			      IP_VS_DBG_ADDR(af, snet));
@@ -1896,7 +1922,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
 
 	/* Ensure the checksum is correct */
 	if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) &&
-	    !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) {
+	    !ip_vs_checksum_icmp_check(skb, ihl, AF_INET)) {
 		/* Failed checksum! */
 		IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
 			  &iph->saddr.ip);
@@ -2057,8 +2083,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
 
 	/* Ensure the checksum is correct */
 	if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ &&
-	    !ip_vs_checksum_common_check(skb, iph->len, IPPROTO_ICMPV6,
-					 AF_INET6)) {
+	    !ip_vs_checksum_icmp_check(skb, iph->len, AF_INET6)) {
 		/* Failed checksum! */
 		IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n",
 			  &iph->saddr);
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 3dbd3096e163..dd54a37dee29 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -193,7 +193,7 @@ sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
 	struct sctphdr *sh;
 	__le32 cmp, val;
 
-	if (!ip_vs_checksum_needed(skb, af))
+	if (skb_csum_unnecessary(skb))
 		return 1;
 	sh = (struct sctphdr *)(skb->data + sctphoff);
 	cmp = sh->checksum;
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 99a286fdc90c..5ac25a27569d 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -304,11 +304,43 @@ static int
 tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
 	       struct ip_vs_iphdr *iph)
 {
-	if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) {
-		IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
-				 "Failed checksum for");
-		return 0;
+	unsigned int tcphoff = iph->len;
+
+	if (skb_csum_unnecessary(skb))
+		return 1;
+	switch (skb->ip_summed) {
+	case CHECKSUM_NONE:
+		skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
+		fallthrough;
+	case CHECKSUM_COMPLETE:
+#ifdef CONFIG_IP_VS_IPV6
+		if (af == AF_INET6) {
+			if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+					    &ipv6_hdr(skb)->daddr,
+					    skb->len - tcphoff,
+					    IPPROTO_TCP,
+					    skb->csum)) {
+				IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+						 "Failed checksum for");
+				return 0;
+			}
+		} else
+#endif
+			if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
+					      ip_hdr(skb)->daddr,
+					      skb->len - tcphoff,
+					      ip_hdr(skb)->protocol,
+					      skb->csum)) {
+				IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+						 "Failed checksum for");
+				return 0;
+			}
+		break;
+	default:
+		/* No need to checksum. */
+		break;
 	}
+
 	return 1;
 }
 
diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c
index f32785682402..e86cfa85ad59 100644
--- a/net/netfilter/ipvs/ip_vs_proto_udp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_udp.c
@@ -300,18 +300,51 @@ static int
 udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
 	       struct ip_vs_iphdr *iph)
 {
+	unsigned int udphoff = iph->len;
 	struct udphdr _udph, *uh;
 
-	uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph);
+	uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph);
 	if (uh == NULL)
 		return 0;
 
-	if (!uh->check)
-		return 1;
-	if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) {
-		IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
-				 "Failed checksum for");
-		return 0;
+	if (uh->check != 0) {
+		if (skb_csum_unnecessary(skb))
+			return 1;
+		switch (skb->ip_summed) {
+		case CHECKSUM_NONE:
+			skb->csum = skb_checksum(skb, udphoff,
+						 skb->len - udphoff, 0);
+			fallthrough;
+		case CHECKSUM_COMPLETE:
+#ifdef CONFIG_IP_VS_IPV6
+			if (af == AF_INET6) {
+				if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+						    &ipv6_hdr(skb)->daddr,
+						    skb->len - udphoff,
+						    IPPROTO_UDP,
+						    skb->csum)) {
+					IP_VS_DBG_RL_PKT(0, af, pp, skb,
+							 iph->off,
+							 "Failed checksum for");
+					return 0;
+				}
+			} else
+#endif
+				if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
+						      ip_hdr(skb)->daddr,
+						      skb->len - udphoff,
+						      ip_hdr(skb)->protocol,
+						      skb->csum)) {
+					IP_VS_DBG_RL_PKT(0, af, pp, skb,
+							 iph->off,
+							 "Failed checksum for");
+					return 0;
+				}
+			break;
+		default:
+			/* No need to checksum. */
+			break;
+		}
 	}
 	return 1;
 }
-- 
2.55.0