[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