[PATCH net-next v4 08/10] ip_gre: add drop reasons to the transmit path

From: Anton Danilov

Date: Tue Sep 22 2026 - 18:17:56 EST


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@xxxxxxxxx>
---
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