* [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons
@ 2026-11-10 20:20 Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 1/8] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
Only vxlan reports drop reasons among the tunnel drivers today. The ones
converted here free what they drop with kfree_skb(), which drop_monitor
and the skb:kfree_skb tracepoint do report, but as NOT_SPECIFIED, with
the call site as the only hint at which check failed. That hint does not
go far: all the failures of ip_tunnel_rcv() end at one call site, and so
do nearly all of those of each transmit function. The call site is not a
stable key either: its offset moves with the compiler, inlining and the
configuration, and the debug info that maps it back to a source line
only serves that one build, as the line moves with any change to the
file. So a filter on it has to follow every rebuild. The reason does not
move with the build: NET_DM_ATTR_REASON reports it, and a BPF program
can match it by name. The device counters group the failures coarsely
too: rx_errors and tx_errors each lump together unrelated conditions.
This series covers the receive paths shared by ipip, sit, gre and their
IPv6 counterparts, plus the GRE and ip6tnl specific receive code, and
the IPv4 transmit side of ip_tunnel and ip_gre; the code specific to
ipip and sit keeps kfree_skb(). So does the tunnel4 and tunnel6 demux,
shared with the xfrm tunnels, which frees the packets that no ipip, sit
or ip6tnl device takes. The IPv6 transmit side, ip6_tnl_xmit() and the
ip6_gre handlers, is left for a later series. GRE reports the reasons
vxlan reports for the same conditions, which commit 7629d6db839f
("vxlan: rename the drop reasons for use by other tunnels") made
generic.
Patches 1-2 convert the generic receive paths, ip_tunnel_rcv() and
__ip6_tnl_rcv(); patch 2 also converts ipxip6_rcv(), the ip6tnl handler
in front of the latter. Two reasons are added:
TUNNEL_OPT_MISMATCH the options a packet carries do not match the
tunnel configuration
TUNNEL_OLD_SEQ the sequence number is older than the one the
tunnel expects, like TCP_OLD_SEQUENCE for TCP
The second one has a failure mode worth naming: when a peer reboots, its
outgoing sequence number restarts at zero, and the receiver can drop
everything until the peer's numbers get past the last one the receiver
accepted. By the counters alone that looks like a misconfiguration: a
packet without the sequence number option bumps the same rx_fifo_errors.
Patches 3-5 convert the GRE specific receive path. gre_parse_header()
returns -EINVAL for every failure, and the only detail its callers could
get was a csum_err flag that none of them read: both ip_gre and ip6_gre
declared it, passed it in and ignored it. gre_parse_header() now returns
a drop reason instead, and its callers take the header length from
tpi->hdr_len. The receive helpers below gre_rcv() return the drop reason
instead of a PACKET_* code, and the PACKET_* codes go away. The GRE
receive paths report TUNNEL_INVALID_HDR and TUNNEL_NOT_FOUND, the length
checks report what pskb_may_pull_reason() returns, and one reason is
added, GRE_CSUM, like TCP_CSUM and UDP_CSUM.
Patches 6-8 convert the IPv4 transmit side. Patch 6 makes
ip_tunnel_encap() return a drop reason: NOMEM when a FOU or GUE callback
fails to allocate, and the new TUNNEL_ENCAP for any other failure to
build the encapsulation header. Patches 7-8 convert the transmit paths
of ip_tunnel and ip_gre.
The transmit side has its own case worth naming: tnl_update_pmtu()
returns -E2BIG after it has already sent the ICMP error back, which is
path MTU discovery working exactly as intended, yet among the device
counters the drop only bumps tx_errors, like a failed encapsulation and
a few other failures do. If the ICMP error never reaches the sender,
the resulting MTU black hole cannot be told from those by the device
counters; the reason tells them apart.
Drop reasons on transmit are not new: vxlan already reports several from
its xmit path, and ip_tunnel_core.c reports RECURSION_LIMIT. They are
most useful for forwarded packets, which is what a tunnel gateway mostly
transmits: the sender is another host, which gets an ICMP error for only
some of these failures, so the drop has to be explained on the gateway.
The reason variable follows one rule in the functions this series
converts that have a drop label: it is initialized to
SKB_DROP_REASON_NOT_SPECIFIED unless the first statement of the function
sets it, and every jump to the drop label comes with a reason of its
own. Every drop path of the series reports a reason other than
NOT_SPECIFIED, except the looped back multicast check in ip_gre's
gre_rcv() and a packet that ip6_tnl_rcv_ctl() refuses.
This series was developed with an LLM-based coding assistant (Claude
Code). It was used to write and revise the code and the changelogs, to
build and test the series in virtme-ng, and to review it before posting
with a local run of the Sashiko review prompts. I have reviewed every
change and every text and take responsibility for them.
Changes since v5:
- v5 is split (Ido): its vxlan patches 1-3 and 14 were posted on their
own and are in net-next; this is v5 4-10 plus the new patch 6, and
v5 11-13, the IPv6 transmit side, follow in a later series
- the drop labels no longer fall back to NOT_SPECIFIED: no jump reaches
them without a reason (Ido). In patch 3, until the handlers return a
reason, gre_rcv() of ip_gre and ip6_gre sets NOT_SPECIFIED once the
header is parsed; patches 4 and 5 remove that again
- new patch 6 makes ip_tunnel_encap() return a drop reason, so that an
allocation failure of the FOU and GUE callbacks is reported as NOMEM
rather than TUNNEL_ENCAP (Ido); TUNNEL_ENCAP is added there instead of
in patch 7. ip6_tnl_encap() follows with the IPv6 transmit side, as
its only caller is ip6_tnl_xmit()
- a packet that ip6_tnl_rcv_ctl() refuses is reported as NOT_SPECIFIED
instead of DEV_READY (Ido)
- the looped back multicast check in ip_gre's gre_rcv() sets
NOT_SPECIFIED explicitly instead of relying on the initializer, so
that every jump to a drop label comes with a reason of its own
- the description of TUNNEL_OLD_SEQ also names reordering as a cause,
not only a restart of the remote endpoint
- the description of RECURSION_LIMIT gives the tunnel case as an example
since the vxlan patches, so patch 7 no longer changes it
- the Assisted-by tags are dropped after commit 1e035297ffda ("docs:
remove Assisted-by references"); the paragraph above describes the
tool use instead
- v5: https://lore.kernel.org/netdev/20260930183910.3151873-1-littlesmilingcloud@gmail.com/
- vxlan part of v5: https://lore.kernel.org/netdev/20261005201417.906344-1-littlesmilingcloud@gmail.com/
- v4: https://lore.kernel.org/netdev/20260922221507.3268127-1-littlesmilingcloud@gmail.com/
- v3: https://lore.kernel.org/netdev/20260916143717.1875082-1-littlesmilingcloud@gmail.com/
- v2: https://lore.kernel.org/netdev/20260913034937.875068-1-littlesmilingcloud@gmail.com/
- v1: https://lore.kernel.org/netdev/20260831215137.549324-1-littlesmilingcloud@gmail.com/
Anton Danilov (8):
ip_tunnel: add drop reasons to the generic RX path
ip6_tunnel: add drop reasons to the receive path
gre: make gre_parse_header() report a drop reason
ip_gre: add drop reasons to the RX path
ip6_gre: add drop reasons to the RX path
ip_tunnel: make ip_tunnel_encap() return a drop reason
ip_tunnel: add drop reasons to the transmit path
ip_gre: add drop reasons to the transmit path
include/net/dropreason-core.h | 31 +++++-
include/net/gre.h | 5 +-
include/net/ip_tunnels.h | 20 ++--
net/ipv4/gre_demux.c | 64 +++++++----
net/ipv4/ip_gre.c | 197 ++++++++++++++++++++++------------
net/ipv4/ip_tunnel.c | 61 ++++++++---
net/ipv6/ip6_gre.c | 62 ++++++-----
net/ipv6/ip6_tunnel.c | 45 +++++---
net/ipv6/sit.c | 2 +-
9 files changed, 328 insertions(+), 159 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 1/8] ip_tunnel: add drop reasons to the generic RX path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 2/8] ip6_tunnel: add drop reasons to the receive path Anton Danilov
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
ip_tunnel_rcv() collapses four distinct failures into the single
kfree_skb() under its drop label:
- the tunnel options carried by the packet do not match the tunnel
configuration (checksum or sequence number),
- the sequence number is older than the expected one,
- the inner network header cannot be pulled,
- the ECN decapsulation check fails (RFC 6040).
drop_monitor and the skb:kfree_skb tracepoint do see these packets, but
all four as SKB_DROP_REASON_NOT_SPECIFIED from the same call site, so
neither the reason nor the location tells the failures apart. Only the
device error counters (rx_crc_errors, rx_fifo_errors, rx_length_errors,
rx_frame_errors) hint at the cause, and they are not tied to the
dropped packet.
Add two drop reasons for the tunnel specific cases and reuse the
existing ones for the rest:
- SKB_DROP_REASON_TUNNEL_OPT_MISMATCH is used when the packet lacks the
checksum or the sequence number option the tunnel is configured for,
or carries a checksum the tunnel is not configured for, as the
checksum check compares both ways. This is a configuration mismatch
between the two endpoints rather than a corrupted checksum: the
checksum itself is validated earlier, in gre_parse_header().
- SKB_DROP_REASON_TUNNEL_OLD_SEQ is used when the sequence number is
older than the expected one. Unlike the previous one, this is not a
configuration mismatch: the packet may have been reordered, in the
network or by the receive steering of this host, and a remote
endpoint that restarts and resets its sequence numbering can have all
of its packets dropped until its sequence numbers catch up with
i_seqno.
- pskb_inet_may_pull_reason() already computes a drop reason,
SKB_DROP_REASON_PKT_TOO_SMALL or SKB_DROP_REASON_NOMEM, which has so
far been discarded.
- SKB_DROP_REASON_IP_TUNNEL_ECN already exists and documents exactly
this check, but until now it was only used by vxlan.
The sequence number check is split in two so that the two cases can be
told apart. The error counters are left unchanged.
ip_tunnel_rcv() is the RX path of ip_gre, ipip and sit, the latter for
IPv4 and MPLS payloads only: ipip6_rcv() handles IPv6 in IPv4 on its
own. The checksum and the sequence number options only exist for GRE,
so the two new reasons are meant for ip_gre. ipip and sit carry
neither option and only hit SKB_DROP_REASON_TUNNEL_OPT_MISMATCH if their
i_flags are given those bits, for instance through IFLA_IPTUN_FLAGS;
such a device then drops every packet that reaches ip_tunnel_rcv(),
with or without this patch. The ECN reason applies to all three, while
the length ones can only be hit through ip_gre: tunnel4_rcv() has
already pulled the inner IPv4 header for ipip and sit, and
pskb_inet_may_pull_reason() has nothing to pull for an MPLS payload.
ip_tunnel_rcv() initializes the reason to SKB_DROP_REASON_NOT_SPECIFIED,
as its first statement does not set it, and every jump to its drop
label comes with a reason of its own. The rest of the series follows
the same rule wherever a function has a drop label: an initializer
unless the first statement sets the reason, and a reason of its own for
every jump to the label.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 17 +++++++++++++++++
net/ipv4/ip_tunnel.c | 19 +++++++++++++++----
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index fa2fe8990557..5c0d9b0211bc 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -128,6 +128,8 @@
FN(PSP_INPUT) \
FN(PSP_OUTPUT) \
FN(RECURSION_LIMIT) \
+ FN(TUNNEL_OPT_MISMATCH) \
+ FN(TUNNEL_OLD_SEQ) \
FNe(MAX)
/**
@@ -612,6 +614,21 @@ enum skb_drop_reason {
* itself.
*/
SKB_DROP_REASON_RECURSION_LIMIT,
+ /**
+ * @SKB_DROP_REASON_TUNNEL_OPT_MISMATCH: the tunnel options carried by
+ * the packet do not match the tunnel configuration, e.g. a GRE tunnel
+ * configured with 'icsum' or 'iseq' received a packet with no checksum
+ * or no sequence number, or a GRE tunnel without 'icsum' received a
+ * packet with a checksum.
+ */
+ SKB_DROP_REASON_TUNNEL_OPT_MISMATCH,
+ /**
+ * @SKB_DROP_REASON_TUNNEL_OLD_SEQ: the sequence number carried by the
+ * packet is older than the one expected by the tunnel, e.g. the packet
+ * was reordered in the network or by the receive processing, or the
+ * remote endpoint restarted and reset its sequence numbering.
+ */
+ SKB_DROP_REASON_TUNNEL_OLD_SEQ,
/**
* @SKB_DROP_REASON_MAX: the maximum of core drop reasons, which
* shouldn't be used as a real 'reason' - only for tracing code gen
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 0875474a578a..7d8744ed1664 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -384,6 +384,7 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
const struct tnl_ptk_info *tpi, struct metadata_dst *tun_dst,
bool log_ecn_error)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
const struct iphdr *iph = ip_hdr(skb);
int nh, err;
@@ -398,14 +399,22 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
test_bit(IP_TUNNEL_CSUM_BIT, tpi->flags)) {
DEV_STATS_INC(tunnel->dev, rx_crc_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OPT_MISMATCH;
goto drop;
}
if (test_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.i_flags)) {
- if (!test_bit(IP_TUNNEL_SEQ_BIT, tpi->flags) ||
- (tunnel->i_seqno && (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0)) {
+ if (!test_bit(IP_TUNNEL_SEQ_BIT, tpi->flags)) {
DEV_STATS_INC(tunnel->dev, rx_fifo_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OPT_MISMATCH;
+ goto drop;
+ }
+ if (tunnel->i_seqno &&
+ (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0) {
+ DEV_STATS_INC(tunnel->dev, rx_fifo_errors);
+ DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OLD_SEQ;
goto drop;
}
tunnel->i_seqno = ntohl(tpi->seq) + 1;
@@ -419,7 +428,8 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
skb_set_network_header(skb, (tunnel->dev->type == ARPHRD_ETHER) ? ETH_HLEN : 0);
- if (!pskb_inet_may_pull(skb)) {
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason) {
DEV_STATS_INC(tunnel->dev, rx_length_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
goto drop;
@@ -434,6 +444,7 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
if (err > 1) {
DEV_STATS_INC(tunnel->dev, rx_frame_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_IP_TUNNEL_ECN;
goto drop;
}
}
@@ -457,7 +468,7 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
drop:
if (tun_dst)
dst_release((struct dst_entry *)tun_dst);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return 0;
}
EXPORT_SYMBOL_GPL(ip_tunnel_rcv);
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 2/8] ip6_tunnel: add drop reasons to the receive path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 1/8] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 3/8] gre: make gre_parse_header() report a drop reason Anton Danilov
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
__ip6_tnl_rcv() mirrors its IPv4 counterpart: all of its failures share
a single plain kfree_skb(). Reuse the drop reasons that ip_tunnel_rcv()
now reports and the ones the length helpers already return.
Note that skb_vlan_inet_prepare() returns an enum skb_drop_reason that
has so far been discarded, and that the ETH_HLEN check gets one by
calling pskb_may_pull_reason() instead of pskb_may_pull().
__ip6_tnl_rcv() is reached two ways: ip6_gre (ip6gre, ip6gretap,
ip6erspan) goes through the exported ip6_tnl_rcv(), while the
ip6_tunnel encapsulations (ip4ip6, ip6ip6, mplsip6) reach it from
ipxip6_rcv(). Only ip6_gre sets the checksum and sequence number bits:
tpi_v4, tpi_v6 and tpi_mpls carry nothing but .proto, and unlike ipip
and sit, ip6_tunnel stores IFLA_IPTUN_FLAGS in parms.flags, not in
parms.i_flags. So the option mismatch and the old sequence reasons are
reachable through ip6_gre alone.
ipxip6_rcv() drops the packets it does not hand to __ip6_tnl_rcv() with
a plain kfree_skb() as well. Give them a reason too:
SKB_DROP_REASON_UNHANDLED_PROTO when the payload is not the one the
tunnel mode carries, SKB_DROP_REASON_XFRM_POLICY when the xfrm policy
check fails, the reason iptunnel_pull_header() returns and
SKB_DROP_REASON_NOMEM when the metadata dst cannot be allocated.
A packet that ip6_tnl_rcv_ctl() refuses stays
SKB_DROP_REASON_NOT_SPECIFIED, now set explicitly. The helper refuses
when the outer destination is not a usable local address, such as a
tentative one, when the outer source belongs to this host, and when the
tunnel cannot receive with the addresses of the packet, and it returns
only 0 or 1. Telling these apart needs ip6_tnl_rcv_ctl() and its
transmit counterpart ip6_tnl_xmit_ctl() to return a reason, which is
left for a follow-up.
ipxip6_rcv() and __ip6_tnl_rcv() follow the rule of ip_tunnel_rcv() for
the reason: it is initialized to SKB_DROP_REASON_NOT_SPECIFIED, and
every jump to the drop label comes with a reason of its own.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv6/ip6_tunnel.c | 45 +++++++++++++++++++++++++++++++------------
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 64a184bca467..4912b08228a1 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -813,6 +813,7 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
struct sk_buff *skb),
bool log_ecn_err)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
const struct ipv6hdr *ipv6h;
int nh, err;
@@ -820,15 +821,22 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
test_bit(IP_TUNNEL_CSUM_BIT, tpi->flags)) {
DEV_STATS_INC(tunnel->dev, rx_crc_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OPT_MISMATCH;
goto drop;
}
if (test_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.i_flags)) {
- if (!test_bit(IP_TUNNEL_SEQ_BIT, tpi->flags) ||
- (tunnel->i_seqno &&
- (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0)) {
+ if (!test_bit(IP_TUNNEL_SEQ_BIT, tpi->flags)) {
DEV_STATS_INC(tunnel->dev, rx_fifo_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OPT_MISMATCH;
+ goto drop;
+ }
+ if (tunnel->i_seqno &&
+ (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0) {
+ DEV_STATS_INC(tunnel->dev, rx_fifo_errors);
+ DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_TUNNEL_OLD_SEQ;
goto drop;
}
tunnel->i_seqno = ntohl(tpi->seq) + 1;
@@ -838,7 +846,8 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
/* Warning: All skb pointers will be invalidated! */
if (tunnel->dev->type == ARPHRD_ETHER) {
- if (!pskb_may_pull(skb, ETH_HLEN)) {
+ reason = pskb_may_pull_reason(skb, ETH_HLEN);
+ if (reason) {
DEV_STATS_INC(tunnel->dev, rx_length_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
goto drop;
@@ -859,7 +868,8 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
skb_reset_network_header(skb);
- if (skb_vlan_inet_prepare(skb, true)) {
+ reason = skb_vlan_inet_prepare(skb, true);
+ if (reason) {
DEV_STATS_INC(tunnel->dev, rx_length_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
goto drop;
@@ -881,6 +891,7 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
if (err > 1) {
DEV_STATS_INC(tunnel->dev, rx_frame_errors);
DEV_STATS_INC(tunnel->dev, rx_errors);
+ reason = SKB_DROP_REASON_IP_TUNNEL_ECN;
goto drop;
}
}
@@ -898,7 +909,7 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
drop:
if (tun_dst)
dst_release((struct dst_entry *)tun_dst);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return 0;
}
@@ -941,6 +952,7 @@ static int ipxip6_rcv(struct sk_buff *skb, u8 ipproto,
const struct ipv6hdr *ipv6h,
struct sk_buff *skb))
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip6_tnl *t;
const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
struct metadata_dst *tun_dst = NULL;
@@ -952,21 +964,30 @@ static int ipxip6_rcv(struct sk_buff *skb, u8 ipproto,
if (t) {
u8 tproto = READ_ONCE(t->parms.proto);
- if (tproto != ipproto && tproto != 0)
+ if (tproto != ipproto && tproto != 0) {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto drop;
- if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
+ }
+ if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
+ reason = SKB_DROP_REASON_XFRM_POLICY;
goto drop;
+ }
ipv6h = ipv6_hdr(skb);
- if (!ip6_tnl_rcv_ctl(t, &ipv6h->daddr, &ipv6h->saddr))
+ if (!ip6_tnl_rcv_ctl(t, &ipv6h->daddr, &ipv6h->saddr)) {
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
goto drop;
- if (iptunnel_pull_header(skb, 0, tpi->proto, false))
+ }
+ reason = iptunnel_pull_header(skb, 0, tpi->proto, false);
+ if (reason)
goto drop;
if (t->parms.collect_md) {
IP_TUNNEL_DECLARE_FLAGS(flags) = { };
tun_dst = ipv6_tun_rx_dst(skb, flags, 0, 0);
- if (!tun_dst)
+ if (!tun_dst) {
+ reason = SKB_DROP_REASON_NOMEM;
goto drop;
+ }
}
ret = __ip6_tnl_rcv(t, skb, tpi, tun_dst, dscp_ecn_decapsulate,
log_ecn_error);
@@ -978,7 +999,7 @@ static int ipxip6_rcv(struct sk_buff *skb, u8 ipproto,
drop:
rcu_read_unlock();
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return 0;
}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 3/8] gre: make gre_parse_header() report a drop reason
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 1/8] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 2/8] ip6_tunnel: add drop reasons to the receive path Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 4/8] ip_gre: add drop reasons to the RX path Anton Danilov
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
gre_parse_header() returns -EINVAL for every failure and its receive
callers turn that into a plain kfree_skb(). The only detail they could
get so far was the csum_err flag, which none of them actually reads:
both ip_gre and ip6_gre declare it, pass it in and then ignore it.
Make gre_parse_header() return the drop reason instead, with
SKB_NOT_DROPPED_YET for a valid header, and let the two receive paths
report it. The header length it used to return is already stored in
tpi->hdr_len, so the callers take it from there. The reasons are:
- SKB_DROP_REASON_TUNNEL_INVALID_HDR, for a header carrying an
unsupported version or the routing bit; its description gets a GRE
example,
- SKB_DROP_REASON_GRE_CSUM, a new reason for a checksum error, like the
existing TCP_CSUM, UDP_CSUM, ICMP_CSUM and IP_CSUM,
- the reason pskb_may_pull_reason() returns when the header cannot be
pulled: SKB_DROP_REASON_PKT_TOO_SMALL for a short packet and
SKB_DROP_REASON_NOMEM for an allocation failure. The byte that tells
WCCPv1 from WCCPv2 is read with skb_header_pointer() instead. It does
not say why it failed, and a short packet is the usual cause, so that
check returns SKB_DROP_REASON_PKT_TOO_SMALL.
gre_rcv() in the demux reports SKB_DROP_REASON_TUNNEL_INVALID_HDR for a
version it cannot dispatch at all, as gre_parse_header() does for a
nonzero version in ip6_gre, which does not go through the demux. A
valid version whose handler is not registered, such as PPTP, is
reported as SKB_DROP_REASON_UNHANDLED_PROTO.
csum_err had a second use: the ICMP error handlers pass NULL for it, so
that a checksum failure does not reject the header and the rest of it is
still parsed, as they only get a part of the original packet. That came
with commit b0350d51f001 ("ip_gre: fix parsing gre header in ipgre_err")
and becomes an explicit ignore_csum_err argument. The checksum is
still computed either way. As the function is exported, the comment
above it becomes a kernel-doc comment, which describes the argument.
Tunnel lookup failures and the other drops of the receive handlers
still report SKB_DROP_REASON_NOT_SPECIFIED at this point; the following
patches convert them. Until then, gre_rcv() of ip_gre and ip6_gre sets
the reason to SKB_DROP_REASON_NOT_SPECIFIED once the header is parsed,
as the handlers do not return one yet.
The reason follows the rule of ip_tunnel_rcv(). gre_rcv() of the demux
and of ip6_gre sets it in its first statement, so it is not initialized
there. ip_gre's gre_rcv() starts with the looped back multicast check,
so it initializes the reason, and that check, which drops before the
header is parsed, sets it to SKB_DROP_REASON_NOT_SPECIFIED explicitly.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 4 +++
include/net/gre.h | 5 +--
net/ipv4/gre_demux.c | 64 ++++++++++++++++++++++++-----------
net/ipv4/ip_gre.c | 23 +++++++------
net/ipv6/ip6_gre.c | 19 ++++++-----
5 files changed, 74 insertions(+), 41 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index 5c0d9b0211bc..de2796184e06 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -130,6 +130,7 @@
FN(RECURSION_LIMIT) \
FN(TUNNEL_OPT_MISMATCH) \
FN(TUNNEL_OLD_SEQ) \
+ FN(GRE_CSUM) \
FNe(MAX)
/**
@@ -537,6 +538,7 @@ enum skb_drop_reason {
* @SKB_DROP_REASON_TUNNEL_INVALID_HDR: tunnel header is invalid. E.g.:
* 1) VXLAN reserved fields are not zero
* 2) VXLAN "I" flag is not set
+ * 3) GRE version is not supported or the routing bit is set
*/
SKB_DROP_REASON_TUNNEL_INVALID_HDR,
/**
@@ -629,6 +631,8 @@ enum skb_drop_reason {
* remote endpoint restarted and reset its sequence numbering.
*/
SKB_DROP_REASON_TUNNEL_OLD_SEQ,
+ /** @SKB_DROP_REASON_GRE_CSUM: GRE checksum error */
+ SKB_DROP_REASON_GRE_CSUM,
/**
* @SKB_DROP_REASON_MAX: the maximum of core drop reasons, which
* shouldn't be used as a real 'reason' - only for tracing code gen
diff --git a/include/net/gre.h b/include/net/gre.h
index b55f67ecd2fc..b5dbe00ab15e 100644
--- a/include/net/gre.h
+++ b/include/net/gre.h
@@ -32,8 +32,9 @@ struct gre_protocol {
int gre_add_protocol(const struct gre_protocol *proto, u8 version);
int gre_del_protocol(const struct gre_protocol *proto, u8 version);
-int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
- bool *csum_err, __be16 proto, int nhs);
+enum skb_drop_reason
+gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
+ bool ignore_csum_err, __be16 proto, int nhs);
static inline bool netif_is_gretap(const struct net_device *dev)
{
diff --git a/net/ipv4/gre_demux.c b/net/ipv4/gre_demux.c
index 96fd7dc6d82d..6c1d1fe914b8 100644
--- a/net/ipv4/gre_demux.c
+++ b/net/ipv4/gre_demux.c
@@ -56,28 +56,47 @@ int gre_del_protocol(const struct gre_protocol *proto, u8 version)
}
EXPORT_SYMBOL_GPL(gre_del_protocol);
-/* Fills in tpi and returns header length to be pulled.
- * Note that caller must use pskb_may_pull() before pulling GRE header.
+/**
+ * gre_parse_header() - parse the GRE header of a packet
+ * @skb: the packet; the GRE header starts @nhs bytes past skb->data
+ * @tpi: filled in with the fields of the header, and with the length of
+ * the header to be pulled in tpi->hdr_len
+ * @ignore_csum_err: do not reject the header when its checksum does not
+ * match. The ICMP error handlers set it, as they only get a part of the
+ * original packet; the checksum is still computed and the rest of the
+ * header is parsed.
+ * @proto: the protocol to report for a WCCP payload, ETH_P_IP or
+ * ETH_P_IPV6
+ * @nhs: the offset of the GRE header from skb->data
+ *
+ * The caller must use pskb_may_pull() before pulling the GRE header.
+ *
+ * Return: SKB_NOT_DROPPED_YET, or the reason to drop the packet if the
+ * header is rejected.
*/
-int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
- bool *csum_err, __be16 proto, int nhs)
+enum skb_drop_reason
+gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
+ bool ignore_csum_err, __be16 proto, int nhs)
{
const struct gre_base_hdr *greh;
+ enum skb_drop_reason reason;
__be32 *options;
int hdr_len;
- if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
- return -EINVAL;
+ reason = pskb_may_pull_reason(skb, nhs + sizeof(struct gre_base_hdr));
+ if (unlikely(reason))
+ return reason;
greh = (struct gre_base_hdr *)(skb->data + nhs);
if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
- return -EINVAL;
+ return SKB_DROP_REASON_TUNNEL_INVALID_HDR;
gre_flags_to_tnl_flags(tpi->flags, greh->flags);
hdr_len = gre_calc_hlen(tpi->flags);
- if (!pskb_may_pull(skb, nhs + hdr_len))
- return -EINVAL;
+ reason = pskb_may_pull_reason(skb, nhs + hdr_len);
+ if (reason)
+ return reason;
greh = (struct gre_base_hdr *)(skb->data + nhs);
tpi->proto = greh->protocol;
@@ -87,9 +106,8 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
if (!skb_checksum_simple_validate(skb)) {
skb_checksum_try_convert(skb, IPPROTO_GRE,
null_compute_pseudo);
- } else if (csum_err) {
- *csum_err = true;
- return -EINVAL;
+ } else if (!ignore_csum_err) {
+ return SKB_DROP_REASON_GRE_CSUM;
}
options++;
@@ -117,7 +135,7 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
val = skb_header_pointer(skb, nhs + hdr_len,
sizeof(_val), &_val);
if (!val)
- return -EINVAL;
+ return SKB_DROP_REASON_PKT_TOO_SMALL;
tpi->proto = proto;
if ((*val & 0xF0) != 0x40)
hdr_len += 4;
@@ -132,29 +150,35 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
greh->protocol == htons(ETH_P_ERSPAN2)) {
struct erspan_base_hdr *ershdr;
- if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr)))
- return -EINVAL;
+ reason = pskb_may_pull_reason(skb,
+ nhs + hdr_len + sizeof(*ershdr));
+ if (reason)
+ return reason;
ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len);
tpi->key = cpu_to_be32(get_session_id(ershdr));
}
- return hdr_len;
+ return SKB_NOT_DROPPED_YET;
}
EXPORT_SYMBOL(gre_parse_header);
static int gre_rcv(struct sk_buff *skb)
{
const struct gre_protocol *proto;
+ enum skb_drop_reason reason;
u8 ver;
int ret;
- if (!pskb_may_pull(skb, 12))
+ reason = pskb_may_pull_reason(skb, 12);
+ if (reason)
goto drop;
ver = skb->data[1]&0x7f;
- if (ver >= GREPROTO_MAX)
+ if (ver >= GREPROTO_MAX) {
+ reason = SKB_DROP_REASON_TUNNEL_INVALID_HDR;
goto drop;
+ }
rcu_read_lock();
proto = rcu_dereference(gre_proto[ver]);
@@ -167,11 +191,11 @@ static int gre_rcv(struct sk_buff *skb)
drop_nohandler:
rcu_read_unlock();
dev_core_stats_rx_nohandler_inc(skb->dev);
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_UNHANDLED_PROTO);
return NET_RX_DROP;
drop:
dev_core_stats_rx_dropped_inc(skb->dev);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return NET_RX_DROP;
}
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 5485586fa0e6..10f1a5a2fd91 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -237,8 +237,8 @@ static void gre_err(struct sk_buff *skb, u32 info)
const int code = icmp_hdr(skb)->code;
struct tnl_ptk_info tpi;
- if (gre_parse_header(skb, &tpi, NULL, htons(ETH_P_IP),
- iph->ihl * 4) < 0)
+ if (gre_parse_header(skb, &tpi, true, htons(ETH_P_IP),
+ iph->ihl * 4))
return;
if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
@@ -439,37 +439,40 @@ static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
static int gre_rcv(struct sk_buff *skb)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct tnl_ptk_info tpi;
- bool csum_err = false;
- int hdr_len;
#ifdef CONFIG_NET_IPGRE_BROADCAST
if (ipv4_is_multicast(ip_hdr(skb)->daddr)) {
/* Looped back packet, drop it! */
- if (rt_is_output_route(skb_rtable(skb)))
+ if (rt_is_output_route(skb_rtable(skb))) {
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
goto drop;
+ }
}
#endif
- hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP), 0);
- if (hdr_len < 0)
+ reason = gre_parse_header(skb, &tpi, false, htons(ETH_P_IP), 0);
+ if (reason)
goto drop;
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
+
if (unlikely(tpi.proto == htons(ETH_P_ERSPAN) ||
tpi.proto == htons(ETH_P_ERSPAN2))) {
- if (erspan_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
+ if (erspan_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
return 0;
goto out;
}
- if (ipgre_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
+ if (ipgre_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
return 0;
out:
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
drop:
dev_core_stats_rx_dropped_inc(skb->dev);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return 0;
}
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index b5d6ed8f0c36..75665a576657 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -389,8 +389,8 @@ static int ip6gre_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
struct tnl_ptk_info tpi;
struct ip6_tnl *t;
- if (gre_parse_header(skb, &tpi, NULL, htons(ETH_P_IPV6),
- offset) < 0)
+ if (gre_parse_header(skb, &tpi, true, htons(ETH_P_IPV6),
+ offset))
return -EINVAL;
ipv6h = (const struct ipv6hdr *)skb->data;
@@ -566,20 +566,21 @@ static int ip6erspan_rcv(struct sk_buff *skb,
static int gre_rcv(struct sk_buff *skb)
{
+ enum skb_drop_reason reason;
struct tnl_ptk_info tpi;
- bool csum_err = false;
- int hdr_len;
- hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IPV6), 0);
- if (hdr_len < 0)
+ reason = gre_parse_header(skb, &tpi, false, htons(ETH_P_IPV6), 0);
+ if (reason)
goto drop;
- if (iptunnel_pull_header(skb, hdr_len, tpi.proto, false))
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
+
+ if (iptunnel_pull_header(skb, tpi.hdr_len, tpi.proto, false))
goto drop;
if (unlikely(tpi.proto == htons(ETH_P_ERSPAN) ||
tpi.proto == htons(ETH_P_ERSPAN2))) {
- if (ip6erspan_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
+ if (ip6erspan_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
return 0;
goto out;
}
@@ -591,7 +592,7 @@ static int gre_rcv(struct sk_buff *skb)
icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
drop:
dev_core_stats_rx_dropped_inc(skb->dev);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return 0;
}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 4/8] ip_gre: add drop reasons to the RX path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
` (2 preceding siblings ...)
2026-11-10 20:20 ` [PATCH net-next v6 3/8] gre: make gre_parse_header() report a drop reason Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 5/8] ip6_gre: " Anton Danilov
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
A packet that reaches gre_rcv() and does not belong to any tunnel is
dropped, after an ICMP port unreachable is sent back, as
SKB_DROP_REASON_NOT_SPECIFIED from the same kfree_skb_reason() call as
a truncated ERSPAN header or a failed metadata allocation. This is the
GRE counterpart of a UDP packet hitting no socket, yet neither the
reason nor the call site tells it apart. The fallback devices such as
gre0 only take such a packet while they are up, and they are created
down.
Report SKB_DROP_REASON_TUNNEL_NOT_FOUND for it, which vxlan reports when
no device matches the VNI, and add GRE to its description.
erspan_rcv(), __ipgre_rcv() and ipgre_rcv() now return
SKB_NOT_DROPPED_YET where they returned PACKET_RCVD, including the paths
that free the packet themselves, and a drop reason where they returned
PACKET_REJECT or PACKET_NEXT. __ipgre_rcv() only returned PACKET_NEXT
when no tunnel matched, so the retry ipgre_rcv() does for ETH_P_TEB is
now taken on SKB_DROP_REASON_TUNNEL_NOT_FOUND.
The length checks use pskb_may_pull_reason(), and the headers are
pulled with __iptunnel_pull_header(), which returns a drop reason since
commit 6c0509aa8900 ("ip_tunnel: make __iptunnel_pull_header() return a
drop reason"). Both report a packet too short to pull
as SKB_DROP_REASON_PKT_TOO_SMALL and an allocation failure as
SKB_DROP_REASON_NOMEM. The metadata allocation failures reuse
SKB_DROP_REASON_NOMEM as well.
erspan_rcv() and __ipgre_rcv() follow the same rule for the reason as
gre_rcv(): it is initialized to SKB_DROP_REASON_NOT_SPECIFIED. As the
handlers now return a reason, gre_rcv() no longer sets
SKB_DROP_REASON_NOT_SPECIFIED once the header is parsed.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 3 +-
net/ipv4/ip_gre.c | 81 +++++++++++++++++++----------------
2 files changed, 47 insertions(+), 37 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index de2796184e06..2df44745cf3b 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -543,7 +543,8 @@ enum skb_drop_reason {
SKB_DROP_REASON_TUNNEL_INVALID_HDR,
/**
* @SKB_DROP_REASON_TUNNEL_NOT_FOUND: no tunnel device found for the
- * packet, e.g. no VXLAN device for its VNI
+ * packet, e.g. no VXLAN device for its VNI or no GRE tunnel for its
+ * endpoints and key
*/
SKB_DROP_REASON_TUNNEL_NOT_FOUND,
/** @SKB_DROP_REASON_MAC_INVALID_SOURCE: source mac is invalid */
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 10f1a5a2fd91..c93c395c997a 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -264,9 +264,11 @@ static bool is_erspan_type1(int gre_hdr_len)
return gre_hdr_len == 4;
}
-static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
- int gre_hdr_len)
+static enum skb_drop_reason erspan_rcv(struct sk_buff *skb,
+ struct tnl_ptk_info *tpi,
+ int gre_hdr_len)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct net *net = dev_net(skb->dev);
struct metadata_dst *tun_dst = NULL;
struct erspan_base_hdr *ershdr;
@@ -288,9 +290,10 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, flags,
iph->saddr, iph->daddr, 0);
} else {
- if (unlikely(!pskb_may_pull(skb,
- gre_hdr_len + sizeof(*ershdr))))
- return PACKET_REJECT;
+ reason = pskb_may_pull_reason(skb,
+ gre_hdr_len + sizeof(*ershdr));
+ if (unlikely(reason))
+ return reason;
ershdr = (struct erspan_base_hdr *)(skb->data + gre_hdr_len);
ver = ershdr->ver;
@@ -306,13 +309,13 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
else
len = gre_hdr_len + erspan_hdr_len(ver);
- if (unlikely(!pskb_may_pull(skb, len)))
- return PACKET_REJECT;
+ reason = pskb_may_pull_reason(skb, len);
+ if (unlikely(reason))
+ return reason;
- if (__iptunnel_pull_header(skb,
- len,
- htons(ETH_P_TEB),
- false, false))
+ reason = __iptunnel_pull_header(skb, len, htons(ETH_P_TEB),
+ false, false);
+ if (reason)
goto drop;
if (tunnel->collect_md) {
@@ -328,7 +331,7 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
tun_dst = ip_tun_rx_dst(skb, flags,
tun_id, sizeof(*md));
if (!tun_dst)
- return PACKET_REJECT;
+ return SKB_DROP_REASON_NOMEM;
/* MUST set options_len before referencing options */
info = &tun_dst->u.tun_info;
@@ -354,18 +357,21 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
skb_reset_mac_header(skb);
ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
- return PACKET_RCVD;
+ return SKB_NOT_DROPPED_YET;
}
- return PACKET_REJECT;
+ return SKB_DROP_REASON_TUNNEL_NOT_FOUND;
drop:
- kfree_skb(skb);
- return PACKET_RCVD;
+ kfree_skb_reason(skb, reason);
+ return SKB_NOT_DROPPED_YET;
}
-static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
- struct ip_tunnel_net *itn, int hdr_len, bool raw_proto)
+static enum skb_drop_reason __ipgre_rcv(struct sk_buff *skb,
+ const struct tnl_ptk_info *tpi,
+ struct ip_tunnel_net *itn,
+ int hdr_len, bool raw_proto)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct metadata_dst *tun_dst = NULL;
const struct iphdr *iph;
struct ip_tunnel *tunnel;
@@ -377,8 +383,9 @@ static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
if (tunnel) {
const struct iphdr *tnl_params;
- if (__iptunnel_pull_header(skb, hdr_len, tpi->proto,
- raw_proto, false))
+ reason = __iptunnel_pull_header(skb, hdr_len, tpi->proto,
+ raw_proto, false);
+ if (reason)
goto drop;
/* Special case for ipgre_header_parse(), which expects the
@@ -401,40 +408,42 @@ static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
tun_id = key32_to_tunnel_id(tpi->key);
tun_dst = ip_tun_rx_dst(skb, flags, tun_id, 0);
if (!tun_dst)
- return PACKET_REJECT;
+ return SKB_DROP_REASON_NOMEM;
}
ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
- return PACKET_RCVD;
+ return SKB_NOT_DROPPED_YET;
}
- return PACKET_NEXT;
+ return SKB_DROP_REASON_TUNNEL_NOT_FOUND;
drop:
- kfree_skb(skb);
- return PACKET_RCVD;
+ kfree_skb_reason(skb, reason);
+ return SKB_NOT_DROPPED_YET;
}
-static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
- int hdr_len)
+static enum skb_drop_reason ipgre_rcv(struct sk_buff *skb,
+ const struct tnl_ptk_info *tpi,
+ int hdr_len)
{
struct net *net = dev_net(skb->dev);
+ enum skb_drop_reason reason;
struct ip_tunnel_net *itn;
- int res;
if (tpi->proto == htons(ETH_P_TEB))
itn = net_generic(net, gre_tap_net_id);
else
itn = net_generic(net, ipgre_net_id);
- res = __ipgre_rcv(skb, tpi, itn, hdr_len, false);
- if (res == PACKET_NEXT && tpi->proto == htons(ETH_P_TEB)) {
+ reason = __ipgre_rcv(skb, tpi, itn, hdr_len, false);
+ if (reason == SKB_DROP_REASON_TUNNEL_NOT_FOUND &&
+ tpi->proto == htons(ETH_P_TEB)) {
/* ipgre tunnels in collect metadata mode should receive
* also ETH_P_TEB traffic.
*/
itn = net_generic(net, ipgre_net_id);
- res = __ipgre_rcv(skb, tpi, itn, hdr_len, true);
+ reason = __ipgre_rcv(skb, tpi, itn, hdr_len, true);
}
- return res;
+ return reason;
}
static int gre_rcv(struct sk_buff *skb)
@@ -456,16 +465,16 @@ static int gre_rcv(struct sk_buff *skb)
if (reason)
goto drop;
- reason = SKB_DROP_REASON_NOT_SPECIFIED;
-
if (unlikely(tpi.proto == htons(ETH_P_ERSPAN) ||
tpi.proto == htons(ETH_P_ERSPAN2))) {
- if (erspan_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
+ reason = erspan_rcv(skb, &tpi, tpi.hdr_len);
+ if (!reason)
return 0;
goto out;
}
- if (ipgre_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
+ reason = ipgre_rcv(skb, &tpi, tpi.hdr_len);
+ if (!reason)
return 0;
out:
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 5/8] ip6_gre: add drop reasons to the RX path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
` (3 preceding siblings ...)
2026-11-10 20:20 ` [PATCH net-next v6 4/8] ip_gre: add drop reasons to the RX path Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 6/8] ip_tunnel: make ip_tunnel_encap() return a drop reason Anton Danilov
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
Mirror the previous patch on the IPv6 side. So far gre_rcv() reported
the drops below as SKB_DROP_REASON_NOT_SPECIFIED, all from the same
kfree_skb_reason() call. Now it reports:
- SKB_DROP_REASON_TUNNEL_NOT_FOUND when no tunnel matches,
- what pskb_may_pull_reason() returns when an ERSPAN header cannot be
pulled,
- what iptunnel_pull_header() and __iptunnel_pull_header() return
when the pull fails,
- SKB_DROP_REASON_NOMEM when the metadata dst cannot be allocated.
Unlike ip_gre, gre_rcv() calls the helper before the tunnel lookup, so a
packet whose ETH_P_TEB inner Ethernet header or WCCPv2 extra word is cut
short is reported as SKB_DROP_REASON_PKT_TOO_SMALL whether a tunnel
matches or not.
Like their IPv4 counterparts, ip6gre_rcv() and ip6erspan_rcv() now
return SKB_NOT_DROPPED_YET where they returned PACKET_RCVD and a drop
reason where they returned PACKET_REJECT. That leaves PACKET_RCVD,
PACKET_REJECT and PACKET_NEXT without users, so remove them.
PACKET_REJECT has the value of SKB_CONSUMED, and a stray one in a
function that now returns a drop reason would build silently and be
traced as a consumed packet.
gre_rcv() no longer sets SKB_DROP_REASON_NOT_SPECIFIED once the header
is parsed: every drop now gets its reason from the helper or the handler
that fails.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/ip_tunnels.h | 4 ----
net/ipv6/ip6_gre.c | 51 ++++++++++++++++++++++------------------
2 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 5cccf4c0e691..792690ae4f26 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -207,10 +207,6 @@ struct tnl_ptk_info {
int hdr_len;
};
-#define PACKET_RCVD 0
-#define PACKET_REJECT 1
-#define PACKET_NEXT 2
-
#define IP_TNL_HASH_BITS 7
#define IP_TNL_HASH_SIZE (1 << IP_TNL_HASH_BITS)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 75665a576657..eae170ea0ba6 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -451,7 +451,8 @@ static int ip6gre_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
return 0;
}
-static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
+static enum skb_drop_reason ip6gre_rcv(struct sk_buff *skb,
+ const struct tnl_ptk_info *tpi)
{
const struct ipv6hdr *ipv6h;
struct ip6_tnl *tunnel;
@@ -471,31 +472,33 @@ static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
tun_dst = ipv6_tun_rx_dst(skb, flags, tun_id, 0);
if (!tun_dst)
- return PACKET_REJECT;
+ return SKB_DROP_REASON_NOMEM;
ip6_tnl_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
} else {
ip6_tnl_rcv(tunnel, skb, tpi, NULL, log_ecn_error);
}
- return PACKET_RCVD;
+ return SKB_NOT_DROPPED_YET;
}
- return PACKET_REJECT;
+ return SKB_DROP_REASON_TUNNEL_NOT_FOUND;
}
-static int ip6erspan_rcv(struct sk_buff *skb,
- struct tnl_ptk_info *tpi,
- int gre_hdr_len)
+static enum skb_drop_reason ip6erspan_rcv(struct sk_buff *skb,
+ struct tnl_ptk_info *tpi,
+ int gre_hdr_len)
{
struct erspan_base_hdr *ershdr;
const struct ipv6hdr *ipv6h;
+ enum skb_drop_reason reason;
struct erspan_md2 *md2;
struct ip6_tnl *tunnel;
u8 ver;
- if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr))))
- return PACKET_REJECT;
+ reason = pskb_may_pull_reason(skb, sizeof(*ershdr));
+ if (unlikely(reason))
+ return reason;
ipv6h = ipv6_hdr(skb);
ershdr = (struct erspan_base_hdr *)skb->data;
@@ -507,13 +510,14 @@ static int ip6erspan_rcv(struct sk_buff *skb,
if (tunnel) {
int len = erspan_hdr_len(ver);
- if (unlikely(!pskb_may_pull(skb, len)))
- return PACKET_REJECT;
+ reason = pskb_may_pull_reason(skb, len);
+ if (unlikely(reason))
+ return reason;
- if (__iptunnel_pull_header(skb, len,
- htons(ETH_P_TEB),
- false, false))
- return PACKET_REJECT;
+ reason = __iptunnel_pull_header(skb, len, htons(ETH_P_TEB),
+ false, false);
+ if (reason)
+ return reason;
if (tunnel->parms.collect_md) {
struct erspan_metadata *pkt_md, *md;
@@ -530,7 +534,7 @@ static int ip6erspan_rcv(struct sk_buff *skb,
tun_dst = ipv6_tun_rx_dst(skb, flags, tun_id,
sizeof(*md));
if (!tun_dst)
- return PACKET_REJECT;
+ return SKB_DROP_REASON_NOMEM;
/* MUST set options_len before referencing options */
info = &tun_dst->u.tun_info;
@@ -558,10 +562,10 @@ static int ip6erspan_rcv(struct sk_buff *skb,
ip6_tnl_rcv(tunnel, skb, tpi, NULL, log_ecn_error);
}
- return PACKET_RCVD;
+ return SKB_NOT_DROPPED_YET;
}
- return PACKET_REJECT;
+ return SKB_DROP_REASON_TUNNEL_NOT_FOUND;
}
static int gre_rcv(struct sk_buff *skb)
@@ -573,19 +577,20 @@ static int gre_rcv(struct sk_buff *skb)
if (reason)
goto drop;
- reason = SKB_DROP_REASON_NOT_SPECIFIED;
-
- if (iptunnel_pull_header(skb, tpi.hdr_len, tpi.proto, false))
+ reason = iptunnel_pull_header(skb, tpi.hdr_len, tpi.proto, false);
+ if (reason)
goto drop;
if (unlikely(tpi.proto == htons(ETH_P_ERSPAN) ||
tpi.proto == htons(ETH_P_ERSPAN2))) {
- if (ip6erspan_rcv(skb, &tpi, tpi.hdr_len) == PACKET_RCVD)
+ reason = ip6erspan_rcv(skb, &tpi, tpi.hdr_len);
+ if (!reason)
return 0;
goto out;
}
- if (ip6gre_rcv(skb, &tpi) == PACKET_RCVD)
+ reason = ip6gre_rcv(skb, &tpi);
+ if (!reason)
return 0;
out:
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 6/8] ip_tunnel: make ip_tunnel_encap() return a drop reason
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
` (4 preceding siblings ...)
2026-11-10 20:20 ` [PATCH net-next v6 5/8] ip6_gre: " Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 7/8] ip_tunnel: add drop reasons to the transmit path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 8/8] ip_gre: " Anton Danilov
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
ip_tunnel_encap() builds the FOU or GUE header of an IPv4 tunnel and
returns 0 or a negative error, so its callers can tell that the packet
has to be dropped, but not why.
Make it return the drop reason instead, like __iptunnel_pull_header()
and gre_parse_header() do:
- SKB_NOT_DROPPED_YET when no encapsulation is configured or the
header is built,
- SKB_DROP_REASON_NOMEM when the build_header() callback fails with
-ENOMEM, which the FOU and GUE callbacks do when the header of a GSO
packet cannot be uncloned,
- SKB_DROP_REASON_TUNNEL_ENCAP, a new reason, for any other failure,
such as an encapsulation type without a registered callback.
The three callers, two in ip_tunnel.c and one in sit.c, tested the
result with "< 0". With the enum, that test would always be false and
no compiler warns about it, so they test for a non-zero value now. The
next patch reports the reason in the transmit path of ip_tunnel; sit
still frees the packet with a plain kfree_skb().
ip6_tnl_encap() keeps returning an int for now: its only caller,
ip6_tnl_xmit(), returns an int as well, and both belong to the IPv6
transmit path, which is left for a later series.
Suggested-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 7 +++++++
include/net/ip_tunnels.h | 16 ++++++++++------
net/ipv4/ip_tunnel.c | 4 ++--
net/ipv6/sit.c | 2 +-
4 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index 2df44745cf3b..fb6f3d8da960 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -131,6 +131,7 @@
FN(TUNNEL_OPT_MISMATCH) \
FN(TUNNEL_OLD_SEQ) \
FN(GRE_CSUM) \
+ FN(TUNNEL_ENCAP) \
FNe(MAX)
/**
@@ -634,6 +635,12 @@ enum skb_drop_reason {
SKB_DROP_REASON_TUNNEL_OLD_SEQ,
/** @SKB_DROP_REASON_GRE_CSUM: GRE checksum error */
SKB_DROP_REASON_GRE_CSUM,
+ /**
+ * @SKB_DROP_REASON_TUNNEL_ENCAP: failed to build the encapsulation
+ * header of a tunnel, e.g. an unknown or unregistered encapsulation
+ * type.
+ */
+ SKB_DROP_REASON_TUNNEL_ENCAP,
/**
* @SKB_DROP_REASON_MAX: the maximum of core drop reasons, which
* shouldn't be used as a real 'reason' - only for tracing code gen
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 792690ae4f26..87046ba50d45 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -541,18 +541,18 @@ static inline int ip_encap_hlen(struct ip_tunnel_encap *e)
return hlen;
}
-static inline int ip_tunnel_encap(struct sk_buff *skb,
- struct ip_tunnel_encap *e,
- u8 *protocol, struct flowi4 *fl4)
+static inline enum skb_drop_reason
+ip_tunnel_encap(struct sk_buff *skb, struct ip_tunnel_encap *e,
+ u8 *protocol, struct flowi4 *fl4)
{
const struct ip_tunnel_encap_ops *ops;
int ret = -EINVAL;
if (e->type == TUNNEL_ENCAP_NONE)
- return 0;
+ return SKB_NOT_DROPPED_YET;
if (e->type >= MAX_IPTUN_ENCAP_OPS)
- return -EINVAL;
+ return SKB_DROP_REASON_TUNNEL_ENCAP;
rcu_read_lock();
ops = rcu_dereference(iptun_encaps[e->type]);
@@ -560,7 +560,11 @@ static inline int ip_tunnel_encap(struct sk_buff *skb,
ret = ops->build_header(skb, e, protocol, fl4);
rcu_read_unlock();
- return ret;
+ if (!ret)
+ return SKB_NOT_DROPPED_YET;
+ if (ret == -ENOMEM)
+ return SKB_DROP_REASON_NOMEM;
+ return SKB_DROP_REASON_TUNNEL_ENCAP;
}
/* Extract dsfield from inner protocol */
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 7d8744ed1664..f3c5d8424925 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -619,7 +619,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (!tunnel_hlen)
tunnel_hlen = ip_encap_hlen(&tun_info->encap);
- if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4) < 0)
+ if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4))
goto tx_error;
use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
@@ -780,7 +780,7 @@ 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)
+ if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4))
goto tx_error;
if (connected && md) {
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index a7ec800216ad..9576d146c961 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -1096,7 +1096,7 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
ttl = iph6->hop_limit;
tos = INET_ECN_encapsulate(tos, ipv6_get_dsfield(iph6));
- if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4) < 0) {
+ if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4)) {
ip_rt_put(rt);
goto tx_error;
}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 7/8] ip_tunnel: add drop reasons to the transmit path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
` (5 preceding siblings ...)
2026-11-10 20:20 ` [PATCH net-next v6 6/8] ip_tunnel: make ip_tunnel_encap() return a drop reason Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 8/8] ip_gre: " Anton Danilov
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
ip_tunnel_xmit() and ip_md_tunnel_xmit() encapsulate the packets sent
through the tunnel, and every failure these two functions detect ends in
a plain kfree_skb(). The device counters separate them a little, but
they are too coarse to act on: tx_errors counts an encapsulation
failure, a routing failure, a routing loop and a packet that is simply
too big alike.
The packet that is too big deserves attention. tnl_update_pmtu()
returns -E2BIG for a non-GSO packet larger than the MTU if it is IPv4
with the DF bit set, or IPv6 and the MTU is at least IPV6_MIN_MTU, after
it has already sent the ICMP error back to the sender. That is path MTU
discovery working as intended, yet among the device counters the drop
only bumps tx_errors, like a failed encapsulation and a few other
failures do. If the ICMP error never reaches the sender, the resulting
MTU black hole cannot be told from those by the device counters. A
failed route lookup and a routing loop do have counters of their own,
tx_carrier_errors and collisions, but in both functions all of these
packets end up in the same kfree_skb() call.
No new reason is needed:
- SKB_DROP_REASON_PKT_TOO_BIG for the case above,
- SKB_DROP_REASON_IP_OUTNOROUTES when the route lookup fails,
- SKB_DROP_REASON_RECURSION_LIMIT when the route points back at the
tunnel device itself, the case its description gives as an example,
- SKB_DROP_REASON_NOMEM when the headroom cannot be expanded,
- SKB_DROP_REASON_NEIGH_CREATEFAIL when the NBMA neighbour lookup
fails, and SKB_DROP_REASON_NO_TX_TARGET when no destination can be
derived at all, which includes a payload that is neither IPv4 nor
IPv6,
- SKB_DROP_REASON_TUNNEL_TXINFO, which already documents a packet
reaching an external mode device without metadata, for the
collect_md path,
- the reason ip_tunnel_encap() returns since the previous patch, when
the encapsulation header cannot be built.
Drop reasons on transmit are not new: vxlan already reports several of
them from its xmit path, and ip_tunnel_core.c reports
SKB_DROP_REASON_RECURSION_LIMIT. They are most useful for forwarded
packets, which is what a tunnel gateway mostly transmits: the sender is
another host, which gets an ICMP error for only some of these failures,
so the drop has to be explained on the gateway.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv4/ip_tunnel.c | 42 ++++++++++++++++++++++++++++++------------
1 file changed, 30 insertions(+), 12 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index f3c5d8424925..924d02d0cc7b 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -586,6 +586,7 @@ static int tnl_update_pmtu(struct net_device *dev, struct sk_buff *skb,
void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
u8 proto, int tunnel_hlen)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel *tunnel = netdev_priv(dev);
u32 headroom = sizeof(struct iphdr);
struct ip_tunnel_info *tun_info;
@@ -599,8 +600,10 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
tun_info = skb_tunnel_info(skb);
if (unlikely(!tun_info || !(tun_info->mode & IP_TUNNEL_INFO_TX) ||
- ip_tunnel_info_af(tun_info) != AF_INET))
+ ip_tunnel_info_af(tun_info) != AF_INET)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto tx_error;
+ }
key = &tun_info->key;
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
@@ -619,7 +622,8 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (!tunnel_hlen)
tunnel_hlen = ip_encap_hlen(&tun_info->encap);
- if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4))
+ reason = ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4);
+ if (reason)
goto tx_error;
use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
@@ -629,6 +633,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
rt = ip_route_output_key(tunnel->net, &fl4);
if (IS_ERR(rt)) {
DEV_STATS_INC(dev, tx_carrier_errors);
+ reason = SKB_DROP_REASON_IP_OUTNOROUTES;
goto tx_error;
}
if (use_cache)
@@ -638,6 +643,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (rt->dst.dev == dev) {
ip_rt_put(rt);
DEV_STATS_INC(dev, collisions);
+ reason = SKB_DROP_REASON_RECURSION_LIMIT;
goto tx_error;
}
@@ -646,6 +652,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (tnl_update_pmtu(dev, skb, rt, df, inner_iph, tunnel_hlen,
key->u.ipv4.dst, true)) {
ip_rt_put(rt);
+ reason = SKB_DROP_REASON_PKT_TOO_BIG;
goto tx_error;
}
@@ -663,6 +670,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len;
if (skb_cow_head(skb, headroom)) {
ip_rt_put(rt);
+ reason = SKB_DROP_REASON_NOMEM;
goto tx_dropped;
}
@@ -677,13 +685,14 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
tx_dropped:
DEV_STATS_INC(dev, tx_dropped);
kfree:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
}
EXPORT_SYMBOL_GPL(ip_md_tunnel_xmit);
void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params, u8 protocol)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel *tunnel = netdev_priv(dev);
struct ip_tunnel_info *tun_info = NULL;
const struct iphdr *inner_iph;
@@ -711,6 +720,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (!skb_dst(skb)) {
DEV_STATS_INC(dev, tx_fifo_errors);
+ reason = SKB_DROP_REASON_NO_TX_TARGET;
goto tx_error;
}
@@ -724,9 +734,8 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
} else if (payload_protocol == htons(ETH_P_IP)) {
rt = skb_rtable(skb);
dst = rt_nexthop(rt, inner_iph->daddr);
- }
#if IS_ENABLED(CONFIG_IPV6)
- else if (payload_protocol == htons(ETH_P_IPV6)) {
+ } else if (payload_protocol == htons(ETH_P_IPV6)) {
const struct in6_addr *addr6;
struct neighbour *neigh;
bool do_tx_error_icmp;
@@ -734,8 +743,10 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
neigh = dst_neigh_lookup(skb_dst(skb),
&ipv6_hdr(skb)->daddr);
- if (!neigh)
+ if (!neigh) {
+ reason = SKB_DROP_REASON_NEIGH_CREATEFAIL;
goto tx_error;
+ }
addr6 = (const struct in6_addr *)&neigh->primary_key;
addr_type = ipv6_addr_type(addr6);
@@ -752,12 +763,15 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
dst = addr6->s6_addr32[3];
}
neigh_release(neigh);
- if (do_tx_error_icmp)
+ if (do_tx_error_icmp) {
+ reason = SKB_DROP_REASON_NO_TX_TARGET;
goto tx_error_icmp;
- }
+ }
#endif
- else
+ } else {
+ reason = SKB_DROP_REASON_NO_TX_TARGET;
goto tx_error;
+ }
if (!md)
connected = false;
@@ -780,7 +794,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))
+ reason = ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4);
+ if (reason)
goto tx_error;
if (connected && md) {
@@ -798,6 +813,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (IS_ERR(rt)) {
DEV_STATS_INC(dev, tx_carrier_errors);
+ reason = SKB_DROP_REASON_IP_OUTNOROUTES;
goto tx_error;
}
if (use_cache)
@@ -811,6 +827,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (rt->dst.dev == dev) {
ip_rt_put(rt);
DEV_STATS_INC(dev, collisions);
+ reason = SKB_DROP_REASON_RECURSION_LIMIT;
goto tx_error;
}
@@ -820,6 +837,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (tnl_update_pmtu(dev, skb, rt, df, inner_iph, 0, 0, false)) {
ip_rt_put(rt);
+ reason = SKB_DROP_REASON_PKT_TOO_BIG;
goto tx_error;
}
@@ -854,7 +872,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
if (skb_cow_head(skb, max_headroom)) {
ip_rt_put(rt);
DEV_STATS_INC(dev, tx_dropped);
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
return;
}
@@ -870,7 +888,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
#endif
tx_error:
DEV_STATS_INC(dev, tx_errors);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
}
EXPORT_SYMBOL_GPL(ip_tunnel_xmit);
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v6 8/8] ip_gre: add drop reasons to the transmit path
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
` (6 preceding siblings ...)
2026-11-10 20:20 ` [PATCH net-next v6 7/8] ip_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-11-10 20:20 ` Anton Danilov
7 siblings, 0 replies; 9+ messages in thread
From: Anton Danilov @ 2026-11-10 20:20 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, linux-kernel
Each transmit function of ip_gre ends all of its failures in one
kfree_skb() and a tx_dropped increment, so a drop can be traced to the
function and to nothing more precise than "the tunnel did not send it".
No new reason is needed. The length helpers already compute one, so
pskb_inet_may_pull_reason() and pskb_may_pull_reason() are used instead
of their boolean wrappers, and the rest reuses:
- SKB_DROP_REASON_NOMEM for the headroom expansions, the offload
handling and the trims,
- SKB_DROP_REASON_TUNNEL_TXINFO for the collect_md paths, when the
metadata is missing or incomplete,
- SKB_DROP_REASON_UNHANDLED_PROTO for an ERSPAN version that is not
implemented,
- SKB_DROP_REASON_SKB_CSUM when the checksum starts before the data
the tunnel is about to send.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv4/ip_gre.c | 101 +++++++++++++++++++++++++++++++++-------------
1 file changed, 74 insertions(+), 27 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index c93c395c997a..71e087a7afcf 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -508,6 +508,7 @@ static int gre_handle_offloads(struct sk_buff *skb, bool csum)
static void gre_fb_xmit(struct sk_buff *skb, struct net_device *dev,
__be16 proto)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags) = { };
struct ip_tunnel_info *tun_info;
@@ -516,19 +517,25 @@ static void gre_fb_xmit(struct sk_buff *skb, struct net_device *dev,
tun_info = skb_tunnel_info(skb);
if (unlikely(!tun_info || !(tun_info->mode & IP_TUNNEL_INFO_TX) ||
- ip_tunnel_info_af(tun_info) != AF_INET))
+ ip_tunnel_info_af(tun_info) != AF_INET)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto err_free_skb;
+ }
key = &tun_info->key;
tunnel_hlen = gre_calc_hlen(key->tun_flags);
- if (skb_cow_head(skb, dev->needed_headroom))
+ if (skb_cow_head(skb, dev->needed_headroom)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto err_free_skb;
+ }
/* Push Tunnel header. */
if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- tunnel->parms.o_flags)))
+ tunnel->parms.o_flags))) {
+ reason = SKB_DROP_REASON_NOMEM;
goto err_free_skb;
+ }
__set_bit(IP_TUNNEL_CSUM_BIT, flags);
__set_bit(IP_TUNNEL_KEY_BIT, flags);
@@ -545,12 +552,13 @@ static void gre_fb_xmit(struct sk_buff *skb, struct net_device *dev,
return;
err_free_skb:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
DEV_STATS_INC(dev, tx_dropped);
}
static void erspan_fb_xmit(struct sk_buff *skb, struct net_device *dev)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags) = { };
struct ip_tunnel_info *tun_info;
@@ -564,29 +572,41 @@ static void erspan_fb_xmit(struct sk_buff *skb, struct net_device *dev)
tun_info = skb_tunnel_info(skb);
if (unlikely(!tun_info || !(tun_info->mode & IP_TUNNEL_INFO_TX) ||
- ip_tunnel_info_af(tun_info) != AF_INET))
+ ip_tunnel_info_af(tun_info) != AF_INET)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto err_free_skb;
+ }
key = &tun_info->key;
- if (!test_bit(IP_TUNNEL_ERSPAN_OPT_BIT, tun_info->key.tun_flags))
+ if (!test_bit(IP_TUNNEL_ERSPAN_OPT_BIT, tun_info->key.tun_flags)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto err_free_skb;
- if (tun_info->options_len < sizeof(*md))
+ }
+ if (tun_info->options_len < sizeof(*md)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto err_free_skb;
+ }
md = ip_tunnel_info_opts(tun_info);
/* ERSPAN has fixed 8 byte GRE header */
version = md->version;
tunnel_hlen = 8 + erspan_hdr_len(version);
- if (skb_cow_head(skb, dev->needed_headroom))
+ if (skb_cow_head(skb, dev->needed_headroom)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto err_free_skb;
+ }
- if (gre_handle_offloads(skb, false))
+ if (gre_handle_offloads(skb, false)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto err_free_skb;
+ }
if (skb->len > dev->mtu + dev->hard_header_len) {
- if (pskb_trim(skb, dev->mtu + dev->hard_header_len))
+ if (pskb_trim(skb, dev->mtu + dev->hard_header_len)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto err_free_skb;
+ }
truncate = true;
}
@@ -618,6 +638,7 @@ static void erspan_fb_xmit(struct sk_buff *skb, struct net_device *dev)
truncate, true);
proto = htons(ETH_P_ERSPAN2);
} else {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto err_free_skb;
}
@@ -630,7 +651,7 @@ static void erspan_fb_xmit(struct sk_buff *skb, struct net_device *dev)
return;
err_free_skb:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
DEV_STATS_INC(dev, tx_dropped);
}
@@ -664,8 +685,10 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
struct ip_tunnel *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags);
const struct iphdr *tnl_params;
+ enum skb_drop_reason reason;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto free_skb;
if (tunnel->collect_md) {
@@ -676,10 +699,13 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
if (dev->header_ops) {
int pull_len = tunnel->hlen + sizeof(struct iphdr);
- if (skb_cow_head(skb, 0))
+ if (skb_cow_head(skb, 0)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
- if (!pskb_may_pull(skb, pull_len))
+ reason = pskb_may_pull_reason(skb, pull_len);
+ if (reason)
goto free_skb;
tnl_params = (const struct iphdr *)skb->data;
@@ -689,25 +715,31 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
skb_reset_mac_header(skb);
if (skb->ip_summed == CHECKSUM_PARTIAL &&
- skb_checksum_start(skb) < skb->data)
+ skb_checksum_start(skb) < skb->data) {
+ reason = SKB_DROP_REASON_SKB_CSUM;
goto free_skb;
+ }
} else {
- if (skb_cow_head(skb, dev->needed_headroom))
+ if (skb_cow_head(skb, dev->needed_headroom)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
tnl_params = &tunnel->parms.iph;
}
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
__gre_xmit(skb, dev, tnl_params, skb->protocol, flags);
return NETDEV_TX_OK;
free_skb:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
DEV_STATS_INC(dev, tx_dropped);
return NETDEV_TX_OK;
}
@@ -717,10 +749,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
{
struct ip_tunnel *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags);
+ enum skb_drop_reason reason;
bool truncate = false;
__be16 proto;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto free_skb;
if (tunnel->collect_md) {
@@ -728,15 +762,21 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
}
- if (gre_handle_offloads(skb, false))
+ if (gre_handle_offloads(skb, false)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
- if (skb_cow_head(skb, dev->needed_headroom))
+ if (skb_cow_head(skb, dev->needed_headroom)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
if (skb->len > dev->mtu + dev->hard_header_len) {
- if (pskb_trim(skb, dev->mtu + dev->hard_header_len))
+ if (pskb_trim(skb, dev->mtu + dev->hard_header_len)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
truncate = true;
}
@@ -757,6 +797,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
truncate, true);
proto = htons(ETH_P_ERSPAN2);
} else {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto free_skb;
}
@@ -765,7 +806,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
free_skb:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
DEV_STATS_INC(dev, tx_dropped);
return NETDEV_TX_OK;
}
@@ -775,8 +816,10 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
{
struct ip_tunnel *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags);
+ enum skb_drop_reason reason;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto free_skb;
if (tunnel->collect_md) {
@@ -786,17 +829,21 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
- if (skb_cow_head(skb, dev->needed_headroom))
+ if (skb_cow_head(skb, dev->needed_headroom)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto free_skb;
+ }
__gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB), flags);
return NETDEV_TX_OK;
free_skb:
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
DEV_STATS_INC(dev, tx_dropped);
return NETDEV_TX_OK;
}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-10 20:20 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 20:20 [PATCH net-next v6 0/8] tunnels: add core and gre drop reasons Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 1/8] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 2/8] ip6_tunnel: add drop reasons to the receive path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 3/8] gre: make gre_parse_header() report a drop reason Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 4/8] ip_gre: add drop reasons to the RX path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 5/8] ip6_gre: " Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 6/8] ip_tunnel: make ip_tunnel_encap() return a drop reason Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 7/8] ip_tunnel: add drop reasons to the transmit path Anton Danilov
2026-11-10 20:20 ` [PATCH net-next v6 8/8] ip_gre: " Anton Danilov
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®