LVS
lvs-devel
Google
 
Web LinuxVirtualServer.org

[PATCH nf v3 3/3] ipvs: do not mangle ICMP replies for non-first fragmen

To: Simon Horman <horms@xxxxxxxxxxxx>
Subject: [PATCH nf v3 3/3] ipvs: do not mangle ICMP replies for non-first fragments
Cc: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx>, Florian Westphal <fw@xxxxxxxxx>, lvs-devel@xxxxxxxxxxxxxxx, netfilter-devel@xxxxxxxxxxxxxxx
From: Julian Anastasov <ja@xxxxxx>
Date: Wed, 22 Jul 2026 13:15:17 +0300
Sashiko warns that ip_vs_nat_icmp() unconditionally mangles the
payload for embedded non-first IPv4 fragments. The problem is
in the very old inverted pp->dont_defrag check which should not
continue when embedded is a non-first TCP/UDP/SCTP fragment.

Check for embedded non-first fragment is also missing from
ip_vs_out_icmp_v6(), it is needed before any connection
lookups that expect ports after the network headers.

Drop the blocking code from ip_vs_in_icmp_v6() which prevents
ICMPv6 from local clients to use non-MASQ forwarding.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260720201122.79882-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@xxxxxx>
---
 include/net/ip_vs.h             | 11 +++---
 net/netfilter/ipvs/ip_vs_core.c | 61 ++++++++++++---------------------
 net/netfilter/ipvs/ip_vs_xmit.c | 28 +++++++++++----
 3 files changed, 48 insertions(+), 52 deletions(-)

diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index a9a5589b8069..1b968f487a06 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1977,8 +1977,7 @@ int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn 
*cp,
                  struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
 int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
                    struct ip_vs_protocol *pp, unsigned int toff,
-                   unsigned int wlen, unsigned int hooknum,
-                   struct ip_vs_iphdr *ciph);
+                   unsigned int hooknum, struct ip_vs_iphdr *ciph);
 void ip_vs_dest_dst_rcu_free(struct rcu_head *head);
 
 #ifdef CONFIG_IP_VS_IPV6
@@ -1992,8 +1991,7 @@ int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct 
ip_vs_conn *cp,
                     struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
 int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
                       struct ip_vs_protocol *pp, unsigned int toff,
-                      unsigned int wlen, unsigned int hooknum,
-                      struct ip_vs_iphdr *ciph);
+                      unsigned int hooknum, struct ip_vs_iphdr *ciph);
 #endif
 
 #ifdef CONFIG_SYSCTL
@@ -2065,12 +2063,13 @@ static inline bool ip_vs_conn_use_hash2(struct 
ip_vs_conn *cp)
 }
 
 void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
-                   struct ip_vs_conn *cp, int dir, unsigned int toff);
+                   struct ip_vs_conn *cp, int dir, unsigned int toff,
+                   bool has_ports);
 
 #ifdef CONFIG_IP_VS_IPV6
 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
                       struct ip_vs_conn *cp, int dir, unsigned int toff,
-                      struct ip_vs_iphdr *ciph);
+                      bool has_ports, struct ip_vs_iphdr *ciph);
 #endif
 
 static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index cd5eb71543ec..7efa209a517b 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -924,7 +924,8 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, 
int af,
  * - inout: 1=in->out, 0=out->in
  */
 void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
-                   struct ip_vs_conn *cp, int inout, unsigned int toff)
+                   struct ip_vs_conn *cp, int inout, unsigned int toff,
+                   bool has_ports)
 {
        struct iphdr *iph        = ip_hdr(skb);
        struct icmphdr *icmph    = (struct icmphdr *)(skb->data + toff);
@@ -944,8 +945,7 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct 
ip_vs_protocol *pp,
        }
 
        /* the TCP/UDP/SCTP port */
