tools/testing/selftests/drivers/net/xdp.py | 53 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-)
In "bpf: Disallow freplace on XDP with mismatched xdp_has_frags values" [1],
these two XDP tests are suggested to add to xdp.py.
1. Verify the failure of attaching non-frag-capable prog to mtu=9k driver.
2. Verify the failure of updating frag-capable prog with non-frag-capable
prog, when the frag-capable prog attaches to mtu=9k driver.
They have been verified against Mellanox CX6 and Intel 82599ES NICs.
With dropping other tests, here are the test logs.
# ethtool -i eth0
driver: mlx5_core
version: 6.19.0-061900-generic
# NETIF=eth0 python3 xdp.py
TAP version 13
1..2
ok 1 xdp.test_xdp_native_attach_sb_to_mb
ok 2 xdp.test_xdp_native_update_mb_to_sb
# Totals: pass:2 fail:0 xfail:0 xpass:0 skip:0 error:0
# ethtool -i eth0
driver: ixgbe
version: 6.19.0-061900-generic
# NETIF=eth0 python3 xdp.py
TAP version 13
1..2
ok 1 xdp.test_xdp_native_attach_sb_to_mb # SKIP RTNETLINK answers: Invalid argument
ok 2 xdp.test_xdp_native_update_mb_to_sb # SKIP RTNETLINK answers: Invalid argument
# Totals: pass:0 fail:0 xfail:0 xpass:0 skip:2 error:0
Links:
[1] https://lore.kernel.org/bpf/20260326124205.1a3bb825@kernel.org/
Signed-off-by: Leon Hwang <leon.huangfu@shopee.com>
---
tools/testing/selftests/drivers/net/xdp.py | 53 +++++++++++++++++++++-
1 file changed, 52 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py
index d86446569f89..fe34b491778b 100755
--- a/tools/testing/selftests/drivers/net/xdp.py
+++ b/tools/testing/selftests/drivers/net/xdp.py
@@ -13,7 +13,7 @@ from enum import Enum
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr
from lib.py import KsftNamedVariant, ksft_variants
-from lib.py import KsftFailEx, NetDrvEpEnv
+from lib.py import KsftFailEx, KsftSkipEx, NetDrvEpEnv
from lib.py import EthtoolFamily, NetdevFamily, NlError
from lib.py import bkg, cmd, rand_port, wait_port_listen
from lib.py import ip, defer
@@ -693,6 +693,55 @@ def test_xdp_native_qstats(cfg, act):
ksft_ge(after['tx-packets'], before['tx-packets'])
+def _set_jumbo_mtu(cfg, mtu):
+ ip(f"link set dev {cfg.ifname} mtu {mtu}")
+ defer(ip, f"link set dev {cfg.ifname} mtu 1500")
+
+
+def _exec_cmd(cfg, obj, sec, ip_opts=""):
+ return cmd(f"ip {ip_opts} link set dev {cfg.ifname} xdpdrv obj {obj} sec {sec}", shell=True, fail=False)
+
+
+def test_xdp_native_attach_sb_to_mb(cfg):
+ obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
+ mtu = 9000
+
+ _set_jumbo_mtu(cfg, mtu)
+
+ probe = _exec_cmd(cfg, obj, "xdp.frags")
+ if probe.ret != 0:
+ output = probe.stderr.strip() or probe.stdout.strip()
+ raise KsftSkipEx(output or "device does not support multi-buffer XDP")
+
+ ip(f"link set dev {cfg.ifname} xdpdrv off")
+
+ probe = _exec_cmd(cfg, obj, "xdp")
+ if probe.ret == 0:
+ ip(f"link set dev {cfg.ifname} xdpdrv off")
+ raise KsftFailEx(f"driver unexpectedly allows non-multi-buffer XDP at MTU {mtu}")
+
+
+def test_xdp_native_update_mb_to_sb(cfg):
+ obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
+
+ _set_jumbo_mtu(cfg, 9000)
+
+ attach = _exec_cmd(cfg, obj, "xdp.frags")
+ if attach.ret != 0:
+ output = attach.stderr.strip() or attach.stdout.strip()
+ raise KsftSkipEx(output or "device does not support multi-buffer XDP")
+
+ defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
+
+ update1 = _exec_cmd(cfg, obj, "xdp.frags", "-force")
+ if update1.ret != 0:
+ raise KsftFailEx("device fails to update multi-buffer XDP")
+
+ update2 = _exec_cmd(cfg, obj, "xdp", "-force")
+ if update2.ret == 0:
+ raise KsftFailEx("device unexpectedly updates non-multi-buffer XDP")
+
+
def main():
"""
Main function to execute the XDP tests.
@@ -718,6 +767,8 @@ def main():
test_xdp_native_adjst_head_grow_data,
test_xdp_native_adjst_head_shrnk_data,
test_xdp_native_qstats,
+ test_xdp_native_attach_sb_to_mb,
+ test_xdp_native_update_mb_to_sb,
],
args=(cfg,))
ksft_exit()
--
2.53.0
On Wed, 1 Apr 2026 13:27:46 +0800 Leon Hwang wrote:
> In "bpf: Disallow freplace on XDP with mismatched xdp_has_frags values" [1],
> these two XDP tests are suggested to add to xdp.py.
>
> 1. Verify the failure of attaching non-frag-capable prog to mtu=9k driver.
> 2. Verify the failure of updating frag-capable prog with non-frag-capable
> prog, when the frag-capable prog attaches to mtu=9k driver.
>
> They have been verified against Mellanox CX6 and Intel 82599ES NICs.
>
> With dropping other tests, here are the test logs.
>
> # ethtool -i eth0
> driver: mlx5_core
> version: 6.19.0-061900-generic
>
> # NETIF=eth0 python3 xdp.py
> TAP version 13
> 1..2
> ok 1 xdp.test_xdp_native_attach_sb_to_mb
> ok 2 xdp.test_xdp_native_update_mb_to_sb
> # Totals: pass:2 fail:0 xfail:0 xpass:0 skip:0 error:0
>
> # ethtool -i eth0
> driver: ixgbe
> version: 6.19.0-061900-generic
>
> # NETIF=eth0 python3 xdp.py
> TAP version 13
> 1..2
> ok 1 xdp.test_xdp_native_attach_sb_to_mb # SKIP RTNETLINK answers: Invalid argument
> ok 2 xdp.test_xdp_native_update_mb_to_sb # SKIP RTNETLINK answers: Invalid argument
> # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:2 error:0
>
> Links:
> [1] https://lore.kernel.org/bpf/20260326124205.1a3bb825@kernel.org/
Thanks for working on it!
> diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py
> index d86446569f89..fe34b491778b 100755
> --- a/tools/testing/selftests/drivers/net/xdp.py
> +++ b/tools/testing/selftests/drivers/net/xdp.py
> @@ -13,7 +13,7 @@ from enum import Enum
>
> from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr
> from lib.py import KsftNamedVariant, ksft_variants
> -from lib.py import KsftFailEx, NetDrvEpEnv
> +from lib.py import KsftFailEx, KsftSkipEx, NetDrvEpEnv
> from lib.py import EthtoolFamily, NetdevFamily, NlError
> from lib.py import bkg, cmd, rand_port, wait_port_listen
> from lib.py import ip, defer
> @@ -693,6 +693,55 @@ def test_xdp_native_qstats(cfg, act):
> ksft_ge(after['tx-packets'], before['tx-packets'])
>
>
> +def _set_jumbo_mtu(cfg, mtu):
> + ip(f"link set dev {cfg.ifname} mtu {mtu}")
> + defer(ip, f"link set dev {cfg.ifname} mtu 1500")
cfg records the original MTU, you can copy this line from here:
https://elixir.bootlin.com/linux/v7.0-rc6/source/tools/testing/selftests/drivers/net/gro.py#L88
> +def _exec_cmd(cfg, obj, sec, ip_opts=""):
> + return cmd(f"ip {ip_opts} link set dev {cfg.ifname} xdpdrv obj {obj} sec {sec}", shell=True, fail=False)
We shouldn't need the shell=True where?
Also it's probably cleaner to pass fail as optional argument here
so that you can let this throw an exception in the cases we expect
to succeed instead of having to check in the caller explicitly
> +def test_xdp_native_attach_sb_to_mb(cfg):
> + obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
> + mtu = 9000
> +
> + _set_jumbo_mtu(cfg, mtu)
> +
> + probe = _exec_cmd(cfg, obj, "xdp.frags")
> + if probe.ret != 0:
> + output = probe.stderr.strip() or probe.stdout.strip()
> + raise KsftSkipEx(output or "device does not support multi-buffer XDP")
May be simpler and cleaner to print the whole command separately:
if probe.ret != 0:
ksft_pr(probe)
raise KsftSkipEx("device does not support multi-buffer XDP")
> + ip(f"link set dev {cfg.ifname} xdpdrv off")
> +
> + probe = _exec_cmd(cfg, obj, "xdp")
> + if probe.ret == 0:
> + ip(f"link set dev {cfg.ifname} xdpdrv off")
> + raise KsftFailEx(f"driver unexpectedly allows non-multi-buffer XDP at MTU {mtu}")
Hm, TBH I don't think this sb_to_mb case is adding any coverage.
Let's just add the test case below?
> +def test_xdp_native_update_mb_to_sb(cfg):
> + obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
> +
> + _set_jumbo_mtu(cfg, 9000)
> +
> + attach = _exec_cmd(cfg, obj, "xdp.frags")
> + if attach.ret != 0:
> + output = attach.stderr.strip() or attach.stdout.strip()
> + raise KsftSkipEx(output or "device does not support multi-buffer XDP")
> +
> + defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
> +
> + update1 = _exec_cmd(cfg, obj, "xdp.frags", "-force")
> + if update1.ret != 0:
> + raise KsftFailEx("device fails to update multi-buffer XDP")
> +
> + update2 = _exec_cmd(cfg, obj, "xdp", "-force")
> + if update2.ret == 0:
> + raise KsftFailEx("device unexpectedly updates non-multi-buffer XDP")
--
pw-bot: cr
On 2/4/26 04:20, Jakub Kicinski wrote:
> On Wed, 1 Apr 2026 13:27:46 +0800 Leon Hwang wrote:
[...]
>> +def _set_jumbo_mtu(cfg, mtu):
>> + ip(f"link set dev {cfg.ifname} mtu {mtu}")
>> + defer(ip, f"link set dev {cfg.ifname} mtu 1500")
>
> cfg records the original MTU, you can copy this line from here:
> https://elixir.bootlin.com/linux/v7.0-rc6/source/tools/testing/selftests/drivers/net/gro.py#L88
>
Sure, will use cfg.dev['mtu'] instead of hard code 1500.
>> +def _exec_cmd(cfg, obj, sec, ip_opts=""):
>> + return cmd(f"ip {ip_opts} link set dev {cfg.ifname} xdpdrv obj {obj} sec {sec}", shell=True, fail=False)
>
> We shouldn't need the shell=True where?
> Also it's probably cleaner to pass fail as optional argument here
> so that you can let this throw an exception in the cases we expect
> to succeed instead of having to check in the caller explicitly
>
Right, shell=True is unnecessary here. Will drop it.
Will pass fail as optional arg to let it fail if fail=True.
>> +def test_xdp_native_attach_sb_to_mb(cfg):
>> + obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
>> + mtu = 9000
>> +
>> + _set_jumbo_mtu(cfg, mtu)
>> +
>> + probe = _exec_cmd(cfg, obj, "xdp.frags")
>> + if probe.ret != 0:
>> + output = probe.stderr.strip() or probe.stdout.strip()
>> + raise KsftSkipEx(output or "device does not support multi-buffer XDP")
>
> May be simpler and cleaner to print the whole command separately:
>
> if probe.ret != 0:
> ksft_pr(probe)
> raise KsftSkipEx("device does not support multi-buffer XDP")
>
Ack.
>> + ip(f"link set dev {cfg.ifname} xdpdrv off")
>> +
>> + probe = _exec_cmd(cfg, obj, "xdp")
>> + if probe.ret == 0:
>> + ip(f"link set dev {cfg.ifname} xdpdrv off")
>> + raise KsftFailEx(f"driver unexpectedly allows non-multi-buffer XDP at MTU {mtu}")
>
> Hm, TBH I don't think this sb_to_mb case is adding any coverage.
> Let's just add the test case below?
>
Ok. Will drop this case.
>
>> +def test_xdp_native_update_mb_to_sb(cfg):
>> + obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
>> +
>> + _set_jumbo_mtu(cfg, 9000)
>> +
>> + attach = _exec_cmd(cfg, obj, "xdp.frags")
>> + if attach.ret != 0:
>> + output = attach.stderr.strip() or attach.stdout.strip()
>> + raise KsftSkipEx(output or "device does not support multi-buffer XDP")
>> +
>> + defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
>> +
>> + update1 = _exec_cmd(cfg, obj, "xdp.frags", "-force")
>> + if update1.ret != 0:
>> + raise KsftFailEx("device fails to update multi-buffer XDP")
>> +
>> + update2 = _exec_cmd(cfg, obj, "xdp", "-force")
>> + if update2.ret == 0:
>> + raise KsftFailEx("device unexpectedly updates non-multi-buffer XDP")
From sashiko's review [1]:
On architectures with larger page sizes, such as 16KB or 64KB on ARM64 or
PowerPC, a 9000-byte packet can fit entirely within a single buffer. In
these environments, the driver might legally accept a non-frag XDP program,
which would lead to a false positive failure here.
WDYT?
[1]
https://sashiko.dev/#/patchset/20260401052746.314667-1-leon.huangfu%40shopee.com
Thanks,
Leon
On Thu, 2 Apr 2026 11:22:15 +0800 Leon Hwang wrote:
> >> +def test_xdp_native_update_mb_to_sb(cfg):
> >> + obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
> >> +
> >> + _set_jumbo_mtu(cfg, 9000)
> >> +
> >> + attach = _exec_cmd(cfg, obj, "xdp.frags")
> >> + if attach.ret != 0:
> >> + output = attach.stderr.strip() or attach.stdout.strip()
> >> + raise KsftSkipEx(output or "device does not support multi-buffer XDP")
> >> +
> >> + defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
> >> +
> >> + update1 = _exec_cmd(cfg, obj, "xdp.frags", "-force")
> >> + if update1.ret != 0:
> >> + raise KsftFailEx("device fails to update multi-buffer XDP")
> >> +
> >> + update2 = _exec_cmd(cfg, obj, "xdp", "-force")
> >> + if update2.ret == 0:
> >> + raise KsftFailEx("device unexpectedly updates non-multi-buffer XDP")
>
> From sashiko's review [1]:
>
> On architectures with larger page sizes, such as 16KB or 64KB on ARM64 or
> PowerPC, a 9000-byte packet can fit entirely within a single buffer. In
> these environments, the driver might legally accept a non-frag XDP program,
> which would lead to a false positive failure here.
Yes the test is indeed not checking all the corner cases, it should be:
# set mtu to 9k
# try attach xdp
# if ret == 0:
# return // must be 64k pages or something
# try attach xdp.frags
# if ret != 0:
# raise SKIP // driver doesn't support multi-buf
# defer xdp off
# try --froce switch to xdp
# if ret == 0:
# raise FAIL // replace is missing a check
© 2016 - 2026 Red Hat, Inc.