]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: ipv6: fix UDPv6 GSO segmentation with NAT
authorFelix Fietkau <nbd@nbd.name>
Sat, 26 Apr 2025 15:32:09 +0000 (17:32 +0200)
committerJakub Kicinski <kuba@kernel.org>
Tue, 29 Apr 2025 21:46:28 +0000 (14:46 -0700)
If any address or port is changed, update it in all packets and recalculate
checksum.

Fixes: 9fd1ff5d2ac7 ("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>
net/ipv4/udp_offload.c

index 2c0725583be39fdc7e379499a163b687d8356abf..9a8142ccbabe44b4c7642393f93711d27d02012c 100644 (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,