Commit b936a9b8 authored by Felix Fietkau's avatar Felix Fietkau Committed by Jakub Kicinski
Browse files

net: ipv6: fix UDPv6 GSO segmentation with NAT



If any address or port is changed, update it in all packets and recalculate
checksum.

Fixes: 9fd1ff5d ("udp: Support UDP fraglist GRO/GSO.")
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
Reviewed-by: default avatarWillem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20250426153210.14044-1-nbd@nbd.name


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 1e0bff3b
Loading
Loading
Loading
Loading
+60 −1
Original line number Diff line number Diff line
@@ -247,6 +247,62 @@ static struct sk_buff *__udpv4_gso_segment_list_csum(struct sk_buff *segs)
	return segs;
}

static void __udpv6_gso_segment_csum(struct sk_buff *seg,
				     struct in6_addr *oldip,
				     const struct in6_addr *newip,
				     __be16 *oldport, __be16 newport)
{
	struct udphdr *uh = udp_hdr(seg);

	if (ipv6_addr_equal(oldip, newip) && *oldport == newport)
		return;

	if (uh->check) {
		inet_proto_csum_replace16(&uh->check, seg, oldip->s6_addr32,
					  newip->s6_addr32, true);

		inet_proto_csum_replace2(&uh->check, seg, *oldport, newport,
					 false);
		if (!uh->check)
			uh->check = CSUM_MANGLED_0;
	}

	*oldip = *newip;
	*oldport = newport;
}

static struct sk_buff *__udpv6_gso_segment_list_csum(struct sk_buff *segs)
{
	const struct ipv6hdr *iph;
	const struct udphdr *uh;
	struct ipv6hdr *iph2;
	struct sk_buff *seg;
	struct udphdr *uh2;

	seg = segs;
	uh = udp_hdr(seg);
	iph = ipv6_hdr(seg);
	uh2 = udp_hdr(seg->next);
	iph2 = ipv6_hdr(seg->next);

	if (!(*(const u32 *)&uh->source ^ *(const u32 *)&uh2->source) &&
	    ipv6_addr_equal(&iph->saddr, &iph2->saddr) &&
	    ipv6_addr_equal(&iph->daddr, &iph2->daddr))
		return segs;

	while ((seg = seg->next)) {
		uh2 = udp_hdr(seg);
		iph2 = ipv6_hdr(seg);

		__udpv6_gso_segment_csum(seg, &iph2->saddr, &iph->saddr,
					 &uh2->source, uh->source);
		__udpv6_gso_segment_csum(seg, &iph2->daddr, &iph->daddr,
					 &uh2->dest, uh->dest);
	}

	return segs;
}

static struct sk_buff *__udp_gso_segment_list(struct sk_buff *skb,
					      netdev_features_t features,
					      bool is_ipv6)
@@ -259,7 +315,10 @@ static struct sk_buff *__udp_gso_segment_list(struct sk_buff *skb,

	udp_hdr(skb)->len = htons(sizeof(struct udphdr) + mss);

	return is_ipv6 ? skb : __udpv4_gso_segment_list_csum(skb);
	if (is_ipv6)
		return __udpv6_gso_segment_list_csum(skb);
	else
		return __udpv4_gso_segment_list_csum(skb);
}

struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,