* [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons
@ 2026-09-16 14:37 Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 1/9] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
` (9 more replies)
0 siblings, 10 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
Only vxlan reports drop reasons among the tunnel drivers today.
Everything else, on both the receive and the transmit side, ends in a
plain kfree_skb(), so a packet that a tunnel throws away is invisible
to dropwatch, drop_monitor and perf trace -e skb:kfree_skb. The device
counters group the failures coarsely: rx_errors and tx_errors each
cover half a dozen unrelated conditions.
This series covers the generic paths shared by ipip, sit, gre and their
IPv6 counterparts, plus the GRE specific parsing, on both directions.
A later series will do the same for geneve, bareudp, fou and the
remaining IP in IP drivers.
Patches 1-2 convert the generic receive paths, ip_tunnel_rcv() and
__ip6_tnl_rcv(). Two reasons are added:
TNL_OPT_MISMATCH the options a packet carries do not match the
tunnel configuration
TNL_OLD_SEQ the sequence number is older than the one the
tunnel expects, next to the existing
TCP_OLD_SEQUENCE
The second one has a failure mode worth naming: when a peer reboots,
its outgoing sequence number restarts at zero and the receiver drops
everything until its own counter catches up. That is indistinguishable
from a misconfiguration by the counters alone.
Patches 3-5 do the GRE specific receive path. gre_parse_header()
returns -EINVAL for six different reasons, 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. It is
replaced by a drop reason. Three reasons are added, mirroring vxlan:
GRE_INVALID_HDR, GRE_CSUM and GRE_TUNNEL_NOT_FOUND.
Patches 6-9 do the transmit side, some eighty failure paths across
ip_tunnel, ip_gre, ip6_tunnel and ip6_gre. One reason is added,
TNL_ENCAP, for a failure to build the encapsulation header. Patch 8 is
a small preparation: prepare_ip6gre_xmit_other() cannot fail, so it is
made void rather than given a drop reason for a branch that never runs.
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 the drop lands in
tx_errors next to genuine failures. An MTU black hole cannot be told
from a broken route by looking at the counters.
Drop reasons on transmit are not new: vxlan already reports several
from its xmit path, and ip_tunnel_core.c reports RECURSION_LIMIT.
Changes since v2:
- ipxip6_tnl_xmit() no longer overwrites the reason ip6_tnl_xmit()
reported: the PKT_TOO_BIG assignment ran for every error, not just
-EMSGSIZE, and ip6_tnl_xmit() already sets that one itself, so every
ip4ip6, ip6ip6 and mplsip6 transmit failure was reported as an MTU
problem
- the NBMA branch of ip6_tnl_xmit() now reports NO_TX_TARGET instead of
IP_OUTNOROUTES for an skb that carries no destination, matching the
sibling branch a few lines above and ip_tunnel_xmit()
- the __iptunnel_pull_header() failures now report NOMEM instead of
HDR_TRUNC: the helper returns -ENOMEM for every failure mode and also
fails when skb_unclone() cannot allocate, the way vxlan_rcv() labels
it. The length checks keep HDR_TRUNC
- fixed the call graph in patch 2: the exported ip6_tnl_rcv() is only
reached from ip6_gre, while ip4ip6, ip6ip6 and mplsip6 come through
ipxip6_rcv()
- reworded what a NULL reason means for gre_parse_header(): a checksum
failure is not reported, but the checksum is still computed
- new patch 8: prepare_ip6gre_xmit_other() cannot fail, its only return
is "return 0", so it is made void and its caller stops checking it.
v2 attached IPV6_BAD_EXTHDR to that branch, which never runs
- the source address selection of a collect_md tunnel in ip6_tnl_xmit()
now reports IP_OUTNOROUTES instead of NO_TX_TARGET: the destination
is known and the route was found, and the IPv6 stack itself treats a
failed source selection as a failed lookup, the way vxlan reports it
- the commit message of the ip6_tunnel transmit patch now lists every
reason the patch uses: TNL_ENCAP and UNHANDLED_PROTO were missing and
RECURSION_LIMIT also covers the address conflict check
- cover letter and commit messages made precise: the transmit side
labels some eighty failure paths, not forty; vti does not use the
converted paths; tnl_update_pmtu() returns -E2BIG for IPv6 payloads
too, not only for IPv4 ones with DF set
- the three selftest patches stay dropped
- 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 (9):
ip_tunnel: add drop reasons to the generic RX path
ip6_tunnel: add drop reasons to the generic RX 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: add drop reasons to the transmit path
ip_gre: add drop reasons to the transmit path
ip6_gre: make prepare_ip6gre_xmit_other() void
ip6_tunnel: add drop reasons to the transmit path
include/net/dropreason-core.h | 38 ++++++++
include/net/gre.h | 2 +-
include/net/ip6_tunnel.h | 3 +-
net/ipv4/gre_demux.c | 52 +++++++---
net/ipv4/ip_gre.c | 144 +++++++++++++++++++---------
net/ipv4/ip_tunnel.c | 60 +++++++++---
net/ipv6/ip6_gre.c | 175 +++++++++++++++++++++++-----------
net/ipv6/ip6_tunnel.c | 95 +++++++++++++-----
8 files changed, 419 insertions(+), 150 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v3 1/9] ip_tunnel: add drop reasons to the generic RX path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 2/9] ip6_tunnel: " Anton Danilov
` (8 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
ip_tunnel_rcv() collapses four distinct failures into a single plain
kfree_skb(), so a packet dropped there simply vanishes:
- 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).
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
reported to drop_monitor or to the skb:kfree_skb tracepoint.
Add two drop reasons for the tunnel specific cases and reuse the
existing ones for the rest:
- SKB_DROP_REASON_TNL_OPT_MISMATCH is used when the packet
does not carry the checksum or the sequence number option the tunnel
is configured for. 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_TNL_OLD_SEQ is used when the sequence number
is older than the expected one. Unlike the previous one this is a
property of the received traffic: a remote endpoint that restarts
and resets its sequence numbering has all of its packets dropped
until i_seqno catches up.
- pskb_inet_may_pull_reason() already computes a drop reason,
SKB_DROP_REASON_PKT_TOO_SMALL or SKB_DROP_REASON_NOMEM, which was
discarded so far.
- 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 test 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 checksum
and the sequence number options only exist for GRE, so the two new
reasons are reachable through ip_gre alone, while the length and the
ECN ones apply to all three.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 16 ++++++++++++++++
net/ipv4/ip_tunnel.c | 19 +++++++++++++++----
2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index 12f909651591..e1fdd11c939f 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -129,6 +129,8 @@
FN(PSP_INPUT) \
FN(PSP_OUTPUT) \
FN(RECURSION_LIMIT) \
+ FN(TNL_OPT_MISMATCH) \
+ FN(TNL_OLD_SEQ) \
FNe(MAX)
/**
@@ -612,6 +614,20 @@ enum skb_drop_reason {
SKB_DROP_REASON_PSP_OUTPUT,
/** @SKB_DROP_REASON_RECURSION_LIMIT: Dead loop on virtual device. */
SKB_DROP_REASON_RECURSION_LIMIT,
+ /**
+ * @SKB_DROP_REASON_TNL_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.
+ */
+ SKB_DROP_REASON_TNL_OPT_MISMATCH,
+ /**
+ * @SKB_DROP_REASON_TNL_OLD_SEQ: the sequence number carried
+ * by the packet is older than the one expected by the tunnel, e.g.
+ * after the remote endpoint restarted and reset its sequence
+ * numbering.
+ */
+ SKB_DROP_REASON_TNL_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 13b5e35e8790..0260a97e990e 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -378,6 +378,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;
@@ -392,14 +393,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_TNL_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_TNL_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_TNL_OLD_SEQ;
goto drop;
}
tunnel->i_seqno = ntohl(tpi->seq) + 1;
@@ -413,7 +422,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;
@@ -428,6 +438,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;
}
}
@@ -451,7 +462,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] 12+ messages in thread
* [PATCH net-next v3 2/9] ip6_tunnel: add drop reasons to the generic RX path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 1/9] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 3/9] gre: make gre_parse_header() report a drop reason Anton Danilov
` (7 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
__ip6_tnl_rcv() mirrors its IPv4 counterpart: five distinct failures
share a single plain kfree_skb(). Reuse the drop reasons introduced
for ip_tunnel_rcv() and the ones the length helpers already return.
Note that skb_vlan_inet_prepare() returns an enum skb_drop_reason that
was simply discarded, and that pskb_may_pull_reason() has been
available all along.
__ip6_tnl_rcv() is reached two ways: ip6_gre (ip6gre, ip6gretap,
erspan) goes through the exported ip6_tnl_rcv(), while the ip6_tunnel
encapsulations (ip4ip6, ip6ip6, mplsip6) reach it from ipxip6_rcv().
As on the IPv4 side, only ip6_gre sets the checksum and sequence
number bits -- tpi_v4, tpi_v6 and tpi_mpls carry nothing but .proto --
so the option mismatch and the old sequence reasons are reachable
through it alone.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv6/ip6_tunnel.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index d5ff50a2ac01..458ce328311b 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_TNL_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_TNL_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_TNL_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;
}
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v3 3/9] gre: make gre_parse_header() report a drop reason
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 1/9] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 2/9] ip6_tunnel: " Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 4/9] ip_gre: add drop reasons to the RX path Anton Danilov
` (6 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
gre_parse_header() returns -EINVAL for six different reasons and its
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.
Replace that dead output parameter with an enum skb_drop_reason one and
let the two receive paths report what happened. Two reasons are added:
- SKB_DROP_REASON_GRE_INVALID_HDR, for a header carrying an unsupported
version or the routing bit,
- SKB_DROP_REASON_GRE_CSUM, for a checksum error, next to the existing
TCP_CSUM, UDP_CSUM, ICMP_CSUM and IP_CSUM.
The header pull failures reuse SKB_DROP_REASON_HDR_TRUNC, which
documents exactly this case, and gre_rcv() in the demux reuses
pskb_may_pull_reason() and SKB_DROP_REASON_UNHANDLED_PROTO.
A NULL reason keeps the meaning a NULL csum_err had: the caller is not
interested in it and a checksum failure must not be reported. The
checksum is still computed either way, the packet is just not rejected
over it. This is what the ICMP error handlers need, as they only get
a part of the original packet.
Tunnel lookup failures still report SKB_DROP_REASON_NOT_SPECIFIED here;
they are addressed in the following patches.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 9 ++++++
include/net/gre.h | 2 +-
net/ipv4/gre_demux.c | 52 +++++++++++++++++++++++++++--------
net/ipv4/ip_gre.c | 6 ++--
net/ipv6/ip6_gre.c | 6 ++--
5 files changed, 56 insertions(+), 19 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index e1fdd11c939f..6ae7a604722d 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -131,6 +131,8 @@
FN(RECURSION_LIMIT) \
FN(TNL_OPT_MISMATCH) \
FN(TNL_OLD_SEQ) \
+ FN(GRE_INVALID_HDR) \
+ FN(GRE_CSUM) \
FNe(MAX)
/**
@@ -628,6 +630,13 @@ enum skb_drop_reason {
* numbering.
*/
SKB_DROP_REASON_TNL_OLD_SEQ,
+ /**
+ * @SKB_DROP_REASON_GRE_INVALID_HDR: the GRE header is invalid, e.g.
+ * an unsupported version or the routing bit is set.
+ */
+ SKB_DROP_REASON_GRE_INVALID_HDR,
+ /** @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..a63f26c3f78e 100644
--- a/include/net/gre.h
+++ b/include/net/gre.h
@@ -33,7 +33,7 @@ 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 *reason, __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..c5d3847848ef 100644
--- a/net/ipv4/gre_demux.c
+++ b/net/ipv4/gre_demux.c
@@ -58,26 +58,44 @@ 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.
+ *
+ * @reason is only written when the header is rejected, so the caller has
+ * to initialise it before the call.
+ *
+ * A NULL @reason means that the caller is not interested in the drop
+ * reason, and also that a checksum failure must not be reported: the
+ * checksum is still computed, the packet is just not rejected over it.
+ * This is what the ICMP error handlers need, as they only get a part of
+ * the original packet.
*/
int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
- bool *csum_err, __be16 proto, int nhs)
+ enum skb_drop_reason *reason, __be16 proto, int nhs)
{
const struct gre_base_hdr *greh;
__be32 *options;
int hdr_len;
- if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
+ if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr)))) {
+ if (reason)
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return -EINVAL;
+ }
greh = (struct gre_base_hdr *)(skb->data + nhs);
- if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
+ if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING))) {
+ if (reason)
+ *reason = SKB_DROP_REASON_GRE_INVALID_HDR;
return -EINVAL;
+ }
gre_flags_to_tnl_flags(tpi->flags, greh->flags);
hdr_len = gre_calc_hlen(tpi->flags);
- if (!pskb_may_pull(skb, nhs + hdr_len))
+ if (!pskb_may_pull(skb, nhs + hdr_len)) {
+ if (reason)
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return -EINVAL;
+ }
greh = (struct gre_base_hdr *)(skb->data + nhs);
tpi->proto = greh->protocol;
@@ -87,8 +105,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;
+ } else if (reason) {
+ *reason = SKB_DROP_REASON_GRE_CSUM;
return -EINVAL;
}
@@ -116,8 +134,11 @@ 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)
+ if (!val) {
+ if (reason)
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return -EINVAL;
+ }
tpi->proto = proto;
if ((*val & 0xF0) != 0x40)
hdr_len += 4;
@@ -132,8 +153,11 @@ 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)))
+ if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr))) {
+ if (reason)
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return -EINVAL;
+ }
ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len);
tpi->key = cpu_to_be32(get_session_id(ershdr));
@@ -145,16 +169,20 @@ EXPORT_SYMBOL(gre_parse_header);
static int gre_rcv(struct sk_buff *skb)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
const struct gre_protocol *proto;
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_UNHANDLED_PROTO;
goto drop;
+ }
rcu_read_lock();
proto = rcu_dereference(gre_proto[ver]);
@@ -167,11 +195,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 82309efd417e..1894c5746a73 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -439,8 +439,8 @@ 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
@@ -451,7 +451,7 @@ static int gre_rcv(struct sk_buff *skb)
}
#endif
- hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP), 0);
+ hdr_len = gre_parse_header(skb, &tpi, &reason, htons(ETH_P_IP), 0);
if (hdr_len < 0)
goto drop;
@@ -469,7 +469,7 @@ static int gre_rcv(struct sk_buff *skb)
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 8ebda0b6a78b..78854cc2dac9 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -569,11 +569,11 @@ static int ip6erspan_rcv(struct sk_buff *skb,
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;
- hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IPV6), 0);
+ hdr_len = gre_parse_header(skb, &tpi, &reason, htons(ETH_P_IPV6), 0);
if (hdr_len < 0)
goto drop;
@@ -594,7 +594,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] 12+ messages in thread
* [PATCH net-next v3 4/9] ip_gre: add drop reasons to the RX path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (2 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 3/9] gre: make gre_parse_header() report a drop reason Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 5/9] ip6_gre: " Anton Danilov
` (5 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, 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, with a plain
kfree_skb(). This is the GRE counterpart of a UDP packet hitting no
socket, and by far the most common way a GRE packet is dropped on
receive, yet nothing tells it apart from a malformed one.
Add SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND for it, in the spirit of the
existing SKB_DROP_REASON_VXLAN_VNI_NOT_FOUND, and report it from
erspan_rcv() and __ipgre_rcv() through a new output parameter, so that a
failed lookup is not reported the same way as a header that could not be
pulled or as a metadata allocation failure.
The length checks reuse SKB_DROP_REASON_HDR_TRUNC. The
__iptunnel_pull_header() failures reuse SKB_DROP_REASON_NOMEM instead:
the helper returns -ENOMEM for every failure mode, and besides a short
header it also fails when skb_unclone() cannot allocate, so a
truncation reason would be wrong there. vxlan_rcv() labels the same
helper the same way. The metadata allocation failures reuse
SKB_DROP_REASON_NOMEM as well.
SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND is documented without naming an
address family. The IPv6 side is converted by the next patch, which
ends its lookup failures with the same reason.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 6 ++++++
net/ipv4/ip_gre.c | 37 +++++++++++++++++++++++------------
2 files changed, 30 insertions(+), 13 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index 6ae7a604722d..fa8bd552122f 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -133,6 +133,7 @@
FN(TNL_OLD_SEQ) \
FN(GRE_INVALID_HDR) \
FN(GRE_CSUM) \
+ FN(GRE_TUNNEL_NOT_FOUND) \
FNe(MAX)
/**
@@ -637,6 +638,11 @@ enum skb_drop_reason {
SKB_DROP_REASON_GRE_INVALID_HDR,
/** @SKB_DROP_REASON_GRE_CSUM: GRE checksum error */
SKB_DROP_REASON_GRE_CSUM,
+ /**
+ * @SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND: no GRE tunnel found for the
+ * endpoints and the key the packet carries.
+ */
+ SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND,
/**
* @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_gre.c b/net/ipv4/ip_gre.c
index 1894c5746a73..dc203937eb9f 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -265,7 +265,7 @@ static bool is_erspan_type1(int gre_hdr_len)
}
static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
- int gre_hdr_len)
+ int gre_hdr_len, enum skb_drop_reason *reason)
{
struct net *net = dev_net(skb->dev);
struct metadata_dst *tun_dst = NULL;
@@ -289,8 +289,10 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
iph->saddr, iph->daddr, 0);
} else {
if (unlikely(!pskb_may_pull(skb,
- gre_hdr_len + sizeof(*ershdr))))
+ gre_hdr_len + sizeof(*ershdr)))) {
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return PACKET_REJECT;
+ }
ershdr = (struct erspan_base_hdr *)(skb->data + gre_hdr_len);
ver = ershdr->ver;
@@ -306,8 +308,10 @@ 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)))
+ if (unlikely(!pskb_may_pull(skb, len))) {
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return PACKET_REJECT;
+ }
if (__iptunnel_pull_header(skb,
len,
@@ -327,8 +331,10 @@ 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)
+ if (!tun_dst) {
+ *reason = SKB_DROP_REASON_NOMEM;
return PACKET_REJECT;
+ }
/* MUST set options_len before referencing options */
info = &tun_dst->u.tun_info;
@@ -356,15 +362,17 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
return PACKET_RCVD;
}
+ *reason = SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND;
return PACKET_REJECT;
drop:
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
return PACKET_RCVD;
}
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)
+ struct ip_tunnel_net *itn, int hdr_len, bool raw_proto,
+ enum skb_drop_reason *reason)
{
struct metadata_dst *tun_dst = NULL;
const struct iphdr *iph;
@@ -400,22 +408,25 @@ 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)
+ if (!tun_dst) {
+ *reason = SKB_DROP_REASON_NOMEM;
return PACKET_REJECT;
+ }
}
ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
return PACKET_RCVD;
}
+ *reason = SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND;
return PACKET_NEXT;
drop:
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
return PACKET_RCVD;
}
static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
- int hdr_len)
+ int hdr_len, enum skb_drop_reason *reason)
{
struct net *net = dev_net(skb->dev);
struct ip_tunnel_net *itn;
@@ -426,13 +437,13 @@ static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
else
itn = net_generic(net, ipgre_net_id);
- res = __ipgre_rcv(skb, tpi, itn, hdr_len, false);
+ res = __ipgre_rcv(skb, tpi, itn, hdr_len, false, reason);
if (res == PACKET_NEXT && 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);
+ res = __ipgre_rcv(skb, tpi, itn, hdr_len, true, reason);
}
return res;
}
@@ -457,12 +468,12 @@ static int gre_rcv(struct sk_buff *skb)
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, hdr_len, &reason) == PACKET_RCVD)
return 0;
goto out;
}
- if (ipgre_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
+ if (ipgre_rcv(skb, &tpi, hdr_len, &reason) == PACKET_RCVD)
return 0;
out:
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v3 5/9] ip6_gre: add drop reasons to the RX path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (3 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 4/9] ip_gre: add drop reasons to the RX path Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 6/9] ip_tunnel: add drop reasons to the transmit path Anton Danilov
` (4 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
Mirror the previous patch on the IPv6 side: report
SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND when no tunnel matches the packet,
and tell that apart from a header that is too short
(SKB_DROP_REASON_HDR_TRUNC) and from the allocation failures behind
__iptunnel_pull_header() and the metadata dst (SKB_DROP_REASON_NOMEM),
which so far all ended up in the same plain kfree_skb() in gre_rcv().
ip6gre_rcv() and ip6erspan_rcv() get the same output parameter as their
IPv4 counterparts.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv6/ip6_gre.c | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 78854cc2dac9..0b270f4ac774 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -454,7 +454,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 int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
+ enum skb_drop_reason *reason)
{
const struct ipv6hdr *ipv6h;
struct ip6_tnl *tunnel;
@@ -473,8 +474,10 @@ static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
tun_id = key32_to_tunnel_id(tpi->key);
tun_dst = ipv6_tun_rx_dst(skb, flags, tun_id, 0);
- if (!tun_dst)
+ if (!tun_dst) {
+ *reason = SKB_DROP_REASON_NOMEM;
return PACKET_REJECT;
+ }
ip6_tnl_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
} else {
@@ -484,12 +487,14 @@ static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
return PACKET_RCVD;
}
+ *reason = SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND;
return PACKET_REJECT;
}
static int ip6erspan_rcv(struct sk_buff *skb,
struct tnl_ptk_info *tpi,
- int gre_hdr_len)
+ int gre_hdr_len,
+ enum skb_drop_reason *reason)
{
struct erspan_base_hdr *ershdr;
const struct ipv6hdr *ipv6h;
@@ -497,8 +502,10 @@ static int ip6erspan_rcv(struct sk_buff *skb,
struct ip6_tnl *tunnel;
u8 ver;
- if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr))))
+ if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr)))) {
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return PACKET_REJECT;
+ }
ipv6h = ipv6_hdr(skb);
ershdr = (struct erspan_base_hdr *)skb->data;
@@ -510,13 +517,17 @@ static int ip6erspan_rcv(struct sk_buff *skb,
if (tunnel) {
int len = erspan_hdr_len(ver);
- if (unlikely(!pskb_may_pull(skb, len)))
+ if (unlikely(!pskb_may_pull(skb, len))) {
+ *reason = SKB_DROP_REASON_HDR_TRUNC;
return PACKET_REJECT;
+ }
if (__iptunnel_pull_header(skb, len,
htons(ETH_P_TEB),
- false, false) < 0)
+ false, false) < 0) {
+ *reason = SKB_DROP_REASON_NOMEM;
return PACKET_REJECT;
+ }
if (tunnel->parms.collect_md) {
struct erspan_metadata *pkt_md, *md;
@@ -532,8 +543,10 @@ static int ip6erspan_rcv(struct sk_buff *skb,
tun_dst = ipv6_tun_rx_dst(skb, flags, tun_id,
sizeof(*md));
- if (!tun_dst)
+ if (!tun_dst) {
+ *reason = SKB_DROP_REASON_NOMEM;
return PACKET_REJECT;
+ }
/* MUST set options_len before referencing options */
info = &tun_dst->u.tun_info;
@@ -564,6 +577,7 @@ static int ip6erspan_rcv(struct sk_buff *skb,
return PACKET_RCVD;
}
+ *reason = SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND;
return PACKET_REJECT;
}
@@ -577,17 +591,19 @@ static int gre_rcv(struct sk_buff *skb)
if (hdr_len < 0)
goto drop;
- if (iptunnel_pull_header(skb, hdr_len, tpi.proto, false))
+ if (iptunnel_pull_header(skb, hdr_len, tpi.proto, false)) {
+ reason = SKB_DROP_REASON_NOMEM;
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, hdr_len, &reason) == PACKET_RCVD)
return 0;
goto out;
}
- if (ip6gre_rcv(skb, &tpi) == PACKET_RCVD)
+ if (ip6gre_rcv(skb, &tpi, &reason) == PACKET_RCVD)
return 0;
out:
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v3 6/9] ip_tunnel: add drop reasons to the transmit path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (4 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 5/9] ip6_gre: " Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 7/9] ip_gre: " Anton Danilov
` (3 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
ip_tunnel_xmit() and ip_md_tunnel_xmit() encapsulate packets that the
tunnel forwards, and every failure on that path ends in the same plain
kfree_skb(). The device counters separate them a little, but they are
too coarse to act on: tx_errors alone covers an encapsulation failure, a
routing failure, a lookup loop and a packet that is simply too big.
The last one deserves attention. tnl_update_pmtu() returns -E2BIG for a
packet larger than the path MTU, an IPv4 one with the DF bit set or any
IPv6 one, after it has already sent the ICMP error back to the sender.
That is path MTU discovery working as intended, yet it lands in
tx_errors next to genuine failures, so a MTU black hole cannot be told
from a broken route by looking at the counters.
No new reason is needed for most of it:
- SKB_DROP_REASON_PKT_TOO_BIG for the case above,
- SKB_DROP_REASON_IP_OUTNOROUTES when no route is found,
- SKB_DROP_REASON_RECURSION_LIMIT when the route points back at the
tunnel device itself, which is the "dead loop on virtual device" that
reason describes,
- SKB_DROP_REASON_NOMEM when the headroom cannot be expanded,
- SKB_DROP_REASON_NEIGH_CREATEFAIL when the NBMA neighbour lookup
fails, SKB_DROP_REASON_NO_TX_TARGET when no destination can be
derived at all, and SKB_DROP_REASON_UNHANDLED_PROTO for 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.
Only the encapsulation failure has no fitting reason, so add
SKB_DROP_REASON_TNL_ENCAP for it.
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.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/dropreason-core.h | 7 ++++++
net/ipv4/ip_tunnel.c | 41 ++++++++++++++++++++++++++++-------
2 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index fa8bd552122f..30378a0d2272 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -134,6 +134,7 @@
FN(GRE_INVALID_HDR) \
FN(GRE_CSUM) \
FN(GRE_TUNNEL_NOT_FOUND) \
+ FN(TNL_ENCAP) \
FNe(MAX)
/**
@@ -643,6 +644,12 @@ enum skb_drop_reason {
* endpoints and the key the packet carries.
*/
SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND,
+ /**
+ * @SKB_DROP_REASON_TNL_ENCAP: failed to build the
+ * encapsulation header of a tunnel, e.g. an unknown or
+ * unregistered encapsulation type.
+ */
+ SKB_DROP_REASON_TNL_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/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 0260a97e990e..e7757c0a09be 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -580,6 +580,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;
@@ -593,8 +594,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);
@@ -613,8 +616,10 @@ 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) < 0) {
+ reason = SKB_DROP_REASON_TNL_ENCAP;
goto tx_error;
+ }
use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
if (use_cache)
@@ -623,6 +628,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)
@@ -632,6 +638,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;
}
@@ -640,6 +647,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;
}
@@ -657,6 +665,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;
}
@@ -671,13 +680,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;
@@ -705,9 +715,15 @@ 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;
}
+ /* Only the branches below can derive a destination. If
+ * none of them matches, the payload protocol is not one
+ * this tunnel can carry.
+ */
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
tun_info = skb_tunnel_info(skb);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX) &&
ip_tunnel_info_af(tun_info) == AF_INET &&
@@ -728,8 +744,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);
@@ -746,8 +764,10 @@ 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
@@ -774,8 +794,10 @@ 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) < 0) {
+ reason = SKB_DROP_REASON_TNL_ENCAP;
goto tx_error;
+ }
if (connected && md) {
use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
@@ -792,6 +814,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)
@@ -805,6 +828,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;
}
@@ -814,6 +838,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;
}
@@ -848,7 +873,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;
}
@@ -864,7 +889,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] 12+ messages in thread
* [PATCH net-next v3 7/9] ip_gre: add drop reasons to the transmit path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (5 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 6/9] ip_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 8/9] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
` (2 subsequent siblings)
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
The five transmit functions of ip_gre collapse about twenty distinct
failures into a plain kfree_skb() and a tx_dropped increment, which says
nothing beyond "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.
Assisted-by: Claude-Code:claude-opus-5
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 dc203937eb9f..373532e90dea 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -507,6 +507,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;
@@ -515,19 +516,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);
@@ -544,12 +551,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;
@@ -563,29 +571,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;
}
@@ -617,6 +637,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;
}
@@ -629,7 +650,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);
}
@@ -660,11 +681,13 @@ static int gre_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
static netdev_tx_t ipgre_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);
const struct iphdr *tnl_params;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto free_skb;
if (tunnel->collect_md) {
@@ -675,10 +698,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;
@@ -688,25 +714,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;
}
@@ -714,12 +746,14 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
static netdev_tx_t erspan_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);
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) {
@@ -727,15 +761,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;
}
@@ -756,6 +796,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;
}
@@ -764,7 +805,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;
}
@@ -772,10 +813,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
static netdev_tx_t gre_tap_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);
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto free_skb;
if (tunnel->collect_md) {
@@ -785,17 +828,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] 12+ messages in thread
* [PATCH net-next v3 8/9] ip6_gre: make prepare_ip6gre_xmit_other() void
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (6 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 7/9] ip_gre: " Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-16 16:27 ` [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Eric Dumazet
9 siblings, 0 replies; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
prepare_ip6gre_xmit_other() copies the flow template of the tunnel and
picks up its encapsulation limit, DS field and mark. Unlike its IPv6
sibling, which fails when the tunnel encapsulation limit option leaves
no room for another header, it has nothing to fail on: its only return
statement is "return 0", and it has been that way since the function
was added by commit 41337f52b967 ("ip6_gre: set DSCP for non-IP"). Its
caller still checks the result and bails out on a branch that never
runs.
Make it void and drop the check, the way prepare_ip6gre_xmit_ipv4() is
already called. The next patch gives every failing branch of the
transmit path a drop reason, and this one would otherwise get a reason
it can never report.
Assisted-by: Claude-Code:claude-fable-5-1
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
net/ipv6/ip6_gre.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 0b270f4ac774..e7d0fe4570e4 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -693,10 +693,10 @@ static int prepare_ip6gre_xmit_ipv6(struct sk_buff *skb,
return 0;
}
-static int prepare_ip6gre_xmit_other(struct sk_buff *skb,
- struct net_device *dev,
- struct flowi6 *fl6, __u8 *dsfield,
- int *encap_limit)
+static void prepare_ip6gre_xmit_other(struct sk_buff *skb,
+ struct net_device *dev,
+ struct flowi6 *fl6, __u8 *dsfield,
+ int *encap_limit)
{
struct ip6_tnl *t = netdev_priv(dev);
@@ -716,8 +716,6 @@ static int prepare_ip6gre_xmit_other(struct sk_buff *skb,
fl6->flowi6_mark = t->parms.fwmark;
fl6->flowi6_uid = sock_net_uid(dev_net(dev), NULL);
-
- return 0;
}
static struct ip_tunnel_info *skb_tunnel_info_txcheck(struct sk_buff *skb)
@@ -878,9 +876,9 @@ static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev)
__u32 mtu;
int err;
- if (!t->parms.collect_md &&
- prepare_ip6gre_xmit_other(skb, dev, &fl6, &dsfield, &encap_limit))
- return -1;
+ if (!t->parms.collect_md)
+ prepare_ip6gre_xmit_other(skb, dev, &fl6,
+ &dsfield, &encap_limit);
err = gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
t->parms.o_flags));
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (7 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 8/9] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
@ 2026-09-16 14:37 ` Anton Danilov
2026-09-16 22:42 ` Jakub Kicinski
2026-09-16 16:27 ` [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Eric Dumazet
9 siblings, 1 reply; 12+ messages in thread
From: Anton Danilov @ 2026-09-16 14:37 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
Do for the IPv6 tunnels what the previous patches did for the IPv4 ones.
The situation is the same, with one difference: ip6_tnl_xmit() does not
free the packet itself, it returns an error and the callers do, so the
reason has to travel with it. Give it an output parameter, threaded
through ipxip6_tnl_xmit(), __gre6_xmit() and the three ip6gre_xmit_*()
helpers, so that the three ndo_start_xmit handlers, where the packet is
actually freed, can report it.
ip6_gre is converted in the same patch because it calls ip6_tnl_xmit()
and would not build otherwise.
The reasons are the ones already used on the IPv4 side:
- SKB_DROP_REASON_PKT_TOO_BIG for a packet that exceeds the path MTU,
- SKB_DROP_REASON_IP_OUTNOROUTES for the route lookups, including the
source address selection that a collect_md tunnel has to do when its
metadata carries no source address,
- SKB_DROP_REASON_NO_TX_TARGET when an NBMA tunnel gets an skb with no
destination to derive its endpoint from,
- SKB_DROP_REASON_NEIGH_CREATEFAIL when the NBMA neighbour lookup
fails,
- SKB_DROP_REASON_RECURSION_LIMIT for a route pointing back at the
tunnel, and for the trivial tunnelling loop ip6_tnl_addr_conflict()
guards against, a packet whose source is the exit point of the
tunnel,
- SKB_DROP_REASON_NOMEM for the allocations,
- SKB_DROP_REASON_TUNNEL_TXINFO for the collect_md metadata checks,
- SKB_DROP_REASON_TNL_ENCAP when the encapsulation header cannot be
built, and for a collect_md tunnel that has an encapsulation
configured, which ip6_tnl_xmit() does not support,
- SKB_DROP_REASON_UNHANDLED_PROTO for a payload the tunnel does not
carry, either by its mode or because it is neither IPv4, IPv6 nor
MPLS, and for an ERSPAN version that is not implemented.
Two more fit here: SKB_DROP_REASON_DEV_READY when ip6_tnl_xmit_ctl()
refuses the transmit, and SKB_DROP_REASON_IPV6_BAD_EXTHDR when the
tunnel encapsulation limit option leaves no room for another header.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/ip6_tunnel.h | 3 +-
net/ipv6/ip6_gre.c | 117 +++++++++++++++++++++++++++------------
net/ipv6/ip6_tunnel.c | 72 +++++++++++++++++-------
3 files changed, 138 insertions(+), 54 deletions(-)
diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h
index b99805ee2fd1..95f6d12254df 100644
--- a/include/net/ip6_tunnel.h
+++ b/include/net/ip6_tunnel.h
@@ -143,7 +143,8 @@ int ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
int ip6_tnl_xmit_ctl(struct ip6_tnl *t, const struct in6_addr *laddr,
const struct in6_addr *raddr);
int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
- struct flowi6 *fl6, int encap_limit, __u32 *pmtu, __u8 proto);
+ struct flowi6 *fl6, int encap_limit, __u32 *pmtu, __u8 proto,
+ enum skb_drop_reason *reason);
__u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw);
__u32 ip6_tnl_get_cap(struct ip6_tnl *t, const struct in6_addr *laddr,
const struct in6_addr *raddr);
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index e7d0fe4570e4..5d1e55813fce 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -732,7 +732,8 @@ static struct ip_tunnel_info *skb_tunnel_info_txcheck(struct sk_buff *skb)
static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
struct net_device *dev, __u8 dsfield,
struct flowi6 *fl6, int encap_limit,
- __u32 *pmtu, __be16 proto)
+ __u32 *pmtu, __be16 proto,
+ enum skb_drop_reason *reason)
{
struct ip6_tnl *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags);
@@ -756,8 +757,10 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
tun_info = skb_tunnel_info_txcheck(skb);
if (IS_ERR(tun_info) ||
- unlikely(ip_tunnel_info_af(tun_info) != AF_INET6))
+ unlikely(ip_tunnel_info_af(tun_info) != AF_INET6)) {
+ *reason = SKB_DROP_REASON_TUNNEL_TXINFO;
return -EINVAL;
+ }
key = &tun_info->key;
memset(fl6, 0, sizeof(*fl6));
@@ -775,8 +778,11 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
ip_tunnel_flags_and(flags, flags, key->tun_flags);
tun_hlen = gre_calc_hlen(flags);
- if (skb_cow_head(skb, dev->needed_headroom ?: tun_hlen + tunnel->encap_hlen))
+ if (skb_cow_head(skb, dev->needed_headroom ?:
+ tun_hlen + tunnel->encap_hlen)) {
+ *reason = SKB_DROP_REASON_NOMEM;
return -ENOMEM;
+ }
gre_build_header(skb, tun_hlen,
flags, protocol,
@@ -786,8 +792,10 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
0);
} else {
- if (skb_cow_head(skb, dev->needed_headroom ?: tunnel->hlen))
+ if (skb_cow_head(skb, dev->needed_headroom ?: tunnel->hlen)) {
+ *reason = SKB_DROP_REASON_NOMEM;
return -ENOMEM;
+ }
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
@@ -799,10 +807,11 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
}
return ip6_tnl_xmit(skb, dev, dsfield, fl6, encap_limit, pmtu,
- NEXTHDR_GRE);
+ NEXTHDR_GRE, reason);
}
-static inline int ip6gre_xmit_ipv4(struct sk_buff *skb, struct net_device *dev)
+static inline int ip6gre_xmit_ipv4(struct sk_buff *skb, struct net_device *dev,
+ enum skb_drop_reason *reason)
{
struct ip6_tnl *t = netdev_priv(dev);
int encap_limit = -1;
@@ -819,11 +828,13 @@ static inline int ip6gre_xmit_ipv4(struct sk_buff *skb, struct net_device *dev)
err = gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
t->parms.o_flags));
- if (err)
+ if (err) {
+ *reason = SKB_DROP_REASON_NOMEM;
return -1;
+ }
err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- skb->protocol);
+ skb->protocol, reason);
if (err != 0) {
/* XXX: send ICMP error even if DF is not set. */
if (err == -EMSGSIZE)
@@ -835,7 +846,8 @@ static inline int ip6gre_xmit_ipv4(struct sk_buff *skb, struct net_device *dev)
return 0;
}
-static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev)
+static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev,
+ enum skb_drop_reason *reason)
{
struct ip6_tnl *t = netdev_priv(dev);
struct ipv6hdr *ipv6h = ipv6_hdr(skb);
@@ -845,19 +857,25 @@ static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev)
__u32 mtu;
int err;
- if (ipv6_addr_equal(&t->parms.raddr, &ipv6h->saddr))
+ if (ipv6_addr_equal(&t->parms.raddr, &ipv6h->saddr)) {
+ *reason = SKB_DROP_REASON_RECURSION_LIMIT;
return -1;
+ }
if (!t->parms.collect_md &&
- prepare_ip6gre_xmit_ipv6(skb, dev, &fl6, &dsfield, &encap_limit))
+ prepare_ip6gre_xmit_ipv6(skb, dev, &fl6, &dsfield, &encap_limit)) {
+ *reason = SKB_DROP_REASON_IPV6_BAD_EXTHDR;
return -1;
+ }
if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- t->parms.o_flags)))
+ t->parms.o_flags))) {
+ *reason = SKB_DROP_REASON_NOMEM;
return -1;
+ }
err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit,
- &mtu, skb->protocol);
+ &mtu, skb->protocol, reason);
if (err != 0) {
if (err == -EMSGSIZE)
icmpv6_ndo_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
@@ -867,7 +885,8 @@ static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev)
return 0;
}
-static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev)
+static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev,
+ enum skb_drop_reason *reason)
{
struct ip6_tnl *t = netdev_priv(dev);
int encap_limit = -1;
@@ -882,9 +901,12 @@ static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev)
err = gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
t->parms.o_flags));
- if (err)
+ if (err) {
+ *reason = SKB_DROP_REASON_NOMEM;
return err;
- err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, skb->protocol);
+ }
+ err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
+ skb->protocol, reason);
return err;
}
@@ -892,16 +914,20 @@ static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev)
static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb,
struct net_device *dev)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel_info *tun_info = NULL;
struct ip6_tnl *t = netdev_priv(dev);
__be16 payload_protocol;
int ret;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto tx_err;
- if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr))
+ if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) {
+ reason = SKB_DROP_REASON_DEV_READY;
goto tx_err;
+ }
if (t->parms.collect_md)
tun_info = skb_tunnel_info_txcheck(skb);
@@ -909,13 +935,13 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb,
payload_protocol = skb_protocol(skb, true);
switch (payload_protocol) {
case htons(ETH_P_IP):
- ret = ip6gre_xmit_ipv4(skb, dev);
+ ret = ip6gre_xmit_ipv4(skb, dev, &reason);
break;
case htons(ETH_P_IPV6):
- ret = ip6gre_xmit_ipv6(skb, dev);
+ ret = ip6gre_xmit_ipv6(skb, dev, &reason);
break;
default:
- ret = ip6gre_xmit_other(skb, dev);
+ ret = ip6gre_xmit_other(skb, dev, &reason);
break;
}
@@ -928,13 +954,14 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb,
if (!IS_ERR(tun_info))
DEV_STATS_INC(dev, tx_errors);
DEV_STATS_INC(dev, tx_dropped);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return NETDEV_TX_OK;
}
static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
struct net_device *dev)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip_tunnel_info *tun_info = NULL;
struct ip6_tnl *t = netdev_priv(dev);
struct dst_entry *dst = skb_dst(skb);
@@ -948,18 +975,25 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
__u32 mtu;
int nhoff;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto tx_err;
- if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr))
+ if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) {
+ reason = SKB_DROP_REASON_DEV_READY;
goto tx_err;
+ }
- if (gre_handle_offloads(skb, false))
+ if (gre_handle_offloads(skb, false)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto tx_err;
+ }
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 tx_err;
+ }
truncate = true;
}
@@ -979,8 +1013,10 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
truncate = true;
}
- if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen))
+ if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen)) {
+ reason = SKB_DROP_REASON_NOMEM;
goto tx_err;
+ }
IPCB(skb)->flags = 0;
@@ -994,8 +1030,10 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
tun_info = skb_tunnel_info_txcheck(skb);
if (IS_ERR(tun_info) ||
- unlikely(ip_tunnel_info_af(tun_info) != AF_INET6))
+ unlikely(ip_tunnel_info_af(tun_info) != AF_INET6)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto tx_err;
+ }
key = &tun_info->key;
memset(&fl6, 0, sizeof(fl6));
@@ -1007,10 +1045,14 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
dsfield = key->tos;
if (!test_bit(IP_TUNNEL_ERSPAN_OPT_BIT,
- tun_info->key.tun_flags))
+ tun_info->key.tun_flags)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto tx_err;
- if (tun_info->options_len < sizeof(*md))
+ }
+ if (tun_info->options_len < sizeof(*md)) {
+ reason = SKB_DROP_REASON_TUNNEL_TXINFO;
goto tx_err;
+ }
md = ip_tunnel_info_opts(tun_info);
tun_id = tunnel_id_to_key32(key->tun_id);
@@ -1028,6 +1070,7 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
truncate, false);
proto = htons(ETH_P_ERSPAN2);
} else {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto tx_err;
}
} else {
@@ -1038,11 +1081,16 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
&dsfield, &encap_limit);
break;
case htons(ETH_P_IPV6):
- if (ipv6_addr_equal(&t->parms.raddr, &ipv6_hdr(skb)->saddr))
+ if (ipv6_addr_equal(&t->parms.raddr,
+ &ipv6_hdr(skb)->saddr)) {
+ reason = SKB_DROP_REASON_RECURSION_LIMIT;
goto tx_err;
+ }
if (prepare_ip6gre_xmit_ipv6(skb, dev, &fl6,
- &dsfield, &encap_limit))
+ &dsfield, &encap_limit)) {
+ reason = SKB_DROP_REASON_IPV6_BAD_EXTHDR;
goto tx_err;
+ }
break;
default:
memcpy(&fl6, &t->fl.u.ip6, sizeof(fl6));
@@ -1061,6 +1109,7 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
truncate, false);
proto = htons(ETH_P_ERSPAN2);
} else {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto tx_err;
}
@@ -1079,7 +1128,7 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
dst->ops->update_pmtu(dst, NULL, skb, mtu, false);
}
err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- NEXTHDR_GRE);
+ NEXTHDR_GRE, &reason);
if (err != 0) {
/* XXX: send ICMP error even if DF is not set. */
if (err == -EMSGSIZE) {
@@ -1098,7 +1147,7 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
if (!IS_ERR(tun_info))
DEV_STATS_INC(dev, tx_errors);
DEV_STATS_INC(dev, tx_dropped);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return NETDEV_TX_OK;
}
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 458ce328311b..138151ed5797 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1097,6 +1097,7 @@ EXPORT_SYMBOL_GPL(ip6_tnl_xmit_ctl);
* @encap_limit: encapsulation limit
* @pmtu: Path MTU is stored if packet is too big
* @proto: next header value
+ * @reason: drop reason, only written when the packet is dropped
*
* Description:
* Build new header and do some sanity checks on the packet before sending
@@ -1110,7 +1111,7 @@ EXPORT_SYMBOL_GPL(ip6_tnl_xmit_ctl);
int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
struct flowi6 *fl6, int encap_limit, __u32 *pmtu,
- __u8 proto)
+ __u8 proto, enum skb_drop_reason *reason)
{
struct ip6_tnl *t = netdev_priv(dev);
struct net *net = t->net;
@@ -1143,13 +1144,17 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
struct neighbour *neigh;
int addr_type;
- if (!skb_dst(skb))
+ if (!skb_dst(skb)) {
+ *reason = SKB_DROP_REASON_NO_TX_TARGET;
goto tx_err_link_failure;
+ }
neigh = dst_neigh_lookup(skb_dst(skb),
&ipv6_hdr(skb)->daddr);
- if (!neigh)
+ if (!neigh) {
+ *reason = SKB_DROP_REASON_NEIGH_CREATEFAIL;
goto tx_err_link_failure;
+ }
addr6 = (struct in6_addr *)&neigh->primary_key;
addr_type = ipv6_addr_type(addr6);
@@ -1162,8 +1167,10 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
} else if (payload_protocol == htons(ETH_P_IP)) {
const struct rtable *rt = skb_rtable(skb);
- if (!rt)
+ if (!rt) {
+ *reason = SKB_DROP_REASON_NO_TX_TARGET;
goto tx_err_link_failure;
+ }
if (rt->rt_gw_family == AF_INET6)
memcpy(&fl6->daddr, &rt->rt_gw6, sizeof(fl6->daddr));
@@ -1180,8 +1187,10 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
if (use_cache)
dst = dst_cache_get(&t->dst_cache);
- if (!ip6_tnl_xmit_ctl(t, &fl6->saddr, &fl6->daddr))
+ if (!ip6_tnl_xmit_ctl(t, &fl6->saddr, &fl6->daddr)) {
+ *reason = SKB_DROP_REASON_DEV_READY;
goto tx_err_link_failure;
+ }
if (!dst) {
route_lookup:
@@ -1190,18 +1199,23 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
dst = ip6_route_output(net, NULL, fl6);
- if (dst->error)
+ if (dst->error) {
+ *reason = SKB_DROP_REASON_IP_OUTNOROUTES;
goto tx_err_link_failure;
+ }
dst = xfrm_lookup(net, dst, flowi6_to_flowi(fl6), NULL, 0);
if (IS_ERR(dst)) {
err = PTR_ERR(dst);
dst = NULL;
+ *reason = SKB_DROP_REASON_IP_OUTNOROUTES;
goto tx_err_link_failure;
}
if (t->parms.collect_md && ipv6_addr_any(&fl6->saddr) &&
ipv6_dev_get_saddr(net, ip6_dst_idev(dst)->dev,
- &fl6->daddr, 0, &fl6->saddr))
+ &fl6->daddr, 0, &fl6->saddr)) {
+ *reason = SKB_DROP_REASON_IP_OUTNOROUTES;
goto tx_err_link_failure;
+ }
ndst = dst;
}
@@ -1211,6 +1225,7 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
DEV_STATS_INC(dev, collisions);
net_warn_ratelimited("%s: Local routing loop detected!\n",
t->parms.name);
+ *reason = SKB_DROP_REASON_RECURSION_LIMIT;
goto tx_err_dst_release;
}
mtu = dst6_mtu(dst) - eth_hlen - psh_hlen - t->tun_hlen;
@@ -1225,6 +1240,7 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
if (skb->len - t->tun_hlen - eth_hlen > mtu && !skb_is_gso(skb)) {
*pmtu = mtu;
err = -EMSGSIZE;
+ *reason = SKB_DROP_REASON_PKT_TOO_BIG;
goto tx_err_dst_release;
}
@@ -1247,12 +1263,16 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
*/
max_headroom += LL_RESERVED_SPACE(tdev);
- if (skb_cow_head(skb, max_headroom))
+ if (skb_cow_head(skb, max_headroom)) {
+ *reason = SKB_DROP_REASON_NOMEM;
goto tx_err_dst_release;
+ }
if (t->parms.collect_md) {
- if (t->encap.type != TUNNEL_ENCAP_NONE)
+ if (t->encap.type != TUNNEL_ENCAP_NONE) {
+ *reason = SKB_DROP_REASON_TNL_ENCAP;
goto tx_err_dst_release;
+ }
} else {
if (use_cache && ndst)
dst_cache_set_ip6(&t->dst_cache, ndst, &fl6->saddr);
@@ -1276,8 +1296,10 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
ip_tunnel_adj_headroom(dev, max_headroom);
err = ip6_tnl_encap(skb, t, &proto, fl6);
- if (err)
+ if (err) {
+ *reason = SKB_DROP_REASON_TNL_ENCAP;
return err;
+ }
if (encap_limit >= 0) {
init_tel_txopt(&opt, encap_limit);
@@ -1306,7 +1328,7 @@ EXPORT_SYMBOL(ip6_tnl_xmit);
static inline int
ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
- u8 protocol)
+ u8 protocol, enum skb_drop_reason *reason)
{
struct ip6_tnl *t = netdev_priv(dev);
struct ipv6hdr *ipv6h;
@@ -1320,8 +1342,10 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
int err;
tproto = READ_ONCE(t->parms.proto);
- if (tproto != protocol && tproto != 0)
+ if (tproto != protocol && tproto != 0) {
+ *reason = SKB_DROP_REASON_UNHANDLED_PROTO;
return -1;
+ }
if (t->parms.collect_md) {
struct ip_tunnel_info *tun_info;
@@ -1329,8 +1353,10 @@ ipxip6_tnl_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_INET6))
+ ip_tunnel_info_af(tun_info) != AF_INET6)) {
+ *reason = SKB_DROP_REASON_TUNNEL_TXINFO;
return -1;
+ }
key = &tun_info->key;
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = protocol;
@@ -1367,6 +1393,7 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
if (tel->encap_limit == 0) {
icmpv6_ndo_send(skb, ICMPV6_PARAMPROB,
ICMPV6_HDR_FIELD, offset + 2);
+ *reason = SKB_DROP_REASON_IPV6_BAD_EXTHDR;
return -1;
}
encap_limit = tel->encap_limit - 1;
@@ -1408,13 +1435,15 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
fl6.flowi6_uid = sock_net_uid(dev_net(dev), NULL);
dsfield = INET_ECN_encapsulate(dsfield, orig_dsfield);
- if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6))
+ if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6)) {
+ *reason = SKB_DROP_REASON_NOMEM;
return -1;
+ }
skb_set_inner_ipproto(skb, protocol);
err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- protocol);
+ protocol, reason);
if (err != 0) {
/* XXX: send ICMP error even if DF is not set. */
if (err == -EMSGSIZE)
@@ -1438,11 +1467,13 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
static netdev_tx_t
ip6_tnl_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
struct ip6_tnl *t = netdev_priv(dev);
u8 ipproto;
int ret;
- if (!pskb_inet_may_pull(skb))
+ reason = pskb_inet_may_pull_reason(skb);
+ if (reason)
goto tx_err;
switch (skb->protocol) {
@@ -1450,18 +1481,21 @@ ip6_tnl_start_xmit(struct sk_buff *skb, struct net_device *dev)
ipproto = IPPROTO_IPIP;
break;
case htons(ETH_P_IPV6):
- if (ip6_tnl_addr_conflict(t, ipv6_hdr(skb)))
+ if (ip6_tnl_addr_conflict(t, ipv6_hdr(skb))) {
+ reason = SKB_DROP_REASON_RECURSION_LIMIT;
goto tx_err;
+ }
ipproto = IPPROTO_IPV6;
break;
case htons(ETH_P_MPLS_UC):
ipproto = IPPROTO_MPLS;
break;
default:
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto tx_err;
}
- ret = ipxip6_tnl_xmit(skb, dev, ipproto);
+ ret = ipxip6_tnl_xmit(skb, dev, ipproto, &reason);
if (ret < 0)
goto tx_err;
@@ -1470,7 +1504,7 @@ ip6_tnl_start_xmit(struct sk_buff *skb, struct net_device *dev)
tx_err:
DEV_STATS_INC(dev, tx_errors);
DEV_STATS_INC(dev, tx_dropped);
- kfree_skb(skb);
+ kfree_skb_reason(skb, reason);
return NETDEV_TX_OK;
}
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
` (8 preceding siblings ...)
2026-09-16 14:37 ` [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-16 16:27 ` Eric Dumazet
9 siblings, 0 replies; 12+ messages in thread
From: Eric Dumazet @ 2026-09-16 16:27 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
On Wed, Sep 16, 2026 at 7:37 AM Anton Danilov
<littlesmilingcloud@gmail.com> wrote:
>
> Only vxlan reports drop reasons among the tunnel drivers today.
> Everything else, on both the receive and the transmit side, ends in a
> plain kfree_skb(), so a packet that a tunnel throws away is invisible
> to dropwatch, drop_monitor and perf trace -e skb:kfree_skb.
This is not correct, and it is the justification the whole series rests on.
kfree_skb() is a one line wrapper, from include/linux/skbuff.h:
static inline void kfree_skb(struct sk_buff *skb)
{
kfree_skb_reason(skb, SKB_DROP_REASON_NOT_SPECIFIED);
}
Both spellings hit the same trace_kfree_skb tracepoint. These packets are
already visible to perf trace -e skb:kfree_skb and are already reported by
drop_monitor today. They just carry reason=NOT_SPECIFIED.
It goes further than that. The tracepoint also carries the call site:
TRACE_EVENT(kfree_skb,
TP_PROTO(struct sk_buff *skb, void *location,
enum skb_drop_reason reason, const struct sock *rx_sk),
drop_monitor's summary mode aggregates on exactly that (trace_drop_common()
keys on point->pc), and packet mode exports it as NET_DM_ATTR_PC plus an
in-kernel resolved NET_DM_ATTR_SYMBOL. So userspace gets
"ip_tunnel_rcv+0x1a4" for each of these drops without any of your patches.
That is how tunnel drops have been triaged since well before drop reasons
existed.
A consequence worth noticing: several of the kfree_skb() ->
kfree_skb_reason(skb, SKB_DROP_REASON_NOT_SPECIFIED) conversions in patch 3
are no-ops.
Now, I am not objecting to the series. I am objecting to the argument,
because the real one is better and you are not making it:
Every failure in these functions funnels into a single drop: / tx_error: /
err_free_skb: label. So "location" is one program counter for all of them.
Four distinct failures in ip_tunnel_rcv(), around twenty in ip_gre's
transmit path, eighty across the series, all collapsing into one bucket.
Call site attribution, which is the pre-drop-reason fallback, tells you
nothing here. On top of that the pc is not a stable interface: it moves with
compiler version, inlining and config, so it cannot be used for filtering or
for comparing across kernels, while NET_DM_ATTR_REASON and BPF filtering on
the reason field can.
Please rewrite the cover letter and the individual changelogs along those
lines. Several of them repeat the "invisible" wording.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path
2026-09-16 14:37 ` [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-16 22:42 ` Jakub Kicinski
0 siblings, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2026-09-16 22:42 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Eric Dumazet, Paolo Abeni, David Ahern,
Simon Horman, Ido Schimmel, linux-kernel
On Wed, 16 Sep 2026 17:37:17 +0300 Anton Danilov wrote:
> -static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev)
> +static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev,
> + enum skb_drop_reason *reason)
Pls ask claude to look thru the functions you're modifying and see if
they can be changed to use the reason as the return code (using
SKB_NOT_DROPPED_YET as success), instead of using the output argument.
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-09-16 22:42 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-16 14:37 [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 1/9] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 2/9] ip6_tunnel: " Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 3/9] gre: make gre_parse_header() report a drop reason Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 4/9] ip_gre: add drop reasons to the RX path Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 5/9] ip6_gre: " Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 6/9] ip_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 7/9] ip_gre: " Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 8/9] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
2026-09-16 14:37 ` [PATCH net-next v3 9/9] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-16 22:42 ` Jakub Kicinski
2026-09-16 16:27 ` [PATCH net-next v3 0/9] tunnels: add core and gre drop reasons Eric Dumazet
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®