ipvs: do not mangle ICMP replies for non-first fragments

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: 1da177e4c3 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260720201122.79882-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Julian Anastasov 2026-07-22 13:15:17 +03:00 committed by Pablo Neira Ayuso
parent 15cab31a37
commit 342e24a339
3 changed files with 48 additions and 52 deletions

View File

@ -1975,8 +1975,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
@ -1990,8 +1989,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
@ -2063,12 +2061,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)

View File

@ -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))

View File

@ -1503,13 +1503,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
@ -1565,6 +1566,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;
@ -1572,7 +1580,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;
@ -1589,10 +1597,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;
@ -1650,6 +1659,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;
@ -1657,7 +1673,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;