ip_vs_in_icmp_v6() is missing checksum validation for ICMPv6
packets from clients. In fact, as for TCP/UDP we should
validate the checksum for ICMP packets only when we
mangle the packets on MASQ or on reply for tunnel.
Also, Sashiko points out that handle_response_icmp() being
common for IPv4 and IPv6 is missing the pseudo-header
calculation while validating ICMPv6 messages from real
servers which is a problem if checksum is not validated
by the hardware.
Fix the problems by creating ip_vs_checksum_common_check()
helper and use it for TCP/UDP/ICMP both for IPv4 and IPv6.
Rely on the nf_checksum() for validating the ICMP messages
but use it also for TCP and UDP.
Use correct IP offset for IP_VS_DBG_RL_PKT for TCP/UDP/SCTP.
IPVS packets (TCP/UDP/SCTP/ICMP) do not need checksum
validation on LOCAL_OUT (local clients or local real
servers) and on FORWARD (traffic from servers on LAN).
Do it only on LOCAL_IN, in case nf_checksum() is not
called on PRE_ROUTING.
Also, ip_vs_checksum_complete() can be marked static.
Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6")
Link: https://sashiko.dev/#/patchset/20260708180315.77413-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
#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
struct ip_vs_conn *cp, int dir);
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset);
-
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
{
__be32 diff[2] = { ~old, new };
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)
{
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
+static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
{
return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0));
}
unsigned int offset, unsigned int ihl,
unsigned int hooknum)
{
+ int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
unsigned int verdict = NF_DROP;
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto after_nat;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) {
/* Failed checksum! */
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
IP_VS_DBG_ADDR(af, snet));
verdict = NF_DROP;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) &&
+ !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) {
/* Failed checksum! */
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
&iph->saddr);
goto out;
}
+ verdict = NF_DROP;
+
+ /* 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)) {
+ /* Failed checksum! */
+ IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n",
+ &iph->saddr);
+ goto out;
+ }
+
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff);
+ struct ip_vs_iphdr *iph);
static int
sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff)
+ struct ip_vs_iphdr *iph)
{
+ unsigned int sctphoff = iph->len;
struct sctphdr *sh;
__le32 cmp, val;
+ if (!ip_vs_checksum_needed(skb, af))
+ return 1;
sh = (struct sctphdr *)(skb->data + sctphoff);
cmp = sh->checksum;
val = sctp_compute_cksum(skb, sctphoff);
if (val != cmp) {
/* CRC failure, dump it. */
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
return 0;
}
return 1;
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff);
+ struct ip_vs_iphdr *iph);
static int
tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff)
+ struct ip_vs_iphdr *iph)
{
- 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, 0,
- "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, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
+ 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;
}
-
return 1;
}
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff);
+ struct ip_vs_iphdr *iph);
static int
udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff)
+ struct ip_vs_iphdr *iph)
{
struct udphdr _udph, *uh;
- uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph);
+ uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph);
if (uh == NULL)
return 0;
- if (uh->check != 0) {
- 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, 0,
- "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, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
- }
+ 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;
}
return 1;
}