* [PATCH net v6 1/3] igb: do not let XDP frames span more than one Rx buffer
2026-11-11 6:51 [PATCH net v6 0/3] igb: only strip the Rx timestamp header from the first buffer of a frame Tjerk Kusters via B4 Relay
@ 2026-11-11 6:51 ` Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 2/3] igb: only strip Rx timestamp header on the first buffer of a frame Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 3/3] igb: do not split AF_XDP zero-copy frames over several Rx buffers Tjerk Kusters via B4 Relay
2 siblings, 0 replies; 5+ messages in thread
From: Tjerk Kusters via B4 Relay @ 2026-11-11 6:51 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Sven Auhagen, Richard Cochran,
Kurt Kanzenbach, Sriram Yagnaraman
Cc: intel-wired-lan, netdev, linux-kernel, bpf, Maciej Fijalkowski,
Paul Moses, stable, Tjerk Kusters
From: Tjerk Kusters <tkusters@aweta.nl>
igb does not support XDP multi-buffer: igb_xdp_setup() only accepts an
MTU whose frame fits in one Rx buffer, and the XDP program only sees
the first buffer of a frame. The maximum receive length is not limited
accordingly, though. RLPML is only lowered when the frame fits a
build_skb buffer on systems with PAGE_SIZE < 8192 and no VFs. Otherwise
the MAC still accepts frames of up to MAX_JUMBO_FRAME_SIZE while XDP is
attached, for example at MTU 3000 on 3 KiB buffers, and at any MTU when
PAGE_SIZE >= 8192. With VFs allocated, RLPML always stays at
MAX_JUMBO_FRAME_SIZE and the PF pool limit in VMOLR is only lowered for
frames that fit a build_skb buffer, so the same happens at MTU 3000.
Such frames span several descriptors. When the program does not return
XDP_PASS for the first buffer, no skb is built, and igb_clean_rx_irq()
handles the next buffer of the same frame as the start of a new frame:
it runs the program on it and may pass it to the stack as a bogus frame
made from the middle of the original payload.
Limit the receive length to the configured maximum frame size while XDP
is attached: in RLPML when no VFs are allocated, and in the PF pool's
VMOLR otherwise, as RLPML then has to stay large for the VFs. Larger
frames are dropped by the MAC.
Also make sure that the limit holds:
- Leave room for the inline Rx timestamp header when checking that the
frame fits in one Rx buffer, on XDP setup and on MTU change, where the
limit is the hardware buffer size. This lowers the largest MTU
accepted with XDP by 16 bytes: from 3046 to 3030 on 3 KiB buffers,
and from 2022 to 2006 with legacy-rx or PAGE_SIZE >= 8192. Setups that
use XDP at those MTUs will have to lower the MTU. The build_skb frame
limit used with 2 KiB buffers is kept, so XDP still attaches up to MTU
1510 on x86. The header is only prepended by 82580 and later MACs; the
room is reserved on all MACs for simplicity.
- Base that check on the buffer size that igb_set_rx_buffer_len() will
select, not on the ring flags, which are only updated when the rings
are configured and can be stale on an interface that is down. As a
result, an MTU change with XDP attached can now also move from 2 KiB
to 3 KiB buffers. The check is skipped when XDP is removed.
- Refuse XDP while rx-all is enabled, and keep rx-all off while XDP is
attached, as the MAC then also stores frames longer than RLPML. If
rx-all was requested meanwhile, it is turned on when XDP is removed.
- Refuse to change the legacy-rx private flag while XDP is attached, as
that changes the Rx buffer size without checking it against the MTU.
- Program both limits from igb_configure(), which runs when the
interface is (re)configured, before Rx is enabled, and no longer from
igb_set_rx_mode(), which can also run without RTNL, for example on an
IPv6 multicast join or a VF mailbox request. Otherwise such a call
during an XDP removal or an MTU change could apply the new limit while
the rings still use the old buffers.
Attaching or removing an XDP program on a running interface goes
through igb_close() and igb_open(), and an MTU change through
igb_down() and igb_up(), so igb_configure() runs in both cases.
igb_set_rx_mode() still updates other bits of the PF pool's VMOLR. The
core calls it under the address lock, so the limit is changed under
that lock. The VF mailbox path calls it from the interrupt handler
without the lock, but interrupts are masked while igb_configure()
runs.
This follows the same approach as the igc fix that limits RLPML while
XDP is attached. igb_set_rx_mode() does not touch VMOLR on i354, but
i354 can have VFs, and then the PF pool limit is the only one. So it is
also written on i354 when VFs are allocated, with or without XDP.
Tested on an I350 at MTU 3000 with Rx timestamping on and xdp-filter
dropping all IPv4 from the peer: 8242-byte frames are now dropped by the
MAC (rx_long_length_errors), instead of their last two buffers being
delivered as a separate frame. XDP attach at MTU 1500 and 3030 still
works, at MTU 3046 it is refused, as is an MTU change from 3030 to 3046
with XDP attached; an MTU change from 1500 to 3000 with XDP attached
now works. With XDP attached, rx-all stays off and the legacy-rx flag
cannot be changed. XDP attach is refused with rx-all on, and at MTU
3000 with legacy-rx on, also when the flag was set while the interface
was down. MTU changes between 1500 and 3000 with XDP attached, while
the peer sent 3000-byte zero-payload frames, let no frame through as a
separate frame.
With two VFs allocated, the same frames are counted by the MAC but
dropped by the PF pool limit in VMOLR before they reach the Rx queue;
without this patch their last two buffers are again delivered as a
separate frame. Read back from the registers, RLPML then stays at 9728
and the PF pool limit is 3026 at MTU 3000 and 1526 at MTU 1500 with XDP
attached. In repeated runs of about 200 MTU changes each, the limit was
right after every change, except for one read in one run, where the
value was not logged; eight reruns showed no mismatch.
The i354 path was checked with the driver built so that the I350 takes
the i354 branches for VMOLR. With two VFs and XDP attached, the PF pool
limit kept its old value of 9728 when the VMOLR write was skipped on
i354, and read back as 3026 at MTU 3000 and 1526 at MTU 1500 with this
patch. Only the registers were checked; I have no i354 to test on.
Link: https://lore.kernel.org/all/20261028083344.3047350-3-p@1g4.org/
Fixes: 9cbc948b5a20 ("igb: add XDP support")
Cc: stable@vger.kernel.org
Suggested-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Signed-off-by: Tjerk Kusters <tkusters@aweta.nl>
---
drivers/net/ethernet/intel/igb/igb_ethtool.c | 9 ++
drivers/net/ethernet/intel/igb/igb_main.c | 149 +++++++++++++++++++--------
2 files changed, 116 insertions(+), 42 deletions(-)
diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c
index f7938c1da835..4f2891fca282 100644
--- a/drivers/net/ethernet/intel/igb/igb_ethtool.c
+++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c
@@ -3438,6 +3438,15 @@ static int igb_set_priv_flags(struct net_device *netdev, u32 priv_flags)
flags |= IGB_FLAG_RX_LEGACY;
if (flags != adapter->flags) {
+ /* the Rx buffer size depends on this flag, see
+ * igb_xdp_setup()
+ */
+ if (igb_xdp_is_enabled(adapter)) {
+ netdev_warn(netdev,
+ "Cannot change legacy-rx while XDP is attached\n");
+ return -EOPNOTSUPP;
+ }
+
adapter->flags = flags;
/* reset interface to repopulate queues */
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index ce91dda00ec0..711a67f0bd80 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -1964,6 +1964,57 @@ static void igb_setup_tx_mode(struct igb_adapter *adapter)
"enabled" : "disabled");
}
+/**
+ * igb_set_rlpml - set the receive length limits
+ * @adapter: private board structure
+ *
+ * Called from igb_configure(), before Rx is enabled. igb_set_rx_mode() can
+ * also run without RTNL, so it does not change these limits.
+ **/
+static void igb_set_rlpml(struct igb_adapter *adapter)
+{
+ u32 rlpml = MAX_JUMBO_FRAME_SIZE, pool_rlpml = MAX_JUMBO_FRAME_SIZE;
+ unsigned int vfn = adapter->vfs_allocated_count;
+ struct e1000_hw *hw = &adapter->hw;
+ u32 vmolr;
+
+#if (PAGE_SIZE < 8192)
+ /* restrict as needed to support build_skb */
+ if (adapter->max_frame_size <= IGB_MAX_FRAME_BUILD_SKB) {
+ pool_rlpml = IGB_MAX_FRAME_BUILD_SKB;
+ if (!vfn)
+ rlpml = IGB_MAX_FRAME_BUILD_SKB;
+ }
+#endif
+ /* XDP does not support frames that span more than one Rx buffer.
+ * With VFs the global limit stays large and the PF pool is limited
+ * through VMOLR.
+ */
+ if (igb_xdp_is_enabled(adapter)) {
+ pool_rlpml = min(pool_rlpml, adapter->max_frame_size);
+ if (!vfn)
+ rlpml = min(rlpml, adapter->max_frame_size);
+ }
+ wr32(E1000_RLPML, rlpml);
+
+ /* the PF pool's VMOLR is used on these MACs, see igb_set_rx_mode(),
+ * and with VFs it carries the only limit for the PF, also on i354
+ */
+ if (hw->mac.type < e1000_82576 ||
+ (hw->mac.type > e1000_i350 && !vfn))
+ return;
+
+ /* igb_set_rx_mode() also updates this register, and the core calls
+ * it under the address lock
+ */
+ netif_addr_lock_bh(adapter->netdev);
+ vmolr = rd32(E1000_VMOLR(vfn));
+ vmolr &= ~E1000_VMOLR_RLPML_MASK;
+ vmolr |= pool_rlpml | E1000_VMOLR_LPE;
+ wr32(E1000_VMOLR(vfn), vmolr);
+ netif_addr_unlock_bh(adapter->netdev);
+}
+
/**
* igb_configure - configure the hardware for RX and TX
* @adapter: private board structure
@@ -1981,6 +2032,7 @@ static void igb_configure(struct igb_adapter *adapter)
igb_setup_tctl(adapter);
igb_setup_mrqc(adapter);
+ igb_set_rlpml(adapter);
igb_setup_rctl(adapter);
igb_nfc_filter_restore(adapter);
@@ -2468,6 +2520,14 @@ void igb_reset(struct igb_adapter *adapter)
static netdev_features_t igb_fix_features(struct net_device *netdev,
netdev_features_t features)
{
+ struct igb_adapter *adapter = netdev_priv(netdev);
+
+ /* with rx-all the MAC also stores frames longer than RLPML, which
+ * XDP cannot handle
+ */
+ if (igb_xdp_is_enabled(adapter))
+ features &= ~NETIF_F_RXALL;
+
/* Since there is no support for separate Rx/Tx vlan accel
* enable/disable make sure Tx flag is always in same state as Rx.
*/
@@ -2886,6 +2946,27 @@ static int igb_setup_tc(struct net_device *dev, enum tc_setup_type type,
}
}
+/* Check that a frame fits in one Rx buffer together with the inline Rx
+ * timestamp header, for the buffer size igb_set_rx_buffer_len() selects.
+ * The ring flags are only updated when the rings are configured, so they
+ * are not used here. For build_skb rings the frame is limited to
+ * IGB_MAX_FRAME_BUILD_SKB, which is below the 2 KiB hardware buffer size
+ * even with the header.
+ */
+static bool igb_xdp_frame_fits(struct igb_adapter *adapter, int max_frame)
+{
+#if (PAGE_SIZE < 8192)
+ if (!(adapter->flags & IGB_FLAG_RX_LEGACY)) {
+ if (max_frame <= IGB_MAX_FRAME_BUILD_SKB &&
+ !IGB_2K_TOO_SMALL_WITH_PADDING)
+ return true;
+
+ return max_frame + IGB_TS_HDR_LEN <= IGB_RXBUFFER_3072;
+ }
+#endif
+ return max_frame + IGB_TS_HDR_LEN <= IGB_RXBUFFER_2048;
+}
+
static int igb_xdp_setup(struct net_device *dev, struct netdev_bpf *bpf)
{
int i, frame_size = dev->mtu + IGB_ETH_PKT_HDR_PAD;
@@ -2894,17 +2975,21 @@ static int igb_xdp_setup(struct net_device *dev, struct netdev_bpf *bpf)
bool running = netif_running(dev);
bool need_reset;
- /* verify igb ring attributes are sufficient for XDP */
- for (i = 0; i < adapter->num_rx_queues; i++) {
- struct igb_ring *ring = adapter->rx_ring[i];
+ /* with rx-all the MAC also stores frames longer than RLPML */
+ if (prog && (dev->features & NETIF_F_RXALL)) {
+ NL_SET_ERR_MSG_MOD(bpf->extack, "XDP is not supported with rx-all");
+ return -EOPNOTSUPP;
+ }
- if (frame_size > igb_rx_bufsz(ring)) {
- NL_SET_ERR_MSG_MOD(bpf->extack,
- "The RX buffer size is too small for the frame size");
- netdev_warn(dev, "XDP RX buffer size %d is too small for the frame size %d\n",
- igb_rx_bufsz(ring), frame_size);
- return -EINVAL;
- }
+ /* verify igb ring attributes are sufficient for XDP, leaving room
+ * for the inline Rx timestamp header
+ */
+ if (prog && !igb_xdp_frame_fits(adapter, frame_size)) {
+ NL_SET_ERR_MSG_MOD(bpf->extack,
+ "The RX buffer size is too small for the frame size");
+ netdev_warn(dev, "XDP RX buffer size is too small for the frame size %d\n",
+ frame_size);
+ return -EINVAL;
}
old_prog = xchg(&adapter->xdp_prog, prog);
@@ -2935,6 +3020,10 @@ static int igb_xdp_setup(struct net_device *dev, struct netdev_bpf *bpf)
if (running)
igb_open(dev);
+ /* rx-all may have been requested while XDP was attached */
+ if (!prog && (dev->wanted_features & NETIF_F_RXALL))
+ netdev_update_features(dev);
+
return 0;
}
@@ -5323,7 +5412,7 @@ static void igb_set_rx_mode(struct net_device *netdev)
struct igb_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
unsigned int vfn = adapter->vfs_allocated_count;
- u32 rctl = 0, vmolr = 0, rlpml = MAX_JUMBO_FRAME_SIZE;
+ u32 rctl = 0, vmolr = 0;
int count;
/* Check for Promiscuous and All Multicast modes */
@@ -5380,14 +5469,6 @@ static void igb_set_rx_mode(struct net_device *netdev)
E1000_RCTL_VFE);
wr32(E1000_RCTL, rctl);
-#if (PAGE_SIZE < 8192)
- if (!adapter->vfs_allocated_count) {
- if (adapter->max_frame_size <= IGB_MAX_FRAME_BUILD_SKB)
- rlpml = IGB_MAX_FRAME_BUILD_SKB;
- }
-#endif
- wr32(E1000_RLPML, rlpml);
-
/* In order to support SR-IOV and eventually VMDq it is necessary to set
* the VMOLR to enable the appropriate modes. Without this workaround
* we will have issues with VLAN tag stripping not being done for frames
@@ -5399,19 +5480,10 @@ static void igb_set_rx_mode(struct net_device *netdev)
/* set UTA to appropriate mode */
igb_set_uta(adapter, !!(vmolr & E1000_VMOLR_ROPE));
+ /* keep the receive length limit set by igb_set_rlpml() */
vmolr |= rd32(E1000_VMOLR(vfn)) &
~(E1000_VMOLR_ROPE | E1000_VMOLR_MPME | E1000_VMOLR_ROMPE);
- /* enable Rx jumbo frames, restrict as needed to support build_skb */
- vmolr &= ~E1000_VMOLR_RLPML_MASK;
-#if (PAGE_SIZE < 8192)
- if (adapter->max_frame_size <= IGB_MAX_FRAME_BUILD_SKB)
- vmolr |= IGB_MAX_FRAME_BUILD_SKB;
- else
-#endif
- vmolr |= MAX_JUMBO_FRAME_SIZE;
- vmolr |= E1000_VMOLR_LPE;
-
wr32(E1000_VMOLR(vfn), vmolr);
igb_restore_vf_multicasts(adapter);
@@ -6707,19 +6779,12 @@ static int igb_change_mtu(struct net_device *netdev, int new_mtu)
struct igb_adapter *adapter = netdev_priv(netdev);
int max_frame = new_mtu + IGB_ETH_PKT_HDR_PAD;
- if (igb_xdp_is_enabled(adapter)) {
- int i;
-
- for (i = 0; i < adapter->num_rx_queues; i++) {
- struct igb_ring *ring = adapter->rx_ring[i];
-
- if (max_frame > igb_rx_bufsz(ring)) {
- netdev_warn(adapter->netdev,
- "Requested MTU size is not supported with XDP. Max frame size is %d\n",
- max_frame);
- return -EINVAL;
- }
- }
+ if (igb_xdp_is_enabled(adapter) &&
+ !igb_xdp_frame_fits(adapter, max_frame)) {
+ netdev_warn(adapter->netdev,
+ "Requested MTU size is not supported with XDP. Frame size is %d\n",
+ max_frame);
+ return -EINVAL;
}
/* adjust max frame to be at least the size of a standard frame */
--
2.27.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net v6 2/3] igb: only strip Rx timestamp header on the first buffer of a frame
2026-11-11 6:51 [PATCH net v6 0/3] igb: only strip the Rx timestamp header from the first buffer of a frame Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 1/3] igb: do not let XDP frames span more than one Rx buffer Tjerk Kusters via B4 Relay
@ 2026-11-11 6:51 ` Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 3/3] igb: do not split AF_XDP zero-copy frames over several Rx buffers Tjerk Kusters via B4 Relay
2 siblings, 0 replies; 5+ messages in thread
From: Tjerk Kusters via B4 Relay @ 2026-11-11 6:51 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Sven Auhagen, Richard Cochran,
Kurt Kanzenbach, Sriram Yagnaraman
Cc: intel-wired-lan, netdev, linux-kernel, bpf, Maciej Fijalkowski,
Paul Moses, stable, Piotr Kwapulinski, Aleksandr Loktionov,
Alexander Nowlin, Tjerk Kusters
From: Tjerk Kusters <tkusters@aweta.nl>
When Rx hardware timestamping is enabled (e.g. ptp4l, which configures
HWTSTAMP_FILTER_ALL), the NIC prepends a 16-byte timestamp header to the
first Rx buffer of every received frame. igb_clean_rx_irq() strips this
header inside its per-buffer loop:
if (igb_test_staterr(rx_desc, E1000_RXDADV_STAT_TSIP)) {
ts_hdr_len = igb_ptp_rx_pktstamp(rx_ring->q_vector,
pktbuf, ×tamp);
pkt_offset += ts_hdr_len;
size -= ts_hdr_len;
}
For a frame that spans more than one Rx buffer (e.g. a jumbo frame), this
block runs once per buffer. The timestamp header only exists at the start
of the first buffer, but igb_ptp_rx_pktstamp() is called for every buffer.
On a continuation buffer the data is packet payload, not a timestamp
header. igb_ptp_rx_pktstamp() already has two guards against acting on a
non-header buffer: it returns 0 if PTP is disabled, and returns 0 if the
reserved dwords (the first 8 bytes) are non-zero. Neither is sufficient
here: PTP is enabled, and a continuation buffer whose payload happens to
begin with 8 zero bytes passes the reserved-dword check. In that case the
payload is mistaken for a valid timestamp header and igb_ptp_rx_pktstamp()
returns IGB_TS_HDR_LEN, and the caller reduces that buffer's size by 16.
igb_add_rx_frag() adds a continuation buffer from its original offset, so
the buffer loses its last 16 bytes and the data of the next buffer
follows directly. A frame spanning N buffers loses 16 bytes per affected
continuation buffer, in the middle of the payload when N > 2. If the last
buffer of a frame holds 8 to 15 bytes, all zero, the unsigned size wraps
and a fragment of about 4 GiB is added to the skb. The sender chooses the
frame length and content, so this can be triggered remotely.
This is easily triggered by a GigE Vision camera streaming dark frames
(mostly 0x00 pixel data) over jumbo UDP with PTP active on the receiver:
the all-zero frames are dropped as truncated while frames with non-zero
content are fine. The driver reports no error; only the IP layer counts
the frames in IpExtInTruncatedPkts.
No content-based check can reliably tell a continuation buffer that begins
with zero bytes from a real timestamp header, because both are all zero.
Fix it structurally instead: only attempt the strip on the first buffer of
a frame, which is the only buffer that can contain a timestamp header. In
igb_clean_rx_irq() skb is NULL until the first buffer has been processed,
so guarding the strip with !skb restricts it to the first buffer
regardless of payload content.
This relies on the previous patch. Without it, an XDP program that does
not return XDP_PASS for the first buffer of a multi-buffer frame leaves
skb NULL for the rest of that frame. With the receive length limited
while XDP is attached, such frames no longer reach the XDP path.
igc has the same bug; Paul Moses' igc series, linked below, fixes it the
same way.
Link: https://lore.kernel.org/all/20261028083344.3047350-2-p@1g4.org/
Fixes: 5379260852b0 ("igb: Fix XDP with PTP enabled")
Cc: stable@vger.kernel.org
Reviewed-by: Piotr Kwapulinski <piotr.kwapulinski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tjerk Kusters <tkusters@aweta.nl>
---
drivers/net/ethernet/intel/igb/igb_main.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 711a67f0bd80..e787095d3cdb 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -9125,8 +9125,11 @@ static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
rx_buffer = igb_get_rx_buffer(rx_ring, size, &rx_buf_pgcnt);
pktbuf = page_address(rx_buffer->page) + rx_buffer->page_offset;
- /* pull rx packet timestamp if available and valid */
- if (igb_test_staterr(rx_desc, E1000_RXDADV_STAT_TSIP)) {
+ /* pull rx packet timestamp if available and valid; it is only
+ * present on the first buffer of a frame
+ */
+ if (!skb &&
+ igb_test_staterr(rx_desc, E1000_RXDADV_STAT_TSIP)) {
int ts_hdr_len;
ts_hdr_len = igb_ptp_rx_pktstamp(rx_ring->q_vector,
--
2.27.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net v6 3/3] igb: do not split AF_XDP zero-copy frames over several Rx buffers
2026-11-11 6:51 [PATCH net v6 0/3] igb: only strip the Rx timestamp header from the first buffer of a frame Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 1/3] igb: do not let XDP frames span more than one Rx buffer Tjerk Kusters via B4 Relay
2026-11-11 6:51 ` [PATCH net v6 2/3] igb: only strip Rx timestamp header on the first buffer of a frame Tjerk Kusters via B4 Relay
@ 2026-11-11 6:51 ` Tjerk Kusters via B4 Relay
2026-11-12 6:51 ` sashiko-bot
2 siblings, 1 reply; 5+ messages in thread
From: Tjerk Kusters via B4 Relay @ 2026-11-11 6:51 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Sven Auhagen, Richard Cochran,
Kurt Kanzenbach, Sriram Yagnaraman
Cc: intel-wired-lan, netdev, linux-kernel, bpf, Maciej Fijalkowski,
Paul Moses, stable, Tjerk Kusters
From: Tjerk Kusters <tkusters@aweta.nl>
SRRCTL.BSIZEPKT has a granularity of 1 KiB. For AF_XDP zero-copy rings,
igb_setup_srrctl() programs xsk_pool_get_rx_frame_size(), which is
rounded down. With 2 KiB UMEM chunks and the default headroom this is
1792 bytes, so the hardware buffer becomes 1 KiB and a full-size frame
spans two Rx buffers. igb_clean_rx_irq_zc() handles every descriptor as
a complete frame, so the AF_XDP socket gets such a frame as two
packets, the second one without headers. With Rx timestamping enabled,
the timestamp header is also stripped from the second buffer, and a
short final buffer makes the size wrap.
Program the same buffer size as for the other rings, 2 KiB or 3 KiB.
This depends on "igb: do not let XDP frames span more than one Rx
buffer": the receive length limit set there while XDP is attached keeps
the hardware from writing more than the frame plus the timestamp header
into the larger buffer. Without it, the hardware can write past the XSK
frame.
Refuse an XSK pool whose frame size cannot hold that, and an MTU change
that would no longer fit a bound pool, also while no XDP program is
attached. The current MTU is checked rather than the largest XDP MTU,
as igb allows XDP up to MTU 3030 and that would rule out 2 KiB chunks.
With VFs, the PF keeps a single Tx queue, and one Rx queue on i350 or
two on 82576. A pool bound to a queue above that would lose its Rx
queue, which the MTU check then no longer sees, or its Tx queue, which
breaks the socket. The core refuses ethtool -L in that case (with
-EINVAL). Likewise, refuse to enable VFs with -EBUSY while an XSK pool
is bound to a queue the PF would lose. On that error, igb_enable_sriov()
also restores the DMA coalescing flag it cleared. The MAC filter
entries it reserved for the VFs before the check stay reserved until
the next reset.
A queue can also be hidden without such a request, for example when the
driver falls back from MSI-X to MSI and keeps one queue. The MTU check
does not see a pool on that queue, so igb_xsk_pool() checks the frame
size again when the ring is set up, and does not use zero-copy if the
frame does not fit. The socket then receives through the copy path,
and its Tx wakeup fails with EINVAL, until the ring is set up again
with an MTU that fits. This path was not tested.
This is the igb counterpart of the igc XSK buffer change.
Tested on an I350 with xdpsock from bpf-examples in rxdrop zero-copy
mode on one queue, Rx timestamping on and 1514-byte frames from the
peer. With 2 KiB chunks, the receive descriptors went from about two
per frame to one per frame; 98-byte frames and 4 KiB chunks were one
per frame with and without this patch. At MTU 3000, 4 KiB chunks
receive 3014-byte frames in one buffer. A 2 KiB chunk pool at MTU 3000
was refused by the new check on the 6.14.9 test kernel; current kernels
already refuse it in xp_assign_dev(). There, with 2 KiB chunks and the
default headroom, the new check refuses MTU 1751 to 1766, which the core
accepts.
On 7.3.1, with xdpsock bound to queue 1 of an I350, enabling two VFs is
now refused with EBUSY and the socket keeps its queue; with the socket on
queue 0, which the PF keeps, the VFs are enabled. The 82576 case, with
two Rx queues and one Tx queue for the PF, was checked with the driver
built so that the I350 keeps two Rx queues with VFs: with the socket on
queue 1, the VF enable is refused as well. Only the refusal was checked.
Link: https://lore.kernel.org/all/20261028083344.3047350-3-p@1g4.org/
Fixes: 2c6196013f84 ("igb: Add AF_XDP zero-copy Rx support")
Cc: stable@vger.kernel.org
Suggested-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Signed-off-by: Tjerk Kusters <tkusters@aweta.nl>
---
drivers/net/ethernet/intel/igb/igb.h | 9 ++++++
drivers/net/ethernet/intel/igb/igb_main.c | 51 +++++++++++++++++++++++++++++--
drivers/net/ethernet/intel/igb/igb_xsk.c | 20 +++++++++++-
3 files changed, 76 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h
index 0fff1df81b7b..0495c02544d6 100644
--- a/drivers/net/ethernet/intel/igb/igb.h
+++ b/drivers/net/ethernet/intel/igb/igb.h
@@ -856,6 +856,15 @@ int igb_add_mac_steering_filter(struct igb_adapter *adapter,
int igb_del_mac_steering_filter(struct igb_adapter *adapter,
const u8 *addr, u8 queue, u8 flags);
+/* XDP does not support frames that span more than one Rx buffer, so a
+ * frame plus the inline Rx timestamp header must fit in one XSK buffer.
+ */
+static inline bool igb_xsk_frame_fits(struct xsk_buff_pool *pool,
+ int max_frame)
+{
+ return max_frame + IGB_TS_HDR_LEN <= xsk_pool_get_rx_frame_size(pool);
+}
+
struct xsk_buff_pool *igb_xsk_pool(struct igb_adapter *adapter,
struct igb_ring *ring);
int igb_xsk_pool_setup(struct igb_adapter *adapter,
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index e787095d3cdb..c9351a8c4f5e 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -3777,9 +3777,29 @@ static int igb_sriov_reinit(struct pci_dev *dev)
struct net_device *netdev = pci_get_drvdata(dev);
struct igb_adapter *adapter = netdev_priv(netdev);
struct pci_dev *pdev = adapter->pdev;
+ unsigned int i, rxq, txq, end;
rtnl_lock();
+ /* With VFs the PF gets fewer Rx queues and a single Tx queue, see
+ * igb_set_interrupt_capability(). Refuse that while an XSK pool is
+ * bound to a queue the PF would lose; the core refuses ethtool -L in
+ * the same case.
+ */
+ rxq = min_t(unsigned int, igb_get_max_rss_queues(adapter),
+ num_online_cpus());
+ txq = adapter->vfs_allocated_count ? 1 : rxq;
+ end = max(netdev->real_num_rx_queues, netdev->real_num_tx_queues);
+ for (i = min(rxq, txq); i < end; i++) {
+ if (xsk_get_pool_from_qid(netdev, i)) {
+ rtnl_unlock();
+ dev_err(&pdev->dev,
+ "Cannot change the number of queues, an AF_XDP socket is bound to queue %u\n",
+ i);
+ return -EBUSY;
+ }
+ }
+
if (netif_running(netdev))
igb_close(netdev);
else
@@ -3844,6 +3864,7 @@ static int igb_enable_sriov(struct pci_dev *pdev, int num_vfs, bool reinit)
{
struct net_device *netdev = pci_get_drvdata(pdev);
struct igb_adapter *adapter = netdev_priv(netdev);
+ bool dmac = adapter->flags & IGB_FLAG_DMAC;
int old_vfs = pci_num_vf(pdev);
struct vf_mac_filter *mac_list;
int err = 0;
@@ -3933,6 +3954,8 @@ static int igb_enable_sriov(struct pci_dev *pdev, int num_vfs, bool reinit)
kfree(adapter->vf_data);
adapter->vf_data = NULL;
adapter->vfs_allocated_count = 0;
+ if (dmac)
+ adapter->flags |= IGB_FLAG_DMAC;
out:
return err;
}
@@ -4836,9 +4859,13 @@ void igb_setup_srrctl(struct igb_adapter *adapter, struct igb_ring *ring)
u32 srrctl = 0;
u32 buf_size;
- if (ring->xsk_pool)
- buf_size = xsk_pool_get_rx_frame_size(ring->xsk_pool);
- else if (ring_uses_large_buffer(ring))
+ /* SRRCTL.BSIZEPKT has a 1 KiB granularity, so programming the XSK
+ * frame size would round it down and split frames over several
+ * buffers. XSK rings use the same buffer size as the other rings.
+ * While XDP is attached, the receive length limit keeps frames
+ * within the XSK frame, see igb_xsk_frame_fits().
+ */
+ if (ring_uses_large_buffer(ring))
buf_size = IGB_RXBUFFER_3072;
else
buf_size = IGB_RXBUFFER_2048;
@@ -6778,6 +6805,7 @@ static int igb_change_mtu(struct net_device *netdev, int new_mtu)
{
struct igb_adapter *adapter = netdev_priv(netdev);
int max_frame = new_mtu + IGB_ETH_PKT_HDR_PAD;
+ int i;
if (igb_xdp_is_enabled(adapter) &&
!igb_xdp_frame_fits(adapter, max_frame)) {
@@ -6791,6 +6819,23 @@ static int igb_change_mtu(struct net_device *netdev, int new_mtu)
if (max_frame < (ETH_FRAME_LEN + ETH_FCS_LEN))
max_frame = ETH_FRAME_LEN + ETH_FCS_LEN;
+ /* the frame must fit the buffers of a bound XSK pool, also while no
+ * XDP program is attached, as attaching one does not check it again
+ */
+ for (i = 0; i < adapter->num_rx_queues; i++) {
+ struct xsk_buff_pool *pool = xsk_get_pool_from_qid(netdev, i);
+
+ if (!pool || !pool->dev)
+ continue;
+
+ if (!igb_xsk_frame_fits(pool, max_frame)) {
+ netdev_warn(adapter->netdev,
+ "Requested MTU size is not supported with AF_XDP. Frame size is %d\n",
+ max_frame);
+ return -EINVAL;
+ }
+ }
+
while (test_and_set_bit(__IGB_RESETTING, &adapter->state))
usleep_range(1000, 2000);
diff --git a/drivers/net/ethernet/intel/igb/igb_xsk.c b/drivers/net/ethernet/intel/igb/igb_xsk.c
index ce4a7b58cad2..7f99e834c1ed 100644
--- a/drivers/net/ethernet/intel/igb/igb_xsk.c
+++ b/drivers/net/ethernet/intel/igb/igb_xsk.c
@@ -91,7 +91,21 @@ struct xsk_buff_pool *igb_xsk_pool(struct igb_adapter *adapter,
if (!igb_xdp_is_enabled(adapter))
return NULL;
- return (pool && pool->dev) ? pool : NULL;
+ if (!pool || !pool->dev)
+ return NULL;
+
+ /* igb_change_mtu() does not see a pool on a queue that is hidden, for
+ * example after a fallback from MSI-X to MSI. The hardware buffer can
+ * be larger than the XSK frame, so check again when the ring is set up.
+ */
+ if (!igb_xsk_frame_fits(pool, adapter->max_frame_size)) {
+ netdev_warn_once(adapter->netdev,
+ "XSK frame size is too small for the MTU, queue %d is not used in zero-copy mode\n",
+ qid);
+ return NULL;
+ }
+
+ return pool;
}
static int igb_xsk_pool_enable(struct igb_adapter *adapter,
@@ -110,6 +124,10 @@ static int igb_xsk_pool_enable(struct igb_adapter *adapter,
qid >= netdev->real_num_tx_queues)
return -EINVAL;
+ /* checked again on MTU changes and when the ring is set up */
+ if (!igb_xsk_frame_fits(pool, adapter->max_frame_size))
+ return -EOPNOTSUPP;
+
err = xsk_pool_dma_map(pool, &adapter->pdev->dev, IGB_RX_DMA_ATTR);
if (err)
return err;
--
2.27.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH net v6 3/3] igb: do not split AF_XDP zero-copy frames over several Rx buffers
2026-11-11 6:51 ` [PATCH net v6 3/3] igb: do not split AF_XDP zero-copy frames over several Rx buffers Tjerk Kusters via B4 Relay
@ 2026-11-12 6:51 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-12 6:51 UTC (permalink / raw)
To: Tjerk Kusters; +Cc: intel-wired-lan, bpf
Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [Medium] igb: PF MAC filter clobber in igb_enable_sriov() before XSK
queue check
- [Medium] igb: skipped PF reinit in igb_disable_sriov() when
igb_sriov_reinit() fails
- [Medium] igb: unlocked VF state and flag rollback in
igb_enable_sriov() on error
- [Medium] igb: stale real queue counts in __igb_open() break
igb_xsk_pool() check
--
commit 0d73c7e718f3a4a479e43541f8ed94977b90b3db
Author: Tjerk Kusters <tkusters@aweta.nl>
igb: do not split AF_XDP zero-copy frames over several Rx buffers
This commit programs standard 2 KiB or 3 KiB hardware Rx buffer sizes for
AF_XDP zero-copy rings in igb_setup_srrctl() so frames are not split across
multiple descriptors, and rejects XSK pool bindings, MTU changes, and SR-IOV
queue reductions when frames or bound queues would no longer fit.
Link: https://lore.kernel.org/all/20261028083344.3047350-3-p@1g4.org/
> diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
> index e787095d3cdbe..c9351a8c4f5e5 100644
> --- a/drivers/net/ethernet/intel/igb/igb_main.c
> +++ b/drivers/net/ethernet/intel/igb/igb_main.c
> @@ -3777,9 +3777,29 @@ static int igb_sriov_reinit(struct pci_dev *dev)
> struct net_device *netdev = pci_get_drvdata(dev);
> struct igb_adapter *adapter = netdev_priv(netdev);
> struct pci_dev *pdev = adapter->pdev;
> + unsigned int i, rxq, txq, end;
>
> rtnl_lock();
>
> + /* With VFs the PF gets fewer Rx queues and a single Tx queue, see
> + * igb_set_interrupt_capability(). Refuse that while an XSK pool is
> + * bound to a queue the PF would lose; the core refuses ethtool -L in
> + * the same case.
> + */
> + rxq = min_t(unsigned int, igb_get_max_rss_queues(adapter),
> + num_online_cpus());
> + txq = adapter->vfs_allocated_count ? 1 : rxq;
> + end = max(netdev->real_num_rx_queues, netdev->real_num_tx_queues);
> + for (i = min(rxq, txq); i < end; i++) {
> + if (xsk_get_pool_from_qid(netdev, i)) {
> + rtnl_unlock();
> + dev_err(&pdev->dev,
> + "Cannot change the number of queues, an AF_XDP socket is bound to queue %u\n",
> + i);
> + return -EBUSY;
> + }
> + }
> +
> if (netif_running(netdev))
> igb_close(netdev);
> else
[Severity: Medium]
When igb_enable_sriov() fails here with -EBUSY, does returning before
igb_close() or igb_reset() wipe active PF unicast MAC filters in the upper
RAR slots?
Before calling igb_sriov_reinit(), igb_enable_sriov() calls
igb_vf_configure() -> igb_set_vf_mac() with a zero MAC address for each VF,
which overwrites the top adapter->vfs_allocated_count entries of
adapter->mac_table, marks them IGB_MAC_STATE_IN_USE, and clears E1000_RAH_AV
in the hardware RAR registers:
drivers/net/ethernet/intel/igb/igb_main.c:igb_set_vf_mac() {
...
int rar_entry = hw->mac.rar_entry_count - (vf + 1);
unsigned char *vf_mac_addr = adapter->vf_data[vf].vf_mac_addresses;
ether_addr_copy(vf_mac_addr, mac_addr);
ether_addr_copy(adapter->mac_table[rar_entry].addr, mac_addr);
adapter->mac_table[rar_entry].queue = vf;
adapter->mac_table[rar_entry].state |= IGB_MAC_STATE_IN_USE;
igb_rar_set_index(adapter, rar_entry);
...
}
Previously igb_sriov_reinit() always ran igb_close() or igb_reset(), which
flushed the MAC table and unsynced netdev->uc via __dev_uc_unsync() before
re-syncing PF filters. Returning early leaves netdev->uc marked synced while
any PF unicast filters (such as macvlan or bridge FDB entries) that occupied
those upper RAR slots are disabled in hardware and leaked as in-use until the
next reset.
[Severity: Medium]
Can this check in igb_sriov_reinit() also return -EBUSY when called from
igb_disable_sriov(), after SR-IOV has already been torn down?
igb_disable_sriov() calls pci_disable_sriov(), frees adapter->vf_data, resets
adapter->vfs_allocated_count to 0, and writes E1000_IOVCTL_REUSE_VFQ before
calling igb_sriov_reinit(). If min(rxq, txq) < end (for example, if CPUs
were offlined so num_online_cpus() is smaller than real_num_rx_queues, or if
VFs were enabled while the interface was down) and an XSK pool is bound to a
queue in that range, igb_sriov_reinit() aborts and leaves the PF running
without reinitializing its queue configuration and interrupt scheme for
non-SR-IOV operation.
> @@ -3844,6 +3864,7 @@ static int igb_enable_sriov(struct pci_dev *pdev, int num_vfs, bool reinit)
> {
> struct net_device *netdev = pci_get_drvdata(pdev);
> struct igb_adapter *adapter = netdev_priv(netdev);
> + bool dmac = adapter->flags & IGB_FLAG_DMAC;
> int old_vfs = pci_num_vf(pdev);
> struct vf_mac_filter *mac_list;
> int err = 0;
> @@ -3933,6 +3954,8 @@ static int igb_enable_sriov(struct pci_dev *pdev, int num_vfs, bool reinit)
> kfree(adapter->vf_data);
> adapter->vf_data = NULL;
> adapter->vfs_allocated_count = 0;
> + if (dmac)
> + adapter->flags |= IGB_FLAG_DMAC;
> out:
> return err;
> }
[Severity: Medium]
When igb_sriov_reinit() returns -EBUSY on a live PF, can the unlocked
rollback in err_out race with concurrent readers of adapter->vf_data and
adapter->flags?
CPU0 CPU1
igb_enable_sriov()
adapter->vfs_allocated_count = num_vfs;
adapter->vf_data = kzalloc_objs(...);
...
igb_sriov_reinit()
rtnl_unlock()
return -EBUSY;
igb_set_rx_mode()
igb_restore_vf_multicasts()
i < adapter->vfs_allocated_count
kfree(adapter->vf_data);
adapter->vf_data = NULL;
&adapter->vf_data[i]
<-- NULL pointer dereference / UAF
adapter->vfs_allocated_count = 0;
igb_enable_sriov() runs without rtnl_lock() or adapter->vfs_lock, and frees
adapter->vf_data before clearing adapter->vfs_allocated_count (unlike
igb_disable_sriov(), which takes adapter->vfs_lock). In addition, saving and
restoring IGB_FLAG_DMAC in adapter->flags performs unlocked read-modify-write
updates that can race with RTNL-protected changes to adapter->flags. Checking
for bound XSK queues before mutating adapter state in igb_enable_sriov()
would avoid having to roll back live VF state and RAR entries on error.
[ ... ]
> @@ -6778,6 +6805,7 @@ static int igb_change_mtu(struct net_device *netdev, int new_mtu)
> {
> struct igb_adapter *adapter = netdev_priv(netdev);
> int max_frame = new_mtu + IGB_ETH_PKT_HDR_PAD;
> + int i;
>
> if (igb_xdp_is_enabled(adapter) &&
> !igb_xdp_frame_fits(adapter, max_frame)) {
> @@ -6791,6 +6819,23 @@ static int igb_change_mtu(struct net_device *netdev, int new_mtu)
> if (max_frame < (ETH_FRAME_LEN + ETH_FCS_LEN))
> max_frame = ETH_FRAME_LEN + ETH_FCS_LEN;
>
> + /* the frame must fit the buffers of a bound XSK pool, also while no
> + * XDP program is attached, as attaching one does not check it again
> + */
> + for (i = 0; i < adapter->num_rx_queues; i++) {
> + struct xsk_buff_pool *pool = xsk_get_pool_from_qid(netdev, i);
> +
> + if (!pool || !pool->dev)
> + continue;
> +
> + if (!igb_xsk_frame_fits(pool, max_frame)) {
> + netdev_warn(adapter->netdev,
> + "Requested MTU size is not supported with AF_XDP. Frame size is %d\n",
> + max_frame);
> + return -EINVAL;
> + }
> + }
> +
> while (test_and_set_bit(__IGB_RESETTING, &adapter->state))
> usleep_range(1000, 2000);
>
> diff --git a/drivers/net/ethernet/intel/igb/igb_xsk.c b/drivers/net/ethernet/intel/igb/igb_xsk.c
> index ce4a7b58cad2f..7f99e834c1edb 100644
> --- a/drivers/net/ethernet/intel/igb/igb_xsk.c
> +++ b/drivers/net/ethernet/intel/igb/igb_xsk.c
> @@ -91,7 +91,21 @@ struct xsk_buff_pool *igb_xsk_pool(struct igb_adapter *adapter,
> if (!igb_xdp_is_enabled(adapter))
> return NULL;
>
> - return (pool && pool->dev) ? pool : NULL;
> + if (!pool || !pool->dev)
> + return NULL;
> +
> + /* igb_change_mtu() does not see a pool on a queue that is hidden, for
> + * example after a fallback from MSI-X to MSI. The hardware buffer can
> + * be larger than the XSK frame, so check again when the ring is set up.
> + */
> + if (!igb_xsk_frame_fits(pool, adapter->max_frame_size)) {
> + netdev_warn_once(adapter->netdev,
> + "XSK frame size is too small for the MTU, queue %d is not used in zero-copy mode\n",
> + qid);
> + return NULL;
> + }
> +
> + return pool;
> }
[Severity: Medium]
When a hidden queue is set up again during __igb_open(), does
xsk_get_pool_from_qid() in igb_xsk_pool() return NULL before reaching
igb_xsk_frame_fits()?
In __igb_open(), igb_configure() sets up the Tx and Rx rings (calling
igb_xsk_pool()) before netif_set_real_num_tx_queues() and
netif_set_real_num_rx_queues() update the netdev queue counts:
drivers/net/ethernet/intel/igb/igb_main.c:__igb_open() {
...
igb_configure(adapter);
err = igb_request_irq(adapter);
if (err)
goto err_req_irq;
/* Notify the stack of the actual queue counts. */
err = netif_set_real_num_tx_queues(adapter->netdev,
adapter->num_tx_queues);
if (err)
goto err_set_queues;
err = netif_set_real_num_rx_queues(adapter->netdev,
adapter->num_rx_queues);
...
}
Because xsk_get_pool_from_qid() checks queue_id against
netdev->real_num_rx_queues and netdev->real_num_tx_queues, it still sees the
reduced queue count (such as 1 after an MSI fallback) during igb_configure()
and returns NULL for any restored queue >= 1. As a result, the restored
queue stays out of zero-copy mode even when the MTU fits, and for the same
reason igb_change_mtu() can miss bound pools if queue counts were changed
while the interface was down and real_num_rx_queues has not been updated yet.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-igb-rx-ts-fix-v6-0-ba5b9903859c@aweta.nl?part=3
^ permalink raw reply [flat|nested] 5+ messages in thread