* [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons
@ 2026-09-22 22:14 Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 01/10] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
` (10 more replies)
0 siblings, 11 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:14 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. 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 interface either: its offset moves with the compiler, inlining
and the configuration, so a filter on it has to follow every rebuild.
The reason stays the same across kernels, whether NET_DM_ATTR_REASON
reports it or a BPF program matches 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 generic paths shared by ipip, sit, gre and their
IPv6 counterparts, plus the GRE specific code, in 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, 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 drops
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-6 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. Three
reasons are added: GRE_INVALID_HDR and GRE_TUNNEL_NOT_FOUND, mirroring
vxlan's VXLAN_INVALID_HDR and VXLAN_VNI_NOT_FOUND, and GRE_CSUM, like
TCP_CSUM and UDP_CSUM.
Patch 4 adds __iptunnel_pull_header_reason(), because
__iptunnel_pull_header() reports a packet too short to pull as -ENOMEM,
the same as an allocation failure, and ip6_gre calls it for every GRE
packet, before the tunnel lookup.
Patches 7-10 convert the transmit side of ip_tunnel, ip_gre, ip6_tunnel
and ip6_gre. One reason is added, TNL_ENCAP, for a failure to build the
encapsulation header. Patch 9 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. ip6_tnl_xmit() leaves
freeing the packet to its callers, so it and the helpers between it and
the ndo_start_xmit handlers return the drop reason instead of an error.
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.
Changes since v3:
- the functions that took a drop reason output parameter now return the
reason, SKB_NOT_DROPPED_YET on success: gre_parse_header(), the GRE
receive helpers, ip6_tnl_xmit() and the helpers between it and the
ndo_start_xmit handlers (Jakub)
- the callers of gre_parse_header() take the header length from
tpi->hdr_len; the ICMP error handlers pass a new icmp_err argument
instead of a NULL reason pointer
- the IPv6 transmit paths send the ICMP error on PKT_TOO_BIG, and
ipgre_rcv() retries ETH_P_TEB on GRE_TUNNEL_NOT_FOUND: the same
conditions as -EMSGSIZE and PACKET_NEXT before
- the motivation no longer says these drops are invisible; the problem
is the NOT_SPECIFIED reason and the call site all the failures of a
function share (Eric)
- new patch 4, __iptunnel_pull_header_reason(): a packet too short to
pull is reported as PKT_TOO_SMALL instead of NOMEM
- PACKET_RCVD, PACKET_REJECT and PACKET_NEXT are removed
- the ip_tunnel transmit patch no longer says the counters cannot tell
an MTU black hole from a failed route lookup
- the TNL_OPT_MISMATCH kernel-doc also covers an unexpected checksum;
other commit message fixes
- 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 (10):
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_tunnel: add __iptunnel_pull_header_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 | 39 +++++++
include/net/gre.h | 5 +-
include/net/ip6_tunnel.h | 5 +-
include/net/ip_tunnels.h | 17 ++-
net/ipv4/gre_demux.c | 42 ++++---
net/ipv4/ip_gre.c | 187 +++++++++++++++++++-----------
net/ipv4/ip_tunnel.c | 60 ++++++++--
net/ipv4/ip_tunnel_core.c | 24 ++--
net/ipv6/ip6_gre.c | 211 ++++++++++++++++++++--------------
net/ipv6/ip6_tunnel.c | 123 ++++++++++++--------
10 files changed, 467 insertions(+), 246 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v4 01/10] ip_tunnel: add drop reasons to the generic RX path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
@ 2026-09-22 22:14 ` Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 02/10] ip6_tunnel: " Anton Danilov
` (9 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:14 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 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_TNL_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_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 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_TNL_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.
Assisted-by: Claude-Code:claude-opus-5
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 12f909651591..edbe58a22ddf 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,21 @@ 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, or a GRE tunnel without
+ * 'icsum' received a packet with a checksum.
+ */
+ 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 0875474a578a..c94f4c055027 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_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;
@@ -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] 15+ messages in thread
* [PATCH net-next v4 02/10] ip6_tunnel: add drop reasons to the generic RX path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 01/10] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
@ 2026-09-22 22:14 ` Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason Anton Danilov
` (8 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:14 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: 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.
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] 15+ messages in thread
* [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 01/10] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 02/10] ip6_tunnel: " Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-23 14:59 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason() Anton Danilov
` (7 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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 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. 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, like 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.
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 icmp_err argument. The checksum is still
computed either way.
Tunnel lookup failures still report SKB_DROP_REASON_NOT_SPECIFIED here,
which gre_rcv() sets again once the header is parsed; 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 | 5 +++--
net/ipv4/gre_demux.c | 42 ++++++++++++++++++++++-------------
net/ipv4/ip_gre.c | 18 +++++++--------
net/ipv6/ip6_gre.c | 18 +++++++--------
5 files changed, 56 insertions(+), 36 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index edbe58a22ddf..ffa11206b6ca 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)
/**
@@ -629,6 +631,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..4cb19abea90d 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 icmp_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..e117056525f0 100644
--- a/net/ipv4/gre_demux.c
+++ b/net/ipv4/gre_demux.c
@@ -56,28 +56,35 @@ 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.
+/* Fills in tpi, including the header length to be pulled in tpi->hdr_len,
+ * and returns SKB_NOT_DROPPED_YET, or the reason to drop the packet if the
+ * header is rejected.
* Note that caller must use pskb_may_pull() before pulling GRE header.
+ *
+ * @icmp_err is set by the ICMP error handlers, which only get a part of
+ * the original packet: a checksum failure does not reject the header then,
+ * the checksum is still computed and the rest of the header is parsed.
*/
-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 icmp_err, __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))))
- return -EINVAL;
+ return SKB_DROP_REASON_HDR_TRUNC;
greh = (struct gre_base_hdr *)(skb->data + nhs);
if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
- return -EINVAL;
+ return SKB_DROP_REASON_GRE_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;
+ return SKB_DROP_REASON_HDR_TRUNC;
greh = (struct gre_base_hdr *)(skb->data + nhs);
tpi->proto = greh->protocol;
@@ -87,9 +94,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 (!icmp_err) {
+ return SKB_DROP_REASON_GRE_CSUM;
}
options++;
@@ -117,7 +123,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_HDR_TRUNC;
tpi->proto = proto;
if ((*val & 0xF0) != 0x40)
hdr_len += 4;
@@ -133,28 +139,32 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
struct erspan_base_hdr *ershdr;
if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr)))
- return -EINVAL;
+ return SKB_DROP_REASON_HDR_TRUNC;
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)
{
+ 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 +177,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 5e877018e006..ae50fd0f6792 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,9 +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
if (ipv4_is_multicast(ip_hdr(skb)->daddr)) {
@@ -451,25 +450,26 @@ static int gre_rcv(struct sk_buff *skb)
}
#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 c851af22b9fe..d36949cbe9fa 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,20 @@ 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);
- if (hdr_len < 0)
+ reason = gre_parse_header(skb, &tpi, false, htons(ETH_P_IPV6), 0);
+ if (reason)
goto drop;
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
- if (iptunnel_pull_header(skb, hdr_len, tpi.proto, false))
+ 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 +591,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] 15+ messages in thread
* [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason()
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (2 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-23 15:41 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 05/10] ip_gre: add drop reasons to the RX path Anton Danilov
` (6 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, linux-kernel
__iptunnel_pull_header() returns -ENOMEM whenever it fails. It can
fail in two pskb_may_pull() calls, one for the tunnel header and one
for the inner Ethernet header of ETH_P_TEB, and in the skb_unclone()
done for GSO packets. pskb_may_pull() fails when the packet is shorter
than the requested length as well as when pulling from the frags cannot
allocate, so a truncated packet and an allocation failure look the same
to the callers. The ones that report a drop reason can only pick
SKB_DROP_REASON_NOMEM, as vxlan_rcv() does, and so would the GRE
receive paths converted by the following patches. In ip6_gre,
gre_rcv() calls the helper before the tunnel lookup, so a packet from
any sender whose ETH_P_TEB inner Ethernet header or WCCPv2 extra word
is cut short would be reported as an out of memory condition.
Add __iptunnel_pull_header_reason(), which returns the reason
pskb_may_pull_reason() already computes, SKB_DROP_REASON_NOMEM when
skb_unclone() fails, and SKB_NOT_DROPPED_YET on success. Turn
__iptunnel_pull_header() into a static inline wrapper that keeps
returning -ENOMEM on any failure, so its existing callers are left
unchanged; the export moves to the new function.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/ip_tunnels.h | 13 +++++++++++--
net/ipv4/ip_tunnel_core.c | 24 ++++++++++++++++--------
2 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 7102aa11fae2..c68031d01c39 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -614,8 +614,17 @@ static inline u8 ip_tunnel_ecn_encap(u8 tos, const struct iphdr *iph,
return INET_ECN_encapsulate(tos, inner);
}
-int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
- __be16 inner_proto, bool raw_proto, bool xnet);
+enum skb_drop_reason
+__iptunnel_pull_header_reason(struct sk_buff *skb, int hdr_len,
+ __be16 inner_proto, bool raw_proto, bool xnet);
+
+static inline int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
+ __be16 inner_proto, bool raw_proto,
+ bool xnet)
+{
+ return __iptunnel_pull_header_reason(skb, hdr_len, inner_proto,
+ raw_proto, xnet) ? -ENOMEM : 0;
+}
static inline int iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
__be16 inner_proto, bool xnet)
diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c
index bab42b9e277f..51f1537ce6c1 100644
--- a/net/ipv4/ip_tunnel_core.c
+++ b/net/ipv4/ip_tunnel_core.c
@@ -106,19 +106,24 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
}
EXPORT_SYMBOL_GPL(iptunnel_xmit);
-int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
- __be16 inner_proto, bool raw_proto, bool xnet)
+enum skb_drop_reason
+__iptunnel_pull_header_reason(struct sk_buff *skb, int hdr_len,
+ __be16 inner_proto, bool raw_proto, bool xnet)
{
- if (unlikely(!pskb_may_pull(skb, hdr_len)))
- return -ENOMEM;
+ enum skb_drop_reason reason;
+
+ reason = pskb_may_pull_reason(skb, hdr_len);
+ if (unlikely(reason))
+ return reason;
skb_pull_rcsum(skb, hdr_len);
if (!raw_proto && inner_proto == htons(ETH_P_TEB)) {
struct ethhdr *eh;
- if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
- return -ENOMEM;
+ reason = pskb_may_pull_reason(skb, ETH_HLEN);
+ if (unlikely(reason))
+ return reason;
eh = (struct ethhdr *)skb->data;
if (likely(eth_proto_is_802_3(eh->h_proto)))
@@ -135,9 +140,12 @@ int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
skb_set_queue_mapping(skb, 0);
skb_scrub_packet(skb, xnet);
- return iptunnel_pull_offloads(skb);
+ if (unlikely(iptunnel_pull_offloads(skb)))
+ return SKB_DROP_REASON_NOMEM;
+
+ return SKB_NOT_DROPPED_YET;
}
-EXPORT_SYMBOL_GPL(__iptunnel_pull_header);
+EXPORT_SYMBOL_GPL(__iptunnel_pull_header_reason);
struct metadata_dst *iptunnel_metadata_reply(struct metadata_dst *md,
gfp_t flags)
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v4 05/10] ip_gre: add drop reasons to the RX path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (3 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason() Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 06/10] ip6_gre: " Anton Danilov
` (5 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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, 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.
Add SKB_DROP_REASON_GRE_TUNNEL_NOT_FOUND for it, in the spirit of the
existing SKB_DROP_REASON_VXLAN_VNI_NOT_FOUND.
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_GRE_TUNNEL_NOT_FOUND.
The length checks reuse SKB_DROP_REASON_HDR_TRUNC. The headers are
pulled with __iptunnel_pull_header_reason(), added by the previous
patch, which reports 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.
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 | 74 ++++++++++++++++++++---------------
2 files changed, 48 insertions(+), 32 deletions(-)
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index ffa11206b6ca..186d9e70e9cb 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)
/**
@@ -638,6 +639,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 ae50fd0f6792..e158d6e9d42a 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -264,13 +264,15 @@ 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)
{
struct net *net = dev_net(skb->dev);
struct metadata_dst *tun_dst = NULL;
struct erspan_base_hdr *ershdr;
IP_TUNNEL_DECLARE_FLAGS(flags);
+ enum skb_drop_reason reason;
struct ip_tunnel_net *itn;
struct ip_tunnel *tunnel;
const struct iphdr *iph;
@@ -290,7 +292,7 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
} else {
if (unlikely(!pskb_may_pull(skb,
gre_hdr_len + sizeof(*ershdr))))
- return PACKET_REJECT;
+ return SKB_DROP_REASON_HDR_TRUNC;
ershdr = (struct erspan_base_hdr *)(skb->data + gre_hdr_len);
ver = ershdr->ver;
@@ -307,12 +309,12 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
len = gre_hdr_len + erspan_hdr_len(ver);
if (unlikely(!pskb_may_pull(skb, len)))
- return PACKET_REJECT;
+ return SKB_DROP_REASON_HDR_TRUNC;
- if (__iptunnel_pull_header(skb,
- len,
- htons(ETH_P_TEB),
- false, false) < 0)
+ reason = __iptunnel_pull_header_reason(skb, len,
+ htons(ETH_P_TEB),
+ false, false);
+ if (reason)
goto drop;
if (tunnel->collect_md) {
@@ -328,7 +330,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,19 +356,22 @@ 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_GRE_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)
{
struct metadata_dst *tun_dst = NULL;
+ enum skb_drop_reason reason;
const struct iphdr *iph;
struct ip_tunnel *tunnel;
@@ -377,8 +382,10 @@ 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) < 0)
+ reason = __iptunnel_pull_header_reason(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_GRE_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_GRE_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)
@@ -453,16 +462,17 @@ static int gre_rcv(struct sk_buff *skb)
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, 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] 15+ messages in thread
* [PATCH net-next v4 06/10] ip6_gre: add drop reasons to the RX path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (4 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 05/10] ip_gre: add drop reasons to the RX path Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path Anton Danilov
` (4 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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. 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_GRE_TUNNEL_NOT_FOUND when no tunnel matches,
- SKB_DROP_REASON_HDR_TRUNC when a header is too short,
- what __iptunnel_pull_header_reason() returns 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.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/ip_tunnels.h | 4 ----
net/ipv6/ip6_gre.c | 46 +++++++++++++++++++++++-----------------
2 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index c68031d01c39..27a9e097996b 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 d36949cbe9fa..ada48e23ca9d 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,22 +472,22 @@ 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_GRE_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;
@@ -495,7 +496,7 @@ static int ip6erspan_rcv(struct sk_buff *skb,
u8 ver;
if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr))))
- return PACKET_REJECT;
+ return SKB_DROP_REASON_HDR_TRUNC;
ipv6h = ipv6_hdr(skb);
ershdr = (struct erspan_base_hdr *)skb->data;
@@ -506,14 +507,16 @@ static int ip6erspan_rcv(struct sk_buff *skb,
tpi->proto);
if (tunnel) {
int len = erspan_hdr_len(ver);
+ enum skb_drop_reason reason;
if (unlikely(!pskb_may_pull(skb, len)))
- return PACKET_REJECT;
+ return SKB_DROP_REASON_HDR_TRUNC;
- if (__iptunnel_pull_header(skb, len,
- htons(ETH_P_TEB),
- false, false) < 0)
- return PACKET_REJECT;
+ reason = __iptunnel_pull_header_reason(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 +533,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 +561,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_GRE_TUNNEL_NOT_FOUND;
}
static int gre_rcv(struct sk_buff *skb)
@@ -572,19 +575,22 @@ static int gre_rcv(struct sk_buff *skb)
reason = gre_parse_header(skb, &tpi, false, htons(ETH_P_IPV6), 0);
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_reason(skb, tpi.hdr_len, tpi.proto,
+ false, 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] 15+ messages in thread
* [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (5 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 06/10] ip6_gre: " Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-23 15:53 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 08/10] ip_gre: " Anton Danilov
` (3 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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 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 for most of it:
- 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, 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, on the same NBMA path,
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. 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.
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 186d9e70e9cb..a72b84b07daa 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)
/**
@@ -644,6 +645,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 c94f4c055027..66cb0b86fa79 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,8 +622,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)
@@ -629,6 +634,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 +644,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 +653,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 +671,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 +686,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,9 +721,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 &&
@@ -734,8 +750,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,8 +770,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
@@ -780,8 +800,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);
@@ -798,6 +820,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 +834,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 +844,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 +879,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 +895,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] 15+ messages in thread
* [PATCH net-next v4 08/10] ip_gre: add drop reasons to the transmit path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (6 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 09/10] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
` (2 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, 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.
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 e158d6e9d42a..ad669b3f8757 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -506,6 +506,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;
@@ -514,19 +515,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);
@@ -543,12 +550,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;
@@ -562,29 +570,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;
}
@@ -616,6 +636,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;
}
@@ -628,7 +649,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);
}
@@ -659,11 +680,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) {
@@ -674,10 +697,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;
@@ -687,25 +713,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;
}
@@ -713,12 +745,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) {
@@ -726,15 +760,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;
}
@@ -755,6 +795,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;
}
@@ -763,7 +804,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;
}
@@ -771,10 +812,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) {
@@ -784,17 +827,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] 15+ messages in thread
* [PATCH net-next v4 09/10] ip6_gre: make prepare_ip6gre_xmit_other() void
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (7 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 08/10] ip_gre: " Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 10/10] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-23 14:19 ` [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Ido Schimmel
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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 packet's tunnel encapsulation limit option
is 0 and so forbids encapsulating it again, it has nothing to fail on:
its only return statement is "return 0", and it has been that way since
commit 41337f52b967 ("ip6_gre: set DSCP for non-IP") added the function.
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 ada48e23ca9d..6a0a508e0091 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -680,10 +680,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);
@@ -703,8 +703,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)
@@ -865,9 +863,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] 15+ messages in thread
* [PATCH net-next v4 10/10] ip6_tunnel: add drop reasons to the transmit path
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (8 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 09/10] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
@ 2026-09-22 22:15 ` Anton Danilov
2026-09-23 14:19 ` [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Ido Schimmel
10 siblings, 0 replies; 15+ messages in thread
From: Anton Danilov @ 2026-09-22 22:15 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.
Make ip6_tnl_xmit() return the drop reason, SKB_NOT_DROPPED_YET on
success, instead of 0, -1 or an errno, and do the same for
ipxip6_tnl_xmit(), __gre6_xmit() and the ip6gre_xmit_*() helpers, so
that the ndo_start_xmit handlers, where the packet is actually freed,
can report it. The callers only told success from failure and looked
for -EMSGSIZE to send an ICMP error back. ip6_tnl_xmit() returns
-EMSGSIZE only for a packet that exceeds the path MTU, which is exactly
when it reports SKB_DROP_REASON_PKT_TOO_BIG, so the ICMP error is now
sent when that reason is returned. A failed xfrm lookup never returns
-EMSGSIZE, so its errno only ever meant failure and goes away.
__gre6_xmit() was declared as returning netdev_tx_t while it returned an
errno, and now returns the reason as well.
ip6_gre is converted in the same patch because it calls ip6_tnl_xmit()
and depends on its return value.
As in ip_gre, the ndo_start_xmit handlers take the length reason from
pskb_inet_may_pull_reason() instead of pskb_inet_may_pull(). The other
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 and xfrm lookups,
including the source address selection that a collect_md tunnel does
when the flow has 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
packet's tunnel encapsulation limit option is 0, which forbids
encapsulating it again.
A collect_md tunnel has no fixed exit point and its raddr is normally
::, so ip6_tnl_addr_conflict() and the same check in ip6gre_xmit_ipv6()
also drop the packets it sends from ::, such as the DAD probes of an
ip6gretap device. They were dropped before as well;
SKB_DROP_REASON_RECURSION_LIMIT names the check that drops them.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
---
include/net/ip6_tunnel.h | 5 +-
net/ipv6/ip6_gre.c | 137 ++++++++++++++++++++++++---------------
net/ipv6/ip6_tunnel.c | 100 ++++++++++++++++------------
3 files changed, 146 insertions(+), 96 deletions(-)
diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h
index d1f0a427e9c8..363eed61b296 100644
--- a/include/net/ip6_tunnel.h
+++ b/include/net/ip6_tunnel.h
@@ -143,8 +143,9 @@ int ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
bool log_ecn_error);
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);
+enum skb_drop_reason
+ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
+ struct flowi6 *fl6, int encap_limit, __u32 *pmtu, __u8 proto);
__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 6a0a508e0091..31930ff813dd 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -716,10 +716,10 @@ static struct ip_tunnel_info *skb_tunnel_info_txcheck(struct sk_buff *skb)
return tun_info;
}
-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)
+static enum skb_drop_reason __gre6_xmit(struct sk_buff *skb,
+ struct net_device *dev, __u8 dsfield,
+ struct flowi6 *fl6, int encap_limit,
+ __u32 *pmtu, __be16 proto)
{
struct ip6_tnl *tunnel = netdev_priv(dev);
IP_TUNNEL_DECLARE_FLAGS(flags);
@@ -744,7 +744,7 @@ 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))
- return -EINVAL;
+ return SKB_DROP_REASON_TUNNEL_TXINFO;
key = &tun_info->key;
memset(fl6, 0, sizeof(*fl6));
@@ -763,7 +763,7 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
tun_hlen = gre_calc_hlen(flags);
if (skb_cow_head(skb, dev->needed_headroom ?: tun_hlen + tunnel->encap_hlen))
- return -ENOMEM;
+ return SKB_DROP_REASON_NOMEM;
gre_build_header(skb, tun_hlen,
flags, protocol,
@@ -774,7 +774,7 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
} else {
if (skb_cow_head(skb, dev->needed_headroom ?: tunnel->hlen))
- return -ENOMEM;
+ return SKB_DROP_REASON_NOMEM;
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
@@ -789,9 +789,11 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
NEXTHDR_GRE);
}
-static inline int ip6gre_xmit_ipv4(struct sk_buff *skb, struct net_device *dev)
+static inline enum skb_drop_reason ip6gre_xmit_ipv4(struct sk_buff *skb,
+ struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
+ enum skb_drop_reason reason;
int encap_limit = -1;
struct flowi6 fl6;
__u8 dsfield = 0;
@@ -807,54 +809,56 @@ 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)
- return -1;
+ return SKB_DROP_REASON_NOMEM;
- err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- skb->protocol);
- if (err != 0) {
+ reason = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
+ skb->protocol);
+ if (reason) {
/* XXX: send ICMP error even if DF is not set. */
- if (err == -EMSGSIZE)
+ if (reason == SKB_DROP_REASON_PKT_TOO_BIG)
icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
- return -1;
+ return reason;
}
- return 0;
+ return SKB_NOT_DROPPED_YET;
}
-static inline int ip6gre_xmit_ipv6(struct sk_buff *skb, struct net_device *dev)
+static inline enum skb_drop_reason ip6gre_xmit_ipv6(struct sk_buff *skb,
+ struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
struct ipv6hdr *ipv6h = ipv6_hdr(skb);
+ enum skb_drop_reason reason;
int encap_limit = -1;
struct flowi6 fl6;
__u8 dsfield = 0;
__u32 mtu;
- int err;
if (ipv6_addr_equal(&t->parms.raddr, &ipv6h->saddr))
- return -1;
+ return SKB_DROP_REASON_RECURSION_LIMIT;
if (!t->parms.collect_md &&
prepare_ip6gre_xmit_ipv6(skb, dev, &fl6, &dsfield, &encap_limit))
- return -1;
+ return SKB_DROP_REASON_IPV6_BAD_EXTHDR;
if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
t->parms.o_flags)))
- return -1;
+ return SKB_DROP_REASON_NOMEM;
- err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit,
- &mtu, skb->protocol);
- if (err != 0) {
- if (err == -EMSGSIZE)
+ reason = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit,
+ &mtu, skb->protocol);
+ if (reason) {
+ if (reason == SKB_DROP_REASON_PKT_TOO_BIG)
icmpv6_ndo_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
- return -1;
+ return reason;
}
- return 0;
+ return SKB_NOT_DROPPED_YET;
}
-static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev)
+static enum skb_drop_reason ip6gre_xmit_other(struct sk_buff *skb,
+ struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
int encap_limit = -1;
@@ -870,25 +874,28 @@ 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)
- return err;
- err = __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, skb->protocol);
+ return SKB_DROP_REASON_NOMEM;
- return err;
+ return __gre6_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
+ skb->protocol);
}
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);
@@ -896,17 +903,17 @@ 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);
+ reason = ip6gre_xmit_ipv4(skb, dev);
break;
case htons(ETH_P_IPV6):
- ret = ip6gre_xmit_ipv6(skb, dev);
+ reason = ip6gre_xmit_ipv6(skb, dev);
break;
default:
- ret = ip6gre_xmit_other(skb, dev);
+ reason = ip6gre_xmit_other(skb, dev);
break;
}
- if (ret < 0)
+ if (reason)
goto tx_err;
return NETDEV_TX_OK;
@@ -915,13 +922,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);
@@ -930,23 +938,29 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
int encap_limit = -1;
__u8 dsfield = false;
struct flowi6 fl6;
- int err = -EINVAL;
__be16 proto;
__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;
}
@@ -966,8 +980,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;
@@ -981,8 +997,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));
@@ -994,10 +1012,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);
@@ -1015,6 +1037,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 {
@@ -1025,11 +1048,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));
@@ -1048,6 +1076,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;
}
@@ -1065,11 +1094,11 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
if (dst_mtu(dst) > mtu)
dst->ops->update_pmtu(dst, NULL, skb, mtu, false);
}
- err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- NEXTHDR_GRE);
- if (err != 0) {
+ reason = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
+ NEXTHDR_GRE);
+ if (reason) {
/* XXX: send ICMP error even if DF is not set. */
- if (err == -EMSGSIZE) {
+ if (reason == SKB_DROP_REASON_PKT_TOO_BIG) {
if (skb->protocol == htons(ETH_P_IP))
icmp_ndo_send(skb, ICMP_DEST_UNREACH,
ICMP_FRAG_NEEDED, htonl(mtu));
@@ -1085,7 +1114,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..77400a1087af 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1103,14 +1103,14 @@ EXPORT_SYMBOL_GPL(ip6_tnl_xmit_ctl);
* it.
*
* Return:
- * 0 on success
- * -1 fail
- * %-EMSGSIZE message too big. return mtu in this case.
+ * %SKB_NOT_DROPPED_YET on success, otherwise the drop reason.
+ * %SKB_DROP_REASON_PKT_TOO_BIG means the message is too big, the path MTU
+ * is stored in @pmtu in this case.
**/
-int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
- struct flowi6 *fl6, int encap_limit, __u32 *pmtu,
- __u8 proto)
+enum skb_drop_reason
+ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
+ struct flowi6 *fl6, int encap_limit, __u32 *pmtu, __u8 proto)
{
struct ip6_tnl *t = netdev_priv(dev);
struct net *net = t->net;
@@ -1121,11 +1121,11 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
int err_count, mtu;
unsigned int eth_hlen = t->dev->type == ARPHRD_ETHER ? ETH_HLEN : 0;
unsigned int psh_hlen = sizeof(struct ipv6hdr) + t->encap_hlen;
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
unsigned int max_headroom = psh_hlen;
__be16 payload_protocol;
bool use_cache = false;
u8 hop_limit;
- int err = -1;
payload_protocol = skb_protocol(skb, true);
@@ -1143,13 +1143,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 +1166,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 +1186,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 +1198,22 @@ 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 +1223,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;
@@ -1224,7 +1237,7 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
skb_dst_update_pmtu_no_confirm(skb, mtu);
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 +1260,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);
@@ -1275,9 +1292,8 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
+ dst->header_len + t->hlen;
ip_tunnel_adj_headroom(dev, max_headroom);
- err = ip6_tnl_encap(skb, t, &proto, fl6);
- if (err)
- return err;
+ if (ip6_tnl_encap(skb, t, &proto, fl6))
+ return SKB_DROP_REASON_TNL_ENCAP;
if (encap_limit >= 0) {
init_tel_txopt(&opt, encap_limit);
@@ -1294,21 +1310,22 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,
ipv6h->saddr = fl6->saddr;
ipv6h->daddr = fl6->daddr;
ip6tunnel_xmit(NULL, skb, dev, 0);
- return 0;
+ return SKB_NOT_DROPPED_YET;
tx_err_link_failure:
DEV_STATS_INC(dev, tx_carrier_errors);
dst_link_failure(skb);
tx_err_dst_release:
dst_release(dst);
- return err;
+ return reason;
}
EXPORT_SYMBOL(ip6_tnl_xmit);
-static inline int
+static inline enum skb_drop_reason
ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
u8 protocol)
{
struct ip6_tnl *t = netdev_priv(dev);
+ enum skb_drop_reason reason;
struct ipv6hdr *ipv6h;
const struct iphdr *iph;
int encap_limit = -1;
@@ -1317,11 +1334,10 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
__u8 dsfield, orig_dsfield;
__u32 mtu;
u8 tproto;
- int err;
tproto = READ_ONCE(t->parms.proto);
if (tproto != protocol && tproto != 0)
- return -1;
+ return SKB_DROP_REASON_UNHANDLED_PROTO;
if (t->parms.collect_md) {
struct ip_tunnel_info *tun_info;
@@ -1330,7 +1346,7 @@ 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))
- return -1;
+ return SKB_DROP_REASON_TUNNEL_TXINFO;
key = &tun_info->key;
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = protocol;
@@ -1367,7 +1383,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);
- return -1;
+ return SKB_DROP_REASON_IPV6_BAD_EXTHDR;
}
encap_limit = tel->encap_limit - 1;
}
@@ -1409,15 +1425,15 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
dsfield = INET_ECN_encapsulate(dsfield, orig_dsfield);
if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6))
- return -1;
+ return SKB_DROP_REASON_NOMEM;
skb_set_inner_ipproto(skb, protocol);
- err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
- protocol);
- if (err != 0) {
+ reason = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu,
+ protocol);
+ if (reason) {
/* XXX: send ICMP error even if DF is not set. */
- if (err == -EMSGSIZE)
+ if (reason == SKB_DROP_REASON_PKT_TOO_BIG)
switch (protocol) {
case IPPROTO_IPIP:
icmp_ndo_send(skb, ICMP_DEST_UNREACH,
@@ -1429,20 +1445,21 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
default:
break;
}
- return -1;
+ return reason;
}
- return 0;
+ return SKB_NOT_DROPPED_YET;
}
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,19 +1467,22 @@ 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);
- if (ret < 0)
+ reason = ipxip6_tnl_xmit(skb, dev, ipproto);
+ if (reason)
goto tx_err;
return NETDEV_TX_OK;
@@ -1470,7 +1490,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] 15+ messages in thread
* Re: [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
` (9 preceding siblings ...)
2026-09-22 22:15 ` [PATCH net-next v4 10/10] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-23 14:19 ` Ido Schimmel
10 siblings, 0 replies; 15+ messages in thread
From: Ido Schimmel @ 2026-09-23 14:19 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Simon Horman, linux-kernel
On Wed, Sep 23, 2026 at 01:14:57AM +0300, Anton Danilov wrote:
> 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 interface either: its offset moves with the compiler, inlining
> and the configuration, so a filter on it has to follow every rebuild.
> The reason stays the same across kernels, whether NET_DM_ATTR_REASON
> reports it or a BPF program matches 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 generic paths shared by ipip, sit, gre and their
> IPv6 counterparts, plus the GRE specific code, in both directions.
> A later series will do the same for geneve, bareudp, fou and the
> remaining IP in IP drivers.
Please only annotate drivers that you are familiar with, using and can
test. Otherwise it's a burden on the reviewer and potentially useless
code churn that will make it harder to backport future fixes.
>
> 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, 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 drops
> 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-6 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. Three
> reasons are added: GRE_INVALID_HDR and GRE_TUNNEL_NOT_FOUND, mirroring
> vxlan's VXLAN_INVALID_HDR and VXLAN_VNI_NOT_FOUND, and GRE_CSUM, like
> TCP_CSUM and UDP_CSUM.
GRE_CSUM looks fine as I'm not aware of other tunnels that have a
dedicated checksum, but GRE_INVALID_HDR and GRE_TUNNEL_NOT_FOUND should
be renamed to something more generic (e.g., TUNNEL_INVALID_HDR and
TUNNEL_NOT_FOUND) so that they could be reused across drivers and
replace the existing VXLAN ones. Note that you don't need the drop
reason to encode the tunnel name in order to know which tunnel driver
dropped the packet.
>
> Patch 4 adds __iptunnel_pull_header_reason(), because
> __iptunnel_pull_header() reports a packet too short to pull as -ENOMEM,
> the same as an allocation failure, and ip6_gre calls it for every GRE
> packet, before the tunnel lookup.
The series is inconsistent about this and returns different reasons
(HDR_TRUNC / PKT_TOO_SMALL) for the same condition. I suggest that you
convert pskb_may_pull() to pskb_may_pull_reason() and return its reason
instead of HDR_TRUNC.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason
2026-09-22 22:15 ` [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason Anton Danilov
@ 2026-09-23 14:59 ` Ido Schimmel
0 siblings, 0 replies; 15+ messages in thread
From: Ido Schimmel @ 2026-09-23 14:59 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Simon Horman, linux-kernel
On Wed, Sep 23, 2026 at 01:15:00AM +0300, Anton Danilov wrote:
> diff --git a/net/ipv4/gre_demux.c b/net/ipv4/gre_demux.c
> index 96fd7dc6d82d..e117056525f0 100644
> --- a/net/ipv4/gre_demux.c
> +++ b/net/ipv4/gre_demux.c
> @@ -56,28 +56,35 @@ 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.
> +/* Fills in tpi, including the header length to be pulled in tpi->hdr_len,
> + * and returns SKB_NOT_DROPPED_YET, or the reason to drop the packet if the
> + * header is rejected.
> * Note that caller must use pskb_may_pull() before pulling GRE header.
> + *
> + * @icmp_err is set by the ICMP error handlers, which only get a part of
> + * the original packet: a checksum failure does not reject the header then,
> + * the checksum is still computed and the rest of the header is parsed.
> */
> -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 icmp_err, __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))))
> - return -EINVAL;
> + return SKB_DROP_REASON_HDR_TRUNC;
Please use pskb_may_pull_reason() as is done throughout the kernel.
AFAICT, there's only one user of HDR_TRUNC (in tun) and it should also
be converted to pskb_may_pull_reason() so that we could remove
HDR_TRUNC. Can you send a patch?
>
> greh = (struct gre_base_hdr *)(skb->data + nhs);
> if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
> - return -EINVAL;
> + return SKB_DROP_REASON_GRE_INVALID_HDR;
[...]
> 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;
This should be TUNNEL_INVALID_HDR to be consistent with the hunk above.
> goto drop;
> + }
>
> rcu_read_lock();
> proto = rcu_dereference(gre_proto[ver]);
> @@ -167,11 +177,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);
This looks correct.
> 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;
> }
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason()
2026-09-22 22:15 ` [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason() Anton Danilov
@ 2026-09-23 15:41 ` Ido Schimmel
0 siblings, 0 replies; 15+ messages in thread
From: Ido Schimmel @ 2026-09-23 15:41 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Simon Horman, linux-kernel
On Wed, Sep 23, 2026 at 01:15:01AM +0300, Anton Danilov wrote:
> diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
> index 7102aa11fae2..c68031d01c39 100644
> --- a/include/net/ip_tunnels.h
> +++ b/include/net/ip_tunnels.h
> @@ -614,8 +614,17 @@ static inline u8 ip_tunnel_ecn_encap(u8 tos, const struct iphdr *iph,
> return INET_ECN_encapsulate(tos, inner);
> }
>
> -int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
> - __be16 inner_proto, bool raw_proto, bool xnet);
> +enum skb_drop_reason
> +__iptunnel_pull_header_reason(struct sk_buff *skb, int hdr_len,
> + __be16 inner_proto, bool raw_proto, bool xnet);
> +
> +static inline int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
> + __be16 inner_proto, bool raw_proto,
> + bool xnet)
> +{
> + return __iptunnel_pull_header_reason(skb, hdr_len, inner_proto,
> + raw_proto, xnet) ? -ENOMEM : 0;
> +}
Either append patches to this series or as a follow-up, after this
series the only caller of __iptunnel_pull_header() other than
iptunnel_pull_header() is the VXLAN driver and it should be converted to
report the actual drop reason instead of NOMEM:
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 347245cc1de4..5c429a70987e 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -1720,11 +1720,11 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
raw_proto = true;
}
- if (__iptunnel_pull_header(skb, VXLAN_HLEN, protocol, raw_proto,
- !net_eq(vxlan->net, dev_net(vxlan->dev)))) {
- reason = SKB_DROP_REASON_NOMEM;
+ reason = __iptunnel_pull_header_reason(skb, VXLAN_HLEN, protocol,
+ raw_proto,
+ !net_eq(vxlan->net, dev_net(vxlan->dev)));
+ if (reason)
goto drop;
- }
if (vxlan->cfg.flags & VXLAN_F_REMCSUM_RX) {
reason = vxlan_remcsum(skb, vxlan->cfg.flags);
Then remove __iptunnel_pull_header():
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 27a9e097996b..a94dadfa81f2 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -614,18 +614,11 @@ enum skb_drop_reason
__iptunnel_pull_header_reason(struct sk_buff *skb, int hdr_len,
__be16 inner_proto, bool raw_proto, bool xnet);
-static inline int __iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
- __be16 inner_proto, bool raw_proto,
- bool xnet)
+static inline bool iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
+ __be16 inner_proto, bool xnet)
{
- return __iptunnel_pull_header_reason(skb, hdr_len, inner_proto,
- raw_proto, xnet) ? -ENOMEM : 0;
-}
-
-static inline int iptunnel_pull_header(struct sk_buff *skb, int hdr_len,
- __be16 inner_proto, bool xnet)
-{
- return __iptunnel_pull_header(skb, hdr_len, inner_proto, false, xnet);
+ return __iptunnel_pull_header_reason(skb, hdr_len, inner_proto, false,
+ xnet) != SKB_NOT_DROPPED_YET;
}
void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path
2026-09-22 22:15 ` [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path Anton Danilov
@ 2026-09-23 15:53 ` Ido Schimmel
0 siblings, 0 replies; 15+ messages in thread
From: Ido Schimmel @ 2026-09-23 15:53 UTC (permalink / raw)
To: Anton Danilov
Cc: netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Simon Horman, linux-kernel
On Wed, Sep 23, 2026 at 01:15:04AM +0300, Anton Danilov wrote:
> @@ -711,9 +721,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 &&
> @@ -734,8 +750,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,8 +770,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
Makes more sense:
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 66cb0b86fa79..20b666fcca9a 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -725,11 +725,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
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 &&
@@ -776,8 +771,10 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
}
}
#endif
- else
+ else {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
goto tx_error;
+ }
if (!md)
connected = false;
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-23 15:53 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-22 22:14 [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 01/10] ip_tunnel: add drop reasons to the generic RX path Anton Danilov
2026-09-22 22:14 ` [PATCH net-next v4 02/10] ip6_tunnel: " Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 03/10] gre: make gre_parse_header() report a drop reason Anton Danilov
2026-09-23 14:59 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 04/10] ip_tunnel: add __iptunnel_pull_header_reason() Anton Danilov
2026-09-23 15:41 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 05/10] ip_gre: add drop reasons to the RX path Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 06/10] ip6_gre: " Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 07/10] ip_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-23 15:53 ` Ido Schimmel
2026-09-22 22:15 ` [PATCH net-next v4 08/10] ip_gre: " Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 09/10] ip6_gre: make prepare_ip6gre_xmit_other() void Anton Danilov
2026-09-22 22:15 ` [PATCH net-next v4 10/10] ip6_tunnel: add drop reasons to the transmit path Anton Danilov
2026-09-23 14:19 ` [PATCH net-next v4 00/10] tunnels: add core and gre drop reasons Ido Schimmel
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®