kernel: fix UDPv6 GSO segmentation with NAT
authorFelix Fietkau <[email protected]>
Sat, 26 Apr 2025 17:10:54 +0000 (19:10 +0200)
committerFelix Fietkau <[email protected]>
Sat, 26 Apr 2025 17:13:32 +0000 (19:13 +0200)
Fixes issues with rx-gro-list and NAT66

Fixes: https://github.com/openwrt/openwrt/issues/18387
Fixes: https://github.com/openwrt/openwrt/issues/18516
Fixes: https://github.com/openwrt/openwrt/issues/18608
Signed-off-by: Felix Fietkau <[email protected]>
target/linux/generic/pending-6.6/691-net-ipv6-fix-UDPv6-GSO-segmentation-with-NAT.patch [new file with mode: 0644]

diff --git a/target/linux/generic/pending-6.6/691-net-ipv6-fix-UDPv6-GSO-segmentation-with-NAT.patch b/target/linux/generic/pending-6.6/691-net-ipv6-fix-UDPv6-GSO-segmentation-with-NAT.patch
new file mode 100644 (file)
index 0000000..6bbe2ca
--- /dev/null
@@ -0,0 +1,88 @@
+From: Felix Fietkau <[email protected]>
+Date: Sat, 26 Apr 2025 17:18:03 +0200
+Subject: [PATCH] net: ipv6: fix UDPv6 GSO segmentation with NAT
+
+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 <[email protected]>
+---
+
+--- a/net/ipv4/udp_offload.c
++++ b/net/ipv4/udp_offload.c
+@@ -247,6 +247,62 @@ static struct sk_buff *__udpv4_gso_segme
+       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
+       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,