* [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net
@ 2026-10-14 18:45 Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
` (14 more replies)
0 siblings, 15 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
v2: https://lore.kernel.org/netdev/20261008114909.734364-1-maciej.fijalkowski@intel.com/
v2 -> v3:
- New patch 2: size the TX UMEM of HW_SW_MAX_RING_SIZE like the RX
one; the case only worked with the single-interface shared UMEM
- Patch 6: test_xsk.sh skips without xskxceiver, -lpthread for glibc
older than 2.34, tools/include ahead of CFLAGS for O= (Sashiko)
- Patch 8: hw_ring_size_reset() skips no-op resets and keeps the NIC's
sizes on failure; TAP header right after parsing (Sashiko)
- Patch 9: test_xsk.py replaces test_xsk.sh (Bjorn), with a KTAP case
per mode and case, a veth pair per case (Sashiko) and
test_xsk_busy_poll; helpers shared with xsk.py go to net/lib/py/xsk.py;
- XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF and XDP_ADJUST_TAIL_GROW are left
out on veth (Jakub, Bjorn)
- Patch 10: wait for the listener's stderr line instead of polling with
ss; pass only when both endpoints pass; -P takes only a port; ABORT
and PROGRESS fixes (Sashiko)
- Patch 11: default MTU of 1500, link wait after MTU or XDP changes,
one-mode cases skip on both endpoints, smaller fixes (Sashiko); RX
fills its fill ring before the bind and each endpoint sizes its own
rings, as zero-copy drivers and HW_SW_MAX_RING_SIZE need
- Patch 12: pass IPv4 fragments to the stack, run both
XDP_ADJUST_TAIL_GROW cases on hardware, read adjust_value via the
skeleton (Sashiko)
- New patch 13 replaces v2 patch 12 (test_xsk_case_defs.h): xskxceiver
-L lists the hardware cases by DUT role for xsk.py (Jakub); hardware
runs gain STAT_RX_FULL, STAT_FILL_EMPTY, XDP_DROP_HALF,
XDP_METADATA_COPY_MULTI_BUFF, the XDP_ADJUST_TAIL cases, TEARDOWN and
HW_SW_{MIN,MAX}_RING_SIZE -- that has been actually a huge oversight
on my side where I did not cover the hw_flags on these cases at v2;
- Patch 14: drv-net conventions: cfg.net_lib_dir, cfg.remote.deploy(),
RSS and ntuple as in iou-zcrx.py, no REMOTE_TYPE checks, sudo or
XSK_* variables (Jakub); control channel to REMOTE_V4 for both remote
types, DUT queue out of RSS in both directions, timeout(1) for the
remote endpoint (Sashiko); one-time queue, ntuple and huge page
setup; test_xsk_busy_poll replaces -b and it is covered within single
ksft_run()
- Patch 15: document both runners with a netns remote example; drop
the XSK_* note from drivers/net/README.rst
- rebase!
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
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 moves with the engine from selftests/bpf to
selftests/net as test_xsk.py. It replaces test_xsk.sh and runs all of
its cases but XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF, which leaks page_pool
pages on veth, and XDP_ADJUST_TAIL_GROW, as veth leaves no room to grow
most frames.
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 rx_SEND_RECEIVE will test the
ice_clean_rx_irq_zc() routine and tx_SEND_RECEIVE 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. The veth test 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.
The test table in test_xsk.h is grouped by the endpoint role that each
case tests, which a hardware run gives to the DUT: RX, TX, both, or
none for the generic cases, which run on veth only. xskxceiver -L lists
each hardware case once per role, and xsk.py makes the variants
ksft_variants() named rx_<case> and tx_<case> from that list, so -t and
-T work as for any other test. Both runners reject -l and -h, as
ksft_run() exits on them before main() can undo the setup of the run.
Patches 1-4 prepare the split. Patch 1 moves the endpoint work out of
the pthread entry points. Patch 2 sizes the TX UMEM of
HW_SW_MAX_RING_SIZE like the RX one, as the case only worked with the
UMEM that the single-interface mode shares. Patches 3 and 4 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 4 also merges the cases that the wrapper left out into the main
list.
Patch 5 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 6 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 7-10 split the engine. The interface capabilities move into one
struct, so that a process can mirror them for the endpoint it does not
own (7). main() is split into setup, run and cleanup (8). xskxceiver
runs one case per invocation, and test_xsk.py, which replaces
test_xsk.sh, owns the mode x case matrix (9). Finally, the RX and TX
endpoints become separate processes that meet over a small TCP control
channel (10).
Patch 11 adds the xskxceiver options that a two-host run needs, and
patch 12 lets the XDP programs pass traffic other than the test flow to
the stack in hardware mode. Patch 13 groups the test table by endpoint
role and lists the hardware cases with xskxceiver -L, and patch 14 adds
xsk.py on top of them. Patch 15 documents both setups.
Tested on both netns and ssh REMOTE_TYPEs on my local setup.
Known issues, follow-ups?:
- 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 the rx_ and tx_ variants, 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 and TX_QUEUE_CONSUMER, which do not run in zero-copy
mode.
- we could move the routines that reserve queues and do RSS updates to
lib as there are several tests doing almost the same thing; this way
xsk.py would become way smaller; reserving hugepages is another
candidate...as well as ntuple commands;
[1]: https://lore.kernel.org/netdev/20260925161547.1b57b653@kernel.org/
Thanks,
Maciej
Maciej Fijalkowski (15):
selftests: xsk: factor endpoint work out of pthread wrappers
selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE
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: list the hardware test cases
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 | 5 +-
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/test_xsk.sh | 246 ------
tools/testing/selftests/bpf/xsk_prereqs.sh | 93 ---
tools/testing/selftests/bpf/xskxceiver.c | 479 -----------
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/config | 1 +
.../drivers/net/hw/lib/py/__init__.py | 5 +
tools/testing/selftests/drivers/net/hw/xsk.py | 263 ++++++
tools/testing/selftests/net/Makefile | 1 +
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 | 34 +
.../testing/selftests/net/lib/py/__init__.py | 4 +
tools/testing/selftests/net/lib/py/xsk.py | 81 ++
.../testing/selftests/net/lib/xsk/README.rst | 166 ++++
.../prog_tests => net/lib/xsk}/test_xsk.c | 765 +++++++++++-------
.../prog_tests => net/lib/xsk}/test_xsk.h | 142 +++-
.../selftests/{bpf => net/lib/xsk}/xsk.c | 109 ++-
.../selftests/{bpf => net/lib/xsk}/xsk.h | 7 +
.../testing/selftests/net/lib/xsk/xsk_peer.c | 293 +++++++
.../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} | 47 ++
.../selftests/net/lib/xsk/xskxceiver.c | 682 ++++++++++++++++
.../{bpf => net/lib/xsk}/xskxceiver.h | 0
tools/testing/selftests/net/test_xsk.py | 154 ++++
33 files changed, 2474 insertions(+), 1388 deletions(-)
delete mode 100644 tools/testing/selftests/bpf/prog_tests/xsk.c
delete mode 100755 tools/testing/selftests/bpf/test_xsk.sh
delete mode 100755 tools/testing/selftests/bpf/xsk_prereqs.sh
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/py/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 (81%)
rename tools/testing/selftests/{bpf/prog_tests => net/lib/xsk}/test_xsk.h (80%)
rename tools/testing/selftests/{bpf => net/lib/xsk}/xsk.c (88%)
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} (70%)
create mode 100644 tools/testing/selftests/net/lib/xsk/xskxceiver.c
rename tools/testing/selftests/{bpf => net/lib/xsk}/xskxceiver.h (100%)
create mode 100755 tools/testing/selftests/net/test_xsk.py
--
2.43.0
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:14 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
` (13 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 that the same
code can run in a process per endpoint later in this series. Barrier,
pacing and test behavior are unchanged.
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] 35+ messages in thread
* [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:15 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
` (12 subsequent siblings)
14 siblings, 2 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
HW_SW_MAX_RING_SIZE gives the RX UMEM 8k frames and 8k rings, then
sends batches of 2k and of the maximum HW TX ring size minus 8. This
only works in the single-interface mode, where the TX socket uses the
RX UMEM, and veth, which cannot change its ring sizes, skips the case.
With two interfaces, TX has its own UMEM of DEFAULT_UMEM_BUFFERS (1k)
frames and 2k rings: the in-flight limit in __send_pkts() turns
negative for a 2k batch, so TX never sends and RX times out, and the
second batch would not fit in the TX ring anyway.
The next patch removes the single-interface mode, so size the TX UMEM
like the RX one. The shared UMEM setup copies the RX UMEM over the TX
one and is not affected.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
tools/testing/selftests/bpf/prog_tests/test_xsk.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 2a1362874092..16fe32e5afc0 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -2513,6 +2513,9 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test)
test->ifobj_rx->xsk->umem->num_frames = max_descs;
test->ifobj_rx->xsk->umem->fill_size = max_descs;
test->ifobj_rx->xsk->umem->comp_size = max_descs;
+ test->ifobj_tx->xsk->umem->num_frames = max_descs;
+ test->ifobj_tx->xsk->umem->fill_size = max_descs;
+ test->ifobj_tx->xsk->umem->comp_size = max_descs;
test->ifobj_tx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS;
test->ifobj_rx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:17 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
` (11 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
xskxceiver -i ethX -i ethX, used for HW tests, bound both sockets to
queue 0 of one interface, which the kernel only allows with a shared
UMEM. With the endpoints about to become separate processes, they would
have to share the UMEM mapping and the socket fd.
The mode also relied on the interface being set up by hand with a
single queue and promiscuous mode, which does not suit CI. The hardware
test added later in this series steers its traffic with ntuple and RSS
configuration instead.
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 16fe32e5afc0..78600ed79522 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)
@@ -2656,8 +2597,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;
@@ -2672,7 +2613,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] 35+ messages in thread
* [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (2 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:18 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
` (10 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 cases inside test_progs, driving
the TX and RX sockets of a veth pair from two threads. The following
patches turn each endpoint into a process of its own that can run
against a peer on another host. Keeping the wrapper would mean rewiring
it at every step of that split and teaching test_progs to spawn and
synchronize peer processes, so drop it. BPF CI loses the ns_xsk_skb and
ns_xsk_drv entries; the same veth coverage stays with test_xsk.sh,
which moves to selftests/net together with the engine.
ci_skip_tests[] held the cases that the wrapper left out: flaky and
slow ones and those that need hugepages or HW ring size support.
Nothing runs only one of the lists anymore, 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] 35+ messages in thread
* [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (3 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:20 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
` (9 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 with $(BPFTOOL),
which defaults to the bpftool in PATH. A failing bpftool leaves no
partial header behind for a later build to take 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] 35+ messages in thread
* [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (4 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:22 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
` (8 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 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 take shared helpers from.
net/lib builds xskxceiver 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; test_xsk.sh then skips too. xskxceiver links -lpthread,
which glibc older than 2.34 needs for the threads of test_xsk.c.
Like the rest of net/lib, xskxceiver builds against the installed uapi
headers, so include linux/kernel.h for min_t() directly rather than via
the tools copy of linux/netlink.h. tools/include goes ahead of CFLAGS,
so that O= builds take linux/kernel.h and linux/types.h from it rather
than from a source tree usr/include.
Move the two ethtool ring helpers from network_helpers into xsk.c 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 xdp_metadata test keep using xsk.c and xsk.h
from their new location. Update the AF_XDP entry in MAINTAINERS.
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 | 35 +++++++++++++
.../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 | 5 ++
.../selftests/{bpf => net}/xsk_prereqs.sh | 2 +-
20 files changed, 119 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 (98%)
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 509b35b5a0e7..1fc9eaedd744 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29783,7 +29783,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..c26088da0489 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,28 @@ 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_LDLIBS += -lpthread
+
+$(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) -I$(top_srcdir)/tools/include $(CFLAGS) -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 78600ed79522..211b8c150965 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 98%
rename from tools/testing/selftests/bpf/test_xsk.sh
rename to tools/testing/selftests/net/test_xsk.sh
index 69fac65f073c..fbf4551d596a 100755
--- a/tools/testing/selftests/bpf/test_xsk.sh
+++ b/tools/testing/selftests/net/test_xsk.sh
@@ -97,6 +97,11 @@ do
esac
done
+if [ ! -x "./${XSKOBJ}" ]; then
+ echo "xskxceiver was not built; skipping"
+ exit $ksft_skip
+fi
+
TEST_NAME="PREREQUISITES"
URANDOM=/dev/urandom
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] 35+ messages in thread
* [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (5 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:27 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
` (7 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 case as separate
processes, possibly on different hosts, each binding only its own
interface. The shared test code still looks at the capabilities of both
ifobjects, e.g. to check both for multi-buffer support or to size the
RX batch from the TX ring limit. As the endpoints do not exchange
capabilities, a process will represent the other side by a shadow
ifobject that mirrors the local ones.
Prepare for that by replacing the per-interface support booleans and
limits in struct ifobject with one struct xsk_caps, so that mirroring
takes a single struct assignment and a capability added later cannot
be left out of the copy. The maximum TX ring size, which comes from the
ethtool ring parameters that a shadow does not mirror, is copied there
too. ifobj_has_cap() and ifobj_set_cap() test and set the flags, and
xsk_get_cap() reads a limit from the TX ifobject, the only one that has
all of them set, as the open-coded reads did.
The unaligned UMEM check 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 together.
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 211b8c150965..8ee4b4f7edd9 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;
@@ -2467,8 +2477,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);
@@ -2561,7 +2571,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] 35+ messages in thread
* [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (6 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:29 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
` (6 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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() sets up one interface from its -i argument, so that
interfaces can get their roles after parsing; -l needs none
- detect_mode_caps() and detect_ifobj_caps() record DRV, zero-copy and
HW ring support as XSK_CAP flags, which mode_supported() checks
- run_pkt_test() returns the result, which main() counts instead of
test->fail, as not every failing path sets it
- cleanup_iface() restores rings, detaches and unloads one interface
and copes with partially initialized objects, as does
pkt_stream_delete(NULL), so main() has a single exit path
As the rings of both interfaces now get restored at exit,
hw_ring_size_reset() skips rings that are already at their defaults,
as the SIOCETHTOOL path passes such no-op requests on to the driver.
When the request fails, it keeps the sizes the NIC still has, so that
a later reset tries again.
Call ksft_print_header(), which also switches stdout to line buffering,
right after parsing the command line: xskxceiver never called it, so a
launcher reading through a pipe only saw the output at exit. -l prints
no TAP header and exits with 0 instead of KSFT_XPASS, as listing the
tests is not a test result.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 24 ++-
.../testing/selftests/net/lib/xsk/test_xsk.h | 2 +
.../selftests/net/lib/xsk/xskxceiver.c | 188 +++++++++---------
3 files changed, 122 insertions(+), 92 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index 8ee4b4f7edd9..e4e0d4dfab5a 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -191,9 +191,21 @@ static int set_ring_size(struct ifobject *ifobj)
int hw_ring_size_reset(struct ifobject *ifobj)
{
+ struct ethtool_ringparam ring = ifobj->ring;
+ int err;
+
+ if (ring.tx_pending == ifobj->set_ring.default_tx &&
+ 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);
+ err = set_ring_size(ifobj);
+ /* Keep the sizes the NIC still has, so a later reset tries again. */
+ if (err)
+ ifobj->ring = ring;
+
+ return err;
}
static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
@@ -369,6 +381,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 +2347,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..02e54b026806 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);
@@ -350,6 +385,10 @@ int main(int argc, char **argv)
setlocale(LC_ALL, "");
+ parse_command_line(ifobj_tx, ifobj_rx, argc, argv);
+ /* Line-buffer stdout so verdicts reach a capturing launcher live. */
+ ksft_print_header();
+
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");
@@ -371,95 +410,62 @@ int main(int argc, char **argv)
ifobj_tx->caps.umem_tailroom = umem_tailroom;
ifobj_rx->caps.umem_tailroom = umem_tailroom;
- 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);
- }
+ 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] 35+ messages in thread
* [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (7 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 8:34 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
` (5 subsequent siblings)
14 siblings, 2 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Replace test_xsk.sh and xsk_prereqs.sh with test_xsk.py, which runs
xskxceiver once per mode and case, so that every case starts from a
fresh process and a crash fails only its own case. A single case is
also the unit that the next patch splits into RX and TX processes.
xskxceiver now requires -m and -t, runs exactly that case, reports one
KTAP result and exits with its verdict. An unsupported mode skips the
case instead of exiting with XFAIL.
test_xsk.py uses the networking selftests Python library, as does the
hardware test added later, so every case is a KTAP case of its own:
test_xsk runs each case that xskxceiver -l lists in skb and drv mode,
e.g. test_xsk.drv_SEND_RECEIVE, and test_xsk_busy_poll runs them again
with busy polling. The usual -t and -T options select cases in place of
the script's -l, -m and -t, and -v and -d go away, as the output of a
run that does not pass is logged. Each case runs on a veth pair of its
own, so an MTU or an XDP program that a crashed case leaves behind
cannot reach the next one. The pair stays in the current network
namespace, as only the initial one has the net.core.max_skb_frags
sysctl that xskxceiver reads. A case is killed and fails after two
minutes, and the test skips when xskxceiver was not built.
test_xsk.py reserves 32 huge pages for the UNALIGNED cases, restores
the count when it ends and leaves those cases out when it cannot
reserve them. ksft_run() exits on -l and -h before the count is
restored, so test_xsk.py rejects both. The huge page, busy-poll and
endpoint wait helpers, which the hardware test shares, go to
net/lib/py/xsk.py, which joins the AF_XDP entry in MAINTAINERS.
test_xsk.py leaves out two cases that test_progs skipped as flaky:
- XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF: bpf_xdp_adjust_tail() frees the
trimmed fragment as the memory type of the XDP RX queue, which veth
and generic XDP leave at MEM_TYPE_PAGE_SHARED for frames that they
copy into page_pool pages, so the page leaks from its pool and the
pool's shutdown stalls
- XDP_ADJUST_TAIL_GROW: it skips in skb mode, and in drv mode veth
copies each frame into a page_pool fragment that leaves no room to
grow the 128-byte frames of the case unless it is the last one of its
page, so the XDP program drops most frames
Drop the xskxceiver.c note on enabling packet dumps with -D in
test_xsk.sh; xskxceiver has no such option and dumps packets on
validation errors.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/net/Makefile | 3 +-
tools/testing/selftests/net/lib/Makefile | 2 +-
.../testing/selftests/net/lib/py/__init__.py | 4 +
tools/testing/selftests/net/lib/py/xsk.py | 58 +++++
.../selftests/net/lib/xsk/xskxceiver.c | 63 ++---
tools/testing/selftests/net/test_xsk.py | 140 +++++++++++
tools/testing/selftests/net/test_xsk.sh | 230 ------------------
tools/testing/selftests/net/xsk_prereqs.sh | 86 -------
9 files changed, 227 insertions(+), 360 deletions(-)
create mode 100644 tools/testing/selftests/net/lib/py/xsk.py
create mode 100755 tools/testing/selftests/net/test_xsk.py
delete mode 100755 tools/testing/selftests/net/test_xsk.sh
delete mode 100755 tools/testing/selftests/net/xsk_prereqs.sh
diff --git a/MAINTAINERS b/MAINTAINERS
index 1fc9eaedd744..6985a42bc09c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29784,6 +29784,7 @@ F: include/uapi/linux/if_xdp.h
F: include/uapi/linux/xdp_diag.h
F: net/xdp/
F: tools/testing/selftests/net/*xsk*
+F: tools/testing/selftests/net/lib/py/xsk.py
F: tools/testing/selftests/net/lib/xsk/
XEN BLOCK SUBSYSTEM
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index aa31b6216dd1..8c8219703bd7 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -118,7 +118,7 @@ TEST_PROGS := \
test_vxlan_under_vrf.sh \
test_vxlan_vnifilter_notify.sh \
test_vxlan_vnifiltering.sh \
- test_xsk.sh \
+ test_xsk.py \
tfo_passive.sh \
traceroute.sh \
txtimestamp.sh \
@@ -210,7 +210,6 @@ 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/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index c26088da0489..5c1612315062 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 used by net/test_xsk.py.
XSK_DIR := xsk
XSK_BPF_OBJ := $(OUTPUT)/xsk_xdp_progs.bpf.o
XSK_SKEL := $(OUTPUT)/xsk_xdp_progs.skel.h
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 71df5880b356..1a93ab8b1e60 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -16,6 +16,8 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
ctl_file_write, wait_file, tool, tc
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
+from .xsk import xsk_reserve_hugepages, xsk_release_hugepages, \
+ xsk_setup_busy_poll, xsk_wait_endpoint_exit
from .ynl import NlError, NlctrlFamily, YnlFamily, \
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily
from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
@@ -31,6 +33,8 @@ __all__ = ["KSRC",
"bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
"wait_port_listen", "ctl_file_write", "wait_file", "tool", "tc",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
+ "xsk_reserve_hugepages", "xsk_release_hugepages",
+ "xsk_setup_busy_poll", "xsk_wait_endpoint_exit",
"NetdevSim", "NetdevSimDev",
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
"YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
diff --git a/tools/testing/selftests/net/lib/py/xsk.py b/tools/testing/selftests/net/lib/py/xsk.py
new file mode 100644
index 000000000000..50985bcb5b80
--- /dev/null
+++ b/tools/testing/selftests/net/lib/py/xsk.py
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Helpers to run xskxceiver, the AF_XDP test program in net/lib/xsk.
+"""
+
+import subprocess
+
+from .ksft import ksft_pr
+from .utils import cmd, ctl_file_write
+
+NR_HUGEPAGES = 32
+
+
+def xsk_reserve_hugepages(saved, host=None):
+ """Raise vm.nr_hugepages on host to what the UNALIGNED cases need, and
+ return whether it has that many. Add the count to restore to saved, a
+ list for xsk_release_hugepages()."""
+ have = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
+ if have >= NR_HUGEPAGES:
+ return True
+ cmd(f"sysctl -qw vm.nr_hugepages={NR_HUGEPAGES}", host=host)
+ saved.append((host, have))
+ got = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
+ return got >= NR_HUGEPAGES
+
+
+def xsk_release_hugepages(saved):
+ """Restore the counts that xsk_reserve_hugepages() added to saved, the
+ last one first, as two hosts can share the sysctl."""
+ for host, have in reversed(saved):
+ cmd(f"sysctl -qw vm.nr_hugepages={have}", host=host)
+
+
+def xsk_setup_busy_poll(ifname):
+ """Set napi_defer_hard_irqs and gro_flush_timeout of ifname until the
+ case ends, so that SO_PREFER_BUSY_POLL of xskxceiver -b keeps the IRQs
+ masked."""
+ sysfs = f"/sys/class/net/{ifname}"
+ ctl_file_write(f"{sysfs}/napi_defer_hard_irqs", 2)
+ ctl_file_write(f"{sysfs}/gro_flush_timeout", 200000)
+
+
+def xsk_wait_endpoint_exit(endpoint, label, timeout):
+ """Wait up to timeout seconds for xskxceiver to exit, kill it if it does
+ not, and return its exit code. Log its output, a KTAP stream of its own,
+ prefixed with label, only if it did not pass."""
+ try:
+ endpoint.process(terminate=False, fail=False, timeout=timeout)
+ except subprocess.TimeoutExpired:
+ endpoint.proc.kill()
+ endpoint.process(terminate=False, fail=False)
+ ksft_pr(f"killed after {timeout}s", line_pfx=f"{label}|")
+ if endpoint.ret:
+ for out in (endpoint.stdout, endpoint.stderr):
+ if out:
+ ksft_pr(out.rstrip(), line_pfx=f"{label}|")
+ return endpoint.ret
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 02e54b026806..09ad623333be 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -6,7 +6,7 @@
* Linux kernel samples/bpf/xdpsock* and modified
* for use.
*
- * See test_xsk.sh for detailed information on test topology
+ * See test_xsk.py for detailed information on test topology
* and prerequisite network setup.
*
* This test program contains two threads, each thread is single socket with
@@ -64,11 +64,6 @@
* - 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,
* and have the right content
- *
- * Enable/disable packet dump mode:
- * --------------------------
- * To enable L2 - L4 headers and payload dump of each packet on STDOUT, add
- * parameter -D to params array in test_xsk.sh, i.e. params=("-S" "-D")
*/
#define _GNU_SOURCE
@@ -169,14 +164,14 @@ 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"
" -b, --busy-poll Enable busy poll\n"
- " -m, --mode Run only mode skb, drv, or zc\n"
+ " -m, --mode Mode to run: skb, drv or zc\n"
" -l, --list List all available tests\n"
- " -t, --test Run a specific test. Enter number from -l option.\n"
+ " -t, --test Test case to run, a number from -l\n"
" -h, --help Display this help and exit\n";
ksft_print_msg(str, basename(argv[0]));
@@ -267,6 +262,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 +366,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);
@@ -422,40 +419,18 @@ 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;
- 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 +440,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.py b/tools/testing/selftests/net/test_xsk.py
new file mode 100755
index 000000000000..acfdba55fbae
--- /dev/null
+++ b/tools/testing/selftests/net/test_xsk.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+r"""
+AF_XDP tests over veth.
+
+Topology:
+
+ -----------
+ _ | Process | _
+ / ----------- \
+ / | \
+ / | \
+ ----------- | -----------
+ | Thread1 | | | Thread2 |
+ ----------- | -----------
+ | | |
+ ----------- | -----------
+ | xskX | | | xskY |
+ ----------- | -----------
+ | | |
+ ----------- | ----------
+ | vethX | --------- | vethY |
+ ----------- peer ----------
+
+Each case runs an xskxceiver process of its own on a veth pair that the case
+creates in the current network namespace and deletes when it ends.
+
+test_xsk runs every case that "xskxceiver -l" lists but EXCLUDED_CASES in SKB
+and DRV mode, and test_xsk_busy_poll runs them again with busy polling.
+UNALIGNED cases are left out without huge pages. Use -t or -T to select
+cases, e.g. -t 'test_xsk.drv_*'. See lib/xsk/xskxceiver.c for what each case
+tests.
+"""
+
+import random
+import re
+import sys
+from pathlib import Path
+
+from lib.py import (KsftNamedVariant, KsftSkipEx, cmd, defer, ip, ksft_eq,
+ ksft_exit, ksft_run, ksft_variants, xsk_release_hugepages,
+ xsk_reserve_hugepages, xsk_setup_busy_poll,
+ xsk_wait_endpoint_exit)
+
+XSK_BIN = Path(__file__).parent / "lib/xskxceiver"
+CASE_TIMEOUT = 120
+SKIP_CODE = 4
+EXCLUDED_CASES = {
+ # bpf_xdp_adjust_tail() frees a frag that it trims off as the memory type
+ # of the XDP RX queue, which veth and generic XDP leave at
+ # MEM_TYPE_PAGE_SHARED for frames they copy into page_pool pages, so the
+ # page leaks from its pool.
+ "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF",
+ # The case skips in SKB mode. In DRV mode, veth copies each frame into a
+ # page_pool fragment that leaves no room to grow the 128-byte frames of
+ # the case unless it is the last one of its page, so the XDP program
+ # drops most frames.
+ "XDP_ADJUST_TAIL_GROW",
+}
+
+
+def _get_cases(hugepages):
+ """
+ each test case from xskxceiver -l comes out in a form below:
+ 0: SEND_RECEIVE
+ """
+ ret = []
+ out = cmd([str(XSK_BIN), "-l"]).stdout
+ cases = re.findall(r"^(\d+): (\w+)$", out, re.MULTILINE)
+ for mode in ("skb", "drv"):
+ for test_id, name in cases:
+ if name in EXCLUDED_CASES:
+ continue
+ if "UNALIGNED" in name and not hugepages:
+ continue
+ ret.append(KsftNamedVariant(f"{mode}_{name}", mode, int(test_id)))
+ return ret
+
+
+def _veth_pair(busy_poll):
+ pfx = f"xsk{random.randrange(1 << 20):05x}"
+ veths = (pfx + "a", pfx + "b")
+ ip(f"link add {veths[0]} numtxqueues 4 numrxqueues 4 type veth "
+ f"peer name {veths[1]} numtxqueues 4 numrxqueues 4")
+ defer(ip, f"link del {veths[0]}")
+ for dev in veths:
+ # The XDP program redirects all traffic, keep IPv6 off the link.
+ ipv6 = Path("/proc/sys/net/ipv6/conf", dev, "disable_ipv6")
+ if ipv6.exists():
+ ipv6.write_text("1", encoding="utf-8")
+ if busy_poll:
+ xsk_setup_busy_poll(dev)
+ for dev in veths:
+ ip(f"link set {dev} up")
+ return veths
+
+
+def _start(args):
+ return cmd([str(XSK_BIN), *args], background=True, fail=False)
+
+
+def _test(mode, test_id, busy_poll):
+ tx_if, rx_if = _veth_pair(busy_poll)
+ args = ["-m", mode,
+ "-t", str(test_id),
+ *(["-b"] if busy_poll else [])]
+ ret = xsk_wait_endpoint_exit(_start(["-i", tx_if, "-i", rx_if, *args]),
+ "xsk", CASE_TIMEOUT)
+ if ret == SKIP_CODE:
+ raise KsftSkipEx("xskxceiver skipped the case")
+ ksft_eq(ret, 0, "xskxceiver exit code")
+
+
+def test_xsk(mode, test_id):
+ _test(mode, test_id, busy_poll=False)
+
+
+def test_xsk_busy_poll(mode, test_id):
+ _test(mode, test_id, busy_poll=True)
+
+
+def main():
+ # ksft_run() exits on -l and -h before main() restores the huge pages.
+ if "-l" in sys.argv or "-h" in sys.argv:
+ sys.exit(f"{sys.argv[0]}: -l and -h are not supported")
+ if not XSK_BIN.exists():
+ print(f"1..0 # SKIP {XSK_BIN} was not built")
+ sys.exit(SKIP_CODE)
+ saved_hugepages = []
+ hugepages = xsk_reserve_hugepages(saved_hugepages)
+ cases = _get_cases(hugepages)
+ ksft_run([ksft_variants(cases)(test_xsk),
+ ksft_variants(cases)(test_xsk_busy_poll)])
+ xsk_release_hugepages(saved_hugepages)
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/net/test_xsk.sh b/tools/testing/selftests/net/test_xsk.sh
deleted file mode 100755
index fbf4551d596a..000000000000
--- a/tools/testing/selftests/net/test_xsk.sh
+++ /dev/null
@@ -1,230 +0,0 @@
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-# Copyright(c) 2020 Intel Corporation, Weqaar Janjua <weqaar.a.janjua@intel.com>
-
-# AF_XDP selftests based on veth
-#
-# End-to-end AF_XDP over Veth test
-#
-# Topology:
-# ---------
-# -----------
-# _ | Process | _
-# / ----------- \
-# / | \
-# / | \
-# ----------- | -----------
-# | Thread1 | | | Thread2 |
-# ----------- | -----------
-# | | |
-# ----------- | -----------
-# | xskX | | | xskY |
-# ----------- | -----------
-# | | |
-# ----------- | ----------
-# | vethX | --------- | vethY |
-# ----------- peer ----------
-#
-# AF_XDP is an address family optimized for high performance packet processing,
-# it is XDP’s user-space interface.
-#
-# An AF_XDP socket is linked to a single UMEM which is a region of virtual
-# contiguous memory, divided into equal-sized frames.
-#
-# Refer to AF_XDP Kernel Documentation for detailed information:
-# https://www.kernel.org/doc/html/latest/networking/af_xdp.html
-#
-# Prerequisites setup by script:
-#
-# Set up veth interfaces as per the topology shown ^^:
-# * setup two veth interfaces
-# ** veth<xxxx>
-# ** veth<yyyy>
-# *** xxxx and yyyy are randomly generated 4 digit numbers used to avoid
-# conflict with any existing interface
-# * tests the veth and xsk layers of the topology
-#
-# See the source xskxceiver.c for information on each test
-#
-# Kernel configuration:
-# ---------------------
-# See "config" file for recommended kernel config options.
-#
-# Turn on XDP sockets and veth support when compiling i.e.
-# Networking support -->
-# Networking options -->
-# [ * ] XDP sockets
-#
-# Executing Tests:
-# ----------------
-# Must run with CAP_NET_ADMIN capability.
-#
-# Run:
-# sudo ./test_xsk.sh
-#
-# If running from kselftests:
-# sudo make run_tests
-#
-# Run with verbose output:
-# sudo ./test_xsk.sh -v
-#
-# 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]
-# sudo ./test_xsk.sh -m MODE
-#
-# List available tests
-# ./test_xsk.sh -l
-#
-# Run a specific test from the test suite
-# sudo ./test_xsk.sh -t TEST_NAME
-#
-# Display the available command line options
-# ./test_xsk.sh -h
-
-. xsk_prereqs.sh
-
-while getopts "vdm:lt:h" flag
-do
- case "${flag}" in
- v) verbose=1;;
- d) debug=1;;
- m) MODE=${OPTARG};;
- l) list=1;;
- t) TEST=${OPTARG};;
- h) help=1;;
- esac
-done
-
-if [ ! -x "./${XSKOBJ}" ]; then
- echo "xskxceiver was not built; skipping"
- exit $ksft_skip
-fi
-
-TEST_NAME="PREREQUISITES"
-
-URANDOM=/dev/urandom
-[ ! -e "${URANDOM}" ] && { echo "${URANDOM} not found. Skipping tests."; test_exit $ksft_fail; }
-
-VETH0_POSTFIX=$(cat ${URANDOM} | tr -dc '0-9' | fold -w 256 | head -n 1 | head --bytes 4)
-VETH0=ve${VETH0_POSTFIX}
-VETH1_POSTFIX=$(cat ${URANDOM} | tr -dc '0-9' | fold -w 256 | head -n 1 | head --bytes 4)
-VETH1=ve${VETH1_POSTFIX}
-MTU=1500
-
-trap ctrl_c INT
-
-function ctrl_c() {
- cleanup_exit ${VETH0} ${VETH1}
- exit 1
-}
-
-setup_vethPairs() {
- if [[ $verbose -eq 1 ]]; then
- echo "setting up ${VETH0}"
- fi
- ip link add ${VETH0} numtxqueues 4 numrxqueues 4 type veth peer name ${VETH1} numtxqueues 4 numrxqueues 4
- if [ -f /proc/net/if_inet6 ]; then
- echo 1 > /proc/sys/net/ipv6/conf/${VETH0}/disable_ipv6
- echo 1 > /proc/sys/net/ipv6/conf/${VETH1}/disable_ipv6
- fi
- if [[ $verbose -eq 1 ]]; then
- echo "setting up ${VETH1}"
- fi
-
- if [[ $busy_poll -eq 1 ]]; then
- echo 2 > /sys/class/net/${VETH0}/napi_defer_hard_irqs
- echo 200000 > /sys/class/net/${VETH0}/gro_flush_timeout
- echo 2 > /sys/class/net/${VETH1}/napi_defer_hard_irqs
- echo 200000 > /sys/class/net/${VETH1}/gro_flush_timeout
- fi
-
- ip link set ${VETH1} mtu ${MTU}
- ip link set ${VETH0} mtu ${MTU}
- ip link set ${VETH1} up
- ip link set ${VETH0} up
-}
-
-if [[ $list -eq 1 ]]; then
- ./${XSKOBJ} -l
- exit
-fi
-
-if [[ $help -eq 1 ]]; then
- ./${XSKOBJ}
- exit
-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
-
-
-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=()
-
-TEST_NAME="XSK_SELFTESTS_${VETH0}_SOFTIRQ"
-
-if [[ $debug -eq 1 ]]; then
- echo "-i" ${VETH0} "-i" ${VETH1}
- exit
-fi
-
-exec_xskxceiver
-
-cleanup_exit ${VETH0} ${VETH1}
-
-if [[ $list -eq 1 ]]; then
- exit
-fi
-
-TEST_NAME="XSK_SELFTESTS_${VETH0}_BUSY_POLL"
-busy_poll=1
-
-setup_vethPairs
-exec_xskxceiver
-
-## END TESTS
-
-cleanup_exit ${VETH0} ${VETH1}
-
-failures=0
-echo -e "\nSummary:"
-for i in "${!statusList[@]}"
-do
- if [ ${statusList[$i]} -ne 0 ]; then
- test_status ${statusList[$i]} ${nameList[$i]}
- failures=1
- fi
-done
-
-if [ $failures -eq 0 ]; then
- echo "All tests successful!"
-else
- exit 1
-fi
diff --git a/tools/testing/selftests/net/xsk_prereqs.sh b/tools/testing/selftests/net/xsk_prereqs.sh
deleted file mode 100755
index 5e5c8ef3fff2..000000000000
--- a/tools/testing/selftests/net/xsk_prereqs.sh
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-# Copyright(c) 2020 Intel Corporation.
-
-ksft_pass=0
-ksft_fail=1
-ksft_xfail=2
-ksft_xpass=3
-ksft_skip=4
-
-XSKOBJ=lib/xskxceiver
-
-validate_root_exec()
-{
- msg="skip all tests:"
- if [ $UID != 0 ]; then
- echo $msg must be run as root >&2
- test_exit $ksft_fail
- else
- return $ksft_pass
- fi
-}
-
-validate_veth_support()
-{
- msg="skip all tests:"
- if [ $(ip link add $1 type veth 2>/dev/null; echo $?;) != 0 ]; then
- echo $msg veth kernel support not available >&2
- test_exit $ksft_skip
- else
- ip link del $1
- return $ksft_pass
- fi
-}
-
-test_status()
-{
- statusval=$1
- if [ $statusval -eq $ksft_fail ]; then
- echo "$2: [ FAIL ]"
- elif [ $statusval -eq $ksft_skip ]; then
- echo "$2: [ SKIPPED ]"
- elif [ $statusval -eq $ksft_pass ]; then
- echo "$2: [ PASS ]"
- fi
-}
-
-test_exit()
-{
- if [ $1 -ne 0 ]; then
- test_status $1 $(basename $0)
- fi
- exit 1
-}
-
-clear_configs()
-{
- [ $(ip link show $1 &>/dev/null; echo $?;) == 0 ] &&
- { ip link del $1; }
-}
-
-cleanup_exit()
-{
- clear_configs $1 $2
-}
-
-validate_ip_utility()
-{
- [ ! $(type -P ip) ] && { echo "'ip' not found. Skipping tests."; test_exit $ksft_skip; }
-}
-
-exec_xskxceiver()
-{
- if [[ $busy_poll -eq 1 ]]; then
- ARGS+="-b "
- fi
-
- ./${XSKOBJ} -i ${VETH0} -i ${VETH1} ${ARGS}
- retval=$?
-
- if [[ $list -ne 1 ]]; then
- test_status $retval "${TEST_NAME}"
- statusList+=($retval)
- nameList+=(${TEST_NAME})
- fi
-}
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (8 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 9:04 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
` (4 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 as separate xskxceiver processes,
so that a later patch can run them on two hosts. 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 a loaded copy of the XDP skeleton, so the shared
test code keeps addressing both sides while only the local interface is
programmed. Once BIDIRECTIONAL swaps the directions, the local
interface runs the shadow's programs.
The pthreads, the barrier, test->fail and -lpthread go away. Each
process runs the worker of its local ifobject, and the endpoints
exchange three fixed-size messages:
- READY, exchanged before each step, 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 as not to overrun the RX UMEM
- ABORT tells the other endpoint to stop waiting after a failure
TX listens and RX connects. The listener says on stderr when it
listens, so the launcher starts the connector then rather than polling
for the port, which could also find a foreign listener. The listener
waits 30s for the connector, and -P takes a port number. As with the
control channels of other selftests, the hosts are trusted and the
channel is not authenticated. Test selection, interface capabilities
and verdicts stay with the launcher.
test_xsk.py starts TX on a random control port on 127.0.0.1 and RX
once TX listens, and kills TX and fails the case when it does not
listen within 30s. The case passes when both endpoints pass, and is
skipped when one skips and neither fails. Only the output of an
endpoint that does not pass is logged, prefixed with its role.
xsk_wait_listen(), which reads the TX stderr up to the listen line,
joins the library helpers, and xsk_wait_endpoint_exit() logs that
output along with the rest.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
tools/testing/selftests/net/lib/Makefile | 3 +-
.../testing/selftests/net/lib/py/__init__.py | 4 +-
tools/testing/selftests/net/lib/py/xsk.py | 35 ++-
.../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 | 293 ++++++++++++++++++
.../testing/selftests/net/lib/xsk/xsk_peer.h | 19 ++
.../selftests/net/lib/xsk/xskxceiver.c | 136 ++++++--
tools/testing/selftests/net/test_xsk.py | 68 ++--
9 files changed, 633 insertions(+), 153 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 5c1612315062..49dac776202b 100644
--- a/tools/testing/selftests/net/lib/Makefile
+++ b/tools/testing/selftests/net/lib/Makefile
@@ -38,13 +38,12 @@ 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)
PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
XSK_LDLIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -lz
-XSK_LDLIBS += -lpthread
$(XSK_BPF_OBJ): $(XSK_DIR)/xsk_xdp_progs.bpf.c $(XSK_DIR)/xsk_xdp_common.h $(BPFOBJ)
$(call msg,BPF_PROG,,$@)
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 1a93ab8b1e60..a82bac8a6b47 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
ctl_file_write, wait_file, tool, tc
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from .xsk import xsk_reserve_hugepages, xsk_release_hugepages, \
- xsk_setup_busy_poll, xsk_wait_endpoint_exit
+ xsk_setup_busy_poll, xsk_wait_endpoint_exit, xsk_wait_listen
from .ynl import NlError, NlctrlFamily, YnlFamily, \
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily
from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
@@ -34,7 +34,7 @@ __all__ = ["KSRC",
"wait_port_listen", "ctl_file_write", "wait_file", "tool", "tc",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"xsk_reserve_hugepages", "xsk_release_hugepages",
- "xsk_setup_busy_poll", "xsk_wait_endpoint_exit",
+ "xsk_setup_busy_poll", "xsk_wait_endpoint_exit", "xsk_wait_listen",
"NetdevSim", "NetdevSimDev",
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
"YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
diff --git a/tools/testing/selftests/net/lib/py/xsk.py b/tools/testing/selftests/net/lib/py/xsk.py
index 50985bcb5b80..6f4ffc4786d2 100644
--- a/tools/testing/selftests/net/lib/py/xsk.py
+++ b/tools/testing/selftests/net/lib/py/xsk.py
@@ -5,9 +5,10 @@ Helpers to run xskxceiver, the AF_XDP test program in net/lib/xsk.
"""
import subprocess
+import time
from .ksft import ksft_pr
-from .utils import cmd, ctl_file_write
+from .utils import cmd, ctl_file_write, fd_read_timeout
NR_HUGEPAGES = 32
@@ -41,10 +42,32 @@ def xsk_setup_busy_poll(ifname):
ctl_file_write(f"{sysfs}/gro_flush_timeout", 200000)
-def xsk_wait_endpoint_exit(endpoint, label, timeout):
- """Wait up to timeout seconds for xskxceiver to exit, kill it if it does
- not, and return its exit code. Log its output, a KTAP stream of its own,
- prefixed with label, only if it did not pass."""
+def xsk_wait_listen(endpoint, port, timeout):
+ """Wait up to timeout seconds for an xskxceiver endpoint to print on
+ stderr that it listens on the control port. Unlike polling for the port,
+ this never takes a foreign listener for it and costs no SSH login per
+ try on a remote host. Return the stderr read so far and whether that
+ line came."""
+ line = f"Listening for XSK peer on port {port}\n".encode()
+ fd = endpoint.proc.stderr.fileno()
+ head = b""
+ deadline = time.monotonic() + 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 xsk_wait_endpoint_exit(endpoint, label, timeout, head=""):
+ """Wait up to timeout seconds for an xskxceiver endpoint to exit, kill it
+ if it does not, and return its exit code. Log its output, a KTAP stream
+ of its own, prefixed with label, only if it did not pass. head is the
+ start of its stderr, if xsk_wait_listen() took it off the pipe."""
try:
endpoint.process(terminate=False, fail=False, timeout=timeout)
except subprocess.TimeoutExpired:
@@ -52,7 +75,7 @@ def xsk_wait_endpoint_exit(endpoint, label, timeout):
endpoint.process(terminate=False, fail=False)
ksft_pr(f"killed after {timeout}s", line_pfx=f"{label}|")
if endpoint.ret:
- for out in (endpoint.stdout, endpoint.stderr):
+ for out in (endpoint.stdout, head + endpoint.stderr):
if out:
ksft_pr(out.rstrip(), line_pfx=f"{label}|")
return endpoint.ret
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index e4e0d4dfab5a..f67a32c0260a 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
@@ -256,7 +283,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;
@@ -264,7 +292,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;
@@ -332,26 +359,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) {
@@ -1001,6 +1033,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;
@@ -1100,9 +1135,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;
@@ -1178,9 +1212,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;
@@ -1262,9 +1305,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;
@@ -1341,9 +1382,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)
@@ -1665,36 +1703,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)
@@ -1749,13 +1808,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))
@@ -1768,7 +1827,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++)
@@ -1780,15 +1839,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;
@@ -1819,51 +1878,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)
@@ -1879,7 +1914,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;
}
@@ -1912,7 +1948,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;
@@ -1951,6 +1987,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;
@@ -2299,7 +2338,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))
@@ -2357,12 +2396,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..e3e353d6abd9
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <stdio.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. As with the control
+ * channels of other selftests, the test hosts are trusted: nothing
+ * authenticates the peer.
+ */
+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;
+ u32 pkts;
+ 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:
+ /* xsk_peer_tx_sync() returns the sum as an int. */
+ pkts = ntohl(msg.pkts);
+ if (pkts > INT_MAX - peer->acked)
+ return -EPROTO;
+ peer->acked += 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);
+ /*
+ * Leave closed to peer_drain(), which first reads what the
+ * peer sent before it went away, such as an ABORT.
+ */
+ if (err && !peer_gone(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 listener says on stderr when it listens, and the launcher waits for
+ * that line before it starts 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,
+ .ai_flags = AI_NUMERICSERV,
+ };
+ 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)) {
+ fprintf(stderr, "Listening for XSK peer on port %s\n", port);
+ 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 09ad623333be..769718cad8cd 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.py 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.py 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
*/
@@ -87,6 +89,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"
@@ -95,8 +98,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)
{
@@ -157,6 +170,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}
};
@@ -164,7 +180,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"
@@ -172,16 +188,18 @@ static void print_usage(char **argv)
" -m, --mode Mode to run: skb, drv or zc\n"
" -l, --list List all available tests\n"
" -t, --test Test case to run, a number from -l\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);
@@ -193,7 +211,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();
}
@@ -208,26 +226,33 @@ 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)
{
- 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;
@@ -256,17 +281,31 @@ 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':
+ parse_u32(optarg, 1, UINT16_MAX, argv);
+ 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)
@@ -352,20 +391,51 @@ 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)
+{
+ /* Once BIDIRECTIONAL swaps the directions, the local side attaches the
+ * shadow's programs, so the shadow needs a skeleton of its own.
+ */
+ 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;
@@ -412,12 +482,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)
@@ -433,8 +517,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.py b/tools/testing/selftests/net/test_xsk.py
index acfdba55fbae..8bf18b913c93 100755
--- a/tools/testing/selftests/net/test_xsk.py
+++ b/tools/testing/selftests/net/test_xsk.py
@@ -6,25 +6,22 @@ AF_XDP tests over veth.
Topology:
- -----------
- _ | Process | _
- / ----------- \
- / | \
- / | \
- ----------- | -----------
- | Thread1 | | | Thread2 |
- ----------- | -----------
- | | |
- ----------- | -----------
- | xskX | | | xskY |
- ----------- | -----------
- | | |
- ----------- | ----------
- | vethX | --------- | vethY |
- ----------- peer ----------
-
-Each case runs an xskxceiver process of its own on a veth pair that the case
-creates in the current network namespace and deletes when it ends.
+ ----------- control -----------
+ | TX proc | ------------ | RX proc |
+ ----------- (TCP, lo) -----------
+ | |
+ ----------- -----------
+ | xskX | | xskY |
+ ----------- -----------
+ | |
+ ----------- ----------
+ | vethX | ------------ | vethY |
+ ----------- peer ----------
+
+Each case runs a TX and an RX xskxceiver process on a veth pair that the case
+creates in the current network namespace and deletes when it ends. The TX
+process listens on a TCP control port on 127.0.0.1, and the RX process
+connects to it once TX says on stderr that it listens.
test_xsk runs every case that "xskxceiver -l" lists but EXCLUDED_CASES in SKB
and DRV mode, and test_xsk_busy_poll runs them again with busy polling.
@@ -38,13 +35,16 @@ import re
import sys
from pathlib import Path
-from lib.py import (KsftNamedVariant, KsftSkipEx, cmd, defer, ip, ksft_eq,
- ksft_exit, ksft_run, ksft_variants, xsk_release_hugepages,
- xsk_reserve_hugepages, xsk_setup_busy_poll,
- xsk_wait_endpoint_exit)
+from lib.py import (KsftFailEx, KsftNamedVariant, KsftSkipEx, cmd, defer, ip,
+ ksft_exit, ksft_in, ksft_run, ksft_variants, rand_port,
+ xsk_release_hugepages, xsk_reserve_hugepages,
+ xsk_setup_busy_poll, xsk_wait_endpoint_exit,
+ xsk_wait_listen)
XSK_BIN = Path(__file__).parent / "lib/xskxceiver"
CASE_TIMEOUT = 120
+# xskxceiver waits as long for its peer to connect.
+LISTEN_TIMEOUT = 30
SKIP_CODE = 4
EXCLUDED_CASES = {
# bpf_xdp_adjust_tail() frees a frag that it trims off as the memory type
@@ -102,14 +102,28 @@ def _start(args):
def _test(mode, test_id, busy_poll):
tx_if, rx_if = _veth_pair(busy_poll)
+ port = rand_port()
args = ["-m", mode,
"-t", str(test_id),
+ "-p", "127.0.0.1",
+ "-P", str(port),
*(["-b"] if busy_poll else [])]
- ret = xsk_wait_endpoint_exit(_start(["-i", tx_if, "-i", rx_if, *args]),
- "xsk", CASE_TIMEOUT)
- if ret == SKIP_CODE:
+ tx = _start(["-i", tx_if, "-e", "tx", *args])
+ head = ""
+ try:
+ head, listening = xsk_wait_listen(tx, port, LISTEN_TIMEOUT)
+ if not listening:
+ tx.proc.kill()
+ raise KsftFailEx(f"TX endpoint did not listen on port {port}")
+ rx = _start(["-i", rx_if, "-e", "rx", *args])
+ rx_ret = xsk_wait_endpoint_exit(rx, "rx", CASE_TIMEOUT)
+ finally:
+ tx_ret = xsk_wait_endpoint_exit(tx, "tx", CASE_TIMEOUT, head)
+ rets = {tx_ret, rx_ret}
+ if SKIP_CODE in rets and rets <= {0, SKIP_CODE}:
raise KsftSkipEx("xskxceiver skipped the case")
- ksft_eq(ret, 0, "xskxceiver exit code")
+ ksft_in(tx_ret, (0, SKIP_CODE), "TX endpoint exit code")
+ ksft_in(rx_ret, (0, SKIP_CODE), "RX endpoint exit code")
def test_xsk(mode, test_id):
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (9 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 9:42 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
` (3 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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 zero-copy DUT endpoint and an SKB-mode remote endpoint
over a physical link, without negotiating capabilities between them.
Add what such a run needs to xskxceiver:
- --hw sends UDP/IPv4 test packets (--udp-src, --udp-dst, --udp-port)
between the real interface MACs (--peer-mac); XDP_SHARED_UMEM, whose
program picks the socket by a synthetic destination MAC, skips
- --queue binds a queue other than 0, and --listen makes a hardware
endpoint listen for its peer instead of connecting
- --max-frags hands both endpoints the DUT limit for TOO_MANY_FRAGS, as
each side builds the packet stream from its own view of it
- a hardware endpoint probes only the mode it runs in, taking zero-copy
support from NETDEV_XDP_ACT_XSK_ZEROCOPY, and its XDP_ZEROCOPY bind
fails rather than falling back to copy mode, so a false advertisement
still fails the case
- a hardware endpoint that only transmits attaches the XDP program only
in zero-copy mode, where a driver can require one for TX wakeups
- hardware RX and TX loops time out after 20s instead of 3s, and once
both endpoints are ready, each waits up to as long for its link, as
an MTU or XDP change on either host can reset a NIC
- an RX endpoint fills its fill ring before the bind, as a zero-copy
driver posts its RX buffers then and would otherwise wait for the
first wakeup, which comes only after READY lets the remote send
- each endpoint sizes the rings of its own interface, as the bursts of
HW_SW_MAX_RING_SIZE need the largest RX ring on the receiver rather
than on the TX interface, and leaves alone the rings that a case
keeps at their defaults, as a driver may reset the NIC even when they
do not change
- the cases that skip in one mode only (STAT_RX_DROPPED,
TX_QUEUE_CONSUMER and both XDP_ADJUST_TAIL_GROW cases) skip on both
hardware endpoints, which run different modes
Cases now default to an MTU of 1500 (ETH_DATA_LEN) rather than the
1518 byte frame size: IFLA_MTU counts the L3 MTU, so the old default
changed a standard MTU, which can reset a NIC, for every case. A
hardware SKB-mode endpoint raises its MTU only when a case needs a
larger one, so a jumbo-MTU peer is not reset twice per case.
cleanup_iface() restores the MTU that bind_iface() found once the XDP
program, which can cap it, is detached, and fails the endpoint if it
cannot, so the result is reported after cleanup_iface(). That MTU
stays out of ifobj->mtu, which would make the zero-copy probe bind
with XDP_USE_SG.
is_frag_valid() now takes the header size from pkt_hdr_size rather
than PKT_HDR_SIZE and rejects fragments too short for the words it
reads instead of reading past their end. pkt_dump() prints only the
words within the length it is given, which is_pkt_valid() now gives as
the first fragment's length.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 299 ++++++++++++++----
.../testing/selftests/net/lib/xsk/test_xsk.h | 10 +-
tools/testing/selftests/net/lib/xsk/xsk.c | 60 ++++
tools/testing/selftests/net/lib/xsk/xsk.h | 3 +
.../selftests/net/lib/xsk/xskxceiver.c | 134 +++++++-
5 files changed, 432 insertions(+), 74 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index f67a32c0260a..a280956d53f4 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,72 @@ 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 u8 *pos = buf;
+ u32 sum = 0;
+ u16 word;
+
+ /* Copy each word, the header fields are not u16 objects. */
+ for (; len > 1; len -= sizeof(word), pos += sizeof(word)) {
+ memcpy(&word, pos, sizeof(word));
+ sum += 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 +267,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)
@@ -216,13 +291,18 @@ static int set_ring_size(struct ifobject *ifobj)
return ret;
}
+static bool hw_ring_size_changed(const struct ifobject *ifobj)
+{
+ return ifobj->ring.tx_pending != ifobj->set_ring.default_tx ||
+ ifobj->ring.rx_pending != ifobj->set_ring.default_rx;
+}
+
int hw_ring_size_reset(struct ifobject *ifobj)
{
struct ethtool_ringparam ring = ifobj->ring;
int err;
- if (ring.tx_pending == ifobj->set_ring.default_tx &&
- ring.rx_pending == ifobj->set_ring.default_rx)
+ if (!hw_ring_size_changed(ifobj))
return 0;
ifobj->ring.tx_pending = ifobj->set_ring.default_tx;
@@ -242,6 +322,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];
@@ -273,19 +354,24 @@ 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;
ifobj->xsk->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
- }
- if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING) &&
- ifobj_is_local(ifobj_tx))
- hw_ring_size_reset(ifobj_tx);
+ if (ifobj_is_local(ifobj) &&
+ ifobj_has_cap(ifobj, XSK_CAP_HW_RING))
+ hw_ring_size_reset(ifobj);
+ }
test->ifobj_tx = ifobj_tx;
test->ifobj_rx = ifobj_rx;
@@ -295,7 +381,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
test->set_ring = false;
test->adjust_tail = false;
test->adjust_tail_support = false;
- test->mtu = MAX_ETH_PKT_SIZE;
+ test->mtu = ETH_DATA_LEN;
test->xdp_prog_rx = ifobj_rx->xdp_progs->progs.xsk_def_prog;
test->xskmap_rx = ifobj_rx->xdp_progs->maps.xsk;
test->xdp_prog_tx = ifobj_tx->xdp_progs->progs.xsk_def_prog;
@@ -359,14 +445,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;
@@ -377,11 +474,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)
@@ -482,6 +579,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;
@@ -666,7 +776,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);
@@ -674,12 +784,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);
@@ -767,10 +877,11 @@ static void pkt_print_data(u32 *data, u32 cnt)
}
}
+/* With eth_header, len counts the bytes after the test header. */
static void pkt_dump(void *pkt, u32 len, bool eth_header)
{
struct ethhdr *ethhdr = pkt;
- u32 i, *data;
+ u32 i, words, *data;
if (eth_header) {
/*extract L2 frame */
@@ -782,18 +893,19 @@ 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;
}
/*extract L5 frame */
+ words = len / sizeof(u32);
ksft_print_msg("\nDEBUG>> L5: seqnum: ");
- pkt_print_data(data, PKT_DUMP_NB_TO_PRINT);
+ pkt_print_data(data, min_t(u32, words, PKT_DUMP_NB_TO_PRINT));
ksft_print_msg("....");
- if (len > PKT_DUMP_NB_TO_PRINT * sizeof(u32)) {
+ if (words > PKT_DUMP_NB_TO_PRINT) {
ksft_print_msg("\n.... ");
- pkt_print_data(data + len / sizeof(u32) - PKT_DUMP_NB_TO_PRINT,
+ pkt_print_data(data + words - PKT_DUMP_NB_TO_PRINT,
PKT_DUMP_NB_TO_PRINT);
}
ksft_print_msg("\n---------------------------------------\n");
@@ -879,11 +991,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;
@@ -917,12 +1033,13 @@ static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 exp
return false;
}
-static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 len)
+static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 frag_len, u32 len)
{
if (pkt->len != len) {
ksft_print_msg("[%s] expected packet length [%d], got length [%d]\n",
__func__, pkt->len, len);
- pkt_dump(xsk_umem__get_data(buffer, addr), len, true);
+ /* Only the first fragment, which holds the header, is at addr. */
+ pkt_dump(xsk_umem__get_data(buffer, addr), frag_len - pkt_hdr_size, true);
return false;
}
@@ -1020,7 +1137,7 @@ static int complete_pkts(struct xsk_socket_info *xsk, int batch_size)
static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
{
- u32 frags_processed = 0, nb_frags = 0, pkt_len = 0;
+ u32 frags_processed = 0, nb_frags = 0, pkt_len = 0, first_len = 0;
u32 idx_rx = 0, idx_fq = 0, rcvd, pkts_sent = 0;
struct pkt_stream *pkt_stream = xsk->pkt_stream;
struct ifobject *ifobj = test->ifobj_rx;
@@ -1097,8 +1214,10 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
!is_metadata_correct(pkt, umem->buffer, addr)))
return TEST_FAILURE;
- if (!nb_frags++)
+ if (!nb_frags++) {
first_addr = addr;
+ first_len = desc->len;
+ }
frags_processed++;
pkt_len += desc->len;
if (ifobj->use_fill_ring)
@@ -1108,7 +1227,7 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
continue;
/* The complete packet has been received */
- if (!is_pkt_valid(pkt, umem->buffer, first_addr, pkt_len) ||
+ if (!is_pkt_valid(pkt, umem->buffer, first_addr, first_len, pkt_len) ||
!is_offset_correct(umem, pkt, addr))
return TEST_FAILURE;
@@ -1163,7 +1282,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;
@@ -1258,7 +1377,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;
@@ -1270,6 +1389,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);
@@ -1286,8 +1406,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",
@@ -1334,7 +1454,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);
@@ -1617,20 +1737,26 @@ static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
if (ret)
return ret;
+ ifobject->xsk = &ifobject->xsk_arr[0];
+
+ /* Before the bind: a zero-copy driver posts its RX buffers then, or in
+ * a poll it schedules then. Otherwise it waits for a wakeup, and the
+ * peer can send before the RX endpoint gets to it.
+ */
+ if (ifobject->rx_on) {
+ ret = xsk_populate_fill_ring(umem, ifobject->xsk->pkt_stream,
+ ifobject->use_fill_ring);
+ if (ret)
+ return ret;
+ }
+
ret = xsk_configure(test, ifobject, umem);
if (ret)
return ret;
- ifobject->xsk = &ifobject->xsk_arr[0];
-
if (!ifobject->rx_on)
return 0;
- ret = xsk_populate_fill_ring(umem, ifobject->xsk->pkt_stream,
- ifobject->use_fill_ring);
- if (ret)
- return ret;
-
for (i = 0; i < test->nb_sockets; i++) {
ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk_arr[i].xsk, i);
if (ret)
@@ -1703,14 +1829,36 @@ static int testapp_validate_rx_endpoint(struct test_spec *test,
return err;
}
+/* An MTU or XDP change can reset a NIC, which takes the link down on both
+ * hosts. Each endpoint makes its changes before it is ready, so once both are,
+ * wait for the local link before any traffic.
+ */
+static int wait_link_up(struct test_spec *test)
+{
+ struct ifobject *ifobj = test->ifobj_tx;
+ int err;
+
+ if (!ifobj_is_local(ifobj))
+ ifobj = test->ifobj_rx;
+ err = xsk_wait_link_up(ifobj->ifname, HW_THREAD_TMOUT * 1000);
+ if (err) {
+ ksft_print_msg("Link on %s is not up at step %u: %d (%s)\n",
+ ifobj->ifname, test->current_step, err, strerror(-err));
+ xsk_peer_abort(ctrl_peer);
+ }
+ return err;
+}
+
static int sync_endpoints(struct test_spec *test)
{
int err = xsk_peer_ready(ctrl_peer);
- if (err)
+ if (err) {
ksft_print_msg("Endpoint synchronization failed at step %u: %d (%s)\n",
test->current_step, err, strerror(-err));
- return err;
+ return err;
+ }
+ return hw_test ? wait_link_up(test) : 0;
}
int worker_testapp_validate_tx(struct test_spec *test)
@@ -1817,6 +1965,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);
@@ -1905,6 +2057,7 @@ static int testapp_validate_traffic(struct test_spec *test)
{
struct ifobject *ifobj_rx = test->ifobj_rx;
struct ifobject *ifobj_tx = test->ifobj_tx;
+ struct ifobject *ifobj;
if (ifobj_rx->xsk->umem->unaligned_mode &&
!ifobj_has_cap(ifobj_rx, XSK_CAP_UNALIGNED)) {
@@ -1913,16 +2066,16 @@ static int testapp_validate_traffic(struct test_spec *test)
}
if (test->set_ring) {
- if (ifobj_has_cap(ifobj_tx, XSK_CAP_HW_RING)) {
- if (ifobj_is_local(ifobj_tx) &&
- set_ring_size(ifobj_tx)) {
- ksft_print_msg("Failed to change HW ring size.\n");
- return TEST_FAILURE;
- }
- } else {
+ /* Each endpoint sizes the rings of its own interface. */
+ ifobj = ifobj_is_local(ifobj_tx) ? ifobj_tx : ifobj_rx;
+ if (!ifobj_has_cap(ifobj, XSK_CAP_HW_RING)) {
ksft_print_msg("Changing HW ring size not supported.\n");
return TEST_SKIP;
}
+ if (hw_ring_size_changed(ifobj) && set_ring_size(ifobj)) {
+ ksft_print_msg("Failed to change HW ring size.\n");
+ return TEST_FAILURE;
+ }
}
return __testapp_validate_traffic(test, ifobj_rx, ifobj_tx);
@@ -2025,6 +2178,9 @@ int testapp_stats_rx_dropped(struct test_spec *test)
struct xsk_umem_info *umem = test->ifobj_rx->xsk->umem;
u32 umem_tr = xsk_get_cap(test, umem_tailroom);
+ /* Hardware endpoints run different modes, so ZC would skip on one only. */
+ if (hw_test)
+ return TEST_SKIP;
if (test->mode == TEST_MODE_ZC) {
ksft_print_msg("Can not run RX_DROPPED test for ZC mode\n");
return TEST_SKIP;
@@ -2242,6 +2398,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;
@@ -2284,7 +2444,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);
@@ -2378,6 +2540,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;
@@ -2406,6 +2569,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);
}
@@ -2519,7 +2688,8 @@ 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 = xsk_get_cap(test, tx_max_pending);
- test->ifobj_tx->ring.rx_pending = test->ifobj_tx->ring.rx_max_pending;
+ /* The receiver needs its largest RX ring for these bursts. */
+ test->ifobj_rx->ring.rx_pending = test->ifobj_rx->ring.rx_max_pending;
test->ifobj_rx->xsk->umem->num_frames = max_descs;
test->ifobj_rx->xsk->umem->fill_size = max_descs;
test->ifobj_rx->xsk->umem->comp_size = max_descs;
@@ -2606,7 +2776,8 @@ int testapp_adjust_tail_shrink_mb(struct test_spec *test)
int testapp_adjust_tail_grow(struct test_spec *test)
{
- if (test->mode == TEST_MODE_SKB)
+ /* The XDP program numbers the grown tail after a non-UDP header. */
+ if (test->mode == TEST_MODE_SKB || hw_test)
return TEST_SKIP;
/* Grow by 4 bytes for testing purpose */
@@ -2617,7 +2788,8 @@ int testapp_adjust_tail_grow_mb(struct test_spec *test)
{
u32 grow_size;
- if (test->mode == TEST_MODE_SKB)
+ /* The XDP program numbers the grown tail after a non-UDP header. */
+ if (test->mode == TEST_MODE_SKB || hw_test)
return TEST_SKIP;
/* worst case scenario is when underlying setup will work on 3k
@@ -2640,6 +2812,9 @@ int testapp_tx_queue_consumer(struct test_spec *test)
{
int nr_packets;
+ /* Hardware endpoints run different modes, so ZC would skip on one only. */
+ if (hw_test)
+ return TEST_SKIP;
if (test->mode == TEST_MODE_ZC) {
ksft_print_msg("Can not run TX_QUEUE_CONSUMER test for ZC mode\n");
return TEST_SKIP;
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 53e5032510a0..85c1eabbe7f0 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;
+ /* Device MTU, which a hardware SKB-mode endpoint may keep above 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..fb0007c34532 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/xsk.c
@@ -826,3 +826,63 @@ 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;
+}
+
+/* ETHTOOL_GLINK reports the carrier. SIOCGIFFLAGS cannot: IFF_LOWER_UP does
+ * not fit in ifr_flags, and IFF_RUNNING follows the carrier only once the link
+ * watch runs. A driver without .get_link has nothing to wait for.
+ */
+int xsk_wait_link_up(const char *ifname, int timeout_ms)
+{
+ struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
+ struct ifreq ifr = { .ifr_data = (void *)&edata };
+ int err;
+
+ for (;;) {
+ err = xsk_ifreq_ioctl(ifname, SIOCETHTOOL, &ifr);
+ if (err == -EOPNOTSUPP)
+ return 0;
+ if (err || edata.data)
+ return err;
+ if (timeout_ms <= 0)
+ return -ETIMEDOUT;
+ usleep(100 * 1000);
+ timeout_ms -= 100;
+ }
+}
diff --git a/tools/testing/selftests/net/lib/xsk/xsk.h b/tools/testing/selftests/net/lib/xsk/xsk.h
index 3f1fea999763..abff4b950214 100644
--- a/tools/testing/selftests/net/lib/xsk/xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/xsk.h
@@ -245,6 +245,9 @@ 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);
+int xsk_wait_link_up(const char *ifname, int timeout_ms);
#ifdef __cplusplus
} /* extern "C" */
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 769718cad8cd..7044822f23c0 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.py starts two processes: Tx and Rx
@@ -80,6 +83,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>
@@ -107,9 +111,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)
{
@@ -173,6 +197,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}
};
@@ -191,6 +223,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]));
@@ -215,6 +255,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();
}
+ /* Not into ifobj->mtu, which makes the zero-copy probe bind with SG. */
+ if (xsk_get_mtu(ifobj->ifname, &ifobj->dev_mtu)) {
+ ksft_print_msg("Error: cannot read MTU of interface %s\n", ifobj->ifname);
+ ksft_exit_fail();
+ }
+ ifobj->orig_mtu = ifobj->dev_mtu;
}
static void print_tests(void)
@@ -296,6 +342,32 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
parse_u32(optarg, 1, UINT16_MAX, argv);
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);
@@ -306,6 +378,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)
@@ -318,7 +394,12 @@ static int run_pkt_test(struct test_spec *test)
int ret;
ret = test->test_func(test);
+ pkt_stream_restore_default(test);
+ return ret;
+}
+static void report_pkt_test(struct test_spec *test, int ret)
+{
switch (ret) {
case TEST_PASS:
ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test),
@@ -336,9 +417,6 @@ 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);
}
-
- pkt_stream_restore_default(test);
- return ret;
}
static bool is_xdp_supported(int ifindex)
@@ -371,9 +449,15 @@ 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 || opt_mode == TEST_MODE_DRV) &&
+ is_xdp_supported(ifobj->ifindex)) {
+ /* A hardware endpoint only probes the mode it runs in. */
ifobj_set_cap(ifobj, XSK_CAP_DRV);
- if (ifobj_zc_avail(ifobj))
+ if (!opt_hw && ifobj_zc_avail(ifobj))
ifobj_set_cap(ifobj, XSK_CAP_ZC);
}
@@ -389,8 +473,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;
@@ -401,8 +487,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. */
@@ -415,8 +509,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));
@@ -424,7 +519,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;
}
@@ -439,6 +537,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);
@@ -482,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;
@@ -493,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);
@@ -517,9 +623,15 @@ 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_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;
+ /* After the cleanup, which can still fail a case that ran. */
+ if (test.test_func)
+ report_pkt_test(&test, ret);
xsk_peer_close(peer);
pkt_stream_delete(test.tx_pkt_stream_default);
pkt_stream_delete(test.rx_pkt_stream_default);
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (10 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 10:05 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases Maciej Fijalkowski
` (2 subsequent siblings)
14 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 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. On a hardware link, this drops neighbour
discovery, LLDP or SSH while a program is attached, and on the remote,
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, IPv4 fragments
included, as a fragment other than the first has no UDP header. The
veth mode keeps redirecting every packet, as its frames carry no IP
header. Matching the RX queue would not be enough, as the runner leaves
the remote's RSS table alone to need no more than ntuple support there.
The port also keeps the drop, tail-adjust and metadata programs off
unrelated frames. It is written when a skeleton is loaded, so it also
reaches the shadow endpoint's copy, whose programs the local side
attaches once BIDIRECTIONAL swaps the directions.
Knowing the port, xsk_xdp_adjust_tail() numbers a grown tail after the
IPv4 and UDP headers of a hardware test packet, as the RX endpoint
checks it, instead of after the Ethernet header of a veth frame, so
both XDP_ADJUST_TAIL_GROW cases run with --hw. As generic XDP leaves no
room to grow the tail, they still skip when the RX endpoint runs in SKB
mode; a hardware TX endpoint does not know its peer's mode and leaves
that skip to RX.
is_adjust_tail_supported() copied all of .bss into an int with
bpf_map_lookup_elem(), past the end of the int even before the port
grew .bss. Read adjust_value through the skeleton instead.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.c | 47 +++++++------------
.../selftests/net/lib/xsk/xsk_xdp_progs.bpf.c | 47 +++++++++++++++++++
2 files changed, 65 insertions(+), 29 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
index a280956d53f4..b70dbefe9729 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.c
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.c
@@ -944,31 +944,12 @@ static bool is_metadata_correct(struct pkt *pkt, void *buffer, u64 addr)
return true;
}
-static int is_adjust_tail_supported(struct xsk_xdp_progs *skel_rx, bool *supported)
+static bool is_adjust_tail_supported(struct xsk_xdp_progs *skel_rx)
{
- struct bpf_map *data_map;
- int adjust_value = 0;
- int key = 0;
- int ret;
-
- data_map = bpf_object__find_map_by_name(skel_rx->obj, "xsk_xdp_.bss");
- if (!data_map || !bpf_map__is_internal(data_map)) {
- ksft_print_msg("Error: could not find bss section of XDP program\n");
- return -EINVAL;
- }
-
- ret = bpf_map_lookup_elem(bpf_map__fd(data_map), &key, &adjust_value);
- if (ret) {
- ksft_print_msg("Error: bpf_map_lookup_elem failed with error %d\n", ret);
- return ret;
- }
-
/* Set the 'adjust_value' variable to -EOPNOTSUPP in the XDP program if the adjust_tail
* helper is not supported. Skip the adjust_tail test case in this scenario.
*/
- *supported = adjust_value != -EOPNOTSUPP;
-
- return 0;
+ return skel_rx->bss->adjust_value != -EOPNOTSUPP;
}
static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 expected_pkt_nb,
@@ -1806,7 +1787,6 @@ static int testapp_prepare_rx_endpoint(struct test_spec *test,
static int testapp_validate_rx_endpoint(struct test_spec *test,
struct ifobject *ifobject)
{
- bool supported;
int err;
err = receive_pkts(test);
@@ -1819,9 +1799,7 @@ static int testapp_validate_rx_endpoint(struct test_spec *test,
if (!test->adjust_tail)
return err;
- if (is_adjust_tail_supported(ifobject->xdp_progs, &supported))
- return TEST_FAILURE;
- if (!supported) {
+ if (!is_adjust_tail_supported(ifobject->xdp_progs)) {
test->adjust_tail_support = false;
return TEST_PASS;
}
@@ -2506,6 +2484,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;
}
@@ -2774,10 +2754,20 @@ int testapp_adjust_tail_shrink_mb(struct test_spec *test)
return testapp_adjust_tail(test, -XSK_UMEM__MAX_FRAME_SIZE, XSK_UMEM__LARGE_FRAME_SIZE * 2);
}
+/* Generic XDP leaves no room to grow the tail. Only the RX endpoint runs the
+ * program on the test flow, and a hardware TX endpoint, which does not know
+ * the mode of its peer, leaves that skip to the RX endpoint.
+ */
+static bool adjust_tail_grow_skip(struct test_spec *test)
+{
+ if (hw_test && !ifobj_is_local(test->ifobj_rx))
+ return false;
+ return test->mode == TEST_MODE_SKB;
+}
+
int testapp_adjust_tail_grow(struct test_spec *test)
{
- /* The XDP program numbers the grown tail after a non-UDP header. */
- if (test->mode == TEST_MODE_SKB || hw_test)
+ if (adjust_tail_grow_skip(test))
return TEST_SKIP;
/* Grow by 4 bytes for testing purpose */
@@ -2788,8 +2778,7 @@ int testapp_adjust_tail_grow_mb(struct test_spec *test)
{
u32 grow_size;
- /* The XDP program numbers the grown tail after a non-UDP header. */
- if (test->mode == TEST_MODE_SKB || hw_test)
+ if (adjust_tail_grow_skip(test))
return TEST_SKIP;
/* worst case scenario is when underlying setup will work on 3k
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..819f850f1e77 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,9 +5,16 @@
#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"
+#define IP_MF 0x2000
+#define IP_OFFSET 0x1fff
+
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 2);
@@ -18,9 +25,36 @@ 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);
+ /* A fragment other than the first has no UDP header. */
+ if ((void *)(ip + 1) > data_end || ip->ihl != 5 ||
+ ip->protocol != IPPROTO_UDP ||
+ ip->frag_off & bpf_htons(IP_MF | IP_OFFSET))
+ 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 +62,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 +77,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 +103,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 +121,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;
@@ -107,6 +151,9 @@ SEC("xdp.frags") int xsk_xdp_adjust_tail(struct xdp_md *xdp)
__u32 len, words_to_end, seq_num;
len = curr_buff_len - PKT_HDR_ALIGN;
+ /* A hardware test packet also has IPv4 and UDP headers. */
+ if (hw_port)
+ len -= sizeof(struct iphdr) + sizeof(struct udphdr);
words_to_end = len / sizeof(*pkt_data) - 1;
seq_num = words_to_end;
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (11 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
14 siblings, 0 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
The hardware test that the next patch adds runs each case with its
zero-copy endpoint as RX, as TX or as both. A list of the cases by ID
in that runner would repeat the test table and silently go stale when a
case is added or moved.
Split tests[] into the tests that check both endpoint roles, those that
check the RX endpoint, those that check the TX endpoint, and the
generic ones, which a hardware run does not do: BIDIRECTIONAL swaps the
directions, and the others skip in hardware mode. The test IDs follow
the order of these arrays, so they change, and the runners take them
from xskxceiver. The notes that came with ci_skip_tests[] go.
-L lists each hardware test once per role that it tests, as
"ID: NAME rx" or "ID: NAME tx", while -l still lists every test by ID.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/test_xsk.h | 67 +++++++++++++------
.../selftests/net/lib/xsk/xskxceiver.c | 47 ++++++++++---
2 files changed, 83 insertions(+), 31 deletions(-)
diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.h b/tools/testing/selftests/net/lib/xsk/test_xsk.h
index 85c1eabbe7f0..f80e16d84a51 100644
--- a/tools/testing/selftests/net/lib/xsk/test_xsk.h
+++ b/tools/testing/selftests/net/lib/xsk/test_xsk.h
@@ -308,49 +308,72 @@ int testapp_xdp_shared_umem(struct test_spec *test);
int worker_testapp_validate_rx(struct test_spec *test);
int worker_testapp_validate_tx(struct test_spec *test);
-static const struct test_spec tests[] = {
+/* Tests by the role of the endpoint that they check. A hardware run, see
+ * xskxceiver -L, gives that role to its zero-copy endpoint. It does not do
+ * the generic tests: they swap the directions, which drivers/net/hw/xsk.py
+ * sets up one at a time, or skip in hardware mode.
+ */
+static const struct test_spec hw_rx_tx_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 = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb},
+ {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned},
+ {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS",
+ .test_func = testapp_send_receive_unaligned_mb},
+ {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size},
+ {.name = "TEARDOWN", .test_func = testapp_teardown},
+};
+
+static const struct test_spec hw_rx_tests[] = {
{.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},
+};
+
+static const struct test_spec hw_tx_tests[] = {
+ {.name = "POLL_TX", .test_func = testapp_poll_tx},
+ {.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 = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs},
+ {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb},
+ {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags},
{.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},
+};
+
+static const struct test_spec generic_tests[] = {
+ {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional},
+ {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped},
+ {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem},
+ {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer},
+};
+
+/* A test ID is the index of the test in these arrays taken in order. */
+static const struct test_group {
+ const struct test_spec *tests;
+ u32 nb_tests;
+ bool hw_rx;
+ bool hw_tx;
+} test_groups[] = {
+ {hw_rx_tx_tests, ARRAY_SIZE(hw_rx_tx_tests), true, true},
+ {hw_rx_tests, ARRAY_SIZE(hw_rx_tests), true, false},
+ {hw_tx_tests, ARRAY_SIZE(hw_tx_tests), false, true},
+ {generic_tests, ARRAY_SIZE(generic_tests), false, false},
};
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 7044822f23c0..7705a9f2b7a1 100644
--- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
+++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
@@ -193,6 +193,7 @@ static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"mode", required_argument, 0, 'm'},
{"list", no_argument, 0, 'l'},
+ {"list-hw", no_argument, 0, 'L'},
{"test", required_argument, 0, 't'},
{"endpoint", required_argument, 0, 'e'},
{"peer", required_argument, 0, 'p'},
@@ -219,6 +220,7 @@ static void print_usage(char **argv)
" -b, --busy-poll Enable busy poll\n"
" -m, --mode Mode to run: skb, drv or zc\n"
" -l, --list List all available tests\n"
+ " -L, --list-hw List hardware tests and the roles they test\n"
" -t, --test Test case to run, a number from -l\n"
" -e, --endpoint Role of this endpoint: rx or tx\n"
" -p, --peer Control host: IPv4/IPv6 address or hostname\n"
@@ -263,13 +265,38 @@ static void bind_iface(struct ifobject *ifobj, const char *ifname, char **argv)
ifobj->orig_mtu = ifobj->dev_mtu;
}
-static void print_tests(void)
+/* List each test, or each hardware test once per role it tests. */
+static void print_tests(bool hw)
+{
+ const struct test_group *group;
+ u32 i, j, id = 0;
+
+ printf("%s:\n", hw ? "Hardware tests" : "Tests");
+ for (i = 0; i < ARRAY_SIZE(test_groups); i++) {
+ group = &test_groups[i];
+ for (j = 0; j < group->nb_tests; j++, id++) {
+ const char *name = group->tests[j].name;
+
+ if (!hw)
+ printf("%u: %s\n", id, name);
+ if (hw && group->hw_rx)
+ printf("%u: %s rx\n", id, name);
+ if (hw && group->hw_tx)
+ printf("%u: %s tx\n", id, name);
+ }
+ }
+}
+
+static const struct test_spec *find_test(u32 id)
{
u32 i;
- printf("Tests:\n");
- for (i = 0; i < ARRAY_SIZE(tests); i++)
- printf("%u: %s\n", i, tests[i].name);
+ for (i = 0; i < ARRAY_SIZE(test_groups); i++) {
+ if (id < test_groups[i].nb_tests)
+ return &test_groups[i].tests[id];
+ id -= test_groups[i].nb_tests;
+ }
+ return NULL;
}
static u32 parse_u32(const char *arg, u32 min, u32 max, char **argv)
@@ -292,7 +319,7 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
opterr = 0;
for (;;) {
- c = getopt_long(argc, argv, "i:vbm:lt:e:p:P:", long_options, &option_index);
+ c = getopt_long(argc, argv, "i:vbm:lLt:e:p:P:", long_options, &option_index);
if (c == -1)
break;
@@ -318,7 +345,8 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj
print_usage(argv);
break;
case 'l':
- print_tests();
+ case 'L':
+ print_tests(c == 'L');
ksft_exit_pass();
break;
case 't':
@@ -529,8 +557,8 @@ static int setup_peer(struct ifobject *local, struct ifobject *shadow,
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;
+ const struct test_spec *test_to_run;
struct ifobject *shadow_ifobj;
struct ifobject *local_ifobj;
struct xsk_peer *peer = NULL;
@@ -576,7 +604,8 @@ int main(int argc, char **argv)
ifobj_tx->caps.umem_tailroom = umem_tailroom;
ifobj_rx->caps.umem_tailroom = umem_tailroom;
- if (opt_run_test != RUN_ALL_TESTS && opt_run_test >= total_tests) {
+ test_to_run = find_test(opt_run_test);
+ if (!test_to_run) {
ksft_print_msg("Error: test %u does not exist.\n", opt_run_test);
ksft_exit_xfail();
}
@@ -619,7 +648,7 @@ int main(int argc, char **argv)
}
ksft_set_plan(1);
- test_init(&test, ifobj_tx, ifobj_rx, opt_mode, &tests[opt_run_test]);
+ test_init(&test, ifobj_tx, ifobj_rx, opt_mode, test_to_run);
ret = run_pkt_test(&test);
out:
--
2.43.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (12 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
2026-10-15 10:29 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
14 siblings, 2 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Run the AF_XDP cases that a physical link can carry as pairs of
xskxceiver processes: a zero-copy endpoint on the DUT and an SKB-mode
endpoint on the remote, which needs no zero-copy support. xsk.py runs
the cases that xskxceiver -L lists, in the DUT directions listed with
them, as KTAP variants named <rx|tx>_<CASE>. test_xsk runs them with
the DUT socket driven by interrupts and test_xsk_busy_poll with it busy
polling, after xsk_setup_busy_poll() sets napi_defer_hard_irqs and
gro_flush_timeout on the DUT so that SO_PREFER_BUSY_POLL keeps its IRQs
masked, as on veth.
main() sets up what the cases share once and undoes it after them, as
defer() works only within a case, so xsk.py rejects -l and -h, on which
ksft_run() exits without returning:
- the DUT endpoint binds the last queue with both an RX and a TX ring,
which is taken out of RSS with ethtool -X equal and handed back with
ethtool -X default; a TX endpoint fills no RX buffers, so in
zero-copy mode its queue drops what it receives and TX cases need the
queue out of RSS as much as RX cases do. A DUT with fewer than two
such queues skips
- an ntuple rule on each host steers the test UDP flow to the receiving
endpoint: the DUT queue in RX cases and queue 0 of the remote, where
its endpoint binds, in TX cases. A host without ntuple support skips.
Setting the rules once spares each TX case the ssh round trips of a
remote rule and toggles ntuple-filters, which resets some NICs, once
per run rather than per case
- huge pages are reserved on both hosts, and the UNALIGNED cases are
left out without them
- both endpoints get the DUT's xdp-zc-max-segs for TOO_MANY_FRAGS,
which is left out without multi-buffer zero-copy
A DUT that does not advertise AF_XDP zero-copy skips the run.
The remote endpoint listens on REMOTE_V4, and the DUT endpoint connects
once the remote says on stderr that it listens. The control connection
shares the tested link, where the XDP programs pass it to the stack.
Killing an ssh client does not stop the command it runs, so timeout(1)
bounds the remote endpoint, and the runner waits for it to exit before
the case ends. The endpoints are waited for with the test_xsk.py
helpers, so the output of one that does not pass is logged, timeouts
included.
The endpoints detach their XDP programs and restore the MTU and ring
sizes when they exit; the runner detaches the programs and restores the
MTU only after one fails or is killed. Each endpoint sees only its own
device and mode, so one may skip a case that the other runs and then
fails once its peer is gone; such a case is reported as skipped.
xsk.py runs the xskxceiver that net/lib builds, which drv-net already
installs, and copies it to the remote with cfg.remote.deploy(). Add
xsk.py to TEST_PROGS and to the AF_XDP entry in MAINTAINERS,
CONFIG_XDP_SOCKETS to the drivers/net/hw config, and the xskxceiver
helpers to the drivers/net/hw library exports.
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 +
.../drivers/net/hw/lib/py/__init__.py | 5 +
tools/testing/selftests/drivers/net/hw/xsk.py | 263 ++++++++++++++++++
tools/testing/selftests/net/lib/Makefile | 2 +-
6 files changed, 272 insertions(+), 1 deletion(-)
create mode 100755 tools/testing/selftests/drivers/net/hw/xsk.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 6985a42bc09c..69c158d42877 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29783,6 +29783,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/py/xsk.py
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/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
index 4077f5f1a1be..aee6cdb4575b 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
@@ -26,6 +26,8 @@ try:
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, \
wait_file, ctl_file_write, tool
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
+ from net.lib.py import xsk_reserve_hugepages, xsk_release_hugepages, \
+ xsk_setup_busy_poll, xsk_wait_endpoint_exit, xsk_wait_listen
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
ksft_setup, ksft_variants, KsftNamedVariant
@@ -43,6 +45,9 @@ try:
"fd_read_timeout", "ip", "rand_port", "rand_ports",
"wait_port_listen", "wait_file", "ctl_file_write", "tool",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
+ "xsk_reserve_hugepages", "xsk_release_hugepages",
+ "xsk_setup_busy_poll", "xsk_wait_endpoint_exit",
+ "xsk_wait_listen",
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
"ksft_setup", "ksft_variants", "KsftNamedVariant",
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..fd708bb3420d
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/xsk.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+AF_XDP zero-copy tests against an SKB-mode peer.
+
+Each case runs a zero-copy xskxceiver endpoint on NETIF and an SKB-mode one
+on the remote interface. The remote endpoint listens on a TCP control port
+on REMOTE_V4, and the DUT endpoint connects to it once the remote says on
+stderr that it listens. The control connection shares the tested link, and
+the XDP programs of both endpoints pass it to the stack.
+
+The DUT endpoint binds to the last queue and the remote endpoint to queue
+0. Before the cases run, main() takes the DUT queue out of RSS and adds an
+ntuple filter on each host that steers the test UDP flow to the queue of
+the receiving endpoint, and it undoes that after them.
+
+test_xsk runs each case that "xskxceiver -L" lists, in each DUT direction
+listed with it, with the DUT endpoint driven by interrupts, and
+test_xsk_busy_poll runs them again with the DUT endpoint busy polling. The
+variant name starts with the direction of the DUT endpoint. UNALIGNED cases
+are left out without huge pages on both hosts, and TOO_MANY_FRAGS without
+multi-buffer zero-copy on the DUT.
+"""
+
+import re
+import shlex
+import sys
+
+from lib.py import (EthtoolFamily, KsftFailEx, KsftNamedVariant, KsftSkipEx,
+ NetdevFamily, NetDrvEpEnv, cmd, defer, ethtool, ip,
+ ksft_eq, ksft_exit, ksft_run, ksft_variants, rand_port,
+ xsk_release_hugepages, xsk_reserve_hugepages,
+ xsk_setup_busy_poll, xsk_wait_endpoint_exit,
+ xsk_wait_listen)
+
+CASE_TIMEOUT = 120
+# xskxceiver waits as long for its peer to connect.
+LISTEN_TIMEOUT = 30
+# Killing ssh leaves the remote endpoint running, so timeout(1) bounds it.
+REMOTE_TIMEOUT = LISTEN_TIMEOUT + CASE_TIMEOUT
+SKIP_CODE = 4
+
+
+def _get_cases(cfg):
+ """
+ each test case from xskxceiver -L comes out in a form below:
+ 0: SEND_RECEIVE rx
+ 0: SEND_RECEIVE tx
+ """
+ ret = []
+ out = cmd([str(cfg.bin_local), "-L"]).stdout
+ cases = re.findall(r"^(\d+): (\w+) (rx|tx)$", out, re.MULTILINE)
+ for test_id, name, direction in cases:
+ if "UNALIGNED" in name and not cfg.hugepages:
+ continue
+ if "TOO_MANY_FRAGS" in name and cfg.max_frags <= 1:
+ continue
+ ret.append(KsftNamedVariant(f"{direction}_{name}", direction,
+ int(test_id)))
+ return ret
+
+
+def _reserve_queue(cfg):
+ """Take the last queue with both an RX and a TX ring out of RSS. A TX
+ endpoint fills no RX buffers, so in zero-copy mode its queue drops what
+ it receives, the control connection included."""
+ ch = cfg.ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})
+ count = ch.get("combined-count", 0) + min(ch.get("rx-count", 0),
+ ch.get("tx-count", 0))
+ if count < 2:
+ return None
+ queue = count - 1
+ ethtool(f"-X {cfg.ifname} equal {queue}")
+ return queue
+
+
+def _steer_udp(cfg, ifname, port, queue, host=None):
+ """Steer the test flow to queue, return False if ifname cannot."""
+ ntuple = ethtool(f"-k {ifname}", json=True, host=host)[0]["ntuple-filters"]
+ if not ntuple["active"]:
+ if ntuple["fixed"]:
+ print(f"1..0 # SKIP {ifname} does not support ntuple-filters")
+ return False
+
+ ethtool(f"-K {ifname} ntuple-filters on", host=host)
+
+ out = ethtool(f"-N {ifname} flow-type udp4 dst-port {port} action {queue}",
+ host=host).stdout
+ rule = re.search(r"ID (\d+)", out).group(1)
+ cfg.rules.append((host, ifname, rule, ntuple["active"]))
+ return True
+
+
+def _unsteer_udp(cfg):
+ for host, ifname, rule, ntuple in cfg.rules:
+ ethtool(f"-N {ifname} delete {rule}", host=host)
+ if not ntuple:
+ ethtool(f"-K {ifname} ntuple-filters off", host=host)
+
+
+def _setup(cfg, busy_poll):
+ for link in (cfg.dev, cfg.remote_dev):
+ if "xdp" in link:
+ raise KsftSkipEx(f"{link['ifname']} already has an XDP program")
+ if busy_poll:
+ xsk_setup_busy_poll(cfg.ifname)
+
+ # Defers run in reverse, so the XDP programs, which can cap the MTU, go
+ # first.
+ fallbacks = [
+ defer(ip, f"link set dev {cfg.ifname} mtu {cfg.dev['mtu']}"),
+ defer(ip, f"link set dev {cfg.remote_ifname} "
+ f"mtu {cfg.remote_dev['mtu']}", host=cfg.remote),
+ defer(ip, f"link set dev {cfg.ifname} xdpdrv off"),
+ defer(ip, f"link set dev {cfg.remote_ifname} xdpgeneric off",
+ host=cfg.remote),
+ ]
+ return fallbacks
+
+
+def _fixup_args(cfg, direction, test_id, ctrl_port):
+ src, dst = cfg.addr_v["4"], cfg.remote_addr_v["4"]
+ if direction == "rx":
+ src, dst = dst, src
+ cfg.args[cfg.args.index("-t") + 1] = str(test_id)
+ cfg.args[cfg.args.index("-P") + 1] = str(ctrl_port)
+ cfg.args[cfg.args.index("--udp-src") + 1] = src
+ cfg.args[cfg.args.index("--udp-dst") + 1] = dst
+
+
+def _run(cfg, local_args, remote_args, ctrl_port):
+ """Start the remote endpoint, and the DUT one once the remote listens.
+ Return their exit codes, None for a DUT endpoint that did not start."""
+ remote = cmd(shlex.join(remote_args), host=cfg.remote, background=True,
+ fail=False)
+ head = ""
+ dut_ret = None
+ try:
+ head, listening = xsk_wait_listen(remote, ctrl_port, LISTEN_TIMEOUT)
+ if listening:
+ local = cmd(local_args, background=True, fail=False)
+ dut_ret = xsk_wait_endpoint_exit(local, "dut", CASE_TIMEOUT)
+ finally:
+ # Even if the DUT endpoint did not run, so that the remote one is
+ # gone before the next case starts.
+ remote_ret = xsk_wait_endpoint_exit(remote, "remote", REMOTE_TIMEOUT,
+ head)
+ return dut_ret, remote_ret
+
+
+def _test(cfg, direction, test_id, busy_poll):
+ cfg.require_ipver("4")
+ ctrl_port = rand_port()
+
+ fallbacks = _setup(cfg, busy_poll)
+ _fixup_args(cfg, direction, test_id, ctrl_port)
+
+ remote = [*cfg.remote_args,
+ "-e", "tx" if direction == "rx" else "rx",
+ *cfg.args]
+ local = [*cfg.local_args,
+ "-e", direction,
+ *(["-b"] if busy_poll else []),
+ *cfg.args]
+
+ dut_ret, remote_ret = _run(cfg, local, remote, ctrl_port)
+ if dut_ret is None:
+ raise KsftFailEx(f"remote endpoint did not listen on port {ctrl_port}")
+ if {dut_ret, remote_ret} <= {0, SKIP_CODE}:
+ # Both endpoints detached their XDP programs and restored the MTU.
+ for fallback in fallbacks:
+ fallback.cancel()
+ # Each endpoint sees only its own device and mode, so one of them can
+ # skip a case that the other runs, which then fails as its peer is gone.
+ if SKIP_CODE in (dut_ret, remote_ret):
+ raise KsftSkipEx("xskxceiver skipped the case")
+ ksft_eq(dut_ret, 0, "DUT endpoint exit code")
+ ksft_eq(remote_ret, 0, "remote endpoint exit code")
+
+
+def test_xsk(cfg, direction, test_id):
+ _test(cfg, direction, test_id, busy_poll=False)
+
+
+def test_xsk_busy_poll(cfg, direction, test_id):
+ _test(cfg, direction, test_id, busy_poll=True)
+
+
+def main():
+ # ksft_run() exits on -l and -h before main() can undo the setup.
+ if "-l" in sys.argv or "-h" in sys.argv:
+ sys.exit(f"{sys.argv[0]}: -l and -h are not supported")
+
+ with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+ cfg.bin_local = cfg.net_lib_dir / "xskxceiver"
+ if not cfg.bin_local.exists():
+ print(f"1..0 # SKIP {cfg.bin_local} was not built")
+ sys.exit(SKIP_CODE)
+
+ cfg.ethnl = EthtoolFamily()
+ cfg.netnl = NetdevFamily()
+
+ dev = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
+ if "xsk-zerocopy" not in dev["xdp-features"]:
+ print("1..0 # SKIP NIC does not advertise AF_XDP zero-copy")
+ sys.exit(SKIP_CODE)
+
+ cfg.max_frags = dev.get("xdp-zc-max-segs", 1)
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+ cfg.queue = _reserve_queue(cfg)
+ if cfg.queue is None:
+ print("1..0 # SKIP Test requires NETIF with at least 2 channels")
+ sys.exit(SKIP_CODE)
+ cfg.udp_port = rand_port()
+ cfg.rules = []
+
+ if not _steer_udp(cfg, cfg.ifname, cfg.udp_port, cfg.queue) or \
+ not _steer_udp(cfg, cfg.remote_ifname, cfg.udp_port, 0, cfg.remote):
+ _unsteer_udp(cfg)
+ ethtool(f"-X {cfg.ifname} default")
+ sys.exit(SKIP_CODE)
+
+ saved_hugepages = []
+ cfg.hugepages = (xsk_reserve_hugepages(saved_hugepages) and
+ xsk_reserve_hugepages(saved_hugepages, cfg.remote))
+
+ cases = _get_cases(cfg)
+
+ # _fixup_args() sets the options that differ between the cases.
+ cfg.args = ["--hw",
+ "-p", cfg.remote_addr_v["4"],
+ "--max-frags", str(cfg.max_frags),
+ "--udp-src", cfg.addr_v["4"],
+ "--udp-dst", cfg.remote_addr_v["4"],
+ "--udp-port", str(cfg.udp_port),
+ "-t", "0",
+ "-P", "0"]
+ cfg.remote_args = ["timeout", str(REMOTE_TIMEOUT),
+ str(cfg.bin_remote),
+ "-i", cfg.remote_ifname,
+ "-m", "skb",
+ "--listen",
+ "--queue", "0",
+ "--peer-mac", cfg.dev["address"]]
+ cfg.local_args = [str(cfg.bin_local),
+ "-i", cfg.ifname,
+ "-m", "zc",
+ "--queue", str(cfg.queue),
+ "--peer-mac", cfg.remote_dev["address"]]
+
+ ksft_run([ksft_variants(cases)(test_xsk),
+ ksft_variants(cases)(test_xsk_busy_poll)], args=(cfg,))
+
+ xsk_release_hugepages(saved_hugepages)
+ _unsteer_udp(cfg)
+ ethtool(f"-X {cfg.ifname} default")
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
index 49dac776202b..c2524cacffd3 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.py.
+# AF_XDP test engine shared by net/test_xsk.py 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] 35+ messages in thread
* [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
` (13 preceding siblings ...)
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
@ 2026-10-14 18:45 ` Maciej Fijalkowski
14 siblings, 0 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-14 18:45 UTC (permalink / raw)
To: netdev
Cc: bpf, magnus.karlsson, stfomichev, kuba, pabeni, tushar.vyavahare,
kerneljasonxing, bjorn, Maciej Fijalkowski
Document the per-case veth runner and the two-host hardware setup with
a zero-copy DUT and an SKB-mode remote: how each runner drives the
endpoints, what the hardware runner sets up and restores, what both
hosts need and how to run the cases. Point the xskxceiver header
comment to the new README.
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
.../testing/selftests/net/lib/xsk/README.rst | 166 ++++++++++++++++++
.../selftests/net/lib/xsk/xskxceiver.c | 2 +
2 files changed, 168 insertions(+)
create mode 100644 tools/testing/selftests/net/lib/xsk/README.rst
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..2bbd11711cee
--- /dev/null
+++ b/tools/testing/selftests/net/lib/xsk/README.rst
@@ -0,0 +1,166 @@
+.. 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.py`` 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
+Python runner 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.py
+
+Each case creates its own veth pair and picks a random control port.
+``test_xsk`` runs each case that ``xskxceiver -l`` lists in SKB and DRV
+mode, and ``test_xsk_busy_poll`` runs them again with busy polling.
+``XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF`` is left out, as it leaks page_pool
+pages on veth, and so is ``XDP_ADJUST_TAIL_GROW``, as veth leaves no room
+to grow most frames. The runner reserves 32 huge pages for the UNALIGNED
+cases and restores the count when it ends, and leaves those cases out when
+it cannot reserve them.
+``-t`` selects cases, e.g. ``-t 'test_xsk.drv_*'``. ``-l`` and ``-h`` are
+not supported, as the runner would exit on them before it restores the
+huge-page count. The generic frame format is unchanged.
+
+Hardware zero-copy test
+=======================
+
+``drivers/net/hw/xsk.py`` runs each case that ``xskxceiver -L`` lists, in
+each DUT direction listed with it, and owns the TAP results.
+``test_xsk.h`` groups the cases by the DUT directions in which they run.
+
+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, e.g.
+``test_xsk.rx_SEND_RECEIVE`` and ``test_xsk.tx_SEND_RECEIVE``. 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.
+
+``xsk.py`` starts the remote XSK endpoint first. It listens on a TCP
+control port on ``REMOTE_V4``, and the DUT endpoint connects to it over
+the tested link. 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 listener waits 30 seconds for its peer, and ``xsk.py`` as long
+for that line. 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. Each endpoint sees only its own device and
+mode, so a case that either endpoint skips is reported as skipped. Killing
+an SSH client does not stop the command it runs, so the remote endpoint
+runs under ``timeout(1)``, and ``xsk.py`` waits for it to exit before the
+case ends.
+
+Both XSK endpoints generate and validate raw IPv4/UDP frames with one UDP
+port, which ``xsk.py`` picks at random for the run, so ntuple rules can
+steer the test flow. They do not use AF_INET sockets. ``xsk.py`` takes
+the last DUT queue that has both an RX and a TX ring out of RSS and binds
+the DUT endpoint to it. For DUT RX cases it steers the test flow to that
+queue, and for DUT TX cases to queue 0 on the remote receiver. A TX-only
+zero-copy socket fills no RX buffers, so its queue drops what it
+receives, and TX cases keep that queue out of RSS as well.
+The XDP programs redirect only the test flow and pass all other traffic,
+the control connection included, to the stack, so the tested link does
+not have to be otherwise idle.
+
+``xsk.py`` sets up the queue, the ntuple rules and huge pages for the
+UNALIGNED cases once, before the cases run, and undoes them when the run
+ends. The run is skipped when either host has no ntuple support,
+and the UNALIGNED cases are left out when either host cannot
+reserve the huge pages. Each endpoint detaches its XDP program and
+restores any changed ring sizes and the MTU before it exits. Cases that
+do not need larger frames default to an MTU of 1500 (``ETH_DATA_LEN``),
+and the SKB-mode remote endpoint only raises its MTU when a case needs a
+larger one, as changing the MTU can reset the NIC. Once both endpoints
+are ready, each waits for its link before sending, as such a reset can
+take the link down on both hosts. The runner gives the queue back to RSS
+with ``ethtool -X default`` and restores the ntuple setting and the
+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 DUT with fewer than two queues that have both rings
+skips all cases, as it has no queue to take out of 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``).
+The framework runs the remote commands for either backend. Both endpoints
+run as root.
+
+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=netns
+ REMOTE_ARGS=xsk-peer
+
+``REMOTE_ARGS`` names the namespace that holds the peer port. With eth0
+connected back to back to eth1 of the same host, set it up as root::
+
+ ip netns add xsk-peer
+ ip link set eth1 netns xsk-peer
+ ip -n xsk-peer link set lo up
+ ip -n xsk-peer addr add 192.0.2.2/24 dev eth1
+ ip -n xsk-peer link set eth1 up
+ ip addr add 192.0.2.1/24 dev eth0
+ ip link set eth0 up
+
+Run as root on the DUT::
+
+ cd tools/testing/selftests/drivers/net/hw
+ sudo ./xsk.py -t test_xsk.rx_SEND_RECEIVE
+
+``xskxceiver -L`` lists the cases. ``xsk.py`` does not support ``-l`` and
+``-h``, as it would exit on them before it undoes the setup of both hosts.
+Omit ``-t`` to run all cases.
+Set the variables in ``net.config`` when using ``sudo`` so they are
+available to the test process.
+
+``test_xsk`` runs each case with the DUT endpoint driven by interrupts.
+``test_xsk_busy_poll`` runs the same cases again with the DUT endpoint
+busy polling as in the busy-poll pass of ``test_xsk.py``, e.g.
+``-t 'test_xsk_busy_poll.*'``. Each of its cases sets
+``napi_defer_hard_irqs`` and ``gro_flush_timeout`` on the DUT to the
+values ``test_xsk.py`` 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. 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, BPF and ``timeout(1)``. In a
+namespace, the remote endpoint runs the DUT's ``xskxceiver`` binary. With
+SSH, ``xsk.py`` copies that binary to the remote, so both hosts must have
+compatible architectures and libraries. The DUT needs RSS support, and
+both hosts need ntuple support. The test uses ``NetDrvEpEnv`` and the
+networking selftest Python helpers for setup and rollback.
+
+The hardware list covers baseline traffic, 2K frames, single packets,
+poll, headroom, invalid TX descriptors, socket statistics, XDP program
+cleanup, XDP drop, metadata, tail adjustment, multi-buffer, 9K,
+unaligned traffic, HW ring sizes, and socket teardown.
+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. A DUT without
+multi-buffer zero-copy does not list the case.
diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
index 7705a9f2b7a1..1d740d7a5e7e 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.py 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] 35+ messages in thread
* Re: [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
@ 2026-10-15 8:14 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:14 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 TX execution, RX setup and RX validation into synchronous helpers
Maybe it's my rustified brain was messed up by "synchronous helpers" --
"extract the work into functions that return normally" ;-)
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
@ 2026-10-15 8:15 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
1 sibling, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:15 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:
> HW_SW_MAX_RING_SIZE gives the RX UMEM 8k frames and 8k rings, then
> sends batches of 2k and of the maximum HW TX ring size minus 8. This
> only works in the single-interface mode, where the TX socket uses the
> RX UMEM, and veth, which cannot change its ring sizes, skips the case.
> With two interfaces, TX has its own UMEM of DEFAULT_UMEM_BUFFERS (1k)
> frames and 2k rings: the in-flight limit in __send_pkts() turns
> negative for a 2k batch, so TX never sends and RX times out, and the
> second batch would not fit in the TX ring anyway.
>
> The next patch removes the single-interface mode, so size the TX UMEM
> like the RX one. The shared UMEM setup copies the RX UMEM over the TX
> one and is not affected.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
@ 2026-10-15 8:17 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:17 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, used for HW tests, bound both sockets to
> queue 0 of one interface, which the kernel only allows with a shared
> UMEM. With the endpoints about to become separate processes, they would
> have to share the UMEM mapping and the socket fd.
>
> The mode also relied on the interface being set up by hand with a
> single queue and promiscuous mode, which does not suit CI. The hardware
> test added later in this series steers its traffic with ntuple and RSS
> configuration instead.
>
> 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>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
@ 2026-10-15 8:18 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8: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:
> prog_tests/xsk.c runs the xskxceiver cases inside test_progs, driving
> the TX and RX sockets of a veth pair from two threads. The following
> patches turn each endpoint into a process of its own that can run
> against a peer on another host. Keeping the wrapper would mean rewiring
> it at every step of that split and teaching test_progs to spawn and
> synchronize peer processes, so drop it. BPF CI loses the ns_xsk_skb and
> ns_xsk_drv entries; the same veth coverage stays with test_xsk.sh,
> which moves to selftests/net together with the engine.
>
> ci_skip_tests[] held the cases that the wrapper left out: flaky and
> slow ones and those that need hugepages or HW ring size support.
> Nothing runs only one of the lists anymore, so fold ci_skip_tests[]
> into tests[]. The test IDs do not change.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
@ 2026-10-15 8:20 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:20 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:
> 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 with $(BPFTOOL),
> which defaults to the bpftool in PATH. A failing bpftool leaves no
> partial header behind for a later build to take 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>
This could potentially be squashed into the next patch.
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
@ 2026-10-15 8:22 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:22 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 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 take shared helpers from.
>
> net/lib builds xskxceiver 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; test_xsk.sh then skips too. xskxceiver links -lpthread,
> which glibc older than 2.34 needs for the threads of test_xsk.c.
>
> Like the rest of net/lib, xskxceiver builds against the installed uapi
> headers, so include linux/kernel.h for min_t() directly rather than via
> the tools copy of linux/netlink.h. tools/include goes ahead of CFLAGS,
> so that O= builds take linux/kernel.h and linux/types.h from it rather
> than from a source tree usr/include.
>
> Move the two ethtool ring helpers from network_helpers into xsk.c 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 xdp_metadata test keep using xsk.c and xsk.h
> from their new location. Update the AF_XDP entry in MAINTAINERS.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
@ 2026-10-15 8:27 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:27 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:
> A later patch runs the TX and RX endpoints of a case as separate
> processes, possibly on different hosts, each binding only its own
> interface. The shared test code still looks at the capabilities of both
> ifobjects, e.g. to check both for multi-buffer support or to size the
> RX batch from the TX ring limit. As the endpoints do not exchange
> capabilities, a process will represent the other side by a shadow
> ifobject that mirrors the local ones.
>
> Prepare for that by replacing the per-interface support booleans and
> limits in struct ifobject with one struct xsk_caps, so that mirroring
> takes a single struct assignment and a capability added later cannot
> be left out of the copy. The maximum TX ring size, which comes from the
> ethtool ring parameters that a shadow does not mirror, is copied there
> too. ifobj_has_cap() and ifobj_set_cap() test and set the flags, and
> xsk_get_cap() reads a limit from the TX ifobject, the only one that has
> all of them set, as the open-coded reads did.
>
> The unaligned UMEM check 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 together.
>
> 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.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
> @@ -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)
nit, and iff respin: I'd leave the whole inlining out. Keep it simple.
Also, please functions instead of macros.
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
@ 2026-10-15 8:29 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:29 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:
> Prepare xskxceiver for running a single endpoint per process by
> untangling main():
>
> - bind_iface() sets up one interface from its -i argument, so that
> interfaces can get their roles after parsing; -l needs none
> - detect_mode_caps() and detect_ifobj_caps() record DRV, zero-copy and
> HW ring support as XSK_CAP flags, which mode_supported() checks
> - run_pkt_test() returns the result, which main() counts instead of
> test->fail, as not every failing path sets it
> - cleanup_iface() restores rings, detaches and unloads one interface
> and copes with partially initialized objects, as does
> pkt_stream_delete(NULL), so main() has a single exit path
>
> As the rings of both interfaces now get restored at exit,
> hw_ring_size_reset() skips rings that are already at their defaults,
> as the SIOCETHTOOL path passes such no-op requests on to the driver.
> When the request fails, it keeps the sizes the NIC still has, so that
> a later reset tries again.
>
> Call ksft_print_header(), which also switches stdout to line buffering,
> right after parsing the command line: xskxceiver never called it, so a
> launcher reading through a pipe only saw the output at exit. -l prints
> no TAP header and exits with 0 instead of KSFT_XPASS, as listing the
> tests is not a test result.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
@ 2026-10-15 8:34 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
1 sibling, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 8:34 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:
> Replace test_xsk.sh and xsk_prereqs.sh with test_xsk.py, which runs
> xskxceiver once per mode and case, so that every case starts from a
> fresh process and a crash fails only its own case. A single case is
> also the unit that the next patch splits into RX and TX processes.
>
> xskxceiver now requires -m and -t, runs exactly that case, reports one
> KTAP result and exits with its verdict. An unsupported mode skips the
> case instead of exiting with XFAIL.
>
> test_xsk.py uses the networking selftests Python library, as does the
> hardware test added later, so every case is a KTAP case of its own:
> test_xsk runs each case that xskxceiver -l lists in skb and drv mode,
> e.g. test_xsk.drv_SEND_RECEIVE, and test_xsk_busy_poll runs them again
> with busy polling. The usual -t and -T options select cases in place of
> the script's -l, -m and -t, and -v and -d go away, as the output of a
> run that does not pass is logged. Each case runs on a veth pair of its
> own, so an MTU or an XDP program that a crashed case leaves behind
> cannot reach the next one. The pair stays in the current network
> namespace, as only the initial one has the net.core.max_skb_frags
> sysctl that xskxceiver reads. A case is killed and fails after two
> minutes, and the test skips when xskxceiver was not built.
This timeout might need to be easily changable from the cli...
follow-up!
> test_xsk.py reserves 32 huge pages for the UNALIGNED cases, restores
> the count when it ends and leaves those cases out when it cannot
> reserve them. ksft_run() exits on -l and -h before the count is
> restored, so test_xsk.py rejects both. The huge page, busy-poll and
> endpoint wait helpers, which the hardware test shares, go to
> net/lib/py/xsk.py, which joins the AF_XDP entry in MAINTAINERS.
>
> test_xsk.py leaves out two cases that test_progs skipped as flaky:
>
> - XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF: bpf_xdp_adjust_tail() frees the
> trimmed fragment as the memory type of the XDP RX queue, which veth
> and generic XDP leave at MEM_TYPE_PAGE_SHARED for frames that they
> copy into page_pool pages, so the page leaks from its pool and the
> pool's shutdown stalls
> - XDP_ADJUST_TAIL_GROW: it skips in skb mode, and in drv mode veth
> copies each frame into a page_pool fragment that leaves no room to
> grow the 128-byte frames of the case unless it is the last one of its
> page, so the XDP program drops most frames
>
> Drop the xskxceiver.c note on enabling packet dumps with -D in
> test_xsk.sh; xskxceiver has no such option and dumps packets on
> validation errors.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
[...]
> tools/testing/selftests/net/test_xsk.py | 140 +++++++++++
> tools/testing/selftests/net/test_xsk.sh | 230 ------------------
Yum! Nice!
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
@ 2026-10-15 9:04 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 9:04 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:
> Run the two endpoints of a test case as separate xskxceiver processes,
> so that a later patch can run them on two hosts. 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 a loaded copy of the XDP skeleton, so the shared
> test code keeps addressing both sides while only the local interface is
> programmed. Once BIDIRECTIONAL swaps the directions, the local
> interface runs the shadow's programs.
>
> The pthreads, the barrier, test->fail and -lpthread go away. Each
> process runs the worker of its local ifobject, and the endpoints
> exchange three fixed-size messages:
>
> - READY, exchanged before each step, 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 as not to overrun the RX UMEM
> - ABORT tells the other endpoint to stop waiting after a failure
>
> TX listens and RX connects. The listener says on stderr when it
> listens, so the launcher starts the connector then rather than polling
> for the port, which could also find a foreign listener. The listener
> waits 30s for the connector, and -P takes a port number. As with the
> control channels of other selftests, the hosts are trusted and the
> channel is not authenticated. Test selection, interface capabilities
> and verdicts stay with the launcher.
>
> test_xsk.py starts TX on a random control port on 127.0.0.1 and RX
> once TX listens, and kills TX and fails the case when it does not
> listen within 30s. The case passes when both endpoints pass, and is
> skipped when one skips and neither fails. Only the output of an
> endpoint that does not pass is logged, prefixed with its role.
> xsk_wait_listen(), which reads the TX stderr up to the listen line,
> joins the library helpers, and xsk_wait_endpoint_exit() logs that
> output along with the rest.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
> tools/testing/selftests/net/lib/Makefile | 3 +-
> .../testing/selftests/net/lib/py/__init__.py | 4 +-
> tools/testing/selftests/net/lib/py/xsk.py | 35 ++-
> .../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 | 293 ++++++++++++++++++
> .../testing/selftests/net/lib/xsk/xsk_peer.h | 19 ++
> .../selftests/net/lib/xsk/xskxceiver.c | 136 ++++++--
> tools/testing/selftests/net/test_xsk.py | 68 ++--
> 9 files changed, 633 insertions(+), 153 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 5c1612315062..49dac776202b 100644
> --- a/tools/testing/selftests/net/lib/Makefile
> +++ b/tools/testing/selftests/net/lib/Makefile
> @@ -38,13 +38,12 @@ 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)
>
> PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
> XSK_LDLIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -lz
> -XSK_LDLIBS += -lpthread
>
> $(XSK_BPF_OBJ): $(XSK_DIR)/xsk_xdp_progs.bpf.c $(XSK_DIR)/xsk_xdp_common.h $(BPFOBJ)
> $(call msg,BPF_PROG,,$@)
> diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
> index 1a93ab8b1e60..a82bac8a6b47 100644
> --- a/tools/testing/selftests/net/lib/py/__init__.py
> +++ b/tools/testing/selftests/net/lib/py/__init__.py
> @@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
> ctl_file_write, wait_file, tool, tc
> from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
> from .xsk import xsk_reserve_hugepages, xsk_release_hugepages, \
> - xsk_setup_busy_poll, xsk_wait_endpoint_exit
> + xsk_setup_busy_poll, xsk_wait_endpoint_exit, xsk_wait_listen
> from .ynl import NlError, NlctrlFamily, YnlFamily, \
> EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily
> from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
> @@ -34,7 +34,7 @@ __all__ = ["KSRC",
> "wait_port_listen", "ctl_file_write", "wait_file", "tool", "tc",
> "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
> "xsk_reserve_hugepages", "xsk_release_hugepages",
> - "xsk_setup_busy_poll", "xsk_wait_endpoint_exit",
> + "xsk_setup_busy_poll", "xsk_wait_endpoint_exit", "xsk_wait_listen",
> "NetdevSim", "NetdevSimDev",
> "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
> "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
> diff --git a/tools/testing/selftests/net/lib/py/xsk.py b/tools/testing/selftests/net/lib/py/xsk.py
> index 50985bcb5b80..6f4ffc4786d2 100644
> --- a/tools/testing/selftests/net/lib/py/xsk.py
> +++ b/tools/testing/selftests/net/lib/py/xsk.py
> @@ -5,9 +5,10 @@ Helpers to run xskxceiver, the AF_XDP test program in net/lib/xsk.
> """
>
> import subprocess
> +import time
>
> from .ksft import ksft_pr
> -from .utils import cmd, ctl_file_write
> +from .utils import cmd, ctl_file_write, fd_read_timeout
>
> NR_HUGEPAGES = 32
>
> @@ -41,10 +42,32 @@ def xsk_setup_busy_poll(ifname):
> ctl_file_write(f"{sysfs}/gro_flush_timeout", 200000)
>
>
> -def xsk_wait_endpoint_exit(endpoint, label, timeout):
> - """Wait up to timeout seconds for xskxceiver to exit, kill it if it does
> - not, and return its exit code. Log its output, a KTAP stream of its own,
> - prefixed with label, only if it did not pass."""
> +def xsk_wait_listen(endpoint, port, timeout):
> + """Wait up to timeout seconds for an xskxceiver endpoint to print on
> + stderr that it listens on the control port. Unlike polling for the port,
> + this never takes a foreign listener for it and costs no SSH login per
> + try on a remote host. Return the stderr read so far and whether that
> + line came."""
> + line = f"Listening for XSK peer on port {port}\n".encode()
> + fd = endpoint.proc.stderr.fileno()
> + head = b""
> + deadline = time.monotonic() + 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 xsk_wait_endpoint_exit(endpoint, label, timeout, head=""):
> + """Wait up to timeout seconds for an xskxceiver endpoint to exit, kill it
> + if it does not, and return its exit code. Log its output, a KTAP stream
> + of its own, prefixed with label, only if it did not pass. head is the
> + start of its stderr, if xsk_wait_listen() took it off the pipe."""
> try:
> endpoint.process(terminate=False, fail=False, timeout=timeout)
> except subprocess.TimeoutExpired:
> @@ -52,7 +75,7 @@ def xsk_wait_endpoint_exit(endpoint, label, timeout):
> endpoint.process(terminate=False, fail=False)
> ksft_pr(f"killed after {timeout}s", line_pfx=f"{label}|")
> if endpoint.ret:
> - for out in (endpoint.stdout, endpoint.stderr):
> + for out in (endpoint.stdout, head + endpoint.stderr):
> if out:
> ksft_pr(out.rstrip(), line_pfx=f"{label}|")
> return endpoint.ret
> diff --git a/tools/testing/selftests/net/lib/xsk/test_xsk.c b/tools/testing/selftests/net/lib/xsk/test_xsk.c
> index e4e0d4dfab5a..f67a32c0260a 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
> @@ -256,7 +283,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;
> @@ -264,7 +292,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;
> @@ -332,26 +359,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) {
> @@ -1001,6 +1033,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;
> @@ -1100,9 +1135,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;
> @@ -1178,9 +1212,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;
> @@ -1262,9 +1305,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;
> @@ -1341,9 +1382,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)
> @@ -1665,36 +1703,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)
> @@ -1749,13 +1808,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))
> @@ -1768,7 +1827,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++)
> @@ -1780,15 +1839,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;
>
> @@ -1819,51 +1878,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)
> @@ -1879,7 +1914,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;
> }
> @@ -1912,7 +1948,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;
> @@ -1951,6 +1987,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;
> @@ -2299,7 +2338,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))
> @@ -2357,12 +2396,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..e3e353d6abd9
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/xsk/xsk_peer.c
> @@ -0,0 +1,293 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <netdb.h>
> +#include <netinet/tcp.h>
> +#include <poll.h>
> +#include <stdio.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. As with the control
> + * channels of other selftests, the test hosts are trusted: nothing
> + * authenticates the peer.
> + */
> +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;
> + u32 pkts;
> + 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:
> + /* xsk_peer_tx_sync() returns the sum as an int. */
> + pkts = ntohl(msg.pkts);
> + if (pkts > INT_MAX - peer->acked)
> + return -EPROTO;
> + peer->acked += 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);
> + /*
> + * Leave closed to peer_drain(), which first reads what the
> + * peer sent before it went away, such as an ABORT.
> + */
> + if (err && !peer_gone(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 listener says on stderr when it listens, and the launcher waits for
> + * that line before it starts 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,
> + .ai_flags = AI_NUMERICSERV,
> + };
> + 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)) {
> + fprintf(stderr, "Listening for XSK peer on port %s\n", port);
> + 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 09ad623333be..769718cad8cd 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.py 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.py 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
> */
>
> @@ -87,6 +89,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"
> @@ -95,8 +98,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)
> {
> @@ -157,6 +170,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}
> };
> @@ -164,7 +180,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"
> @@ -172,16 +188,18 @@ static void print_usage(char **argv)
> " -m, --mode Mode to run: skb, drv or zc\n"
> " -l, --list List all available tests\n"
> " -t, --test Test case to run, a number from -l\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);
> @@ -193,7 +211,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();
> }
> @@ -208,26 +226,33 @@ 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)
> {
> - 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;
> @@ -256,17 +281,31 @@ 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':
> + parse_u32(optarg, 1, UINT16_MAX, argv);
If you respin; This is useless. Remove this and the function. We'll
catch an incorrect port elsewhere.
Reviewed-by: Björn Töpel <bjorn@kernel.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
@ 2026-10-15 9:42 ` Björn Töpel
2026-10-15 10:55 ` Maciej Fijalkowski
0 siblings, 1 reply; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 9:42 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:
> drivers/net/hw/xsk.py, added later in this series, runs each hardware
> AF_XDP case as a zero-copy DUT endpoint and an SKB-mode remote endpoint
> over a physical link, without negotiating capabilities between them.
> Add what such a run needs to xskxceiver:
>
> - --hw sends UDP/IPv4 test packets (--udp-src, --udp-dst, --udp-port)
> between the real interface MACs (--peer-mac); XDP_SHARED_UMEM, whose
> program picks the socket by a synthetic destination MAC, skips
This reads weird to me.
I wonder if we can avoid introducing a separate "hardware" mode here? It
seems to bundle packet format, link handling, and assumptions about the
endpoint modes. Those are separate concerns, and much of this should
work with veth too, no?
Could we first make the real MAC addresses and IPv4/UDP format common to
both veth and physical-device runs?
Then --listen could work independently, timeouts could be configurable,
and test selection could reflect the actual endpoint modes and
capabilities. For example, skipping TX_QUEUE_CONSUMER because hw_test
implies mixed ZC/SKB endpoints seems to encode the launcher's current
configuration into the test itself. "Real" endpoints could also run the
same mode.
If we need to keep both packet formats, something like
--packet-format=udp would maybe describe it better. I'd prefer keeping
these behaviors independent rather than giving the combined mode another
name.
Also, we need IPv6 support here in addition to IPv4. You can't assume
that the DUT is onlink or IPv4.
Björn
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
@ 2026-10-15 10:05 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 10:05 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:
> The XDP programs of xskxceiver redirect every packet to the XSKMAP and
> drop what no socket takes. On a hardware link, this drops neighbour
> discovery, LLDP or SSH while a program is attached, and on the remote,
> 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
Again, IPv6 as well, please.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
@ 2026-10-15 10:29 ` Björn Töpel
2026-10-15 12:52 ` Maciej Fijalkowski
2026-10-15 18:46 ` sashiko-bot
1 sibling, 1 reply; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 10:29 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:
> Run the AF_XDP cases that a physical link can carry as pairs of
> xskxceiver processes: a zero-copy endpoint on the DUT and an SKB-mode
> endpoint on the remote, which needs no zero-copy support. xsk.py runs
> the cases that xskxceiver -L lists, in the DUT directions listed with
> them, as KTAP variants named <rx|tx>_<CASE>. test_xsk runs them with
> the DUT socket driven by interrupts and test_xsk_busy_poll with it busy
> polling, after xsk_setup_busy_poll() sets napi_defer_hard_irqs and
> gro_flush_timeout on the DUT so that SO_PREFER_BUSY_POLL keeps its IRQs
> masked, as on veth.
>
> main() sets up what the cases share once and undoes it after them, as
> defer() works only within a case, so xsk.py rejects -l and -h, on which
> ksft_run() exits without returning:
>
> - the DUT endpoint binds the last queue with both an RX and a TX ring,
> which is taken out of RSS with ethtool -X equal and handed back with
> ethtool -X default; a TX endpoint fills no RX buffers, so in
> zero-copy mode its queue drops what it receives and TX cases need the
> queue out of RSS as much as RX cases do. A DUT with fewer than two
> such queues skips
> - an ntuple rule on each host steers the test UDP flow to the receiving
> endpoint: the DUT queue in RX cases and queue 0 of the remote, where
> its endpoint binds, in TX cases. A host without ntuple support skips.
> Setting the rules once spares each TX case the ssh round trips of a
> remote rule and toggles ntuple-filters, which resets some NICs, once
> per run rather than per case
> - huge pages are reserved on both hosts, and the UNALIGNED cases are
> left out without them
> - both endpoints get the DUT's xdp-zc-max-segs for TOO_MANY_FRAGS,
> which is left out without multi-buffer zero-copy
>
> A DUT that does not advertise AF_XDP zero-copy skips the run.
>
> The remote endpoint listens on REMOTE_V4, and the DUT endpoint connects
> once the remote says on stderr that it listens. The control connection
> shares the tested link, where the XDP programs pass it to the stack.
> Killing an ssh client does not stop the command it runs, so timeout(1)
> bounds the remote endpoint, and the runner waits for it to exit before
> the case ends. The endpoints are waited for with the test_xsk.py
> helpers, so the output of one that does not pass is logged, timeouts
> included.
>
> The endpoints detach their XDP programs and restore the MTU and ring
> sizes when they exit; the runner detaches the programs and restores the
> MTU only after one fails or is killed. Each endpoint sees only its own
> device and mode, so one may skip a case that the other runs and then
> fails once its peer is gone; such a case is reported as skipped.
>
> xsk.py runs the xskxceiver that net/lib builds, which drv-net already
> installs, and copies it to the remote with cfg.remote.deploy(). Add
> xsk.py to TEST_PROGS and to the AF_XDP entry in MAINTAINERS,
> CONFIG_XDP_SOCKETS to the drivers/net/hw config, and the xskxceiver
> helpers to the drivers/net/hw library exports.
>
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
I just skimmed this, and it seems like it needs some scrutiny on the
cleanup side. The LLM flagged a few cleanup issues. Could you check
these paths?
| main() only restores shared state on the normal path. Exceptions
| during setup can leave RSS, steering rules or hugepage reservations
| changed. Would a run-scoped ExitStack help here?
|
| _steer_udp() enables ntuple filtering before recording its original
| state. If rule insertion fails, that change isn't tracked.
|
| _unsteer_udp() stops on the first error, leaving subsequent cleanup
| undone.
|
| _run() waits for the remote in finally, but doesn't ensure the local
| endpoint is stopped/reaped if its wait is interrupted.
|
| The fallback cleanup restores XDP/MTU, but not ring sizes after an
| endpoint is killed.
|
| "-X default" resets RSS rather than restoring the original table.
|
| An unconditional SKIP when either endpoint skips can hide a genuine
| failure on the other endpoint, including a crash or cleanup failure.
The defer ordering itself looks sensible: detach XDP before restoring
the MTU.
Björn
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver
2026-10-15 9:42 ` Björn Töpel
@ 2026-10-15 10:55 ` Maciej Fijalkowski
2026-10-15 16:03 ` Björn Töpel
0 siblings, 1 reply; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-15 10:55 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
On Thu, Oct 15, 2026 at 11:42:51AM +0200, Björn Töpel wrote:
> Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>
> > drivers/net/hw/xsk.py, added later in this series, runs each hardware
> > AF_XDP case as a zero-copy DUT endpoint and an SKB-mode remote endpoint
> > over a physical link, without negotiating capabilities between them.
> > Add what such a run needs to xskxceiver:
> >
> > - --hw sends UDP/IPv4 test packets (--udp-src, --udp-dst, --udp-port)
> > between the real interface MACs (--peer-mac); XDP_SHARED_UMEM, whose
> > program picks the socket by a synthetic destination MAC, skips
>
> This reads weird to me.
>
> I wonder if we can avoid introducing a separate "hardware" mode here? It
> seems to bundle packet format, link handling, and assumptions about the
> endpoint modes. Those are separate concerns, and much of this should
> work with veth too, no?
>
> Could we first make the real MAC addresses and IPv4/UDP format common to
> both veth and physical-device runs?
I must admit I have not tried that and --hw introduction feels like veth
approach is still a first-class citizen here. Aligning both test runners
would simplify stuff I assume, will look at it.
>
> Then --listen could work independently, timeouts could be configurable,
> and test selection could reflect the actual endpoint modes and
> capabilities.
You select cases based on DUT's endpoint and its capabilities. Regarding
timeouts, we can add a knob for it, sure.
> For example, skipping TX_QUEUE_CONSUMER because hw_test
> implies mixed ZC/SKB endpoints seems to encode the launcher's current
> configuration into the test itself. "Real" endpoints could also run the
> same mode.
This test has nothing to do with ZC at all, Jason added this during one of
the many xsk_generic_xmit() bugfixes.
https://lore.kernel.org/bpf/20250703141712.33190-3-kerneljasonxing@gmail.com/
Remote endpoint's role is rather limited and I wanted to be sure that we
cover both tx and rx sides from particular test cases, that's the purpose
behind --direction and storing test cases within 'hw_group's.
>
> If we need to keep both packet formats, something like
> --packet-format=udp would maybe describe it better. I'd prefer keeping
> these behaviors independent rather than giving the combined mode another
> name.
Nah, let's make veth runner use what currently 'hw' side does.
>
> Also, we need IPv6 support here in addition to IPv4. You can't assume
> that the DUT is onlink or IPv4.
Is that currently a show-stopper? That is another left-over from old
architecture where data path was rather on a local and isolated
environment... /me finding excuses ;)
>
>
> Björn
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-15 10:29 ` Björn Töpel
@ 2026-10-15 12:52 ` Maciej Fijalkowski
0 siblings, 0 replies; 35+ messages in thread
From: Maciej Fijalkowski @ 2026-10-15 12:52 UTC (permalink / raw)
To: Björn Töpel
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
On Thu, Oct 15, 2026 at 12:29:30PM +0200, Björn Töpel wrote:
> Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>
> > Run the AF_XDP cases that a physical link can carry as pairs of
> > xskxceiver processes: a zero-copy endpoint on the DUT and an SKB-mode
> > endpoint on the remote, which needs no zero-copy support. xsk.py runs
> > the cases that xskxceiver -L lists, in the DUT directions listed with
> > them, as KTAP variants named <rx|tx>_<CASE>. test_xsk runs them with
> > the DUT socket driven by interrupts and test_xsk_busy_poll with it busy
> > polling, after xsk_setup_busy_poll() sets napi_defer_hard_irqs and
> > gro_flush_timeout on the DUT so that SO_PREFER_BUSY_POLL keeps its IRQs
> > masked, as on veth.
> >
> > main() sets up what the cases share once and undoes it after them, as
> > defer() works only within a case, so xsk.py rejects -l and -h, on which
> > ksft_run() exits without returning:
> >
> > - the DUT endpoint binds the last queue with both an RX and a TX ring,
> > which is taken out of RSS with ethtool -X equal and handed back with
> > ethtool -X default; a TX endpoint fills no RX buffers, so in
> > zero-copy mode its queue drops what it receives and TX cases need the
> > queue out of RSS as much as RX cases do. A DUT with fewer than two
> > such queues skips
> > - an ntuple rule on each host steers the test UDP flow to the receiving
> > endpoint: the DUT queue in RX cases and queue 0 of the remote, where
> > its endpoint binds, in TX cases. A host without ntuple support skips.
> > Setting the rules once spares each TX case the ssh round trips of a
> > remote rule and toggles ntuple-filters, which resets some NICs, once
> > per run rather than per case
> > - huge pages are reserved on both hosts, and the UNALIGNED cases are
> > left out without them
> > - both endpoints get the DUT's xdp-zc-max-segs for TOO_MANY_FRAGS,
> > which is left out without multi-buffer zero-copy
> >
> > A DUT that does not advertise AF_XDP zero-copy skips the run.
> >
> > The remote endpoint listens on REMOTE_V4, and the DUT endpoint connects
> > once the remote says on stderr that it listens. The control connection
> > shares the tested link, where the XDP programs pass it to the stack.
> > Killing an ssh client does not stop the command it runs, so timeout(1)
> > bounds the remote endpoint, and the runner waits for it to exit before
> > the case ends. The endpoints are waited for with the test_xsk.py
> > helpers, so the output of one that does not pass is logged, timeouts
> > included.
> >
> > The endpoints detach their XDP programs and restore the MTU and ring
> > sizes when they exit; the runner detaches the programs and restores the
> > MTU only after one fails or is killed. Each endpoint sees only its own
> > device and mode, so one may skip a case that the other runs and then
> > fails once its peer is gone; such a case is reported as skipped.
> >
> > xsk.py runs the xskxceiver that net/lib builds, which drv-net already
> > installs, and copies it to the remote with cfg.remote.deploy(). Add
> > xsk.py to TEST_PROGS and to the AF_XDP entry in MAINTAINERS,
> > CONFIG_XDP_SOCKETS to the drivers/net/hw config, and the xskxceiver
> > helpers to the drivers/net/hw library exports.
> >
> > Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
>
> I just skimmed this, and it seems like it needs some scrutiny on the
> cleanup side. The LLM flagged a few cleanup issues. Could you check
> these paths?
>
> | main() only restores shared state on the normal path. Exceptions
> | during setup can leave RSS, steering rules or hugepage reservations
> | changed. Would a run-scoped ExitStack help here?
If there would be no objections to ExitStack usage, then I'm gonna do it
> |
> | _steer_udp() enables ntuple filtering before recording its original
> | state. If rule insertion fails, that change isn't tracked.
> |
> | _unsteer_udp() stops on the first error, leaving subsequent cleanup
> | undone.
dang you nit-picking AI; these are valid tho; maybe these come from a
desire to setup steering once, not per each test-case, so I couldn't have
a matching defer()...
For gro_stats.py it's not a big deal but here we run ~80 test cases, so
pulling out things that can be configured once per ksft_run() is important
from execution time POV.
> |
> | _run() waits for the remote in finally, but doesn't ensure the local
> | endpoint is stopped/reaped if its wait is interrupted.
> |
> | The fallback cleanup restores XDP/MTU, but not ring sizes after an
> | endpoint is killed.
this one is also valid as cleanup_iface() wouldn't be reached - i assume
it would be nicer to setup ring sizes from python side now; i could get
back at passing the test name and based on that issue ethtool calls rather
than plain ioctls from C. We might pass the info of ring size via flag
when calling xskxceiver.
I'll see if this simplify anything or make things worse.
> |
> | "-X default" resets RSS rather than restoring the original table.
It means someone else could have modified the table before reserving the
last queue - should we respect it? But that sounds to me like other tests
would be doing bad job at cleaning up after themselves. My assumption was
that we start with clean state.
> |
> | An unconditional SKIP when either endpoint skips can hide a genuine
> | failure on the other endpoint, including a crash or cleanup failure.
>
> The defer ordering itself looks sensible: detach XDP before restoring
> the MTU.
>
>
> Björn
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver
2026-10-15 10:55 ` Maciej Fijalkowski
@ 2026-10-15 16:03 ` Björn Töpel
0 siblings, 0 replies; 35+ messages in thread
From: Björn Töpel @ 2026-10-15 16:03 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: netdev, bpf, magnus.karlsson, stfomichev, kuba, pabeni,
tushar.vyavahare, kerneljasonxing
Maciej Fijalkowski <maciej.fijalkowski@intel.com> writes:
>> Also, we need IPv6 support here in addition to IPv4. You can't assume
>> that the DUT is onlink or IPv4.
>
> Is that currently a show-stopper? That is another left-over from old
> architecture where data path was rather on a local and isolated
> environment... /me finding excuses ;)
People still run that legacy IP protocol? :-P
More seriously -- my homelab, and our internal labs are v6 only...
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
2026-10-15 8:15 ` Björn Töpel
@ 2026-10-15 18:46 ` sashiko-bot
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-15 18:46 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: infinite loop if hardware TX ring > 8200
--
commit 183d9676e829720fbcf94af5e89e100b2685ba42
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE
This patch configures the TX UMEM sizes in the testapp_hw_sw_max_ring_size
selftest to match the RX UMEM sizing. It addresses an issue where TX
would fail in two-interface mode due to the default UMEM being too small
for the batch sizes used in this test.
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
> index 2a13628740924..16fe32e5afc06 100644
> --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
> @@ -2513,6 +2513,9 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test)
> test->ifobj_rx->xsk->umem->num_frames = max_descs;
> test->ifobj_rx->xsk->umem->fill_size = max_descs;
> test->ifobj_rx->xsk->umem->comp_size = max_descs;
> + test->ifobj_tx->xsk->umem->num_frames = max_descs;
> + test->ifobj_tx->xsk->umem->fill_size = max_descs;
> + test->ifobj_tx->xsk->umem->comp_size = max_descs;
[Severity: Medium]
Since max_descs is fixed to 8192 (XSK_RING_PROD__DEFAULT_NUM_DESCS * 4), can
this cause an infinite loop in __send_pkts() on hardware with large rings?
Further down in testapp_hw_sw_max_ring_size(), the batch size is explicitly set
based on the raw hardware maximums:
test->ifobj_tx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8;
If a NIC supports a maximum TX ring size greater than 8200 (such as some mlx5
or bnxt cards), the calculated batch_size will strictly exceed the software
ring capacity (max_descs = 8192) configured here.
When that happens, __send_pkts() will enter an infinite loop:
while (xsk_ring_prod__reserve(&xsk->tx, xsk->batch_size, &idx) < xsk->batch_size) {
...
complete_pkts(xsk, xsk->batch_size);
}
Because xsk_ring_prod__reserve() can never grant a reservation larger than the
total ring capacity, this loop will spin indefinitely waiting for space that
can never exist.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014184539.774708-1-maciej.fijalkowski@intel.com?part=2
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-15 8:34 ` Björn Töpel
@ 2026-10-15 18:46 ` sashiko-bot
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-15 18:46 UTC (permalink / raw)
To: Maciej Fijalkowski; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] selftests: sysctl leak of vm.nr_hugepages on exception
- [Low] selftests: unused argument in ksft_print_msg() format string
--
commit 7acec71c768966e15c22ec5e2e55704dfaf4978e
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
selftests: xsk: run one test case per xskxceiver invocation
This commit replaces test_xsk.sh and xsk_prereqs.sh with test_xsk.py to run
xskxceiver once per mode and case. This ensures that every test case runs in a
fresh process and crashes only fail the specific test.
> diff --git a/tools/testing/selftests/net/lib/xsk/xskxceiver.c b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> index 02e54b026806b..09ad623333be5 100644
> --- a/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> +++ b/tools/testing/selftests/net/lib/xsk/xskxceiver.c
> @@ -169,14 +164,14 @@ 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"
> " -b, --busy-poll Enable busy poll\n"
> - " -m, --mode Run only mode skb, drv, or zc\n"
> + " -m, --mode Mode to run: skb, drv or zc\n"
> " -l, --list List all available tests\n"
> - " -t, --test Run a specific test. Enter number from -l option.\n"
> + " -t, --test Test case to run, a number from -l\n"
> " -h, --help Display this help and exit\n";
>
> ksft_print_msg(str, basename(argv[0]));
[Severity: Low]
Does this code silently ignore the executable name argument? The format string
in 'str' does not contain a '%s' specifier, so basename(argv[0]) is not printed
when print_usage() is called.
[ ... ]
> diff --git a/tools/testing/selftests/net/test_xsk.py b/tools/testing/selftests/net/test_xsk.py
> new file mode 100755
> index 0000000000000..acfdba55fbae8
> --- /dev/null
> +++ b/tools/testing/selftests/net/test_xsk.py
> @@ -0,0 +1,140 @@
[ ... ]
> +def main():
> + # ksft_run() exits on -l and -h before main() restores the huge pages.
> + if "-l" in sys.argv or "-h" in sys.argv:
> + sys.exit(f"{sys.argv[0]}: -l and -h are not supported")
> + if not XSK_BIN.exists():
> + print(f"1..0 # SKIP {XSK_BIN} was not built")
> + sys.exit(SKIP_CODE)
> + saved_hugepages = []
> + hugepages = xsk_reserve_hugepages(saved_hugepages)
> + cases = _get_cases(hugepages)
> + ksft_run([ksft_variants(cases)(test_xsk),
> + ksft_variants(cases)(test_xsk_busy_poll)])
> + xsk_release_hugepages(saved_hugepages)
> + ksft_exit()
[Severity: Medium]
Can this leak the host's vm.nr_hugepages sysctl modifications if an exception
occurs?
If _get_cases() or ksft_run() raise an unhandled exception (such as a
CmdExitFailure if the executable crashes, or a KeyboardInterrupt), execution
will terminate before reaching xsk_release_hugepages().
Should these calls be wrapped in a try/finally block to ensure hugepages are
properly restored?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014184539.774708-1-maciej.fijalkowski@intel.com?part=9
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-15 10:29 ` Björn Töpel
@ 2026-10-15 18:46 ` sashiko-bot
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-10-15 18:46 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: drv-net: system state leaked on exception in xsk.py
--
commit 800c14b8cb37c2773db542cb20c9fa28d14761b7
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
selftests: drv-net: test AF_XDP zero-copy with an SKB peer
This commit adds a new test script to run AF_XDP zero-copy tests against
an SKB-mode peer using pairs of xskxceiver processes. It sets up ntuple
filters, reserves huge pages, configures queues out of RSS, and cleans up
the configuration when the tests are complete.
> 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..fd708bb3420db
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/hw/xsk.py
[ ... ]
> +def _steer_udp(cfg, ifname, port, queue, host=None):
> + """Steer the test flow to queue, return False if ifname cannot."""
> + ntuple = ethtool(f"-k {ifname}", json=True, host=host)[0]["ntuple-filters"]
> + if not ntuple["active"]:
> + if ntuple["fixed"]:
> + print(f"1..0 # SKIP {ifname} does not support ntuple-filters")
> + return False
> +
> + ethtool(f"-K {ifname} ntuple-filters on", host=host)
> +
> + out = ethtool(f"-N {ifname} flow-type udp4 dst-port {port} action {queue}",
> + host=host).stdout
[Severity: Medium]
If the ethtool command fails here, cmd() will raise a CmdExitFailure
exception since fail=True is the default in net/lib/py. Does this leave
ntuple-filters on permanently enabled on the physical NIC, because the
exception prevents appending the rule to cfg.rules for later cleanup?
> + rule = re.search(r"ID (\d+)", out).group(1)
> + cfg.rules.append((host, ifname, rule, ntuple["active"]))
> + return True
[ ... ]
> +def main():
> + # ksft_run() exits on -l and -h before main() can undo the setup.
> + if "-l" in sys.argv or "-h" in sys.argv:
> + sys.exit(f"{sys.argv[0]}: -l and -h are not supported")
> +
> + with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
[ ... ]
> + cases = _get_cases(cfg)
> +
> + # _fixup_args() sets the options that differ between the cases.
> + cfg.args = ["--hw",
> + "-p", cfg.remote_addr_v["4"],
[ ... ]
> + cfg.local_args = [str(cfg.bin_local),
> + "-i", cfg.ifname,
> + "-m", "zc",
> + "--queue", str(cfg.queue),
> + "--peer-mac", cfg.remote_dev["address"]]
> +
> + ksft_run([ksft_variants(cases)(test_xsk),
> + ksft_variants(cases)(test_xsk_busy_poll)], args=(cfg,))
> +
> + xsk_release_hugepages(saved_hugepages)
> + _unsteer_udp(cfg)
> + ethtool(f"-X {cfg.ifname} default")
[Severity: Medium]
If an exception is raised earlier in main(), such as during _get_cases() or
other setup commands, will execution exit the function immediately and bypass
these cleanup calls?
Since this can leak system-wide configurations like hugepage allocations and
hardware NIC RSS/ntuple settings, would it make sense to protect the cleanup
sequence with a try...finally block?
> + ksft_exit()
> +
> +
> +if __name__ == "__main__":
> + main()
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014184539.774708-1-maciej.fijalkowski@intel.com?part=14
^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-10-15 18:46 UTC | newest]
Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-15 8:14 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
2026-10-15 8:15 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
2026-10-15 8:17 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
2026-10-15 8:18 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
2026-10-15 8:20 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
2026-10-15 8:22 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
2026-10-15 8:27 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
2026-10-15 8:29 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-15 8:34 ` Björn Töpel
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
2026-10-15 9:04 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
2026-10-15 9:42 ` Björn Töpel
2026-10-15 10:55 ` Maciej Fijalkowski
2026-10-15 16:03 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
2026-10-15 10:05 ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-15 10:29 ` Björn Töpel
2026-10-15 12:52 ` Maciej Fijalkowski
2026-10-15 18:46 ` sashiko-bot
2026-10-14 18:45 ` [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs 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.