Netdev List
 help / color / mirror / Atom feed
* [PATCH net v4 0/4] ipmr: forward GSO multicast packets
@ 2026-11-01 16:42 Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 1/4] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-01 16:42 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	steffen.klassert, willemb, jiayuan.chen, mfleming, almasrymina,
	bigeasy, richardbgobert, ecree.xilinx, linux-kernel

With fraglist GRO or UDP GRO forwarding on, a routed IPv4 multicast
stream arrives as GSO packets. ipmr_prepare_xmit() drops every DF
aggregate larger than the MTU, and ip_encap() sets no tunnel GSO state,
so a GSO packet sent to a VIFF_TUNNEL vif is dropped during
segmentation. With that state set, a fraglist aggregate reaches
skb_segment_list() with tunnel headers, which can write before the head
of a frag_list member; this also happens today when such a packet is
routed into a tunnel device. Checking the segment length instead of
skb->len relies on skb_gso_transport_seglen(), which counts the inner
UDP header of an encapsulated UDP GSO packet 8 bytes short.

Patch 1 makes room for the tunnel headers in skb_segment_list().
Patch 2 counts the inner UDP header of encapsulated UDP GSO packets.
Patch 3 sets up IPXIP4 GSO state, outer IP IDs and length in ip_encap().
Patch 4 lets DF GSO packets pass when their segments fit the MTU.

Tested in QEMU (KASAN) on veth, one run per arm: an MFC entry to a plain
vif and a tunnel vif, 40 x 1316-byte datagrams per GSO packet, 4000
datagrams per arm, UDP_SEGMENT, fraglist GRO and UDP GRO forwarding, DF
set and clear, software and device segmentation on egress:
- net (7076f684d513): every DF aggregate dropped on both vifs (FragFails
  2 per aggregate), and with software segmentation nothing arrives over
  the tunnel vif; fraglist GRO routed into VXLAN reports a KASAN
  slab-out-of-bounds write in skb_segment_list(), then panics.
- this series, all 12 arms: 4000 of 4000 datagrams on the plain vif,
  FragFails 0; on the tunnel vif 4000 of 4000 IPIP packets with unique
  outer IDs with software segmentation, and the 100 GSO packets of 52688
  bytes whole with device segmentation.
- VXLAN with DF, 1404-byte outer segments, route MTU 1400: net and this
  series without patch 2 forward all 1000 segments at 1404 bytes through
  ip_forward(), and the series without patch 2 also through the MFC
  entry; with patch 2 none leave and FragFails counts them.
- a GRE GSO packet (skb->encapsulation set) multicast-routed to both
  vifs: all 1000 segments on the plain vif, none on the tunnel vif.
- fraglist GRO routed into VXLAN and IPIP with software segmentation:
  every segment reaches the peer, no KASAN report.

Changes in v4:
- Add patch 2 (Sashiko).
- Patch 3: drop GSO packets that are already encapsulated, and set the
  outer length with iph_set_totlen() (Sashiko).
- Drop v3 patch 2 (TCP fixed-ID flag on encapsulation): multicast
  routing does not carry TCP, and the flag also needs the decap and
  GRE/UDP segmentation paths, which go to a separate series (Sashiko).
- Rebase onto net.
- v3: https://lore.kernel.org/netdev/cover.1792293419.git.julius@bairaktaris.de/

Changes in v3:
- Patch 1: name the VXLAN out-of-bounds write (Sashiko).
- Add patch 2 (Sashiko).
- Patch 3: reserve one outer IP ID per segment (Sashiko).
- v2: https://lore.kernel.org/netdev/20261011162957.1328474-1-julius@bairaktaris.de/

Changes in v2:
- Add patches 1 and 3; v1 let DF GSO packets reach ip_encap() on tunnel
  vifs, where they were dropped during segmentation (Sashiko).
- Keep the additive MTU comparison and check mtu > encap before
  subtracting, so a route MTU below 20 cannot wrap (Sashiko).
- v1: https://lore.kernel.org/netdev/20261010131842.2157365-1-julius@bairaktaris.de/

I used Claude (Anthropic) to help write these patches.

Julius Bairaktaris (4):
  net: make room for the tunnel header in skb_segment_list()
  net: gso: count the inner UDP header of encapsulated UDP GSO packets
  ipmr: set up GSO state for tunnel vifs
  ipmr: do not drop GSO packets that fit the MTU after segmentation

 net/core/gso.c    |  2 ++
 net/core/skbuff.c |  4 ++++
 net/ipv4/ipmr.c   | 29 ++++++++++++++++++++++-------
 3 files changed, 28 insertions(+), 7 deletions(-)


base-commit: 7076f684d5132e6a6145e1da2fa74771ecd052e5
-- 
2.53.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net v4 1/4] net: make room for the tunnel header in skb_segment_list()
  2026-11-01 16:42 [PATCH net v4 0/4] ipmr: forward GSO multicast packets Julius Bairaktaris
