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