-       if (IPPROTO_TCP == ciph->protocol || IPPROTO_UDP == ciph->protocol ||
-           IPPROTO_SCTP == ciph->protocol) {
+       if (has_ports) {
                __be16 *ports = (void *)ciph + ciph->ihl*4;
 
                if (inout)
@@ -970,18 +970,15 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct 
ip_vs_protocol *pp,
 #ifdef CONFIG_IP_VS_IPV6
 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
                       struct ip_vs_conn *cp, int inout, unsigned int toff,
-                      struct ip_vs_iphdr *ciph)
+                      bool has_ports, struct ip_vs_iphdr *ciph)
 {
        struct ipv6hdr *iph      = ipv6_hdr(skb);
-       int protocol;
        struct icmp6hdr *icmph;
        struct ipv6hdr *cih;
 
        icmph = (struct icmp6hdr *)(skb->data + toff);
        cih = (struct ipv6hdr *)(skb->data + ciph->off);
 
-       protocol = ciph->protocol;
-
        if (inout) {
                iph->saddr = cp->vaddr.in6;
                cih->daddr = cp->vaddr.in6;
@@ -991,9 +988,7 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct 
ip_vs_protocol *pp,
        }
 
        /* the TCP/UDP/SCTP port */
-       if (!ciph->fragoffs &&
-           (protocol == IPPROTO_TCP  || protocol == IPPROTO_UDP ||
-            protocol == IPPROTO_SCTP)) {
+       if (has_ports) {
                __be16 *ports = (void *)(skb->data + ciph->len);
 
                IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__,
@@ -1035,6 +1030,7 @@ static int handle_response_icmp(int af, struct sk_buff 
*skb,
        int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
        unsigned int verdict = NF_DROP;
        unsigned int ctoff = ciph->len;
+       bool has_ports = false;
 
        if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
                goto after_nat;
@@ -1048,17 +1044,19 @@ static int handle_response_icmp(int af, struct sk_buff 
*skb,
        }
 
        if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
-           ciph->protocol == IPPROTO_SCTP)
+           ciph->protocol == IPPROTO_SCTP) {
                ctoff += 2 * sizeof(__u16);
+               has_ports = true;
+       }
        if (skb_ensure_writable(skb, ctoff))
                goto out;
 
 #ifdef CONFIG_IP_VS_IPV6
        if (af == AF_INET6)
-               ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph);
+               ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph);
        else
 #endif
-               ip_vs_nat_icmp(skb, pp, cp, 1, toff);
+               ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports);
 
        if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum))
                goto out;
@@ -1142,8 +1140,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct 
sk_buff *skb,
                return NF_ACCEPT;
 
        /* Is the embedded protocol header present? */
-       if (unlikely(cih->frag_off & htons(IP_OFFSET) &&
-                    pp->dont_defrag))
+       if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
                return NF_ACCEPT;
 
        IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
@@ -1207,6 +1204,10 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, 
struct sk_buff *skb,
        if (!pp)
                return NF_ACCEPT;
 
+       /* Is the embedded protocol header present? */
+       if (unlikely(ciph.fragoffs && !pp->dont_defrag))
+               return NF_ACCEPT;
+
        /* The embedded headers contain source and dest in reverse order */
        cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto,
                             ipvs, AF_INET6, skb, &ciph);
@@ -1865,8 +1866,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff 
*skb, int *related,
        pp = pd->pp;
 
        /* Is the embedded protocol header present? */
-       if (unlikely(cih->frag_off & htons(IP_OFFSET) &&
-                    pp->dont_defrag))
+       if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
                return NF_ACCEPT;
 
        IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
@@ -1874,7 +1874,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff 
*skb, int *related,
 
        offset2 = offset;
        ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph);
-       offset = ciph.len;
 
        /* The embedded headers contain source and dest in reverse order.
         * For IPIP/UDP/GRE tunnel this is error for request, not for reply.
@@ -1968,11 +1967,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff 
*skb, int *related,
 
        /* do the statistics and put it back */
        ip_vs_in_stats(cp, skb);
-       if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol ||
-           IPPROTO_SCTP == cih->protocol)
-               offset += 2 * sizeof(__u16);
-       verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum,
-                                 &ciph);
+       verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, hooknum, &ciph);
 
 out:
        if (likely(!new_cp))
@@ -2032,8 +2027,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, 
struct sk_buff *skb,
                return NF_ACCEPT;
        pp = pd->pp;
 
