net: ipv6: fix UDPv6 GSO segmentation with NAT

[ Upstream commit b936a9b8d4a585ccb6d454921c36286bfe63e01d ]

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

Fixes: 9fd1ff5d2a ("udp: Support UDP fraglist GRO/GSO.")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20250426153210.14044-1-nbd@nbd.name
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Felix Fietkau
2025-04-26 17:32:09 +02:00
committed by Greg Kroah-Hartman
parent 8acf08b1e4
commit e027284499

View File

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