@ 2026-11-01 16:42 ` Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 2/4] net: gso: count the inner UDP header of encapsulated UDP GSO packets Julius Bairaktaris
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-01 16:42 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	steffen.klassert, willemb, jiayuan.chen, mfleming, almasrymina,
	bigeasy, richardbgobert, ecree.xilinx, linux-kernel

skb_segment_list() pushes each frag_list member back to the outer MAC
header and copies the tunnel headers in front of it, but never checks
that the member has the headroom. A fraglist GRO packet forwarded into
a tunnel and segmented in software then hits skb_under_panic() on IPIP,
and on VXLAN the copy writes before the member's head (KASAN:
slab-out-of-bounds write in skb_segment_list()).

Call skb_cow_head() on each member before pushing it.

Fixes: 3a1296a38d0c ("net: Support GRO/GSO fraglist chaining.")
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
 net/core/skbuff.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 48f6764117243..48291a2d21241 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4777,6 +4777,10 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb,
 			}
 		}
 
+		if (!err)
+			err = skb_cow_head(nskb, offset + tnl_hlen -
+					   skb_network_offset(nskb));
+
 		if (!tail)
 			skb->next = nskb;
 		else
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH net v4 2/4] net: gso: count the inner UDP header of encapsulated UDP GSO packets
  2026-11-01 16:42 [PATCH net v4 0/4] ipmr: forward GSO multicast packets Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 1/4] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
@ 2026-11-01 16:42 ` Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 3/4] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 4/4] ipmr: do not drop GSO packets that fit the MTU after segmentation Julius Bairaktaris
  3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-01 16:42 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	steffen.klassert, willemb, jiayuan.chen, mfleming, almasrymina,
	bigeasy, richardbgobert, ecree.xilinx, linux-kernel

skb_gso_transport_seglen() adds the inner TCP header length for an
encapsulated GSO packet, but not the inner UDP header for
SKB_GSO_UDP_L4, whose gso_size covers the payload only. Every segment
of a UDP GSO packet in a UDP tunnel, built by UDP GRO behind a tunnel
socket or sent with UDP_SEGMENT over VXLAN, is counted 8 bytes short.
ip_forward() and ip_finish_output_gso() then let DF segments that
exceed the MTU by up to 8 bytes leave unfragmented.

Add the inner UDP header as the unencapsulated branch does.

Fixes: ee80d1ebe5ba ("udp: add udp gso")
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
 net/core/gso.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/net/core/gso.c b/net/core/gso.c
index b5bd269e56e4e..eb95c9d2819b1 100644
--- a/net/core/gso.c
+++ b/net/core/gso.c
@@ -156,6 +156,8 @@ static unsigned int skb_gso_transport_seglen(const struct sk_buff *skb)
 
 		if (likely(shinfo->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)))
 			thlen += inner_tcp_hdrlen(skb);
+		else if (shinfo->gso_type & SKB_GSO_UDP_L4)
+			thlen += sizeof(struct udphdr);
 	} else if (likely(shinfo->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))) {
 		thlen = tcp_hdrlen(skb);
 	} else if (unlikely(skb_is_gso_sctp(skb))) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH net v4 3/4] ipmr: set up GSO state for tunnel vifs
  2026-11-01 16:42 [PATCH net v4 0/4] ipmr: forward GSO multicast packets Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 1/4] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 2/4] net: gso: count the inner UDP header of encapsulated UDP GSO packets Julius Bairaktaris
@ 2026-11-01 16:42 ` Julius Bairaktaris
  2026-11-01 16:42 ` [PATCH net v4 4/4] ipmr: do not drop GSO packets that fit the MTU after segmentation Julius Bairaktaris
  3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-01 16:42 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	steffen.klassert, willemb, jiayuan.chen, mfleming, almasrymina,
	bigeasy, richardbgobert, ecree.xilinx, linux-kernel

ip_encap() pushes the outer IPv4 header but does not set SKB_GSO_IPXIP4,
skb->encapsulation or the inner headers. A GSO packet forwarded to a
VIFF_TUNNEL vif is then rejected by ipip_gso_segment() and dropped in
validate_xmit_skb(), after being counted as sent. A device with UDP
segmentation offload is handed a GSO packet whose transport header
points at the inner IP header.

Call iptunnel_handle_offloads() and skb_set_inner_ipproto() as
ipip_tunnel_xmit() does, reserve one outer IP ID per segment as
iptunnel_xmit() does, and set the outer length with iph_set_totlen(),
since a GSO packet plus the outer header can exceed 64 KB. A GSO packet
that GRE or UDP tunnel GRO marked as encapsulated already uses the inner
headers for its own tunnel; drop it, as segmentation did before.

Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
 net/ipv4/ipmr.c | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index acacaae05cc49..98e38839f8ccb 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1849,12 +1849,21 @@ static struct notifier_block ip_mr_notifier = {
  * This avoids tunnel drivers and other mess and gives us the speed so
  * important for multicast video.
  */
-static void ip_encap(struct net *net, struct sk_buff *skb,
-		     __be32 saddr, __be32 daddr)
+static int ip_encap(struct net *net, struct sk_buff *skb,
+		    __be32 saddr, __be32 daddr)
 {
+	const struct iphdr *old_iph;
 	struct iphdr *iph;
-	const struct iphdr *old_iph = ip_hdr(skb);
 
+	/* The inner headers can describe one tunnel only */
+	if (skb_is_gso(skb) && skb->encapsulation)
+		return -1;
+
+	if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4))
+		return -1;
+	skb_set_inner_ipproto(skb, IPPROTO_IPIP);
+
+	old_iph = ip_hdr(skb);
 	skb_push(skb, sizeof(struct iphdr));
 	skb->transport_header = skb->network_header;
 	skb_reset_network_header(skb);