-       /* Cannot handle fragmented embedded protocol */
-       if (ciph.fragoffs)
+       /* Is the embedded protocol header present? */
+       if (ciph.fragoffs && !pp->dont_defrag)
                return NF_ACCEPT;
 
        IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset,
@@ -2057,13 +2052,6 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, 
struct sk_buff *skb,
                new_cp = true;
        }
 
-       /* VS/TUN, VS/DR and LOCALNODE just let it go */
-       if ((hooknum == NF_INET_LOCAL_OUT) &&
-           (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)) {
-               verdict = NF_ACCEPT;
-               goto out;
-       }
-
        verdict = NF_DROP;
 
        /* Ensure the checksum is correct */
@@ -2079,14 +2067,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, 
struct sk_buff *skb,
        /* do the statistics and put it back */
        ip_vs_in_stats(cp, skb);
 
-       /* Need to mangle contained IPv6 header in ICMPv6 packet */
-       offset = ciph.len;
-       if (IPPROTO_TCP == ciph.protocol || IPPROTO_UDP == ciph.protocol ||
-           IPPROTO_SCTP == ciph.protocol)
-               offset += 2 * sizeof(__u16); /* Also mangle ports */
-
-       verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum,
-                                    &ciph);
+       verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, hooknum, &ciph);
 
 out:
        if (likely(!new_cp))
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index 3b6a99fb4cf7..cdc567ceabf4 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c
@@ -1505,13 +1505,14 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn 
*cp,
 int
 ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
                struct ip_vs_protocol *pp, unsigned int toff,
-               unsigned int wlen, unsigned int hooknum,
-               struct ip_vs_iphdr *ciph)
+               unsigned int hooknum, struct ip_vs_iphdr *ciph)
 {
        struct rtable   *rt;    /* Route to the other host */
        int rc;
        int local;
        int rt_mode, was_input;
+       bool has_ports = false;
+       unsigned int wlen;
 
        /* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be
           forwarded directly here, because there is no need to
@@ -1567,6 +1568,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn 
*cp,
                goto tx_error;
        }
 
+       wlen = ciph->len;
+       if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
+           ciph->protocol == IPPROTO_SCTP) {
+               wlen += 2 * sizeof(__u16); /* Also mangle ports */
+               has_ports = true;
+       }
+
        /* copy-on-write the packet before mangling it */
        if (skb_ensure_writable(skb, wlen))
                goto tx_error;
@@ -1574,7 +1582,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn 
*cp,
        if (skb_cow(skb, rt->dst.dev->hard_header_len))
                goto tx_error;
 
-       ip_vs_nat_icmp(skb, pp, cp, 0, toff);
+       ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports);
 
        /* Another hack: avoid icmp_send in ip_fragment */
        skb->ignore_df = 1;
@@ -1591,10 +1599,11 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn 
*cp,
 int
 ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
                struct ip_vs_protocol *pp, unsigned int toff,
-               unsigned int wlen, unsigned int hooknum,
-               struct ip_vs_iphdr *ciph)
+               unsigned int hooknum, struct ip_vs_iphdr *ciph)
 {
+       bool has_ports = false;
        struct rt6_info *rt;    /* Route to the other host */
+       unsigned int wlen;
        int rc;
        int local;
        int rt_mode;
@@ -1652,6 +1661,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct 
ip_vs_conn *cp,
                goto tx_error;
        }
 
+       wlen = ciph->len;
+       if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
+           ciph->protocol == IPPROTO_SCTP) {
+               wlen += 2 * sizeof(__u16); /* Also mangle ports */
+               has_ports = true;
+       }
+
        /* copy-on-write the packet before mangling it */
        if (skb_ensure_writable(skb, wlen))
                goto tx_error;
@@ -1659,7 +1675,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn 
*cp,
        if (skb_cow(skb, rt->dst.dev->hard_header_len))
                goto tx_error;
 
-       ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph);
+       ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, has_ports, ciph);
 
        /* Another hack: avoid icmp_send in ip_fragment */
        skb->ignore_df = 1;
-- 
2.55.0




<Prev in Thread] Current Thread [Next in Thread>