* [PATCH net v2 0/3] ipmr: forward GSO multicast packets
@ 2026-10-11 16:29 Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Julius Bairaktaris @ 2026-10-11 16:29 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, 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 a GSO packet sent to a VIFF_TUNNEL
vif is dropped during segmentation because ip_encap() sets no tunnel
GSO state. Once ip_encap() sets it, a fraglist aggregate reaches
skb_segment_list() with tunnel headers, which can push a frag_list
member below its head. The same panic happens today with a fraglist
aggregate routed into an ipip device.
Patch 1 makes room for the tunnel headers in skb_segment_list().
Patch 2 sets up IPXIP4 GSO state in ip_encap().
Patch 3 lets DF GSO packets pass when their segments fit the MTU,
leaving room for the outer header on tunnel vifs.
Tested in netns on veth, MFC entry to a plain vif and a tunnel vif,
software segmentation on egress, 4000 1316-byte datagrams per run, n=3
per arm, fraglist GRO, UDP GRO forwarding and UDP_SEGMENT, DF set and
clear:
- net (af32da41b032): DF aggregates dropped on both vifs (FragFails),
non-DF aggregates dropped on the tunnel vif (tx_dropped); fraglist
into an ipip device panics in skb_segment_list().
- v1: DF aggregates dropped on the tunnel vif (tx_dropped).
- this series: all 4000 delivered on both vifs, FragFails 0, no tx
drops, no panic; ipip device 4000 of 4000.
Changes in v2:
- Add patches 1 and 2; 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/
Julius Bairaktaris (3):
net: make room for the tunnel header in skb_segment_list()
ipmr: set up GSO state for tunnel vifs
ipmr: do not drop GSO packets that fit the MTU after segmentation
net/core/skbuff.c | 4 ++++
net/ipv4/ipmr.c | 21 ++++++++++++++++-----
2 files changed, 20 insertions(+), 5 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list()
2026-10-11 16:29 [PATCH net v2 0/3] ipmr: forward GSO multicast packets Julius Bairaktaris
@ 2026-10-11 16:29 ` Julius Bairaktaris
2026-10-12 17:19 ` netdev-bot+sashiko
2026-10-11 16:29 ` [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 3/3] ipmr: do not drop GSO packets that fit the MTU after segmentation Julius Bairaktaris
2 siblings, 1 reply; 6+ messages in thread
From: Julius Bairaktaris @ 2026-10-11 16:29 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, 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
an IPIP tunnel and segmented in software hits skb_under_panic() when
the members were received with less headroom than the tunnel headers
need.
Call skb_cow_head() on each member before pushing it.
Fixes: 3a1296a38d0c ("net: Support GRO/GSO fraglist chaining.")
Assisted-by: Claude:claude-opus-5-5
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 41beaf625421a..3f358c0fc0483 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4729,6 +4729,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 [flat|nested] 6+ messages in thread
* [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs
2026-10-11 16:29 [PATCH net v2 0/3] ipmr: forward GSO multicast packets Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
@ 2026-10-11 16:29 ` Julius Bairaktaris
2026-10-12 17:19 ` netdev-bot+sashiko
2026-10-11 16:29 ` [PATCH net v2 3/3] ipmr: do not drop GSO packets that fit the MTU after segmentation Julius Bairaktaris
2 siblings, 1 reply; 6+ messages in thread
From: Julius Bairaktaris @ 2026-10-11 16:29 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, 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.
Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
net/ipv4/ipmr.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index b9c544d48c452..f0666749b14a7 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1835,12 +1835,17 @@ 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);
+ 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);
@@ -1860,6 +1865,7 @@ static void ip_encap(struct net *net, struct sk_buff *skb,
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,
@@ -1968,7 +1974,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 [flat|nested] 6+ messages in thread
* [PATCH net v2 3/3] ipmr: do not drop GSO packets that fit the MTU after segmentation
2026-10-11 16:29 [PATCH net v2 0/3] ipmr: forward GSO multicast packets Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
@ 2026-10-11 16:29 ` Julius Bairaktaris
2 siblings, 0 replies; 6+ messages in thread
From: Julius Bairaktaris @ 2026-10-11 16:29 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, 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.")
Assisted-by: Claude:claude-opus-5-5
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 f0666749b14a7..983565c0711fa 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1914,6 +1914,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)
@@ -1946,7 +1947,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 [flat|nested] 6+ messages in thread
* Re: [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list()
2026-10-11 16:29 ` [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
@ 2026-10-12 17:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 17:19 UTC (permalink / raw)
To: julius
Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message describes the bug only as an IPIP case that
ends in skb_under_panic().
--
net: make room for the tunnel header in skb_segment_list()
This patch adds a skb_cow_head() call for each frag_list member in
skb_segment_list(). The call happens before the member is pushed back to
the outer MAC header and the tunnel headers are copied in front of it.
The stated goal is to avoid skb_under_panic() when a fraglist GRO packet
is forwarded into an IPIP tunnel and segmented in software.
> 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
> an IPIP tunnel and segmented in software hits skb_under_panic() when
> the members were received with less headroom than the tunnel headers
> need.
[Severity: Medium]
Does this description cover the whole impact? The same missing headroom
check seems to also allow a silent out-of-bounds write before
nskb->head on the UDP tunnel GSO path (VXLAN, GENEVE and similar). On
that path there is no skb_under_panic() at all.
On that path, __skb_udp_tunnel_segment() resets the mac header to the
inner header before segmenting:
net/ipv4/udp_offload.c:__skb_udp_tunnel_segment() {
...
__skb_pull(skb, tnl_hlen);
skb_reset_mac_header(skb);
...
}
After that, skb_tnl_header_len() returns the full outer header length
inside skb_segment_list(). That is 50 bytes for VXLAN over IPv4 and 70
over IPv6. Meanwhile, offset is only the inner MAC length.
Before this patch, skb_push(nskb, -skb_network_offset(nskb) + offset)
only moved each member back to its own received inner MAC header, so it
never triggered skb_under_panic(). The copy that follows is a plain
memcpy with no bounds check:
net/core/skbuff.c:skb_segment_list() {
...
skb_copy_from_linear_data_offset(skb, -tnl_hlen,
nskb->data - tnl_hlen,
offset + tnl_hlen);
...
}
If a member has less headroom than tnl_hlen, this copy writes before
nskb->head. For example, a napi_alloc_skb() member with NET_SKB_PAD (64)
headroom, forwarded into VXLAN over IPv6 (70 bytes), would get 6 bytes
written before head.
vxlan_setup() advertises NETIF_F_GSO_SOFTWARE, which includes
NETIF_F_GSO_FRAGLIST. So fraglist GRO packets can reach this path
without being segmented first.
The new skb_cow_head(nskb, offset + tnl_hlen - skb_network_offset(nskb))
reserves the push length plus tnl_hlen. The code change therefore seems
to fix this case too.
Could the commit message describe the UDP tunnel case and the heap
corruption, so that stable and security triage don't treat this as a
crash-only issue?
It might also be worth saying that skb_cow_head() now unclones members
whose header is cloned before the tunnel headers are written into them.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261011162957.1328474-1-julius%40bairaktaris.de
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs
2026-10-11 16:29 ` [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
@ 2026-10-12 17:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 17:19 UTC (permalink / raw)
To: julius
Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
steffen.klassert, willemb, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] After this patch, a GSO skb sent through a VIFF_TUNNEL vif is
a valid SKB_GSO_IPXIP4 encapsulated packet and gets segmented.
- [Low] ip_encap() now turns the skb into an encapsulated GSO packet
(iptunnel_handle_offloads() sets skb->encapsulation = 1 and ORs in…
--
ipmr: set up GSO state for tunnel vifs
This commit makes ip_encap() call iptunnel_handle_offloads() with
SKB_GSO_IPXIP4 and then skb_set_inner_ipproto(). GSO packets forwarded to
a VIFF_TUNNEL vif are now segmented instead of being dropped in
validate_xmit_skb(). ip_encap() now returns an error, and
ipmr_prepare_xmit() passes it up.
> diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
> index b9c544d48c452..f0666749b14a7 100644
> --- a/net/ipv4/ipmr.c
> +++ b/net/ipv4/ipmr.c
> @@ -1835,12 +1835,17 @@ 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);
>
> + if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4))
> + return -1;
> + skb_set_inner_ipproto(skb, IPPROTO_IPIP);
[Severity: Medium]
A GSO skb sent out a VIFF_TUNNEL vif is now a valid SKB_GSO_IPXIP4 packet
and really does get segmented. Is one IP ID reservation for the outer
header still enough?
Later in ip_encap(), the outer header is built with DF clear and a single
ID:
net/ipv4/ipmr.c:ip_encap() {
...
iph->frag_off = 0;
...
ip_select_ident(net, skb, NULL);
...
}
ip_select_ident() hard-codes a segment count of one:
include/net/ip.h:ip_select_ident() {
ip_select_ident_segs(net, skb, sk, 1);
}
Later, inet_gso_segment() handles the outer header with encap == 0, and
fixedid is normally false. Each segment gets its own consecutive ID from
iph->id = htons(id++), so a packet with N segments uses IDs id..id+N-1.
ip_idents_reserve() moves the bucket forward by only 1 per packet. It adds
random perturbation only when the jiffy changes.
Can back-to-back GSO packets on the same tunnel then reuse most of each
other's outer IDs?
DF is clear on these outer datagrams. If they are fragmented further along
the path, the tunnel endpoint could reassemble fragments from different
packets that share an ID.
The commit message says this matches ipip_tunnel_xmit(). That path ends
in iptunnel_xmit(), which reserves gso_segs IDs:
net/ipv4/ip_tunnel_core.c:iptunnel_xmit() {
...
__ip_select_ident(net, iph, skb_shinfo(skb)->gso_segs ?: 1);
...
}
__ip_queue_xmit() does the same. Should ip_encap() call
ip_select_ident_segs(net, skb, NULL, skb_shinfo(skb)->gso_segs ?: 1)
instead?
[Severity: Low]
Should an existing SKB_GSO_TCP_FIXEDID bit be moved to
SKB_GSO_TCP_FIXEDID_INNER here?
tcp4_gro_complete() sets SKB_GSO_TCP_FIXEDID to describe the original
received IPv4 header:
skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4 |
(NAPI_GRO_CB(skb)->ip_fixedid * SKB_GSO_TCP_FIXEDID);
Once iptunnel_handle_offloads() sets skb->encapsulation, that header
becomes the inner one. However, this documentation file:
Documentation/networking/segmentation-offloads.rst
says "For encapsulated packets, SKB_GSO_TCP_FIXEDID refers only to the
outer header." inet_gso_segment() follows that rule:
fixedid = !!(skb_shinfo(skb)->gso_type & (SKB_GSO_TCP_FIXEDID << encap));
Take a TCP flow with fixed IP IDs, sent to a multicast group whose route
goes out a VIFF_TUNNEL vif. Would every outer segment get the same IP ID,
while the inner header gets incrementing IDs?
The outer header built by ip_encap() always has frag_off = 0, so this
would send non-atomic datagrams with repeated IDs, which RFC 6864 does not
allow.
ipip_tunnel_xmit() calls the same helper. Its outer DF normally comes from
the tunnel's pmtudisc setting, while ip_encap() always clears DF.
Before this patch these packets were dropped in ipip_gso_segment(), so this
path is newly reachable.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261011162957.1328474-1-julius%40bairaktaris.de
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-12 17:19 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-11 16:29 [PATCH net v2 0/3] ipmr: forward GSO multicast packets Julius Bairaktaris
2026-10-11 16:29 ` [PATCH net v2 1/3] net: make room for the tunnel header in skb_segment_list() Julius Bairaktaris
2026-10-12 17:19 ` netdev-bot+sashiko
2026-10-11 16:29 ` [PATCH net v2 2/3] ipmr: set up GSO state for tunnel vifs Julius Bairaktaris
2026-10-12 17:19 ` netdev-bot+sashiko
2026-10-11 16:29 ` [PATCH net v2 3/3] 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
all inboxes | Powered by JetHome®