From: Chengfeng Ye <nicoyip.dev@gmail.com>
To: David Ahern <dsahern@kernel.org>,
Ido Schimmel <idosch@nvidia.com>,
netdev@vger.kernel.org
Cc: "David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>, Xin Long <lucien.xin@gmail.com>,
William Tu <u9012063@gmail.com>,
linux-kernel@vger.kernel.org,
Chengfeng Ye <nicoyip.dev@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH net v3] ip_tunnel: reserve FOU/GUE headroom before encapsulation
Date: Mon, 24 Aug 2026 19:19:44 +0800 [thread overview]
Message-ID: <20260824111944.187200-1-nicoyip.dev@gmail.com> (raw)
ip_tunnel_encap() expects its callers to reserve headroom based on
ip_encap_hlen(). Unlike the IPv6 tunnel transmit paths, the IPv4
ip_tunnel_xmit() and ip_md_tunnel_xmit() push FOU and GUE headers
before they grow the skb headroom.
That becomes visible when ipgre_changelink() publishes UDP
encapsulation before it updates the device headroom. The transmit path
does not serialize with RTNL, so it can interleave as follows:
CPU 0 (ipgre_changelink) CPU 1 (ipgre_xmit)
install GUE encapsulation
reserve the old needed_headroom
publish larger GRE flags
update tunnel->tun_hlen
push the larger GRE header
push the GUE and UDP headers
update dev->needed_headroom
With REMCSUM, the new layout can push 16 bytes of GRE and 20 bytes of
GUE/UDP headers into an skb with only 32 bytes of actual headroom. The
final UDP push writes four bytes before skb->head.
With the update window widened, the kernel reported:
skbuff: skb_under_panic: ... len:128 put:8 ... dev:gre0poc
kernel BUG at net/core/skbuff.c:214!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
Call Trace:
skb_push
fou_build_udp
gue_build_header
ip_tunnel_xmit
__gre_xmit
ipgre_xmit
Use ip_encap_hlen() up front, route and perform PMTU handling first,
then reserve the final headroom before ip_tunnel_encap() builds the
UDP tunnel headers. This matches the existing IPv6 pattern and keeps
ip_tunnel_encap() as a pure header builder.
Fixes: dd9d598c6657 ("ip_gre: add the support for i/o_flags update via netlink")
Cc: stable@vger.kernel.org
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
---
Changes in v3:
- Move the headroom reservation into ip_tunnel_xmit() and
ip_md_tunnel_xmit() instead of growing the skb inside the FOU/GUE
builders.
- Use ip_encap_hlen() to reserve the final caller-side headroom before
ip_tunnel_encap(), matching the existing IPv6 transmit pattern.
- Drop the IPv4 raw-pointer refreshes that were only needed when
skb_cow_head() could run inside the encapsulation builders.
Link: https://lore.kernel.org/netdev/20260808005956.3761487-1-nicoyip.dev@gmail.com/ [v2]
Link: https://lore.kernel.org/netdev/20260801060115.3538849-1-nicoyip.dev@gmail.com/ [v1]
---
net/ipv4/ip_tunnel.c | 28 +++++++++++++++++++++-------
1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 9d114bd575f9..5d5e7db11b3d 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -578,6 +578,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *inner_iph;
struct rtable *rt = NULL;
struct flowi4 fl4;
+ int encap_hlen;
__be16 df = 0;
u8 tos, ttl;
bool use_cache;
@@ -601,11 +602,11 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
tos & INET_DSCP_MASK, tunnel->net, 0, skb->mark,
skb_get_hash(skb), key->flow_flags);
- if (!tunnel_hlen)
- tunnel_hlen = ip_encap_hlen(&tun_info->encap);
-
- if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4) < 0)
+ encap_hlen = ip_encap_hlen(&tun_info->encap);
+ if (encap_hlen < 0)
goto tx_error;
+ if (!tunnel_hlen)
+ tunnel_hlen = encap_hlen;
use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
if (use_cache)
@@ -645,7 +646,8 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
ttl = ip4_dst_hoplimit(&rt->dst);
}
- headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len;
+ headroom += encap_hlen + LL_RESERVED_SPACE(rt->dst.dev) +
+ rt->dst.header_len;
if (skb_cow_head(skb, headroom)) {
ip_rt_put(rt);
goto tx_dropped;
@@ -653,6 +655,11 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
ip_tunnel_adj_headroom(dev, headroom);
+ if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4) < 0) {
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+
iptunnel_xmit(NULL, rt, skb, fl4.saddr, fl4.daddr, proto, tos, ttl,
df, !net_eq(tunnel->net, dev_net(dev)), 0);
return;
@@ -677,6 +684,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
__be16 payload_protocol;
bool use_cache = false;
struct flowi4 fl4;
+ int encap_hlen;
bool md = false;
bool connected;
int err_count;
@@ -765,7 +773,8 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
tunnel->net, READ_ONCE(tunnel->parms.link),
tunnel->fwmark, skb_get_hash(skb), 0);
- if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4) < 0)
+ encap_hlen = ip_encap_hlen(&tunnel->encap);
+ if (encap_hlen < 0)
goto tx_error;
if (connected && md) {
@@ -834,7 +843,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
}
max_headroom = LL_RESERVED_SPACE(rt->dst.dev) + sizeof(struct iphdr)
- + rt->dst.header_len + ip_encap_hlen(&tunnel->encap);
+ + rt->dst.header_len + encap_hlen;
if (skb_cow_head(skb, max_headroom)) {
ip_rt_put(rt);
@@ -845,6 +854,11 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
ip_tunnel_adj_headroom(dev, max_headroom);
+ if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4) < 0) {
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+
iptunnel_xmit(NULL, rt, skb, fl4.saddr, fl4.daddr, protocol, tos, ttl,
df, !net_eq(tunnel->net, dev_net(dev)), 0);
return;
--
2.43.0
next reply other threads:[~2026-08-24 11:19 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-24 11:19 Chengfeng Ye [this message]
2026-08-25 7:41 ` [PATCH net v3] ip_tunnel: reserve FOU/GUE headroom before encapsulation Ido Schimmel
2026-09-04 16:57 ` Chengfeng Ye
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260824111944.187200-1-nicoyip.dev@gmail.com \
--to=nicoyip.dev@gmail.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lucien.xin@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--cc=u9012063@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.