* [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net
@ 2026-10-08 11:48 Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 01/14] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
` (14 more replies)
0 siblings, 15 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:48 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
v1: https://lore.kernel.org/netdev/20261001202124.692591-1-maciej.fijalkowski@intel.com/
v1 -> v2:
- Rebase on net-next; NIPA could not apply v1 (Stanislav)
- Patch 9: fail TX when RX exits with packets in flight instead of
continuing until the case timeout
- Patch 10: use the full packet length for IPv4/UDP headers in verbatim
multi-buffer streams
- New patch 11: in hardware mode, the XDP programs redirect only the
UDP/IPv4 test flow and pass all other traffic to the stack, so the
link under test no longer has to carry the test traffic alone
(Stanislav)
- Patch 13: besides an SSH remote, xsk.py supports the peer port of the
same host in a network namespace (REMOTE_TYPE=netns), which is how
NIPA sets up its E810 and X710 pairs. The control channel then runs
over the tested link to REMOTE_V4, and DUT TX cases also take their
queue out of RSS: the DUT TX socket fills no RX buffers, so its
zero-copy queue would drop the control traffic (Stanislav)
- Patches 13 and 14: drop XSK_STATIC and link xskxceiver dynamically,
like the other networking selftest helpers; a remote with an older
libc can run its own build with XSK_REMOTE_DEPLOY=0
- Patch 4: remove a partially written skeleton header when bpftool
fails, so a later build does not take it as up to date
- Patch 5: skip xskxceiver also when bpftool is in PATH but does not
run, not only when it is missing
- Patch 14: document both remote types and the traffic filter
Heads-up for ice: to reserve a queue, xsk.py updates only the RSS
indirection table over ethtool netlink. On ice this also turns on
symmetric Toeplitz hashing, and restoring the table afterwards fails.
The fix, "ice: keep the RSS hash function on indirection-only updates",
goes to iwl-net separately [0]. Without it, the first case that takes a
queue out of RSS fails in its cleanup, and the table stays modified.
Hi,
This work moves the AF_XDP test suite over to selftests/net and adds
a hardware test on top of the python-based drv-net infrastructure.
Since a non-zero effort went into implementing xskxceiver's (not so
great testing app name) test cases, we did not want to completely
abandon it and start everything from scratch within different infra.
However, hooking it up to the networking CI will allow us to run
cyclic tests on real HW; before that, all of our HW tests were manual
local runs.
Tests were based on a process with two threads responsible for the RX
and TX paths, whereas the new infra expects two separate processes for
the DUT and remote side, where each side has either rx or tx role.
To satisfy this requirement, this series makes each xskxceiver endpoint
a process of its own, so that the RX and TX sides of a case can run on
different hosts, and adds drivers/net/hw/xsk.py, which runs the existing
test cases with the DUT in zero-copy mode against an SKB-mode xskxceiver
on a remote host or on the peer port of the same host in a network
namespace. The veth test keeps its cases and moves with the
engine from selftests/bpf to selftests/net.
ZC tests used to expect a single NIC in loopback mode, with both
sockets on one of its queues sharing a UMEM. Now we step away from it:
the DUT and the remote are separate interfaces, on two hosts or on one
host with the remote in a namespace, and an ntuple rule steers
the test traffic to the AF_XDP queue.
The remote endpoint is xskxceiver in SKB mode rather than a plain
socket, so both ends keep sharing the packet stream generation and
validation of the existing cases. This reduces the need for remote
interface being a NIC from narrow set of NICs that are AF_XDP ZC
capable.
This implies that during the test run only one side is actually
exercised, so let's introduce the concept of direction per test case.
For example, this means SEND_RECEIVE in XSK_HW_RX will test
ice_clean_rx_irq_zc() routine and in XSK_HW_TX the ice_xmit_zc().
BPF's 'test_progs -t xsk' is removed, as well as single interface mode,
which was used for ZC tests. BPF CI therefore no longer runs the
xskxceiver cases. test_xsk.sh is kept, as it is the only run that needs
no hardware and not all tests are currently covered by the HW test side.
We can decide whether to keep the delta test cases, drop them or somehow
enable within HW tests.
Thread-based approach had a pacing mechanism that was a simple in-flight
packet counter updated within critical section by both ends.
Process-based way now is going to do this pacing via xsk_peer.
xsk_peer, the control channel, carries three fixed-size messages: READY
is the barrier between steps, PROGRESS tells TX how many packets RX has
consumed so that TX does not overrun the RX UMEM, and ABORT stops the
peer after a failure.
test_xsk_case_defs.h lists each case with the DUT directions.
xskxceiver builds its test table from that file, and xsk.py parses it to
make the variants ksft_variants() named rx_<case> and tx_<case>, so -l,
-t and -T work as for any other test.
Patches 1-3 prepare the split. Patch 1 moves the endpoint work out of
the pthread entry points. Patches 2 and 3 drop the single-interface
loopback mode and the test_progs wrapper, which runs both endpoints as
threads of test_progs; neither can work with one endpoint per process.
Nothing else runs a subset of the cases, so patch 3 also merges the
cases that the wrapper left out into the main list.
Patch 4 adds a generic rule for BPF skeletons to net/bpf.mk, as Jakub
suggested in the review of the xdp_features move [1]; xskxceiver is
its first user. If that series lands first with the same rule, this
patch can be dropped.
Patch 5 moves the engine, its XDP program and the veth launcher to
selftests/net. xsk.py needs xskxceiver, and a drv-net test can only
rely on net/lib: the selftests build pulls net/lib in for net,
drivers/net and drivers/net/hw, while it skips selftests/bpf by
default. The veth test is software-only, so it goes to selftests/net,
as the drv-net README asks. selftests/bpf keeps building xsk.c from
its new place for xdp_hw_metadata and the xdp_metadata test.
Patches 6-9 split the engine. The interface capabilities move into one
struct, so that a process can mirror them for the endpoint it does not
own (6). main() is split into setup, run and cleanup (7). xskxceiver
runs one case per invocation, and test_xsk.sh owns the mode x case
matrix (8). Finally, the RX and TX endpoints become separate processes
that meet over a small TCP control channel (9).
Patch 10 adds the xskxceiver options that a two-host run needs, and
patch 11 lets the XDP programs pass traffic other than the test flow to
the stack in hardware mode. Patch 12 moves the case list into
test_xsk_case_defs.h, so that xsk.py can read it as well, and patch 13
adds xsk.py on top of them. Patch 14 documents both setups.
Known issues:
- Every case pays for process start-up, XDP attach and detach and, on
hardware, its remote commands. We used to configure resources once
and then execute the whole test suite; it doesn't seem to be
CI-friendly and it is preferred to have each case's resource
management separated; that on the other hand increases the
execution time of the whole test suite.
- With a netns remote, the control channel shares the tested link, so
DUT TX cases also take their queue out of RSS; a one-channel DUT
skips them, as it skips the RX cases.
- The control channel is unauthenticated TCP, and the remote endpoint
listens on all addresses while its case runs.
- Both endpoints get only the case number, and the control channel
does not check that they run the same case. By default xsk.py
copies the DUT's xskxceiver to the remote; it is configurable via
XSK_REMOTE_DEPLOY at net.config.
- busy-poll testing happens to be done via -b passed to xsk.py, however
i am not sure if CI uses args, we might add a shell wrapper over
python script ;) or it could be embedded within the cases list
generation.
- Not on hardware yet:
* BIDIRECTIONAL, which needs both directions set up in one case; i
think it should be removed as we sort of achieve the bi-directional
coverage via introduced XSK_HW_{R,T}X, and it currently does not fit
our KISS-policy on remote side;
* XDP_SHARED_UMEM, whose XDP program picks the socket by the synthetic
MAC address of the veth test;
* STAT_RX_DROPPED, STAT_RX_FULL, STAT_FILL_EMPTY, XDP_DROP_HALF,
XDP_METADATA_COPY_MULTI_BUFF, the XDP_ADJUST_TAIL, TX_QUEUE_CONSUMER,
TEARDOWN, HW_SW_MIN_RING_SIZE and HW_SW_MAX_RING_SIZE.
[0]: https://lore.kernel.org/netdev/20261007200311.730443-1-maciej.fijalkowski@intel.com/T/#u
[1]: https://lore.kernel.org/netdev/20260925161547.1b57b653@kernel.org/
Thanks,
Maciej
Maciej Fijalkowski (14):
selftests: xsk: factor endpoint work out of pthread wrappers
selftests: xsk: drop the single-interface loopback mode
selftests/bpf: drop the test_progs AF_XDP wrapper
selftests: net: add a generic rule for BPF skeletons
selftests: xsk: move the AF_XDP test suite to selftests/net
selftests: xsk: collect interface capabilities in struct xsk_caps
selftests: xsk: split xskxceiver main() into setup, run and cleanup
selftests: xsk: run one test case per xskxceiver invocation
selftests: xsk: run the RX and TX endpoints in separate processes
selftests: xsk: add a hardware mode to xskxceiver
selftests: xsk: pass non-test traffic to the stack in hardware mode
selftests: xsk: share test case definitions with hardware runner
selftests: drv-net: test AF_XDP zero-copy with an SKB peer
selftests: xsk: document generic and hardware endpoint runs
Documentation/networking/af_xdp.rst | 6 +-
MAINTAINERS | 4 +-
tools/testing/selftests/bpf/.gitignore | 1 -
tools/testing/selftests/bpf/Makefile | 29 +-
tools/testing/selftests/bpf/network_helpers.c | 48 --
tools/testing/selftests/bpf/network_helpers.h | 2 -
tools/testing/selftests/bpf/prog_tests/xsk.c | 170 -----
tools/testing/selftests/bpf/xskxceiver.c | 479 -------------
.../testing/selftests/drivers/net/README.rst | 7 +
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/config | 1 +
tools/testing/selftests/drivers/net/hw/xsk.py | 417 +++++++++++
tools/testing/selftests/net/Makefile | 2 +
tools/testing/selftests/net/bpf.mk | 6 +
tools/testing/selftests/net/config | 1 +
tools/testing/selftests/net/lib/.gitignore | 2 +
tools/testing/selftests/net/lib/Makefile | 36 +-
.../testing/selftests/net/lib/xsk/README.rst | 138 ++++
.../prog_tests => net/lib/xsk}/test_xsk.c | 621 ++++++++++--------
.../prog_tests => net/lib/xsk}/test_xsk.h | 121 ++--
.../net/lib/xsk/test_xsk_case_defs.h | 57 ++
.../selftests/{bpf => net/lib/xsk}/xsk.c | 86 ++-
.../selftests/{bpf => net/lib/xsk}/xsk.h | 6 +
.../testing/selftests/net/lib/xsk/xsk_peer.c | 280 ++++++++
.../testing/selftests/net/lib/xsk/xsk_peer.h | 19 +
.../{bpf => net/lib/xsk}/xsk_xdp_common.h | 0
.../lib/xsk/xsk_xdp_progs.bpf.c} | 39 ++
.../selftests/net/lib/xsk/xskxceiver.c | 654 ++++++++++++++++++
.../{bpf => net/lib/xsk}/xskxceiver.h | 0
.../selftests/{bpf => net}/test_xsk.sh | 186 +++--
.../selftests/{bpf => net}/xsk_prereqs.sh | 64 +-
31 files changed, 2382 insertions(+), 1101 deletions(-)
delete mode 100644 tools/testing/selftests/bpf/prog_tests/xsk.c
delete mode 100644 tools/testing/selftests/bpf/xskxceiver.c
create mode 100755 tools/testing/selftests/drivers/net/hw/xsk.py
create mode 100644 tools/testing/selftests/net/lib/xsk/README.rst
rename tools/testing/selftests/{bpf/prog_tests => net/lib/xsk}/test_xsk.c (85%)
rename tools/testing/selftests/{bpf/prog_tests => net/lib/xsk}/test_xsk.h (67%)
create mode 100644 tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk.c (91%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk.h (95%)
create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.c
create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.h
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk_xdp_common.h (100%)
rename tools/testing/selftests/{bpf/progs/xsk_xdp_progs.c => net/lib/xsk/xsk_xdp_progs.bpf.c} (75%)
create mode 100644 tools/testing/selftests/net/lib/xsk/xskxceiver.c
rename tools/testing/selftests/{bpf => net/lib/xsk}/xskxceiver.h (100%)
rename tools/testing/selftests/{bpf => net}/test_xsk.sh (57%)
rename tools/testing/selftests/{bpf => net}/xsk_prereqs.sh (53%)
--
2.43.0
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 01/14] selftests: xsk: factor endpoint work out of pthread wrappers
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
@ 2026-10-08 11:48 ` Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
` (13 subsequent siblings)
14 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:48 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Move TX execution, RX setup and RX validation into synchronous helpers
and leave the pthread entry points as thin wrappers, so the same code
can later run without a thread per endpoint. Barrier, pacing and test
behavior are unchanged, but plan is to step away from thread-based
model in favor of separate process approach, hence this preparation.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../selftests/bpf/prog_tests/test_xsk.c | 98 +++++++++++--------
1 file changed, 59 insertions(+), 39 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 4549358cc8c2..2a1362874092 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -1623,40 +1623,31 @@ static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
return 0;
}
-void *worker_testapp_validate_tx(void *arg)
+static int testapp_validate_tx_endpoint(struct test_spec *test,
+ struct ifobject *ifobject)
{
- struct test_spec *test = (struct test_spec *)arg;
- struct ifobject *ifobject = test->ifobj_tx;
int err;
if (test->current_step == 1) {
- if (!ifobject->shared_umem) {
- if (thread_common_ops(test, ifobject)) {
- test->fail = true;
- pthread_exit(NULL);
- }
- } else {
- if (thread_common_ops_tx(test, ifobject)) {
- test->fail = true;
- pthread_exit(NULL);
- }
- }
+ if (!ifobject->shared_umem)
+ err = thread_common_ops(test, ifobject);
+ else
+ err = thread_common_ops_tx(test, ifobject);
+ if (err)
+ return err;
}
err = send_pkts(test, ifobject);
if (!err && ifobject->validation_func)
err = ifobject->validation_func(ifobject);
- if (err)
- test->fail = true;
- pthread_exit(NULL);
+ return err;
}
-void *worker_testapp_validate_rx(void *arg)
+static int testapp_prepare_rx_endpoint(struct test_spec *test,
+ struct ifobject *ifobject)
{
- struct test_spec *test = (struct test_spec *)arg;
- struct ifobject *ifobject = test->ifobj_rx;
int err;
if (test->current_step == 1) {
@@ -1669,35 +1660,64 @@ void *worker_testapp_validate_rx(void *arg)
strerror(-err));
}
- if (test->use_barrier)
- pthread_barrier_wait(&barr);
+ return err;
+}
- /* We leave only now in case of error to avoid getting stuck in the barrier */
- if (err) {
- test->fail = true;
- pthread_exit(NULL);
- }
+static int testapp_validate_rx_endpoint(struct test_spec *test,
+ struct ifobject *ifobject)
+{
+ bool supported;
+ int err;
err = receive_pkts(test);
if (!err && ifobject->validation_func)
err = ifobject->validation_func(ifobject);
+ if (!err)
+ return TEST_PASS;
- if (err) {
- if (!test->adjust_tail) {
- test->fail = true;
- } else {
- bool supported;
+ if (!test->adjust_tail)
+ return err;
- if (is_adjust_tail_supported(ifobject->xdp_progs, &supported))
- test->fail = true;
- else if (!supported)
- test->adjust_tail_support = false;
- else
- test->fail = true;
- }
+ if (is_adjust_tail_supported(ifobject->xdp_progs, &supported))
+ return TEST_FAILURE;
+ if (!supported) {
+ test->adjust_tail_support = false;
+ return TEST_PASS;
}
+ return err;
+}
+
+void *worker_testapp_validate_tx(void *arg)
+{
+ struct test_spec *test = (struct test_spec *)arg;
+ int err;
+
+ err = testapp_validate_tx_endpoint(test, test->ifobj_tx);
+ if (err)
+ test->fail = true;
+
+ pthread_exit(NULL);
+}
+
+void *worker_testapp_validate_rx(void *arg)
+{
+ struct test_spec *test = (struct test_spec *)arg;
+ struct ifobject *ifobject = test->ifobj_rx;
+ int err;
+
+ err = testapp_prepare_rx_endpoint(test, ifobject);
+
+ if (test->use_barrier)
+ pthread_barrier_wait(&barr);
+
+ /* We leave only now in case of error to avoid getting stuck in the barrier */
+ if (!err)
+ err = testapp_validate_rx_endpoint(test, ifobject);
+ if (err)
+ test->fail = true;
+
pthread_exit(NULL);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 01/14] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
@ 2026-10-08 11:48 ` Maciej Fijalkowski
2026-10-09 9:46 ` Björn Töpel
2026-10-08 11:48 ` [PATCH v2 net-next 03/14] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
` (12 subsequent siblings)
14 siblings, 1 reply; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:48 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
xskxceiver -i ethX -i ethX was dedicated for HW tests and both sockets
were bound on queue 0 of one interface, which the kernel only allows
with a shared UMEM setting. That mode is going away: the endpoints are
about to become separate processes and would have to share the UMEM
mapping and the socket fd across them.
Furthermore, we were forcing an interface under test to a single queue
configuration via ethtool's set_channel op and turning on promiscuous
mode. That setting is not very CI-friendly, we are going to handle it
via ntuple and rss configuration from now on - this is not something
contained in this change but rather a preparation for reader. Reason
because it is not addressed here was because we were manually running
these commands before test suite run. It's just that HW testing was
only locally executed and it was the least exhausting way to do it.
Remove the shared-UMEM TX setup, the split UMEM addressing and the
POLL_TXQ_FULL workaround it needed, and drop the -i IFACE option of
test_xsk.sh that used it. XDP_SHARED_UMEM (two sockets of one endpoint)
is unaffected.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../selftests/bpf/prog_tests/test_xsk.c | 97 ++++---------------
.../selftests/bpf/prog_tests/test_xsk.h | 3 -
tools/testing/selftests/bpf/prog_tests/xsk.c | 4 -
tools/testing/selftests/bpf/test_xsk.sh | 49 +++-------
tools/testing/selftests/bpf/xsk_prereqs.sh | 7 --
tools/testing/selftests/bpf/xskxceiver.c | 5 -
6 files changed, 33 insertions(+), 132 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 2a1362874092..cee959cfe7e5 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -101,10 +101,6 @@ int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void
return ret;
umem->buffer = buffer;
- if (ifobj->shared_umem && ifobj->rx_on) {
- umem->base_addr = umem_size(umem);
- umem->next_buffer = umem_size(umem);
- }
return 0;
}
@@ -115,8 +111,8 @@ static u64 umem_alloc_buffer(struct xsk_umem_info *umem)
addr = umem->next_buffer;
umem->next_buffer += umem->frame_size;
- if (umem->next_buffer >= umem->base_addr + umem_size(umem))
- umem->next_buffer = umem->base_addr;
+ if (umem->next_buffer >= umem_size(umem))
+ umem->next_buffer = 0;
return addr;
}
@@ -207,7 +203,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
for (i = 0; i < MAX_INTERFACES; i++) {
struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx;
- struct xsk_umem_info *umem_real;
+ struct xsk_umem_info *umem;
ifobj->xsk = &ifobj->xsk_arr[0];
ifobj->use_poll = false;
@@ -224,16 +220,14 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
ifobj->tx_on = false;
}
- umem_real = ifobj->xsk_arr[0].umem_real;
- memset(umem_real, 0, sizeof(*umem_real));
+ umem = ifobj->xsk_arr[0].umem;
+ memset(umem, 0, sizeof(*umem));
for (j = 0; j < MAX_SOCKETS; j++) {
struct xsk_socket_info *xsk = &ifobj->xsk_arr[j];
memset(xsk, 0, sizeof(*xsk));
xsk->rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS;
- if (j == 0)
- xsk->umem_real = umem_real;
- xsk->umem = umem_real;
+ xsk->umem = umem;
xsk->batch_size = DEFAULT_BATCH_SIZE;
if (i == 0)
xsk->pkt_stream = test->tx_pkt_stream_default;
@@ -825,8 +819,6 @@ static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 exp
void *data = xsk_umem__get_data(umem->buffer, addr);
u64 umem_sz = umem_size(umem);
- addr -= umem->base_addr;
-
if (addr >= umem_sz || addr + len > umem_sz) {
ksft_print_msg("Frag invalid addr: %llx len: %u\n",
(unsigned long long)addr, len);
@@ -1164,7 +1156,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
bool test_timeout)
{
u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len;
- struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem_real;
+ struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem;
struct pkt_stream *pkt_stream = xsk->pkt_stream;
bool use_poll = ifobject->use_poll;
struct pollfd fds = { };
@@ -1468,12 +1460,12 @@ static int validate_tx_invalid_descs(struct ifobject *ifobject)
}
static int xsk_configure(struct test_spec *test, struct ifobject *ifobject,
- struct xsk_umem_info *umem, bool tx)
+ struct xsk_umem_info *umem)
{
int i, ret;
for (i = 0; i < test->nb_sockets; i++) {
- bool shared = (ifobject->shared_umem && tx) ? true : !!i;
+ bool shared = !!i;
u32 ctr = 0;
while (ctr++ < SOCK_RECONF_CTR) {
@@ -1497,31 +1489,6 @@ static int xsk_configure(struct test_spec *test, struct ifobject *ifobject,
return 0;
}
-static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobject)
-{
- struct xsk_umem_info *umem_rx, *umem_tx;
- int ret;
-
- if (!test->ifobj_rx || !test->ifobj_rx->xsk_arr[0].umem->umem) {
- ksft_print_msg("Error: RX UMEM is not initialized before shared-UMEM TX setup\n");
- return -EINVAL;
- }
-
- umem_rx = test->ifobj_rx->xsk_arr[0].umem;
- umem_tx = ifobject->xsk_arr[0].umem_real;
- memcpy(umem_tx, umem_rx, sizeof(*umem_tx));
- umem_tx->base_addr = 0;
- umem_tx->next_buffer = 0;
-
- ret = xsk_configure(test, ifobject, umem_rx, true);
- if (ret)
- return ret;
- ifobject->xsk = &ifobject->xsk_arr[0];
- ifobject->xskmap = test->ifobj_rx->xskmap;
-
- return 0;
-}
-
static int xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream,
bool fill_up)
{
@@ -1547,7 +1514,7 @@ static int xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream
if (!pkt) {
if (!fill_up)
break;
- addr = filled * umem->frame_size + umem->base_addr;
+ addr = filled * umem->frame_size;
} else if (pkt->offset >= 0) {
addr = pkt->offset % umem->frame_size + umem_alloc_buffer(umem);
} else {
@@ -1584,9 +1551,6 @@ static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
if (umem->unaligned_mode)
mmap_flags |= MAP_HUGETLB | MAP_HUGE_2MB;
- if (ifobject->shared_umem)
- umem_sz *= 2;
-
mmap_sz = umem->unaligned_mode ?
ceil_u64(umem_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE : umem_sz;
@@ -1600,7 +1564,7 @@ static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
if (ret)
return ret;
- ret = xsk_configure(test, ifobject, umem, false);
+ ret = xsk_configure(test, ifobject, umem);
if (ret)
return ret;
@@ -1629,10 +1593,7 @@ static int testapp_validate_tx_endpoint(struct test_spec *test,
int err;
if (test->current_step == 1) {
- if (!ifobject->shared_umem)
- err = thread_common_ops(test, ifobject);
- else
- err = thread_common_ops_tx(test, ifobject);
+ err = thread_common_ops(test, ifobject);
if (err)
return err;
}
@@ -1779,7 +1740,7 @@ static int xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_r
return err;
}
- if (!ifobj_tx || ifobj_tx->shared_umem)
+ if (!ifobj_tx)
return 0;
if (xdp_prog_changed_tx(test))
@@ -1805,7 +1766,7 @@ static void clean_umem(struct test_spec *test, struct ifobject *ifobj1, struct i
return;
testapp_clean_xsk_umem(ifobj1);
- if (ifobj2 && !ifobj2->shared_umem)
+ if (ifobj2)
testapp_clean_xsk_umem(ifobj2);
}
@@ -2184,12 +2145,6 @@ int testapp_invalid_desc(struct test_spec *test)
pkts[8].valid = false;
}
- if (test->ifobj_tx->shared_umem) {
- pkts[4].offset += umem_sz;
- pkts[5].offset += umem_sz;
- pkts[6].offset += umem_sz;
- }
-
if (pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)))
return TEST_FAILURE;
return testapp_validate_traffic(test);
@@ -2248,28 +2203,14 @@ int testapp_xdp_shared_umem(struct test_spec *test)
int testapp_poll_txq_tmout(struct test_spec *test)
{
- bool shared_umem = test->ifobj_tx->shared_umem;
- int ret;
-
test->poll_tmout = true;
- /*
- * POLL_TXQ_FULL exercises TX timeout setup in isolation.
- * Keep TX out of shared-UMEM mode here so TX setup does not require
- * RX UMEM to be initialized first.
- */
- test->ifobj_tx->shared_umem = false;
test->ifobj_tx->use_poll = true;
/* create invalid frame by set umem frame_size and pkt length equal to 2048 */
test->ifobj_tx->xsk->umem->frame_size = 2048;
- if (pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048)) {
- test->ifobj_tx->shared_umem = shared_umem;
+ if (pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048))
return TEST_FAILURE;
- }
-
- ret = testapp_validate_traffic_single_thread(test, test->ifobj_tx);
- test->ifobj_tx->shared_umem = shared_umem;
- return ret;
+ return testapp_validate_traffic_single_thread(test, test->ifobj_tx);
}
int testapp_poll_rxq_tmout(struct test_spec *test)
@@ -2653,8 +2594,8 @@ struct ifobject *ifobject_create(void)
if (!ifobj->xsk_arr)
goto out_xsk_arr;
- ifobj->xsk_arr[0].umem_real = calloc(1, sizeof(struct xsk_umem_info));
- if (!ifobj->xsk_arr[0].umem_real)
+ ifobj->xsk_arr[0].umem = calloc(1, sizeof(struct xsk_umem_info));
+ if (!ifobj->xsk_arr[0].umem)
goto out_umem;
return ifobj;
@@ -2669,7 +2610,7 @@ struct ifobject *ifobject_create(void)
void ifobject_delete(struct ifobject *ifobj)
{
if (ifobj->xsk_arr)
- free(ifobj->xsk_arr[0].umem_real);
+ free(ifobj->xsk_arr[0].umem);
free(ifobj->xsk_arr);
free(ifobj);
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
index 03753ddc5dcd..379be8481079 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
@@ -83,7 +83,6 @@ typedef int (*test_func_t)(struct test_spec *test);
struct xsk_socket_info {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
- struct xsk_umem_info *umem_real;
struct xsk_umem_info *umem;
struct xsk_socket *xsk;
struct pkt_stream *pkt_stream;
@@ -108,7 +107,6 @@ struct xsk_umem_info {
u32 frame_headroom;
void *buffer;
u32 frame_size;
- u32 base_addr;
u32 fill_size;
u32 comp_size;
bool unaligned_mode;
@@ -145,7 +143,6 @@ struct ifobject {
bool busy_poll;
bool use_fill_ring;
bool release_rx;
- bool shared_umem;
bool use_metadata;
bool unaligned_supp;
bool multi_buff_supp;
diff --git a/tools/testing/selftests/bpf/prog_tests/xsk.c b/tools/testing/selftests/bpf/prog_tests/xsk.c
index 6e2f63ee2a6c..e191f84d819c 100644
--- a/tools/testing/selftests/bpf/prog_tests/xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/xsk.c
@@ -53,10 +53,6 @@ int configure_ifobj(struct ifobject *tx, struct ifobject *rx)
if (!ASSERT_OK_FD(tx->ifindex, "get TX ifindex"))
return -1;
- tx->shared_umem = false;
- rx->shared_umem = false;
-
-
return 0;
}
diff --git a/tools/testing/selftests/bpf/test_xsk.sh b/tools/testing/selftests/bpf/test_xsk.sh
index 62db060298a4..69fac65f073c 100755
--- a/tools/testing/selftests/bpf/test_xsk.sh
+++ b/tools/testing/selftests/bpf/test_xsk.sh
@@ -71,9 +71,6 @@
# Set up veth interfaces and leave them up so xskxceiver can be launched in a debugger:
# sudo ./test_xsk.sh -d
#
-# Run test suite for physical device in loopback mode
-# sudo ./test_xsk.sh -i IFACE
-#
# Run test suite in a specific mode only [skb,drv,zc]
# sudo ./test_xsk.sh -m MODE
#
@@ -88,14 +85,11 @@
. xsk_prereqs.sh
-ETH=""
-
-while getopts "vi:dm:lt:h" flag
+while getopts "vdm:lt:h" flag
do
case "${flag}" in
v) verbose=1;;
d) debug=1;;
- i) ETH=${OPTARG};;
m) MODE=${OPTARG};;
l) list=1;;
t) TEST=${OPTARG};;
@@ -157,21 +151,16 @@ if [[ $help -eq 1 ]]; then
exit
fi
-if [ ! -z $ETH ]; then
- VETH0=${ETH}
- VETH1=${ETH}
-else
- validate_root_exec
- validate_veth_support ${VETH0}
- validate_ip_utility
- setup_vethPairs
-
- retval=$?
- if [ $retval -ne 0 ]; then
- test_status $retval "${TEST_NAME}"
- cleanup_exit ${VETH0} ${VETH1}
- exit $retval
- fi
+validate_root_exec
+validate_veth_support ${VETH0}
+validate_ip_utility
+setup_vethPairs
+
+retval=$?
+if [ $retval -ne 0 ]; then
+ test_status $retval "${TEST_NAME}"
+ cleanup_exit ${VETH0} ${VETH1}
+ exit $retval
fi
@@ -203,11 +192,7 @@ fi
exec_xskxceiver
-if [ -z $ETH ]; then
- cleanup_exit ${VETH0} ${VETH1}
-else
- cleanup_iface ${ETH} ${MTU}
-fi
+cleanup_exit ${VETH0} ${VETH1}
if [[ $list -eq 1 ]]; then
exit
@@ -216,18 +201,12 @@ fi
TEST_NAME="XSK_SELFTESTS_${VETH0}_BUSY_POLL"
busy_poll=1
-if [ -z $ETH ]; then
- setup_vethPairs
-fi
+setup_vethPairs
exec_xskxceiver
## END TESTS
-if [ -z $ETH ]; then
- cleanup_exit ${VETH0} ${VETH1}
-else
- cleanup_iface ${ETH} ${MTU}
-fi
+cleanup_exit ${VETH0} ${VETH1}
failures=0
echo -e "\nSummary:"
diff --git a/tools/testing/selftests/bpf/xsk_prereqs.sh b/tools/testing/selftests/bpf/xsk_prereqs.sh
index 47c7b8064f38..ebff0f95d14b 100755
--- a/tools/testing/selftests/bpf/xsk_prereqs.sh
+++ b/tools/testing/selftests/bpf/xsk_prereqs.sh
@@ -53,13 +53,6 @@ test_exit()
exit 1
}
-cleanup_iface()
-{
- ip link set $1 mtu $2
- ip link set $1 xdp off
- ip link set $1 xdpgeneric off
-}
-
clear_configs()
{
[ $(ip link show $1 &>/dev/null; echo $?;) == 0 ] &&
diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/bpf/xskxceiver.c
index 7dad8556a722..24109dd7264e 100644
--- a/tools/testing/selftests/bpf/xskxceiver.c
+++ b/tools/testing/selftests/bpf/xskxceiver.c
@@ -341,7 +341,6 @@ int main(int argc, char **argv)
u32 i, j, failed_tests = 0, nb_tests;
int modes = TEST_MODE_SKB + 1;
struct test_spec test;
- bool shared_netdev;
int ret;
/* Use libbpf 1.0 API mode */
@@ -388,10 +387,6 @@ int main(int argc, char **argv)
ksft_exit_xfail();
}
- shared_netdev = (ifobj_tx->ifindex == ifobj_rx->ifindex);
- ifobj_tx->shared_umem = shared_netdev;
- ifobj_rx->shared_umem = shared_netdev;
-
if (!validate_interface(ifobj_tx) || !validate_interface(ifobj_rx))
print_usage(argv);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 03/14] selftests/bpf: drop the test_progs AF_XDP wrapper
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 01/14] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
@ 2026-10-08 11:48 ` Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 04/14] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
` (11 subsequent siblings)
14 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:48 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
prog_tests/xsk.c runs the xskxceiver test cases inside test_progs: it
creates a veth pair and drives the TX and RX sockets from two threads of
the test_progs process. The following patches turn each endpoint into a
separate process that can run against a peer on another host, so the
in-process two-thread model the wrapper depends on is going away.
Keeping the wrapper would mean rewiring it at every step of that split
and teaching test_progs to spawn and synchronize peer processes. Drop it
instead. BPF CI loses the ns_xsk_skb and ns_xsk_drv entries; the same
veth coverage remains available through test_xsk.sh, which moves to
selftests/net together with the test engine.
The wrapper ran only the cases in tests[]. ci_skip_tests[] held the ones
it left out: flaky and slow cases and those that need hugepages or HW
ring size support. xskxceiver runs both lists, and without the wrapper
nothing runs only one, so fold ci_skip_tests[] into tests[]. The test
IDs do not change.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../selftests/bpf/prog_tests/test_xsk.h | 3 -
tools/testing/selftests/bpf/prog_tests/xsk.c | 166 ------------------
tools/testing/selftests/bpf/xskxceiver.c | 10 +-
3 files changed, 2 insertions(+), 177 deletions(-)
delete mode 100644 tools/testing/selftests/bpf/prog_tests/xsk.c
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
index 379be8481079..b182a75d5705 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
@@ -294,9 +294,6 @@ static const struct test_spec tests[] = {
{.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags},
{.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink},
{.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer},
- };
-
-static const struct test_spec ci_skip_tests[] = {
/* Flaky tests */
{.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb},
{.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow},
diff --git a/tools/testing/selftests/bpf/prog_tests/xsk.c b/tools/testing/selftests/bpf/prog_tests/xsk.c
deleted file mode 100644
index e191f84d819c..000000000000
--- a/tools/testing/selftests/bpf/prog_tests/xsk.c
+++ /dev/null
@@ -1,166 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-#include <net/if.h>
-#include <stdarg.h>
-
-#include "network_helpers.h"
-#include "test_progs.h"
-#include "test_xsk.h"
-#include "xsk_xdp_progs.skel.h"
-
-#define VETH_RX "veth0"
-#define VETH_TX "veth1"
-#define MTU 1500
-
-int setup_veth(bool busy_poll)
-{
- SYS(fail,
- "ip link add %s numtxqueues 4 numrxqueues 4 type veth peer name %s numtxqueues 4 numrxqueues 4",
- VETH_RX, VETH_TX);
- SYS(fail, "sysctl -wq net.ipv6.conf.%s.disable_ipv6=1", VETH_RX);
- SYS(fail, "sysctl -wq net.ipv6.conf.%s.disable_ipv6=1", VETH_TX);
-
- if (busy_poll) {
- SYS(fail, "echo 2 > /sys/class/net/%s/napi_defer_hard_irqs", VETH_RX);
- SYS(fail, "echo 200000 > /sys/class/net/%s/gro_flush_timeout", VETH_RX);
- SYS(fail, "echo 2 > /sys/class/net/%s/napi_defer_hard_irqs", VETH_TX);
- SYS(fail, "echo 200000 > /sys/class/net/%s/gro_flush_timeout", VETH_TX);
- }
-
- SYS(fail, "ip link set %s mtu %d", VETH_RX, MTU);
- SYS(fail, "ip link set %s mtu %d", VETH_TX, MTU);
- SYS(fail, "ip link set %s up", VETH_RX);
- SYS(fail, "ip link set %s up", VETH_TX);
-
- return 0;
-
-fail:
- return -1;
-}
-
-void delete_veth(void)
-{
- SYS_NOFAIL("ip link del %s", VETH_RX);
- SYS_NOFAIL("ip link del %s", VETH_TX);
-}
-
-int configure_ifobj(struct ifobject *tx, struct ifobject *rx)
-{
- rx->ifindex = if_nametoindex(VETH_RX);
- if (!ASSERT_OK_FD(rx->ifindex, "get RX ifindex"))
- return -1;
-
- tx->ifindex = if_nametoindex(VETH_TX);
- if (!ASSERT_OK_FD(tx->ifindex, "get TX ifindex"))
- return -1;
-
- return 0;
-}
-
-static void test_xsk(const struct test_spec *test_to_run, enum test_mode mode)
-{
- u32 max_frags, umem_tailroom, cache_line_size;
- struct ifobject *ifobj_tx, *ifobj_rx;
- struct test_spec test;
- int ret;
-
- ifobj_tx = ifobject_create();
- if (!ASSERT_OK_PTR(ifobj_tx, "create ifobj_tx"))
- return;
-
- ifobj_rx = ifobject_create();
- if (!ASSERT_OK_PTR(ifobj_rx, "create ifobj_rx"))
- goto delete_tx;
-
- if (!ASSERT_OK(configure_ifobj(ifobj_tx, ifobj_rx), "conigure ifobj"))
- goto delete_rx;
-
- ret = get_hw_ring_size(ifobj_tx->ifname, &ifobj_tx->ring);
- if (!ret) {
- ifobj_tx->hw_ring_size_supp = true;
- ifobj_tx->set_ring.default_tx = ifobj_tx->ring.tx_pending;
- ifobj_tx->set_ring.default_rx = ifobj_tx->ring.rx_pending;
- }
-
- cache_line_size = read_procfs_val(SMP_CACHE_BYTES_PATH);
- if (!cache_line_size)
- cache_line_size = 64;
-
- max_frags = read_procfs_val(MAX_SKB_FRAGS_PATH);
- if (!max_frags)
- max_frags = 17;
-
- ifobj_tx->max_skb_frags = max_frags;
- ifobj_rx->max_skb_frags = max_frags;
-
- /* 48 bytes is a part of skb_shared_info w/o frags array;
- * 16 bytes is sizeof(skb_frag_t)
- */
- umem_tailroom = ALIGN(48 + (max_frags * 16), cache_line_size);
- ifobj_tx->umem_tailroom = umem_tailroom;
- ifobj_rx->umem_tailroom = umem_tailroom;
-
- if (!ASSERT_OK(init_iface(ifobj_rx, worker_testapp_validate_rx), "init RX"))
- goto delete_rx;
- if (!ASSERT_OK(init_iface(ifobj_tx, worker_testapp_validate_tx), "init TX"))
- goto delete_rx;
-
- test_init(&test, ifobj_tx, ifobj_rx, 0, &tests[0]);
-
- test.tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
- if (!ASSERT_OK_PTR(test.tx_pkt_stream_default, "TX pkt generation"))
- goto delete_rx;
- test.rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
- if (!ASSERT_OK_PTR(test.rx_pkt_stream_default, "RX pkt generation"))
- goto delete_rx;
-
-
- test_init(&test, ifobj_tx, ifobj_rx, mode, test_to_run);
- ret = test.test_func(&test);
- if (ret != TEST_SKIP)
- ASSERT_OK(ret, "Run test");
- pkt_stream_restore_default(&test);
-
- if (ifobj_tx->hw_ring_size_supp)
- hw_ring_size_reset(ifobj_tx);
-
- pkt_stream_delete(test.tx_pkt_stream_default);
- pkt_stream_delete(test.rx_pkt_stream_default);
- xsk_xdp_progs__destroy(ifobj_tx->xdp_progs);
- xsk_xdp_progs__destroy(ifobj_rx->xdp_progs);
-
-delete_rx:
- ifobject_delete(ifobj_rx);
-delete_tx:
- ifobject_delete(ifobj_tx);
-}
-
-void test_ns_xsk_skb(void)
-{
- int i;
-
- if (!ASSERT_OK(setup_veth(false), "setup veth"))
- return;
-
- for (i = 0; i < ARRAY_SIZE(tests); i++) {
- if (test__start_subtest(tests[i].name))
- test_xsk(&tests[i], TEST_MODE_SKB);
- }
-
- delete_veth();
-}
-
-void test_ns_xsk_drv(void)
-{
- int i;
-
- if (!ASSERT_OK(setup_veth(false), "setup veth"))
- return;
-
- for (i = 0; i < ARRAY_SIZE(tests); i++) {
- if (test__start_subtest(tests[i].name))
- test_xsk(&tests[i], TEST_MODE_DRV);
- }
-
- delete_veth();
-}
-
diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/bpf/xskxceiver.c
index 24109dd7264e..1256242959cb 100644
--- a/tools/testing/selftests/bpf/xskxceiver.c
+++ b/tools/testing/selftests/bpf/xskxceiver.c
@@ -327,14 +327,12 @@ static void print_tests(void)
printf("Tests:\n");
for (i = 0; i < ARRAY_SIZE(tests); i++)
printf("%u: %s\n", i, tests[i].name);
- for (i = ARRAY_SIZE(tests); i < ARRAY_SIZE(tests) + ARRAY_SIZE(ci_skip_tests); i++)
- printf("%u: %s\n", i, ci_skip_tests[i - ARRAY_SIZE(tests)].name);
}
int main(int argc, char **argv)
{
- const size_t total_tests = ARRAY_SIZE(tests) + ARRAY_SIZE(ci_skip_tests);
u32 cache_line_size, max_frags, umem_tailroom;
+ const size_t total_tests = ARRAY_SIZE(tests);
struct pkt_stream *rx_pkt_stream_default;
struct pkt_stream *tx_pkt_stream_default;
struct ifobject *ifobj_tx, *ifobj_rx;
@@ -444,11 +442,7 @@ int main(int argc, char **argv)
if (opt_run_test != RUN_ALL_TESTS && j != opt_run_test)
continue;
- if (j < ARRAY_SIZE(tests))
- test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]);
- else
- test_init(&test, ifobj_tx, ifobj_rx, i,
- &ci_skip_tests[j - ARRAY_SIZE(tests)]);
+ test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]);
run_pkt_test(&test);
usleep(USLEEP_MAX);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 04/14] selftests: net: add a generic rule for BPF skeletons
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (2 preceding siblings ...)
2026-10-08 11:48 ` [PATCH v2 net-next 03/14] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
@ 2026-10-08 11:48 ` Maciej Fijalkowski
2026-10-08 11:49 ` [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
` (10 subsequent siblings)
14 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:48 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
net/bpf.mk builds the BPF programs of the networking selftests, but a
test that loads its program through a bpftool-generated skeleton has to
bring its own rule for the header. Add a pattern rule that generates
$(OUTPUT)/<name>.skel.h from $(OUTPUT)/<name>.bpf.o. BPFTOOL selects the
bpftool binary and defaults to the one in PATH. When bpftool fails, the
rule removes the partial header, so a later build does not take it as
up to date.
xskxceiver, which moves to net/lib in the next patch, is the first user.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
tools/testing/selftests/net/bpf.mk | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tools/testing/selftests/net/bpf.mk b/tools/testing/selftests/net/bpf.mk
index a4f6755dd894..0eca0c5de5b5 100644
--- a/tools/testing/selftests/net/bpf.mk
+++ b/tools/testing/selftests/net/bpf.mk
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# Rules to generate bpf objs
CLANG ?= clang
+BPFTOOL ?= bpftool
SCRATCH_DIR := $(OUTPUT)/tools
BUILD_DIR := $(SCRATCH_DIR)/build
BPFDIR := $(top_srcdir)/tools/lib/bpf
@@ -42,6 +43,11 @@ $(BPF_PROG_OBJS): $(OUTPUT)/%.o : %.c $(BPFOBJ) | $(MAKE_DIRS)
$(Q)$(CLANG) -O2 -g --target=bpf $(CCINCLUDE) $(CLANG_SYS_INCLUDES) \
-c $< -o $@
+$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o
+ $(call msg,GEN-SKEL,,$@)
+ $(Q)$(BPFTOOL) gen skeleton $< name $(notdir $*) > $@ || \
+ { rm -f $@; exit 1; }
+
$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
$(APIDIR)/linux/bpf.h \
| $(BUILD_DIR)/libbpf
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (3 preceding siblings ...)
2026-10-08 11:48 ` [PATCH v2 net-next 04/14] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-09 11:18 ` Björn Töpel
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 06/14] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
` (9 subsequent siblings)
14 siblings, 2 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Move the xskxceiver engine, the AF_XDP userspace library, the test
definitions and the XDP program to net/lib/xsk, and the veth launcher
test_xsk.sh with its prerequisites script to selftests/net. A later
patch drives the same engine against real NICs from drivers/net/hw, and
net/lib is where both suites already take shared helpers from, so the
binary does not need a second copy.
net/lib builds xskxceiver together with its XDP program and skeleton,
using the bpftool in PATH, and skips it with a warning when bpftool is
missing or does not run, so the rest of net/lib still builds.
selftests/net installs test_xsk.sh, which runs lib/xskxceiver.
Like the rest of net/lib, xskxceiver builds against the installed uapi
headers rather than the tools/include/uapi copies that the bpf selftests
use. xskxceiver.c got min_t() only because the tools copy of
linux/netlink.h includes linux/kernel.h, so include linux/kernel.h
directly.
Move the two ethtool ring helpers from network_helpers into xsk.c so the
engine no longer depends on network_helpers, and point the kselftest.h
include of test_xsk.h at the net/lib include path. The XDP program moves
unchanged. Its Ethernet header bound check compares pointers of distinct
types, so net/lib builds it with -Wno-compare-distinct-pointer-types, as
the bpf selftests do.
xdp_hw_metadata and the test_progs xdp_metadata test keep using xsk.c
and xsk.h from their new location.
Point the AF_XDP entry in MAINTAINERS at the new paths.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
Documentation/networking/af_xdp.rst | 6 +--
MAINTAINERS | 3 +-
tools/testing/selftests/bpf/.gitignore | 1 -
tools/testing/selftests/bpf/Makefile | 29 +++++------
tools/testing/selftests/bpf/network_helpers.c | 48 ------------------
tools/testing/selftests/bpf/network_helpers.h | 2 -
tools/testing/selftests/net/Makefile | 2 +
tools/testing/selftests/net/config | 1 +
tools/testing/selftests/net/lib/.gitignore | 2 +
tools/testing/selftests/net/lib/Makefile | 34 +++++++++++++
.../prog_tests => net/lib/xsk}/test_xsk.c | 2 +-
.../prog_tests => net/lib/xsk}/test_xsk.h | 2 +-
.../selftests/{bpf => net/lib/xsk}/xsk.c | 49 ++++++++++++++++++-
.../selftests/{bpf => net/lib/xsk}/xsk.h | 4 ++
.../{bpf => net/lib/xsk}/xsk_xdp_common.h | 0
.../lib/xsk/xsk_xdp_progs.bpf.c} | 0
.../{bpf => net/lib/xsk}/xskxceiver.c | 7 +--
.../{bpf => net/lib/xsk}/xskxceiver.h | 0
.../selftests/{bpf => net}/test_xsk.sh | 0
.../selftests/{bpf => net}/xsk_prereqs.sh | 2 +-
20 files changed, 113 insertions(+), 81 deletions(-)
rename tools/testing/selftests/{bpf/prog_tests => net/lib/xsk}/test_xsk.c (99%)
rename tools/testing/selftests/{bpf/prog_tests => net/lib/xsk}/test_xsk.h (99%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk.c (95%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk.h (97%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk_xdp_common.h (100%)
rename tools/testing/selftests/{bpf/progs/xsk_xdp_progs.c => net/lib/xsk/xsk_xdp_progs.bpf.c} (100%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xskxceiver.c (99%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xskxceiver.h (100%)
rename tools/testing/selftests/{bpf => net}/test_xsk.sh (100%)
rename tools/testing/selftests/{bpf => net}/xsk_prereqs.sh (98%)
diff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst
index cc3f0d16b28f..538e71adb759 100644
--- a/Documentation/networking/af_xdp.rst
+++ b/Documentation/networking/af_xdp.rst
@@ -211,7 +211,7 @@ Libbpf
Libbpf is a helper library for eBPF and XDP that makes using these
technologies a lot simpler. It also contains specific helper functions
-in tools/testing/selftests/bpf/xsk.h for facilitating the use of
+in tools/testing/selftests/net/lib/xsk/xsk.h for facilitating the use of
AF_XDP. It contains two types of functions: those that can be used to
make the setup of AF_XDP socket easier and ones that can be used in the
data plane to access the rings safely and quickly.
@@ -751,7 +751,7 @@ return values:
returned number signifies the max number of frags supported.
For an example on how these are used through libbpf, please take a
-look at tools/testing/selftests/bpf/xskxceiver.c.
+look at tools/testing/selftests/net/lib/xsk/xskxceiver.c.
Multi-Buffer Support for Zero-Copy Drivers
------------------------------------------
@@ -785,7 +785,7 @@ can be displayed with "-h", as usual.
This sample application uses libbpf to make the setup and usage of
AF_XDP simpler. If you want to know how the raw uapi of AF_XDP is
really used to make something more advanced, take a look at the libbpf
-code in tools/testing/selftests/bpf/xsk.[ch].
+code in tools/testing/selftests/net/lib/xsk/xsk.[ch].
FAQ
=======
diff --git a/MAINTAINERS b/MAINTAINERS
index 72ca3aab2106..0baa0d037c73 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29778,7 +29778,8 @@ F: include/net/xsk_buff_pool.h
F: include/uapi/linux/if_xdp.h
F: include/uapi/linux/xdp_diag.h
F: net/xdp/
-F: tools/testing/selftests/bpf/*xsk*
+F: tools/testing/selftests/net/*xsk*
+F: tools/testing/selftests/net/lib/xsk/
XEN BLOCK SUBSYSTEM
M: Roger Pau Monné <roger@xenproject.org>
diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index 986a6389186b..3c3183ea3c83 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -37,7 +37,6 @@ test_cpp
/uprobe_multi
*.ko
*.tmp
-xskxceiver
xdp_redirect_multi
xdp_synproxy
xdp_hw_metadata
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 93c707116fad..a2d357781c0f 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -62,7 +62,8 @@ COMMON_CFLAGS = -g $(OPT_FLAGS) -rdynamic -std=gnu11 \
$(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS) \
-I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \
-I$(TOOLSINCDIR) -I$(TOOLSARCHINCDIR) -I$(APIDIR) -I$(OUTPUT) \
- -I$(CURDIR)/libarena/include
+ -I$(CURDIR)/libarena/include \
+ -I$(CURDIR)/../net/lib/xsk
LDFLAGS += $(SAN_LDFLAGS)
LDLIBS += $(LIBELF_LIBS) -lz -lrt -lpthread
@@ -118,14 +119,13 @@ TEST_GEN_PROGS += test_progs-cpuv4
TEST_INST_SUBDIRS += cpuv4
endif
-TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
+TEST_FILES = $(wildcard progs/btf_dump_test_case_*.c)
# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
test_lirc_mode2.sh \
test_bpftool_build.sh \
test_doc_build.sh \
- test_xsk.sh \
test_xdp_features.sh
TEST_PROGS_EXTENDED := \
@@ -144,8 +144,7 @@ TEST_GEN_PROGS_EXTENDED = \
veristat \
xdp_features \
xdp_hw_metadata \
- xdp_synproxy \
- xskxceiver
+ xdp_synproxy
TEST_GEN_FILES += $(TEST_KMODS) liburandom_read.so urandom_read sign-file uprobe_multi
@@ -542,7 +541,6 @@ linked_maps.skel.h-deps := linked_maps1.bpf.o linked_maps2.bpf.o
test_subskeleton.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o test_subskeleton.bpf.o
test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o
test_usdt.skel.h-deps := test_usdt.bpf.o test_usdt_multispec.bpf.o
-xsk_xdp_progs.skel.h-deps := xsk_xdp_progs.bpf.o
xdp_hw_metadata.skel.h-deps := xdp_hw_metadata.bpf.o
xdp_features.skel.h-deps := xdp_features.bpf.o
tracing_multi.skel.h-deps := tracing_multi_attach.bpf.o tracing_multi_check.bpf.o
@@ -769,6 +767,8 @@ $(TRUNNER_EXTRA_OBJS): $(TRUNNER_OUTPUT)/%.o: \
$$(call msg,EXT-OBJ,$(TRUNNER_BINARY),$$@)
$(Q)$$(CC) $$(CFLAGS) -c $$< $$(LDLIBS) -o $$@
+$(TRUNNER_OUTPUT)/xsk.o: ../net/lib/xsk/xsk.h
+
$(TRUNNER_LIB_OBJS): $(TRUNNER_OUTPUT)/%.o:$(TOOLSDIR)/lib/%.c
$$(call msg,LIB-OBJ,$(TRUNNER_BINARY),$$@)
$(Q)$$(CC) $$(CFLAGS) -c $$< $$(LDLIBS) -o $$@
@@ -845,6 +845,10 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
endif
+# xsk.c lives in ../net/lib/xsk so it can be shared with selftests/net;
+# let make find it there instead of keeping a local copy.
+vpath %.c ../net/lib/xsk
+
# Define test_progs test runner.
TRUNNER_TESTS_DIR := prog_tests
TRUNNER_BPF_PROGS_DIR := progs
@@ -931,18 +935,9 @@ $(OUTPUT)/test_verifier: test_verifier.c verifier/tests.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
-# Keep xskxceiver independent from test_progs object dependencies.
-$(OUTPUT)/xskxceiver: xskxceiver.c xsk.c network_helpers.c \
- $(TOOLSDIR)/lib/find_bit.c prog_tests/test_xsk.c \
- xskxceiver.h xsk.h network_helpers.h \
- prog_tests/test_xsk.h test_progs.h bpf_util.h \
- $(OUTPUT)/xsk_xdp_progs.skel.h $(BPFOBJ) | $(OUTPUT)
- $(call msg,BINARY,,$@)
- $(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
-
-$(OUTPUT)/xdp_hw_metadata: xdp_hw_metadata.c xsk.c network_helpers.c \
+$(OUTPUT)/xdp_hw_metadata: xdp_hw_metadata.c ../net/lib/xsk/xsk.c network_helpers.c \
$(TOOLSDIR)/lib/find_bit.c xdp_metadata.h \
- xsk.h network_helpers.h test_progs.h bpf_util.h \
+ ../net/lib/xsk/xsk.h network_helpers.h test_progs.h bpf_util.h \
$(OUTPUT)/xdp_hw_metadata.skel.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
diff --git a/tools/testing/selftests/bpf/network_helpers.c b/tools/testing/selftests/bpf/network_helpers.c
index cdf2d7d3ab32..d72d7022cc69 100644
--- a/tools/testing/selftests/bpf/network_helpers.c
+++ b/tools/testing/selftests/bpf/network_helpers.c
@@ -622,54 +622,6 @@ int get_socket_local_port(int sock_fd)
return -1;
}
-int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
-{
- struct ifreq ifr = {0};
- int sockfd, err;
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- return -errno;
-
- memcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-
- ring_param->cmd = ETHTOOL_GRINGPARAM;
- ifr.ifr_data = (char *)ring_param;
-
- if (ioctl(sockfd, SIOCETHTOOL, &ifr) < 0) {
- err = errno;
- close(sockfd);
- return -err;
- }
-
- close(sockfd);
- return 0;
-}
-
-int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
-{
- struct ifreq ifr = {0};
- int sockfd, err;
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd < 0)
- return -errno;
-
- memcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-
- ring_param->cmd = ETHTOOL_SRINGPARAM;
- ifr.ifr_data = (char *)ring_param;
-
- if (ioctl(sockfd, SIOCETHTOOL, &ifr) < 0) {
- err = errno;
- close(sockfd);
- return -err;
- }
-
- close(sockfd);
- return 0;
-}
-
struct send_recv_arg {
int fd;
uint32_t bytes;
diff --git a/tools/testing/selftests/bpf/network_helpers.h b/tools/testing/selftests/bpf/network_helpers.h
index 75133119c04a..e7333b769150 100644
--- a/tools/testing/selftests/bpf/network_helpers.h
+++ b/tools/testing/selftests/bpf/network_helpers.h
@@ -89,8 +89,6 @@ int make_sockaddr(int family, const char *addr_str, __u16 port,
struct sockaddr_storage *addr, socklen_t *len);
char *ping_command(int family);
int get_socket_local_port(int sock_fd);
-int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
-int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
int open_tuntap(const char *dev_name, bool need_mac);
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 54beea2e348c..aa31b6216dd1 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -118,6 +118,7 @@ TEST_PROGS := \
test_vxlan_under_vrf.sh \
test_vxlan_vnifilter_notify.sh \
test_vxlan_vnifiltering.sh \
+ test_xsk.sh \
tfo_passive.sh \
traceroute.sh \
txtimestamp.sh \
@@ -209,6 +210,7 @@ TEST_FILES := \
in_netns.sh \
lib.sh \
settings \
+ xsk_prereqs.sh \
# end of TEST_FILES
# YNL files, must be before "include ..lib.mk"
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index d355cf980597..bdaa052eb052 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -137,5 +137,6 @@ CONFIG_USER_NS=y
CONFIG_VETH=y
CONFIG_VLAN_8021Q=y
CONFIG_VXLAN=m
+CONFIG_XDP_SOCKETS=y
CONFIG_XFRM_INTERFACE=m
CONFIG_XFRM_USER=m
diff --git a/tools/testing/selftests/net/lib/.gitignore b/tools/testing/selftests/net/lib/.gitignore
index 6cd2b762af5d..1106fef3041f 100644
--- a/tools/testing/selftests/net/lib/.gitignore
+++ b/tools/testing/selftests/net/lib/.gitignore
@@ -2,3 +2,5 @@
csum
gro
xdp_helper
+xsk_xdp_progs.skel.h
+xskxceiver
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index ff83603397d0..a542757e2957 100644
--- a/tools/testing/selftests/net/lib/Makefile
+++ b/tools/testing/selftests/net/lib/Makefile
@@ -5,6 +5,15 @@ CFLAGS += -I../../../../../usr/include/ $(KHDR_INCLUDES)
# Additional include paths needed by kselftest.h
CFLAGS += -I../../
+# xskxceiver needs a working bpftool to generate its skeleton
+BPFTOOL ?= bpftool
+HAS_BPFTOOL := $(shell $(BPFTOOL) version >/dev/null 2>&1 && echo y)
+ifeq ($(HAS_BPFTOOL),y)
+COND_GEN_FILES += xskxceiver
+else
+$(warning excluding xskxceiver, bpftool not installed or not working)
+endif
+
TEST_FILES := \
../../../../net/ynl \
../../../../../Documentation/netlink/specs \
@@ -13,6 +22,7 @@ TEST_FILES := \
TEST_GEN_FILES := \
$(patsubst %.c,%.o,$(wildcard *.bpf.c)) \
+ $(COND_GEN_FILES) \
csum \
gro \
xdp_helper \
@@ -23,3 +33,27 @@ TEST_INCLUDES := $(wildcard py/*.py sh/*.sh)
include ../../lib.mk
include ../bpf.mk
+
+# AF_XDP test engine used by net/test_xsk.sh.
+XSK_DIR := xsk
+XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
+XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
+XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c) \
+ $(top_srcdir)/tools/lib/find_bit.c
+XSK_HEADERS := $(wildcard $(XSK_DIR)/*.h)
+
+PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+XSK_LDLIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -lz
+
+$(XSK_BPF_OBJ): $(XSK_DIR)/xsk_xdp_progs.bpf.c $(XSK_DIR)/xsk_xdp_common.h $(BPFOBJ)
+ $(call msg,BPF_PROG,,$@)
+ $(Q)$(CLANG) -O2 -g --target=bpf $(CCINCLUDE) $(CLANG_SYS_INCLUDES) \
+ -Wno-compare-distinct-pointer-types -c $< -o $@
+
+$(OUTPUT)/xskxceiver: $(XSK_SOURCES) $(XSK_HEADERS) $(XSK_SKEL) $(BPFOBJ)
+ $(call msg,CC,,$@)
+ $(Q)$(CC) $(CFLAGS) -I$(top_srcdir)/tools/include -I$(OUTPUT) \
+ -I$(SCRATCH_DIR)/include \
+ $(XSK_SOURCES) $(BPFOBJ) $(LDLIBS) $(XSK_LDLIBS) -o $@
+
+EXTRA_CLEAN += $(XSK_BPF_OBJ) $(XSK_SKEL)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
similarity index 99%
rename from tools/testing/selftests/bpf/prog_tests/test_xsk.c
rename to tools/testing/selftests/net/lib/xsk/test_xsk.c
index cee959cfe7e5..237d9e076e10 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#include <arpa/inet.h>
#include <bpf/bpf.h>
#include <errno.h>
#include <linux/bitmap.h>
@@ -13,7 +14,6 @@
#include <sys/time.h>
#include <unistd.h>
-#include "network_helpers.h"
#include "test_xsk.h"
#include "xsk_xdp_common.h"
#include "xsk_xdp_progs.skel.h"
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
similarity index 99%
rename from tools/testing/selftests/bpf/prog_tests/test_xsk.h
rename to tools/testing/selftests/net/lib/xsk/test_xsk.h
index b182a75d5705..0717d855e4df 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -5,7 +5,7 @@
#include <linux/ethtool.h>
#include <linux/if_xdp.h>
-#include "../kselftest.h"
+#include "kselftest.h"
#include "xsk.h"
#ifndef SO_PREFER_BUSY_POLL
diff --git a/tools/testing/selftests/bpf/xsk.c b/tools/testing/selftests/net/lib/xsk/xsk.c
similarity index 95%
rename from tools/testing/selftests/bpf/xsk.c
rename to tools/testing/selftests/net/lib/xsk/xsk.c
index 25d568abf0f2..6bd32caf5d08 100644
--- a/tools/testing/selftests/bpf/xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/xsk.c
@@ -35,7 +35,6 @@
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "xsk.h"
-#include "bpf_util.h"
#ifndef SOL_XDP
#define SOL_XDP 283
@@ -779,3 +778,51 @@ void xsk_socket__delete(struct xsk_socket *xsk)
close(xsk->fd);
free(xsk);
}
+
+int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
+{
+ struct ifreq ifr = {0};
+ int sockfd, err;
+
+ sockfd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ memcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ ring_param->cmd = ETHTOOL_GRINGPARAM;
+ ifr.ifr_data = (char *)ring_param;
+
+ if (ioctl(sockfd, SIOCETHTOOL, &ifr) < 0) {
+ err = errno;
+ close(sockfd);
+ return -err;
+ }
+
+ close(sockfd);
+ return 0;
+}
+
+int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
+{
+ struct ifreq ifr = {0};
+ int sockfd, err;
+
+ sockfd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ memcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ ring_param->cmd = ETHTOOL_SRINGPARAM;
+ ifr.ifr_data = (char *)ring_param;
+
+ if (ioctl(sockfd, SIOCETHTOOL, &ifr) < 0) {
+ err = errno;
+ close(sockfd);
+ return -err;
+ }
+
+ close(sockfd);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/xsk.h b/tools/testing/selftests/net/lib/xsk/xsk.h
similarity index 97%
rename from tools/testing/selftests/bpf/xsk.h
rename to tools/testing/selftests/net/lib/xsk/xsk.h
index 48729da142c2..3f1fea999763 100644
--- a/tools/testing/selftests/bpf/xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/xsk.h
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
+#include <linux/ethtool.h>
#include <linux/if_xdp.h>
#include <bpf/libbpf.h>
@@ -242,6 +243,9 @@ void xsk_socket__delete(struct xsk_socket *xsk);
int xsk_set_mtu(int ifindex, int mtu);
+int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
+int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/tools/testing/selftests/bpf/xsk_xdp_common.h b/tools/testing/selftests/net/lib/xsk/xsk_xdp_common.h
similarity index 100%
rename from tools/testing/selftests/bpf/xsk_xdp_common.h
rename to tools/testing/selftests/net/lib/xsk/xsk_xdp_common.h
diff --git a/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c b/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
similarity index 100%
rename from tools/testing/selftests/bpf/progs/xsk_xdp_progs.c
rename to tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
similarity index 99%
rename from tools/testing/selftests/bpf/xskxceiver.c
rename to tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 1256242959cb..f4a2f45a6752 100644
--- a/tools/testing/selftests/bpf/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -81,6 +81,7 @@
#include <linux/netdev.h>
#include <linux/ethtool.h>
#include <linux/align.h>
+#include <linux/kernel.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <locale.h>
@@ -91,7 +92,7 @@
#include <sys/mman.h>
#include <sys/types.h>
-#include "prog_tests/test_xsk.h"
+#include "test_xsk.h"
#include "xsk_xdp_progs.skel.h"
#include "xsk.h"
#include "xskxceiver.h"
@@ -100,14 +101,10 @@
#include "kselftest.h"
#include "xsk_xdp_common.h"
-#include <network_helpers.h>
-
static bool opt_print_tests;
static enum test_mode opt_mode = TEST_MODE_ALL;
static u32 opt_run_test = RUN_ALL_TESTS;
-void test__fail(void) { /* for network_helpers.c */ }
-
static void __exit_with_error(int error, const char *file, const char *func, int line)
{
ksft_test_result_fail("[%s:%s:%i]: ERROR: %d/\"%s\"\n", file, func, line,
diff --git a/tools/testing/selftests/bpf/xskxceiver.h b/tools/testing/selftests/net/lib/xsk/xskxceiver.h
similarity index 100%
rename from tools/testing/selftests/bpf/xskxceiver.h
rename to tools/testing/selftests/net/lib/xsk/xskxceiver.h
diff --git a/tools/testing/selftests/bpf/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
similarity index 100%
rename from tools/testing/selftests/bpf/test_xsk.sh
rename to tools/testing/selftests/net/test_xsk.sh
diff --git a/tools/testing/selftests/bpf/xsk_prereqs.sh b/tools/testing/selftests/net/xsk_prereqs.sh
similarity index 98%
rename from tools/testing/selftests/bpf/xsk_prereqs.sh
rename to tools/testing/selftests/net/xsk_prereqs.sh
index ebff0f95d14b..5e5c8ef3fff2 100755
--- a/tools/testing/selftests/bpf/xsk_prereqs.sh
+++ b/tools/testing/selftests/net/xsk_prereqs.sh
@@ -8,7 +8,7 @@ ksft_xfail=2
ksft_xpass=3
ksft_skip=4
-XSKOBJ=xskxceiver
+XSKOBJ=lib/xskxceiver
validate_root_exec()
{
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 06/14] selftests: xsk: collect interface capabilities in struct xsk_caps
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (4 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-08 11:49 ` [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
` (8 subsequent siblings)
14 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
A later patch runs the TX and RX endpoints of a test case as separate
processes, possibly on different hosts, and each process binds only its
own interface. The shared test code still looks at the capabilities of
both the TX and the RX ifobject, for instance when it checks both sides
for multi-buffer support or when it sizes the RX batch from the TX ring
limit. The endpoints do not exchange capabilities, so a process will
represent the other side by a shadow ifobject that mirrors the local
capabilities.
Prepare for that by replacing the per-interface support booleans and
limits in struct ifobject with one struct xsk_caps holding capability
flags and interface limits. Mirroring the local capabilities then takes
a single struct assignment, and a capability added later cannot be left
out of the copy. The maximum TX ring size is copied there from the
ethtool ring parameters, which a shadow does not mirror.
Add ifobj_has_cap() and ifobj_set_cap() to test and set the flags, and
xsk_get_cap() to read a limit. Like the open-coded reads it replaces,
xsk_get_cap() takes the limit from the TX ifobject, the only one that
has all of them set.
The unaligned UMEM check, which this patch rewrites anyway, now looks
at the RX ifobject only. The flag comes from a huge page probe that does
not depend on the interface, and test_spec_set_unaligned() switches
both UMEMs to unaligned mode together, so checking the TX side as well
adds nothing.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 84 +++++++++++--------
.../testing/selftests/net/lib/xsk/test_xsk.h | 34 ++++++--
.../selftests/net/lib/xsk/xskxceiver.c | 13 +--
3 files changed, 81 insertions(+), 50 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 237d9e076e10..22a81723d5de 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -244,7 +244,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
ifobj->xsk->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
}
- if (ifobj_tx->hw_ring_size_supp)
+ if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING))
hw_ring_size_reset(ifobj_tx);
test->ifobj_tx = ifobj_tx;
@@ -1774,20 +1774,24 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i
struct ifobject *ifobj2)
{
pthread_t t0, t1;
+ u32 mbuf_cap;
int err;
- if (test->mtu > MAX_ETH_PKT_SIZE) {
- if (test->mode == TEST_MODE_ZC && (!ifobj1->multi_buff_zc_supp ||
- (ifobj2 && !ifobj2->multi_buff_zc_supp))) {
- ksft_print_msg("Multi buffer for zero-copy not supported.\n");
- return TEST_SKIP;
- }
- if (test->mode != TEST_MODE_ZC && (!ifobj1->multi_buff_supp ||
- (ifobj2 && !ifobj2->multi_buff_supp))) {
- ksft_print_msg("Multi buffer not supported.\n");
- return TEST_SKIP;
- }
+ if (test->mtu <= MAX_ETH_PKT_SIZE)
+ goto skip_mbuf_check;
+
+ mbuf_cap = test->mode == TEST_MODE_ZC ? XSK_CAP_MBUF_ZC :
+ XSK_CAP_MBUF;
+
+ if (!ifobj_has_cap(ifobj1, mbuf_cap) ||
+ (ifobj2 && !ifobj_has_cap(ifobj2, mbuf_cap))) {
+ ksft_print_msg("Multi buffer%s not supported.\n",
+ mbuf_cap == XSK_CAP_MBUF_ZC ?
+ " for zero-copy" : "");
+ return TEST_SKIP;
}
+
+skip_mbuf_check:
err = test_spec_set_mtu(test, test->mtu);
if (err) {
ksft_print_msg("Error, could not set mtu.\n");
@@ -1852,14 +1856,14 @@ static int testapp_validate_traffic(struct test_spec *test)
struct ifobject *ifobj_rx = test->ifobj_rx;
struct ifobject *ifobj_tx = test->ifobj_tx;
- if ((ifobj_rx->xsk->umem->unaligned_mode && !ifobj_rx->unaligned_supp) ||
- (ifobj_tx->xsk->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) {
+ if (ifobj_rx->xsk->umem->unaligned_mode &&
+ !ifobj_has_cap(ifobj_rx, XSK_CAP_UNALIGNED)) {
ksft_print_msg("No huge pages present.\n");
return TEST_SKIP;
}
if (test->set_ring) {
- if (ifobj_tx->hw_ring_size_supp) {
+ if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING)) {
if (set_ring_size(ifobj_tx)) {
ksft_print_msg("Failed to change HW ring size.\n");
return TEST_FAILURE;
@@ -1965,7 +1969,7 @@ int testapp_headroom(struct test_spec *test)
int testapp_stats_rx_dropped(struct test_spec *test)
{
struct xsk_umem_info *umem = test->ifobj_rx->xsk->umem;
- u32 umem_tr = test->ifobj_tx->umem_tailroom;
+ u32 umem_tr = xsk_get_cap(test, umem_tailroom);
if (test->mode == TEST_MODE_ZC) {
ksft_print_msg("Can not run RX_DROPPED test for ZC mode\n");
@@ -2227,9 +2231,9 @@ int testapp_too_many_frags(struct test_spec *test)
int ret = TEST_FAILURE;
if (test->mode == TEST_MODE_ZC) {
- max_frags = test->ifobj_tx->xdp_zc_max_segs;
+ max_frags = xsk_get_cap(test, xdp_zc_max_segs);
} else {
- max_frags = test->ifobj_tx->max_skb_frags;
+ max_frags = xsk_get_cap(test, max_skb_frags);
max_frags += 1;
}
@@ -2289,7 +2293,6 @@ static int xsk_load_xdp_programs(struct ifobject *ifobj)
return 0;
}
-/* Simple test */
static bool hugepages_present(void)
{
size_t mmap_sz = 2 * DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE;
@@ -2305,21 +2308,13 @@ static bool hugepages_present(void)
return true;
}
-int init_iface(struct ifobject *ifobj, thread_func_t func_ptr)
+static int detect_ifobj_caps(struct ifobject *ifobj)
{
LIBBPF_OPTS(bpf_xdp_query_opts, query_opts);
int err;
- ifobj->func_ptr = func_ptr;
-
- err = xsk_load_xdp_programs(ifobj);
- if (err) {
- ksft_print_msg("Error loading XDP program\n");
- return err;
- }
-
if (hugepages_present())
- ifobj->unaligned_supp = true;
+ ifobj_set_cap(ifobj, XSK_CAP_UNALIGNED);
err = bpf_xdp_query(ifobj->ifindex, XDP_FLAGS_DRV_MODE, &query_opts);
if (err) {
@@ -2327,19 +2322,34 @@ int init_iface(struct ifobject *ifobj, thread_func_t func_ptr)
return err;
}
if (query_opts.feature_flags & NETDEV_XDP_ACT_RX_SG)
- ifobj->multi_buff_supp = true;
+ ifobj_set_cap(ifobj, XSK_CAP_MBUF);
if (query_opts.feature_flags & NETDEV_XDP_ACT_XSK_ZEROCOPY) {
if (query_opts.xdp_zc_max_segs > 1) {
- ifobj->multi_buff_zc_supp = true;
- ifobj->xdp_zc_max_segs = query_opts.xdp_zc_max_segs;
+ ifobj_set_cap(ifobj, XSK_CAP_MBUF_ZC);
+ ifobj->caps.xdp_zc_max_segs = query_opts.xdp_zc_max_segs;
} else {
- ifobj->xdp_zc_max_segs = 0;
+ ifobj->caps.xdp_zc_max_segs = 0;
}
}
return 0;
}
+int init_iface(struct ifobject *ifobj, thread_func_t func_ptr)
+{
+ int err;
+
+ ifobj->func_ptr = func_ptr;
+
+ err = xsk_load_xdp_programs(ifobj);
+ if (err) {
+ ksft_print_msg("Error loading XDP program\n");
+ return err;
+ }
+
+ return detect_ifobj_caps(ifobj);
+}
+
int testapp_send_receive(struct test_spec *test)
{
return testapp_validate_traffic(test);
@@ -2449,7 +2459,7 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test)
test->set_ring = true;
test->total_steps = 2;
- test->ifobj_tx->ring.tx_pending = test->ifobj_tx->ring.tx_max_pending;
+ test->ifobj_tx->ring.tx_pending = xsk_get_cap(test, tx_max_pending);
test->ifobj_tx->ring.rx_pending = test->ifobj_tx->ring.rx_max_pending;
test->ifobj_rx->xsk->umem->num_frames = max_descs;
test->ifobj_rx->xsk->umem->fill_size = max_descs;
@@ -2464,8 +2474,8 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test)
/* Set batch_size to 8152 for testing, as the ice HW ignores the 3 lowest bits when
* updating the Rx HW tail register.
*/
- test->ifobj_tx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8;
- test->ifobj_rx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8;
+ test->ifobj_tx->xsk->batch_size = xsk_get_cap(test, tx_max_pending) - 8;
+ test->ifobj_rx->xsk->batch_size = xsk_get_cap(test, tx_max_pending) - 8;
if (pkt_stream_replace(test, max_descs, MIN_PKT_SIZE)) {
clean_sockets(test, test->ifobj_tx);
clean_sockets(test, test->ifobj_rx);
@@ -2558,7 +2568,7 @@ int testapp_adjust_tail_grow_mb(struct test_spec *test)
grow_size = XSK_UMEM__MAX_FRAME_SIZE -
XSK_UMEM__LARGE_FRAME_SIZE -
XDP_PACKET_HEADROOM -
- test->ifobj_tx->umem_tailroom;
+ xsk_get_cap(test, umem_tailroom);
test->mtu = MAX_ETH_JUMBO_SIZE;
return testapp_adjust_tail(test, grow_size, XSK_UMEM__LARGE_FRAME_SIZE * 2);
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 0717d855e4df..1d17e214db91 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -119,8 +119,22 @@ struct set_hw_ring {
int hw_ring_size_reset(struct ifobject *ifobj);
+#define XSK_CAP_UNALIGNED (1U << 0)
+#define XSK_CAP_MBUF (1U << 1)
+#define XSK_CAP_MBUF_ZC (1U << 2)
+#define XSK_CAP_HW_RING (1U << 3)
+
+struct xsk_caps {
+ u32 flags;
+ u32 xdp_zc_max_segs;
+ u32 max_skb_frags;
+ u32 umem_tailroom;
+ u32 tx_max_pending;
+};
+
struct ifobject {
char ifname[MAX_INTERFACE_NAME_CHARS];
+ struct xsk_caps caps;
struct xsk_socket_info *xsk;
struct xsk_socket_info *xsk_arr;
thread_func_t func_ptr;
@@ -134,9 +148,6 @@ struct ifobject {
int ifindex;
int mtu;
u32 bind_flags;
- u32 xdp_zc_max_segs;
- u32 umem_tailroom;
- u32 max_skb_frags;
bool tx_on;
bool rx_on;
bool use_poll;
@@ -144,11 +155,20 @@ struct ifobject {
bool use_fill_ring;
bool release_rx;
bool use_metadata;
- bool unaligned_supp;
- bool multi_buff_supp;
- bool multi_buff_zc_supp;
- bool hw_ring_size_supp;
};
+
+static inline bool ifobj_has_cap(const struct ifobject *ifobj, u32 cap)
+{
+ return ifobj && (ifobj->caps.flags & cap);
+}
+
+static inline void ifobj_set_cap(struct ifobject *ifobj, u32 cap)
+{
+ ifobj->caps.flags |= cap;
+}
+
+#define xsk_get_cap(test, cap) ((test)->ifobj_tx->caps.cap)
+
struct ifobject *ifobject_create(void);
void ifobject_delete(struct ifobject *ifobj);
int init_iface(struct ifobject *ifobj, thread_func_t func_ptr);
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index f4a2f45a6752..8b3e4d18e982 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -361,15 +361,15 @@ int main(int argc, char **argv)
ksft_print_msg("Can't get MAX_SKB_FRAGS from system, using default (17)\n");
max_frags = 17;
}
- ifobj_tx->max_skb_frags = max_frags;
- ifobj_rx->max_skb_frags = max_frags;
+ ifobj_tx->caps.max_skb_frags = max_frags;
+ ifobj_rx->caps.max_skb_frags = max_frags;
/* 48 bytes is a part of skb_shared_info w/o frags array;
* 16 bytes is sizeof(skb_frag_t)
*/
umem_tailroom = ALIGN(48 + (max_frags * 16), cache_line_size);
- ifobj_tx->umem_tailroom = umem_tailroom;
- ifobj_rx->umem_tailroom = umem_tailroom;
+ ifobj_tx->caps.umem_tailroom = umem_tailroom;
+ ifobj_rx->caps.umem_tailroom = umem_tailroom;
parse_command_line(ifobj_tx, ifobj_rx, argc, argv);
@@ -393,7 +393,8 @@ int main(int argc, char **argv)
ret = get_hw_ring_size(ifobj_tx->ifname, &ifobj_tx->ring);
if (!ret) {
- ifobj_tx->hw_ring_size_supp = true;
+ ifobj_set_cap(ifobj_tx, XSK_CAP_HW_RING);
+ ifobj_tx->caps.tx_max_pending = ifobj_tx->ring.tx_max_pending;
ifobj_tx->set_ring.default_tx = ifobj_tx->ring.tx_pending;
ifobj_tx->set_ring.default_rx = ifobj_tx->ring.rx_pending;
}
@@ -448,7 +449,7 @@ int main(int argc, char **argv)
}
}
- if (ifobj_tx->hw_ring_size_supp)
+ if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING))
hw_ring_size_reset(ifobj_tx);
pkt_stream_delete(tx_pkt_stream_default);
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (5 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 06/14] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
` (7 subsequent siblings)
14 siblings, 1 reply; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Prepare xskxceiver for running a single endpoint per process by
untangling main():
- bind_iface() resolves and initializes one interface from its -i
argument, so the interface list can be assigned to roles after
parsing; -l lists tests and exits without needing interfaces
- detect_mode_caps() records DRV and zero-copy support and
detect_ifobj_caps() HW ring support as XSK_CAP flags, and
mode_supported() tells from them whether a mode can run
- run_pkt_test() returns the result, and main() counts failures from
it rather than from test->fail, which not every failing path sets
- cleanup_iface() restores rings, detaches and unloads per interface
and is safe on partially initialized objects, as is
pkt_stream_delete(NULL), so main() has a single exit path
Call ksft_print_header() before ksft_set_plan(): xskxceiver never did,
and that is where kselftest.h switches stdout to line buffering, so a
launcher reading through a pipe only saw the output at exit.
While at it, exit with 0 for -l instead of KSFT_XPASS: listing the
tests is not a test result, so scripts should be able to run it like
any other command.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 10 +
.../testing/selftests/net/lib/xsk/test_xsk.h | 2 +
.../selftests/net/lib/xsk/xskxceiver.c | 184 +++++++++---------
3 files changed, 107 insertions(+), 89 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 22a81723d5de..70a61fb511c8 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -369,6 +369,9 @@ static struct pkt *pkt_stream_get_next_rx_pkt(struct pkt_stream *pkt_stream, u32
void pkt_stream_delete(struct pkt_stream *pkt_stream)
{
+ if (!pkt_stream)
+ return;
+
free(pkt_stream->pkts);
free(pkt_stream);
}
@@ -2332,6 +2335,13 @@ static int detect_ifobj_caps(struct ifobject *ifobj)
}
}
+ if (!get_hw_ring_size(ifobj->ifname, &ifobj->ring)) {
+ ifobj_set_cap(ifobj, XSK_CAP_HW_RING);
+ ifobj->caps.tx_max_pending = ifobj->ring.tx_max_pending;
+ ifobj->set_ring.default_tx = ifobj->ring.tx_pending;
+ ifobj->set_ring.default_rx = ifobj->ring.rx_pending;
+ }
+
return 0;
}
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 1d17e214db91..861a2ee3b8e1 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -123,6 +123,8 @@ int hw_ring_size_reset(struct ifobject *ifobj);
#define XSK_CAP_MBUF (1U << 1)
#define XSK_CAP_MBUF_ZC (1U << 2)
#define XSK_CAP_HW_RING (1U << 3)
+#define XSK_CAP_DRV (1U << 4)
+#define XSK_CAP_ZC (1U << 5)
struct xsk_caps {
u32 flags;
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 8b3e4d18e982..afd6b242aa0d 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -81,7 +81,6 @@
#include <linux/netdev.h>
#include <linux/ethtool.h>
#include <linux/align.h>
-#include <linux/kernel.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <locale.h>
@@ -101,7 +100,6 @@
#include "kselftest.h"
#include "xsk_xdp_common.h"
-static bool opt_print_tests;
static enum test_mode opt_mode = TEST_MODE_ALL;
static u32 opt_run_test = RUN_ALL_TESTS;
@@ -185,17 +183,40 @@ static void print_usage(char **argv)
ksft_exit_xfail();
}
-static bool validate_interface(struct ifobject *ifobj)
+static void bind_iface(struct ifobject *ifobj, const char *ifname, thread_func_t func,
+ char **argv)
{
- if (!strcmp(ifobj->ifname, ""))
- return false;
- return true;
+ size_t len = ifname ? strlen(ifname) : 0;
+
+ if (!len || len >= sizeof(ifobj->ifname))
+ print_usage(argv);
+
+ memcpy(ifobj->ifname, ifname, len + 1);
+ ifobj->ifindex = if_nametoindex(ifobj->ifname);
+ if (!ifobj->ifindex) {
+ ksft_print_msg("Error: cannot resolve interface %s\n", ifobj->ifname);
+ ksft_exit_fail();
+ }
+
+ if (init_iface(ifobj, func)) {
+ ksft_print_msg("Error: cannot initialize interface %s\n", ifobj->ifname);
+ ksft_exit_fail();
+ }
+}
+
+static void print_tests(void)
+{
+ u32 i;
+
+ printf("Tests:\n");
+ for (i = 0; i < ARRAY_SIZE(tests); i++)
+ printf("%u: %s\n", i, tests[i].name);
}
static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx, int argc,
char **argv)
{
- struct ifobject *ifobj;
+ const char *ifname[2] = {};
u32 interface_nb = 0;
int option_index, c;
@@ -208,21 +229,10 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
switch (c) {
case 'i':
- if (interface_nb == 0)
- ifobj = ifobj_tx;
- else if (interface_nb == 1)
- ifobj = ifobj_rx;
- else
+ if (interface_nb >= ARRAY_SIZE(ifname))
break;
- memcpy(ifobj->ifname, optarg,
- min_t(size_t, MAX_INTERFACE_NAME_CHARS, strlen(optarg)));
-
- ifobj->ifindex = if_nametoindex(ifobj->ifname);
- if (!ifobj->ifindex)
- exit_with_error(errno);
-
- interface_nb++;
+ ifname[interface_nb++] = optarg;
break;
case 'v':
opt_verbose = true;
@@ -242,7 +252,8 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
print_usage(argv);
break;
case 'l':
- opt_print_tests = true;
+ print_tests();
+ ksft_exit_pass();
break;
case 't':
errno = 0;
@@ -255,6 +266,9 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
print_usage(argv);
}
}
+
+ bind_iface(ifobj_tx, ifname[0], worker_testapp_validate_tx, argv);
+ bind_iface(ifobj_rx, ifname[1], worker_testapp_validate_rx, argv);
}
static void xsk_unload_xdp_programs(struct ifobject *ifobj)
@@ -262,7 +276,7 @@ static void xsk_unload_xdp_programs(struct ifobject *ifobj)
xsk_xdp_progs__destroy(ifobj->xdp_progs);
}
-static void run_pkt_test(struct test_spec *test)
+static int run_pkt_test(struct test_spec *test)
{
int ret;
@@ -287,6 +301,7 @@ static void run_pkt_test(struct test_spec *test)
}
pkt_stream_restore_default(test);
+ return ret;
}
static bool is_xdp_supported(int ifindex)
@@ -317,26 +332,46 @@ static bool is_xdp_supported(int ifindex)
return true;
}
-static void print_tests(void)
+static u32 detect_mode_caps(struct ifobject *ifobj)
{
- u32 i;
+ if (is_xdp_supported(ifobj->ifindex)) {
+ ifobj_set_cap(ifobj, XSK_CAP_DRV);
+ if (ifobj_zc_avail(ifobj))
+ ifobj_set_cap(ifobj, XSK_CAP_ZC);
+ }
- printf("Tests:\n");
- for (i = 0; i < ARRAY_SIZE(tests); i++)
- printf("%u: %s\n", i, tests[i].name);
+ return ifobj->caps.flags;
+}
+
+static bool mode_supported(enum test_mode mode, u32 caps)
+{
+ if (mode == TEST_MODE_SKB)
+ return true;
+ if (mode == TEST_MODE_DRV)
+ return caps & XSK_CAP_DRV;
+ return caps & XSK_CAP_ZC;
+}
+
+static void cleanup_iface(struct ifobject *ifobj)
+{
+ if (ifobj_has_cap(ifobj, XSK_CAP_HW_RING))
+ hw_ring_size_reset(ifobj);
+ if (ifobj->xdp_prog)
+ xsk_detach_xdp_program(ifobj->ifindex,
+ ifobj->mode == TEST_MODE_SKB ?
+ XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE);
+ xsk_unload_xdp_programs(ifobj);
}
int main(int argc, char **argv)
{
u32 cache_line_size, max_frags, umem_tailroom;
const size_t total_tests = ARRAY_SIZE(tests);
- struct pkt_stream *rx_pkt_stream_default;
- struct pkt_stream *tx_pkt_stream_default;
struct ifobject *ifobj_tx, *ifobj_rx;
u32 i, j, failed_tests = 0, nb_tests;
- int modes = TEST_MODE_SKB + 1;
- struct test_spec test;
- int ret;
+ struct test_spec test = {};
+ int ret = TEST_FAILURE;
+ u32 caps, modes = 0;
/* Use libbpf 1.0 API mode */
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
@@ -373,93 +408,64 @@ int main(int argc, char **argv)
parse_command_line(ifobj_tx, ifobj_rx, argc, argv);
- if (opt_print_tests) {
- print_tests();
- ksft_exit_xpass();
- }
if (opt_run_test != RUN_ALL_TESTS && opt_run_test >= total_tests) {
ksft_print_msg("Error: test %u does not exist.\n", opt_run_test);
ksft_exit_xfail();
}
- if (!validate_interface(ifobj_tx) || !validate_interface(ifobj_rx))
- print_usage(argv);
-
- if (is_xdp_supported(ifobj_tx->ifindex)) {
- modes++;
- if (ifobj_zc_avail(ifobj_tx))
- modes++;
- }
+ test.tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
+ test.rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
+ if (!test.tx_pkt_stream_default || !test.rx_pkt_stream_default)
+ goto out;
- ret = get_hw_ring_size(ifobj_tx->ifname, &ifobj_tx->ring);
- if (!ret) {
- ifobj_set_cap(ifobj_tx, XSK_CAP_HW_RING);
- ifobj_tx->caps.tx_max_pending = ifobj_tx->ring.tx_max_pending;
- ifobj_tx->set_ring.default_tx = ifobj_tx->ring.tx_pending;
- ifobj_tx->set_ring.default_rx = ifobj_tx->ring.rx_pending;
- }
+ caps = detect_mode_caps(ifobj_tx);
- if (init_iface(ifobj_rx, worker_testapp_validate_rx) ||
- init_iface(ifobj_tx, worker_testapp_validate_tx)) {
- ksft_print_msg("Error : can't initialize interfaces\n");
+ if (opt_mode != TEST_MODE_ALL && !mode_supported(opt_mode, caps)) {
+ if (opt_mode == TEST_MODE_DRV)
+ ksft_print_msg("Error: XDP_DRV mode not supported.\n");
+ else
+ ksft_print_msg("Error: zero-copy mode not supported.\n");
ksft_exit_xfail();
}
- test_init(&test, ifobj_tx, ifobj_rx, 0, &tests[0]);
- tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
- rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
- if (!tx_pkt_stream_default || !rx_pkt_stream_default)
- exit_with_error(ENOMEM);
- test.tx_pkt_stream_default = tx_pkt_stream_default;
- test.rx_pkt_stream_default = rx_pkt_stream_default;
-
+ for (i = TEST_MODE_SKB; i <= TEST_MODE_ZC; i++)
+ if ((opt_mode == TEST_MODE_ALL || i == opt_mode) && mode_supported(i, caps))
+ modes++;
if (opt_run_test == RUN_ALL_TESTS)
nb_tests = total_tests;
else
nb_tests = 1;
- if (opt_mode == TEST_MODE_ALL) {
- ksft_set_plan(modes * nb_tests);
- } else {
- if (opt_mode == TEST_MODE_DRV && modes <= TEST_MODE_DRV) {
- ksft_print_msg("Error: XDP_DRV mode not supported.\n");
- ksft_exit_xfail();
- }
- if (opt_mode == TEST_MODE_ZC && modes <= TEST_MODE_ZC) {
- ksft_print_msg("Error: zero-copy mode not supported.\n");
- ksft_exit_xfail();
- }
-
- ksft_set_plan(nb_tests);
- }
+ /* Line-buffer stdout so verdicts reach a capturing launcher live. */
+ ksft_print_header();
+ ksft_set_plan(modes * nb_tests);
- for (i = 0; i < modes; i++) {
+ for (i = TEST_MODE_SKB; i <= TEST_MODE_ZC; i++) {
if (opt_mode != TEST_MODE_ALL && i != opt_mode)
continue;
+ if (!mode_supported(i, caps))
+ continue;
for (j = 0; j < total_tests; j++) {
if (opt_run_test != RUN_ALL_TESTS && j != opt_run_test)
continue;
test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]);
- run_pkt_test(&test);
- usleep(USLEEP_MAX);
-
- if (test.fail)
+ if (run_pkt_test(&test) == TEST_FAILURE)
failed_tests++;
+ usleep(USLEEP_MAX);
}
}
+ ret = failed_tests ? TEST_FAILURE : TEST_PASS;
- if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING))
- hw_ring_size_reset(ifobj_tx);
-
- pkt_stream_delete(tx_pkt_stream_default);
- pkt_stream_delete(rx_pkt_stream_default);
- xsk_unload_xdp_programs(ifobj_tx);
- xsk_unload_xdp_programs(ifobj_rx);
+out:
+ cleanup_iface(ifobj_tx);
+ cleanup_iface(ifobj_rx);
+ pkt_stream_delete(test.tx_pkt_stream_default);
+ pkt_stream_delete(test.rx_pkt_stream_default);
ifobject_delete(ifobj_tx);
ifobject_delete(ifobj_rx);
- if (failed_tests)
+ if (ret)
ksft_exit_fail();
else
ksft_exit_pass();
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (6 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-09 11:25 ` Björn Töpel
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
` (6 subsequent siblings)
14 siblings, 2 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Make test_xsk.sh own the mode x case matrix and run xskxceiver once per
case, so that every case starts from a fresh process and a case that
crashes fails only itself. A single case is also the unit that the next
patch splits into separate RX and TX endpoint processes.
xskxceiver now requires -m and -t and runs exactly that case. It
reports one KTAP result and exits with its verdict. An unsupported mode
skips the case instead of exiting with XFAIL.
test_xsk.sh runs every case in the skb and drv modes, or in the one
given with -m, in both the softirq and the busy-poll pass, and prints a
summary of the passed, skipped and failed cases. -t also accepts a test
name, which is resolved through xskxceiver -l. Only skb and drv modes
are accepted, as veth has no zero-copy support, and the script skips
when xskxceiver was not built. As ARGS is now rebuilt for every case,
exec_xskxceiver() adds -b to a local copy of it.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../selftests/net/lib/xsk/xskxceiver.c | 52 ++++-----
tools/testing/selftests/net/test_xsk.sh | 102 ++++++++++++++----
tools/testing/selftests/net/xsk_prereqs.sh | 6 +-
3 files changed, 103 insertions(+), 57 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index afd6b242aa0d..b8a52846eaf8 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -169,7 +169,7 @@ static struct option long_options[] = {
static void print_usage(char **argv)
{
const char *str =
- " Usage: xskxceiver [OPTIONS]\n"
+ " Usage: xskxceiver -i TX_IFACE -i RX_IFACE -m MODE -t TEST [OPTIONS]\n"
" Options:\n"
" -i, --interface Use interface\n"
" -v, --verbose Verbose output\n"
@@ -267,6 +267,9 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
}
}
+ if (opt_run_test == RUN_ALL_TESTS || opt_mode == TEST_MODE_ALL)
+ print_usage(argv);
+
bind_iface(ifobj_tx, ifname[0], worker_testapp_validate_tx, argv);
bind_iface(ifobj_rx, ifname[1], worker_testapp_validate_rx, argv);
}
@@ -368,10 +371,9 @@ int main(int argc, char **argv)
u32 cache_line_size, max_frags, umem_tailroom;
const size_t total_tests = ARRAY_SIZE(tests);
struct ifobject *ifobj_tx, *ifobj_rx;
- u32 i, j, failed_tests = 0, nb_tests;
struct test_spec test = {};
int ret = TEST_FAILURE;
- u32 caps, modes = 0;
+ u32 caps;
/* Use libbpf 1.0 API mode */
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
@@ -420,42 +422,20 @@ int main(int argc, char **argv)
caps = detect_mode_caps(ifobj_tx);
- if (opt_mode != TEST_MODE_ALL && !mode_supported(opt_mode, caps)) {
+ if (!mode_supported(opt_mode, caps)) {
if (opt_mode == TEST_MODE_DRV)
- ksft_print_msg("Error: XDP_DRV mode not supported.\n");
+ ksft_print_msg("XDP_DRV mode not supported.\n");
else
- ksft_print_msg("Error: zero-copy mode not supported.\n");
- ksft_exit_xfail();
+ ksft_print_msg("zero-copy mode not supported.\n");
+ ret = TEST_SKIP;
+ goto out;
}
- for (i = TEST_MODE_SKB; i <= TEST_MODE_ZC; i++)
- if ((opt_mode == TEST_MODE_ALL || i == opt_mode) && mode_supported(i, caps))
- modes++;
- if (opt_run_test == RUN_ALL_TESTS)
- nb_tests = total_tests;
- else
- nb_tests = 1;
/* Line-buffer stdout so verdicts reach a capturing launcher live. */
ksft_print_header();
- ksft_set_plan(modes * nb_tests);
-
- for (i = TEST_MODE_SKB; i <= TEST_MODE_ZC; i++) {
- if (opt_mode != TEST_MODE_ALL && i != opt_mode)
- continue;
- if (!mode_supported(i, caps))
- continue;
-
- for (j = 0; j < total_tests; j++) {
- if (opt_run_test != RUN_ALL_TESTS && j != opt_run_test)
- continue;
-
- test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]);
- if (run_pkt_test(&test) == TEST_FAILURE)
- failed_tests++;
- usleep(USLEEP_MAX);
- }
- }
- ret = failed_tests ? TEST_FAILURE : TEST_PASS;
+ ksft_set_plan(1);
+ test_init(&test, ifobj_tx, ifobj_rx, opt_mode, &tests[opt_run_test]);
+ ret = run_pkt_test(&test);
out:
cleanup_iface(ifobj_tx);
@@ -465,6 +445,12 @@ int main(int argc, char **argv)
ifobject_delete(ifobj_tx);
ifobject_delete(ifobj_rx);
+ if (ret == TEST_SKIP && ksft_test_num()) {
+ ksft_print_cnts();
+ return KSFT_SKIP;
+ }
+ if (ret == TEST_SKIP)
+ ksft_exit_skip("mode not supported\n");
if (ret)
ksft_exit_fail();
else
diff --git a/tools/testing/selftests/net/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
index 69fac65f073c..e476556eb05b 100755
--- a/tools/testing/selftests/net/test_xsk.sh
+++ b/tools/testing/selftests/net/test_xsk.sh
@@ -71,7 +71,7 @@
# Set up veth interfaces and leave them up so xskxceiver can be launched in a debugger:
# sudo ./test_xsk.sh -d
#
-# Run test suite in a specific mode only [skb,drv,zc]
+# Run test suite in a specific mode only [skb,drv]
# sudo ./test_xsk.sh -m MODE
#
# List available tests
@@ -85,6 +85,11 @@
. xsk_prereqs.sh
+if [ ! -x "./${XSKOBJ}" ]; then
+ echo "xskxceiver was not built; skipping"
+ exit $ksft_skip
+fi
+
while getopts "vdm:lt:h" flag
do
case "${flag}" in
@@ -151,6 +156,31 @@ if [[ $help -eq 1 ]]; then
exit
fi
+if [ -n "$MODE" ]; then
+ case "$MODE" in
+ skb|drv) MODES=("$MODE");;
+ *) echo "Unsupported veth mode: $MODE (expected skb or drv)" >&2
+ exit 1;;
+ esac
+else
+ MODES=(skb drv)
+fi
+
+if [ -n "$TEST" ]; then
+ if [[ "$TEST" =~ ^[0-9]+$ ]]; then
+ CASES=("$TEST")
+ else
+ mapfile -t CASES < <(./${XSKOBJ} -l |
+ awk -F ': ' -v name="$TEST" '$2 == name {print $1}')
+ fi
+else
+ mapfile -t CASES < <(./${XSKOBJ} -l | awk -F ': ' '/^[0-9]+: / {print $1}')
+fi
+if [ ${#CASES[@]} -eq 0 ]; then
+ echo "Unknown AF_XDP test: $TEST" >&2
+ exit 1
+fi
+
validate_root_exec
validate_veth_support ${VETH0}
validate_ip_utility
@@ -168,29 +198,38 @@ if [[ $verbose -eq 1 ]]; then
ARGS+="-v "
fi
-if [ -n "$MODE" ]; then
- ARGS+="-m ${MODE} "
-fi
-
-if [ -n "$TEST" ]; then
- ARGS+="-t ${TEST} "
-fi
-
retval=$?
test_status $retval "${TEST_NAME}"
## START TESTS
statusList=()
+nameList=()
+
+run_matrix()
+{
+ local mode case_id
+
+ for mode in "${MODES[@]}"; do
+ for case_id in "${CASES[@]}"; do
+ ARGS="${BASE_ARGS} -m ${mode} -t ${case_id}"
+ TEST_NAME="XSK_${mode}_${case_id}_${RUN_VARIANT}_${VETH0}"
+ exec_xskxceiver
+ done
+ done
+}
+
+BASE_ARGS="${ARGS}"
-TEST_NAME="XSK_SELFTESTS_${VETH0}_SOFTIRQ"
+RUN_VARIANT=SOFTIRQ
if [[ $debug -eq 1 ]]; then
- echo "-i" ${VETH0} "-i" ${VETH1}
+ ARGS="${BASE_ARGS} -m ${MODES[0]} -t ${CASES[0]}"
+ echo "./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}"
exit
fi
-exec_xskxceiver
+run_matrix
cleanup_exit ${VETH0} ${VETH1}
@@ -198,28 +237,47 @@ if [[ $list -eq 1 ]]; then
exit
fi
-TEST_NAME="XSK_SELFTESTS_${VETH0}_BUSY_POLL"
+RUN_VARIANT=BUSY_POLL
busy_poll=1
setup_vethPairs
-exec_xskxceiver
+run_matrix
## END TESTS
cleanup_exit ${VETH0} ${VETH1}
+passes=0
+skips=0
failures=0
-echo -e "\nSummary:"
+failed_tests=()
for i in "${!statusList[@]}"
do
- if [ ${statusList[$i]} -ne 0 ]; then
- test_status ${statusList[$i]} ${nameList[$i]}
- failures=1
- fi
+ case ${statusList[$i]} in
+ $ksft_pass)
+ passes=$((passes + 1))
+ ;;
+ $ksft_skip)
+ skips=$((skips + 1))
+ ;;
+ *)
+ failures=$((failures + 1))
+ failed_tests+=("${nameList[$i]}")
+ ;;
+ esac
done
-if [ $failures -eq 0 ]; then
- echo "All tests successful!"
-else
+echo
+echo "Summary:"
+printf " Tests: %d\n" "${#statusList[@]}"
+printf " Passed: %d\n" "$passes"
+printf " Skipped: %d\n" "$skips"
+printf " Failed: %d\n" "$failures"
+
+if [ $failures -ne 0 ]; then
+ echo "Failed tests:"
+ for TEST_NAME in "${failed_tests[@]}"; do
+ echo " $TEST_NAME"
+ done
exit 1
fi
diff --git a/tools/testing/selftests/net/xsk_prereqs.sh b/tools/testing/selftests/net/xsk_prereqs.sh
index 5e5c8ef3fff2..30173db12c56 100755
--- a/tools/testing/selftests/net/xsk_prereqs.sh
+++ b/tools/testing/selftests/net/xsk_prereqs.sh
@@ -71,11 +71,13 @@ validate_ip_utility()
exec_xskxceiver()
{
+ local run_args="${ARGS}"
+
if [[ $busy_poll -eq 1 ]]; then
- ARGS+="-b "
+ run_args+=" -b"
fi
- ./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}
+ ./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${run_args}
retval=$?
if [[ $list -ne 1 ]]; then
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (7 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
` (5 subsequent siblings)
14 siblings, 1 reply; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Run the two endpoints of a test case in separate xskxceiver processes,
so that they no longer have to share a process or a host. A process
binds only the interface given with -i, takes its role from -e rx|tx
and meets the other endpoint over a small TCP control channel (-p HOST
-P PORT). The other side is represented by a shadow ifobject that
mirrors the local capabilities and holds an unattached copy of the XDP
skeleton, so the shared test code keeps addressing both sides while
only the local one is programmed.
The pthreads, the barrier and test->fail go away. Each process calls the
worker of its local ifobject directly, and the endpoints exchange three
fixed-size messages instead:
- READY is exchanged before each step and works as a barrier; TCP
ordering puts all progress of the previous step before it
- PROGRESS carries the number of packets RX consumed, which TX
subtracts from its in-flight count so that it does not overrun the
RX UMEM
- ABORT tells the other endpoint to stop waiting after a failure
The TX endpoint listens and RX connects. Test selection, interface
capabilities and verdicts stay with the launcher and are not exchanged
between the endpoints.
For each case, test_xsk.sh starts the TX endpoint in the background,
listening on a control port on 127.0.0.1 (random unless -p is given),
runs the RX endpoint once TX listens and prints the TX log when TX
fails or with -v.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
tools/testing/selftests/net/lib/Makefile | 2 +-
.../testing/selftests/net/lib/xsk/test_xsk.c | 205 +++++++------
.../testing/selftests/net/lib/xsk/test_xsk.h | 23 +-
.../testing/selftests/net/lib/xsk/xsk_peer.c | 275 ++++++++++++++++++
.../testing/selftests/net/lib/xsk/xsk_peer.h | 19 ++
.../selftests/net/lib/xsk/xskxceiver.c | 121 ++++++--
tools/testing/selftests/net/test_xsk.sh | 49 ++--
tools/testing/selftests/net/xsk_prereqs.sh | 53 +++-
8 files changed, 609 insertions(+), 138 deletions(-)
create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.c
create mode 100644 tools/testing/selftests/net/lib/xsk/xsk_peer.h
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index a542757e2957..066d011c478a 100644
--- a/tools/testing/selftests/net/lib/Makefile
+++ b/tools/testing/selftests/net/lib/Makefile
@@ -38,7 +38,7 @@ include ../bpf.mk
XSK_DIR := xsk
XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
-XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c) \
+XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c xsk_peer.c) \
$(top_srcdir)/tools/lib/find_bit.c
XSK_HEADERS := $(wildcard $(XSK_DIR)/*.h)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 70a61fb511c8..8d30c39c94c8 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -7,7 +7,6 @@
#include <linux/mman.h>
#include <linux/netdev.h>
#include <poll.h>
-#include <pthread.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
@@ -15,6 +14,7 @@
#include <unistd.h>
#include "test_xsk.h"
+#include "xsk_peer.h"
#include "xsk_xdp_common.h"
#include "xsk_xdp_progs.skel.h"
@@ -38,10 +38,37 @@
static const u8 g_mac[ETH_ALEN] = {0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
bool opt_verbose;
-pthread_barrier_t barr;
-pthread_mutex_t pacing_mutex = PTHREAD_MUTEX_INITIALIZER;
int pkts_in_flight;
+static struct xsk_peer *ctrl_peer;
+
+void xsk_set_endpoint(struct xsk_peer *peer)
+{
+ ctrl_peer = peer;
+}
+
+static int pacing_rx_progress(u32 pkts)
+{
+ int err = xsk_peer_rx_progress(ctrl_peer, pkts);
+
+ if (err)
+ ksft_print_msg("RX control channel failed: %d (%s)\n", err,
+ strerror(-err));
+ return err;
+}
+
+static int pacing_tx_sync(void)
+{
+ int acked = xsk_peer_tx_sync(ctrl_peer);
+
+ if (acked < 0) {
+ ksft_print_msg("TX control channel failed: %d (%s)\n", acked,
+ strerror(-acked));
+ return acked;
+ }
+ pkts_in_flight -= acked;
+ return 0;
+}
/* The payload is a word consisting of a packet sequence number in the upper
* 16-bits and a intra packet data sequence number in the lower 16 bits. So the 3rd packet's
@@ -244,7 +271,8 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
ifobj->xsk->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
}
- if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING))
+ if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING) &&
+ ifobj_is_local(ifobj_tx))
hw_ring_size_reset(ifobj_tx);
test->ifobj_tx = ifobj_tx;
@@ -252,7 +280,6 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
test->current_step = 0;
test->total_steps = 1;
test->nb_sockets = 1;
- test->fail = false;
test->set_ring = false;
test->adjust_tail = false;
test->adjust_tail_support = false;
@@ -320,26 +347,31 @@ static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *x
test->xskmap_tx = xskmap_tx;
}
-static int test_spec_set_mtu(struct test_spec *test, int mtu)
+static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, int mtu)
{
int err;
- if (test->ifobj_rx->mtu != mtu) {
- err = xsk_set_mtu(test->ifobj_rx->ifindex, mtu);
+ if (ifobj_is_local(ifobj) && ifobj->mtu != mtu) {
+ err = xsk_set_mtu(ifobj->ifindex, mtu);
if (err)
return err;
- test->ifobj_rx->mtu = mtu;
- }
- if (test->ifobj_tx->mtu != mtu) {
- err = xsk_set_mtu(test->ifobj_tx->ifindex, mtu);
- if (err)
- return err;
- test->ifobj_tx->mtu = mtu;
}
+ ifobj->mtu = mtu;
return 0;
}
+static int test_spec_set_mtu(struct test_spec *test, int mtu)
+{
+ int err;
+
+ err = test_spec_set_mtu_ifobj(test->ifobj_rx, mtu);
+ if (err)
+ return err;
+
+ return test_spec_set_mtu_ifobj(test->ifobj_tx, mtu);
+}
+
void pkt_stream_reset(struct pkt_stream *pkt_stream)
{
if (pkt_stream) {
@@ -989,6 +1021,9 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
fds.fd = xsk_socket__fd(xsk->xsk);
fds.events = POLLIN;
+ if (pacing_rx_progress(0))
+ return TEST_FAILURE;
+
ret = kick_rx(xsk);
if (ret)
return TEST_FAILURE;
@@ -1088,9 +1123,8 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
if (ifobj->release_rx)
xsk_ring_cons__release(&xsk->rx, frags_processed);
- pthread_mutex_lock(&pacing_mutex);
- pkts_in_flight -= pkts_sent;
- pthread_mutex_unlock(&pacing_mutex);
+ if (pacing_rx_progress(pkts_sent))
+ return TEST_FAILURE;
pkts_sent = 0;
return TEST_CONTINUE;
@@ -1166,9 +1200,18 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
int ret;
buffer_len = pkt_get_buffer_len(umem, pkt_stream->max_pkt_len);
+ if (pacing_tx_sync())
+ return TEST_FAILURE;
+
/* pkts_in_flight might be negative if many invalid packets are sent */
if (pkts_in_flight >= (int)((umem_size(umem) - xsk->batch_size * buffer_len) /
buffer_len) && !test_timeout) {
+ /* Only RX progress reports can lower pkts_in_flight. */
+ if (xsk_peer_closed(ctrl_peer)) {
+ ksft_print_msg("ERROR: [%s] RX exited with %d packets in flight\n",
+ __func__, pkts_in_flight);
+ return TEST_FAILURE;
+ }
ret = kick_tx(xsk);
if (ret)
return TEST_FAILURE;
@@ -1250,9 +1293,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
valid_frags += nb_frags;
}
- pthread_mutex_lock(&pacing_mutex);
pkts_in_flight += valid_pkts;
- pthread_mutex_unlock(&pacing_mutex);
xsk_ring_prod__submit(&xsk->tx, i);
xsk->outstanding_tx += valid_frags;
@@ -1329,9 +1370,6 @@ static int send_pkts(struct test_spec *test, struct ifobject *ifobject)
if (ret != TEST_CONTINUE)
return ret;
- if (test->fail)
- return TEST_FAILURE;
-
if (!test->poll_tmout) {
ret = wait_for_tx_completion(&ifobject->xsk_arr[i]);
if (ret)
@@ -1653,36 +1691,57 @@ static int testapp_validate_rx_endpoint(struct test_spec *test,
return err;
}
-void *worker_testapp_validate_tx(void *arg)
+static int sync_endpoints(struct test_spec *test)
+{
+ int err = xsk_peer_ready(ctrl_peer);
+
+ if (err)
+ ksft_print_msg("Endpoint synchronization failed at step %u: %d (%s)\n",
+ test->current_step, err, strerror(-err));
+ return err;
+}
+
+int worker_testapp_validate_tx(struct test_spec *test)
{
- struct test_spec *test = (struct test_spec *)arg;
int err;
- err = testapp_validate_tx_endpoint(test, test->ifobj_tx);
+ err = sync_endpoints(test);
if (err)
- test->fail = true;
+ return err;
- pthread_exit(NULL);
+ err = testapp_validate_tx_endpoint(test, test->ifobj_tx);
+ if (err) {
+ ksft_print_msg("TX endpoint traffic failed at step %u: %d\n",
+ test->current_step, err);
+ xsk_peer_abort(ctrl_peer);
+ }
+ return err;
}
-void *worker_testapp_validate_rx(void *arg)
+int worker_testapp_validate_rx(struct test_spec *test)
{
- struct test_spec *test = (struct test_spec *)arg;
struct ifobject *ifobject = test->ifobj_rx;
int err;
err = testapp_prepare_rx_endpoint(test, ifobject);
+ if (err) {
+ ksft_print_msg("RX endpoint setup failed at step %u: %d\n",
+ test->current_step, err);
+ xsk_peer_abort(ctrl_peer);
+ return err;
+ }
- if (test->use_barrier)
- pthread_barrier_wait(&barr);
-
- /* We leave only now in case of error to avoid getting stuck in the barrier */
- if (!err)
- err = testapp_validate_rx_endpoint(test, ifobject);
+ err = sync_endpoints(test);
if (err)
- test->fail = true;
+ return err;
- pthread_exit(NULL);
+ err = testapp_validate_rx_endpoint(test, ifobject);
+ if (err) {
+ ksft_print_msg("RX endpoint traffic failed at step %u: %d\n",
+ test->current_step, err);
+ xsk_peer_abort(ctrl_peer);
+ }
+ return err;
}
static void testapp_clean_xsk_umem(struct ifobject *ifobj)
@@ -1737,13 +1796,13 @@ static int xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_r
{
int err = 0;
- if (xdp_prog_changed_rx(test)) {
+ if (ifobj_is_local(ifobj_rx) && xdp_prog_changed_rx(test)) {
err = xsk_reattach_xdp(ifobj_rx, test->xdp_prog_rx, test->xskmap_rx, test->mode);
if (err)
return err;
}
- if (!ifobj_tx)
+ if (!ifobj_tx || !ifobj_is_local(ifobj_tx))
return 0;
if (xdp_prog_changed_tx(test))
@@ -1756,7 +1815,7 @@ static void clean_sockets(struct test_spec *test, struct ifobject *ifobj)
{
u32 i;
- if (!ifobj || !test)
+ if (!ifobj || !test || !ifobj_is_local(ifobj))
return;
for (i = 0; i < test->nb_sockets; i++)
@@ -1768,15 +1827,15 @@ static void clean_umem(struct test_spec *test, struct ifobject *ifobj1, struct i
if (!ifobj1)
return;
- testapp_clean_xsk_umem(ifobj1);
- if (ifobj2)
+ if (ifobj_is_local(ifobj1))
+ testapp_clean_xsk_umem(ifobj1);
+ if (ifobj2 && ifobj_is_local(ifobj2))
testapp_clean_xsk_umem(ifobj2);
}
static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *ifobj1,
struct ifobject *ifobj2)
{
- pthread_t t0, t1;
u32 mbuf_cap;
int err;
@@ -1807,51 +1866,27 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i
err, strerror(-err));
return TEST_FAILURE;
}
- test->use_barrier = !!ifobj2;
-
- if (test->use_barrier) {
- if (pthread_barrier_init(&barr, NULL, 2))
- return TEST_FAILURE;
+ if (ifobj2)
pkt_stream_reset(ifobj2->xsk->pkt_stream);
- }
-
test->current_step++;
pkt_stream_reset(ifobj1->xsk->pkt_stream);
pkts_in_flight = 0;
- /*Spawn RX thread */
- pthread_create(&t0, NULL, ifobj1->func_ptr, test);
-
- if (test->use_barrier) {
- pthread_barrier_wait(&barr);
- if (pthread_barrier_destroy(&barr)) {
- test->use_barrier = false;
- pthread_join(t0, NULL);
- clean_sockets(test, ifobj1);
- clean_umem(test, ifobj1, NULL);
- return TEST_FAILURE;
- }
- }
-
- if (ifobj2) {
- /*Spawn TX thread */
- pthread_create(&t1, NULL, ifobj2->func_ptr, test);
- pthread_join(t1, NULL);
- }
-
- pthread_join(t0, NULL);
-
- if (test->total_steps == test->current_step || test->fail) {
+ /* In a one-sided step, the idle endpoint only keeps in step. */
+ if (ifobj_is_local(ifobj1))
+ err = ifobj1->func_ptr(test);
+ else if (ifobj2 && ifobj_is_local(ifobj2))
+ err = ifobj2->func_ptr(test);
+ else
+ err = sync_endpoints(test);
+ if (test->total_steps == test->current_step || err) {
clean_sockets(test, ifobj1);
clean_sockets(test, ifobj2);
clean_umem(test, ifobj1, ifobj2);
}
- if (test->fail)
- return TEST_FAILURE;
-
- return TEST_PASS;
+ return err ? TEST_FAILURE : TEST_PASS;
}
static int testapp_validate_traffic(struct test_spec *test)
@@ -1867,7 +1902,8 @@ static int testapp_validate_traffic(struct test_spec *test)
if (test->set_ring) {
if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING)) {
- if (set_ring_size(ifobj_tx)) {
+ if (ifobj_is_local(ifobj_tx) &&
+ set_ring_size(ifobj_tx)) {
ksft_print_msg("Failed to change HW ring size.\n");
return TEST_FAILURE;
}
@@ -1900,7 +1936,7 @@ int testapp_teardown(struct test_spec *test)
static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2)
{
- thread_func_t tmp_func_ptr = (*ifobj1)->func_ptr;
+ test_func_t tmp_func_ptr = (*ifobj1)->func_ptr;
struct ifobject *tmp_ifobj = (*ifobj1);
(*ifobj1)->func_ptr = (*ifobj2)->func_ptr;
@@ -1939,6 +1975,9 @@ static int swap_xsk_resources(struct test_spec *test)
test->ifobj_tx->xsk = &test->ifobj_tx->xsk_arr[1];
test->ifobj_rx->xsk = &test->ifobj_rx->xsk_arr[1];
+ if (!ifobj_is_local(test->ifobj_rx))
+ return TEST_PASS;
+
ret = xsk_update_xskmap(test->ifobj_rx->xskmap, test->ifobj_rx->xsk->xsk, 0);
if (ret)
return TEST_FAILURE;
@@ -2287,7 +2326,7 @@ int testapp_too_many_frags(struct test_spec *test)
return ret;
}
-static int xsk_load_xdp_programs(struct ifobject *ifobj)
+int xsk_load_xdp_programs(struct ifobject *ifobj)
{
ifobj->xdp_progs = xsk_xdp_progs__open_and_load();
if (libbpf_get_error(ifobj->xdp_progs))
@@ -2345,12 +2384,10 @@ static int detect_ifobj_caps(struct ifobject *ifobj)
return 0;
}
-int init_iface(struct ifobject *ifobj, thread_func_t func_ptr)
+int init_iface(struct ifobject *ifobj)
{
int err;
- ifobj->func_ptr = func_ptr;
-
err = xsk_load_xdp_programs(ifobj);
if (err) {
ksft_print_msg("Error loading XDP program\n");
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 861a2ee3b8e1..53e5032510a0 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -77,9 +77,13 @@ enum test_mode {
struct ifobject;
struct test_spec;
typedef int (*validation_func_t)(struct ifobject *ifobj);
-typedef void *(*thread_func_t)(void *arg);
typedef int (*test_func_t)(struct test_spec *test);
+struct xsk_peer;
+
+/* The control channel to the other endpoint. */
+void xsk_set_endpoint(struct xsk_peer *peer);
+
struct xsk_socket_info {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
@@ -139,7 +143,7 @@ struct ifobject {
struct xsk_caps caps;
struct xsk_socket_info *xsk;
struct xsk_socket_info *xsk_arr;
- thread_func_t func_ptr;
+ test_func_t func_ptr;
validation_func_t validation_func;
struct xsk_xdp_progs *xdp_progs;
struct bpf_map *xskmap;
@@ -169,11 +173,18 @@ static inline void ifobj_set_cap(struct ifobject *ifobj, u32 cap)
ifobj->caps.flags |= cap;
}
+/* The shadow of the peer's ifobject is never bound to an interface. */
+static inline bool ifobj_is_local(const struct ifobject *ifobj)
+{
+ return ifobj->ifindex;
+}
+
#define xsk_get_cap(test, cap) ((test)->ifobj_tx->caps.cap)
struct ifobject *ifobject_create(void);
void ifobject_delete(struct ifobject *ifobj);
-int init_iface(struct ifobject *ifobj, thread_func_t func_ptr);
+int init_iface(struct ifobject *ifobj);
+int xsk_load_xdp_programs(struct ifobject *ifobj);
int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, u64 size);
int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem,
@@ -222,12 +233,10 @@ struct test_spec {
u16 total_steps;
u16 current_step;
u16 nb_sockets;
- bool fail;
bool set_ring;
bool adjust_tail;
bool adjust_tail_support;
bool poll_tmout;
- bool use_barrier;
enum test_mode mode;
char name[MAX_TEST_NAME_SIZE];
};
@@ -288,8 +297,8 @@ int testapp_xdp_metadata_mb(struct test_spec *test);
int testapp_xdp_prog_cleanup(struct test_spec *test);
int testapp_xdp_shared_umem(struct test_spec *test);
-void *worker_testapp_validate_rx(void *arg);
-void *worker_testapp_validate_tx(void *arg);
+int worker_testapp_validate_rx(struct test_spec *test);
+int worker_testapp_validate_tx(struct test_spec *test);
static const struct test_spec tests[] = {
{.name = "SEND_RECEIVE", .test_func = testapp_send_receive},
diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.c b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
new file mode 100644
index 000000000000..faec74aae379
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "xsk_peer.h"
+
+#define XSK_PEER_MAGIC 0x58534b50
+#define XSK_PEER_ACCEPT_TMOUT_MS 30000
+
+/* One case per connection; the launcher owns the verdict. */
+enum xsk_peer_msg_type {
+ XSK_PEER_READY = 1,
+ XSK_PEER_PROGRESS,
+ XSK_PEER_ABORT,
+};
+
+struct xsk_peer_msg {
+ u32 magic;
+ u32 type;
+ u32 pkts;
+};
+
+struct xsk_peer {
+ int fd;
+ u32 acked;
+ bool ready_seen;
+ bool closed;
+};
+
+static int io_full(int fd, void *buf, size_t len, bool write_op)
+{
+ size_t done = 0;
+ ssize_t ret;
+
+ while (done < len) {
+ if (write_op)
+ ret = send(fd, (char *)buf + done, len - done, MSG_NOSIGNAL);
+ else
+ ret = read(fd, (char *)buf + done, len - done);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ if (ret < 0)
+ return -errno;
+ if (!ret)
+ return -EPIPE;
+ done += ret;
+ }
+ return 0;
+}
+
+/*
+ * Past its last barrier the peer may exit at any time, and it resets the
+ * connection if our progress reports are still unread.
+ */
+static bool peer_gone(int err)
+{
+ return err == -EPIPE || err == -ECONNRESET;
+}
+
+static int peer_send(struct xsk_peer *peer, u32 type, u32 pkts)
+{
+ struct xsk_peer_msg msg = {
+ .magic = htonl(XSK_PEER_MAGIC),
+ .type = htonl(type),
+ .pkts = htonl(pkts),
+ };
+
+ return io_full(peer->fd, &msg, sizeof(msg), true);
+}
+
+static int peer_recv_one(struct xsk_peer *peer)
+{
+ struct xsk_peer_msg msg;
+ int err;
+
+ err = io_full(peer->fd, &msg, sizeof(msg), false);
+ if (peer_gone(err))
+ peer->closed = true;
+ if (err)
+ return err;
+ if (ntohl(msg.magic) != XSK_PEER_MAGIC)
+ return -EPROTO;
+
+ switch (ntohl(msg.type)) {
+ case XSK_PEER_READY:
+ /* The peer waits for our READY, so it is at most one step ahead. */
+ if (peer->ready_seen)
+ return -EPROTO;
+ peer->ready_seen = true;
+ return 0;
+ case XSK_PEER_PROGRESS:
+ peer->acked += ntohl(msg.pkts);
+ return 0;
+ case XSK_PEER_ABORT:
+ return -ECANCELED;
+ default:
+ return -EPROTO;
+ }
+}
+
+/*
+ * Consume what the peer has sent so far without blocking. A closed connection
+ * is not an error here, see peer_gone(); xsk_peer_closed() reports it.
+ */
+static int peer_drain(struct xsk_peer *peer)
+{
+ struct pollfd pfd = { .fd = peer->fd, .events = POLLIN };
+ int ret;
+
+ while (!peer->closed) {
+ ret = poll(&pfd, 1, 0);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ if (ret < 0)
+ return -errno;
+ if (!ret)
+ break;
+ ret = peer_recv_one(peer);
+ if (ret && !peer->closed)
+ return ret;
+ }
+ return 0;
+}
+
+/* Return once both endpoints are set up for the next step. */
+int xsk_peer_ready(struct xsk_peer *peer)
+{
+ int err;
+
+ err = peer_send(peer, XSK_PEER_READY, 0);
+ while (!err && !peer->ready_seen)
+ err = peer_recv_one(peer);
+ if (err)
+ return err;
+
+ /* TCP ordering puts all progress of the previous step before READY. */
+ peer->ready_seen = false;
+ peer->acked = 0;
+ return 0;
+}
+
+void xsk_peer_abort(struct xsk_peer *peer)
+{
+ if (peer && !peer->closed)
+ peer_send(peer, XSK_PEER_ABORT, 0);
+}
+
+int xsk_peer_rx_progress(struct xsk_peer *peer, u32 pkts)
+{
+ int err;
+
+ if (pkts && !peer->closed) {
+ err = peer_send(peer, XSK_PEER_PROGRESS, pkts);
+ if (peer_gone(err))
+ peer->closed = true;
+ else if (err)
+ return err;
+ }
+ return peer_drain(peer);
+}
+
+/* Return the packets RX reported consumed since the last call, or -errno. */
+int xsk_peer_tx_sync(struct xsk_peer *peer)
+{
+ int err = peer_drain(peer);
+ u32 acked = peer->acked;
+
+ if (err)
+ return err;
+ peer->acked = 0;
+ return acked;
+}
+
+bool xsk_peer_closed(struct xsk_peer *peer)
+{
+ return peer->closed;
+}
+
+static int accept_tmout(int lfd)
+{
+ struct pollfd pfd = { .fd = lfd, .events = POLLIN };
+ int ret;
+
+ do {
+ ret = poll(&pfd, 1, XSK_PEER_ACCEPT_TMOUT_MS);
+ } while (ret < 0 && errno == EINTR);
+ if (!ret)
+ errno = ETIMEDOUT;
+ return ret > 0 ? accept(lfd, NULL, NULL) : -1;
+}
+
+/*
+ * The launcher starts the listener and waits for its port before starting
+ * the connector, so connect() needs no retry. accept() is bounded in case
+ * the connector exits before it gets that far.
+ */
+static int peer_open_tcp(const char *host, const char *port, bool listen_side)
+{
+ struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
+ int fd = -1, lfd, saved = ECONNREFUSED, one = 1, err;
+ struct addrinfo *res, *ai;
+
+ err = getaddrinfo(host, port, &hints, &res);
+ if (err)
+ return -EINVAL;
+
+ if (!listen_side)
+ goto conn;
+
+ for (ai = res; ai; ai = ai->ai_next) {
+ lfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (lfd < 0)
+ continue;
+ setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (!bind(lfd, ai->ai_addr, ai->ai_addrlen) && !listen(lfd, 1)) {
+ fd = accept_tmout(lfd);
+ saved = errno;
+ close(lfd);
+ break;
+ }
+ saved = errno;
+ close(lfd);
+ }
+ freeaddrinfo(res);
+ return fd >= 0 ? fd : -saved;
+
+conn:
+ for (ai = res; ai; ai = ai->ai_next) {
+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (fd < 0)
+ continue;
+ if (!connect(fd, ai->ai_addr, ai->ai_addrlen))
+ break;
+ saved = errno;
+ close(fd);
+ fd = -1;
+ }
+ freeaddrinfo(res);
+ return fd >= 0 ? fd : -saved;
+}
+
+struct xsk_peer *xsk_peer_open(const char *host, const char *port,
+ bool listen_side)
+{
+ int fd = peer_open_tcp(host, port, listen_side);
+ struct xsk_peer *peer;
+ int one = 1;
+
+ if (fd < 0) {
+ errno = -fd;
+ return NULL;
+ }
+ peer = calloc(1, sizeof(*peer));
+ if (!peer) {
+ close(fd);
+ return NULL;
+ }
+ peer->fd = fd;
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
+ return peer;
+}
+
+void xsk_peer_close(struct xsk_peer *peer)
+{
+ if (peer) {
+ close(peer->fd);
+ free(peer);
+ }
+}
diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.h b/tools/testing/selftests/net/lib/xsk/xsk_peer.h
new file mode 100644
index 000000000000..da4462192994
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef XSK_PEER_H_
+#define XSK_PEER_H_
+
+#include <linux/types.h>
+
+struct xsk_peer;
+
+struct xsk_peer *xsk_peer_open(const char *host, const char *port,
+ bool listen_side);
+void xsk_peer_close(struct xsk_peer *peer);
+
+int xsk_peer_ready(struct xsk_peer *peer);
+void xsk_peer_abort(struct xsk_peer *peer);
+int xsk_peer_rx_progress(struct xsk_peer *peer, u32 pkts);
+int xsk_peer_tx_sync(struct xsk_peer *peer);
+bool xsk_peer_closed(struct xsk_peer *peer);
+
+#endif /* XSK_PEER_H_ */
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index b8a52846eaf8..e66625810a9d 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -9,9 +9,9 @@
* See test_xsk.sh for detailed information on test topology
* and prerequisite network setup.
*
- * This test program contains two threads, each thread is single socket with
- * a unique UMEM. It validates in-order packet delivery and packet content
- * by sending packets to each other.
+ * Each instance of this test program runs one endpoint, Tx or Rx, with a
+ * single socket and a unique UMEM. Two instances validate in-order packet
+ * delivery and packet content by sending packets to each other.
*
* Tests Information:
* ------------------
@@ -57,12 +57,14 @@
*
* Flow:
* -----
- * - Single process spawns two threads: Tx and Rx
- * - Each of these two threads attach to a veth interface
- * - Each thread creates one AF_XDP socket connected to a unique umem for each
+ * - test_xsk.sh starts two processes: Tx and Rx
+ * - Each of these two processes attach to a veth interface
+ * - Each process creates one AF_XDP socket connected to a unique umem for each
* veth interface
- * - Tx thread Transmits a number of packets from veth<xxxx> to veth<yyyy>
- * - Rx thread verifies if all packets were received and delivered in-order,
+ * - Tx process listens on a TCP control port, Rx process connects to it and
+ * the two synchronize each test step over that connection
+ * - Tx process Transmits a number of packets from veth<xxxx> to veth<yyyy>
+ * - Rx process verifies if all packets were received and delivered in-order,
* and have the right content
*
* Enable/disable packet dump mode:
@@ -92,6 +94,7 @@
#include <sys/types.h>
#include "test_xsk.h"
+#include "xsk_peer.h"
#include "xsk_xdp_progs.skel.h"
#include "xsk.h"
#include "xskxceiver.h"
@@ -100,8 +103,18 @@
#include "kselftest.h"
#include "xsk_xdp_common.h"
+enum xsk_endpoint_role {
+ XSK_ENDPOINT_NONE,
+ XSK_ENDPOINT_RX,
+ XSK_ENDPOINT_TX,
+};
+
static enum test_mode opt_mode = TEST_MODE_ALL;
static u32 opt_run_test = RUN_ALL_TESTS;
+static enum xsk_endpoint_role opt_endpoint_role = XSK_ENDPOINT_NONE;
+static const char *opt_peer_host;
+static const char *opt_peer_port;
+static const char *opt_ifname;
static void __exit_with_error(int error, const char *file, const char *func, int line)
{
@@ -162,6 +175,9 @@ static struct option long_options[] = {
{"mode", required_argument, 0, 'm'},
{"list", no_argument, 0, 'l'},
{"test", required_argument, 0, 't'},
+ {"endpoint", required_argument, 0, 'e'},
+ {"peer", required_argument, 0, 'p'},
+ {"peer-port", required_argument, 0, 'P'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
@@ -169,7 +185,7 @@ static struct option long_options[] = {
static void print_usage(char **argv)
{
const char *str =
- " Usage: xskxceiver -i TX_IFACE -i RX_IFACE -m MODE -t TEST [OPTIONS]\n"
+ " Usage: xskxceiver -i IFACE -e rx|tx -m MODE -t TEST -p HOST -P PORT [OPTIONS]\n"
" Options:\n"
" -i, --interface Use interface\n"
" -v, --verbose Verbose output\n"
@@ -177,16 +193,18 @@ static void print_usage(char **argv)
" -m, --mode Run only mode skb, drv, or zc\n"
" -l, --list List all available tests\n"
" -t, --test Run a specific test. Enter number from -l option.\n"
+ " -e, --endpoint Role of this endpoint: rx or tx\n"
+ " -p, --peer Control host: IPv4/IPv6 address or hostname\n"
+ " -P, --peer-port Control TCP port (1-65535)\n"
" -h, --help Display this help and exit\n";
ksft_print_msg(str, basename(argv[0]));
ksft_exit_xfail();
}
-static void bind_iface(struct ifobject *ifobj, const char *ifname, thread_func_t func,
- char **argv)
+static void bind_iface(struct ifobject *ifobj, const char *ifname, char **argv)
{
- size_t len = ifname ? strlen(ifname) : 0;
+ size_t len = strlen(ifname);
if (!len || len >= sizeof(ifobj->ifname))
print_usage(argv);
@@ -198,7 +216,7 @@ static void bind_iface(struct ifobject *ifobj, const char *ifname, thread_func_t
ksft_exit_fail();
}
- if (init_iface(ifobj, func)) {
+ if (init_iface(ifobj)) {
ksft_print_msg("Error: cannot initialize interface %s\n", ifobj->ifname);
ksft_exit_fail();
}
@@ -216,23 +234,18 @@ static void print_tests(void)
static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx, int argc,
char **argv)
{
- const char *ifname[2] = {};
- u32 interface_nb = 0;
int option_index, c;
opterr = 0;
for (;;) {
- c = getopt_long(argc, argv, "i:vbm:lt:", long_options, &option_index);
+ c = getopt_long(argc, argv, "i:vbm:lt:e:p:P:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'i':
- if (interface_nb >= ARRAY_SIZE(ifname))
- break;
-
- ifname[interface_nb++] = optarg;
+ opt_ifname = optarg;
break;
case 'v':
opt_verbose = true;
@@ -261,17 +274,30 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
if (errno)
print_usage(argv);
break;
+ case 'e':
+ if (!strcmp(optarg, "rx"))
+ opt_endpoint_role = XSK_ENDPOINT_RX;
+ else if (!strcmp(optarg, "tx"))
+ opt_endpoint_role = XSK_ENDPOINT_TX;
+ else
+ print_usage(argv);
+ break;
+ case 'p':
+ opt_peer_host = optarg;
+ break;
+ case 'P':
+ opt_peer_port = optarg;
+ break;
case 'h':
default:
print_usage(argv);
}
}
- if (opt_run_test == RUN_ALL_TESTS || opt_mode == TEST_MODE_ALL)
+ if (!opt_ifname || opt_endpoint_role == XSK_ENDPOINT_NONE || !opt_peer_host ||
+ !*opt_peer_host || !opt_peer_port || opt_run_test == RUN_ALL_TESTS ||
+ opt_mode == TEST_MODE_ALL)
print_usage(argv);
-
- bind_iface(ifobj_tx, ifname[0], worker_testapp_validate_tx, argv);
- bind_iface(ifobj_rx, ifname[1], worker_testapp_validate_rx, argv);
}
static void xsk_unload_xdp_programs(struct ifobject *ifobj)
@@ -357,20 +383,49 @@ static bool mode_supported(enum test_mode mode, u32 caps)
static void cleanup_iface(struct ifobject *ifobj)
{
+ /* A peer shadow has a skeleton but no bound interface. */
+ if (!ifobj_is_local(ifobj))
+ goto unload;
+
if (ifobj_has_cap(ifobj, XSK_CAP_HW_RING))
hw_ring_size_reset(ifobj);
if (ifobj->xdp_prog)
xsk_detach_xdp_program(ifobj->ifindex,
ifobj->mode == TEST_MODE_SKB ?
XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE);
+unload:
xsk_unload_xdp_programs(ifobj);
}
+/* Connect the two endpoints without negotiating the remote NIC's capabilities. */
+static int setup_peer(struct ifobject *local, struct ifobject *shadow,
+ struct xsk_peer **peer)
+{
+ /* Unattached skeleton copy; the engine only attaches the local side. */
+ if (xsk_load_xdp_programs(shadow))
+ return TEST_FAILURE;
+
+ /* TX listens and RX connects. */
+ *peer = xsk_peer_open(opt_peer_host, opt_peer_port,
+ opt_endpoint_role == XSK_ENDPOINT_TX);
+ if (!*peer) {
+ ksft_print_msg("Failed to connect XSK peer: %s\n", strerror(errno));
+ return TEST_FAILURE;
+ }
+
+ shadow->caps = local->caps;
+ xsk_set_endpoint(*peer);
+ return TEST_PASS;
+}
+
int main(int argc, char **argv)
{
u32 cache_line_size, max_frags, umem_tailroom;
const size_t total_tests = ARRAY_SIZE(tests);
struct ifobject *ifobj_tx, *ifobj_rx;
+ struct ifobject *shadow_ifobj;
+ struct ifobject *local_ifobj;
+ struct xsk_peer *peer = NULL;
struct test_spec test = {};
int ret = TEST_FAILURE;
u32 caps;
@@ -415,12 +470,26 @@ int main(int argc, char **argv)
ksft_exit_xfail();
}
+ /* swap_directions() swaps the workers, so the shadow needs one too. */
+ ifobj_tx->func_ptr = worker_testapp_validate_tx;
+ ifobj_rx->func_ptr = worker_testapp_validate_rx;
+ if (opt_endpoint_role == XSK_ENDPOINT_TX) {
+ local_ifobj = ifobj_tx;
+ shadow_ifobj = ifobj_rx;
+ } else {
+ local_ifobj = ifobj_rx;
+ shadow_ifobj = ifobj_tx;
+ }
+ bind_iface(local_ifobj, opt_ifname, argv);
+ caps = detect_mode_caps(local_ifobj);
+
test.tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
test.rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
if (!test.tx_pkt_stream_default || !test.rx_pkt_stream_default)
goto out;
- caps = detect_mode_caps(ifobj_tx);
+ if (setup_peer(local_ifobj, shadow_ifobj, &peer))
+ goto out;
if (!mode_supported(opt_mode, caps)) {
if (opt_mode == TEST_MODE_DRV)
@@ -438,8 +507,10 @@ int main(int argc, char **argv)
ret = run_pkt_test(&test);
out:
+ xsk_set_endpoint(NULL);
cleanup_iface(ifobj_tx);
cleanup_iface(ifobj_rx);
+ xsk_peer_close(peer);
pkt_stream_delete(test.tx_pkt_stream_default);
pkt_stream_delete(test.rx_pkt_stream_default);
ifobject_delete(ifobj_tx);
diff --git a/tools/testing/selftests/net/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
index e476556eb05b..2ef9ce7e1b27 100755
--- a/tools/testing/selftests/net/test_xsk.sh
+++ b/tools/testing/selftests/net/test_xsk.sh
@@ -8,22 +8,20 @@
#
# Topology:
# ---------
-# -----------
-# _ | Process | _
-# / ----------- \
-# / | \
-# / | \
-# ----------- | -----------
-# | Thread1 | | | Thread2 |
-# ----------- | -----------
-# | | |
-# ----------- | -----------
-# | xskX | | | xskY |
-# ----------- | -----------
-# | | |
-# ----------- | ----------
-# | vethX | --------- | vethY |
-# ----------- peer ----------
+# ----------- control -----------
+# | TX proc | ------------ | RX proc |
+# ----------- (TCP, lo) -----------
+# | |
+# ----------- -----------
+# | xskX | | xskY |
+# ----------- -----------
+# | |
+# ----------- ----------
+# | vethX | ------------ | vethY |
+# ----------- peer ----------
+#
+# The two xskxceiver processes each drive one veth and synchronize over a
+# TCP control connection on 127.0.0.1.
#
# AF_XDP is an address family optimized for high performance packet processing,
# it is XDP’s user-space interface.
@@ -71,6 +69,9 @@
# Set up veth interfaces and leave them up so xskxceiver can be launched in a debugger:
# sudo ./test_xsk.sh -d
#
+# Use a specific TCP port for the control connection (random by default)
+# sudo ./test_xsk.sh -p PORT
+#
# Run test suite in a specific mode only [skb,drv]
# sudo ./test_xsk.sh -m MODE
#
@@ -90,11 +91,15 @@ if [ ! -x "./${XSKOBJ}" ]; then
exit $ksft_skip
fi
-while getopts "vdm:lt:h" flag
+PEER_HOST=127.0.0.1
+PEER_PORT=
+
+while getopts "vdm:lt:p:h" flag
do
case "${flag}" in
v) verbose=1;;
d) debug=1;;
+ p) PEER_PORT=${OPTARG};;
m) MODE=${OPTARG};;
l) list=1;;
t) TEST=${OPTARG};;
@@ -181,6 +186,13 @@ if [ ${#CASES[@]} -eq 0 ]; then
exit 1
fi
+if [ -z "$PEER_PORT" ]; then
+ PEER_PORT=$(random_port) || {
+ echo "Could not find an unused TCP port" >&2
+ exit 1
+ }
+fi
+
validate_root_exec
validate_veth_support ${VETH0}
validate_ip_utility
@@ -225,7 +237,8 @@ RUN_VARIANT=SOFTIRQ
if [[ $debug -eq 1 ]]; then
ARGS="${BASE_ARGS} -m ${MODES[0]} -t ${CASES[0]}"
- echo "./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}"
+ echo "./${XSKOBJ} -i ${VETH0} -e tx -p ${PEER_HOST} -P ${PEER_PORT} ${ARGS}"
+ echo "./${XSKOBJ} -i ${VETH1} -e rx -p ${PEER_HOST} -P ${PEER_PORT} ${ARGS}"
exit
fi
diff --git a/tools/testing/selftests/net/xsk_prereqs.sh b/tools/testing/selftests/net/xsk_prereqs.sh
index 30173db12c56..82cd7839d77d 100755
--- a/tools/testing/selftests/net/xsk_prereqs.sh
+++ b/tools/testing/selftests/net/xsk_prereqs.sh
@@ -69,16 +69,63 @@ validate_ip_utility()
[ ! $(type -P ip) ] && { echo "'ip' not found. Skipping tests."; test_exit $ksft_skip; }
}
+wait_port_listen()
+{
+ local i
+
+ for i in $(seq 50); do
+ if [ -n "$(ss -Hltn "sport = :$1" 2>/dev/null)" ]; then
+ return 0
+ fi
+ kill -0 $2 2>/dev/null || return 1
+ sleep 0.1
+ done
+ return 1
+}
+
+random_port()
+{
+ local i port
+
+ for i in $(seq 50); do
+ port=$((32768 + RANDOM))
+ if [ -z "$(ss -Hltn "sport = :$port" 2>/dev/null)" ]; then
+ echo "$port"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# The TX endpoint listens on the control port and runs in the background,
+# the RX endpoint connects to it and reports; both print the same verdicts.
exec_xskxceiver()
{
- local run_args="${ARGS}"
+ local tx_log tx_pid tx_ret run_args="${ARGS}"
if [[ $busy_poll -eq 1 ]]; then
run_args+=" -b"
fi
- ./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${run_args}
- retval=$?
+ tx_log=$(mktemp)
+ ./${XSKOBJ} -i ${VETH0} -e tx -p ${PEER_HOST} -P ${PEER_PORT} ${run_args} > ${tx_log} 2>&1 &
+ tx_pid=$!
+
+ if wait_port_listen ${PEER_PORT} ${tx_pid}; then
+ ./${XSKOBJ} -i ${VETH1} -e rx -p ${PEER_HOST} -P ${PEER_PORT} ${run_args}
+ retval=$?
+ else
+ echo "TX endpoint did not listen on ${PEER_HOST}:${PEER_PORT}"
+ retval=1
+ fi
+
+ wait ${tx_pid}
+ tx_ret=$?
+ if [[ $tx_ret -ne 0 || $verbose -eq 1 ]]; then
+ sed 's/^/tx| /' ${tx_log}
+ fi
+ rm -f ${tx_log}
+ [[ $retval -eq 0 ]] && retval=$tx_ret
if [[ $list -ne 1 ]]; then
test_status $retval "${TEST_NAME}"
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (8 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
` (4 subsequent siblings)
14 siblings, 2 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
drivers/net/hw/xsk.py, added later in this series, runs each hardware
AF_XDP case as a DUT and a remote xskxceiver endpoint over a physical
link. The DUT endpoint uses zero-copy mode and the remote endpoint uses
SKB mode, without negotiating remote capabilities.
xskxceiver gains what such a two-host run needs:
- --hw sends UDP/IPv4 test packets (--udp-src, --udp-dst, --udp-port)
between the real interface MACs (--peer-mac) over a link that carries
no other traffic, as the XDP programs redirect every packet;
XDP_SHARED_UMEM, whose program picks the socket by the synthetic
destination MAC, skips
- --queue binds a queue other than 0, and --listen makes a hardware
endpoint listen for its peer instead of connecting and say so on
stderr once it does
- --max-frags hands both endpoints the DUT limit for TOO_MANY_FRAGS,
since each side builds the packet stream from its own view of it
- with --hw, zero-copy support is taken from
NETDEV_XDP_ACT_XSK_ZEROCOPY instead of a trial attach and bind; an
XDP_ZEROCOPY bind fails rather than falling back to copy mode, so a
false advertisement still fails the case
- with --hw, an endpoint that only transmits skips the XDP program
unless it runs in zero-copy mode, where a driver can require one for
TX wakeups
- the RX and TX loops of hardware cases time out after 20s
(HW_THREAD_TMOUT) instead of 3s
- a hardware endpoint prints no KTAP output of its own and reports
through its exit code, as xsk.py reports the case
Skip hw_ring_size_reset() when the rings are already at their defaults.
cleanup_iface() restores the rings after every case on both hosts, and
the SIOCETHTOOL path passes such no-op requests on to the driver.
cleanup_iface() also puts back the MTU that bind_iface() found, so a
case does not leave its MTU to the next case or to the user. It does
so after detaching the XDP program, which can cap the MTU, and fails
the endpoint if it cannot. As an MTU change can reset the NIC, a
hardware endpoint in SKB mode only raises its MTU when a case needs a
larger one, which generic XDP allows, so a jumbo-MTU peer is not reset
twice for every case.
is_frag_valid() now takes the header size from pkt_hdr_size instead of
the fixed PKT_HDR_SIZE. Guard both reads it makes off that variable
length: a first fragment shorter than the header plus one word, or a
later fragment shorter than one word, must be rejected as invalid
instead of reading past the end of the buffer.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 183 +++++++++++++++---
.../testing/selftests/net/lib/xsk/test_xsk.h | 10 +-
tools/testing/selftests/net/lib/xsk/xsk.c | 37 ++++
tools/testing/selftests/net/lib/xsk/xsk.h | 2 +
.../testing/selftests/net/lib/xsk/xsk_peer.c | 5 +
.../selftests/net/lib/xsk/xskxceiver.c | 155 +++++++++++++--
6 files changed, 348 insertions(+), 44 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 8d30c39c94c8..542c0579062a 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -6,6 +6,8 @@
#include <linux/if_link.h>
#include <linux/mman.h>
#include <linux/netdev.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
@@ -27,8 +29,11 @@
#define PKT_DUMP_NB_TO_PRINT 16
/* Just to align the data in the packet */
#define PKT_HDR_SIZE (sizeof(struct ethhdr) + 2)
+#define UDP_PKT_HDR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \
+ sizeof(struct udphdr) + 2)
#define POLL_TMOUT 1000
#define THREAD_TMOUT 3
+#define HW_THREAD_TMOUT 20
#define UMEM_HEADROOM_TEST_SIZE 128
#define XSK_DESC__INVALID_OPTION (0xffff)
#define XSK_UMEM__INVALID_FRAME_SIZE (MAX_ETH_JUMBO_SIZE + 1)
@@ -36,15 +41,23 @@
#define XSK_UMEM__MAX_FRAME_SIZE (4 * 1024)
static const u8 g_mac[ETH_ALEN] = {0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
+static bool udp_packets;
+static struct in_addr udp_src_ip;
+static struct in_addr udp_dst_ip;
+static u16 udp_port;
+static u32 pkt_hdr_size = PKT_HDR_SIZE;
+static u32 max_frags_override;
bool opt_verbose;
int pkts_in_flight;
static struct xsk_peer *ctrl_peer;
+static bool hw_test;
-void xsk_set_endpoint(struct xsk_peer *peer)
+void xsk_set_endpoint(struct xsk_peer *peer, bool hardware)
{
ctrl_peer = peer;
+ hw_test = hardware;
}
static int pacing_rx_progress(u32 pkts)
@@ -84,11 +97,70 @@ static void write_payload(void *dest, u32 pkt_nb, u32 start, u32 size)
ptr[i] = htonl(pkt_nb << 16 | (i + start));
}
-static void gen_eth_hdr(struct xsk_socket_info *xsk, struct ethhdr *eth_hdr)
+int xsk_set_udp_packet_format(const char *src_ip, const char *dst_ip, u16 port)
{
+ if (inet_pton(AF_INET, src_ip, &udp_src_ip) != 1 ||
+ inet_pton(AF_INET, dst_ip, &udp_dst_ip) != 1 || !port)
+ return -EINVAL;
+ udp_packets = true;
+ udp_port = port;
+ pkt_hdr_size = UDP_PKT_HDR_SIZE;
+ return 0;
+}
+
+void xsk_set_max_frags(u32 max_frags)
+{
+ max_frags_override = max_frags;
+}
+
+static u16 ip_checksum(const void *buf, size_t len)
+{
+ const u16 *word = buf;
+ u32 sum = 0;
+
+ while (len > 1) {
+ sum += *word++;
+ len -= sizeof(*word);
+ }
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
+
+static void gen_pkt_hdr(struct xsk_socket_info *xsk, void *data, u32 total_len)
+{
+ struct ethhdr *eth_hdr = data;
+ struct udphdr udp = {
+ .source = htons(udp_port),
+ .dest = htons(udp_port),
+ .len = htons(total_len - sizeof(*eth_hdr) -
+ sizeof(struct iphdr)),
+ };
+ struct iphdr ip = {
+ .version = 4,
+ .ihl = 5,
+ .ttl = 64,
+ .protocol = IPPROTO_UDP,
+ .tot_len = htons(total_len - sizeof(*eth_hdr)),
+ .saddr = udp_src_ip.s_addr,
+ .daddr = udp_dst_ip.s_addr,
+ };
+ u8 *ptr = data;
+
memcpy(eth_hdr->h_dest, xsk->dst_mac, ETH_ALEN);
memcpy(eth_hdr->h_source, xsk->src_mac, ETH_ALEN);
- eth_hdr->h_proto = htons(ETH_P_LOOPBACK);
+ if (!udp_packets) {
+ eth_hdr->h_proto = htons(ETH_P_LOOPBACK);
+ return;
+ }
+
+ eth_hdr->h_proto = htons(ETH_P_IP);
+ ip.check = ip_checksum(&ip, sizeof(ip));
+ ptr += sizeof(*eth_hdr);
+ memcpy(ptr, &ip, sizeof(ip));
+ ptr += sizeof(ip);
+ memcpy(ptr, &udp, sizeof(udp));
+ memset(ptr + sizeof(udp), 0, 2);
}
static u32 mode_to_xdp_flags(enum test_mode mode)
@@ -193,7 +265,8 @@ int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem
txr = ifobject->tx_on ? &xsk->tx : NULL;
rxr = ifobject->rx_on ? &xsk->rx : NULL;
- return xsk_socket__create(&xsk->xsk, ifobject->ifindex, 0, umem->umem, rxr, txr, &cfg);
+ return xsk_socket__create(&xsk->xsk, ifobject->ifindex, ifobject->queue_id,
+ umem->umem, rxr, txr, &cfg);
}
static int set_ring_size(struct ifobject *ifobj)
@@ -218,6 +291,10 @@ static int set_ring_size(struct ifobject *ifobj)
int hw_ring_size_reset(struct ifobject *ifobj)
{
+ if (ifobj->ring.tx_pending == ifobj->set_ring.default_tx &&
+ ifobj->ring.rx_pending == ifobj->set_ring.default_rx)
+ return 0;
+
ifobj->ring.tx_pending = ifobj->set_ring.default_tx;
ifobj->ring.rx_pending = ifobj->set_ring.default_rx;
return set_ring_size(ifobj);
@@ -230,6 +307,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
for (i = 0; i < MAX_INTERFACES; i++) {
struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx;
+ struct ifobject *peer = i ? ifobj_tx : ifobj_rx;
struct xsk_umem_info *umem;
ifobj->xsk = &ifobj->xsk_arr[0];
@@ -261,10 +339,15 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
else
xsk->pkt_stream = test->rx_pkt_stream_default;
- memcpy(xsk->src_mac, g_mac, ETH_ALEN);
- memcpy(xsk->dst_mac, g_mac, ETH_ALEN);
- xsk->src_mac[5] += ((j * 2) + 0);
- xsk->dst_mac[5] += ((j * 2) + 1);
+ if (hw_test) {
+ memcpy(xsk->src_mac, ifobj->caps.mac, ETH_ALEN);
+ memcpy(xsk->dst_mac, peer->caps.mac, ETH_ALEN);
+ } else {
+ memcpy(xsk->src_mac, g_mac, ETH_ALEN);
+ memcpy(xsk->dst_mac, g_mac, ETH_ALEN);
+ xsk->src_mac[5] += ((j * 2) + 0);
+ xsk->dst_mac[5] += ((j * 2) + 1);
+ }
}
ifobj->xsk->umem->num_frames = DEFAULT_UMEM_BUFFERS;
@@ -347,14 +430,25 @@ static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *x
test->xskmap_tx = xskmap_tx;
}
-static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, int mtu)
+/* An MTU change can reset a NIC. A hardware SKB-mode endpoint runs a case at
+ * any larger MTU, as generic XDP does not limit it, so only raise its MTU.
+ */
+static bool mtu_change_needed(struct ifobject *ifobj, enum test_mode mode, int mtu)
+{
+ if (hw_test && mode == TEST_MODE_SKB)
+ return ifobj->dev_mtu < mtu;
+ return ifobj->dev_mtu != mtu;
+}
+
+static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, enum test_mode mode, int mtu)
{
int err;
- if (ifobj_is_local(ifobj) && ifobj->mtu != mtu) {
+ if (ifobj_is_local(ifobj) && mtu_change_needed(ifobj, mode, mtu)) {
err = xsk_set_mtu(ifobj->ifindex, mtu);
if (err)
return err;
+ ifobj->dev_mtu = mtu;
}
ifobj->mtu = mtu;
@@ -365,11 +459,11 @@ static int test_spec_set_mtu(struct test_spec *test, int mtu)
{
int err;
- err = test_spec_set_mtu_ifobj(test->ifobj_rx, mtu);
+ err = test_spec_set_mtu_ifobj(test->ifobj_rx, test->mode, mtu);
if (err)
return err;
- return test_spec_set_mtu_ifobj(test->ifobj_tx, mtu);
+ return test_spec_set_mtu_ifobj(test->ifobj_tx, test->mode, mtu);
}
void pkt_stream_reset(struct pkt_stream *pkt_stream)
@@ -470,6 +564,19 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk
return nb_frags;
}
+/* A verbatim stream has one entry per descriptor, so add up the packet's. */
+static u32 pkt_total_len(struct pkt_stream *pkt_stream, struct pkt *pkt, u32 nb_frags)
+{
+ u32 i, len = 0;
+
+ if (!pkt_stream->verbatim)
+ return pkt->len;
+
+ for (i = 0; i < nb_frags; i++)
+ len += pkt[i].len;
+ return len;
+}
+
static bool set_pkt_valid(int offset, u32 len)
{
return len <= MAX_ETH_JUMBO_SIZE;
@@ -654,7 +761,7 @@ static void pkt_stream_cancel(struct pkt_stream *pkt_stream)
}
static void pkt_generate(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, u64 addr, u32 len,
- u32 pkt_nb, u32 bytes_written)
+ u32 total_len, u32 pkt_nb, u32 bytes_written)
{
void *data = xsk_umem__get_data(umem->buffer, addr);
@@ -662,12 +769,12 @@ static void pkt_generate(struct xsk_socket_info *xsk, struct xsk_umem_info *umem
return;
if (!bytes_written) {
- gen_eth_hdr(xsk, data);
+ gen_pkt_hdr(xsk, data, total_len);
- len -= PKT_HDR_SIZE;
- data += PKT_HDR_SIZE;
+ len -= pkt_hdr_size;
+ data += pkt_hdr_size;
} else {
- bytes_written -= PKT_HDR_SIZE;
+ bytes_written -= pkt_hdr_size;
}
write_payload(data, pkt_nb, bytes_written, len);
@@ -770,7 +877,7 @@ static void pkt_dump(void *pkt, u32 len, bool eth_header)
for (i = 0; i < ETH_ALEN; i++)
ksft_print_msg("%02X", ethhdr->h_source[i]);
- data = pkt + PKT_HDR_SIZE;
+ data = pkt + pkt_hdr_size;
} else {
data = pkt;
}
@@ -867,11 +974,15 @@ static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 exp
pkt_data = data;
if (!bytes_processed) {
- pkt_data += PKT_HDR_SIZE / sizeof(*pkt_data);
- len -= PKT_HDR_SIZE;
+ if (len < pkt_hdr_size + sizeof(*pkt_data))
+ return false;
+ pkt_data += pkt_hdr_size / sizeof(*pkt_data);
+ len -= pkt_hdr_size;
} else {
- bytes_processed -= PKT_HDR_SIZE;
+ bytes_processed -= pkt_hdr_size;
}
+ if (len < sizeof(*pkt_data))
+ return false;
expected_seqnum = bytes_processed / sizeof(*pkt_data);
seqnum = ntohl(*pkt_data) & 0xffff;
@@ -1151,7 +1262,7 @@ bool all_packets_received(struct test_spec *test, struct xsk_socket_info *xsk, u
static int receive_pkts(struct test_spec *test)
{
- struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0};
+ struct timeval tv_end, tv_now, tv_timeout = {hw_test ? HW_THREAD_TMOUT : THREAD_TMOUT, 0};
DECLARE_BITMAP(bitmap, test->nb_sockets);
struct xsk_socket_info *xsk;
u32 sock_num = 0;
@@ -1246,7 +1357,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
for (i = 0; i < xsk->batch_size; i++) {
struct pkt *pkt = pkt_stream_get_next_tx_pkt(pkt_stream);
- u32 nb_frags_left, nb_frags, bytes_written = 0;
+ u32 nb_frags_left, nb_frags, total_len, bytes_written = 0;
if (!pkt)
break;
@@ -1258,6 +1369,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
break;
}
nb_frags_left = nb_frags;
+ total_len = pkt_total_len(pkt_stream, pkt, nb_frags);
while (nb_frags_left--) {
struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i);
@@ -1274,8 +1386,8 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
tx_desc->options = 0;
}
if (pkt->valid)
- pkt_generate(xsk, umem, tx_desc->addr, tx_desc->len, pkt->pkt_nb,
- bytes_written);
+ pkt_generate(xsk, umem, tx_desc->addr, tx_desc->len, total_len,
+ pkt->pkt_nb, bytes_written);
bytes_written += tx_desc->len;
print_verbose("Tx addr: %llx len: %u options: %u pkt_nb: %u\n",
@@ -1322,7 +1434,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
static int wait_for_tx_completion(struct xsk_socket_info *xsk)
{
- struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0};
+ struct timeval tv_end, tv_now, tv_timeout = {hw_test ? HW_THREAD_TMOUT : THREAD_TMOUT, 0};
int ret;
ret = gettimeofday(&tv_now, NULL);
@@ -1805,6 +1917,10 @@ static int xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_r
if (!ifobj_tx || !ifobj_is_local(ifobj_tx))
return 0;
+ /* A ZC driver can require XDP to be enabled for TX wakeups. */
+ if (hw_test && !ifobj_tx->rx_on && test->mode != TEST_MODE_ZC)
+ return 0;
+
if (xdp_prog_changed_tx(test))
err = xsk_reattach_xdp(ifobj_tx, test->xdp_prog_tx, test->xskmap_tx, test->mode);
@@ -2230,6 +2346,10 @@ int testapp_xdp_shared_umem(struct test_spec *test)
struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs;
int ret;
+ /* The XDP program picks the socket from the synthetic destination MAC. */
+ if (hw_test)
+ return TEST_SKIP;
+
test->total_steps = 1;
test->nb_sockets = 2;
@@ -2272,7 +2392,9 @@ int testapp_too_many_frags(struct test_spec *test)
u32 max_frags, i;
int ret = TEST_FAILURE;
- if (test->mode == TEST_MODE_ZC) {
+ if (max_frags_override) {
+ max_frags = max_frags_override;
+ } else if (test->mode == TEST_MODE_ZC) {
max_frags = xsk_get_cap(test, xdp_zc_max_segs);
} else {
max_frags = xsk_get_cap(test, max_skb_frags);
@@ -2366,6 +2488,7 @@ static int detect_ifobj_caps(struct ifobject *ifobj)
if (query_opts.feature_flags & NETDEV_XDP_ACT_RX_SG)
ifobj_set_cap(ifobj, XSK_CAP_MBUF);
if (query_opts.feature_flags & NETDEV_XDP_ACT_XSK_ZEROCOPY) {
+ ifobj_set_cap(ifobj, XSK_CAP_ZC_ADVERTISED);
if (query_opts.xdp_zc_max_segs > 1) {
ifobj_set_cap(ifobj, XSK_CAP_MBUF_ZC);
ifobj->caps.xdp_zc_max_segs = query_opts.xdp_zc_max_segs;
@@ -2394,6 +2517,12 @@ int init_iface(struct ifobject *ifobj)
return err;
}
+ err = xsk_get_mac(ifobj->ifname, ifobj->caps.mac);
+ if (err) {
+ ksft_print_msg("Error reading interface MAC address\n");
+ return err;
+ }
+
return detect_ifobj_caps(ifobj);
}
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 53e5032510a0..c37423030eb6 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -82,7 +82,9 @@ typedef int (*test_func_t)(struct test_spec *test);
struct xsk_peer;
/* The control channel to the other endpoint. */
-void xsk_set_endpoint(struct xsk_peer *peer);
+void xsk_set_endpoint(struct xsk_peer *peer, bool hw_test);
+int xsk_set_udp_packet_format(const char *src_ip, const char *dst_ip, u16 port);
+void xsk_set_max_frags(u32 max_frags);
struct xsk_socket_info {
struct xsk_ring_cons rx;
@@ -129,6 +131,7 @@ int hw_ring_size_reset(struct ifobject *ifobj);
#define XSK_CAP_HW_RING (1U << 3)
#define XSK_CAP_DRV (1U << 4)
#define XSK_CAP_ZC (1U << 5)
+#define XSK_CAP_ZC_ADVERTISED (1U << 6)
struct xsk_caps {
u32 flags;
@@ -136,6 +139,7 @@ struct xsk_caps {
u32 max_skb_frags;
u32 umem_tailroom;
u32 tx_max_pending;
+ u8 mac[ETH_ALEN];
};
struct ifobject {
@@ -152,7 +156,11 @@ struct ifobject {
struct set_hw_ring set_ring;
enum test_mode mode;
int ifindex;
+ u32 queue_id;
int mtu;
+ /* Above mtu when a hardware SKB-mode endpoint keeps a larger MTU. */
+ int dev_mtu;
+ int orig_mtu;
u32 bind_flags;
bool tx_on;
bool rx_on;
diff --git a/tools/testing/selftests/net/lib/xsk/xsk.c b/tools/testing/selftests/net/lib/xsk/xsk.c
index 6bd32caf5d08..bd2ef3ee9124 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/xsk.c
@@ -826,3 +826,40 @@ int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
close(sockfd);
return 0;
}
+
+static int xsk_ifreq_ioctl(const char *ifname, unsigned long req, struct ifreq *ifr)
+{
+ int fd, err = 0;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return -errno;
+
+ strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name) - 1);
+ if (ioctl(fd, req, ifr) < 0)
+ err = -errno;
+ close(fd);
+ return err;
+}
+
+int xsk_get_mac(const char *ifname, u8 mac[ETH_ALEN])
+{
+ struct ifreq ifr = {};
+ int err;
+
+ err = xsk_ifreq_ioctl(ifname, SIOCGIFHWADDR, &ifr);
+ if (!err)
+ memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+ return err;
+}
+
+int xsk_get_mtu(const char *ifname, int *mtu)
+{
+ struct ifreq ifr = {};
+ int err;
+
+ err = xsk_ifreq_ioctl(ifname, SIOCGIFMTU, &ifr);
+ if (!err)
+ *mtu = ifr.ifr_mtu;
+ return err;
+}
diff --git a/tools/testing/selftests/net/lib/xsk/xsk.h b/tools/testing/selftests/net/lib/xsk/xsk.h
index 3f1fea999763..c09ddbc38db9 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/xsk.h
@@ -245,6 +245,8 @@ int xsk_set_mtu(int ifindex, int mtu);
int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
+int xsk_get_mac(const char *ifname, u8 mac[ETH_ALEN]);
+int xsk_get_mtu(const char *ifname, int *mtu);
#ifdef __cplusplus
} /* extern "C" */
diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.c b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
index faec74aae379..15e048dd604b 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk_peer.c
+++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
@@ -4,6 +4,7 @@
#include <netdb.h>
#include <netinet/tcp.h>
#include <poll.h>
+#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
@@ -199,6 +200,9 @@ static int accept_tmout(int lfd)
* The launcher starts the listener and waits for its port before starting
* the connector, so connect() needs no retry. accept() is bounded in case
* the connector exits before it gets that far.
+ *
+ * The listener also says on stderr when it listens, so that a launcher can
+ * wait for that line instead of polling for the port over SSH.
*/
static int peer_open_tcp(const char *host, const char *port, bool listen_side)
{
@@ -219,6 +223,7 @@ static int peer_open_tcp(const char *host, const char *port, bool listen_side)
continue;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (!bind(lfd, ai->ai_addr, ai->ai_addrlen) && !listen(lfd, 1)) {
+ fprintf(stderr, "Listening for XSK peer on port %s\n", port);
fd = accept_tmout(lfd);
saved = errno;
close(lfd);
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index e66625810a9d..8b94dc3709ea 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -55,6 +55,9 @@
* l. If multi-buffer is supported, try various nasty combinations of descriptors to
* check if they pass the validation or not
*
+ * drivers/net/hw/xsk.py runs the hardware cases on a physical device in
+ * zero-copy mode, with an SKB-mode peer on a remote host.
+ *
* Flow:
* -----
* - test_xsk.sh starts two processes: Tx and Rx
@@ -85,6 +88,7 @@
#include <linux/align.h>
#include <arpa/inet.h>
#include <net/if.h>
+#include <netinet/ether.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
@@ -112,9 +116,29 @@ enum xsk_endpoint_role {
static enum test_mode opt_mode = TEST_MODE_ALL;
static u32 opt_run_test = RUN_ALL_TESTS;
static enum xsk_endpoint_role opt_endpoint_role = XSK_ENDPOINT_NONE;
+static bool opt_hw;
+static bool opt_listen;
static const char *opt_peer_host;
static const char *opt_peer_port;
static const char *opt_ifname;
+static const char *opt_udp_src;
+static const char *opt_udp_dst;
+static const struct ether_addr *opt_peer_mac;
+static struct ether_addr peer_mac;
+static u16 opt_udp_port;
+static u32 opt_queue;
+static u32 opt_max_frags;
+
+enum {
+ OPT_HW = 256,
+ OPT_LISTEN,
+ OPT_UDP_SRC,
+ OPT_UDP_DST,
+ OPT_UDP_PORT,
+ OPT_PEER_MAC,
+ OPT_QUEUE,
+ OPT_MAX_FRAGS,
+};
static void __exit_with_error(int error, const char *file, const char *func, int line)
{
@@ -178,6 +202,14 @@ static struct option long_options[] = {
{"endpoint", required_argument, 0, 'e'},
{"peer", required_argument, 0, 'p'},
{"peer-port", required_argument, 0, 'P'},
+ {"hw", no_argument, 0, OPT_HW},
+ {"listen", no_argument, 0, OPT_LISTEN},
+ {"udp-src", required_argument, 0, OPT_UDP_SRC},
+ {"udp-dst", required_argument, 0, OPT_UDP_DST},
+ {"udp-port", required_argument, 0, OPT_UDP_PORT},
+ {"peer-mac", required_argument, 0, OPT_PEER_MAC},
+ {"queue", required_argument, 0, OPT_QUEUE},
+ {"max-frags", required_argument, 0, OPT_MAX_FRAGS},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
@@ -196,6 +228,14 @@ static void print_usage(char **argv)
" -e, --endpoint Role of this endpoint: rx or tx\n"
" -p, --peer Control host: IPv4/IPv6 address or hostname\n"
" -P, --peer-port Control TCP port (1-65535)\n"
+ " --listen Listen instead of connecting (hardware peer)\n"
+ " --hw One physical-link case, controlled by xsk.py\n"
+ " --udp-src IP IPv4 source address for test packets\n"
+ " --udp-dst IP IPv4 destination address for test packets\n"
+ " --udp-port PORT UDP source and destination port\n"
+ " --peer-mac MAC Remote interface MAC address\n"
+ " --queue N AF_XDP queue to bind (default 0)\n"
+ " --max-frags N Fragment limit selected by the hardware runner\n"
" -h, --help Display this help and exit\n";
ksft_print_msg(str, basename(argv[0]));
@@ -220,6 +260,12 @@ static void bind_iface(struct ifobject *ifobj, const char *ifname, char **argv)
ksft_print_msg("Error: cannot initialize interface %s\n", ifobj->ifname);
ksft_exit_fail();
}
+ if (xsk_get_mtu(ifobj->ifname, &ifobj->mtu)) {
+ ksft_print_msg("Error: cannot read MTU of interface %s\n", ifobj->ifname);
+ ksft_exit_fail();
+ }
+ ifobj->dev_mtu = ifobj->mtu;
+ ifobj->orig_mtu = ifobj->mtu;
}
static void print_tests(void)
@@ -231,6 +277,18 @@ static void print_tests(void)
printf("%u: %s\n", i, tests[i].name);
}
+static u32 parse_u32(const char *arg, u32 min, u32 max, char **argv)
+{
+ unsigned long val;
+ char *end;
+
+ errno = 0;
+ val = strtoul(arg, &end, 10);
+ if (arg[0] < '0' || arg[0] > '9' || errno || *end || val < min || val > max)
+ print_usage(argv);
+ return val;
+}
+
static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx, int argc,
char **argv)
{
@@ -288,6 +346,32 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
case 'P':
opt_peer_port = optarg;
break;
+ case OPT_HW:
+ opt_hw = true;
+ break;
+ case OPT_LISTEN:
+ opt_listen = true;
+ break;
+ case OPT_UDP_SRC:
+ opt_udp_src = optarg;
+ break;
+ case OPT_UDP_DST:
+ opt_udp_dst = optarg;
+ break;
+ case OPT_UDP_PORT:
+ opt_udp_port = parse_u32(optarg, 1, UINT16_MAX, argv);
+ break;
+ case OPT_PEER_MAC:
+ opt_peer_mac = ether_aton_r(optarg, &peer_mac);
+ if (!opt_peer_mac)
+ print_usage(argv);
+ break;
+ case OPT_QUEUE:
+ opt_queue = parse_u32(optarg, 0, UINT32_MAX, argv);
+ break;
+ case OPT_MAX_FRAGS:
+ opt_max_frags = parse_u32(optarg, 1, UINT16_MAX, argv);
+ break;
case 'h':
default:
print_usage(argv);
@@ -298,6 +382,10 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
!*opt_peer_host || !opt_peer_port || opt_run_test == RUN_ALL_TESTS ||
opt_mode == TEST_MODE_ALL)
print_usage(argv);
+ if (opt_hw && (!opt_udp_src || !opt_udp_dst || !opt_udp_port || !opt_peer_mac))
+ print_usage(argv);
+ if ((opt_listen || opt_max_frags) && !opt_hw)
+ print_usage(argv);
}
static void xsk_unload_xdp_programs(struct ifobject *ifobj)
@@ -305,12 +393,8 @@ static void xsk_unload_xdp_programs(struct ifobject *ifobj)
xsk_xdp_progs__destroy(ifobj->xdp_progs);
}
-static int run_pkt_test(struct test_spec *test)
+static void report_pkt_test(struct test_spec *test, int ret)
{
- int ret;
-
- ret = test->test_func(test);
-
switch (ret) {
case TEST_PASS:
ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test),
@@ -328,7 +412,15 @@ static int run_pkt_test(struct test_spec *test)
ksft_test_result_fail("FAIL: %s %s%s -- Unexpected returned value (%d)\n",
mode_string(test), busy_poll_string(test), test->name, ret);
}
+}
+static int run_pkt_test(struct test_spec *test)
+{
+ int ret;
+
+ ret = test->test_func(test);
+ if (!opt_hw)
+ report_pkt_test(test, ret);
pkt_stream_restore_default(test);
return ret;
}
@@ -363,7 +455,11 @@ static bool is_xdp_supported(int ifindex)
static u32 detect_mode_caps(struct ifobject *ifobj)
{
- if (is_xdp_supported(ifobj->ifindex)) {
+ if (opt_hw && opt_mode == TEST_MODE_ZC &&
+ ifobj_has_cap(ifobj, XSK_CAP_ZC_ADVERTISED)) {
+ /* Trust the advertised flag; an XDP_ZEROCOPY bind never falls back to copy. */
+ ifobj_set_cap(ifobj, XSK_CAP_DRV | XSK_CAP_ZC);
+ } else if (!opt_hw && is_xdp_supported(ifobj->ifindex)) {
ifobj_set_cap(ifobj, XSK_CAP_DRV);
if (ifobj_zc_avail(ifobj))
ifobj_set_cap(ifobj, XSK_CAP_ZC);
@@ -381,8 +477,10 @@ static bool mode_supported(enum test_mode mode, u32 caps)
return caps & XSK_CAP_ZC;
}
-static void cleanup_iface(struct ifobject *ifobj)
+static int cleanup_iface(struct ifobject *ifobj)
{
+ int err = 0;
+
/* A peer shadow has a skeleton but no bound interface. */
if (!ifobj_is_local(ifobj))
goto unload;
@@ -393,8 +491,16 @@ static void cleanup_iface(struct ifobject *ifobj)
xsk_detach_xdp_program(ifobj->ifindex,
ifobj->mode == TEST_MODE_SKB ?
XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE);
+ /* After the detach, as an attached program can cap the MTU. */
+ if (ifobj->dev_mtu != ifobj->orig_mtu) {
+ err = xsk_set_mtu(ifobj->ifindex, ifobj->orig_mtu);
+ if (err)
+ ksft_print_msg("Failed to restore MTU %d on %s\n",
+ ifobj->orig_mtu, ifobj->ifname);
+ }
unload:
xsk_unload_xdp_programs(ifobj);
+ return err;
}
/* Connect the two endpoints without negotiating the remote NIC's capabilities. */
@@ -405,8 +511,9 @@ static int setup_peer(struct ifobject *local, struct ifobject *shadow,
if (xsk_load_xdp_programs(shadow))
return TEST_FAILURE;
- /* TX listens and RX connects. */
+ /* Generic TX listens; Python chooses the listener for hardware cases. */
*peer = xsk_peer_open(opt_peer_host, opt_peer_port,
+ opt_hw ? opt_listen :
opt_endpoint_role == XSK_ENDPOINT_TX);
if (!*peer) {
ksft_print_msg("Failed to connect XSK peer: %s\n", strerror(errno));
@@ -414,7 +521,10 @@ static int setup_peer(struct ifobject *local, struct ifobject *shadow,
}
shadow->caps = local->caps;
- xsk_set_endpoint(*peer);
+ if (opt_hw)
+ memcpy(shadow->caps.mac, opt_peer_mac->ether_addr_octet, ETH_ALEN);
+
+ xsk_set_endpoint(*peer, opt_hw);
return TEST_PASS;
}
@@ -429,6 +539,7 @@ int main(int argc, char **argv)
struct test_spec test = {};
int ret = TEST_FAILURE;
u32 caps;
+ int err;
/* Use libbpf 1.0 API mode */
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
@@ -470,6 +581,11 @@ int main(int argc, char **argv)
ksft_exit_xfail();
}
+ xsk_set_max_frags(opt_max_frags);
+ if (opt_hw && xsk_set_udp_packet_format(opt_udp_src, opt_udp_dst,
+ opt_udp_port))
+ print_usage(argv);
+
/* swap_directions() swaps the workers, so the shadow needs one too. */
ifobj_tx->func_ptr = worker_testapp_validate_tx;
ifobj_rx->func_ptr = worker_testapp_validate_rx;
@@ -481,6 +597,8 @@ int main(int argc, char **argv)
shadow_ifobj = ifobj_tx;
}
bind_iface(local_ifobj, opt_ifname, argv);
+ local_ifobj->queue_id = opt_queue;
+ /* The zero-copy probe binds to queue_id, so set it first. */
caps = detect_mode_caps(local_ifobj);
test.tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE);
@@ -501,27 +619,32 @@ int main(int argc, char **argv)
}
/* Line-buffer stdout so verdicts reach a capturing launcher live. */
- ksft_print_header();
- ksft_set_plan(1);
+ if (!opt_hw) {
+ ksft_print_header();
+ ksft_set_plan(1);
+ }
test_init(&test, ifobj_tx, ifobj_rx, opt_mode, &tests[opt_run_test]);
ret = run_pkt_test(&test);
out:
- xsk_set_endpoint(NULL);
- cleanup_iface(ifobj_tx);
- cleanup_iface(ifobj_rx);
+ xsk_set_endpoint(NULL, false);
+ err = cleanup_iface(ifobj_tx);
+ err |= cleanup_iface(ifobj_rx);
+ /* xsk.py trusts a passed or skipped endpoint to have undone the MTU. */
+ if (err)
+ ret = TEST_FAILURE;
xsk_peer_close(peer);
pkt_stream_delete(test.tx_pkt_stream_default);
pkt_stream_delete(test.rx_pkt_stream_default);
ifobject_delete(ifobj_tx);
ifobject_delete(ifobj_rx);
- if (ret == TEST_SKIP && ksft_test_num()) {
+ if (ret == TEST_SKIP && !opt_hw && ksft_test_num()) {
ksft_print_cnts();
return KSFT_SKIP;
}
if (ret == TEST_SKIP)
- ksft_exit_skip("mode not supported\n");
+ ksft_exit_skip("mode or test not supported\n");
if (ret)
ksft_exit_fail();
else
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (9 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner Maciej Fijalkowski
` (3 subsequent siblings)
14 siblings, 1 reply; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
The XDP programs of xskxceiver redirect every packet to the XSKMAP and
drop what no socket takes, so a hardware run needs a link that carries
nothing but the test flow. While a program is attached, neighbour
discovery, LLDP or SSH on the tested link are dropped, and on the
remote receiver, whose socket shares queue 0 with RSS, such a packet
reaches the socket and fails the case.
In hardware mode, redirect only the UDP/IPv4 packets sent to the test
port (--udp-port) and pass everything else to the stack. The veth mode
keeps redirecting every packet, as its frames carry no IP header.
I think that xdpsock used to do matching based on rx queue index, but
here it would not be enough: to need no more than ntuple support from
the remote, the runner leaves its RSS table alone, so the remote's
socket shares queue 0 with whatever RSS hashes there. The port also
keeps the drop, tail-adjust and metadata programs from acting on
unrelated frames.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 2 +
.../selftests/net/lib/xsk/xsk_xdp_progs.bpf.c | 39 +++++++++++++++++++
2 files changed, 41 insertions(+)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 542c0579062a..1033a5430697 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -2454,6 +2454,8 @@ int xsk_load_xdp_programs(struct ifobject *ifobj)
if (libbpf_get_error(ifobj->xdp_progs))
return libbpf_get_error(ifobj->xdp_progs);
+ /* The shadow's copy too, swapped directions attach its programs. */
+ ifobj->xdp_progs->bss->hw_port = udp_port;
return 0;
}
diff --git a/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c b/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
index 023d8befd4ca..716360d023a7 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
+++ b/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
@@ -5,7 +5,11 @@
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/in.h>
#include <linux/errno.h>
+#include <bpf/bpf_endian.h>
+#include <stdbool.h>
#include "xsk_xdp_common.h"
struct {
@@ -18,9 +22,34 @@ struct {
static unsigned int idx;
int adjust_value = 0;
int count = 0;
+/* UDP port of the hardware test flow, 0 redirects every packet */
+__u16 hw_port;
+
+static __always_inline bool is_test_packet(struct xdp_md *xdp)
+{
+ void *data_end = (void *)(long)xdp->data_end;
+ void *data = (void *)(long)xdp->data;
+ struct ethhdr *eth = data;
+ struct udphdr *udp;
+ struct iphdr *ip;
+
+ if (!hw_port)
+ return true;
+ if ((void *)(eth + 1) > data_end ||
+ eth->h_proto != bpf_htons(ETH_P_IP))
+ return false;
+ ip = (void *)(eth + 1);
+ if ((void *)(ip + 1) > data_end || ip->ihl != 5 ||
+ ip->protocol != IPPROTO_UDP)
+ return false;
+ udp = (void *)(ip + 1);
+ return (void *)(udp + 1) <= data_end && udp->dest == bpf_htons(hw_port);
+}
SEC("xdp.frags") int xsk_def_prog(struct xdp_md *xdp)
{
+ if (!is_test_packet(xdp))
+ return XDP_PASS;
return bpf_redirect_map(&xsk, 0, XDP_DROP);
}
@@ -28,6 +57,8 @@ SEC("xdp.frags") int xsk_xdp_drop(struct xdp_md *xdp)
{
static unsigned int drop_idx;
+ if (!is_test_packet(xdp))
+ return XDP_PASS;
/* Drop every other packet */
if (drop_idx++ % 2)
return XDP_DROP;
@@ -41,6 +72,9 @@ SEC("xdp.frags") int xsk_xdp_populate_metadata(struct xdp_md *xdp)
struct xdp_info *meta;
int err;
+ if (!is_test_packet(xdp))
+ return XDP_PASS;
+
/* Reserve enough for all custom metadata. */
err = bpf_xdp_adjust_meta(xdp, -(int)sizeof(struct xdp_info));
if (err)
@@ -64,6 +98,8 @@ SEC("xdp") int xsk_xdp_shared_umem(struct xdp_md *xdp)
void *data_end = (void *)(long)xdp->data_end;
struct ethhdr *eth = data;
+ if (!is_test_packet(xdp))
+ return XDP_PASS;
if (eth + 1 > data_end)
return XDP_DROP;
@@ -80,6 +116,9 @@ SEC("xdp.frags") int xsk_xdp_adjust_tail(struct xdp_md *xdp)
__u32 buff_len, curr_buff_len;
int ret;
+ if (!is_test_packet(xdp))
+ return XDP_PASS;
+
buff_len = bpf_xdp_get_buff_len(xdp);
if (buff_len == 0)
return XDP_DROP;
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (10 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-09 12:12 ` Björn Töpel
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
` (2 subsequent siblings)
14 siblings, 1 reply; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Keep case names, functions, and hardware directions in an X-macro
header. The C test table keeps its order, so the test IDs do not
change. This allows the Python runner added by the next patch to read
the same definitions. Only the runner uses the hardware directions,
the DUT roles in which it runs a case; xskxceiver ignores them.
Install the header with selftests so the Python runner can read it
from an installed tree.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
tools/testing/selftests/net/lib/Makefile | 2 +-
.../testing/selftests/net/lib/xsk/test_xsk.h | 46 ++-------------
.../net/lib/xsk/test_xsk_case_defs.h | 57 +++++++++++++++++++
3 files changed, 62 insertions(+), 43 deletions(-)
create mode 100644 tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index 066d011c478a..15353021cecf 100644
--- a/tools/testing/selftests/net/lib/Makefile
+++ b/tools/testing/selftests/net/lib/Makefile
@@ -28,7 +28,7 @@ TEST_GEN_FILES := \
xdp_helper \
# end of TEST_GEN_FILES
-TEST_INCLUDES := $(wildcard py/*.py sh/*.sh)
+TEST_INCLUDES := $(wildcard py/*.py sh/*.sh) xsk/test_xsk_case_defs.h
include ../../lib.mk
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index c37423030eb6..e1ffd054c8f7 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -309,48 +309,10 @@ int worker_testapp_validate_rx(struct test_spec *test);
int worker_testapp_validate_tx(struct test_spec *test);
static const struct test_spec tests[] = {
- {.name = "SEND_RECEIVE", .test_func = testapp_send_receive},
- {.name = "SEND_RECEIVE_2K_FRAME", .test_func = testapp_send_receive_2k_frame},
- {.name = "SEND_RECEIVE_SINGLE_PKT", .test_func = testapp_single_pkt},
- {.name = "POLL_RX", .test_func = testapp_poll_rx},
- {.name = "POLL_TX", .test_func = testapp_poll_tx},
- {.name = "POLL_RXQ_FULL", .test_func = testapp_poll_rxq_tmout},
- {.name = "POLL_TXQ_FULL", .test_func = testapp_poll_txq_tmout},
- {.name = "ALIGNED_INV_DESC", .test_func = testapp_aligned_inv_desc},
- {.name = "ALIGNED_INV_DESC_2K_FRAME_SIZE", .test_func = testapp_aligned_inv_desc_2k_frame},
- {.name = "UMEM_HEADROOM", .test_func = testapp_headroom},
- {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional},
- {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped},
- {.name = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs},
- {.name = "STAT_RX_FULL", .test_func = testapp_stats_rx_full},
- {.name = "STAT_FILL_EMPTY", .test_func = testapp_stats_fill_empty},
- {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup},
- {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop},
- {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem},
- {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata},
- {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb},
- {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb},
- {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags},
- {.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink},
- {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer},
- /* Flaky tests */
- {.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb},
- {.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow},
- {.name = "XDP_ADJUST_TAIL_GROW_MULTI_BUFF", .test_func = testapp_adjust_tail_grow_mb},
- {.name = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb},
- /* Tests with huge page dependency */
- {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned},
- {.name = "UNALIGNED_INV_DESC", .test_func = testapp_unaligned_inv_desc},
- {.name = "UNALIGNED_INV_DESC_4001_FRAME_SIZE",
- .test_func = testapp_unaligned_inv_desc_4001_frame},
- {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS",
- .test_func = testapp_send_receive_unaligned_mb},
- {.name = "UNALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_unaligned_inv_desc_mb},
- /* Test with HW ring size dependency */
- {.name = "HW_SW_MIN_RING_SIZE", .test_func = testapp_hw_sw_min_ring_size},
- {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size},
- /* Too long test */
- {.name = "TEARDOWN", .test_func = testapp_teardown},
+#define XSK_TEST_CASE(_name, _func, ...) \
+ { .name = #_name, .test_func = _func },
+#include "test_xsk_case_defs.h"
+#undef XSK_TEST_CASE
};
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h b/tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
new file mode 100644
index 000000000000..873e89c9d4d5
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Each XSK_TEST_CASE entry has a name, function, and hardware directions.
+ * Only xsk.py reads the directions, the DUT roles in which it runs a case.
+ *
+ * Keep this list in test ID order. It is shared by xskxceiver and xsk.py.
+ */
+XSK_TEST_CASE(SEND_RECEIVE, testapp_send_receive, XSK_HW_RX | XSK_HW_TX)
+XSK_TEST_CASE(SEND_RECEIVE_2K_FRAME, testapp_send_receive_2k_frame,
+ XSK_HW_RX | XSK_HW_TX)
+XSK_TEST_CASE(SEND_RECEIVE_SINGLE_PKT, testapp_single_pkt,
+ XSK_HW_RX | XSK_HW_TX)
+XSK_TEST_CASE(POLL_RX, testapp_poll_rx, XSK_HW_RX)
+XSK_TEST_CASE(POLL_TX, testapp_poll_tx, XSK_HW_TX)
+XSK_TEST_CASE(POLL_RXQ_FULL, testapp_poll_rxq_tmout, XSK_HW_RX)
+XSK_TEST_CASE(POLL_TXQ_FULL, testapp_poll_txq_tmout, XSK_HW_TX)
+XSK_TEST_CASE(ALIGNED_INV_DESC, testapp_aligned_inv_desc, XSK_HW_TX)
+XSK_TEST_CASE(ALIGNED_INV_DESC_2K_FRAME_SIZE,
+ testapp_aligned_inv_desc_2k_frame, XSK_HW_TX)
+XSK_TEST_CASE(UMEM_HEADROOM, testapp_headroom, XSK_HW_RX)
+XSK_TEST_CASE(BIDIRECTIONAL, testapp_bidirectional, 0)
+XSK_TEST_CASE(STAT_RX_DROPPED, testapp_stats_rx_dropped, 0)
+XSK_TEST_CASE(STAT_TX_INVALID, testapp_stats_tx_invalid_descs, XSK_HW_TX)
+XSK_TEST_CASE(STAT_RX_FULL, testapp_stats_rx_full, 0)
+XSK_TEST_CASE(STAT_FILL_EMPTY, testapp_stats_fill_empty, 0)
+XSK_TEST_CASE(XDP_PROG_CLEANUP, testapp_xdp_prog_cleanup, XSK_HW_RX)
+XSK_TEST_CASE(XDP_DROP_HALF, testapp_xdp_drop, 0)
+XSK_TEST_CASE(XDP_SHARED_UMEM, testapp_xdp_shared_umem, 0)
+XSK_TEST_CASE(XDP_METADATA_COPY, testapp_xdp_metadata, XSK_HW_RX)
+XSK_TEST_CASE(XDP_METADATA_COPY_MULTI_BUFF, testapp_xdp_metadata_mb, 0)
+XSK_TEST_CASE(ALIGNED_INV_DESC_MULTI_BUFF, testapp_aligned_inv_desc_mb,
+ XSK_HW_TX)
+XSK_TEST_CASE(TOO_MANY_FRAGS, testapp_too_many_frags, XSK_HW_TX)
+XSK_TEST_CASE(XDP_ADJUST_TAIL_SHRINK, testapp_adjust_tail_shrink, 0)
+XSK_TEST_CASE(TX_QUEUE_CONSUMER, testapp_tx_queue_consumer, 0)
+/* Flaky tests */
+XSK_TEST_CASE(XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF,
+ testapp_adjust_tail_shrink_mb, 0)
+XSK_TEST_CASE(XDP_ADJUST_TAIL_GROW, testapp_adjust_tail_grow, 0)
+XSK_TEST_CASE(XDP_ADJUST_TAIL_GROW_MULTI_BUFF, testapp_adjust_tail_grow_mb, 0)
+XSK_TEST_CASE(SEND_RECEIVE_9K_PACKETS, testapp_send_receive_mb,
+ XSK_HW_RX | XSK_HW_TX)
+/* Tests with huge page dependency */
+XSK_TEST_CASE(SEND_RECEIVE_UNALIGNED, testapp_send_receive_unaligned,
+ XSK_HW_RX | XSK_HW_TX)
+XSK_TEST_CASE(UNALIGNED_INV_DESC, testapp_unaligned_inv_desc, XSK_HW_TX)
+XSK_TEST_CASE(UNALIGNED_INV_DESC_4001_FRAME_SIZE,
+ testapp_unaligned_inv_desc_4001_frame, XSK_HW_TX)
+XSK_TEST_CASE(SEND_RECEIVE_UNALIGNED_9K_PACKETS,
+ testapp_send_receive_unaligned_mb, XSK_HW_RX | XSK_HW_TX)
+XSK_TEST_CASE(UNALIGNED_INV_DESC_MULTI_BUFF, testapp_unaligned_inv_desc_mb,
+ XSK_HW_TX)
+/* Tests with HW ring size dependency */
+XSK_TEST_CASE(HW_SW_MIN_RING_SIZE, testapp_hw_sw_min_ring_size, 0)
+XSK_TEST_CASE(HW_SW_MAX_RING_SIZE, testapp_hw_sw_max_ring_size, 0)
+/* Too long test */
+XSK_TEST_CASE(TEARDOWN, testapp_teardown, 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (11 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-08 21:37 ` Jakub Kicinski
` (2 more replies)
2026-10-08 11:49 ` [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
2026-10-08 21:30 ` [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Jakub Kicinski
14 siblings, 3 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Run each hardware AF_XDP case as a separate DUT and remote xskxceiver
pair. The DUT endpoint uses zero-copy mode and the remote endpoint uses
SKB mode, without negotiating remote capabilities. The cases, and the
DUT direction each of them covers, come from test_xsk_case_defs.h and
become KTAP variants named <rx|tx>_<case>, so -t and -T select them as
for any other ksft_variants() test.
Use the networking selftest Python helpers for remote execution, process
lifetime, deferred restoration, TAP variants and random control ports.
Each case sets up only what its DUT direction needs and undoes it with
defer() when it ends: RX cases take the last DUT queue out of RSS and
steer the test UDP flow to it, TX cases steer the flow to queue 0 on the
remote, where its SKB-mode socket is bound, and UNALIGNED cases reserve
hugepages on both hosts. A setup failure therefore skips only the cases
that need that setup: a one-channel DUT still runs the TX cases and a
remote without ntuple filters still runs the RX ones.
xsk.py -b runs the cases as test_xsk_busy_poll instead, like the
busy-poll pass of test_xsk.sh. The DUT endpoint then busy polls, and
each case sets napi_defer_hard_irqs and gro_flush_timeout on the DUT to
the values test_xsk.sh uses on veth, so that SO_PREFER_BUSY_POLL keeps
the device IRQs masked, and restores them when it ends. The remote
endpoint does not busy poll.
Each remote command costs an SSH login, so the runner keeps them few. It
adds a steering rule first and checks ntuple-filters only if the NIC
refuses the rule, and it starts the DUT endpoint once the remote
endpoint prints on stderr that it listens, instead of polling the remote
for the port. The endpoints detach their XDP programs and restore the
MTU when they exit, and the runner does so for them only after one fails
or is killed. Only the deployed remote binary, the DUT zero-copy check
and the idle MTU of the remote link are shared between cases. The remote
link is not checked again, as a remote endpoint replaces an XDP program
that an earlier one left behind.
xsk.py runs the xskxceiver built by net/lib, which drv-net already pulls
in as an install dependency, and deploys it to the remote.
XSK_REMOTE_BIN names a remote path to copy it to instead, or, with
XSK_REMOTE_DEPLOY=0, an already installed binary. XSK_REMOTE_SUDO runs
the remote commands through sudo -n, and XSK_UDP_PORT overrides the port
of the test flow.
The remote is either another host over SSH or the peer port of the DUT
host in a network namespace, as on the drv-net setups that loop two
ports of one NIC back to back. With SSH, the control channel connects to
the SSH host. A namespace is reached only over the tested link, so the
control channel connects to REMOTE_V4 there and relies on the XDP
programs passing non-test traffic to the stack. The DUT TX socket fills
no RX buffers, so its zero-copy queue drops what it receives; with a
namespace remote, DUT TX cases therefore also take that queue out of RSS
so the control connection does not land on it.
Add xsk.py to TEST_PROGS and to the AF_XDP entry in MAINTAINERS, and
CONFIG_XDP_SOCKETS to the drv-net config.
Assisted-by: LLM
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
MAINTAINERS | 1 +
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/config | 1 +
tools/testing/selftests/drivers/net/hw/xsk.py | 417 ++++++++++++++++++
tools/testing/selftests/net/lib/Makefile | 2 +-
5 files changed, 421 insertions(+), 1 deletion(-)
create mode 100755 tools/testing/selftests/drivers/net/hw/xsk.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 0baa0d037c73..c170ea3267c9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29778,6 +29778,7 @@ F: include/net/xsk_buff_pool.h
F: include/uapi/linux/if_xdp.h
F: include/uapi/linux/xdp_diag.h
F: net/xdp/
+F: tools/testing/selftests/drivers/net/hw/xsk.py
F: tools/testing/selftests/net/*xsk*
F: tools/testing/selftests/net/lib/xsk/
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 18ce235a77f8..02e48e74f6c6 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -52,6 +52,7 @@ TEST_PROGS = \
uso.py \
vlan.py \
xdp_metadata.py \
+ xsk.py \
xsk_reconfig.py \
#
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index c6c2b64bb712..f28335170d6b 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -26,4 +26,5 @@ CONFIG_UDMABUF=y
CONFIG_USER_NS=y
CONFIG_VLAN_8021Q=m
CONFIG_VXLAN=y
+CONFIG_XDP_SOCKETS=y
CONFIG_XFRM_USER=y
diff --git a/tools/testing/selftests/drivers/net/hw/xsk.py b/tools/testing/selftests/drivers/net/hw/xsk.py
new file mode 100755
index 000000000000..a33d4af869c7
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/xsk.py
@@ -0,0 +1,417 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""AF_XDP zero-copy hardware tests with an SKB-mode XSK peer."""
+
+import re
+import shlex
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+from lib.py import (EthtoolFamily, KsftFailEx, KsftNamedVariant, KsftSkipEx,
+ NetdevFamily, NetDrvEpEnv, bkg, cmd, defer, ethtool,
+ fd_read_timeout, ip, ksft_exit, ksft_pr, ksft_run,
+ ksft_variants, rand_port)
+
+CASE_TIMEOUT = 120
+LISTEN_TIMEOUT = 30
+NR_HUGEPAGES = 32
+SKIP_CODE = 4
+XSK_BIN = (Path(__file__).parent / "../../../net/lib/xskxceiver").resolve()
+XSK_CASES = XSK_BIN.parent / "xsk/test_xsk_case_defs.h"
+
+
+def _hardware_cases():
+ defs = XSK_CASES.read_text()
+ entries = re.findall(r"(?m)^XSK_TEST_CASE\((\w+),\s*\w+,\s*([^)]+)\)", defs)
+ if not entries or len(entries) != defs.count("XSK_TEST_CASE("):
+ raise KsftFailEx(f"cannot parse {XSK_CASES}")
+ variants = []
+ for test_id, (name, hw) in enumerate(entries):
+ flags = {flag.strip() for flag in hw.split("|")}
+ if not flags <= {"0", "XSK_HW_RX", "XSK_HW_TX"}:
+ raise KsftFailEx(f"invalid hardware directions for {name}: {hw}")
+ variants += [KsftNamedVariant(f"{d}_{name.lower()}", name, d, test_id)
+ for d in ("rx", "tx") if f"XSK_HW_{d.upper()}" in flags]
+ return variants
+
+
+def _netns_remote(cfg):
+ return cfg.env.get("REMOTE_TYPE") == "netns"
+
+
+def _control_addr(cfg):
+ """An SSH remote is reached over its management link. A netns remote is
+ reached only over the tested link, where the XDP programs pass the
+ control connection to the stack."""
+ if _netns_remote(cfg):
+ return cfg.remote_addr_v["4"]
+ return cfg.remote.name.rpartition("@")[2]
+
+
+def _root_cmd(command, host=None, sudo=False, **kwargs):
+ if sudo:
+ command = "sudo -n " + command
+ return cmd(command, host=host, **kwargs)
+
+
+def _remote_binary(cfg, local_binary):
+ binary = cfg.env.get("XSK_REMOTE_BIN")
+ if not binary:
+ return cfg.remote.deploy(local_binary)
+ if cfg.env.get("REMOTE_TYPE") != "ssh":
+ raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
+ if cfg.env.get("XSK_REMOTE_DEPLOY", "1") == "0":
+ result = cmd(f"test -x {shlex.quote(binary)}", host=cfg.remote,
+ fail=False)
+ if result.ret:
+ raise KsftSkipEx(f"remote xskxceiver is not executable: {binary}")
+ return binary
+ upload = binary + ".upload"
+ cmd(["scp", "-q", str(local_binary), f"{cfg.remote.name}:{upload}"],
+ shell=False)
+ cmd(f"mv -f {shlex.quote(upload)} {shlex.quote(binary)}",
+ host=cfg.remote)
+ return binary
+
+
+def _remote_sudo(cfg):
+ return cfg.env.get("XSK_REMOTE_SUDO", "").lower() not in ("", "0", "false")
+
+
+def _restore_mtu(ifname, mtu, host=None, sudo=False):
+ actual = ip(f"link show dev {shlex.quote(ifname)}", json=True,
+ host=host)[0]["mtu"]
+ if actual == mtu:
+ return
+ _root_cmd(f"ip link set dev {shlex.quote(ifname)} mtu {mtu}",
+ host=host, sudo=sudo)
+
+
+def _reserve_hugepages(host=None, sudo=False):
+ have = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
+ if have >= NR_HUGEPAGES:
+ return
+ _root_cmd(f"sysctl -qw vm.nr_hugepages={NR_HUGEPAGES}", host=host,
+ sudo=sudo, fail=False)
+ got = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
+ if got != have:
+ defer(_root_cmd, f"sysctl -qw vm.nr_hugepages={have}", host=host,
+ sudo=sudo)
+ if got < NR_HUGEPAGES:
+ raise KsftSkipEx(f"only {got}/{NR_HUGEPAGES} hugepages available")
+
+
+def _prefer_busy_poll(ifname):
+ """SO_PREFER_BUSY_POLL keeps the device IRQs masked between busy polls
+ only while napi_defer_hard_irqs and gro_flush_timeout are set. Use the
+ values that test_xsk.sh sets on veth."""
+ for knob, value in (("napi_defer_hard_irqs", "2"),
+ ("gro_flush_timeout", "200000")):
+ path = Path("/sys/class/net", ifname, knob)
+ orig = path.read_text(encoding="utf-8").strip()
+ if orig != value:
+ path.write_text(value, encoding="utf-8")
+ defer(path.write_text, orig, encoding="utf-8")
+
+
+def _feature_state(ifname, feature, host=None):
+ """Read an ethtool -k feature. Parse the text output, as older ethtool
+ versions can't print -k as JSON."""
+ output = ethtool(f"-k {shlex.quote(ifname)}", host=host).stdout
+ match = re.search(rf"^{re.escape(feature)}:\s+(on|off)(.*)$", output,
+ re.MULTILINE)
+ if not match:
+ raise KsftSkipEx(f"ethtool does not report {feature} on {ifname}")
+ return {"active": match[1] == "on", "fixed": "[fixed]" in match[2]}
+
+
+def _steer_udp(ifname, port, queue, host=None, sudo=False):
+ dev = shlex.quote(ifname)
+ rule = f"ethtool -N {dev} flow-type udp4 dst-port {port} action {queue}"
+ # Look at ntuple-filters only once the rule is refused, as every command
+ # on the remote costs a round trip.
+ added = _root_cmd(rule, host=host, sudo=sudo, fail=False)
+ if added.ret:
+ ntuple = _feature_state(ifname, "ntuple-filters", host)
+ if not ntuple["active"]:
+ if ntuple["fixed"]:
+ raise KsftSkipEx("NIC does not support ntuple filters")
+ _root_cmd(f"ethtool -K {dev} ntuple-filters on", host=host,
+ sudo=sudo)
+ defer(_root_cmd, f"ethtool -K {dev} ntuple-filters off",
+ host=host, sudo=sudo)
+ added = _root_cmd(rule, host=host, sudo=sudo, fail=False)
+ if added.ret:
+ ksft_pr(repr(added))
+ raise KsftSkipEx(f"cannot steer UDP flow to queue {queue} on {ifname}")
+ match = re.search(r"ID (\d+)", added.stdout)
+ if not match:
+ raise KsftFailEx(f"cannot identify new ntuple rule: {added.stdout}")
+ defer(_root_cmd, f"ethtool -N {dev} delete {match[1]}", host=host,
+ sudo=sudo)
+
+
+def _reserve_queue(cfg):
+ """Move the last RX queue out of default RSS and return it."""
+ header = {"dev-index": cfg.ifindex}
+ channels = cfg.ethnl.channels_get({"header": header})
+ count = (channels.get("combined-count") or 0) + (channels.get("rx-count") or 0)
+ if count < 2:
+ raise KsftSkipEx("RX queue isolation needs at least two channels")
+ queue = count - 1
+
+ try:
+ rss = cfg.ethnl.rss_get({"header": header})
+ saved = list(rss["indir"])
+ except Exception as exc:
+ raise KsftSkipEx(f"cannot read RSS indirection table: {exc}") from exc
+ if queue in saved:
+ updated = [0 if entry == queue else entry for entry in saved]
+ try:
+ cfg.ethnl.rss_set({"header": header, "indir": updated})
+ except Exception as exc:
+ raise KsftSkipEx(f"cannot reserve RX queue in RSS: {exc}") from exc
+ defer(cfg.ethnl.rss_set, {"header": header, "indir": saved})
+ actual = cfg.ethnl.rss_get({"header": header}).get("indir")
+ if actual != updated:
+ raise KsftSkipEx("NIC did not remove the reserved queue from RSS")
+ return queue
+
+
+def _reserve_rx_queue(cfg, port):
+ """Reserve an RX queue outside RSS and steer the test UDP flow to it."""
+ queue = _reserve_queue(cfg)
+ _steer_udp(cfg.ifname, port, queue)
+ return queue
+
+
+def _case_args(cfg, name):
+ if name != "TOO_MANY_FRAGS":
+ return []
+ try:
+ info = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
+ except Exception as exc:
+ raise KsftSkipEx(f"cannot query DUT fragment limit: {exc}") from exc
+ max_frags = info.get("xdp-zc-max-segs", 0)
+ if max_frags <= 1:
+ raise KsftSkipEx("DUT does not advertise multi-buffer zero-copy")
+ return ["--max-frags", str(max_frags)]
+
+
+def _idle_mtu(ifname, host=None):
+ """Return the MTU of a link that has no XDP program attached."""
+ info = ip(f"link show dev {shlex.quote(ifname)}", json=True, host=host)[0]
+ if "xdp" in info:
+ raise KsftSkipEx(f"{ifname} already has an XDP program")
+ return info["mtu"]
+
+
+def _remote_idle_mtu(cfg):
+ """Look at the remote link once. Every case leaves its MTU as it found
+ it and an endpoint replaces a program left behind by an earlier case,
+ so looking again would only add a round trip to each case."""
+ if not hasattr(cfg, "xsk_remote_mtu"):
+ cfg.xsk_remote_mtu = _idle_mtu(cfg.remote_ifname, cfg.remote)
+ return cfg.xsk_remote_mtu
+
+
+def _detach_xdp(ifname, mode, host=None, sudo=False):
+ """A killed endpoint leaves its XDP program behind; name the mode, as a
+ bare "xdp off" only detaches driver mode on a native-XDP NIC."""
+ info = ip(f"link show dev {shlex.quote(ifname)}", json=True, host=host)[0]
+ if "xdp" not in info:
+ return
+ _root_cmd(f"ip link set dev {shlex.quote(ifname)} {mode} off", host=host,
+ sudo=sudo)
+
+
+def _wait_remote_listen(remote, port):
+ """Read the remote endpoint's stderr up to the line it prints once it
+ listens, as polling its sockets would cost a round trip per try. Return
+ what was read and whether that line came."""
+ line = f"Listening for XSK peer on port {port}\n".encode()
+ fd = remote.proc.stderr.fileno()
+ head = b""
+ deadline = time.monotonic() + LISTEN_TIMEOUT
+ while line not in head:
+ try:
+ data = fd_read_timeout(fd, max(deadline - time.monotonic(), 0))
+ except TimeoutError:
+ break
+ if not data:
+ break
+ head += data
+ return head.decode(), line in head
+
+
+def _require_zc(cfg):
+ if not hasattr(cfg, "xsk_zc"):
+ xdp_features = cfg.netnl.dev_get({"ifindex": cfg.ifindex})["xdp-features"]
+ cfg.xsk_zc = "xsk-zerocopy" in xdp_features
+ if not cfg.xsk_zc:
+ raise KsftSkipEx("NIC does not advertise AF_XDP zero-copy")
+
+
+def _remote_test_binary(cfg):
+ if not hasattr(cfg, "xsk_remote_bin"):
+ cfg.xsk_remote_bin = _remote_binary(cfg, XSK_BIN)
+ return cfg.xsk_remote_bin
+
+
+def _udp_port(cfg):
+ port = int(cfg.env.get("XSK_UDP_PORT", "42567"))
+ if not 1 <= port <= 65535:
+ raise KsftFailEx("XSK_UDP_PORT must be from 1 to 65535")
+ return port
+
+
+def _setup_case(cfg, name, direction, port, busy_poll):
+ """Set up what this case alone needs; defer() undoes it when it ends.
+ Return the DUT queue and the fallbacks for what the endpoints undo."""
+ sudo = _remote_sudo(cfg)
+ local_mtu = _idle_mtu(cfg.ifname)
+ remote_mtu = _remote_idle_mtu(cfg)
+ if "UNALIGNED" in name:
+ _reserve_hugepages()
+ _reserve_hugepages(cfg.remote, sudo)
+ if busy_poll:
+ _prefer_busy_poll(cfg.ifname)
+
+ if direction == "rx":
+ queue = _reserve_rx_queue(cfg, port)
+ else:
+ # The DUT TX socket fills no RX buffers, so its zero-copy queue drops
+ # what it receives. The control connection of a netns remote shares
+ # the link, so keep that queue out of RSS.
+ queue = _reserve_queue(cfg) if _netns_remote(cfg) else 0
+ _steer_udp(cfg.remote_ifname, port, 0, cfg.remote, sudo)
+
+ # Undo what a failed or killed endpoint may leave behind. Defers run in
+ # reverse, so the XDP programs, which can cap the MTU, are detached first.
+ fallbacks = [
+ defer(_restore_mtu, cfg.ifname, local_mtu),
+ defer(_restore_mtu, cfg.remote_ifname, remote_mtu, cfg.remote, sudo),
+ defer(_detach_xdp, cfg.ifname, "xdpdrv"),
+ defer(_detach_xdp, cfg.remote_ifname, "xdpgeneric", cfg.remote, sudo),
+ ]
+ return queue, fallbacks
+
+
+def _udp_args(cfg, direction, port):
+ if direction == "rx":
+ src, dst = cfg.remote_addr_v["4"], cfg.addr_v["4"]
+ else:
+ src, dst = cfg.addr_v["4"], cfg.remote_addr_v["4"]
+ return ["--udp-src", src, "--udp-dst", dst, "--udp-port", str(port)]
+
+
+def _case_result(local, remote):
+ if local and local.stdout:
+ ksft_pr(local.stdout, line_pfx="dut|")
+ if local and local.stderr:
+ ksft_pr(local.stderr, line_pfx="dut|")
+ if remote.stdout:
+ ksft_pr(remote.stdout, line_pfx="remote|")
+ if remote.stderr:
+ ksft_pr(remote.stderr, line_pfx="remote|")
+ if local is None:
+ raise KsftFailEx("remote XSK endpoint did not listen on the control "
+ f"port, exit {remote.ret}")
+ if local.ret == SKIP_CODE or remote.ret == SKIP_CODE:
+ raise KsftSkipEx("AF_XDP case unsupported by one endpoint")
+ if local.ret or remote.ret:
+ raise KsftFailEx(f"DUT exit {local.ret}, remote exit {remote.ret}")
+
+
+def _run_local(args):
+ local = cmd(args, background=True, shell=False, fail=False)
+ try:
+ local.process(terminate=False, fail=False, timeout=CASE_TIMEOUT)
+ except subprocess.TimeoutExpired as exc:
+ local.proc.kill()
+ local.process(terminate=False, fail=False)
+ raise KsftFailEx(f"DUT case timed out after {CASE_TIMEOUT}s") from exc
+ return local
+
+
+def _run_endpoints(cfg, local_args, remote_args, control_port, fallbacks):
+ local = None
+ remote = bkg(shlex.join(remote_args), host=cfg.remote, exit_wait=True,
+ fail=False)
+ try:
+ head, listening = _wait_remote_listen(remote, control_port)
+ if listening:
+ local = _run_local(local_args)
+ remote.process(terminate=False, fail=False, timeout=10)
+ finally:
+ if remote.ret is None:
+ remote.process(terminate=True, fail=False)
+ # _wait_remote_listen() took the start of stderr off the pipe.
+ remote.stderr = head + remote.stderr
+ if local and {local.ret, remote.ret} <= {0, SKIP_CODE}:
+ # Both endpoints detached XDP and restored the MTU when they exited.
+ for fallback in fallbacks:
+ fallback.cancel()
+ _case_result(local, remote)
+
+
+def test_xsk(cfg, name, direction, test_id, busy_poll=False):
+ cfg.require_ipver("4")
+ sync_addr = _control_addr(cfg)
+ udp_port = _udp_port(cfg)
+ _require_zc(cfg)
+ remote_bin = _remote_test_binary(cfg)
+ case_args = _case_args(cfg, name)
+ queue, fallbacks = _setup_case(cfg, name, direction, udp_port, busy_poll)
+
+ udp_args = _udp_args(cfg, direction, udp_port)
+ control_port = rand_port()
+ remote_args = [str(remote_bin), "-i", cfg.remote_ifname,
+ "--hw", "--listen", "-m", "skb",
+ "-t", str(test_id),
+ "-e", "tx" if direction == "rx" else "rx",
+ "-p", "0.0.0.0", "-P", str(control_port), *udp_args,
+ "--peer-mac", cfg.dev["address"], "--queue", "0", *case_args]
+ if _remote_sudo(cfg):
+ remote_args = ["sudo", "-n"] + remote_args
+ local_args = [str(XSK_BIN), "-i", cfg.ifname, "--hw", "-m", "zc",
+ "-t", str(test_id), "-e", direction,
+ "-p", sync_addr, "-P", str(control_port), *udp_args,
+ "--peer-mac", cfg.remote_dev["address"],
+ "--queue", str(queue), *case_args]
+ if busy_poll:
+ local_args.append("-b")
+ _run_endpoints(cfg, local_args, remote_args, control_port, fallbacks)
+
+
+def test_xsk_busy_poll(cfg, name, direction, test_id):
+ """Run a case with the DUT endpoint busy polling."""
+ test_xsk(cfg, name, direction, test_id, busy_poll=True)
+
+
+def main():
+ # ksft_run() rejects options it does not know, so take -b out first.
+ busy_poll = "-b" in sys.argv[1:]
+ if busy_poll:
+ sys.argv.remove("-b")
+ test = test_xsk_busy_poll if busy_poll else test_xsk
+ variants = ksft_variants(_hardware_cases())(test)
+
+ if any(arg in ("-h", "-l") for arg in sys.argv[1:]):
+ ksft_run(cases=[variants])
+ return
+ if not XSK_BIN.exists():
+ print(f"1..0 # SKIP {XSK_BIN} was not built")
+ sys.exit(SKIP_CODE)
+ with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+ cfg.ethnl = EthtoolFamily()
+ cfg.netnl = NetdevFamily()
+ ksft_run(cases=[variants], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index 15353021cecf..8e65327b8831 100644
--- a/tools/testing/selftests/net/lib/Makefile
+++ b/tools/testing/selftests/net/lib/Makefile
@@ -34,7 +34,7 @@ include ../../lib.mk
include ../bpf.mk
-# AF_XDP test engine used by net/test_xsk.sh.
+# AF_XDP test engine shared by net/test_xsk.sh and drivers/net/hw/xsk.py.
XSK_DIR := xsk
XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (12 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
@ 2026-10-08 11:49 ` Maciej Fijalkowski
2026-10-08 21:41 ` Jakub Kicinski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 21:30 ` [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Jakub Kicinski
14 siblings, 2 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-08 11:49 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Document the per-case veth launcher and the two-host hardware setup
where the DUT uses AF_XDP zero-copy and the remote xskxceiver uses SKB
mode.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/drivers/net/README.rst | 7 +
.../testing/selftests/net/lib/xsk/README.rst | 138 ++++++++++++++++++
.../selftests/net/lib/xsk/xskxceiver.c | 2 +
3 files changed, 147 insertions(+)
create mode 100644 tools/testing/selftests/net/lib/xsk/README.rst
diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
index 3fe49bce4f3a..c62443a4cbfc 100644
--- a/tools/testing/selftests/drivers/net/README.rst
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -141,6 +141,13 @@ Communication channel dependent::
for netns - name of the "remote" namespace
for ssh - name/address of the remote host
+Test specific variables
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Some tests read further variables from the same environment or
+``net.config``. ``hw/xsk.py`` and its ``XSK_*`` variables are described in
+``tools/testing/selftests/net/lib/xsk/README.rst``.
+
Example
=======
diff --git a/tools/testing/selftests/net/lib/xsk/README.rst b/tools/testing/selftests/net/lib/xsk/README.rst
new file mode 100644
index 000000000000..d0a0a902ed78
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/README.rst
@@ -0,0 +1,138 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==========
+xskxceiver
+==========
+
+The AF_XDP engine in ``net/lib/xsk`` is built as ``net/lib/xskxceiver``.
+The generic ``net/test_xsk.sh`` test runs SKB and DRV modes over veth.
+
+Generic veth test
+=================
+
+Two ``xskxceiver`` processes own the TX and RX veth interfaces. The TX
+process listens on a TCP control port and the RX process connects. The
+shell wrapper starts a fresh process pair for each mode and case. A small
+control channel reports readiness, packet progress, and aborts. For example::
+
+ make -C tools/testing/selftests/net/lib
+ cd tools/testing/selftests/net
+ sudo ./test_xsk.sh
+
+The shell wrapper creates the veth pair. ``-m skb|drv`` selects a mode,
+``-t`` selects a test by the name or number shown by ``xskxceiver -l``,
+and ``-p N`` selects a fixed control port instead of the default random
+port. The generic frame format is unchanged.
+
+Hardware zero-copy test
+=======================
+
+``test_xsk_case_defs.h`` defines the cases for both ``xskxceiver`` and
+``drivers/net/hw/xsk.py``, and the DUT directions in which the Python
+runner runs each of them. The runner reads this list and owns the TAP
+results.
+
+It starts one ``xskxceiver`` process on each host for each case. The DUT
+runs in ZC mode; the remote runs in SKB mode and does not need zero-copy.
+RX and TX cases are named separately in TAP. The hardware runner skips a
+DUT that does not advertise AF_XDP zero-copy. The DUT endpoint binds with
+``XDP_ZEROCOPY``, which fails instead of falling back to copy mode, so an
+advertised device that cannot bind or run a case fails it.
+
+The remote XSK endpoint listens on a TCP control port and the DUT connects.
+The listener prints a line on stderr once it listens, so ``xsk.py`` starts
+the DUT endpoint without polling the remote for the port. The small
+per-case channel reports readiness, packet progress, and aborts. It has no
+capabilities or verdict handshake. ``xsk.py`` chooses the case, sets the
+timeout, and reports the result from both process exit codes.
+
+Both XSK endpoints generate and validate raw IPv4/UDP frames with one fixed
+UDP port, so ntuple rules can steer the test flow. They do not use AF_INET
+sockets. On DUT RX cases ``xsk.py`` reserves the last RX queue outside RSS
+and steers test traffic to it. On DUT TX cases it steers traffic to queue 0
+on the remote receiver. The XDP programs redirect only the test flow and
+pass all other traffic to the stack, so the tested link does not have to
+be otherwise idle.
+Each case sets up only what its direction needs and undoes it when the
+case ends, so a setup failure skips only the cases that need that setup.
+Each endpoint detaches its XDP program and restores any changed ring sizes
+and the MTU before it exits. The SKB-mode remote endpoint only raises its
+MTU when a case needs a larger one, as changing the MTU can reset the NIC.
+The runner restores the RSS table, ntuple setting, and huge-page count.
+After an endpoint fails or is killed, the runner also detaches the XDP
+program and restores the MTU. The runner does not change channel counts.
+A one-channel DUT skips RX cases because it has no queue to reserve
+outside RSS.
+
+The remote is either another host (``REMOTE_TYPE=ssh``) or the peer port
+of the same host moved to a network namespace (``REMOTE_TYPE=netns``).
+With SSH, the control channel goes to the ``REMOTE_ARGS`` host. In a
+namespace, the remote endpoint runs the DUT's binary through
+``ip netns exec`` and the control channel goes to ``REMOTE_V4`` over the
+tested link. The DUT TX socket fills no RX buffers, so its zero-copy queue
+drops what it receives; with a namespace remote, DUT TX cases also reserve
+that queue outside RSS, so a one-channel DUT skips them as well.
+
+Setup
+-----
+
+Build the engine on the DUT::
+
+ make -C tools/testing/selftests/net/lib
+
+A top-level selftests build with ``TARGETS=drivers/net/hw`` includes
+``net/lib`` as well.
+
+Configure the usual driver test variables in
+``tools/testing/selftests/drivers/net/hw/net.config`` or the environment::
+
+ NETIF=eth0
+ LOCAL_V4=192.0.2.1
+ REMOTE_V4=192.0.2.2
+ REMOTE_TYPE=ssh
+ REMOTE_ARGS=user@remote.example.com
+ XSK_REMOTE_BIN=/path/to/remote/xskxceiver
+ XSK_REMOTE_SUDO=1
+
+Run as root on the DUT::
+
+ cd tools/testing/selftests/drivers/net/hw
+ sudo ./xsk.py -t test_xsk.rx_send_receive
+
+``./xsk.py -l`` lists the case names. Omit ``-t`` to run the full hardware
+matrix. Set the variables in ``net.config`` when using ``sudo`` so they are
+available to the test process.
+
+``./xsk.py -b`` runs the same cases as ``test_xsk_busy_poll`` instead, with
+the DUT endpoint busy polling as in the busy-poll pass of ``test_xsk.sh``.
+Each case then sets ``napi_defer_hard_irqs`` and ``gro_flush_timeout`` on
+the DUT to the values ``test_xsk.sh`` uses on veth and restores them when
+it ends. The remote endpoint does not busy poll.
+
+With SSH, each case starts its remote endpoint over SSH, and a DUT TX case
+also adds and removes a flow steering rule on the remote. SSH connection
+sharing for the remote host (``ControlMaster``, ``ControlPath`` and
+``ControlPersist`` in ssh_config(5)) avoids a new SSH login for each remote
+command. With ``sudo``, that is root's SSH configuration.
+
+The remote host needs AF_XDP in SKB mode and BPF. ``xsk.py`` copies the
+DUT's ``xskxceiver`` binary to the remote, so both hosts must have
+compatible architectures and libraries. ``XSK_REMOTE_BIN`` chooses a fixed
+destination path for that copy. Set ``XSK_REMOTE_DEPLOY=0`` to use a binary
+built on the remote from the same test case definitions instead.
+``XSK_REMOTE_SUDO=1`` runs the remote endpoint and setup commands through
+passwordless ``sudo -n``; omit it when SSH logs in as root. The DUT needs
+ntuple and RSS support for RX cases, while the remote needs ntuple support
+for DUT TX cases. The test uses ``NetDrvEpEnv`` and the networking selftest
+Python helpers for setup and rollback.
+
+With SSH, the DUT uses the SSH host name to connect to the remote control
+listener.
+``XSK_UDP_PORT`` changes the fixed test UDP port (default 42567). The remote
+listens on all addresses for control.
+
+The hardware list covers baseline traffic, 2K frames, poll, headroom,
+invalid TX descriptors, TX invalid-descriptor statistics, metadata, 9K,
+and unaligned traffic. For ``TOO_MANY_FRAGS``, the runner reads the DUT's
+``xdp-zc-max-segs`` value and passes it to both endpoint processes so the
+SKB peer validates the same packet stream without a capabilities exchange.
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 8b94dc3709ea..510cb8e4e054 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -9,6 +9,8 @@
* See test_xsk.sh for detailed information on test topology
* and prerequisite network setup.
*
+ * See README.rst for the generic and hardware test setup.
+ *
* Each instance of this test program runs one endpoint, Tx or Rx, with a
* single socket and a unique UMEM. Two instances validate in-order packet
* delivery and packet content by sending packets to each other.
--
2.43.0
^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (13 preceding siblings ...)
2026-10-08 11:49 ` [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
@ 2026-10-08 21:30 ` Jakub Kicinski
2026-10-09 12:02 ` Björn Töpel
14 siblings, 1 reply; 38+ messages in thread
From: Jakub Kicinski @ 2026-10-08 21:30 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
On Thu, 8 Oct 2026 13:48:55 +0200 Maciej Fijalkowski wrote:
> Heads-up for ice: to reserve a queue, xsk.py updates only the RSS
> indirection table over ethtool netlink. On ice this also turns on
> symmetric Toeplitz hashing, and restoring the table afterwards fails.
> The fix, "ice: keep the RSS hash function on indirection-only updates",
> goes to iwl-net separately [0]. Without it, the first case that takes a
> queue out of RSS fails in its cleanup, and the table stays modified.
Good news it that it applied, bad news is that the non-HW test_xsk.sh
crashes the host:
https://netdev-ctrl.bots.linux.dev/logs/vmksft/net-dbg/results/857863/166-test-xsk-sh/stderr
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
@ 2026-10-08 21:37 ` Jakub Kicinski
2026-10-09 13:13 ` Maciej Fijalkowski
2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
2 siblings, 1 reply; 38+ messages in thread
From: Jakub Kicinski @ 2026-10-08 21:37 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
On Thu, 8 Oct 2026 13:49:08 +0200 Maciej Fijalkowski wrote:
> +XSK_BIN = (Path(__file__).parent / "../../../net/lib/xskxceiver").resolve()
cfg.test_dir ? Please don't reinvent the wheel
> +XSK_CASES = XSK_BIN.parent / "xsk/test_xsk_case_defs.h"
> +def _hardware_cases():
> + defs = XSK_CASES.read_text()
> + entries = re.findall(r"(?m)^XSK_TEST_CASE\((\w+),\s*\w+,\s*([^)]+)\)", defs)
> + if not entries or len(entries) != defs.count("XSK_TEST_CASE("):
> + raise KsftFailEx(f"cannot parse {XSK_CASES}")
> + variants = []
> + for test_id, (name, hw) in enumerate(entries):
> + flags = {flag.strip() for flag in hw.split("|")}
> + if not flags <= {"0", "XSK_HW_RX", "XSK_HW_TX"}:
> + raise KsftFailEx(f"invalid hardware directions for {name}: {hw}")
> + variants += [KsftNamedVariant(f"{d}_{name.lower()}", name, d, test_id)
> + for d in ("rx", "tx") if f"XSK_HW_{d.upper()}" in flags]
> + return variants
?? Just list them, don't over optimize for de-duplication
it's obviously all LLM generated now.
> +def _netns_remote(cfg):
> + return cfg.env.get("REMOTE_TYPE") == "netns"
You shouldn't have to care, please don't break the abstractions
> +def _control_addr(cfg):
> + """An SSH remote is reached over its management link. A netns remote is
> + reached only over the tested link, where the XDP programs pass the
> + control connection to the stack."""
> + if _netns_remote(cfg):
> + return cfg.remote_addr_v["4"]
> + return cfg.remote.name.rpartition("@")[2]
Suspicious? Not sure why you need this. No existing test does
something like this
> +def _root_cmd(command, host=None, sudo=False, **kwargs):
> + if sudo:
> + command = "sudo -n " + command
> + return cmd(command, host=host, **kwargs)
No hacks like this please, all networking tests can assume root
> +def _remote_binary(cfg, local_binary):
> + binary = cfg.env.get("XSK_REMOTE_BIN")
Why this variable? Just assume the binary in tree is the one we need?
> + if not binary:
> + return cfg.remote.deploy(local_binary)
> + if cfg.env.get("REMOTE_TYPE") != "ssh":
> + raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
> + if cfg.env.get("XSK_REMOTE_DEPLOY", "1") == "0":
> + result = cmd(f"test -x {shlex.quote(binary)}", host=cfg.remote,
> + fail=False)
> + if result.ret:
> + raise KsftSkipEx(f"remote xskxceiver is not executable: {binary}")
> + return binary
> + upload = binary + ".upload"
> + cmd(["scp", "-q", str(local_binary), f"{cfg.remote.name}:{upload}"],
> + shell=False)
> + cmd(f"mv -f {shlex.quote(upload)} {shlex.quote(binary)}",
> + host=cfg.remote)
Why any of these hacks/complexity? I think you need a better LLM and
point it at the existing tests. Weird stuff going on here :(
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs
2026-10-08 11:49 ` [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
@ 2026-10-08 21:41 ` Jakub Kicinski
2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: Jakub Kicinski @ 2026-10-08 21:41 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
On Thu, 8 Oct 2026 13:49:09 +0200 Maciej Fijalkowski wrote:
> Document the per-case veth launcher and the two-host hardware setup
> where the DUT uses AF_XDP zero-copy and the remote xskxceiver uses SKB
> mode.
Why all these instructions? Just make ksft run all the combinations you
care about testing, no?
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode
2026-10-08 11:48 ` [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
@ 2026-10-09 9:46 ` Björn Töpel
2026-10-09 12:43 ` Maciej Fijalkowski
0 siblings, 1 reply; 38+ messages in thread
From: Björn Töpel @ 2026-10-09 9:46 UTC (permalink / raw)
To: Maciej Fijalkowski, netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, Maciej Fijalkowski
Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
> xskxceiver -i ethX -i ethX was dedicated for HW tests and both sockets
> were bound on queue 0 of one interface, which the kernel only allows
> with a shared UMEM setting. That mode is going away: the endpoints are
> about to become separate processes and would have to share the UMEM
> mapping and the socket fd across them.
I'm *not* asking you to add anything, but curious if the HW
loopback-plug mode is supported running two processes?
Björn
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-08 11:49 ` [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
@ 2026-10-09 11:18 ` Björn Töpel
2026-10-09 12:46 ` Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 1 reply; 38+ messages in thread
From: Björn Töpel @ 2026-10-09 11:18 UTC (permalink / raw)
To: Maciej Fijalkowski, netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, Maciej Fijalkowski
Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
> Move the xskxceiver engine, the AF_XDP userspace library, the test
> definitions and the XDP program to net/lib/xsk, and the veth launcher
> test_xsk.sh with its prerequisites script to selftests/net. A later
> patch drives the same engine against real NICs from drivers/net/hw, and
> net/lib is where both suites already take shared helpers from, so the
> binary does not need a second copy.
>
> net/lib builds xskxceiver together with its XDP program and skeleton,
> using the bpftool in PATH, and skips it with a warning when bpftool is
> missing or does not run, so the rest of net/lib still builds.
> selftests/net installs test_xsk.sh, which runs lib/xskxceiver.
>
> Like the rest of net/lib, xskxceiver builds against the installed uapi
> headers rather than the tools/include/uapi copies that the bpf selftests
> use. xskxceiver.c got min_t() only because the tools copy of
> linux/netlink.h includes linux/kernel.h, so include linux/kernel.h
> directly.
>
> Move the two ethtool ring helpers from network_helpers into xsk.c so the
> engine no longer depends on network_helpers, and point the kselftest.h
> include of test_xsk.h at the net/lib include path. The XDP program moves
> unchanged. Its Ethernet header bound check compares pointers of distinct
> types, so net/lib builds it with -Wno-compare-distinct-pointer-types, as
> the bpf selftests do.
>
> xdp_hw_metadata and the test_progs xdp_metadata test keep using xsk.c
> and xsk.h from their new location.
>
> Point the AF_XDP entry in MAINTAINERS at the new paths.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
> Documentation/networking/af_xdp.rst | 6 +--
> MAINTAINERS | 3 +-
> tools/testing/selftests/bpf/.gitignore | 1 -
> tools/testing/selftests/bpf/Makefile | 29 +++++------
> tools/testing/selftests/bpf/network_helpers.c | 48 ------------------
> tools/testing/selftests/bpf/network_helpers.h | 2 -
> tools/testing/selftests/net/Makefile | 2 +
> tools/testing/selftests/net/config | 1 +
> tools/testing/selftests/net/lib/.gitignore | 2 +
> tools/testing/selftests/net/lib/Makefile | 34 +++++++++++++
> .../prog_tests => net/lib/xsk}/test_xsk.c | 2 +-
> .../prog_tests => net/lib/xsk}/test_xsk.h | 2 +-
> .../selftests/{bpf => net/lib/xsk}/xsk.c | 49 ++++++++++++++++++-
> .../selftests/{bpf => net/lib/xsk}/xsk.h | 4 ++
> .../{bpf => net/lib/xsk}/xsk_xdp_common.h | 0
> .../lib/xsk/xsk_xdp_progs.bpf.c} | 0
> .../{bpf => net/lib/xsk}/xskxceiver.c | 7 +--
> .../{bpf => net/lib/xsk}/xskxceiver.h | 0
> .../selftests/{bpf => net}/test_xsk.sh | 0
I would not carry the test_xsk.sh over. Just do a Python variant, like
the drivers/net/hw/xsk.py, but for net instead. Look at the the
nk_lease.py pair for inspiration.
It's just a cogitive load switch between Python and bash. We're starting
out fresh(ish) here.
The Python ksft world gives us a lot for free.
Björn
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation
2026-10-08 11:49 ` [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
@ 2026-10-09 11:25 ` Björn Töpel
2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: Björn Töpel @ 2026-10-09 11:25 UTC (permalink / raw)
To: Maciej Fijalkowski, netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, Maciej Fijalkowski
Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
> Make test_xsk.sh own the mode x case matrix and run xskxceiver once per
> case, so that every case starts from a fresh process and a case that
> crashes fails only itself. A single case is also the unit that the next
> patch splits into separate RX and TX endpoint processes.
>
> xskxceiver now requires -m and -t and runs exactly that case. It
> reports one KTAP result and exits with its verdict. An unsupported mode
> skips the case instead of exiting with XFAIL.
>
> test_xsk.sh runs every case in the skb and drv modes, or in the one
> given with -m, in both the softirq and the busy-poll pass, and prints a
> summary of the passed, skipped and failed cases. -t also accepts a test
> name, which is resolved through xskxceiver -l. Only skb and drv modes
> are accepted, as veth has no zero-copy support, and the script skips
> when xskxceiver was not built. As ARGS is now rebuilt for every case,
> exec_xskxceiver() adds -b to a local copy of it.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
> .../selftests/net/lib/xsk/xskxceiver.c | 52 ++++-----
> tools/testing/selftests/net/test_xsk.sh | 102 ++++++++++++++----
> tools/testing/selftests/net/xsk_prereqs.sh | 6 +-
> 3 files changed, 103 insertions(+), 57 deletions(-)
Again, Python please. Maybe carry the bash side over in the first
commit, and here introduce a new, shiny Python test driver?
Björn
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver
2026-10-08 11:49 ` [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
@ 2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: sashiko-bot @ 2026-10-09 11:50 UTC (permalink / raw)
To: Maciej Fijalkowski; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] selftests/xsk: hardware ring software state out of sync on reset failure
--
commit b6f9e6a7023dfe76a4790e608f82c3aef2c3cf4a
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
selftests: xsk: add a hardware mode to xskxceiver
This commit adds a hardware testing mode to the xskxceiver selftest, passing
physical interface parameters over UDP/IPv4. It also introduces an early
return optimization in hw_ring_size_reset to skip restoring hardware rings
if they are already at their default sizes.
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index 8d30c39c94c84..542c0579062a4 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
[ ... ]
> @@ -218,6 +291,10 @@ static int set_ring_size(struct ifobject *ifobj)
>
> int hw_ring_size_reset(struct ifobject *ifobj)
> {
> + if (ifobj->ring.tx_pending == ifobj->set_ring.default_tx &&
> + ifobj->ring.rx_pending == ifobj->set_ring.default_rx)
> + return 0;
> +
> ifobj->ring.tx_pending = ifobj->set_ring.default_tx;
> ifobj->ring.rx_pending = ifobj->set_ring.default_rx;
> return set_ring_size(ifobj);
[Severity: Medium]
Does this early-return optimization leave the software state out of sync if
the ioctl fails?
If set_ring_size() fails (for example, due to EBUSY), the underlying hardware
retains its previous, non-default ring size. However, because
ifobj->ring.tx_pending and ifobj->ring.rx_pending were updated optimistically
before the call, they are left permanently reflecting the default values.
With this newly added early return, subsequent attempts to call
hw_ring_size_reset() will read the corrupted software state and immediately
return 0 without attempting to retry the ioctl. Could this leave the network
interface with an incorrect ring size during test case cleanup?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008114909.734364-1-maciej.fijalkowski@intel.com?part=10
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-08 21:37 ` Jakub Kicinski
@ 2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
2 siblings, 0 replies; 38+ messages in thread
From: sashiko-bot @ 2026-10-09 11:50 UTC (permalink / raw)
To: Maciej Fijalkowski; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] selftests: drv-net: xsk.py crashes due to missing test_xsk_case_defs.h dependency
--
commit e8144917db97e8dcd48aff778b6c6ea34e332b01
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
selftests: drv-net: test AF_XDP zero-copy with an SKB peer
This commit introduces xsk.py to test hardware AF_XDP cases using a
device under test and a remote xskxceiver pair. It adds the script to the
drivers/net/hw selftests and utilizes Python networking helpers for remote
execution. The runner dynamically parses test cases from the C header
definitions in the net/lib subsystem.
> diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
> index 18ce235a77f84..02e48e74f6c6c 100644
> --- a/tools/testing/selftests/drivers/net/hw/Makefile
> +++ b/tools/testing/selftests/drivers/net/hw/Makefile
> @@ -52,6 +52,7 @@ TEST_PROGS = \
> uso.py \
> vlan.py \
> xdp_metadata.py \
> + xsk.py \
> xsk_reconfig.py \
> #
[Severity: Low]
Does xsk.py rely on any external test files that need to be explicitly
copied during out-of-tree builds?
Because the required data file ../../../net/lib/xsk/test_xsk_case_defs.h
is missing from TEST_INCLUDES in this Makefile, it appears this causes a
regression where the file won't be copied during out-of-tree run_tests
for this subsystem.
> diff --git a/tools/testing/selftests/drivers/net/hw/xsk.py b/tools/testing/selftests/drivers/net/hw/xsk.py
> new file mode 100755
> index 0000000000000..a33d4af869c7a
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/hw/xsk.py
[ ... ]
> +SKIP_CODE = 4
> +XSK_BIN = (Path(__file__).parent / "../../../net/lib/xskxceiver").resolve()
> +XSK_CASES = XSK_BIN.parent / "xsk/test_xsk_case_defs.h"
> +
> +
> +def _hardware_cases():
> + defs = XSK_CASES.read_text()
[Severity: Low]
Could this cause a regression where it unconditionally throws a
FileNotFoundError and crashes the script if the file was not deployed?
When running an out-of-tree build where the header is omitted from
TEST_INCLUDES (or if xskxceiver is legitimately not built), this
read_text() call in _hardware_cases() will crash before the
XSK_BIN.exists() graceful skip check can execute.
Would it be safer to verify XSK_CASES exists before reading it, or handle
the exception gracefully?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008114909.734364-1-maciej.fijalkowski@intel.com?part=13
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net
2026-10-08 21:30 ` [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Jakub Kicinski
@ 2026-10-09 12:02 ` Björn Töpel
2026-10-09 13:15 ` Maciej Fijalkowski
0 siblings, 1 reply; 38+ messages in thread
From: Björn Töpel @ 2026-10-09 12:02 UTC (permalink / raw)
To: Jakub Kicinski, Maciej Fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing
Jakub Kicinski <kuba@kernel.org> writes:
> On Thu, 8 Oct 2026 13:48:55 +0200 Maciej Fijalkowski wrote:
>> Heads-up for ice: to reserve a queue, xsk.py updates only the RSS
>> indirection table over ethtool netlink. On ice this also turns on
>> symmetric Toeplitz hashing, and restoring the table afterwards fails.
>> The fix, "ice: keep the RSS hash function on indirection-only updates",
>> goes to iwl-net separately [0]. Without it, the first case that takes a
>> queue out of RSS fails in its cleanup, and the table stays modified.
>
> Good news it that it applied, bad news is that the non-HW test_xsk.sh
> crashes the host:
>
> https://netdev-ctrl.bots.linux.dev/logs/vmksft/net-dbg/results/857863/166-test-xsk-sh/stderr
That's also good! A bug has been surfaced by the test! XD
AFAIU this is a know veth issue [1] (right, Maciej?).
Disable until a fix a landed?
Björn
[1] https://lore.kernel.org/netdev/20260509084858.773921-1-maciej.fijalkowski@intel.com/
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner
2026-10-08 11:49 ` [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner Maciej Fijalkowski
@ 2026-10-09 12:12 ` Björn Töpel
2026-10-09 12:52 ` Maciej Fijalkowski
0 siblings, 1 reply; 38+ messages in thread
From: Björn Töpel @ 2026-10-09 12:12 UTC (permalink / raw)
To: Maciej Fijalkowski, netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, Maciej Fijalkowski
Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
> Keep case names, functions, and hardware directions in an X-macro
> header. The C test table keeps its order, so the test IDs do not
> change. This allows the Python runner added by the next patch to read
> the same definitions. Only the runner uses the hardware directions,
> the DUT roles in which it runs a case; xskxceiver ignores them.
>
> Install the header with selftests so the Python runner can read it
> from an installed tree.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
> tools/testing/selftests/net/lib/Makefile | 2 +-
> .../testing/selftests/net/lib/xsk/test_xsk.h | 46 ++-------------
> .../net/lib/xsk/test_xsk_case_defs.h | 57 +++++++++++++++++++
> 3 files changed, 62 insertions(+), 43 deletions(-)
> create mode 100644 tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
>
> diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
> index 066d011c478a..15353021cecf 100644
> --- a/tools/testing/selftests/net/lib/Makefile
> +++ b/tools/testing/selftests/net/lib/Makefile
> @@ -28,7 +28,7 @@ TEST_GEN_FILES := \
> xdp_helper \
> # end of TEST_GEN_FILES
>
> -TEST_INCLUDES := $(wildcard py/*.py sh/*.sh)
> +TEST_INCLUDES := $(wildcard py/*.py sh/*.sh) xsk/test_xsk_case_defs.h
>
> include ../../lib.mk
>
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> index c37423030eb6..e1ffd054c8f7 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> @@ -309,48 +309,10 @@ int worker_testapp_validate_rx(struct test_spec *test);
> int worker_testapp_validate_tx(struct test_spec *test);
>
> static const struct test_spec tests[] = {
> - {.name = "SEND_RECEIVE", .test_func = testapp_send_receive},
> - {.name = "SEND_RECEIVE_2K_FRAME", .test_func = testapp_send_receive_2k_frame},
> - {.name = "SEND_RECEIVE_SINGLE_PKT", .test_func = testapp_single_pkt},
> - {.name = "POLL_RX", .test_func = testapp_poll_rx},
> - {.name = "POLL_TX", .test_func = testapp_poll_tx},
> - {.name = "POLL_RXQ_FULL", .test_func = testapp_poll_rxq_tmout},
> - {.name = "POLL_TXQ_FULL", .test_func = testapp_poll_txq_tmout},
> - {.name = "ALIGNED_INV_DESC", .test_func = testapp_aligned_inv_desc},
> - {.name = "ALIGNED_INV_DESC_2K_FRAME_SIZE", .test_func = testapp_aligned_inv_desc_2k_frame},
> - {.name = "UMEM_HEADROOM", .test_func = testapp_headroom},
> - {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional},
> - {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped},
> - {.name = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs},
> - {.name = "STAT_RX_FULL", .test_func = testapp_stats_rx_full},
> - {.name = "STAT_FILL_EMPTY", .test_func = testapp_stats_fill_empty},
> - {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup},
> - {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop},
> - {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem},
> - {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata},
> - {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb},
> - {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb},
> - {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags},
> - {.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink},
> - {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer},
> - /* Flaky tests */
> - {.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb},
> - {.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow},
> - {.name = "XDP_ADJUST_TAIL_GROW_MULTI_BUFF", .test_func = testapp_adjust_tail_grow_mb},
> - {.name = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb},
> - /* Tests with huge page dependency */
> - {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned},
> - {.name = "UNALIGNED_INV_DESC", .test_func = testapp_unaligned_inv_desc},
> - {.name = "UNALIGNED_INV_DESC_4001_FRAME_SIZE",
> - .test_func = testapp_unaligned_inv_desc_4001_frame},
> - {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS",
> - .test_func = testapp_send_receive_unaligned_mb},
> - {.name = "UNALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_unaligned_inv_desc_mb},
> - /* Test with HW ring size dependency */
> - {.name = "HW_SW_MIN_RING_SIZE", .test_func = testapp_hw_sw_min_ring_size},
> - {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size},
> - /* Too long test */
> - {.name = "TEARDOWN", .test_func = testapp_teardown},
> +#define XSK_TEST_CASE(_name, _func, ...) \
> + { .name = #_name, .test_func = _func },
> +#include "test_xsk_case_defs.h"
> +#undef XSK_TEST_CASE
> };
Hmm. Maybe not for this revision, but I'm getting a vibe that we're
mixing reusable operations with test receipes.
Ideally the operations would reside in the C-code, and the recipes in
Python...
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode
2026-10-09 9:46 ` Björn Töpel
@ 2026-10-09 12:43 ` Maciej Fijalkowski
0 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-09 12:43 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
On Fri, Oct 09, 2026 at 11:46:12AM +0200, Björn Töpel wrote:
> Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>
> > xskxceiver -i ethX -i ethX was dedicated for HW tests and both sockets
> > were bound on queue 0 of one interface, which the kernel only allows
> > with a shared UMEM setting. That mode is going away: the endpoints are
> > about to become separate processes and would have to share the UMEM
> > mapping and the socket fd across them.
>
> I'm *not* asking you to add anything, but curious if the HW
> loopback-plug mode is supported running two processes?
You could now spawn two xskxceiver's (rx/tx) on the same interface and
would achieve the same what we've been doing via
ethtool -K $IFACE loopback <on|off> with single xskxceiver process.
>
>
> Björn
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-09 11:18 ` Björn Töpel
@ 2026-10-09 12:46 ` Maciej Fijalkowski
0 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-09 12:46 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
On Fri, Oct 09, 2026 at 01:18:10PM +0200, Björn Töpel wrote:
> Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>
> > Move the xskxceiver engine, the AF_XDP userspace library, the test
> > definitions and the XDP program to net/lib/xsk, and the veth launcher
> > test_xsk.sh with its prerequisites script to selftests/net. A later
> > patch drives the same engine against real NICs from drivers/net/hw, and
> > net/lib is where both suites already take shared helpers from, so the
> > binary does not need a second copy.
> >
> > net/lib builds xskxceiver together with its XDP program and skeleton,
> > using the bpftool in PATH, and skips it with a warning when bpftool is
> > missing or does not run, so the rest of net/lib still builds.
> > selftests/net installs test_xsk.sh, which runs lib/xskxceiver.
> >
> > Like the rest of net/lib, xskxceiver builds against the installed uapi
> > headers rather than the tools/include/uapi copies that the bpf selftests
> > use. xskxceiver.c got min_t() only because the tools copy of
> > linux/netlink.h includes linux/kernel.h, so include linux/kernel.h
> > directly.
> >
> > Move the two ethtool ring helpers from network_helpers into xsk.c so the
> > engine no longer depends on network_helpers, and point the kselftest.h
> > include of test_xsk.h at the net/lib include path. The XDP program moves
> > unchanged. Its Ethernet header bound check compares pointers of distinct
> > types, so net/lib builds it with -Wno-compare-distinct-pointer-types, as
> > the bpf selftests do.
> >
> > xdp_hw_metadata and the test_progs xdp_metadata test keep using xsk.c
> > and xsk.h from their new location.
> >
> > Point the AF_XDP entry in MAINTAINERS at the new paths.
> >
> > Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> > ---
> > Documentation/networking/af_xdp.rst | 6 +--
> > MAINTAINERS | 3 +-
> > tools/testing/selftests/bpf/.gitignore | 1 -
> > tools/testing/selftests/bpf/Makefile | 29 +++++------
> > tools/testing/selftests/bpf/network_helpers.c | 48 ------------------
> > tools/testing/selftests/bpf/network_helpers.h | 2 -
> > tools/testing/selftests/net/Makefile | 2 +
> > tools/testing/selftests/net/config | 1 +
> > tools/testing/selftests/net/lib/.gitignore | 2 +
> > tools/testing/selftests/net/lib/Makefile | 34 +++++++++++++
> > .../prog_tests => net/lib/xsk}/test_xsk.c | 2 +-
> > .../prog_tests => net/lib/xsk}/test_xsk.h | 2 +-
> > .../selftests/{bpf => net/lib/xsk}/xsk.c | 49 ++++++++++++++++++-
> > .../selftests/{bpf => net/lib/xsk}/xsk.h | 4 ++
> > .../{bpf => net/lib/xsk}/xsk_xdp_common.h | 0
> > .../lib/xsk/xsk_xdp_progs.bpf.c} | 0
> > .../{bpf => net/lib/xsk}/xskxceiver.c | 7 +--
> > .../{bpf => net/lib/xsk}/xskxceiver.h | 0
> > .../selftests/{bpf => net}/test_xsk.sh | 0
>
> I would not carry the test_xsk.sh over. Just do a Python variant, like
> the drivers/net/hw/xsk.py, but for net instead. Look at the the
> nk_lease.py pair for inspiration.
>
> It's just a cogitive load switch between Python and bash. We're starting
> out fresh(ish) here.
>
> The Python ksft world gives us a lot for free.
Plan was to move fast as you wanted + watch the code churn not to blow
off. I assume now it's better to take some time and do things right from
the start.
>
>
> Björn
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner
2026-10-09 12:12 ` Björn Töpel
@ 2026-10-09 12:52 ` Maciej Fijalkowski
0 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-09 12:52 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
On Fri, Oct 09, 2026 at 02:12:52PM +0200, Björn Töpel wrote:
> Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>
> > Keep case names, functions, and hardware directions in an X-macro
> > header. The C test table keeps its order, so the test IDs do not
> > change. This allows the Python runner added by the next patch to read
> > the same definitions. Only the runner uses the hardware directions,
> > the DUT roles in which it runs a case; xskxceiver ignores them.
> >
> > Install the header with selftests so the Python runner can read it
> > from an installed tree.
> >
> > Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> > ---
> > tools/testing/selftests/net/lib/Makefile | 2 +-
> > .../testing/selftests/net/lib/xsk/test_xsk.h | 46 ++-------------
> > .../net/lib/xsk/test_xsk_case_defs.h | 57 +++++++++++++++++++
> > 3 files changed, 62 insertions(+), 43 deletions(-)
> > create mode 100644 tools/testing/selftests/net/lib/xsk/test_xsk_case_defs.h
> >
> > diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
> > index 066d011c478a..15353021cecf 100644
> > --- a/tools/testing/selftests/net/lib/Makefile
> > +++ b/tools/testing/selftests/net/lib/Makefile
> > @@ -28,7 +28,7 @@ TEST_GEN_FILES := \
> > xdp_helper \
> > # end of TEST_GEN_FILES
> >
> > -TEST_INCLUDES := $(wildcard py/*.py sh/*.sh)
> > +TEST_INCLUDES := $(wildcard py/*.py sh/*.sh) xsk/test_xsk_case_defs.h
> >
> > include ../../lib.mk
> >
> > diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> > index c37423030eb6..e1ffd054c8f7 100644
> > --- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
> > +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
> > @@ -309,48 +309,10 @@ int worker_testapp_validate_rx(struct test_spec *test);
> > int worker_testapp_validate_tx(struct test_spec *test);
> >
> > static const struct test_spec tests[] = {
> > - {.name = "SEND_RECEIVE", .test_func = testapp_send_receive},
> > - {.name = "SEND_RECEIVE_2K_FRAME", .test_func = testapp_send_receive_2k_frame},
> > - {.name = "SEND_RECEIVE_SINGLE_PKT", .test_func = testapp_single_pkt},
> > - {.name = "POLL_RX", .test_func = testapp_poll_rx},
> > - {.name = "POLL_TX", .test_func = testapp_poll_tx},
> > - {.name = "POLL_RXQ_FULL", .test_func = testapp_poll_rxq_tmout},
> > - {.name = "POLL_TXQ_FULL", .test_func = testapp_poll_txq_tmout},
> > - {.name = "ALIGNED_INV_DESC", .test_func = testapp_aligned_inv_desc},
> > - {.name = "ALIGNED_INV_DESC_2K_FRAME_SIZE", .test_func = testapp_aligned_inv_desc_2k_frame},
> > - {.name = "UMEM_HEADROOM", .test_func = testapp_headroom},
> > - {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional},
> > - {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped},
> > - {.name = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs},
> > - {.name = "STAT_RX_FULL", .test_func = testapp_stats_rx_full},
> > - {.name = "STAT_FILL_EMPTY", .test_func = testapp_stats_fill_empty},
> > - {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup},
> > - {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop},
> > - {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem},
> > - {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata},
> > - {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb},
> > - {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb},
> > - {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags},
> > - {.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink},
> > - {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer},
> > - /* Flaky tests */
> > - {.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb},
> > - {.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow},
> > - {.name = "XDP_ADJUST_TAIL_GROW_MULTI_BUFF", .test_func = testapp_adjust_tail_grow_mb},
> > - {.name = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb},
> > - /* Tests with huge page dependency */
> > - {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned},
> > - {.name = "UNALIGNED_INV_DESC", .test_func = testapp_unaligned_inv_desc},
> > - {.name = "UNALIGNED_INV_DESC_4001_FRAME_SIZE",
> > - .test_func = testapp_unaligned_inv_desc_4001_frame},
> > - {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS",
> > - .test_func = testapp_send_receive_unaligned_mb},
> > - {.name = "UNALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_unaligned_inv_desc_mb},
> > - /* Test with HW ring size dependency */
> > - {.name = "HW_SW_MIN_RING_SIZE", .test_func = testapp_hw_sw_min_ring_size},
> > - {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size},
> > - /* Too long test */
> > - {.name = "TEARDOWN", .test_func = testapp_teardown},
> > +#define XSK_TEST_CASE(_name, _func, ...) \
> > + { .name = #_name, .test_func = _func },
> > +#include "test_xsk_case_defs.h"
> > +#undef XSK_TEST_CASE
> > };
>
> Hmm. Maybe not for this revision, but I'm getting a vibe that we're
> mixing reusable operations with test receipes.
>
> Ideally the operations would reside in the C-code, and the recipes in
> Python...
Yeah this change was about adding new test cases within C-code and then
letting python side consume the up-to-date list. What you describe would
probably be something like testapp_send_receive() is the reusable op and
test cases would sit in python, prepping the stuff that is now being setup
within C routines.
That would of course pump up the diffstat, so up to You and drv-net
people.
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-08 21:37 ` Jakub Kicinski
@ 2026-10-09 13:13 ` Maciej Fijalkowski
0 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-09 13:13 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
On Thu, Oct 08, 2026 at 02:37:10PM -0700, Jakub Kicinski wrote:
> On Thu, 8 Oct 2026 13:49:08 +0200 Maciej Fijalkowski wrote:
> > +XSK_BIN = (Path(__file__).parent / "../../../net/lib/xskxceiver").resolve()
>
> cfg.test_dir ? Please don't reinvent the wheel
ok I can convert from global var and base this on cfg.net_lib_dir. This
was done in this ugly form as list of test cases was generated without
creating an instance of NetDrvEpEnv.
>
> > +XSK_CASES = XSK_BIN.parent / "xsk/test_xsk_case_defs.h"
>
> > +def _hardware_cases():
> > + defs = XSK_CASES.read_text()
> > + entries = re.findall(r"(?m)^XSK_TEST_CASE\((\w+),\s*\w+,\s*([^)]+)\)", defs)
> > + if not entries or len(entries) != defs.count("XSK_TEST_CASE("):
> > + raise KsftFailEx(f"cannot parse {XSK_CASES}")
> > + variants = []
> > + for test_id, (name, hw) in enumerate(entries):
> > + flags = {flag.strip() for flag in hw.split("|")}
> > + if not flags <= {"0", "XSK_HW_RX", "XSK_HW_TX"}:
> > + raise KsftFailEx(f"invalid hardware directions for {name}: {hw}")
> > + variants += [KsftNamedVariant(f"{d}_{name.lower()}", name, d, test_id)
> > + for d in ("rx", "tx") if f"XSK_HW_{d.upper()}" in flags]
> > + return variants
>
> ?? Just list them, don't over optimize for de-duplication
> it's obviously all LLM generated now.
Looks nasty but it spits out a test case in correlation with direction we
will be testing. I wrote in cover letter that, speaking in AF_XDP ZC ice,
SEND_RECEIVE in XSK_HW_RX would focus on testing ice_clean_rx_irq_zc() and
analogously this same case in Tx direction would be testing ice_xmit_zc().
Some tests are only Rx or Tx, some don't define these flags.
>
> > +def _netns_remote(cfg):
> > + return cfg.env.get("REMOTE_TYPE") == "netns"
>
> You shouldn't have to care, please don't break the abstractions
>
> > +def _control_addr(cfg):
> > + """An SSH remote is reached over its management link. A netns remote is
> > + reached only over the tested link, where the XDP programs pass the
> > + control connection to the stack."""
> > + if _netns_remote(cfg):
> > + return cfg.remote_addr_v["4"]
> > + return cfg.remote.name.rpartition("@")[2]
>
> Suspicious? Not sure why you need this. No existing test does
> something like this
I'll try to simplify the noise behind setup variables, these mostly come
from my intial local setup where I had issues on remote host, sorry about
that. Also as you say test code should not be special casing what remote
type comes from net.config.
>
> > +def _root_cmd(command, host=None, sudo=False, **kwargs):
> > + if sudo:
> > + command = "sudo -n " + command
> > + return cmd(command, host=host, **kwargs)
>
> No hacks like this please, all networking tests can assume root
>
> > +def _remote_binary(cfg, local_binary):
> > + binary = cfg.env.get("XSK_REMOTE_BIN")
>
> Why this variable? Just assume the binary in tree is the one we need?
>
> > + if not binary:
> > + return cfg.remote.deploy(local_binary)
> > + if cfg.env.get("REMOTE_TYPE") != "ssh":
> > + raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
> > + if cfg.env.get("XSK_REMOTE_DEPLOY", "1") == "0":
> > + result = cmd(f"test -x {shlex.quote(binary)}", host=cfg.remote,
> > + fail=False)
> > + if result.ret:
> > + raise KsftSkipEx(f"remote xskxceiver is not executable: {binary}")
> > + return binary
> > + upload = binary + ".upload"
> > + cmd(["scp", "-q", str(local_binary), f"{cfg.remote.name}:{upload}"],
> > + shell=False)
> > + cmd(f"mv -f {shlex.quote(upload)} {shlex.quote(binary)}",
> > + host=cfg.remote)
>
> Why any of these hacks/complexity? I think you need a better LLM and
> point it at the existing tests. Weird stuff going on here :(
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net
2026-10-09 12:02 ` Björn Töpel
@ 2026-10-09 13:15 ` Maciej Fijalkowski
0 siblings, 0 replies; 38+ messages in thread
From: Maciej Fijalkowski @ 2026-10-09 13:15 UTC (permalink / raw)
To: Björn Töpel
Cc: Jakub Kicinski, netdev, bpf, magnus.karlsson, stfomichev, pabeni,
tushar.vyavahare, kerneljasonxing
On Fri, Oct 09, 2026 at 02:02:01PM +0200, Björn Töpel wrote:
> Jakub Kicinski <kuba@kernel.org> writes:
>
> > On Thu, 8 Oct 2026 13:48:55 +0200 Maciej Fijalkowski wrote:
> >> Heads-up for ice: to reserve a queue, xsk.py updates only the RSS
> >> indirection table over ethtool netlink. On ice this also turns on
> >> symmetric Toeplitz hashing, and restoring the table afterwards fails.
> >> The fix, "ice: keep the RSS hash function on indirection-only updates",
> >> goes to iwl-net separately [0]. Without it, the first case that takes a
> >> queue out of RSS fails in its cleanup, and the table stays modified.
> >
> > Good news it that it applied, bad news is that the non-HW test_xsk.sh
> > crashes the host:
> >
> > https://netdev-ctrl.bots.linux.dev/logs/vmksft/net-dbg/results/857863/166-test-xsk-sh/stderr
>
> That's also good! A bug has been surfaced by the test! XD
>
> AFAIU this is a know veth issue [1] (right, Maciej?).
>
> Disable until a fix a landed?
Yes that's the ever-lasting veth issue where mem_type is changed during
skb's CoW and system page_pool comes to play.
Gonna disable tests that adjust tail on veth side until we resolve the
veth side :<
>
>
> Björn
>
>
> [1] https://lore.kernel.org/netdev/20260509084858.773921-1-maciej.fijalkowski@intel.com/
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-08 11:49 ` [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
2026-10-09 11:18 ` Björn Töpel
@ 2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] In this commit, net/lib/Makefile deliberately leaves
xskxceiver out of TEST_GEN_FILES when `bpftool version` fails, and…
- [Low] In this commit, net/lib/Makefile links xskxceiver with `$(CC)
$(CFLAGS) ... $(XSK_SOURCES) $(BPFOBJ) $(LDLIBS) $(XSK_LDLIBS) -o $@`.
- [Low] xskxceiver needs the tools headers: tools/include/linux/kernel.h
for min_t() (the only min_t definition under tools/include, line 46),…
--
selftests: xsk: move the AF_XDP test suite to selftests/net
This patch moves the xskxceiver engine, the AF_XDP userspace library, the
test definitions and the XDP program from selftests/bpf to
selftests/net/lib/xsk. It also moves test_xsk.sh and xsk_prereqs.sh to
selftests/net. net/lib builds xskxceiver only when a working bpftool is
found, and the ethtool ring helpers move from network_helpers into xsk.c.
> diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
> index ff83603397d06..a542757e29576 100644
> --- a/tools/testing/selftests/net/lib/Makefile
> +++ b/tools/testing/selftests/net/lib/Makefile
> @@ -5,6 +5,15 @@ CFLAGS += -I../../../../../usr/include/ $(KHDR_INCLUDES)
> # Additional include paths needed by kselftest.h
> CFLAGS += -I../../
>
> +# xskxceiver needs a working bpftool to generate its skeleton
> +BPFTOOL ?= bpftool
> +HAS_BPFTOOL := $(shell $(BPFTOOL) version >/dev/null 2>&1 && echo y)
> +ifeq ($(HAS_BPFTOOL),y)
> +COND_GEN_FILES += xskxceiver
> +else
> +$(warning excluding xskxceiver, bpftool not installed or not working)
> +endif
[Severity: Medium]
When bpftool is missing, the build leaves out xskxceiver. But
selftests/net/Makefile still adds test_xsk.sh to TEST_PROGS
unconditionally. What happens when test_xsk.sh runs and lib/xskxceiver
does not exist?
Neither test_xsk.sh nor xsk_prereqs.sh checks that the binary exists.
exec_xskxceiver() in xsk_prereqs.sh just runs it:
./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}
retval=$?
Here bash returns 127. test_status() does not print anything for that
value, but the value is still added to statusList for both runs. The
summary loop in test_xsk.sh then does:
if [ ${statusList[$i]} -ne 0 ]; then
test_status ${statusList[$i]} ${nameList[$i]}
failures=1
fi
...
exit 1
Would kselftest then report a failure instead of $ksft_skip for a binary
that the build skipped on purpose?
A later patch in the series, "selftests: xsk: run one test case per
xskxceiver invocation", adds this check to test_xsk.sh:
if [ ! -x "./${XSKOBJ}" ]; then
echo "xskxceiver was not built; skipping"
exit $ksft_skip
fi
Could that check move into this patch, so the commits in between do not
report a false failure?
[ ... ]
> @@ -23,3 +33,27 @@ TEST_INCLUDES := $(wildcard py/*.py sh/*.sh)
> include ../../lib.mk
>
> include ../bpf.mk
> +
> +# AF_XDP test engine used by net/test_xsk.sh.
> +XSK_DIR := xsk
> +XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
> +XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
> +XSK_SOURCES := $(addprefix $(XSK_DIR)/,xskxceiver.c xsk.c test_xsk.c) \
> + $(top_srcdir)/tools/lib/find_bit.c
> +XSK_HEADERS := $(wildcard $(XSK_DIR)/*.h)
> +
> +PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
> +XSK_LDLIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -lz
[Severity: Low]
Is -lpthread missing from the xskxceiver link?
XSK_LDLIBS adds only libelf and -lz. lib.mk never sets LDLIBS, so
nothing here pulls in libpthread.
test_xsk.c is linked into xskxceiver, and __testapp_validate_traffic()
calls pthread_barrier_init(), pthread_create() and pthread_join().
The old rule in selftests/bpf/Makefile got these symbols from:
LDLIBS += $(LIBELF_LIBS) -lz -lrt -lpthread
On glibc older than 2.34 the pthread symbols live in a separate
libpthread. Would this link fail there with undefined references?
The later patch "selftests: xsk: run the RX and TX endpoints in separate
processes" removes the pthreads and the barrier. So this only affects the
commits in between, which matters for bisection. Could -lpthread be
added here until that patch lands?
> +
> +$(XSK_BPF_OBJ): $(XSK_DIR)/xsk_xdp_progs.bpf.c $(XSK_DIR)/xsk_xdp_common.h $(BPFOBJ)
[ ... ]
> +$(OUTPUT)/xskxceiver: $(XSK_SOURCES) $(XSK_HEADERS) $(XSK_SKEL) $(BPFOBJ)
> + $(call msg,CC,,$@)
> + $(Q)$(CC) $(CFLAGS) -I$(top_srcdir)/tools/include -I$(OUTPUT) \
> + -I$(SCRATCH_DIR)/include \
> + $(XSK_SOURCES) $(BPFOBJ) $(LDLIBS) $(XSK_LDLIBS) -o $@
[Severity: Low]
Can <linux/kernel.h> in xskxceiver.c resolve to the uapi header instead
of tools/include/linux/kernel.h?
-I$(top_srcdir)/tools/include comes after $(CFLAGS), and CFLAGS starts
with:
CFLAGS += -I../../../../../usr/include/ $(KHDR_INCLUDES)
In the default in-tree build, KHDR_INCLUDES is an -isystem for the same
<srctree>/usr/include directory. The compiler drops the duplicate -I, so
tools/include wins.
For an O= or KBUILD_OUTPUT build, tools/testing/selftests/Makefile sets:
KHDR_INCLUDES := -isystem ${abs_objtree}/usr/include
In that case the two directories differ. If <srctree>/usr/include also
exists, for example from an earlier in-tree make headers, then
-I<srctree>/usr/include stays first in the search order.
The new include in xskxceiver.c:
#include <linux/kernel.h>
would then pick up the exported uapi kernel.h, which has no min_t(). That
breaks the build of parse_command_line():
memcpy(ifobj->ifname, optarg,
min_t(size_t, MAX_INTERFACE_NAME_CHARS, strlen(optarg)));
linux/types.h would also resolve to the uapi copy, which has no u32/u64
typedefs.
This ordering is still the same at the end of the series. Should
tools/include come before the source tree uapi directory?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup
2026-10-08 11:49 ` [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
@ 2026-10-12 8:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Concern: this patch moves get_hw_ring_size() into
detect_ifobj_caps(), which init_iface() runs for both interfaces.
- [Low] In main() (tools/testing/selftests/net/lib/xsk/xskxceiver.c:439),
the new ksft_print_header() call comes after setup code that can…
--
selftests: xsk: split xskxceiver main() into setup, run and cleanup
This commit splits xskxceiver main() into separate steps for interface
binding, capability detection, test execution and a single cleanup path.
HW ring detection moves into detect_ifobj_caps(), and run_pkt_test() now
returns its result. ksft_print_header() is now called before
ksft_set_plan() so that stdout is line buffered.
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index 22a81723d5dea..70a61fb511c8b 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
[ ... ]
> @@ -2332,6 +2335,13 @@ static int detect_ifobj_caps(struct ifobject *ifobj)
> }
> }
>
> + if (!get_hw_ring_size(ifobj->ifname, &ifobj->ring)) {
> + ifobj_set_cap(ifobj, XSK_CAP_HW_RING);
> + ifobj->caps.tx_max_pending = ifobj->ring.tx_max_pending;
> + ifobj->set_ring.default_tx = ifobj->ring.tx_pending;
> + ifobj->set_ring.default_rx = ifobj->ring.rx_pending;
> + }
> +
> return 0;
> }
[Severity: Low]
init_iface() runs detect_ifobj_caps() for both interfaces. Does the RX
ifobject now get XSK_CAP_HW_RING as well?
Before this change, main() only called get_hw_ring_size() for ifobj_tx.
Now cleanup_iface(ifobj_rx) also reaches hw_ring_size_reset() at every
exit:
static void cleanup_iface(struct ifobject *ifobj)
{
if (ifobj_has_cap(ifobj, XSK_CAP_HW_RING))
hw_ring_size_reset(ifobj);
...
That sends ETHTOOL_SRINGPARAM to the RX netdev even though the tests never
changed its rings. A driver that does not short-circuit an unchanged
request could reset its queues here.
The commit message doesn't say that ring handling now covers RX. Was this
intended?
The later patch "selftests: xsk: add a hardware mode to xskxceiver" makes
hw_ring_size_reset() return early when the rings are already at their
defaults. That would make this a no-op for RX by the end of the series.
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index 8b3e4d18e982b..afd6b242aa0df 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
[ ... ]
> @@ -373,93 +408,64 @@ int main(int argc, char **argv)
>
> parse_command_line(ifobj_tx, ifobj_rx, argc, argv);
>
[ ... ]
> + /* Line-buffer stdout so verdicts reach a capturing launcher live. */
> + ksft_print_header();
> + ksft_set_plan(modes * nb_tests);
[Severity: Low]
Should ksft_print_header() be called earlier in main()?
kselftest.h says ksft_print_header() should be the first call. It does
this:
setvbuf(stdout, NULL, _IOLBF, 0);
if (!(getenv("KSFT_TAP_LEVEL")))
printf("TAP version 13\n");
By the time it runs here, main() may already have written to stdout. One
example is when read_procfs_val() fails:
cache_line_size = read_procfs_val(SMP_CACHE_BYTES_PATH);
if (!cache_line_size) {
ksft_print_msg("Can't get SMP_CACHE_BYTES from system, using default (64)\n");
cache_line_size = 64;
}
The MAX_SKB_FRAGS fallback is the same. Anything printed from
parse_command_line(), bind_iface() or detect_mode_caps() also comes
before the header.
In those cases the '#' lines appear before the "TAP version 13" line.
setvbuf() is also called after the stream has been used, and ISO C only
defines setvbuf() before any other operation on the stream.
The placement stays the same through the rest of the series. There,
ksft_print_header() runs after setup_peer(), which blocks until the TCP
peer connects, so any earlier output stays buffered until then.
Would it help to move ksft_print_header() to the start of main(),
possibly after the -l handling?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation
2026-10-08 11:49 ` [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-09 11:25 ` Björn Töpel
@ 2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] At this commit, the jumbo MTU set by a multi-buffer case
carries over to the next case's xskxceiver process.
- [Medium] Each case now runs in its own process, and XDP state a crashed
case leaves behind affects the cases after it.
- [Low] `./test_xsk.sh -h` runs `./${XSKOBJ}` with no arguments, so what
the user sees is xskxceiver's print_usage().
- [Low] The new summary in test_xsk.sh counts skipped cases, but it only
changes the exit status when there are failures.
--
selftests: xsk: run one test case per xskxceiver invocation
This commit moves the mode x case matrix from xskxceiver into test_xsk.sh.
xskxceiver now requires -m and -t and runs a single case per process. An
unsupported mode now becomes a skip instead of XFAIL, and test_xsk.sh
prints a summary of passed, skipped and failed cases.
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index afd6b242aa0df..b8a52846eaf86 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> @@ -169,7 +169,7 @@ static struct option long_options[] = {
> static void print_usage(char **argv)
> {
> const char *str =
> - " Usage: xskxceiver [OPTIONS]\n"
> + " Usage: xskxceiver -i TX_IFACE -i RX_IFACE -m MODE -t TEST [OPTIONS]\n"
> " Options:\n"
> " -i, --interface Use interface\n"
> " -v, --verbose Verbose output\n"
[Severity: Low]
Should the option descriptions in print_usage() change along with the
usage line? The rest of the text still says:
" -m, --mode Run only mode skb, drv, or zc\n"
...
" -t, --test Run a specific test. Enter number from -l option.\n"
That presents -m and -t as optional filters. However, parse_command_line()
now refuses to run unless both are given:
if (opt_run_test == RUN_ALL_TESTS || opt_mode == TEST_MODE_ALL)
print_usage(argv);
Users also see this text from ./test_xsk.sh -h, because the help branch
there runs ./${XSKOBJ} with no arguments. After this patch, test_xsk.sh
rejects -m zc with "Unsupported veth mode: zc (expected skb or drv)". It
also accepts test names for -t, and it does not take the -i TX_IFACE -i
RX_IFACE arguments that the new usage line shows.
The mismatch is still there at the end of the series, where the usage
line becomes "-i IFACE -e rx|tx -m MODE -t TEST -p HOST -P PORT".
[ ... ]
> @@ -420,42 +422,20 @@ int main(int argc, char **argv)
>
> caps = detect_mode_caps(ifobj_tx);
>
> - if (opt_mode != TEST_MODE_ALL && !mode_supported(opt_mode, caps)) {
> + if (!mode_supported(opt_mode, caps)) {
> if (opt_mode == TEST_MODE_DRV)
> - ksft_print_msg("Error: XDP_DRV mode not supported.\n");
> + ksft_print_msg("XDP_DRV mode not supported.\n");
> else
> - ksft_print_msg("Error: zero-copy mode not supported.\n");
> - ksft_exit_xfail();
> + ksft_print_msg("zero-copy mode not supported.\n");
> + ret = TEST_SKIP;
> + goto out;
> }
[Severity: Medium]
Can a multi-buffer case leave the veth pair at the jumbo MTU and cause
every later drv case to skip here?
For multi-buffer cases, __testapp_validate_traffic() calls
test_spec_set_mtu(test, test->mtu), which sets the jumbo MTU on both
veths. At this commit nothing restores it before the process exits.
cleanup_iface() only resets HW rings, detaches XDP and unloads the
skeleton.
The next case runs in a new process. It calls
detect_mode_caps()->is_xdp_supported(), which attaches a native program
without frags support, and veth_xdp_set() refuses it:
drivers/net/veth.c:veth_xdp_set() {
...
if (peer->mtu > max_mtu) {
NL_SET_ERR_MSG_MOD(extack, "Peer MTU is too large to set XDP");
err = -ERANGE;
goto err;
}
...
}
As a result, mode_supported(TEST_MODE_DRV, caps) returns false and the case
is reported as a skip. The MTU is still not put back, so the remaining drv
cases in that pass would skip as well, and test_xsk.sh still exits 0.
The later patch "selftests: xsk: add a hardware mode to xskxceiver" in this
series seems to fix this. It tracks dev_mtu/orig_mtu and restores the MTU
in cleanup_iface(), so only the intermediate commits are affected. Could
the MTU restore be moved into this patch so the series stays bisectable?
[ ... ]
> diff --git a/tools/testing/selftests/net/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
> index 69fac65f073c5..e476556eb05bb 100755
> --- a/tools/testing/selftests/net/test_xsk.sh
> +++ b/tools/testing/selftests/net/test_xsk.sh
[ ... ]
> @@ -168,29 +198,38 @@ if [[ $verbose -eq 1 ]]; then
[ ... ]
> ## START TESTS
>
> statusList=()
> +nameList=()
> +
> +run_matrix()
> +{
> + local mode case_id
> +
> + for mode in "${MODES[@]}"; do
> + for case_id in "${CASES[@]}"; do
> + ARGS="${BASE_ARGS} -m ${mode} -t ${case_id}"
> + TEST_NAME="XSK_${mode}_${case_id}_${RUN_VARIANT}_${VETH0}"
> + exec_xskxceiver
> + done
> + done
> +}
[Severity: Medium]
The commit message says:
"so that every case starts from a fresh process and a case that crashes
fails only itself"
Is that true when a crashed skb case is followed by drv cases on the same
veth pair?
In an skb case, xsk_reattach_xdp()->xsk_attach_xdp_program() attaches the
program with bpf_xdp_attach(..., XDP_FLAGS_SKB_MODE, NULL). That is a
netdev attachment, not a bpf_link, so it outlives the process. If the case
dies abnormally, for example from a signal or an assert() in test_xsk.c,
main() never reaches cleanup_iface() and the generic program stays on
VETH0.
The next drv case then fails its probe in
main()->detect_mode_caps()->is_xdp_supported(). dev_xdp_attach() refuses
native mode while a generic program is attached:
net/core/dev.c:dev_xdp_attach() {
...
if (!offload && dev_xdp_prog(dev, other_mode)) {
NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time");
return -EEXIST;
}
...
}
DRV is then reported as unsupported and the case becomes TEST_SKIP.
cleanup_iface() does not detach anything, because ifobj->xdp_prog is NULL
in the new process:
if (ifobj->xdp_prog)
xsk_detach_xdp_program(...);
run_matrix() runs all skb cases before all drv cases, and the veths are
only recreated between the SOFTIRQ and BUSY_POLL passes. Wouldn't a crash
in the last skb case turn the whole drv pass into skips? With a single -t
selection, the next case is always the one affected.
Later skb cases do recover, because xsk_reattach_xdp() detaches in skb
mode first. The problem is still there at the end of the series: the TX
endpoint's probe fails on VETH0 and it skips while its RX peer goes ahead.
Should leftover XDP programs be detached from the veths before each case?
[ ... ]
> @@ -198,28 +237,47 @@ if [[ $list -eq 1 ]]; then
[ ... ]
> + case ${statusList[$i]} in
> + $ksft_pass)
> + passes=$((passes + 1))
> + ;;
> + $ksft_skip)
> + skips=$((skips + 1))
> + ;;
> + *)
> + failures=$((failures + 1))
> + failed_tests+=("${nameList[$i]}")
> + ;;
> + esac
> done
>
> -if [ $failures -eq 0 ]; then
> - echo "All tests successful!"
> -else
> +echo
> +echo "Summary:"
> +printf " Tests: %d\n" "${#statusList[@]}"
> +printf " Passed: %d\n" "$passes"
> +printf " Skipped: %d\n" "$skips"
> +printf " Failed: %d\n" "$failures"
> +
> +if [ $failures -ne 0 ]; then
> + echo "Failed tests:"
> + for TEST_NAME in "${failed_tests[@]}"; do
> + echo " $TEST_NAME"
> + done
> exit 1
> fi
[Severity: Low]
What exit status should the script return when every selected case skips?
Skips are counted, but only failures change the exit code. A run where
nothing passed therefore exits 0, and the kselftest runner reports PASS
instead of SKIP.
For example, ./test_xsk.sh -m skb -t XDP_ADJUST_TAIL_GROW skips in both
passes, because testapp_adjust_tail_grow() returns early:
if (test->mode == TEST_MODE_SKB)
return TEST_SKIP;
The summary shows zero passed and two skipped, but the script exits 0.
This patch also changes an unsupported mode in main() from
ksft_exit_xfail() to a per-case skip. So a -m drv run whose DRV probe fails
used to fail, and now reports success.
Could the script exit with $ksft_skip when passes is 0 and skips is
non-zero?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes
2026-10-08 11:49 ` [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
@ 2026-10-12 8:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found 11 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 5 · Low: 6
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] exec_xskxceiver() in
tools/testing/selftests/net/xsk_prereqs.sh merges the two endpoint…
- [Medium] The new rendezvous helpers in xsk_prereqs.sh,
wait_port_listen() and random_port(), depend on iproute2's `ss` and…
- [Medium] exec_xskxceiver() gives the background TX endpoint a fixed
budget of about 5 s (50 × `sleep 0.1` in wait_port_listen()) to reach…
- [Medium] The launcher's rendezvous checks the port and then uses it
later, with nothing in between to hold it.
- [Medium] The new TX/RX control channel is an unauthenticated TCP socket
on 127.0.0.1 in the root network namespace, and both ends run as root.
- [Low] The commit message says the endpoints "no longer have to share a
process or a host".
- [Low] xskxceiver's usage text documents `-P, --peer-port Control TCP
port (1-65535)`, but parse_command_line() stores optarg without…
- [Low] test_xsk.sh `-d` is documented as leaving the veths up "so
xskxceiver can be launched in a debugger", and it now prints two…
- [Low] exec_xskxceiver() now starts the TX endpoint as a background job
(`... > ${tx_log} 2>&1 &`) and creates a mktemp log.
- [Low] In xsk_peer_rx_progress(), a peer_send() failure with -EPIPE or
-ECONNRESET sets peer->closed.
- [Low] The concern: xsk_peer_tx_sync() returns the u32 peer->acked as an
int.
--
selftests: xsk: run the RX and TX endpoints in separate processes
This splits xskxceiver so that each process drives one endpoint, selected
with -e rx|tx. The two processes synchronize over a small TCP control
channel using READY, PROGRESS and ABORT messages, and the remote side is
represented by a shadow ifobject. test_xsk.sh starts TX in the background
on a 127.0.0.1 control port and runs RX once TX is listening.
> Run the two endpoints of a test case in separate xskxceiver processes,
> so that they no longer have to share a process or a host. A process
[Severity: Low]
Is "or a host" accurate for this commit on its own? As far as I can tell,
only the same-host veth setup works here.
test_xsk.sh hard-codes PEER_HOST=127.0.0.1, and the generic data path uses
the synthetic g_mac. setup_peer() also assumes the remote side matches
the local one:
shadow->caps = local->caps;
Later patches in the series add --hw, --peer-mac, --listen and
drivers/net/hw/xsk.py to run the endpoints on separate hosts. Those
patches are "selftests: xsk: add a hardware mode to xskxceiver" and
"selftests: drv-net: test AF_XDP zero-copy with an SKB peer". Could the
wording say that separate hosts arrive later in the series?
> diff --git a/tools/testing/selftests/net/lib/xsk/xsk_peer.c b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
> new file mode 100644
> index 0000000000000..faec74aae3794
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
> @@ -0,0 +1,275 @@
[ ... ]
> +static int peer_drain(struct xsk_peer *peer)
> +{
> + struct pollfd pfd = { .fd = peer->fd, .events = POLLIN };
> + int ret;
> +
> + while (!peer->closed) {
[ ... ]
> +int xsk_peer_rx_progress(struct xsk_peer *peer, u32 pkts)
> +{
> + int err;
> +
> + if (pkts && !peer->closed) {
> + err = peer_send(peer, XSK_PEER_PROGRESS, pkts);
> + if (peer_gone(err))
> + peer->closed = true;
> + else if (err)
> + return err;
> + }
> + return peer_drain(peer);
> +}
[Severity: Low]
Can an ABORT from TX get lost here?
Suppose TX fails, sends XSK_PEER_ABORT, and exits while unread PROGRESS
messages are still in its receive queue. Its kernel then resets the
connection.
If the RST arrives between the pacing_rx_progress(0) drain at the top of
__receive_pkts() and the later pacing_rx_progress(pkts_sent), this
peer_send() fails with -ECONNRESET or -EPIPE and sets peer->closed.
peer_drain() then skips its loop because of while (!peer->closed). The
ABORT already queued on the RX socket is never read, so -ECANCELED is
never returned.
The RX side never checks xsk_peer_closed(), so receive_pkts() keeps going
until THREAD_TMOUT and reports "Receive loop timed out". The result is
still a failure, but it shows up late and points at the wrong cause.
Could pending data be drained before the connection is treated as
closed?
[ ... ]
> +/* Return the packets RX reported consumed since the last call, or -errno. */
> +int xsk_peer_tx_sync(struct xsk_peer *peer)
> +{
> + int err = peer_drain(peer);
> + u32 acked = peer->acked;
> +
> + if (err)
> + return err;
> + peer->acked = 0;
> + return acked;
> +}
[Severity: Low]
This returns the u32 peer->acked as an int. A value of 2^31 or more would
look like -errno to pacing_tx_sync(), which then calls strerror(-acked).
Inflated counts would also push pkts_in_flight negative.
With the in-tree peers, acked is reset at every READY and never exceeds
the packets in one step (DEFAULT_PKT_CNT). This only matters if something
other than the real RX endpoint is on the other end of the channel; see
the accept_tmout() comment below. Is it worth bounding the value that
peer_recv_one() adds to peer->acked?
> +static int accept_tmout(int lfd)
> +{
> + struct pollfd pfd = { .fd = lfd, .events = POLLIN };
> + int ret;
> +
> + do {
> + ret = poll(&pfd, 1, XSK_PEER_ACCEPT_TMOUT_MS);
> + } while (ret < 0 && errno == EINTR);
> + if (!ret)
> + errno = ETIMEDOUT;
> + return ret > 0 ? accept(lfd, NULL, NULL) : -1;
> +}
[Severity: Medium]
Can an unprivileged local user take over this control channel?
Both endpoints run as root, but the channel is a plain TCP socket on
127.0.0.1 in the root network namespace:
- accept_tmout() accepts the first connection from anyone.
- peer_open_tcp() then closes the listener.
- peer_recv_one() only checks the public XSK_PEER_MAGIC constant.
random_port() picks PEER_PORT once, any user can see it with ss -ltn,
and it is reused for every case. An unprivileged user can either:
- connect to the TX listener before the real RX does, or
- bind 127.0.0.1:PEER_PORT between cases, so that the next TX bind()
fails and wait_port_listen() treats the squatter as TX.
>From there, injected READY, PROGRESS and ABORT frames can abort cases or
skew the pacing. They can also stall the root RX process indefinitely in
xsk_peer_ready(), which reads with no timeout.
Would checking SO_PEERCRED after accept(), or passing a per-run token
from the launcher, address this?
[ ... ]
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index b8a52846eaf86..e66625810a9d8 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
[ ... ]
> @@ -177,16 +193,18 @@ static void print_usage(char **argv)
> " -m, --mode Run only mode skb, drv, or zc\n"
> " -l, --list List all available tests\n"
> " -t, --test Run a specific test. Enter number from -l option.\n"
> + " -e, --endpoint Role of this endpoint: rx or tx\n"
> + " -p, --peer Control host: IPv4/IPv6 address or hostname\n"
> + " -P, --peer-port Control TCP port (1-65535)\n"
> " -h, --help Display this help and exit\n";
[ ... ]
> @@ -261,17 +274,30 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
[ ... ]
> + case 'P':
> + opt_peer_port = optarg;
> + break;
[Severity: Low]
The usage text gives the range 1-65535, but the value is stored without
any check. peer_open_tcp() then passes it to getaddrinfo() without
AI_NUMERICSERV, so service names like "ssh" and the value "0" are
accepted.
With "0", TX binds an ephemeral port, but the launcher's
wait_port_listen 0 looks for sport = :0. A healthy run then fails with
"TX endpoint did not listen on ...". test_xsk.sh -p also forwards the
value without checking it.
Should -P be parsed as a number in 1-65535, the way -t is checked here
and --udp-port is checked with parse_u32() later in the series?
[ ... ]
> diff --git a/tools/testing/selftests/net/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
> index e476556eb05bb..2ef9ce7e1b27d 100755
> --- a/tools/testing/selftests/net/test_xsk.sh
> +++ b/tools/testing/selftests/net/test_xsk.sh
[ ... ]
> @@ -181,6 +186,13 @@ if [ ${#CASES[@]} -eq 0 ]; then
> exit 1
> fi
>
> +if [ -z "$PEER_PORT" ]; then
> + PEER_PORT=$(random_port) || {
> + echo "Could not find an unused TCP port" >&2
> + exit 1
> + }
> +fi
> +
> validate_root_exec
> validate_veth_support ${VETH0}
> validate_ip_utility
[Severity: Medium]
What happens on a system that has ip but no usable ss?
random_port() and wait_port_listen() both depend on iproute2's ss and
send its stderr to /dev/null. Only ip is checked, through
validate_ip_utility(), and that check runs after random_port() here.
If ss is missing, or too old to accept -H, random_port() treats every
port as free. This test in wait_port_listen() then never succeeds:
if [ -n "$(ss -Hltn "sport = :$1" 2>/dev/null)" ]; then
Every case would report FAIL with "TX endpoint did not listen on ..."
instead of being skipped. Each case also waits out the 30s accept timeout
in TX, because the background process is not killed.
Should there be a check for ss next to validate_ip_utility()?
> @@ -225,7 +237,8 @@ RUN_VARIANT=SOFTIRQ
>
> if [[ $debug -eq 1 ]]; then
> ARGS="${BASE_ARGS} -m ${MODES[0]} -t ${CASES[0]}"
> - echo "./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}"
> + echo "./${XSKOBJ} -i ${VETH0} -e tx -p ${PEER_HOST} -P ${PEER_PORT} ${ARGS}"
> + echo "./${XSKOBJ} -i ${VETH1} -e rx -p ${PEER_HOST} -P ${PEER_PORT} ${ARGS}"
> exit
> fi
[Severity: Low]
Does the -d workflow still work when these two commands are run by hand,
for example under a debugger?
The control channel assumes the scripted launcher, as the comment above
peer_open_tcp() says:
/*
* The launcher starts the listener and waits for its port before starting
* the connector, so connect() needs no retry. accept() is bounded in case
* the connector exits before it gets that far.
*/
connect() is tried only once, so starting RX first fails at once with
ECONNREFUSED. Starting RX more than XSK_PEER_ACCEPT_TMOUT_MS (30s) after
TX reaches accept() makes TX fail with ETIMEDOUT.
Should the printed commands mention the required order and time limit,
or should connect() retry?
> diff --git a/tools/testing/selftests/net/xsk_prereqs.sh b/tools/testing/selftests/net/xsk_prereqs.sh
> index 30173db12c567..82cd7839d77d9 100755
> --- a/tools/testing/selftests/net/xsk_prereqs.sh
> +++ b/tools/testing/selftests/net/xsk_prereqs.sh
> @@ -69,16 +69,63 @@ validate_ip_utility()
> [ ! $(type -P ip) ] && { echo "'ip' not found. Skipping tests."; test_exit $ksft_skip; }
> }
>
> +wait_port_listen()
> +{
> + local i
> +
> + for i in $(seq 50); do
> + if [ -n "$(ss -Hltn "sport = :$1" 2>/dev/null)" ]; then
> + return 0
> + fi
> + kill -0 $2 2>/dev/null || return 1
> + sleep 0.1
> + done
> + return 1
> +}
[Severity: Medium]
Can this pick up a listener that is not the TX endpoint?
wait_port_listen() returns success as soon as ss shows any listener on
the port. It only looks at $2 (tx_pid) when nothing is listening.
random_port() checks the port only once, before the veth setup. Nothing
reserves it, and the same PEER_PORT is used for every case.
If another process holds the port, or -p is given a busy port, TX's
bind() fails with EADDRINUSE and TX exits. wait_port_listen() still
succeeds, and RX connects to the foreign listener and ends up in:
xsk_peer_ready()
peer_recv_one()
io_full()
read() <- no timeout
A listener that never sends anything hangs the case indefinitely. One
that does send data makes RX fail with -EPROTO. Either way, the verdict
comes from an unrelated process.
> +
> +random_port()
> +{
> + local i port
> +
> + for i in $(seq 50); do
> + port=$((32768 + RANDOM))
> + if [ -z "$(ss -Hltn "sport = :$port" 2>/dev/null)" ]; then
> + echo "$port"
> + return 0
> + fi
> + done
> + return 1
> +}
> +
> +# The TX endpoint listens on the control port and runs in the background,
> +# the RX endpoint connects to it and reports; both print the same verdicts.
> exec_xskxceiver()
> {
> - local run_args="${ARGS}"
> + local tx_log tx_pid tx_ret run_args="${ARGS}"
>
> if [[ $busy_poll -eq 1 ]]; then
> run_args+=" -b"
> fi
>
> - ./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${run_args}
> - retval=$?
> + tx_log=$(mktemp)
> + ./${XSKOBJ} -i ${VETH0} -e tx -p ${PEER_HOST} -P ${PEER_PORT} ${run_args} > ${tx_log} 2>&1 &
> + tx_pid=$!
[Severity: Low]
What happens to this background TX process and to tx_log on Ctrl-C?
The only signal handler is ctrl_c() in test_xsk.sh:
trap ctrl_c INT
function ctrl_c() {
cleanup_exit ${VETH0} ${VETH1}
exit 1
}
In a non-interactive bash script, background jobs ignore SIGINT, so only
the foreground RX is killed.
The TX xskxceiver keeps running with its AF_XDP socket, UMEM and XDP
program while the trap deletes the veths under it. It stops only when it
sees EOF or hits its accept timeout.
The mktemp file is also left behind, because rm -f ${tx_log} only runs
on the normal path.
> +
> + if wait_port_listen ${PEER_PORT} ${tx_pid}; then
> + ./${XSKOBJ} -i ${VETH1} -e rx -p ${PEER_HOST} -P ${PEER_PORT} ${run_args}
> + retval=$?
> + else
> + echo "TX endpoint did not listen on ${PEER_HOST}:${PEER_PORT}"
> + retval=1
> + fi
> +
> + wait ${tx_pid}
> + tx_ret=$?
[Severity: Medium]
Is about 5 seconds (50 x sleep 0.1) enough time for TX to reach
listen()?
In xskxceiver main(), listen() happens only in
setup_peer()->xsk_peer_open(). Before that, TX runs:
bind_iface() skeleton open_and_load, capability detection
detect_mode_caps() is_xdp_supported() attach/detach, and
ifobj_zc_avail() UMEM mmap plus XDP_ZEROCOPY bind
pkt_stream_generate() twice, with DEFAULT_PKT_CNT
xsk_load_xdp_programs() a second skeleton load for the shadow
On a slow machine (KASAN, lockdep, nested virtualization) this could miss
the window.
If it does, retval is set to 1 and RX is never started. tx_pid is not
killed, so wait ${tx_pid} blocks until accept_tmout() gives up after
XSK_PEER_ACCEPT_TMOUT_MS (30s). A healthy pair is then reported as a
failure after a stall of about 35 seconds.
Should TX be killed in the else branch, and should the two timeouts be
aligned?
Separately, random_port() draws ports from 32768 + RANDOM, which overlaps
the default ephemeral port range, and the port is reused for every case.
Can a later ephemeral bind on 127.0.0.1 or the wildcard address make
TX's bind() fail for all remaining cases?
> + if [[ $tx_ret -ne 0 || $verbose -eq 1 ]]; then
> + sed 's/^/tx| /' ${tx_log}
> + fi
> + rm -f ${tx_log}
> + [[ $retval -eq 0 ]] && retval=$tx_ret
[Severity: Medium]
Can a TX failure be hidden here?
TX's status is used only when RX returned 0. As a result, RX SKIP (4)
with TX FAIL (1) is recorded as SKIPPED, while TX SKIP with RX FAIL is
recorded as FAIL.
The comment above exec_xskxceiver() says "both print the same verdicts",
but test_spec is no longer shared between the endpoints.
For the XDP_ADJUST_TAIL* cases, testapp_adjust_tail() sets
adjust_tail_support in both processes. Only the RX process can clear it,
in testapp_validate_rx_endpoint():
if (is_adjust_tail_supported(ifobject->xdp_progs, &supported))
return TEST_FAILURE;
if (!supported) {
test->adjust_tail_support = false;
return TEST_PASS;
}
testapp_adjust_tail() then picks its verdict from that per-process flag:
if (!test->adjust_tail_support) {
ksft_print_msg("%s %sResize pkt with bpf_xdp_adjust_tail() not supported\n",
mode_string(test), busy_poll_string(test));
return TEST_SKIP;
}
So when the helper is unsupported, TX prints PASS and RX prints SKIP.
Now suppose TX also fails after packets have reached
xsk_xdp_adjust_tail on the RX side. The RX error, including -ECANCELED
from the ABORT, is turned into TEST_PASS above. testapp_adjust_tail()
then returns TEST_SKIP, and the case is recorded as SKIPPED.
Before this patch, the TX thread set test->fail and the same case was
reported as FAIL.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver
2026-10-08 11:49 ` [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
2026-10-09 11:50 ` sashiko-bot
@ 2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 6
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] In --hw mode, the code compares two different MTU units, so
the NIC MTU is changed on almost every case.
- [Medium] The commit message says is_frag_valid() now rejects fragments
that are too short 'instead of reading past the end of the buffer'.
- [Medium] When a hardware endpoint changes its MTU (on standard 1500-MTU
hosts that happens every case, see the MTU-unit finding), nothing…
- [Low] The commit message says 'a hardware endpoint prints no KTAP
output of its own'.
- [Low] The XDP_ADJUST_TAIL_GROW header-size mismatch is real in the
code: userspace uses pkt_hdr_size = 44 (eth + IPv4 + UDP + 2), while…
- [Low] Mode-dependent test gating differs between the ZC DUT and the SKB
peer.
- [Low] With --hw, detect_mode_caps() never sets XSK_CAP_DRV unless the
mode is ZC and ZC is advertised.
- [Low] bind_iface() now loads the device's current MTU into ifobj->mtu
before detect_mode_caps() runs.
- [Low] The new ip_checksum() reads a struct iphdr through a 'const u16
*'.
--
selftests: xsk: add a hardware mode to xskxceiver
This adds a --hw mode to xskxceiver. A later drivers/net/hw/xsk.py runner
uses it to drive a zero-copy DUT and an SKB-mode remote endpoint over a
physical link, sending UDP/IPv4 test packets between the real interface
MACs. It also adds --queue, --listen and --max-frags, longer timeouts for
hardware cases, MTU restore in cleanup_iface(), and length guards in
is_frag_valid() for the variable header size.
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index 8d30c39c94c84..542c0579062a4 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
[ ... ]
> @@ -84,11 +97,70 @@ static void write_payload(void *dest, u32 pkt_nb, u32 start, u32 size)
> ptr[i] = htonl(pkt_nb << 16 | (i + start));
> }
>
> -static void gen_eth_hdr(struct xsk_socket_info *xsk, struct ethhdr *eth_hdr)
> +int xsk_set_udp_packet_format(const char *src_ip, const char *dst_ip, u16 port)
> {
> + if (inet_pton(AF_INET, src_ip, &udp_src_ip) != 1 ||
> + inet_pton(AF_INET, dst_ip, &udp_dst_ip) != 1 || !port)
> + return -EINVAL;
> + udp_packets = true;
> + udp_port = port;
> + pkt_hdr_size = UDP_PKT_HDR_SIZE;
[Severity: Low]
The xsk.py runner later in the series can't reach this, but should
xsk_xdp_adjust_tail() in xsk_xdp_progs.bpf.c use this header size as well?
With --hw, userspace now uses a 44 byte header. The BPF side still computes
the grown tail seqnum with the fixed 16 byte header:
len = curr_buff_len - PKT_HDR_ALIGN;
test_xsk_case_defs.h gives XDP_ADJUST_TAIL_GROW and
XDP_ADJUST_TAIL_GROW_MULTI_BUFF hardware direction 0, and the SKB peer skips
these cases anyway. So this would only show up if someone started two --hw
endpoints in ZC mode by hand.
> + return 0;
> +}
> +
> +void xsk_set_max_frags(u32 max_frags)
> +{
> + max_frags_override = max_frags;
> +}
> +
> +static u16 ip_checksum(const void *buf, size_t len)
> +{
> + const u16 *word = buf;
> + u32 sum = 0;
> +
> + while (len > 1) {
> + sum += *word++;
> + len -= sizeof(*word);
> + }
[Severity: Low]
This doesn't affect the test results as far as I can tell, but
ip_checksum() reads the local struct iphdr through a const u16 pointer.
That includes the __be32 saddr and daddr fields that gen_pkt_hdr() has just
stored.
xskxceiver is now built from tools/testing/selftests/net/lib with -O2 and
without -fno-strict-aliasing:
CFLAGS += -Wall -Wl,--no-as-needed -O2 -g
Under strict aliasing, could the compiler move these u16 loads ahead of the
u32 stores and produce a wrong IPv4 checksum? Copying the bytes with
memcpy() or using a union would avoid the type punning.
> + while (sum >> 16)
> + sum = (sum & 0xffff) + (sum >> 16);
> + return ~sum;
> +}
[ ... ]
> @@ -347,14 +430,25 @@ static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *x
> test->xskmap_tx = xskmap_tx;
> }
>
> -static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, int mtu)
> +/* An MTU change can reset a NIC. A hardware SKB-mode endpoint runs a case at
> + * any larger MTU, as generic XDP does not limit it, so only raise its MTU.
> + */
> +static bool mtu_change_needed(struct ifobject *ifobj, enum test_mode mode, int mtu)
> +{
> + if (hw_test && mode == TEST_MODE_SKB)
> + return ifobj->dev_mtu < mtu;
> + return ifobj->dev_mtu != mtu;
> +}
[Severity: Medium]
Are dev_mtu and the mtu argument in the same units here?
bind_iface() fills dev_mtu and orig_mtu from SIOCGIFMTU, which is the L3
MTU (normally 1500). The default test->mtu set in __test_spec_init() is a
frame size:
test->mtu = MAX_ETH_PKT_SIZE;
That 1518 value then reaches xsk_set_mtu() as IFLA_MTU.
On a host with the standard 1500 MTU, mtu_change_needed() returns true for
the ZC DUT (1500 != 1518) and for the SKB peer (1500 < 1518). Every case
then sets the NIC to 1518 first, and cleanup_iface() puts it back to 1500
afterwards:
if (ifobj->dev_mtu != ifobj->orig_mtu) {
err = xsk_set_mtu(ifobj->ifindex, ifobj->orig_mtu);
That is two NIC reconfigurations per case on both hosts, which this comment
and the commit message aim to avoid. On ixgbe, for example, ndo_change_mtu
goes through ixgbe_reinit_locked(), and ixgbe_down() turns off the TX
laser.
A peer whose max_mtu is ETH_DATA_LEN would also reject 1518 in
dev_validate_mtu(). __testapp_validate_traffic() would then fail every
hardware case with "could not set mtu", even though the default cases only
send 64 byte frames.
> +
> +static int test_spec_set_mtu_ifobj(struct ifobject *ifobj, enum test_mode mode, int mtu)
> {
> int err;
>
> - if (ifobj_is_local(ifobj) && ifobj->mtu != mtu) {
> + if (ifobj_is_local(ifobj) && mtu_change_needed(ifobj, mode, mtu)) {
> err = xsk_set_mtu(ifobj->ifindex, mtu);
> if (err)
> return err;
> + ifobj->dev_mtu = mtu;
> }
[Severity: Medium]
After xsk_set_mtu() succeeds on a hardware endpoint, does anything wait for
the physical link to come back before traffic starts?
__testapp_validate_traffic() goes from test_spec_set_mtu() straight to
xsk_attach_xdp_progs(), socket setup and the READY exchange. The READY
exchange uses the separate control connection:
err = test_spec_set_mtu(test, test->mtu);
...
err = xsk_attach_xdp_progs(test, ifobj1, ifobj2);
On a driver like ixgbe, ixgbe_change_mtu() calls ixgbe_reinit_locked() and
carrier drops on both ends. While carrier is down, generic AF_XDP TX on the
SKB-mode peer goes through __dev_direct_xmit(), which drops and completes
the frames.
AF_XDP never retransmits. Can the RX endpoint then time out and report a
false failure? The longer HW_THREAD_TMOUT would not bring back frames that
were already dropped.
Combined with the MTU unit question above, this would happen on every case
on hosts with the standard 1500 MTU.
>
> ifobj->mtu = mtu;
[ ... ]
> @@ -867,11 +974,15 @@ static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 exp
>
> pkt_data = data;
> if (!bytes_processed) {
> - pkt_data += PKT_HDR_SIZE / sizeof(*pkt_data);
> - len -= PKT_HDR_SIZE;
> + if (len < pkt_hdr_size + sizeof(*pkt_data))
> + return false;
> + pkt_data += pkt_hdr_size / sizeof(*pkt_data);
> + len -= pkt_hdr_size;
> } else {
> - bytes_processed -= PKT_HDR_SIZE;
> + bytes_processed -= pkt_hdr_size;
> }
> + if (len < sizeof(*pkt_data))
> + return false;
[Severity: Medium]
The commit message says these guards reject short fragments "instead of
reading past the end of the buffer". Can the error path still read past
the fragment through pkt_dump()?
In UDP mode pkt_hdr_size is 44, so this guard accepts a 48 byte first
fragment. If pkt_nb or seqnum then mismatches, is_frag_valid() reaches:
error:
pkt_dump(data, len, !bytes_processed);
pkt_dump() then does:
data = pkt + pkt_hdr_size;
...
pkt_print_data(data, PKT_DUMP_NB_TO_PRINT);
That reads 16 words (64 bytes) starting at offset 44, so it reaches byte
108 of a 48 byte fragment.
is_pkt_valid() also calls pkt_dump() with the first fragment's address and
the full packet length:
pkt_dump(xsk_umem__get_data(buffer, addr), len, true);
So the tail dump at data + len / sizeof(u32) - PKT_DUMP_NB_TO_PRINT always
ends pkt_hdr_size bytes past the packet (44 now, 16 before). For a
multi-fragment packet it ends well past the first frame.
If the first fragment is in one of the last UMEM frames, could this run past
the mmap and crash the RX endpoint with SIGSEGV instead of reporting a
failure? With --hw, the received lengths come from the physical link and
the driver under test.
>
> expected_seqnum = bytes_processed / sizeof(*pkt_data);
> seqnum = ntohl(*pkt_data) & 0xffff;
[ ... ]
> @@ -2230,6 +2346,10 @@ int testapp_xdp_shared_umem(struct test_spec *test)
> struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs;
> int ret;
>
> + /* The XDP program picks the socket from the synthetic destination MAC. */
> + if (hw_test)
> + return TEST_SKIP;
> +
[Severity: Low]
The runner added later in the series doesn't hit this, but several cases
skip based on test->mode, and the two --hw endpoints run in different
modes. STAT_RX_DROPPED and TX_QUEUE_CONSUMER skip in ZC mode. For example,
testapp_stats_rx_dropped() has:
if (test->mode == TEST_MODE_ZC) {
ksft_print_msg("Can not run RX_DROPPED test for ZC mode\n");
return TEST_SKIP;
ADJUST_TAIL_GROW and ADJUST_TAIL_GROW_MB skip in SKB mode instead. So one
endpoint could skip while its peer runs the case.
Later in the series, test_xsk_case_defs.h gives these cases hardware
direction 0, and xsk.py's _case_result() treats a skip from either endpoint
as a case skip. Only manual runs are affected. Would it make sense to
handle these cases with hw_test, as done for XDP_SHARED_UMEM here, so both
endpoints agree?
> test->total_steps = 1;
> test->nb_sockets = 2;
[ ... ]
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index e66625810a9d8..8b94dc3709ea8 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
[ ... ]
> @@ -220,6 +260,12 @@ static void bind_iface(struct ifobject *ifobj, const char *ifname, char **argv)
> ksft_print_msg("Error: cannot initialize interface %s\n", ifobj->ifname);
> ksft_exit_fail();
> }
> + if (xsk_get_mtu(ifobj->ifname, &ifobj->mtu)) {
> + ksft_print_msg("Error: cannot read MTU of interface %s\n", ifobj->ifname);
> + ksft_exit_fail();
> + }
> + ifobj->dev_mtu = ifobj->mtu;
> + ifobj->orig_mtu = ifobj->mtu;
> }
[Severity: Low]
Does reading the device MTU into ifobj->mtu here change the zero-copy probe
for runs without --hw?
bind_iface() runs before detect_mode_caps(). ifobj_zc_avail() binds through
xsk_configure_socket(), which does:
if (ifobject->mtu > MAX_ETH_PKT_SIZE)
cfg.bind_flags |= XDP_USE_SG;
Before this patch, ifobj->mtu was 0 when the probe ran, so the probe was
always a single-buffer bind. xp_assign_dev() rejects an XDP_USE_SG bind
with -EOPNOTSUPP when the driver's xdp_zc_max_segs is 1.
Take such a driver with a device MTU between 1519 and roughly 3814. Would
XSK_CAP_ZC now stay unset, so a manual ZC run without --hw skips where it
used to run? Larger MTUs already failed the probe before this patch.
[ ... ]
> @@ -363,7 +455,11 @@ static bool is_xdp_supported(int ifindex)
>
> static u32 detect_mode_caps(struct ifobject *ifobj)
> {
> - if (is_xdp_supported(ifobj->ifindex)) {
> + if (opt_hw && opt_mode == TEST_MODE_ZC &&
> + ifobj_has_cap(ifobj, XSK_CAP_ZC_ADVERTISED)) {
> + /* Trust the advertised flag; an XDP_ZEROCOPY bind never falls back to copy. */
> + ifobj_set_cap(ifobj, XSK_CAP_DRV | XSK_CAP_ZC);
> + } else if (!opt_hw && is_xdp_supported(ifobj->ifindex)) {
[Severity: Low]
With --hw, is XSK_CAP_DRV ever set other than in the advertised ZC case?
The is_xdp_supported() probe now only runs when !opt_hw. As a result,
"xskxceiver --hw -m drv" always ends up in main() at:
ksft_print_msg("XDP_DRV mode not supported.\n");
This happens even on NICs with native XDP. xsk.py only uses zc and skb, so
only manual runs are affected. However, the skip reason is misleading, and
the commit message only mentions the zero-copy detection change.
> ifobj_set_cap(ifobj, XSK_CAP_DRV);
> if (ifobj_zc_avail(ifobj))
> ifobj_set_cap(ifobj, XSK_CAP_ZC);
[ ... ]
> @@ -501,27 +619,32 @@ int main(int argc, char **argv)
> }
>
> /* Line-buffer stdout so verdicts reach a capturing launcher live. */
> - ksft_print_header();
> - ksft_set_plan(1);
> + if (!opt_hw) {
> + ksft_print_header();
> + ksft_set_plan(1);
> + }
[ ... ]
> - if (ret == TEST_SKIP && ksft_test_num()) {
> + if (ret == TEST_SKIP && !opt_hw && ksft_test_num()) {
> ksft_print_cnts();
> return KSFT_SKIP;
> }
> if (ret == TEST_SKIP)
> - ksft_exit_skip("mode not supported\n");
> + ksft_exit_skip("mode or test not supported\n");
> if (ret)
> ksft_exit_fail();
> else
[Severity: Low]
The commit message says "a hardware endpoint prints no KTAP output of its
own and reports through its exit code". Is that accurate?
With --hw, ksft_print_header() and ksft_set_plan() are skipped. A skip
still goes through ksft_exit_skip(), which prints the KTAP plan line when
no plan was set:
1..0 # SKIP mode or test not supported
ksft_exit_pass() and ksft_exit_fail() call ksft_print_cnts(), which prints
the "# Totals: ..." line.
xsk.py prefixes each forwarded line with "#" through ksft_pr(), so the
runner's TAP stream is unaffected. Still, the commit message doesn't match
what the code does.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode
2026-10-08 11:49 ` [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
@ 2026-10-12 8:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The comment this patch adds in xsk_load_xdp_programs() ("The
shadow's copy too, swapped directions attach its programs.") is…
- [Low] In hardware (UDP) mode, xsk_xdp_adjust_tail() in
xsk_xdp_progs.bpf.c writes the wrong sequence number at the end of a…
- [Low] is_test_packet() in xsk_xdp_progs.bpf.c does not check
ip->frag_off before reading the bytes after the IPv4 header as a…
Pre-existing issues:
- [Medium] is_adjust_tail_supported() in
tools/testing/selftests/net/lib/xsk/test_xsk.c overflows a stack…
--
selftests: xsk: pass non-test traffic to the stack in hardware mode
In hardware mode, this commit makes the xskxceiver XDP programs redirect
only UDP/IPv4 packets sent to the test port (--udp-port). Everything else
is passed to the stack. A new hw_port .bss variable is set from
xsk_load_xdp_programs(), and an is_test_packet() helper gates each
program. When hw_port is 0, every packet is still redirected.
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index 542c0579062a4..1033a5430697a 100644
> --- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
> +++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> @@ -2454,6 +2454,8 @@ int xsk_load_xdp_programs(struct ifobject *ifobj)
> if (libbpf_get_error(ifobj->xdp_progs))
> return libbpf_get_error(ifobj->xdp_progs);
>
> + /* The shadow's copy too, swapped directions attach its programs. */
> + ifobj->xdp_progs->bss->hw_port = udp_port;
[Severity: Low]
This comment looks right, but it contradicts the comment in setup_peer()
in xskxceiver.c, which was added earlier in this series:
tools/testing/selftests/net/lib/xsk/xskxceiver.c:setup_peer() {
/* Unattached skeleton copy; the engine only attaches the local side. */
if (xsk_load_xdp_programs(shadow))
return TEST_FAILURE;
...
}
test_spec_reset() binds test->xdp_prog_tx and test->xskmap_tx to
ifobj_tx->xdp_progs. That is the shadow when the local endpoint is RX.
swap_directions() only swaps the ifobject pointers. After the swap,
xsk_attach_xdp_progs() does:
if (xdp_prog_changed_tx(test))
err = xsk_reattach_xdp(ifobj_tx, test->xdp_prog_tx, test->xskmap_tx, test->mode);
This attaches the shadow's program and XSKMAP to the real NIC, for
example in testapp_bidirectional().
Should the setup_peer() comment be updated too? A reader who trusts it
could drop the hw_port write for the shadow. That would bring back the
redirect-everything behaviour after a direction swap.
> return 0;
> }
>
> diff --git a/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c b/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
> index 023d8befd4cab..716360d023a77 100644
> --- a/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
> +++ b/tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c
[ ... ]
> @@ -18,9 +22,34 @@ struct {
> static unsigned int idx;
> int adjust_value = 0;
> int count = 0;
> +/* UDP port of the hardware test flow, 0 redirects every packet */
> +__u16 hw_port;
[Severity: Medium]
This is a pre-existing issue, but adding hw_port to .bss makes it
somewhat worse. is_adjust_tail_supported() in test_xsk.c reads the whole
.bss map value into a 4-byte local:
tools/testing/selftests/net/lib/xsk/test_xsk.c:is_adjust_tail_supported() {
int adjust_value = 0;
...
ret = bpf_map_lookup_elem(bpf_map__fd(data_map), &key, &adjust_value);
...
}
bpf_map_lookup_elem() copies value_size bytes, which here is the full
.bss section. Before this patch, .bss already held idx, adjust_value,
count and drop_idx, so about 16 bytes were written into a 4-byte int.
With hw_port added, it is now about 18-20 bytes.
Can this overwrite other stack variables, or trip the stack protector?
This happens when testapp_validate_rx_endpoint() calls
is_adjust_tail_supported() after an adjust_tail test fails.
This patch already sets hw_port through the skeleton. Could
is_adjust_tail_supported() read skel_rx->bss->adjust_value the same way?
> +
> +static __always_inline bool is_test_packet(struct xdp_md *xdp)
> +{
> + void *data_end = (void *)(long)xdp->data_end;
> + void *data = (void *)(long)xdp->data;
> + struct ethhdr *eth = data;
> + struct udphdr *udp;
> + struct iphdr *ip;
> +
> + if (!hw_port)
> + return true;
> + if ((void *)(eth + 1) > data_end ||
> + eth->h_proto != bpf_htons(ETH_P_IP))
> + return false;
> + ip = (void *)(eth + 1);
> + if ((void *)(ip + 1) > data_end || ip->ihl != 5 ||
> + ip->protocol != IPPROTO_UDP)
> + return false;
> + udp = (void *)(ip + 1);
> + return (void *)(udp + 1) <= data_end && udp->dest == bpf_htons(hw_port);
> +}
[Severity: Low]
Should is_test_packet() also check ip->frag_off? A non-initial IPv4
fragment has no UDP header after the IP header. If it has ihl == 5 and
protocol == IPPROTO_UDP, its payload bytes 2-3 are read here as
udp->dest.
If those bytes happen to equal htons(hw_port), is_test_packet() returns
true. xsk_def_prog() and the other programs then redirect, drop or
modify an unrelated fragment instead of returning XDP_PASS.
That datagram can then not be reassembled. The test socket may also
receive a frame that fails validation.
The commit message says only UDP/IPv4 packets sent to the test port are
redirected. Other parsers in the tree, such as xdp_flowtable.c and
xdp_synproxy_kern.c, reject fragments by checking
frag_off & bpf_htons(IP_MF | IP_OFFSET).
[ ... ]
> @@ -80,6 +116,9 @@ SEC("xdp.frags") int xsk_xdp_adjust_tail(struct xdp_md *xdp)
> __u32 buff_len, curr_buff_len;
> int ret;
>
> + if (!is_test_packet(xdp))
> + return XDP_PASS;
> +
> buff_len = bpf_xdp_get_buff_len(xdp);
> if (buff_len == 0)
> return XDP_DROP;
[Severity: Low]
This patch gates xsk_xdp_adjust_tail() for hardware mode. Does its grow
path still write the wrong end sequence number for UDP frames?
tools/testing/selftests/net/lib/xsk/xsk_xdp_progs.bpf.c:xsk_xdp_adjust_tail() {
...
if (curr_buff_len > buff_len) {
...
len = curr_buff_len - PKT_HDR_ALIGN;
words_to_end = len / sizeof(*pkt_data) - 1;
...
}
PKT_HDR_ALIGN is sizeof(struct ethhdr) + 2, which is 16. In hardware
mode, xsk_set_udp_packet_format() sets pkt_hdr_size to UDP_PKT_HDR_SIZE
(44), and is_frag_valid() uses that value:
pkt_data += pkt_hdr_size / sizeof(*pkt_data);
Take a 132-byte grown frame. The program stores (132 - 16) / 4 - 1 = 28,
but is_frag_valid() expects (132 - 44) / 4 - 1 = 21. The check then
prints "expected seqnum at end [21], got seqnum [28]". The multi-buffer
variant is off by the same 7 words.
testapp_adjust_tail_grow() and testapp_adjust_tail_grow_mb() only skip
TEST_MODE_SKB. testapp_xdp_shared_umem(), by contrast, returns TEST_SKIP
under hw_test.
xsk.py does not schedule these cases on hardware, because their hardware
directions are 0 in test_xsk_case_defs.h. Running xskxceiver --hw
directly with these cases would fail rather than skip.
The mismatch comes from the earlier patch "selftests: xsk: add a hardware
mode to xskxceiver", and this patch leaves it in place. Should these
cases skip under hw_test, or should the program use the UDP header size
in hardware mode?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-08 21:37 ` Jakub Kicinski
2026-10-09 11:50 ` sashiko-bot
@ 2026-10-12 8:50 ` netdev-bot+sashiko
2 siblings, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 4 · Low: 5
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] In DUT TX cases with an SSH remote, _setup_case() binds the
DUT zero-copy XSK socket to queue 0 (`queue = _reserve_queue(cfg) if…
- [Medium] For SSH remotes, _control_addr() takes the control-channel
address from the SSH destination…
- [Medium] _reserve_queue() undoes its RSS change with
`defer(cfg.ethnl.rss_set, {"header": header, "indir": saved})`, which…
- [Medium] With the SSH backend, the finally block in _run_endpoints()
(`if remote.ret is None: remote.process(terminate=True, fail=False)`)…
- [Low] The commit message is inaccurate about skip coverage and
configuration knobs.
- [Low] Two timeout paths in _run_endpoints() skip _case_result(), so the
endpoint output is never printed.
- [Low] Concern: _steer_udp() registers the deferred `ethtool -N <dev>
delete <id>` only after it parses 'ID (\d+)' from stdout.
- [Low] _reserve_queue() always picks `combined-count + rx-count - 1`,
i.e. the last RX queue, as the DUT queue.
- [Low] selftests: drv-net: xsk.py crashes due to missing
test_xsk_case_defs.h dependency
--
selftests: drv-net: test AF_XDP zero-copy with an SKB peer
This commit adds drivers/net/hw/xsk.py. The script runs each hardware
AF_XDP case from test_xsk_case_defs.h as a pair of xskxceiver processes:
a zero-copy DUT endpoint and a remote SKB-mode endpoint, where the remote
is reached over SSH or lives in a netns. Each case sets up RSS and ntuple
steering, hugepages and busy-poll knobs as needed, and undoes them with
defer().
> A setup failure therefore skips only the cases
> that need that setup: a one-channel DUT still runs the TX cases and a
> remote without ntuple filters still runs the RX ones.
[Severity: Low]
Is the one-channel statement accurate for a netns remote? In that topology
the TX cases also call _reserve_queue() from _setup_case(), and that
function skips when there are fewer than two channels:
if count < 2:
raise KsftSkipEx("RX queue isolation needs at least two channels")
With REMOTE_TYPE=netns, a one-channel DUT therefore skips the TX cases too.
A later patch in the series ("selftests: xsk: document generic and
hardware endpoint runs") corrects this in the README for netns. The
message for this commit still says otherwise.
> XSK_REMOTE_BIN names a remote path to copy it to instead, or, with
> XSK_REMOTE_DEPLOY=0, an already installed binary.
These knobs read as if they work with any backend, but _remote_binary()
skips for anything other than SSH:
if cfg.env.get("REMOTE_TYPE") != "ssh":
raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
Nothing is cached on that path, so every case hits the skip again. Could
the commit message say that XSK_REMOTE_BIN only works with SSH?
> diff --git a/tools/testing/selftests/drivers/net/hw/xsk.py b/tools/testing/selftests/drivers/net/hw/xsk.py
> new file mode 100755
> index 0000000000000..a33d4af869c7a
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/hw/xsk.py
> @@ -0,0 +1,417 @@
[ ... ]
> +def _control_addr(cfg):
> + """An SSH remote is reached over its management link. A netns remote is
> + reached only over the tested link, where the XDP programs pass the
> + control connection to the stack."""
> + if _netns_remote(cfg):
> + return cfg.remote_addr_v["4"]
> + return cfg.remote.name.rpartition("@")[2]
[Severity: Medium]
Can this give the DUT xskxceiver an address it cannot connect to?
cfg.remote.name is the ssh destination from REMOTE_ARGS.
remote_ssh.Remote.cmd() passes it straight to ssh, so ssh_config is what
resolves it. The DUT endpoint gets the same string as -p, but
peer_open_tcp() in xsk_peer.c resolves it with getaddrinfo() and returns
-EINVAL on failure:
err = getaddrinfo(host, port, &hints, &res);
if (err)
return -EINVAL;
This looks like it fails in several valid SSH setups:
- an ssh_config Host alias, which is common with the ControlMaster setup
the series' README recommends, does not resolve
- an IPv6 literal or IPv6-only management host resolves to AF_INET6, but
test_xsk() starts the remote listener with "-p", "0.0.0.0", so it only
listens on IPv4
- an ssh:// URI still leaves "host:port" after the rpartition()
In each of these cases the DUT endpoint exits with an error, and every case
is reported as a failure, not a skip. cfg.require_ipver("4") only checks
the tested link, not the management address.
Should the control address come from something that is known to be a
resolvable IPv4 address? Alternatively, could a resolution failure be
reported as a skip?
[ ... ]
> +def _steer_udp(ifname, port, queue, host=None, sudo=False):
[ ... ]
> + match = re.search(r"ID (\d+)", added.stdout)
> + if not match:
> + raise KsftFailEx(f"cannot identify new ntuple rule: {added.stdout}")
> + defer(_root_cmd, f"ethtool -N {dev} delete {match[1]}", host=host,
> + sudo=sudo)
[Severity: Low]
This isn't a bug, but the deferred delete is only registered after the ID
has been parsed. If ethtool ever added the rule without printing "Added
rule with ID N", the KsftFailEx here would leave the rule installed.
Current ethtool always prints the ID when no location is given. Other
drv-net tests (devmem_lib.py, iou-zcrx.py, nk_qlease.py) depend on the same
output, so this only matters for cleanup ordering.
> +def _reserve_queue(cfg):
> + """Move the last RX queue out of default RSS and return it."""
> + header = {"dev-index": cfg.ifindex}
> + channels = cfg.ethnl.channels_get({"header": header})
> + count = (channels.get("combined-count") or 0) + (channels.get("rx-count") or 0)
> + if count < 2:
> + raise KsftSkipEx("RX queue isolation needs at least two channels")
> + queue = count - 1
[Severity: Low]
Can this pick a queue that has no matching TX queue? If a driver has
RX-only channels next to combined ones, count - 1 is an RX-only queue.
For example, ice_set_channels() accepts 2 combined + 2 RX-only channels,
which gives num_rxq = 4 and num_txq = 2. This code picks queue 3, and
ice_xsk_pool_setup() rejects that queue even for an RX-only socket:
if (qid >= vsi->num_rxq || qid >= vsi->num_txq) {
...
The zero-copy bind then fails, so every RX case (and every netns TX case)
fails, even though combined queue 1 would have worked. Would it be safer
to pick the last combined queue when there are combined channels?
> +
> + try:
> + rss = cfg.ethnl.rss_get({"header": header})
> + saved = list(rss["indir"])
> + except Exception as exc:
> + raise KsftSkipEx(f"cannot read RSS indirection table: {exc}") from exc
> + if queue in saved:
> + updated = [0 if entry == queue else entry for entry in saved]
> + try:
> + cfg.ethnl.rss_set({"header": header, "indir": updated})
> + except Exception as exc:
> + raise KsftSkipEx(f"cannot reserve RX queue in RSS: {exc}") from exc
> + defer(cfg.ethnl.rss_set, {"header": header, "indir": saved})
[Severity: Medium]
Does this restore leave the indirection table marked as user configured?
Writing back an explicit copy of the table goes through ethnl_rss_set().
That function records a user size for any non-empty indir and clears it
only on a reset:
net/ethtool/rss.c:ethnl_rss_set() {
...
} else if (indir_reset) {
dev->ethtool->rss_indir_user_size = 0;
} else if (indir_mod) {
dev->ethtool->rss_indir_user_size = indir_user_size;
}
...
}
So if a device had the driver default table before the test,
netif_is_rxfh_configured() returns true after every RX case and every
netns TX case. From then on, ethtool_get_max_rxfh_channel() checks channel
reductions against the table, so later ethtool -L reductions are refused.
Drivers such as ice, bnxt, mlx5, gve and fbnic also stop regenerating the
default spread when the channel count changes.
Other drv-net tests (rss_ctx.py, rss_api.py, rss_drv.py, ntuple.py,
devmem_lib.py, gro_stats.py and others) restore with ethtool -X <if>
default. Should this reset the table the same way instead?
[ ... ]
> +def _setup_case(cfg, name, direction, port, busy_poll):
[ ... ]
> + if direction == "rx":
> + queue = _reserve_rx_queue(cfg, port)
> + else:
> + # The DUT TX socket fills no RX buffers, so its zero-copy queue drops
> + # what it receives. The control connection of a netns remote shares
> + # the link, so keep that queue out of RSS.
> + queue = _reserve_queue(cfg) if _netns_remote(cfg) else 0
> + _steer_udp(cfg.remote_ifname, port, 0, cfg.remote, sudo)
[Severity: Medium]
What happens here when the SSH remote is reached over the tested link?
With SSH, the DUT TX socket is bound to queue 0, and queue 0 stays in the
default RSS spread. The DUT endpoint runs with -e tx, and in that mode
test_xsk.c never fills the fill ring:
if (!ifobject->rx_on)
return 0;
On drivers where the zero-copy pool supplies the queue's RX buffers (ice,
i40e), queue 0 then drops everything it receives, as the comment above
says.
_control_addr() assumes the SSH host is on a separate management link.
The example in drivers/net/README.rst uses the REMOTE_V4 address as the
SSH host:
LOCAL_V4=192.168.1.1
REMOTE_V4=192.168.1.2
REMOTE_ARGS=root@192.168.1.2
In that setup, three kinds of traffic arrive on the DUT's tested NIC:
- the SSH session that carries the remote's "Listening" line and exit
status
- the per-case TCP control connection
- non-hashed traffic such as ARP
Whichever of these lands on queue 0 loses all RX for that case, so TX
cases can hit LISTEN_TIMEOUT or CASE_TIMEOUT. If a persistent ControlMaster
connection hashes to queue 0, every TX case would break.
Should TX cases also take the queue out of RSS for SSH remotes, or at
least when the SSH host is REMOTE_V4?
[ ... ]
> +def _run_local(args):
> + local = cmd(args, background=True, shell=False, fail=False)
> + try:
> + local.process(terminate=False, fail=False, timeout=CASE_TIMEOUT)
> + except subprocess.TimeoutExpired as exc:
> + local.proc.kill()
> + local.process(terminate=False, fail=False)
> + raise KsftFailEx(f"DUT case timed out after {CASE_TIMEOUT}s") from exc
> + return local
> +
> +
> +def _run_endpoints(cfg, local_args, remote_args, control_port, fallbacks):
> + local = None
> + remote = bkg(shlex.join(remote_args), host=cfg.remote, exit_wait=True,
> + fail=False)
> + try:
> + head, listening = _wait_remote_listen(remote, control_port)
> + if listening:
> + local = _run_local(local_args)
> + remote.process(terminate=False, fail=False, timeout=10)
[Severity: Low]
Is the endpoint output lost on these timeout paths?
If the remote has not exited within 10 seconds after the DUT finishes,
remote.process() raises subprocess.TimeoutExpired, and nothing catches it.
The finally block terminates the ssh client and the exception propagates.
As a result, neither the stderr merge below nor _case_result() runs. The
DUT's stdout and stderr, and the remote stderr already read into head, are
never printed, and ksft_run() reports a raw TimeoutExpired traceback.
The same happens when the DUT exceeds CASE_TIMEOUT: _run_local() collects
the DUT output after killing it, then raises KsftFailEx without printing
that output.
> + finally:
> + if remote.ret is None:
> + remote.process(terminate=True, fail=False)
[Severity: Medium]
With the SSH backend, does this actually stop the remote xskxceiver?
remote_ssh.Remote.cmd() runs "ssh -q host cmd" without -t, so terminating
here only signals the local ssh client. The remote command has no
controlling tty, gets no SIGHUP, and can keep running. This path runs on
the listen timeout, after CASE_TIMEOUT, and when the 10 second remote wait
times out.
The fallbacks are not cancelled on these paths. The deferred
_detach_xdp(..., "xdpgeneric", ...) and _restore_mtu() calls for the remote
therefore run while the old endpoint may still hold its XDP program and an
XSK socket on queue 0.
The next case then starts a new remote endpoint on the same interface and
queue 0. The link is not checked again because _remote_idle_mtu() is
cached, and the new bind can fail with EBUSY while the old socket holds
the queue.
When the orphaned endpoint does exit, its cleanup in xskxceiver.c calls
xsk_detach_xdp_program(), which does:
bpf_xdp_detach(ifindex, xdp_flags, NULL);
It then calls xsk_set_mtu(ifobj->ifindex, ifobj->orig_mtu). Both act on
whatever is on the link at that point, so they can remove the next case's
XDP program or reset its MTU in the middle of the test.
Can one failed or timed-out case cause the following cases to fail this
way? The netns backend is not affected.
[ ... ]
> @@ -0,0 +1,417 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +"""AF_XDP zero-copy hardware tests with an SKB-mode XSK peer."""
> +
> +import re
> +import shlex
> +import subprocess
> +import sys
> +import time
> +from pathlib import Path
> +
> +from lib.py import (EthtoolFamily, KsftFailEx, KsftNamedVariant, KsftSkipEx,
> + NetdevFamily, NetDrvEpEnv, bkg, cmd, defer, ethtool,
> + fd_read_timeout, ip, ksft_exit, ksft_pr, ksft_run,
> + ksft_variants, rand_port)
> +
> +CASE_TIMEOUT = 120
> +LISTEN_TIMEOUT = 30
> +NR_HUGEPAGES = 32
> +SKIP_CODE = 4
> +XSK_BIN = (Path(__file__).parent / "../../../net/lib/xskxceiver").resolve()
> +XSK_CASES = XSK_BIN.parent / "xsk/test_xsk_case_defs.h"
> +
> +
> +def _hardware_cases():
> + defs = XSK_CASES.read_text()
> + entries = re.findall(r"(?m)^XSK_TEST_CASE\((\w+),\s*\w+,\s*([^)]+)\)", defs)
> + if not entries or len(entries) != defs.count("XSK_TEST_CASE("):
> + raise KsftFailEx(f"cannot parse {XSK_CASES}")
> + variants = []
> + for test_id, (name, hw) in enumerate(entries):
> + flags = {flag.strip() for flag in hw.split("|")}
> + if not flags <= {"0", "XSK_HW_RX", "XSK_HW_TX"}:
> + raise KsftFailEx(f"invalid hardware directions for {name}: {hw}")
> + variants += [KsftNamedVariant(f"{d}_{name.lower()}", name, d, test_id)
> + for d in ("rx", "tx") if f"XSK_HW_{d.upper()}" in flags]
> + return variants
> +
> +
> +def _netns_remote(cfg):
> + return cfg.env.get("REMOTE_TYPE") == "netns"
> +
> +
> +def _control_addr(cfg):
> + """An SSH remote is reached over its management link. A netns remote is
> + reached only over the tested link, where the XDP programs pass the
> + control connection to the stack."""
> + if _netns_remote(cfg):
> + return cfg.remote_addr_v["4"]
> + return cfg.remote.name.rpartition("@")[2]
> +
> +
> +def _root_cmd(command, host=None, sudo=False, **kwargs):
> + if sudo:
> + command = "sudo -n " + command
> + return cmd(command, host=host, **kwargs)
> +
> +
> +def _remote_binary(cfg, local_binary):
> + binary = cfg.env.get("XSK_REMOTE_BIN")
> + if not binary:
> + return cfg.remote.deploy(local_binary)
> + if cfg.env.get("REMOTE_TYPE") != "ssh":
> + raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
> + if cfg.env.get("XSK_REMOTE_DEPLOY", "1") == "0":
> + result = cmd(f"test -x {shlex.quote(binary)}", host=cfg.remote,
> + fail=False)
> + if result.ret:
> + raise KsftSkipEx(f"remote xskxceiver is not executable: {binary}")
> + return binary
> + upload = binary + ".upload"
> + cmd(["scp", "-q", str(local_binary), f"{cfg.remote.name}:{upload}"],
> + shell=False)
> + cmd(f"mv -f {shlex.quote(upload)} {shlex.quote(binary)}",
> + host=cfg.remote)
> + return binary
> +
> +
> +def _remote_sudo(cfg):
> + return cfg.env.get("XSK_REMOTE_SUDO", "").lower() not in ("", "0", "false")
> +
> +
> +def _restore_mtu(ifname, mtu, host=None, sudo=False):
> + actual = ip(f"link show dev {shlex.quote(ifname)}", json=True,
> + host=host)[0]["mtu"]
> + if actual == mtu:
> + return
> + _root_cmd(f"ip link set dev {shlex.quote(ifname)} mtu {mtu}",
> + host=host, sudo=sudo)
> +
> +
> +def _reserve_hugepages(host=None, sudo=False):
> + have = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
> + if have >= NR_HUGEPAGES:
> + return
> + _root_cmd(f"sysctl -qw vm.nr_hugepages={NR_HUGEPAGES}", host=host,
> + sudo=sudo, fail=False)
> + got = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
> + if got != have:
> + defer(_root_cmd, f"sysctl -qw vm.nr_hugepages={have}", host=host,
> + sudo=sudo)
> + if got < NR_HUGEPAGES:
> + raise KsftSkipEx(f"only {got}/{NR_HUGEPAGES} hugepages available")
> +
> +
> +def _prefer_busy_poll(ifname):
> + """SO_PREFER_BUSY_POLL keeps the device IRQs masked between busy polls
> + only while napi_defer_hard_irqs and gro_flush_timeout are set. Use the
> + values that test_xsk.sh sets on veth."""
> + for knob, value in (("napi_defer_hard_irqs", "2"),
> + ("gro_flush_timeout", "200000")):
> + path = Path("/sys/class/net", ifname, knob)
> + orig = path.read_text(encoding="utf-8").strip()
> + if orig != value:
> + path.write_text(value, encoding="utf-8")
> + defer(path.write_text, orig, encoding="utf-8")
> +
> +
> +def _feature_state(ifname, feature, host=None):
> + """Read an ethtool -k feature. Parse the text output, as older ethtool
> + versions can't print -k as JSON."""
> + output = ethtool(f"-k {shlex.quote(ifname)}", host=host).stdout
> + match = re.search(rf"^{re.escape(feature)}:\s+(on|off)(.*)$", output,
> + re.MULTILINE)
> + if not match:
> + raise KsftSkipEx(f"ethtool does not report {feature} on {ifname}")
> + return {"active": match[1] == "on", "fixed": "[fixed]" in match[2]}
> +
> +
> +def _steer_udp(ifname, port, queue, host=None, sudo=False):
> + dev = shlex.quote(ifname)
> + rule = f"ethtool -N {dev} flow-type udp4 dst-port {port} action {queue}"
> + # Look at ntuple-filters only once the rule is refused, as every command
> + # on the remote costs a round trip.
> + added = _root_cmd(rule, host=host, sudo=sudo, fail=False)
> + if added.ret:
> + ntuple = _feature_state(ifname, "ntuple-filters", host)
> + if not ntuple["active"]:
> + if ntuple["fixed"]:
> + raise KsftSkipEx("NIC does not support ntuple filters")
> + _root_cmd(f"ethtool -K {dev} ntuple-filters on", host=host,
> + sudo=sudo)
> + defer(_root_cmd, f"ethtool -K {dev} ntuple-filters off",
> + host=host, sudo=sudo)
> + added = _root_cmd(rule, host=host, sudo=sudo, fail=False)
> + if added.ret:
> + ksft_pr(repr(added))
> + raise KsftSkipEx(f"cannot steer UDP flow to queue {queue} on {ifname}")
> + match = re.search(r"ID (\d+)", added.stdout)
> + if not match:
> + raise KsftFailEx(f"cannot identify new ntuple rule: {added.stdout}")
> + defer(_root_cmd, f"ethtool -N {dev} delete {match[1]}", host=host,
> + sudo=sudo)
> +
> +
> +def _reserve_queue(cfg):
> + """Move the last RX queue out of default RSS and return it."""
> + header = {"dev-index": cfg.ifindex}
> + channels = cfg.ethnl.channels_get({"header": header})
> + count = (channels.get("combined-count") or 0) + (channels.get("rx-count") or 0)
> + if count < 2:
> + raise KsftSkipEx("RX queue isolation needs at least two channels")
> + queue = count - 1
> +
> + try:
> + rss = cfg.ethnl.rss_get({"header": header})
> + saved = list(rss["indir"])
> + except Exception as exc:
> + raise KsftSkipEx(f"cannot read RSS indirection table: {exc}") from exc
> + if queue in saved:
> + updated = [0 if entry == queue else entry for entry in saved]
> + try:
> + cfg.ethnl.rss_set({"header": header, "indir": updated})
> + except Exception as exc:
> + raise KsftSkipEx(f"cannot reserve RX queue in RSS: {exc}") from exc
> + defer(cfg.ethnl.rss_set, {"header": header, "indir": saved})
> + actual = cfg.ethnl.rss_get({"header": header}).get("indir")
> + if actual != updated:
> + raise KsftSkipEx("NIC did not remove the reserved queue from RSS")
> + return queue
> +
> +
> +def _reserve_rx_queue(cfg, port):
> + """Reserve an RX queue outside RSS and steer the test UDP flow to it."""
> + queue = _reserve_queue(cfg)
> + _steer_udp(cfg.ifname, port, queue)
> + return queue
> +
> +
> +def _case_args(cfg, name):
> + if name != "TOO_MANY_FRAGS":
> + return []
> + try:
> + info = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
> + except Exception as exc:
> + raise KsftSkipEx(f"cannot query DUT fragment limit: {exc}") from exc
> + max_frags = info.get("xdp-zc-max-segs", 0)
> + if max_frags <= 1:
> + raise KsftSkipEx("DUT does not advertise multi-buffer zero-copy")
> + return ["--max-frags", str(max_frags)]
> +
> +
> +def _idle_mtu(ifname, host=None):
> + """Return the MTU of a link that has no XDP program attached."""
> + info = ip(f"link show dev {shlex.quote(ifname)}", json=True, host=host)[0]
> + if "xdp" in info:
> + raise KsftSkipEx(f"{ifname} already has an XDP program")
> + return info["mtu"]
> +
> +
> +def _remote_idle_mtu(cfg):
> + """Look at the remote link once. Every case leaves its MTU as it found
> + it and an endpoint replaces a program left behind by an earlier case,
> + so looking again would only add a round trip to each case."""
> + if not hasattr(cfg, "xsk_remote_mtu"):
> + cfg.xsk_remote_mtu = _idle_mtu(cfg.remote_ifname, cfg.remote)
> + return cfg.xsk_remote_mtu
> +
> +
> +def _detach_xdp(ifname, mode, host=None, sudo=False):
> + """A killed endpoint leaves its XDP program behind; name the mode, as a
> + bare "xdp off" only detaches driver mode on a native-XDP NIC."""
> + info = ip(f"link show dev {shlex.quote(ifname)}", json=True, host=host)[0]
> + if "xdp" not in info:
> + return
> + _root_cmd(f"ip link set dev {shlex.quote(ifname)} {mode} off", host=host,
> + sudo=sudo)
> +
> +
> +def _wait_remote_listen(remote, port):
> + """Read the remote endpoint's stderr up to the line it prints once it
> + listens, as polling its sockets would cost a round trip per try. Return
> + what was read and whether that line came."""
> + line = f"Listening for XSK peer on port {port}\n".encode()
> + fd = remote.proc.stderr.fileno()
> + head = b""
> + deadline = time.monotonic() + LISTEN_TIMEOUT
> + while line not in head:
> + try:
> + data = fd_read_timeout(fd, max(deadline - time.monotonic(), 0))
> + except TimeoutError:
> + break
> + if not data:
> + break
> + head += data
> + return head.decode(), line in head
> +
> +
> +def _require_zc(cfg):
> + if not hasattr(cfg, "xsk_zc"):
> + xdp_features = cfg.netnl.dev_get({"ifindex": cfg.ifindex})["xdp-features"]
> + cfg.xsk_zc = "xsk-zerocopy" in xdp_features
> + if not cfg.xsk_zc:
> + raise KsftSkipEx("NIC does not advertise AF_XDP zero-copy")
> +
> +
> +def _remote_test_binary(cfg):
> + if not hasattr(cfg, "xsk_remote_bin"):
> + cfg.xsk_remote_bin = _remote_binary(cfg, XSK_BIN)
> + return cfg.xsk_remote_bin
> +
> +
> +def _udp_port(cfg):
> + port = int(cfg.env.get("XSK_UDP_PORT", "42567"))
> + if not 1 <= port <= 65535:
> + raise KsftFailEx("XSK_UDP_PORT must be from 1 to 65535")
> + return port
> +
> +
> +def _setup_case(cfg, name, direction, port, busy_poll):
> + """Set up what this case alone needs; defer() undoes it when it ends.
> + Return the DUT queue and the fallbacks for what the endpoints undo."""
> + sudo = _remote_sudo(cfg)
> + local_mtu = _idle_mtu(cfg.ifname)
> + remote_mtu = _remote_idle_mtu(cfg)
> + if "UNALIGNED" in name:
> + _reserve_hugepages()
> + _reserve_hugepages(cfg.remote, sudo)
> + if busy_poll:
> + _prefer_busy_poll(cfg.ifname)
> +
> + if direction == "rx":
> + queue = _reserve_rx_queue(cfg, port)
> + else:
> + # The DUT TX socket fills no RX buffers, so its zero-copy queue drops
> + # what it receives. The control connection of a netns remote shares
> + # the link, so keep that queue out of RSS.
> + queue = _reserve_queue(cfg) if _netns_remote(cfg) else 0
> + _steer_udp(cfg.remote_ifname, port, 0, cfg.remote, sudo)
> +
> + # Undo what a failed or killed endpoint may leave behind. Defers run in
> + # reverse, so the XDP programs, which can cap the MTU, are detached first.
> + fallbacks = [
> + defer(_restore_mtu, cfg.ifname, local_mtu),
> + defer(_restore_mtu, cfg.remote_ifname, remote_mtu, cfg.remote, sudo),
> + defer(_detach_xdp, cfg.ifname, "xdpdrv"),
> + defer(_detach_xdp, cfg.remote_ifname, "xdpgeneric", cfg.remote, sudo),
> + ]
> + return queue, fallbacks
> +
> +
> +def _udp_args(cfg, direction, port):
> + if direction == "rx":
> + src, dst = cfg.remote_addr_v["4"], cfg.addr_v["4"]
> + else:
> + src, dst = cfg.addr_v["4"], cfg.remote_addr_v["4"]
> + return ["--udp-src", src, "--udp-dst", dst, "--udp-port", str(port)]
> +
> +
> +def _case_result(local, remote):
> + if local and local.stdout:
> + ksft_pr(local.stdout, line_pfx="dut|")
> + if local and local.stderr:
> + ksft_pr(local.stderr, line_pfx="dut|")
> + if remote.stdout:
> + ksft_pr(remote.stdout, line_pfx="remote|")
> + if remote.stderr:
> + ksft_pr(remote.stderr, line_pfx="remote|")
> + if local is None:
> + raise KsftFailEx("remote XSK endpoint did not listen on the control "
> + f"port, exit {remote.ret}")
> + if local.ret == SKIP_CODE or remote.ret == SKIP_CODE:
> + raise KsftSkipEx("AF_XDP case unsupported by one endpoint")
> + if local.ret or remote.ret:
> + raise KsftFailEx(f"DUT exit {local.ret}, remote exit {remote.ret}")
> +
> +
> +def _run_local(args):
> + local = cmd(args, background=True, shell=False, fail=False)
> + try:
> + local.process(terminate=False, fail=False, timeout=CASE_TIMEOUT)
> + except subprocess.TimeoutExpired as exc:
> + local.proc.kill()
> + local.process(terminate=False, fail=False)
> + raise KsftFailEx(f"DUT case timed out after {CASE_TIMEOUT}s") from exc
> + return local
> +
> +
> +def _run_endpoints(cfg, local_args, remote_args, control_port, fallbacks):
> + local = None
> + remote = bkg(shlex.join(remote_args), host=cfg.remote, exit_wait=True,
> + fail=False)
> + try:
> + head, listening = _wait_remote_listen(remote, control_port)
> + if listening:
> + local = _run_local(local_args)
> + remote.process(terminate=False, fail=False, timeout=10)
> + finally:
> + if remote.ret is None:
> + remote.process(terminate=True, fail=False)
> + # _wait_remote_listen() took the start of stderr off the pipe.
> + remote.stderr = head + remote.stderr
> + if local and {local.ret, remote.ret} <= {0, SKIP_CODE}:
> + # Both endpoints detached XDP and restored the MTU when they exited.
> + for fallback in fallbacks:
> + fallback.cancel()
> + _case_result(local, remote)
> +
> +
> +def test_xsk(cfg, name, direction, test_id, busy_poll=False):
> + cfg.require_ipver("4")
> + sync_addr = _control_addr(cfg)
> + udp_port = _udp_port(cfg)
> + _require_zc(cfg)
> + remote_bin = _remote_test_binary(cfg)
> + case_args = _case_args(cfg, name)
> + queue, fallbacks = _setup_case(cfg, name, direction, udp_port, busy_poll)
> +
> + udp_args = _udp_args(cfg, direction, udp_port)
> + control_port = rand_port()
> + remote_args = [str(remote_bin), "-i", cfg.remote_ifname,
> + "--hw", "--listen", "-m", "skb",
> + "-t", str(test_id),
> + "-e", "tx" if direction == "rx" else "rx",
> + "-p", "0.0.0.0", "-P", str(control_port), *udp_args,
> + "--peer-mac", cfg.dev["address"], "--queue", "0", *case_args]
> + if _remote_sudo(cfg):
> + remote_args = ["sudo", "-n"] + remote_args
> + local_args = [str(XSK_BIN), "-i", cfg.ifname, "--hw", "-m", "zc",
> + "-t", str(test_id), "-e", direction,
> + "-p", sync_addr, "-P", str(control_port), *udp_args,
> + "--peer-mac", cfg.remote_dev["address"],
> + "--queue", str(queue), *case_args]
> + if busy_poll:
> + local_args.append("-b")
> + _run_endpoints(cfg, local_args, remote_args, control_port, fallbacks)
> +
> +
> +def test_xsk_busy_poll(cfg, name, direction, test_id):
> + """Run a case with the DUT endpoint busy polling."""
> + test_xsk(cfg, name, direction, test_id, busy_poll=True)
> +
> +
> +def main():
> + # ksft_run() rejects options it does not know, so take -b out first.
> + busy_poll = "-b" in sys.argv[1:]
> + if busy_poll:
> + sys.argv.remove("-b")
> + test = test_xsk_busy_poll if busy_poll else test_xsk
> + variants = ksft_variants(_hardware_cases())(test)
> +
> + if any(arg in ("-h", "-l") for arg in sys.argv[1:]):
> + ksft_run(cases=[variants])
> + return
> + if not XSK_BIN.exists():
> + print(f"1..0 # SKIP {XSK_BIN} was not built")
> + sys.exit(SKIP_CODE)
> + with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
> + cfg.ethnl = EthtoolFamily()
> + cfg.netnl = NetdevFamily()
> + ksft_run(cases=[variants], args=(cfg,))
> + ksft_exit()
> +
> +
> +if __name__ == "__main__":
> + main()
[Severity: Low]
main() calls _hardware_cases() to build the variants before it gets to
the XSK_BIN.exists() check. That check is what turns a missing
xskxceiver into a skip.
If test_xsk_case_defs.h is not at XSK_CASES, read_text() raises
FileNotFoundError. This happens outside ksft_run(), so the script dies
with a Python traceback instead of printing a KTAP skip line. The -h and
-l paths go through _hardware_cases() too, so even listing the cases
crashes.
How is the header expected to get there? drivers/net/hw/Makefile does
not list ../../../net/lib/xsk/test_xsk_case_defs.h in TEST_INCLUDES. The
commit message relies on net/lib being an install dependency of drv-net.
An out-of-tree run such as:
make O=build TARGETS=drivers/net/hw run_tests
only rsyncs TEST_PROGS, TEST_FILES and TEST_INCLUDES into the output
directory. It does not install INSTALL_DEP_TARGETS. As far as I can
tell, the header then never lands under build/net/lib/xsk/, and xsk.py
crashes before it can skip.
Should the header be added to TEST_INCLUDES? Separately, could main()
check XSK_CASES (and XSK_BIN) before _hardware_cases() runs? That way a
missing net/lib build would be reported as a skip rather than a
traceback.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs
2026-10-08 11:49 ` [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
2026-10-08 21:41 ` Jakub Kicinski
@ 2026-10-12 8:50 ` netdev-bot+sashiko
1 sibling, 0 replies; 38+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 8:50 UTC (permalink / raw)
To: maciej.fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing, bjorn
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The new README describes XSK_REMOTE_DEPLOY and XSK_REMOTE_BIN as
if each works on its own, but xsk.py does not handle them that way.
- [Low] The 'Generic veth test' section of the new README.rst ends with
"The generic frame format is unchanged." That is a changelog note, not…
- [Low] The README says "The XDP programs redirect only the test flow and
pass all other traffic to the stack, so the tested link does not have…
- [Low] The README says "The remote listens on all addresses for
control." In test_xsk()…
--
selftests: xsk: document generic and hardware endpoint runs
This adds a README.rst under net/lib/xsk. It describes the per-case veth
launcher in test_xsk.sh and the two-host hardware setup driven by
drivers/net/hw/xsk.py, where the DUT uses AF_XDP zero-copy and the remote
xskxceiver uses SKB mode. It also points to the new document from
drivers/net/README.rst and xskxceiver.c.
> diff --git a/tools/testing/selftests/net/lib/xsk/README.rst b/tools/testing/selftests/net/lib/xsk/README.rst
> new file mode 100644
> index 0000000000000..d0a0a902ed783
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/xsk/README.rst
> @@ -0,0 +1,138 @@
[ ... ]
> +The shell wrapper creates the veth pair. ``-m skb|drv`` selects a mode,
> +``-t`` selects a test by the name or number shown by ``xskxceiver -l``,
> +and ``-p N`` selects a fixed control port instead of the default random
> +port. The generic frame format is unchanged.
[Severity: Low]
This isn't a bug, but "The generic frame format is unchanged" reads like a
changelog note. Once the series is merged, someone reading this file on
its own has nothing to compare it against.
Could this sentence either describe the frame format, or move to the
commit message or cover letter?
[ ... ]
> +Both XSK endpoints generate and validate raw IPv4/UDP frames with one fixed
> +UDP port, so ntuple rules can steer the test flow. They do not use AF_INET
> +sockets. On DUT RX cases ``xsk.py`` reserves the last RX queue outside RSS
> +and steers test traffic to it. On DUT TX cases it steers traffic to queue 0
> +on the remote receiver. The XDP programs redirect only the test flow and
> +pass all other traffic to the stack, so the tested link does not have to
> +be otherwise idle.
[Severity: Low]
Does "does not have to be otherwise idle" hold for DUT TX cases with
REMOTE_TYPE=ssh?
In _setup_case() in drivers/net/hw/xsk.py, the TX branch reserves a queue
only for a netns remote:
queue = _reserve_queue(cfg) if _netns_remote(cfg) else 0
With an SSH remote, the DUT zero-copy socket binds to queue 0, and queue 0
stays in the RSS indirection table. A later paragraph of this README notes
that the DUT TX socket fills no RX buffers, so its zero-copy queue drops
what it receives.
If unrelated traffic on the tested link hashes to DUT queue 0, wouldn't it
be dropped before XDP_PASS can hand it to the stack? That includes non-IP
frames such as ARP or LLDP, which many NICs put on queue 0.
The test still passes, because _control_addr() sends the SSH control
channel over the management link. Even so, this guarantee only seems to
hold for netns remotes.
[ ... ]
> +Configure the usual driver test variables in
> +``tools/testing/selftests/drivers/net/hw/net.config`` or the environment::
> +
> + NETIF=eth0
> + LOCAL_V4=192.0.2.1
> + REMOTE_V4=192.0.2.2
> + REMOTE_TYPE=ssh
> + REMOTE_ARGS=user@remote.example.com
> + XSK_REMOTE_BIN=/path/to/remote/xskxceiver
> + XSK_REMOTE_SUDO=1
[ ... ]
> +The remote host needs AF_XDP in SKB mode and BPF. ``xsk.py`` copies the
> +DUT's ``xskxceiver`` binary to the remote, so both hosts must have
> +compatible architectures and libraries. ``XSK_REMOTE_BIN`` chooses a fixed
> +destination path for that copy. Set ``XSK_REMOTE_DEPLOY=0`` to use a binary
> +built on the remote from the same test case definitions instead.
[Severity: Low]
Does this match how xsk.py handles these two variables?
In _remote_binary() in drivers/net/hw/xsk.py, XSK_REMOTE_DEPLOY is only
checked once XSK_REMOTE_BIN is set:
binary = cfg.env.get("XSK_REMOTE_BIN")
if not binary:
return cfg.remote.deploy(local_binary)
if cfg.env.get("REMOTE_TYPE") != "ssh":
raise KsftSkipEx("XSK_REMOTE_BIN requires the SSH remote backend")
if cfg.env.get("XSK_REMOTE_DEPLOY", "1") == "0":
If only XSK_REMOTE_DEPLOY=0 is set, as this paragraph suggests, wouldn't
the DUT binary still be copied to the remote without any warning?
Also, XSK_REMOTE_BIN causes a skip for any remote that isn't SSH, and the
example config above sets it. The netns paragraph doesn't say to unset it.
If someone copies the example and switches REMOTE_TYPE to netns, wouldn't
every case be skipped with "XSK_REMOTE_BIN requires the SSH remote
backend"?
Could the README say that XSK_REMOTE_DEPLOY=0 needs XSK_REMOTE_BIN, and
that XSK_REMOTE_BIN only works with REMOTE_TYPE=ssh?
[ ... ]
> +With SSH, the DUT uses the SSH host name to connect to the remote control
> +listener.
> +``XSK_UDP_PORT`` changes the fixed test UDP port (default 42567). The remote
> +listens on all addresses for control.
[Severity: Low]
Is "listens on all addresses" accurate? test_xsk() in drivers/net/hw/xsk.py
starts the remote listener with:
"-p", "0.0.0.0", "-P", str(control_port), *udp_args,
peer_open_tcp() in net/lib/xsk/xsk_peer.c resolves this through
getaddrinfo() with AF_UNSPEC. For "0.0.0.0" that returns only an AF_INET
wildcard, so the listener binds to IPv4 only.
With an SSH remote, _control_addr() makes the DUT connect to the host part
of REMOTE_ARGS. If REMOTE_ARGS names an IPv6 literal or a host with only
AAAA records, can the DUT reach the listener?
The remote still prints its listening line, so the case would time out or
fail rather than skip.
Should the README say the listener is IPv4 only, or should the listener
also bind an IPv6 wildcard?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008114909.734364-1-maciej.fijalkowski%40intel.com
^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2026-10-12 8:50 UTC | newest]
Thread overview: 38+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-08 11:48 [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 01/14] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 02/14] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
2026-10-09 9:46 ` Björn Töpel
2026-10-09 12:43 ` Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 03/14] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
2026-10-08 11:48 ` [PATCH v2 net-next 04/14] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
2026-10-08 11:49 ` [PATCH v2 net-next 05/14] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
2026-10-09 11:18 ` Björn Töpel
2026-10-09 12:46 ` Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 06/14] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
2026-10-08 11:49 ` [PATCH v2 net-next 07/14] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 08/14] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-09 11:25 ` Björn Töpel
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 09/14] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 10/14] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 11/14] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 12/14] selftests: xsk: share test case definitions with hardware runner Maciej Fijalkowski
2026-10-09 12:12 ` Björn Töpel
2026-10-09 12:52 ` Maciej Fijalkowski
2026-10-08 11:49 ` [PATCH v2 net-next 13/14] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-08 21:37 ` Jakub Kicinski
2026-10-09 13:13 ` Maciej Fijalkowski
2026-10-09 11:50 ` sashiko-bot
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 11:49 ` [PATCH v2 net-next 14/14] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
2026-10-08 21:41 ` Jakub Kicinski
2026-10-12 8:50 ` netdev-bot+sashiko
2026-10-08 21:30 ` [PATCH v2 net-next 00/14] selftests: net: migrate AF_XDP test suite over to net Jakub Kicinski
2026-10-09 12:02 ` Björn Töpel
2026-10-09 13:15 ` Maciej Fijalkowski
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.