@@ -1868,12 +1877,13 @@ static void ip_encap(struct net *net, struct sk_buff *skb,
 	iph->saddr	=	saddr;
 	iph->protocol	=	IPPROTO_IPIP;
 	iph->ihl	=	5;
-	iph->tot_len	=	htons(skb->len);
-	ip_select_ident(net, skb, NULL);
+	iph_set_totlen(iph, skb->len);
+	ip_select_ident_segs(net, skb, NULL, skb_shinfo(skb)->gso_segs ?: 1);
 	ip_send_check(iph);
 
 	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
 	nf_reset_ct(skb);
+	return 0;
 }
 
 static inline int ipmr_forward_finish(struct net *net, struct sock *sk,
@@ -1982,7 +1992,8 @@ static int ipmr_prepare_xmit(struct net *net, struct mr_table *mrt,
 	 * What do we do with netfilter? -- RR
 	 */
 	if (vif->flags & VIFF_TUNNEL) {
-		ip_encap(net, skb, vif->local, vif->remote);
+		if (ip_encap(net, skb, vif->local, vif->remote))
+			return -1;
 		/* FIXME: extra output firewall step used to be here. --RR */
 		DEV_STATS_INC(vif_dev, tx_packets);
 		DEV_STATS_ADD(vif_dev, tx_bytes, skb->len);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH net v4 4/4] ipmr: do not drop GSO packets that fit the MTU after segmentation
  2026-11-01 16:42 [PATCH net v4 0/4] ipmr: forward GSO multicast packets Julius Bairaktaris
                   ` (2 preceding siblings ...)
  2026-11-01 16:42 ` [PATCH net v4 3/4] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
@ 2026-11-01 16:42 ` Julius Bairaktaris
  3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-01 16:42 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
	steffen.klassert, willemb, jiayuan.chen, mfleming, almasrymina,
	bigeasy, richardbgobert, ecree.xilinx, linux-kernel

ipmr_prepare_xmit() drops a packet with DF set when skb->len exceeds the
route MTU, without checking whether it is a GSO packet. A UDP multicast
stream received with fraglist GRO or UDP GRO forwarding enabled is
aggregated into GSO packets, and every aggregate is dropped with
FragFails, while ip_forward() lets the same packets pass by checking
skb_gso_validate_network_len(). Do the same for multicast routing,
leaving room for the outer header on tunnel vifs. ip6mr has no such
check and leaves GSO packets to ip6_output().

On an IPQ8074 router with fraglist GRO on, a 10 Mbit/s stream of
1316-byte DF datagrams routed by igmpproxy loses 14.5 to 15.5% (n=3)
and 0% with this change.

Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
 net/ipv4/ipmr.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 98e38839f8ccb..1fb728fb15741 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1932,6 +1932,7 @@ static int ipmr_prepare_xmit(struct net *net, struct mr_table *mrt,
 	struct rtable *rt;
 	struct flowi4 fl4;
 	int    encap = 0;
+	unsigned int mtu;
 
 	vif_dev = vif_dev_read(vif);
 	if (!vif_dev)
@@ -1964,7 +1965,10 @@ static int ipmr_prepare_xmit(struct net *net, struct mr_table *mrt,
 			return -1;
 	}
 
-	if (skb->len+encap > dst4_mtu(&rt->dst) && (ntohs(iph->frag_off) & IP_DF)) {
+	mtu = dst4_mtu(&rt->dst);
+	if (skb->len + encap > mtu && (ntohs(iph->frag_off) & IP_DF) &&
+	    !(skb_is_gso(skb) && mtu > encap &&
+	      skb_gso_validate_network_len(skb, mtu - encap))) {
 		/* Do not fragment multicasts. Alas, IPv4 does not
 		 * allow to send ICMP, so that packets will disappear
 		 * to blackhole.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-01 16:42 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-01 16:42 [PATCH net v4 0/4] ipmr: forward GSO multicast packets Julius Bairaktaris
2026-11-01 16:42 ` [PATCH net v4 1/4] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
2026-11-01 16:42 ` [PATCH net v4 2/4] net: gso: count the inner UDP header of encapsulated UDP GSO packets Julius Bairaktaris
2026-11-01 16:42 ` [PATCH net v4 3/4] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
2026-11-01 16:42 ` [PATCH net v4 4/4] ipmr: do not drop GSO packets that fit the MTU after segmentation Julius Bairaktaris

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox