[RFC net-next 2/6] net: reserve extra TX headroom for local egress

From: David Keogh

Date: Thu Oct 15 2026 - 19:55:23 EST


Use LL_ALLOC_SPACE() instead of LL_RESERVED_SPACE() where the stack
allocates packets on behalf of a local sender and sizes them for the
output device:

- __ip_append_data() and __ip6_append_data(), covering UDP, ICMP,
ping and raw sockets without IP_HDRINCL;
- raw_send_hdrinc() and rawv6_send_hdrinc();
- the three AF_PACKET transmit paths;
- AF_XDP copy mode, which open-codes the same calculation from
needed_headroom.

These are the main paths where a packet's headroom is decided by the
device's advertised requirement and nothing else. Adding the
administrator's extra reservation here means such packets reach the TC
egress hook with room for additional headers already in place.

Paths that check an existing packet against LL_RESERVED_SPACE() and
expand it on demand, such as ip_finish_output2() and the tunnel
transmit functions, are deliberately left alone. Using the larger value
there would force a reallocation of every packet that lacks the full
extra reservation, including TCP segments that already have enough
room for the headers an egress program actually pushes.

Signed-off-by: David Keogh <dkeogh@xxxxxxxxxx>
---
net/ipv4/ip_output.c | 2 +-
net/ipv4/raw.c | 2 +-
net/ipv6/ip6_output.c | 2 +-
net/ipv6/raw.c | 2 +-
net/packet/af_packet.c | 6 +++---
net/xdp/xsk.c | 6 ++++--
6 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index 3396f177a..bf718b8f6 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -988,7 +988,7 @@ static int __ip_append_data(struct sock *sk,
mtu = cork->gso_size ? IP_MAX_MTU : cork->fragsize;
paged = !!cork->gso_size;

- hh_len = LL_RESERVED_SPACE(rt->dst.dev);
+ hh_len = LL_ALLOC_SPACE(rt->dst.dev);

fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 9d01006d8..a1e72e634 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -355,7 +355,7 @@ static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
if (flags&MSG_PROBE)
goto out;

- hlen = LL_RESERVED_SPACE(rt->dst.dev);
+ hlen = LL_ALLOC_SPACE(rt->dst.dev);
tlen = rt->dst.dev->needed_tailroom;
skb = sock_alloc_send_skb(sk,
length + hlen + tlen + 15,
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 25fe0ba98..a3626d5f0 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1490,7 +1490,7 @@ static int __ip6_append_data(struct sock *sk,
mtu = cork->gso_size ? IP6_MAX_MTU : cork->fragsize;
orig_mtu = mtu;

- hh_len = LL_RESERVED_SPACE(rt->dst.dev);
+ hh_len = LL_ALLOC_SPACE(rt->dst.dev);

fragheaderlen = sizeof(struct ipv6hdr) + rt->rt6i_nfheader_len +
(opt ? opt->opt_nflen : 0);
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index b965258cf..7f2da5581 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -601,7 +601,7 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
struct sk_buff *skb;
int err;
struct rt6_info *rt = dst_rt6_info(*dstp);
- int hlen = LL_RESERVED_SPACE(rt->dst.dev);
+ int hlen = LL_ALLOC_SPACE(rt->dst.dev);
int tlen = rt->dst.dev->needed_tailroom;

if (length > rt->dst.dev->mtu) {
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 823e0911c..8790fd038 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -2031,7 +2031,7 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg,
goto out_unlock;

if (!skb) {
- size_t reserved = LL_RESERVED_SPACE_EX(dev, hard_header_len);
+ size_t reserved = LL_ALLOC_SPACE_EX(dev, hard_header_len);
int tlen = dev->needed_tailroom;
unsigned int hhlen = dev->header_ops ? hard_header_len : 0;

@@ -2863,7 +2863,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
goto tpacket_error;

status = TP_STATUS_SEND_REQUEST;
- hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len);
+ hlen = LL_ALLOC_SPACE_EX(dev, hard_header_len);
tlen = dev->needed_tailroom;
if (vnet_hdr_sz) {
data += vnet_hdr_sz;
@@ -3090,7 +3090,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
goto out_unlock;

err = -ENOBUFS;
- hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len);
+ hlen = LL_ALLOC_SPACE_EX(dev, hard_header_len);
tlen = dev->needed_tailroom;
linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len);
linear = max(linear, min_t(int, len, hard_header_len));
diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
index 33475b180..58d64025d 100644
--- a/net/xdp/xsk.c
+++ b/net/xdp/xsk.c
@@ -1045,7 +1045,8 @@ static struct sk_buff *xsk_build_skb_zerocopy(struct xdp_sock *xs,
buffer = xsk_buff_raw_get_data(pool, addr);

if (!skb) {
- hr = max(NET_SKB_PAD, L1_CACHE_ALIGN(xs->dev->needed_headroom));
+ hr = max(NET_SKB_PAD,
+ L1_CACHE_ALIGN(netdev_tx_alloc_headroom(xs->dev)));

skb = sock_alloc_send_skb(&xs->sk, hr, 1, &err);
if (unlikely(!skb))
@@ -1128,7 +1129,8 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,
len = desc->len;

if (!skb) {
- hr = max(NET_SKB_PAD, L1_CACHE_ALIGN(dev->needed_headroom));
+ hr = max(NET_SKB_PAD,
+ L1_CACHE_ALIGN(netdev_tx_alloc_headroom(dev)));
tr = dev->needed_tailroom;
skb = sock_alloc_send_skb(&xs->sk, hr + len + tr, 1, &err);
if (unlikely(!skb))
--
2.50.1