* [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test
@ 2026-10-18 14:05 Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
This series fixes the XDP feature test and moves it to drivers/net/hw.
The tester uses AF_PACKET, so the remote host does not need native XDP.
Existing remote XDP programs and offload settings are left unchanged.
Changes since v5:
- Fix control EOF handling and preserve existing XDP programs on attach
failure in the initial cleanup patch.
- Bound shell readiness, fix signal cleanup and namespace ownership,
isolate tracepoint counters, and fail on capability mismatches.
- Support a separate control network, use an atomic signal flag, return
failure on interruption, and query capabilities after attachment.
- Fix inherited source paths, host-tool selection and installed-header
priority in the shared BPF build rules.
- Use argv lists for local commands and SSH deployment. Remove the old
ignore entry and fix the README heading.
- Run veth XDP_TX when peer GRO is enabled without native XDP. Skip only
when the peer has neither. This covers the case addressed by [1].
Tested on x86_64 Ubuntu with Linux 6.8.0-124-generic:
- Normal and separate-output builds, relative inherited srctree, host
bootstrap with target CC/LD disabled, and conflicting source headers.
- Earlier baseline across three veth configurations: 17 PASS and
1 expected XDP_TX SKIP.
- All six features with separate IPv4 and IPv6 control networks: 12 PASS.
- Nine error/cleanup checks, six protocol/isolation checks, and DUT/tester
attach-failure preservation checks passed.
- Patch 2: two concurrent six-case runs and three repeated-TERM runs
left no helper processes or namespaces.
- SSH deployment and execution from a path containing spaces and a quote.
- GRO-only XDP_TX: isolated veth modules with and without the one-line
fix reproduce failure and success for both IPv4 and IPv6. The runner
also passes the expected skip/failure/success checks with ethtool 5.16.
- GCC -fanalyzer reported no diagnostics. Clang diagnostics were checked;
the unused assignment was removed and model false positives documented.
Tests used veth devices; physical-NIC testing remains unverified.
[1] https://lists.openwall.net/netdev/2026/10/16/565
Previous version:
https://lore.kernel.org/all/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40@gmail.com/
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
Bochao Cao (5):
selftests/bpf: Fix XDP feature test setup and teardown
selftests/bpf: Track XDP test processes and isolate namespaces
selftests/bpf: Send XDP feature probes with AF_PACKET
selftests/net: Add a shared BPF skeleton rule
selftests/net: Move the XDP feature test to drivers/net/hw
tools/testing/selftests/bpf/.gitignore | 1 -
tools/testing/selftests/bpf/Makefile | 11 +-
tools/testing/selftests/bpf/test_xdp_features.sh | 107 --
tools/testing/selftests/bpf/xdp_features.c | 719 --------------
tools/testing/selftests/drivers/net/README.rst | 19 +
tools/testing/selftests/drivers/net/hw/.gitignore | 3 +
tools/testing/selftests/drivers/net/hw/Makefile | 15 +-
tools/testing/selftests/drivers/net/hw/config | 3 +
.../net/hw/xdp_features.bpf.c} | 60 +-
.../selftests/drivers/net/hw/xdp_features.c | 1023 ++++++++++++++++++++
.../{bpf => drivers/net/hw}/xdp_features.h | 1 +
.../selftests/drivers/net/hw/xdp_features.py | 146 +++
.../selftests/drivers/net/lib/py/remote_ssh.py | 2 +-
tools/testing/selftests/net/bpf.mk | 33 +-
14 files changed, 1266 insertions(+), 877 deletions(-)
---
base-commit: 8df0638138d3e0344fd1fb36cf2d1ca1cf5028f0
change-id: 20261008-xdp-v5-send-20261008-301d0e1d3165
Best regards,
--
Bochao Cao <bochaolucky@gmail.com>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
@ 2026-10-18 14:05 ` Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
From: Bochao Cao <bochaolucky@gmail.com>
Initialize the DUT state and socket address lengths. Return the echo
socket to the caller and join the worker only after it starts. Stop the
worker on control EOF and other errors.
Read tester counters before teardown, which can produce extra echo
traffic. Detach only after a successful attach so a failed test
preserves an existing XDP program.
Tested on Ubuntu 6.8 with gcc. DUT and tester attach failures both
preserve the existing program ID.
Fixes: 4dba3e7852b7 ("selftests/bpf: introduce XDP compliance test tool")
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
tools/testing/selftests/bpf/xdp_features.c | 63 ++++++++++++++++++++----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
index a27ed663967c4..3076fdda61fa4 100644
--- a/tools/testing/selftests/bpf/xdp_features.c
+++ b/tools/testing/selftests/bpf/xdp_features.c
@@ -227,51 +227,53 @@ static void *dut_echo_thread(void *arg)
int sockfd = *(int *)arg;
while (!exiting) {
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
struct sockaddr_storage addr;
- socklen_t addrlen;
- size_t n;
+ socklen_t addrlen = sizeof(addr);
+ ssize_t n;
n = recvfrom(sockfd, buf, sizeof(buf), MSG_WAITALL,
(struct sockaddr *)&addr, &addrlen);
- if (n != ntohs(tlv->len))
+ if (n != sizeof(*tlv) || n != ntohs(tlv->len))
continue;
if (ntohs(tlv->type) != CMD_ECHO)
continue;
sendto(sockfd, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM,
(struct sockaddr *)&addr, addrlen);
}
- pthread_exit((void *)0);
close(sockfd);
return NULL;
}
static int dut_run_echo_thread(pthread_t *t, int *sockfd)
{
- int err;
+ int *fds, err;
- sockfd = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
+ fds = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
DUT_ECHO_PORT, 0, 1);
- if (!sockfd) {
+ if (!fds) {
fprintf(stderr,
"Failed creating data UDP socket on device %s\n",
env.ifname);
return -errno;
}
+ *sockfd = *fds;
+ free(fds);
+
/* start echo channel */
err = pthread_create(t, NULL, dut_echo_thread, sockfd);
if (err) {
fprintf(stderr,
"Failed creating data UDP thread on device %s: %s\n",
- env.ifname, strerror(-err));
- free_fds(sockfd, 1);
+ env.ifname, strerror(err));
+ close(*sockfd);
return -EINVAL;
}
return 0;
}
@@ -339,14 +341,18 @@ static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
size_t val_size)
{
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
- size_t len;
+ ssize_t len;
len = recv(sockfd, buf, bufsize, 0);
- if (len != ntohs(tlv->len) || len < sizeof(*tlv))
+ if (len < 0)
+ return -errno;
+ if (!len)
+ return -ECONNRESET;
+ if (len < sizeof(*tlv) || len != ntohs(tlv->len))
return -EINVAL;
if (val) {
len -= sizeof(*tlv);
if (len > val_size)
@@ -359,14 +365,15 @@ static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
}
static int dut_run(struct xdp_features *skel)
{
int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
- int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
+ int state = CMD_STOP, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
struct sockaddr_storage ctrl_addr;
- pthread_t dut_thread = 0;
- socklen_t addrlen;
+ pthread_t dut_thread;
+ bool thread_started = false, xdp_attached = false;
+ socklen_t addrlen = sizeof(ctrl_addr);
sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
DUT_CTRL_PORT, 0, 1);
if (!sockfd) {
fprintf(stderr,
@@ -387,12 +394,14 @@ static int dut_run(struct xdp_features *skel)
while (!exiting) {
unsigned char buf[BUFSIZE] = {};
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
- if (err)
+ if (err == -EAGAIN || err == -EINTR)
continue;
+ if (err)
+ goto end_thread;
switch (ntohs(tlv->type)) {
case CMD_START: {
if (state == CMD_START)
continue;
@@ -401,13 +410,16 @@ static int dut_run(struct xdp_features *skel)
/* Load the XDP program on the DUT */
err = dut_attach_xdp_prog(skel, flags);
if (err)
goto out;
+ xdp_attached = true;
+
err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
if (err < 0)
goto out;
+ thread_started = true;
tlv->type = htons(CMD_ACK);
tlv->len = htons(sizeof(*tlv));
err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
if (err < 0)
@@ -420,10 +432,11 @@ static int dut_run(struct xdp_features *skel)
state = CMD_STOP;
exiting = true;
bpf_xdp_detach(env.ifindex, flags, NULL);
+ xdp_attached = false;
tlv->type = htons(CMD_ACK);
tlv->len = htons(sizeof(*tlv));
err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
goto end_thread;
@@ -482,13 +495,18 @@ static int dut_run(struct xdp_features *skel)
break;
}
}
end_thread:
- pthread_join(dut_thread, NULL);
+ exiting = true;
+ if (thread_started) {
+ shutdown(echo_sockfd, SHUT_RDWR);
+ pthread_join(dut_thread, NULL);
+ }
out:
- bpf_xdp_detach(env.ifindex, flags, NULL);
+ if (xdp_attached)
+ bpf_xdp_detach(env.ifindex, flags, NULL);
close(ctrl_sockfd);
free_fds(sockfd, 1);
return err;
}
@@ -576,11 +594,11 @@ static int tester_run(struct xdp_features *skel)
int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
unsigned long long advertised_feature;
struct bpf_program *prog;
unsigned int stats;
int i, err, sockfd;
- bool detected_cap;
+ bool detected_cap, xdp_attached = false;
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd < 0) {
fprintf(stderr,
"Failed creating tester service control socket\n");
@@ -618,10 +636,11 @@ static int tester_run(struct xdp_features *skel)
fprintf(stderr, "Failed attaching XDP program to device %s\n",
env.ifname);
goto out;
}
+ xdp_attached = true;
err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);
if (err)
goto out;
for (i = 0; i < 10 && !exiting; i++) {
@@ -634,23 +653,25 @@ static int tester_run(struct xdp_features *skel)
err = send_and_recv_msg(sockfd, CMD_GET_STATS, &stats, sizeof(stats));
if (err)
goto out;
+ /* Sample before teardown, which can produce additional echo traffic. */
+ detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
+
/* stop the test */
err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 0);
/* send a new echo message to wake echo thread of the dut */
send_echo_msg();
- detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
-
fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
: RED("NOT ADVERTISED"));
out:
- bpf_xdp_detach(env.ifindex, flags, NULL);
+ if (xdp_attached)
+ bpf_xdp_detach(env.ifindex, flags, NULL);
close(sockfd);
return err < 0 ? err : 0;
}
int main(int argc, char **argv)
--
Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
@ 2026-10-18 14:05 ` Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
From: Bochao Cao <bochaolucky@gmail.com>
Track the DUT shell job and give each run separate DUT and tester
namespaces. Bound the readiness wait and handle signals during setup and
cleanup.
Filter tracepoint counters by this run's program and CPU map IDs. Return
failure when detected and advertised features differ.
Tested with two concurrent six-feature runs and three repeated-TERM
runs. All passed and left no helper processes or namespaces.
Fixes: 4dba3e7852b7 ("selftests/bpf: introduce XDP compliance test tool")
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
tools/testing/selftests/bpf/progs/xdp_features.c | 18 +++-
tools/testing/selftests/bpf/test_xdp_features.sh | 132 ++++++++++++++++++-----
tools/testing/selftests/bpf/xdp_features.c | 26 +++++
3 files changed, 146 insertions(+), 30 deletions(-)
diff --git a/tools/testing/selftests/bpf/progs/xdp_features.c b/tools/testing/selftests/bpf/progs/xdp_features.c
index 67424084a38a1..07d1eaea5fbe5 100644
--- a/tools/testing/selftests/bpf/progs/xdp_features.c
+++ b/tools/testing/selftests/bpf/progs/xdp_features.c
@@ -2,10 +2,11 @@
#include <stdbool.h>
#include <linux/bpf.h>
#include <linux/netdev.h>
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_tracing.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
@@ -20,11 +21,20 @@
(a).s6_addr32[1] == (b).s6_addr32[1] && \
(a).s6_addr32[2] == (b).s6_addr32[2] && \
(a).s6_addr32[3] == (b).s6_addr32[3])
struct net_device;
-struct bpf_prog;
+struct bpf_prog_aux {
+ __u32 id;
+} __attribute__((preserve_access_index));
+
+struct bpf_prog {
+ struct bpf_prog_aux *aux;
+} __attribute__((preserve_access_index));
+
+__u32 dut_prog_id;
+__u32 dut_cpumap_id;
struct xdp_cpumap_stats {
unsigned int redirect;
unsigned int pass;
unsigned int drop;
@@ -226,10 +236,13 @@ SEC("tp_btf/xdp_exception")
int BPF_PROG(xdp_exception, const struct net_device *dev,
const struct bpf_prog *xdp, __u32 act)
{
__u32 *val, key = 0;
+ if (!dut_prog_id || BPF_CORE_READ(xdp, aux, id) != dut_prog_id)
+ return 0;
+
val = bpf_map_lookup_elem(&dut_stats, &key);
if (val)
__sync_add_and_fetch(val, 1);
return 0;
@@ -239,10 +252,13 @@ SEC("tp_btf/xdp_cpumap_kthread")
int BPF_PROG(tp_xdp_cpumap_kthread, int map_id, unsigned int processed,
unsigned int drops, int sched, struct xdp_cpumap_stats *xdp_stats)
{
__u32 *val, key = 0;
+ if (!dut_cpumap_id || map_id != dut_cpumap_id)
+ return 0;
+
val = bpf_map_lookup_elem(&dut_stats, &key);
if (val)
__sync_add_and_fetch(val, 1);
return 0;
diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
index 0aa71c4455c05..5b68b20d94ac9 100755
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ b/tools/testing/selftests/bpf/test_xdp_features.sh
@@ -1,107 +1,181 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
readonly NS="ns1-$(mktemp -u XXXXXX)"
+readonly DUT_NS="dut-${NS}"
readonly V0_IP4=10.10.0.11
readonly V1_IP4=10.10.0.1
readonly V0_IP6=2001:db8::11
readonly V1_IP6=2001:db8::1
ret=1
+dut_pid=""
+peer_created=false
+dut_created=false
+setup_in_progress=true
+pending_signal=0
setup() {
{
- ip netns add ${NS}
-
- ip link add v1 type veth peer name v0 netns ${NS}
-
- ip link set v1 up
- ip addr add $V1_IP4/24 dev v1
- ip addr add $V1_IP6/64 nodad dev v1
+ [ ! -e "/run/netns/${NS}" ]
+ peer_created=true
+ ip netns add "${NS}"
+ [ ! -e "/run/netns/${DUT_NS}" ]
+ dut_created=true
+ ip netns add "${DUT_NS}"
+
+ ip -n "$DUT_NS" link add v1 type veth peer name v0 netns ${NS}
+
+ ip -n "$DUT_NS" link set v1 up
+ ip -n "$DUT_NS" addr add $V1_IP4/24 dev v1
+ ip -n "$DUT_NS" addr add $V1_IP6/64 nodad dev v1
ip -n ${NS} link set dev v0 up
ip -n ${NS} addr add $V0_IP4/24 dev v0
ip -n ${NS} addr add $V0_IP6/64 nodad dev v0
# Enable XDP mode and disable checksum offload
- ethtool -K v1 gro on
- ethtool -K v1 tx-checksumming off
+ ip netns exec "$DUT_NS" ethtool -K v1 gro on
+ ip netns exec "$DUT_NS" ethtool -K v1 tx-checksumming off
ip netns exec ${NS} ethtool -K v0 gro on
ip netns exec ${NS} ethtool -K v0 tx-checksumming off
} > /dev/null 2>&1
+ setup_in_progress=false
+ [ "$pending_signal" -eq 0 ] || exit "$pending_signal"
+}
+
+terminate_dut_server() {
+ local pid
+
+ # Use the shell job instead of dut_pid, which may not have been assigned
+ # yet if a signal arrived immediately after the server was started.
+ pid=$(jobs -pr 2> /dev/null) || true
+ if [ -z "$pid" ]; then
+ dut_pid=""
+ return
+ fi
+
+ kill -KILL %% 2> /dev/null || true
+ wait "$pid" 2> /dev/null || true
+ dut_pid=""
}
cleanup() {
- ip link del v1 2> /dev/null
- ip netns del ${NS} 2> /dev/null
- [ "$(pidof xdp_features)" = "" ] || kill $(pidof xdp_features) 2> /dev/null
+ trap '' HUP INT QUIT ABRT TERM
+ trap - EXIT
+ terminate_dut_server
+ if "$dut_created"; then
+ ip netns del "$DUT_NS" 2> /dev/null || true
+ fi
+ if "$peer_created"; then
+ ip netns del "$NS" 2> /dev/null || true
+ fi
}
wait_for_dut_server() {
- while sleep 1; do
- ss -tlp | grep -q xdp_features
- [ $? -eq 0 ] && break
+ local attempt
+
+ for ((attempt = 0; attempt < 30; attempt++)); do
+ if [ "$(jobs -pr 2> /dev/null)" != "$dut_pid" ]; then
+ echo "xdp_features server $dut_pid exited before readiness" >&2
+ return 1
+ fi
+
+ if ip netns exec "$DUT_NS" ss -tlp 2> /dev/null |
+ grep -q "pid=$dut_pid,"; then
+ return 0
+ fi
+
+ sleep 1
done
+ echo "xdp_features server $dut_pid did not become ready" >&2
+ return 1
+}
+
+start_dut_server() {
+ ip netns exec "$DUT_NS" ./xdp_features "$@" &
+ dut_pid=$!
+ wait_for_dut_server
+}
+
+reap_dut_server() {
+ local status=0
+
+ wait "$dut_pid" || status=$?
+ dut_pid=""
+ return "$status"
}
test_xdp_features() {
setup
## XDP_PASS
- ./xdp_features -f XDP_PASS -D $V1_IP6 -T $V0_IP6 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_PASS -D $V1_IP6 -T $V0_IP6 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_PASS \
-D $V1_IP6 -C $V1_IP6 \
-T $V0_IP6 v0
[ $? -ne 0 ] && exit
+ reap_dut_server
## XDP_DROP
- ./xdp_features -f XDP_DROP -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_DROP -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_DROP \
-D ::ffff:$V1_IP4 \
-C ::ffff:$V1_IP4 \
-T ::ffff:$V0_IP4 v0
[ $? -ne 0 ] && exit
+ reap_dut_server
## XDP_ABORTED
- ./xdp_features -f XDP_ABORTED -D $V1_IP6 -T $V0_IP6 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_ABORTED -D $V1_IP6 -T $V0_IP6 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_ABORTED \
-D $V1_IP6 -C $V1_IP6 \
-T $V0_IP6 v0
[ $? -ne 0 ] && exit
+ reap_dut_server
## XDP_TX
- ./xdp_features -f XDP_TX -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_TX -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_TX \
-D ::ffff:$V1_IP4 \
-C ::ffff:$V1_IP4 \
-T ::ffff:$V0_IP4 v0
[ $? -ne 0 ] && exit
+ reap_dut_server
## XDP_REDIRECT
- ./xdp_features -f XDP_REDIRECT -D $V1_IP6 -T $V0_IP6 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_REDIRECT -D $V1_IP6 -T $V0_IP6 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_REDIRECT \
-D $V1_IP6 -C $V1_IP6 \
-T $V0_IP6 v0
[ $? -ne 0 ] && exit
+ reap_dut_server
## XDP_NDO_XMIT
- ./xdp_features -f XDP_NDO_XMIT -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
- wait_for_dut_server
+ start_dut_server -f XDP_NDO_XMIT -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_NDO_XMIT \
-D ::ffff:$V1_IP4 \
-C ::ffff:$V1_IP4 \
-T ::ffff:$V0_IP4 v0
ret=$?
- cleanup
+ reap_dut_server
+}
+
+handle_signal() {
+ if "$setup_in_progress"; then
+ pending_signal=$1
+ else
+ exit "$1"
+ fi
}
set -e
-trap cleanup 2 3 6 9
+trap cleanup EXIT
+trap 'handle_signal 129' HUP
+trap 'handle_signal 130' INT
+trap 'handle_signal 131' QUIT
+trap 'handle_signal 134' ABRT
+trap 'handle_signal 143' TERM
test_xdp_features
exit $ret
diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
index 3076fdda61fa4..97c0e871c5b65 100644
--- a/tools/testing/selftests/bpf/xdp_features.c
+++ b/tools/testing/selftests/bpf/xdp_features.c
@@ -665,17 +665,39 @@ static int tester_run(struct xdp_features *skel)
fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
: RED("NOT ADVERTISED"));
+ if (!err && detected_cap != !!(env.feature.drv_feature & advertised_feature))
+ err = -EINVAL;
out:
if (xdp_attached)
bpf_xdp_detach(env.ifindex, flags, NULL);
close(sockfd);
return err < 0 ? err : 0;
}
+static int dut_set_trace_ids(struct xdp_features *skel)
+{
+ struct bpf_prog_info prog = {};
+ struct bpf_map_info map = {};
+ __u32 len = sizeof(prog);
+ int err;
+
+ err = bpf_prog_get_info_by_fd(bpf_program__fd(skel->progs.xdp_do_aborted),
+ &prog, &len);
+ if (err)
+ return err;
+ len = sizeof(map);
+ err = bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.cpu_map), &map, &len);
+ if (err)
+ return err;
+ skel->bss->dut_prog_id = prog.id;
+ skel->bss->dut_cpumap_id = map.id;
+ return 0;
+}
+
int main(int argc, char **argv)
{
struct xdp_features *skel;
int err;
@@ -714,10 +736,14 @@ int main(int argc, char **argv)
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
+ err = dut_set_trace_ids(skel);
+ if (err)
+ goto cleanup;
+
err = xdp_features__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
--
Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
@ 2026-10-18 14:05 ` Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
From: Bochao Cao <bochaolucky@gmail.com>
Send probes and count replies with AF_PACKET so the tester needs no XDP
support. Build UDP checksums in software and match replies by address,
port and echo header.
Use an XDP link on the DUT to release only this test's attachment on
exit. Handle partial control transfers and EOF, allow a separate control
network, and query capabilities after attachment. Return failure when
interrupted.
Add a native-XDP query and drop the network_helpers dependency. Skip
veth XDP_TX without a peer native XDP program.
Tested with the full series: all six features over separate IPv4 and
IPv6 control networks, TCP fragmentation and coalescing, EOF, signals
and attachment cleanup.
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
tools/testing/selftests/bpf/Makefile | 6 +-
tools/testing/selftests/bpf/progs/xdp_features.c | 42 +-
tools/testing/selftests/bpf/test_xdp_features.sh | 10 +-
tools/testing/selftests/bpf/xdp_features.c | 621 ++++++++++++++++-------
tools/testing/selftests/bpf/xdp_features.h | 1 +
5 files changed, 450 insertions(+), 230 deletions(-)
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 93c707116fad0..883c92701b9f1 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -945,14 +945,12 @@ $(OUTPUT)/xdp_hw_metadata: xdp_hw_metadata.c xsk.c network_helpers.c \
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 $@
-$(OUTPUT)/xdp_features: xdp_features.c network_helpers.c xdp_features.h \
- network_helpers.h \
- test_progs.h bpf_util.h $(OUTPUT)/xdp_features.skel.h \
- $(BPFOBJ) | $(OUTPUT)
+$(OUTPUT)/xdp_features: xdp_features.c xdp_features.h \
+ $(OUTPUT)/xdp_features.skel.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
# Make sure we are able to include and link libbpf against c++.
CXXFLAGS += $(CFLAGS)
diff --git a/tools/testing/selftests/bpf/progs/xdp_features.c b/tools/testing/selftests/bpf/progs/xdp_features.c
index 07d1eaea5fbe5..8c94b8eade87b 100644
--- a/tools/testing/selftests/bpf/progs/xdp_features.c
+++ b/tools/testing/selftests/bpf/progs/xdp_features.c
@@ -38,17 +38,10 @@ struct xdp_cpumap_stats {
unsigned int redirect;
unsigned int pass;
unsigned int drop;
};
-struct {
- __uint(type, BPF_MAP_TYPE_ARRAY);
- __type(key, __u32);
- __type(value, __u32);
- __uint(max_entries, 1);
-} stats SEC(".maps");
-
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u32);
__uint(max_entries, 1);
@@ -133,67 +126,46 @@ xdp_process_echo_packet(struct xdp_md *xdp, bool dut)
port = dut ? uh->dest : uh->source;
if (port != bpf_htons(DUT_ECHO_PORT))
return -EINVAL;
tlv = (struct tlv_hdr *)(uh + 1);
- if (tlv + 1 > data_end)
+ if ((void *)(tlv + 1) > data_end)
return -EINVAL;
return bpf_htons(tlv->type) == CMD_ECHO ? 0 : -EINVAL;
}
static __always_inline int
-xdp_update_stats(struct xdp_md *xdp, bool tx, bool dut)
+xdp_update_stats(struct xdp_md *xdp)
{
__u32 *val, key = 0;
- if (xdp_process_echo_packet(xdp, tx))
+ if (xdp_process_echo_packet(xdp, true))
return -EINVAL;
- if (dut)
- val = bpf_map_lookup_elem(&dut_stats, &key);
- else
- val = bpf_map_lookup_elem(&stats, &key);
+ val = bpf_map_lookup_elem(&dut_stats, &key);
if (val)
__sync_add_and_fetch(val, 1);
return 0;
}
-/* Tester */
-
-SEC("xdp")
-int xdp_tester_check_tx(struct xdp_md *xdp)
-{
- xdp_update_stats(xdp, true, false);
-
- return XDP_PASS;
-}
-
-SEC("xdp")
-int xdp_tester_check_rx(struct xdp_md *xdp)
-{
- xdp_update_stats(xdp, false, false);
-
- return XDP_PASS;
-}
-
/* DUT */
SEC("xdp")
int xdp_do_pass(struct xdp_md *xdp)
{
- xdp_update_stats(xdp, true, true);
+ xdp_update_stats(xdp);
return XDP_PASS;
}
SEC("xdp")
int xdp_do_drop(struct xdp_md *xdp)
{
- if (xdp_update_stats(xdp, true, true))
+ if (xdp_update_stats(xdp))
return XDP_PASS;
return XDP_DROP;
}
@@ -211,11 +183,11 @@ int xdp_do_tx(struct xdp_md *xdp)
{
void *data = (void *)(long)xdp->data;
struct ethhdr *eh = data;
__u8 tmp_mac[ETH_ALEN];
- if (xdp_update_stats(xdp, true, true))
+ if (xdp_update_stats(xdp))
return XDP_PASS;
__builtin_memcpy(tmp_mac, eh->h_source, ETH_ALEN);
__builtin_memcpy(eh->h_source, eh->h_dest, ETH_ALEN);
__builtin_memcpy(eh->h_dest, tmp_mac, ETH_ALEN);
diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
index 5b68b20d94ac9..ad221c530b4ef 100755
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ b/tools/testing/selftests/bpf/test_xdp_features.sh
@@ -131,18 +131,12 @@ test_xdp_features() {
-D $V1_IP6 -C $V1_IP6 \
-T $V0_IP6 v0
[ $? -ne 0 ] && exit
reap_dut_server
- ## XDP_TX
- start_dut_server -f XDP_TX -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_TX \
- -D ::ffff:$V1_IP4 \
- -C ::ffff:$V1_IP4 \
- -T ::ffff:$V0_IP4 v0
- [ $? -ne 0 ] && exit
- reap_dut_server
+ # veth only flushes XDP_TX to a peer with native XDP attached.
+ echo "SKIP: XDP_TX needs a peer native XDP program"
## XDP_REDIRECT
start_dut_server -f XDP_REDIRECT -D $V1_IP6 -T $V0_IP6 v1
ip netns exec ${NS} ./xdp_features -t -f XDP_REDIRECT \
-D $V1_IP6 -C $V1_IP6 \
diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
index 97c0e871c5b65..be4acb768e0a2 100644
--- a/tools/testing/selftests/bpf/xdp_features.c
+++ b/tools/testing/selftests/bpf/xdp_features.c
@@ -1,24 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
-#include <uapi/linux/bpf.h>
-#include <uapi/linux/netdev.h>
+#include <netinet/in.h>
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#include <linux/netdev.h>
#include <linux/if_link.h>
-#include <signal.h>
#include <argp.h>
+#include <errno.h>
#include <net/if.h>
+#include <pthread.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/socket.h>
-#include <netinet/in.h>
+#include <sys/time.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
-#include <pthread.h>
-#include <network_helpers.h>
-
-#include "bpf_util.h"
#include "xdp_features.skel.h"
#include "xdp_features.h"
#define RED(str) "\033[0;31m" str "\033[0m"
#define GREEN(str) "\033[0;32m" str "\033[0m"
@@ -27,10 +39,11 @@
static struct env {
bool verbosity;
char ifname[IF_NAMESIZE];
int ifindex;
bool is_tester;
+ bool query_only;
struct {
enum netdev_xdp_act drv_feature;
enum xdp_action action;
} feature;
struct sockaddr_storage dut_ctrl_addr;
@@ -38,25 +51,90 @@ static struct env {
struct sockaddr_storage tester_addr;
} env;
#define BUFSIZE 128
-void test__fail(void) { /* for network_helpers.c */ }
+static int make_sockaddr(const char *addr_str, __u16 port,
+ struct sockaddr_storage *addr)
+{
+ struct sockaddr_in6 *sin6 = (void *)addr;
+
+ memset(addr, 0, sizeof(*addr));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(port);
+ if (addr_str && inet_pton(AF_INET6, addr_str, &sin6->sin6_addr) != 1)
+ return -1;
+
+ return 0;
+}
+
+static int settimeo(int fd, int timeout_ms)
+{
+ struct timeval timeout = { .tv_sec = 3 };
+
+ if (timeout_ms > 0) {
+ timeout.tv_sec = timeout_ms / 1000;
+ timeout.tv_usec = (timeout_ms % 1000) * 1000;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
+ sizeof(timeout)) ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
+ sizeof(timeout)))
+ return -1;
+
+ return 0;
+}
+
+static int start_server(int type, __u16 port)
+{
+ struct sockaddr_storage addr = env.dut_addr;
+ int fd, on = 1;
+
+ fd = socket(AF_INET6, type, 0);
+ if (fd < 0)
+ return -1;
+
+ /* The control connection may use a separate management network. */
+ if (type == SOCK_STREAM)
+ ((struct sockaddr_in6 *)&addr)->sin6_addr = in6addr_any;
+ ((struct sockaddr_in6 *)&addr)->sin6_port = htons(port);
+ if (settimeo(fd, 0) ||
+ (type == SOCK_STREAM &&
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) ||
+ bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in6)) ||
+ (type == SOCK_STREAM && listen(fd, 1))) {
+ int err = errno;
+
+ close(fd);
+ errno = err;
+ return -1;
+ }
+
+ return fd;
+}
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{
if (level == LIBBPF_DEBUG && !env.verbosity)
return 0;
return vfprintf(stderr, format, args);
}
-static volatile bool exiting;
+_Static_assert(ATOMIC_INT_LOCK_FREE == 2, "Signal flag must be lock-free");
+static atomic_int stop_signal;
+static atomic_bool exiting;
+
+static bool should_exit(void)
+{
+ return atomic_load(&stop_signal) || atomic_load(&exiting);
+}
static void sig_handler(int sig)
{
- exiting = true;
+ atomic_store(&stop_signal, sig);
}
const char *argp_program_version = "xdp-features 0.0";
const char argp_program_doc[] =
"XDP features detection application.\n"
@@ -76,10 +154,11 @@ const char argp_program_doc[] =
"- XDP_TX\n";
static const struct argp_option opts[] = {
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
{ "tester", 't', NULL, 0, "Tester mode" },
+ { "query", 'q', NULL, 0, "Check native XDP support without attaching" },
{ "feature", 'f', "XDP-FEATURE", 0, "XDP feature to test" },
{ "dut_data_ip", 'D', "DUT-DATA-IP", 0, "DUT IP data channel" },
{ "dut_ctrl_ip", 'C', "DUT-CTRL-IP", 0, "DUT IP control channel" },
{ "tester_data_ip", 'T', "TESTER-DATA-IP", 0, "Tester IP data channel" },
{},
@@ -138,10 +217,13 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case 'v':
env.verbosity = true;
break;
+ case 'q':
+ env.query_only = true;
+ break;
case 't':
env.is_tester = true;
break;
case 'f':
if (get_xdp_feature(arg) < 0) {
@@ -149,29 +231,27 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
argp_usage(state);
return ARGP_ERR_UNKNOWN;
}
break;
case 'D':
- if (make_sockaddr(AF_INET6, arg, DUT_ECHO_PORT,
- &env.dut_addr, NULL)) {
+ if (make_sockaddr(arg, DUT_ECHO_PORT, &env.dut_addr)) {
fprintf(stderr,
"Invalid address assigned to the Device Under Test: %s\n",
arg);
return ARGP_ERR_UNKNOWN;
}
break;
case 'C':
- if (make_sockaddr(AF_INET6, arg, DUT_CTRL_PORT,
- &env.dut_ctrl_addr, NULL)) {
+ if (make_sockaddr(arg, DUT_CTRL_PORT, &env.dut_ctrl_addr)) {
fprintf(stderr,
"Invalid address assigned to the Device Under Test: %s\n",
arg);
return ARGP_ERR_UNKNOWN;
}
break;
case 'T':
- if (make_sockaddr(AF_INET6, arg, 0, &env.tester_addr, NULL)) {
+ if (make_sockaddr(arg, 0, &env.tester_addr)) {
fprintf(stderr,
"Invalid address assigned to the Tester device: %s\n",
arg);
return ARGP_ERR_UNKNOWN;
}
@@ -211,24 +291,23 @@ static const struct argp argp = {
static void set_env_default(void)
{
env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
env.feature.action = -EINVAL;
env.ifindex = -ENODEV;
- strscpy(env.ifname, "unknown");
- make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_CTRL_PORT,
- &env.dut_ctrl_addr, NULL);
- make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_ECHO_PORT,
- &env.dut_addr, NULL);
- make_sockaddr(AF_INET6, "::ffff:127.0.0.1", 0, &env.tester_addr, NULL);
+ snprintf(env.ifname, sizeof(env.ifname), "unknown");
+ make_sockaddr("::ffff:127.0.0.1", DUT_CTRL_PORT,
+ &env.dut_ctrl_addr);
+ make_sockaddr("::ffff:127.0.0.1", DUT_ECHO_PORT, &env.dut_addr);
+ make_sockaddr("::ffff:127.0.0.1", 0, &env.tester_addr);
}
static void *dut_echo_thread(void *arg)
{
unsigned char buf[sizeof(struct tlv_hdr)];
int sockfd = *(int *)arg;
- while (!exiting) {
+ while (!should_exit()) {
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
ssize_t n;
@@ -249,24 +328,20 @@ static void *dut_echo_thread(void *arg)
return NULL;
}
static int dut_run_echo_thread(pthread_t *t, int *sockfd)
{
- int *fds, err;
+ int err;
- fds = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
- DUT_ECHO_PORT, 0, 1);
- if (!fds) {
+ *sockfd = start_server(SOCK_DGRAM, DUT_ECHO_PORT);
+ if (*sockfd < 0) {
fprintf(stderr,
"Failed creating data UDP socket on device %s\n",
env.ifname);
return -errno;
}
- *sockfd = *fds;
- free(fds);
-
/* start echo channel */
err = pthread_create(t, NULL, dut_echo_thread, sockfd);
if (err) {
fprintf(stderr,
"Failed creating data UDP thread on device %s: %s\n",
@@ -276,16 +351,17 @@ static int dut_run_echo_thread(pthread_t *t, int *sockfd)
}
return 0;
}
-static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
+static int dut_attach_xdp_prog(struct xdp_features *skel)
{
enum xdp_action action = env.feature.action;
struct bpf_program *prog;
unsigned int key = 0;
int err, fd = 0;
+ LIBBPF_OPTS(bpf_link_create_opts, opts, .flags = XDP_FLAGS_DRV_MODE);
if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) {
struct bpf_devmap_val entry = {
.ifindex = env.ifindex,
};
@@ -330,70 +406,112 @@ static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
}
default:
return -EINVAL;
}
- err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
- if (err)
+ err = bpf_link_create(bpf_program__fd(prog), env.ifindex, BPF_XDP,
+ &opts);
+ if (err < 0)
fprintf(stderr, "Failed attaching XDP program to device %s\n",
env.ifname);
return err;
}
+static int send_msg(int sockfd, const void *buf, size_t len)
+{
+ const char *data = buf;
+
+ while (len && !should_exit()) {
+ ssize_t n = send(sockfd, data, len, MSG_NOSIGNAL);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (!n)
+ return -EIO;
+ data += n;
+ len -= n;
+ }
+ return len ? -EINTR : 0;
+}
+
static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
size_t val_size)
{
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
- ssize_t len;
+ ssize_t received;
+ size_t len;
- len = recv(sockfd, buf, bufsize, 0);
- if (len < 0)
+ received = recv(sockfd, buf, sizeof(*tlv), MSG_WAITALL);
+ if (received < 0)
return -errno;
- if (!len)
+ if (received != sizeof(*tlv))
return -ECONNRESET;
- if (len < sizeof(*tlv) || len != ntohs(tlv->len))
+ len = ntohs(tlv->len);
+ if (len < sizeof(*tlv) || len > bufsize)
return -EINVAL;
+ if (len > sizeof(*tlv)) {
+ received = recv(sockfd, tlv->data, len - sizeof(*tlv), MSG_WAITALL);
+ if (received < 0 || (size_t)received != len - sizeof(*tlv))
+ return -ECONNRESET;
+ }
if (val) {
len -= sizeof(*tlv);
- if (len > val_size)
+ if (len != val_size)
return -ENOMEM;
memcpy(val, tlv->data, len);
}
return 0;
}
static int dut_run(struct xdp_features *skel)
{
- int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
- int state = CMD_STOP, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
+ int link_fd = -1;
+ int state = CMD_STOP, err = 0, sockfd, ctrl_sockfd, echo_sockfd;
struct sockaddr_storage ctrl_addr;
pthread_t dut_thread;
- bool thread_started = false, xdp_attached = false;
+ bool thread_started = false;
+ int attempt;
socklen_t addrlen = sizeof(ctrl_addr);
- sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
- DUT_CTRL_PORT, 0, 1);
- if (!sockfd) {
+ sockfd = start_server(SOCK_STREAM, DUT_CTRL_PORT);
+ if (sockfd < 0) {
fprintf(stderr,
"Failed creating control socket on device %s\n", env.ifname);
return -errno;
}
- ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen);
+ /* Allow remote startup and BPF loading on slow test machines. */
+ ctrl_sockfd = -1;
+ for (attempt = 0; attempt < 20 && !should_exit(); attempt++) {
+ addrlen = sizeof(ctrl_addr);
+ ctrl_sockfd = accept(sockfd, (struct sockaddr *)&ctrl_addr,
+ &addrlen);
+ if (ctrl_sockfd >= 0 || (errno != EAGAIN && errno != EINTR))
+ break;
+ }
if (ctrl_sockfd < 0) {
fprintf(stderr,
"Failed accepting connections on device %s control socket\n",
env.ifname);
- free_fds(sockfd, 1);
- return -errno;
+ err = should_exit() ? -EINTR : -errno;
+ close(sockfd);
+ return err;
+ }
+
+ if (settimeo(ctrl_sockfd, 3000)) {
+ err = -errno;
+ goto out;
}
/* CTRL loop */
- while (!exiting) {
+ while (!should_exit()) {
unsigned char buf[BUFSIZE] = {};
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
if (err == -EAGAIN || err == -EINTR)
@@ -406,42 +524,52 @@ static int dut_run(struct xdp_features *skel)
if (state == CMD_START)
continue;
state = CMD_START;
/* Load the XDP program on the DUT */
- err = dut_attach_xdp_prog(skel, flags);
- if (err)
+ link_fd = dut_attach_xdp_prog(skel);
+ if (link_fd < 0) {
+ err = link_fd;
goto out;
-
- xdp_attached = true;
+ }
err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
if (err < 0)
goto out;
thread_started = true;
tlv->type = htons(CMD_ACK);
tlv->len = htons(sizeof(*tlv));
- err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
+ err = send_msg(ctrl_sockfd, buf, sizeof(*tlv));
if (err < 0)
goto end_thread;
break;
}
case CMD_STOP:
if (state != CMD_START)
break;
- state = CMD_STOP;
-
- exiting = true;
- bpf_xdp_detach(env.ifindex, flags, NULL);
- xdp_attached = false;
-
tlv->type = htons(CMD_ACK);
tlv->len = htons(sizeof(*tlv));
- err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
+ err = send_msg(ctrl_sockfd, buf, sizeof(*tlv));
goto end_thread;
+ case CMD_GET_MAC: {
+ struct ifreq ifr = {};
+
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", env.ifname);
+ if (ioctl(sockfd, SIOCGIFHWADDR, &ifr)) {
+ err = -errno;
+ goto end_thread;
+ }
+ tlv->type = htons(CMD_ACK);
+ tlv->len = htons(sizeof(*tlv) + ETH_ALEN);
+ memcpy(tlv->data, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+ err = send_msg(ctrl_sockfd, buf, sizeof(*tlv) + ETH_ALEN);
+ if (err)
+ goto end_thread;
+ break;
+ }
case CMD_GET_XDP_CAP: {
LIBBPF_OPTS(bpf_xdp_query_opts, opts);
unsigned long long val;
size_t n;
@@ -459,11 +587,11 @@ static int dut_run(struct xdp_features *skel)
tlv->len = htons(n);
val = htobe64(opts.feature_flags);
memcpy(tlv->data, &val, sizeof(val));
- err = send(ctrl_sockfd, buf, n, 0);
+ err = send_msg(ctrl_sockfd, buf, n);
if (err < 0)
goto end_thread;
break;
}
case CMD_GET_STATS: {
@@ -484,11 +612,11 @@ static int dut_run(struct xdp_features *skel)
tlv->len = htons(n);
val = htonl(val);
memcpy(tlv->data, &val, sizeof(val));
- err = send(ctrl_sockfd, buf, n, 0);
+ err = send_msg(ctrl_sockfd, buf, n);
if (err < 0)
goto end_thread;
break;
}
default:
@@ -501,181 +629,301 @@ end_thread:
if (thread_started) {
shutdown(echo_sockfd, SHUT_RDWR);
pthread_join(dut_thread, NULL);
}
out:
- if (xdp_attached)
- bpf_xdp_detach(env.ifindex, flags, NULL);
+ if (link_fd >= 0)
+ close(link_fd);
close(ctrl_sockfd);
- free_fds(sockfd, 1);
+ close(sockfd);
return err;
}
-static bool tester_collect_detected_cap(struct xdp_features *skel,
- unsigned int dut_stats)
-{
- unsigned int err, key = 0, val;
-
- if (!dut_stats)
- return false;
-
- err = bpf_map__lookup_elem(skel->maps.stats, &key, sizeof(key),
- &val, sizeof(val), 0);
- if (err) {
- fprintf(stderr, "bpf_map_lookup_elem failed (%d)\n", err);
- return false;
- }
-
- switch (env.feature.action) {
- case XDP_PASS:
- case XDP_TX:
- case XDP_REDIRECT:
- return val > 0;
- case XDP_DROP:
- case XDP_ABORTED:
- return val == 0;
- default:
- break;
- }
-
- if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
- return val > 0;
-
- return false;
-}
-
static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val,
size_t val_size)
{
unsigned char buf[BUFSIZE] = {};
struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
int err;
tlv->type = htons(cmd);
tlv->len = htons(sizeof(*tlv));
- err = send(sockfd, buf, sizeof(*tlv), 0);
+ err = send_msg(sockfd, buf, sizeof(*tlv));
if (err < 0)
return err;
err = recv_msg(sockfd, buf, BUFSIZE, val, val_size);
if (err < 0)
return err;
return ntohs(tlv->type) == CMD_ACK ? 0 : -EINVAL;
}
-static int send_echo_msg(void)
+/* Compute checksums in software; packet sockets do not request TX offload. */
+static __u16 packet_checksum(const void *data, size_t len)
{
- unsigned char buf[sizeof(struct tlv_hdr)];
- struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
- int sockfd, n;
+ const unsigned char *p = data;
+ unsigned int sum = 0;
- sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
- if (sockfd < 0) {
- fprintf(stderr,
- "Failed creating data UDP socket on device %s\n",
- env.ifname);
- return -errno;
+ while (len > 1) {
+ sum += (p[0] << 8) | p[1];
+ p += 2;
+ len -= 2;
}
+ if (len)
+ sum += p[0] << 8;
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
- tlv->type = htons(CMD_ECHO);
- tlv->len = htons(sizeof(*tlv));
+static size_t make_echo_packet(unsigned char *packet, __be16 source_port)
+{
+ const struct in6_addr *src = &((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr;
+ const struct in6_addr *dst = &((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr;
+ struct udphdr udp = {
+ .source = source_port,
+ .dest = htons(DUT_ECHO_PORT),
+ .len = htons(sizeof(udp) + sizeof(struct tlv_hdr)),
+ };
+ struct tlv_hdr echo = {
+ .type = htons(CMD_ECHO),
+ .len = htons(sizeof(echo)),
+ };
+ unsigned char pseudo[40 + sizeof(udp) + sizeof(echo)] = {};
+ size_t ip_len, pseudo_len;
+ __u16 checksum;
+
+ if (IN6_IS_ADDR_V4MAPPED(dst)) {
+ struct iphdr ip = {
+ .version = 4, .ihl = 5, .ttl = 64,
+ .protocol = IPPROTO_UDP,
+ .tot_len = htons(sizeof(ip) + sizeof(udp) + sizeof(echo)),
+ };
- n = sendto(sockfd, buf, sizeof(*tlv), MSG_NOSIGNAL | MSG_CONFIRM,
- (struct sockaddr *)&env.dut_addr, sizeof(env.dut_addr));
- close(sockfd);
+ memcpy(&ip.saddr, &src->s6_addr[12], 4);
+ memcpy(&ip.daddr, &dst->s6_addr[12], 4);
+ ip.check = htons(packet_checksum(&ip, sizeof(ip)));
+ memcpy(packet, &ip, sizeof(ip));
+ memcpy(pseudo, &ip.saddr, 4);
+ memcpy(pseudo + 4, &ip.daddr, 4);
+ pseudo[9] = IPPROTO_UDP;
+ memcpy(pseudo + 10, &udp.len, 2);
+ pseudo_len = 12;
+ ip_len = sizeof(ip);
+ } else {
+ struct ipv6hdr ip = {
+ .version = 6, .hop_limit = 64,
+ .nexthdr = IPPROTO_UDP, .payload_len = udp.len,
+ .saddr = *src, .daddr = *dst,
+ };
+ __be32 len = htonl(sizeof(udp) + sizeof(echo));
+
+ memcpy(packet, &ip, sizeof(ip));
+ memcpy(pseudo, src, sizeof(*src));
+ memcpy(pseudo + 16, dst, sizeof(*dst));
+ memcpy(pseudo + 32, &len, sizeof(len));
+ pseudo[39] = IPPROTO_UDP;
+ pseudo_len = 40;
+ ip_len = sizeof(ip);
+ }
+ memcpy(pseudo + pseudo_len, &udp, sizeof(udp));
+ memcpy(pseudo + pseudo_len + sizeof(udp), &echo, sizeof(echo));
+ checksum = packet_checksum(pseudo, pseudo_len + sizeof(udp) + sizeof(echo));
+ udp.check = htons(checksum ? checksum : 0xffff);
+ memcpy(packet + ip_len, &udp, sizeof(udp));
+ memcpy(packet + ip_len + sizeof(udp), &echo, sizeof(echo));
+ return ip_len + sizeof(udp) + sizeof(echo);
+}
- return n == ntohs(tlv->len) ? 0 : -EINVAL;
+static bool is_echo_reply(const unsigned char *packet, size_t len,
+ __be16 source_port)
+{
+ const struct in6_addr *tester = &((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr;
+ const struct in6_addr *dut = &((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr;
+ bool reflected = env.feature.action == XDP_TX ||
+ env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT;
+ const struct in6_addr *src = reflected ? tester : dut;
+ const struct in6_addr *dst = reflected ? dut : tester;
+ struct tlv_hdr echo;
+ struct udphdr udp;
+ size_t ip_len;
+
+ if (IN6_IS_ADDR_V4MAPPED(dut)) {
+ struct iphdr ip;
+
+ if (len < sizeof(ip))
+ return false;
+ memcpy(&ip, packet, sizeof(ip));
+ ip_len = ip.ihl * 4;
+ if (ip.version != 4 || ip_len < sizeof(ip) ||
+ ip.protocol != IPPROTO_UDP ||
+ memcmp(&ip.saddr, &src->s6_addr[12], 4) ||
+ memcmp(&ip.daddr, &dst->s6_addr[12], 4))
+ return false;
+ } else {
+ struct ipv6hdr ip;
+
+ if (len < sizeof(ip))
+ return false;
+ memcpy(&ip, packet, sizeof(ip));
+ ip_len = sizeof(ip);
+ if (ip.version != 6 || ip.nexthdr != IPPROTO_UDP ||
+ memcmp(&ip.saddr, src, sizeof(*src)) ||
+ memcmp(&ip.daddr, dst, sizeof(*dst)))
+ return false;
+ }
+ if (len < ip_len + sizeof(udp) + sizeof(echo))
+ return false;
+ memcpy(&udp, packet + ip_len, sizeof(udp));
+ memcpy(&echo, packet + ip_len + sizeof(udp), sizeof(echo));
+ return udp.source == (reflected ? source_port : htons(DUT_ECHO_PORT)) &&
+ udp.dest == (reflected ? htons(DUT_ECHO_PORT) : source_port) &&
+ echo.type == htons(CMD_ECHO) && echo.len == htons(sizeof(echo));
}
-static int tester_run(struct xdp_features *skel)
+static long long monotonic_ms(void)
{
- int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
- unsigned long long advertised_feature;
- struct bpf_program *prog;
- unsigned int stats;
- int i, err, sockfd;
- bool detected_cap, xdp_attached = false;
+ struct timespec ts;
- sockfd = socket(AF_INET6, SOCK_STREAM, 0);
- if (sockfd < 0) {
- fprintf(stderr,
- "Failed creating tester service control socket\n");
- return -errno;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+static int collect_echoes(int fd, __be16 source_port, unsigned int *count)
+{
+ long long deadline = monotonic_ms() + 1000;
+ struct pollfd pfd = { .fd = fd, .events = POLLIN };
+
+ while (!should_exit()) {
+ unsigned char packet[2048];
+ struct sockaddr_ll from;
+ socklen_t addrlen = sizeof(from);
+ int remaining = deadline - monotonic_ms();
+ ssize_t len;
+ int err;
+
+ if (remaining <= 0)
+ break;
+ err = poll(&pfd, 1, remaining);
+ if (err < 0) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (!err)
+ break;
+ len = recvfrom(fd, packet, sizeof(packet), MSG_DONTWAIT,
+ (struct sockaddr *)&from, &addrlen);
+ if (len < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (env.verbosity)
+ fprintf(stderr, "packet type=%u len=%zd match=%d\n",
+ from.sll_pkttype, len,
+ is_echo_reply(packet, len, source_port));
+ if (from.sll_pkttype != PACKET_OUTGOING &&
+ is_echo_reply(packet, len, source_port))
+ (*count)++;
}
+ return should_exit() ? -EINTR : 0;
+}
- if (settimeo(sockfd, 1000) < 0)
- return -EINVAL;
+static int tester_run(void)
+{
+ struct sockaddr_ll peer = {
+ .sll_family = AF_PACKET, .sll_ifindex = env.ifindex,
+ .sll_protocol = htons(ETH_P_ALL), .sll_halen = ETH_ALEN,
+ };
+ struct sockaddr_in6 local = *(struct sockaddr_in6 *)&env.tester_addr;
+ unsigned long long advertised_feature;
+ unsigned int stats, received = 0;
+ unsigned char packet[128];
+ int data_fd = -1, port_fd = -1;
+ socklen_t addrlen = sizeof(local);
+ int i, err, sockfd;
+ size_t packet_len;
+ bool detected_cap;
- err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
- sizeof(env.dut_ctrl_addr));
- if (err) {
- fprintf(stderr,
- "Failed connecting to the Device Under Test control socket\n");
+ sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (sockfd < 0)
return -errno;
+ if (settimeo(sockfd, 30000) ||
+ connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
+ sizeof(struct sockaddr_in6))) {
+ err = -errno;
+ goto out;
}
+ err = send_and_recv_msg(sockfd, CMD_GET_MAC, peer.sll_addr, ETH_ALEN);
+ if (err)
+ goto out;
- err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
- sizeof(advertised_feature));
- if (err < 0) {
- close(sockfd);
- return err;
+ /* Reserve an ephemeral source port without changing the peer NIC. */
+ port_fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (port_fd < 0 || bind(port_fd, (struct sockaddr *)&local, sizeof(local)) ||
+ getsockname(port_fd, (struct sockaddr *)&local, &addrlen)) {
+ err = -errno;
+ goto out;
}
-
- advertised_feature = be64toh(advertised_feature);
-
- if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT ||
- env.feature.action == XDP_TX)
- prog = skel->progs.xdp_tester_check_tx;
- else
- prog = skel->progs.xdp_tester_check_rx;
-
- err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
- if (err) {
- fprintf(stderr, "Failed attaching XDP program to device %s\n",
- env.ifname);
+ data_fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
+ if (data_fd < 0 || bind(data_fd, (struct sockaddr *)&peer, sizeof(peer))) {
+ err = -errno;
goto out;
}
-
- xdp_attached = true;
+ peer.sll_protocol = IN6_IS_ADDR_V4MAPPED(&local.sin6_addr) ?
+ htons(ETH_P_IP) : htons(ETH_P_IPV6);
+ packet_len = make_echo_packet(packet, local.sin6_port);
err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);
if (err)
goto out;
+ err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
+ sizeof(advertised_feature));
+ if (err)
+ goto out;
+ advertised_feature = be64toh(advertised_feature);
+ for (i = 0; i < 10 && !should_exit(); i++) {
+ ssize_t len;
- for (i = 0; i < 10 && !exiting; i++) {
- err = send_echo_msg();
- if (err < 0)
+ len = sendto(data_fd, packet, packet_len, 0,
+ (struct sockaddr *)&peer, sizeof(peer));
+ if (len < 0 || (size_t)len != packet_len) {
+ err = len < 0 ? -errno : -EIO;
+ goto out;
+ }
+ err = collect_echoes(data_fd, local.sin6_port, &received);
+ if (err)
goto out;
-
- sleep(1);
}
-
err = send_and_recv_msg(sockfd, CMD_GET_STATS, &stats, sizeof(stats));
if (err)
goto out;
-
- /* Sample before teardown, which can produce additional echo traffic. */
- detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
-
- /* stop the test */
+ detected_cap = ntohl(stats) &&
+ ((env.feature.action == XDP_DROP ||
+ env.feature.action == XDP_ABORTED) ? !received : received);
+ if (env.verbosity)
+ fprintf(stderr, "dut=%u received=%u advertised=%llx\n",
+ ntohl(stats), received, advertised_feature);
err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 0);
- /* send a new echo message to wake echo thread of the dut */
- send_echo_msg();
-
+ if (err)
+ goto out;
fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
: RED("NOT ADVERTISED"));
if (!err && detected_cap != !!(env.feature.drv_feature & advertised_feature))
err = -EINVAL;
out:
- if (xdp_attached)
- bpf_xdp_detach(env.ifindex, flags, NULL);
+ if (data_fd >= 0)
+ close(data_fd);
+ if (port_fd >= 0)
+ close(port_fd);
close(sockfd);
- return err < 0 ? err : 0;
+ return err;
}
static int dut_set_trace_ids(struct xdp_features *skel)
{
struct bpf_prog_info prog = {};
@@ -704,10 +952,12 @@ int main(int argc, char **argv)
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
libbpf_set_print(libbpf_print_fn);
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
+ signal(SIGALRM, sig_handler);
+ alarm(90);
set_env_default();
/* Parse command line arguments */
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
@@ -717,10 +967,24 @@ int main(int argc, char **argv)
if (env.ifindex < 0) {
fprintf(stderr, "Invalid device name %s\n", env.ifname);
return -ENODEV;
}
+ if (env.query_only) {
+ LIBBPF_OPTS(bpf_xdp_query_opts, opts);
+
+ err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE, &opts);
+ if (err)
+ return 1;
+ return opts.feature_flags & NETDEV_XDP_ACT_BASIC ? 0 : 4;
+ }
+
+ if (env.is_tester) {
+ err = tester_run();
+ return atomic_load(&stop_signal) || err ? EXIT_FAILURE : EXIT_SUCCESS;
+ }
+
/* Load and verify BPF application */
skel = xdp_features__open();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return -EINVAL;
@@ -746,21 +1010,12 @@ int main(int argc, char **argv)
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
- if (env.is_tester) {
- /* Tester */
- fprintf(stdout, "Starting tester service on device %s\n",
- env.ifname);
- err = tester_run(skel);
- } else {
- /* DUT */
- fprintf(stdout, "Starting test on device %s\n", env.ifname);
- err = dut_run(skel);
- }
+ err = dut_run(skel);
cleanup:
xdp_features__destroy(skel);
- return err < 0 ? -err : 0;
+ return atomic_load(&stop_signal) || err ? EXIT_FAILURE : EXIT_SUCCESS;
}
diff --git a/tools/testing/selftests/bpf/xdp_features.h b/tools/testing/selftests/bpf/xdp_features.h
index 2670c541713b6..342c794563f4e 100644
--- a/tools/testing/selftests/bpf/xdp_features.h
+++ b/tools/testing/selftests/bpf/xdp_features.h
@@ -6,10 +6,11 @@ enum test_commands {
CMD_START, /* CMD */
CMD_ECHO, /* CMD */
CMD_ACK, /* CMD + data */
CMD_GET_XDP_CAP, /* CMD */
CMD_GET_STATS, /* CMD */
+ CMD_GET_MAC, /* CMD */
};
#define DUT_CTRL_PORT 12345
#define DUT_ECHO_PORT 12346
--
Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
` (2 preceding siblings ...)
2026-10-18 14:05 ` [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
@ 2026-10-18 14:05 ` Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
From: Bochao Cao <bochaolucky@gmail.com>
Add a shared skeleton rule and build host bpftool when BPFTOOL is not
supplied. Use host CC/LD and absolute source paths in submakes.
Pass KHDR_INCLUDES to the BPF compiler without overriding it with
source-tree headers. Generate the skeleton through a temporary file.
Tested normal and separate-output builds, inherited relative srctree,
and host bootstrap with unusable target CC/LD. A conflicting source
header no longer overrides the installed header.
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
tools/testing/selftests/net/bpf.mk | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/bpf.mk b/tools/testing/selftests/net/bpf.mk
index a4f6755dd8941..016e32960280a 100644
--- a/tools/testing/selftests/net/bpf.mk
+++ b/tools/testing/selftests/net/bpf.mk
@@ -4,12 +4,12 @@ CLANG ?= clang
SCRATCH_DIR := $(OUTPUT)/tools
BUILD_DIR := $(SCRATCH_DIR)/build
BPFDIR := $(top_srcdir)/tools/lib/bpf
APIDIR := $(top_srcdir)/tools/include/uapi
+CCINCLUDE += $(KHDR_INCLUDES)
CCINCLUDE += -I$(selfdir)/bpf
-CCINCLUDE += -I$(top_srcdir)/usr/include/
CCINCLUDE += -I$(SCRATCH_DIR)/include
BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
MAKE_DIRS := $(BUILD_DIR)/libbpf
@@ -44,10 +44,39 @@ $(BPF_PROG_OBJS): $(OUTPUT)/%.o : %.c $(BPFOBJ) | $(MAKE_DIRS)
$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
$(APIDIR)/linux/bpf.h \
| $(BUILD_DIR)/libbpf
$(call msg,MAKE,,$@)
- $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) srctree=$(abspath $(top_srcdir)) \
+ OUTPUT=$(BUILD_DIR)/libbpf/ \
EXTRA_CFLAGS='-g -O0' \
DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
EXTRA_CLEAN += $(SCRATCH_DIR)
+
+# Build a host bpftool only when a test needs a generated skeleton.
+HOSTCC ?= gcc
+HOSTLD ?= ld
+BPFTOOL_BUILD := $(BUILD_DIR)/bpftool
+ifeq ($(origin BPFTOOL),undefined)
+BPFTOOL := $(BPFTOOL_BUILD)/bootstrap/bpftool
+BPFTOOL_DEP := $(BPFTOOL)
+else
+BPFTOOL_DEP := $(shell command -v $(BPFTOOL))
+endif
+
+$(BPFTOOL_BUILD)/bootstrap/bpftool: \
+ $(wildcard $(top_srcdir)/tools/bpf/bpftool/*.[ch]) \
+ $(top_srcdir)/tools/bpf/bpftool/Makefile \
+ $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile)
+ $(call msg,MAKE,,$@)
+ $(Q)mkdir -p $(BPFTOOL_BUILD)
+ $(Q)$(MAKE) $(submake_extras) -C $(top_srcdir)/tools/bpf/bpftool \
+ OUTPUT=$(BPFTOOL_BUILD)/ srctree=$(abspath $(top_srcdir)) \
+ ARCH= CROSS_COMPILE= CC="$(HOSTCC)" LD="$(HOSTLD)" bootstrap
+
+$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o $(BPFTOOL_DEP)
+ $(call msg,GEN-SKEL,,$@)
+ $(Q)$(BPFTOOL) gen skeleton $< name $(notdir $*) > $@.tmp
+ $(Q)mv $@.tmp $@
+
+EXTRA_CLEAN += $(wildcard $(OUTPUT)/*.skel.h $(OUTPUT)/*.skel.h.tmp)
--
Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
` (3 preceding siblings ...)
2026-10-18 14:05 ` [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
@ 2026-10-18 14:05 ` Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-18 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Simon Horman
Cc: linux-kernel, bpf, linux-kselftest, netdev, Alexis Lothoré,
Lorenzo Bianconi, Tiezhu Yang, Bochao Cao, Willem de Bruijn,
Petr Machata
From: Bochao Cao <bochaolucky@gmail.com>
Replace the private-veth script with a NetDrvEpEnv test for a local DUT
and remote tester. Build the helper with the shared BPF rules and
installed kernel headers.
Compare detected and advertised features, wait for DUT readiness and
bound the remote helper lifetime. Pass local commands and scp arguments
as lists so paths containing spaces work.
Run veth XDP_TX when peer GRO or native XDP provides NAPI. Skip only
when neither is present. Read GRO with ethtool without changing the
peer configuration.
Tested the runner's skip, failure and pass paths on Ubuntu 6.8 with
ethtool 5.16. With isolated veth modules, IPv4 and IPv6 probes receive
0/10 replies before the GRO-only XDP_TX flush fix and 10/10 after it.
Existing peer XDP programs and offload settings are preserved. SSH
deployment and execution from a path with spaces and a quote also pass.
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
tools/testing/selftests/bpf/.gitignore | 1 -
tools/testing/selftests/bpf/Makefile | 9 +-
tools/testing/selftests/bpf/test_xdp_features.sh | 175 ---------------------
tools/testing/selftests/drivers/net/README.rst | 19 +++
tools/testing/selftests/drivers/net/hw/.gitignore | 3 +
tools/testing/selftests/drivers/net/hw/Makefile | 15 +-
tools/testing/selftests/drivers/net/hw/config | 3 +
.../net/hw/xdp_features.bpf.c} | 0
.../{bpf => drivers/net/hw}/xdp_features.c | 2 +
.../{bpf => drivers/net/hw}/xdp_features.h | 0
.../selftests/drivers/net/hw/xdp_features.py | 146 +++++++++++++++++
.../selftests/drivers/net/lib/py/remote_ssh.py | 2 +-
12 files changed, 189 insertions(+), 186 deletions(-)
diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index 986a6389186ba..a82717a2f81aa 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -39,11 +39,10 @@ test_cpp
*.tmp
xskxceiver
xdp_redirect_multi
xdp_synproxy
xdp_hw_metadata
-xdp_features
verification_cert.h
*.BTF
*.BTF_ids
*.BTF.base
usdt_1
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 883c92701b9f1..cb76b68c97dbb 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -124,11 +124,11 @@ TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
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 := \
ima_setup.sh verify_sig_setup.sh
TEST_KMODS := bpf_testmod.ko bpf_test_no_cfi.ko bpf_test_modorder_x.ko \
@@ -140,11 +140,10 @@ TEST_GEN_PROGS_EXTENDED = \
bench \
flow_dissector_load \
test_cpp \
test_lirc_mode2_user \
veristat \
- xdp_features \
xdp_hw_metadata \
xdp_synproxy \
xskxceiver
TEST_GEN_FILES += $(TEST_KMODS) liburandom_read.so urandom_read sign-file uprobe_multi
@@ -542,11 +541,10 @@ 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
tracing_multi_module.skel.h-deps := tracing_multi_attach_module.bpf.o tracing_multi_check.bpf.o
tracing_multi_intersect.skel.h-deps := tracing_multi_intersect_attach.bpf.o tracing_multi_check.bpf.o
tracing_multi_session.skel.h-deps := tracing_multi_session_attach.bpf.o tracing_multi_check.bpf.o
@@ -945,15 +943,10 @@ $(OUTPUT)/xdp_hw_metadata: xdp_hw_metadata.c xsk.c network_helpers.c \
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 $@
-$(OUTPUT)/xdp_features: xdp_features.c xdp_features.h \
- $(OUTPUT)/xdp_features.skel.h $(BPFOBJ) | $(OUTPUT)
- $(call msg,BINARY,,$@)
- $(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
-
# Make sure we are able to include and link libbpf against c++.
CXXFLAGS += $(CFLAGS)
CXXFLAGS := $(subst -D_GNU_SOURCE=,,$(CXXFLAGS))
CXXFLAGS := $(subst -std=gnu11,-std=gnu++11,$(CXXFLAGS))
$(OUTPUT)/test_cpp: test_cpp.cpp $(OUTPUT)/test_core_extern.skel.h $(BPFOBJ)
diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
deleted file mode 100755
index ad221c530b4ef..0000000000000
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ /dev/null
@@ -1,175 +0,0 @@
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-
-readonly NS="ns1-$(mktemp -u XXXXXX)"
-readonly DUT_NS="dut-${NS}"
-readonly V0_IP4=10.10.0.11
-readonly V1_IP4=10.10.0.1
-readonly V0_IP6=2001:db8::11
-readonly V1_IP6=2001:db8::1
-
-ret=1
-dut_pid=""
-peer_created=false
-dut_created=false
-setup_in_progress=true
-pending_signal=0
-
-setup() {
- {
- [ ! -e "/run/netns/${NS}" ]
- peer_created=true
- ip netns add "${NS}"
- [ ! -e "/run/netns/${DUT_NS}" ]
- dut_created=true
- ip netns add "${DUT_NS}"
-
- ip -n "$DUT_NS" link add v1 type veth peer name v0 netns ${NS}
-
- ip -n "$DUT_NS" link set v1 up
- ip -n "$DUT_NS" addr add $V1_IP4/24 dev v1
- ip -n "$DUT_NS" addr add $V1_IP6/64 nodad dev v1
- ip -n ${NS} link set dev v0 up
- ip -n ${NS} addr add $V0_IP4/24 dev v0
- ip -n ${NS} addr add $V0_IP6/64 nodad dev v0
-
- # Enable XDP mode and disable checksum offload
- ip netns exec "$DUT_NS" ethtool -K v1 gro on
- ip netns exec "$DUT_NS" ethtool -K v1 tx-checksumming off
- ip netns exec ${NS} ethtool -K v0 gro on
- ip netns exec ${NS} ethtool -K v0 tx-checksumming off
- } > /dev/null 2>&1
- setup_in_progress=false
- [ "$pending_signal" -eq 0 ] || exit "$pending_signal"
-}
-
-terminate_dut_server() {
- local pid
-
- # Use the shell job instead of dut_pid, which may not have been assigned
- # yet if a signal arrived immediately after the server was started.
- pid=$(jobs -pr 2> /dev/null) || true
- if [ -z "$pid" ]; then
- dut_pid=""
- return
- fi
-
- kill -KILL %% 2> /dev/null || true
- wait "$pid" 2> /dev/null || true
- dut_pid=""
-}
-
-cleanup() {
- trap '' HUP INT QUIT ABRT TERM
- trap - EXIT
- terminate_dut_server
- if "$dut_created"; then
- ip netns del "$DUT_NS" 2> /dev/null || true
- fi
- if "$peer_created"; then
- ip netns del "$NS" 2> /dev/null || true
- fi
-}
-
-wait_for_dut_server() {
- local attempt
-
- for ((attempt = 0; attempt < 30; attempt++)); do
- if [ "$(jobs -pr 2> /dev/null)" != "$dut_pid" ]; then
- echo "xdp_features server $dut_pid exited before readiness" >&2
- return 1
- fi
-
- if ip netns exec "$DUT_NS" ss -tlp 2> /dev/null |
- grep -q "pid=$dut_pid,"; then
- return 0
- fi
-
- sleep 1
- done
- echo "xdp_features server $dut_pid did not become ready" >&2
- return 1
-}
-
-start_dut_server() {
- ip netns exec "$DUT_NS" ./xdp_features "$@" &
- dut_pid=$!
- wait_for_dut_server
-}
-
-reap_dut_server() {
- local status=0
-
- wait "$dut_pid" || status=$?
- dut_pid=""
- return "$status"
-}
-
-test_xdp_features() {
- setup
-
- ## XDP_PASS
- start_dut_server -f XDP_PASS -D $V1_IP6 -T $V0_IP6 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_PASS \
- -D $V1_IP6 -C $V1_IP6 \
- -T $V0_IP6 v0
- [ $? -ne 0 ] && exit
- reap_dut_server
-
- ## XDP_DROP
- start_dut_server -f XDP_DROP -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_DROP \
- -D ::ffff:$V1_IP4 \
- -C ::ffff:$V1_IP4 \
- -T ::ffff:$V0_IP4 v0
- [ $? -ne 0 ] && exit
- reap_dut_server
-
- ## XDP_ABORTED
- start_dut_server -f XDP_ABORTED -D $V1_IP6 -T $V0_IP6 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_ABORTED \
- -D $V1_IP6 -C $V1_IP6 \
- -T $V0_IP6 v0
- [ $? -ne 0 ] && exit
- reap_dut_server
-
- # veth only flushes XDP_TX to a peer with native XDP attached.
- echo "SKIP: XDP_TX needs a peer native XDP program"
-
- ## XDP_REDIRECT
- start_dut_server -f XDP_REDIRECT -D $V1_IP6 -T $V0_IP6 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_REDIRECT \
- -D $V1_IP6 -C $V1_IP6 \
- -T $V0_IP6 v0
- [ $? -ne 0 ] && exit
- reap_dut_server
-
- ## XDP_NDO_XMIT
- start_dut_server -f XDP_NDO_XMIT -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1
- ip netns exec ${NS} ./xdp_features -t -f XDP_NDO_XMIT \
- -D ::ffff:$V1_IP4 \
- -C ::ffff:$V1_IP4 \
- -T ::ffff:$V0_IP4 v0
- ret=$?
- reap_dut_server
-}
-
-handle_signal() {
- if "$setup_in_progress"; then
- pending_signal=$1
- else
- exit "$1"
- fi
-}
-
-set -e
-trap cleanup EXIT
-trap 'handle_signal 129' HUP
-trap 'handle_signal 130' INT
-trap 'handle_signal 131' QUIT
-trap 'handle_signal 134' ABRT
-trap 'handle_signal 143' TERM
-
-test_xdp_features
-
-exit $ret
diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
index 3fe49bce4f3a5..d66173580499b 100644
--- a/tools/testing/selftests/drivers/net/README.rst
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -308,5 +308,24 @@ The code should be clean, avoid disabling pylint warnings explicitly!
Running tests CI-style
======================
See https://github.com/linux-netdev/nipa/wiki for instructions on how
to easily run the tests using ``virtme-ng``.
+
+XDP feature test
+================
+
+``hw/xdp_features.py`` compares the DUT's advertised XDP capabilities with
+packet observations. The endpoints must be directly connected at layer 2.
+The remote helper needs packet-socket privileges and GNU ``timeout``, but
+does not load BPF, attach XDP, or change interface/offload settings.
+Probe checksums are computed in software; TX checksum offload may remain
+at its configured value on both endpoints.
+
+A DUT without advertised native XDP support is skipped. On veth,
+``XDP_TX`` requires peer NAPI, enabled by GRO or an existing native XDP
+program. The TX subtest is skipped when neither is present. The test
+reads GRO with ``ethtool`` and does not change the remote configuration.
+Kernels without the veth GRO-only XDP_TX flush fix fail this subtest
+when peer GRO is enabled without native XDP. Advertised versus observed
+mismatches are failures, including unsupported features that unexpectedly
+work.
diff --git a/tools/testing/selftests/drivers/net/hw/.gitignore b/tools/testing/selftests/drivers/net/hw/.gitignore
index 46540468a7753..a0cd8bc3739d8 100644
--- a/tools/testing/selftests/drivers/net/hw/.gitignore
+++ b/tools/testing/selftests/drivers/net/hw/.gitignore
@@ -1,4 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
iou-zcrx
ncdevmem
toeplitz
+tools/
+xdp_features
+xdp_features.skel.h
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 18ce235a77f84..bd818e8625c23 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -49,10 +49,11 @@ TEST_PROGS = \
toeplitz.py \
tso.py \
userns_devmem.py \
uso.py \
vlan.py \
+ xdp_features.py \
xdp_metadata.py \
xsk_reconfig.py \
#
TEST_PROGS_EXTENDED := \
@@ -77,11 +78,11 @@ TEST_INCLUDES := \
YNL_GEN_FILES := \
ncdevmem \
toeplitz \
$(COND_GEN_FILES) \
# end of YNL_GEN_FILES
-TEST_GEN_FILES += $(YNL_GEN_FILES)
+TEST_GEN_FILES += $(YNL_GEN_FILES) xdp_features
TEST_GEN_FILES += $(patsubst %.c,%.o,$(wildcard *.bpf.c))
include ../../../lib.mk
# YNL build
@@ -92,8 +93,20 @@ YNL_GENS := \
include ../../../net/ynl.mk
include ../../../net/bpf.mk
+$(OUTPUT)/xdp_features.bpf.o: xdp_features.h
+
+$(OUTPUT)/xdp_features: CFLAGS += -I$(OUTPUT) -I$(SCRATCH_DIR)/include
+$(OUTPUT)/xdp_features: CFLAGS += -I../../../net/lib
+$(OUTPUT)/xdp_features: LDLIBS += -lelf -lz -lpthread
+$(OUTPUT)/xdp_features: xdp_features.c xdp_features.h \
+ $(OUTPUT)/xdp_features.skel.h $(BPFOBJ)
+ $(call msg,BINARY,,$@)
+ $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(KHDR_INCLUDES) \
+ $(filter %.c %.o,$^) $(BPFOBJ) \
+ $(LDFLAGS) $(LDLIBS) -o $@
+
ifeq ($(HAS_IOURING_ZCRX),y)
$(OUTPUT)/iou-zcrx: LDLIBS += -luring -lpthread
endif
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index c6c2b64bb7124..14a8041b468ba 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -1,6 +1,8 @@
+CONFIG_BPF_EVENTS=y
CONFIG_BPF_SYSCALL=y
+CONFIG_DEBUG_INFO_BTF=y
CONFIG_FAIL_FUNCTION=y
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FUNCTION_ERROR_INJECTION=y
CONFIG_HUGETLBFS=y
@@ -19,10 +21,11 @@ CONFIG_NET_DEVMEM=y
CONFIG_NET_IPGRE=y
CONFIG_NET_IPGRE_DEMUX=y
CONFIG_NET_IPIP=y
CONFIG_NETKIT=y
CONFIG_NET_SCH_INGRESS=y
+CONFIG_PACKET=y
CONFIG_SYNC_FILE=y
CONFIG_UDMABUF=y
CONFIG_USER_NS=y
CONFIG_VLAN_8021Q=m
CONFIG_VXLAN=y
diff --git a/tools/testing/selftests/bpf/progs/xdp_features.c b/tools/testing/selftests/drivers/net/hw/xdp_features.bpf.c
similarity index 100%
rename from tools/testing/selftests/bpf/progs/xdp_features.c
rename to tools/testing/selftests/drivers/net/hw/xdp_features.bpf.c
diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/drivers/net/hw/xdp_features.c
similarity index 99%
rename from tools/testing/selftests/bpf/xdp_features.c
rename to tools/testing/selftests/drivers/net/hw/xdp_features.c
index be4acb768e0a2..a0ec7000e444b 100644
--- a/tools/testing/selftests/bpf/xdp_features.c
+++ b/tools/testing/selftests/drivers/net/hw/xdp_features.c
@@ -27,10 +27,11 @@
#include <unistd.h>
#include <arpa/inet.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
+#include "ksft.h"
#include "xdp_features.skel.h"
#include "xdp_features.h"
#define RED(str) "\033[0;31m" str "\033[0m"
#define GREEN(str) "\033[0;32m" str "\033[0m"
@@ -482,10 +483,11 @@ static int dut_run(struct xdp_features *skel)
if (sockfd < 0) {
fprintf(stderr,
"Failed creating control socket on device %s\n", env.ifname);
return -errno;
}
+ ksft_ready();
/* Allow remote startup and BPF loading on slow test machines. */
ctrl_sockfd = -1;
for (attempt = 0; attempt < 20 && !should_exit(); attempt++) {
addrlen = sizeof(ctrl_addr);
diff --git a/tools/testing/selftests/bpf/xdp_features.h b/tools/testing/selftests/drivers/net/hw/xdp_features.h
similarity index 100%
rename from tools/testing/selftests/bpf/xdp_features.h
rename to tools/testing/selftests/drivers/net/hw/xdp_features.h
diff --git a/tools/testing/selftests/drivers/net/hw/xdp_features.py b/tools/testing/selftests/drivers/net/hw/xdp_features.py
new file mode 100755
index 0000000000000..dda114033b8ab
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/xdp_features.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Check that a device's advertised XDP features match its behavior."""
+
+import ipaddress
+from pathlib import Path
+import re
+import shlex
+
+from lib.py import bkg, cmd, ethtool, ip, ksft_eq, ksft_exit, ksft_run
+from lib.py import KsftFailEx, KsftSkipEx, NetDrvEpEnv
+
+
+ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*m")
+RESULT = re.compile(
+ r"Feature .*: \[(NOT )?DETECTED\]\[(NOT )?ADVERTISED\]"
+)
+
+
+def mapped_address(address):
+ """Return an IPv6 or IPv4-mapped IPv6 address for xdp_features."""
+ address = ipaddress.ip_address(address)
+ if address.version == 4:
+ return f"::ffff:{address}"
+ return str(address)
+
+
+def feature_command(binary, feature, dut_addr, tester_addr, ifname,
+ tester=False):
+ """Build the helper command with the configured endpoint addresses."""
+ args = [str(binary)]
+ if tester:
+ args.append("-t")
+ args += ["-f", feature, "-D", dut_addr]
+ if tester:
+ args += ["-C", dut_addr]
+ args += ["-T", tester_addr, ifname]
+ return args
+
+
+def run_feature(cfg, feature, ipver):
+ """Run one feature with bounded helper lifetimes and verify its result."""
+ cfg.require_cmd("timeout", local=False, remote=True)
+ probe = cmd([str(cfg.xdp_features), "-q", cfg.ifname],
+ fail=False)
+ if probe.ret == 4:
+ raise KsftSkipEx(f"{cfg.ifname} does not advertise native XDP")
+ if probe.ret:
+ raise KsftFailEx(f"Unable to query XDP support: {probe}")
+ if feature == "XDP_TX":
+ link = ip(f"-d link show dev {cfg.ifname}", json=True)[0]
+ if link.get("linkinfo", {}).get("info_kind") == "veth":
+ peer = ip(f"-d link show dev {cfg.remote_ifname}", json=True,
+ host=cfg.remote)[0]
+ if peer.get("xdp", {}).get("mode") != 1:
+ cfg.require_cmd("ethtool", local=False, remote=True)
+ features = ethtool(f"-k {cfg.remote_ifname}", host=cfg.remote)
+ gro = re.search(r"^generic-receive-offload: (on|off)"
+ r"(?: \[fixed\])?$", features.stdout, re.M)
+ if not gro:
+ raise KsftFailEx("Unable to query peer GRO state")
+ # The peer needs NAPI to receive XDP frames.
+ if gro.group(1) == "off":
+ raise KsftSkipEx("veth XDP_TX needs peer GRO or an "
+ "existing peer native XDP program")
+ if not cfg.addr_v[ipver]:
+ ipver = "4" if ipver == "6" else "6"
+ cfg.require_ipver(ipver)
+ dut_addr = mapped_address(cfg.addr_v[ipver])
+ tester_addr = mapped_address(cfg.remote_addr_v[ipver])
+
+ dut_cmd = feature_command(cfg.xdp_features, feature, dut_addr,
+ tester_addr, cfg.ifname)
+ tester_cmd = feature_command(cfg.remote_xdp_features, feature, dut_addr,
+ tester_addr, cfg.remote_ifname, tester=True)
+
+ with bkg(dut_cmd, exit_wait=True, ksft_ready=True):
+ # Bound the remote process itself, not just the local SSH wait.
+ remote_cmd = shlex.join(["timeout", "--signal=TERM", "--kill-after=5",
+ "90"] + tester_cmd)
+ result = cmd(remote_cmd,
+ host=cfg.remote, timeout=100)
+
+ output = ANSI_ESCAPE.sub("", result.stdout)
+ match = RESULT.search(output)
+ if not match:
+ raise KsftFailEx(f"Unable to parse xdp_features output: {output}")
+
+ detected = match.group(1) is None
+ advertised = match.group(2) is None
+ ksft_eq(detected, advertised,
+ comment=f"{feature}: detected and advertised support")
+
+
+def test_xdp_pass(cfg):
+ """Check XDP_PASS detection against the advertised capability."""
+ run_feature(cfg, "XDP_PASS", "6")
+
+
+def test_xdp_drop(cfg):
+ """Check XDP_DROP detection against the advertised capability."""
+ run_feature(cfg, "XDP_DROP", "4")
+
+
+def test_xdp_aborted(cfg):
+ """Check XDP_ABORTED detection against the advertised capability."""
+ run_feature(cfg, "XDP_ABORTED", "6")
+
+
+def test_xdp_tx(cfg):
+ """Check XDP_TX detection against the advertised capability."""
+ run_feature(cfg, "XDP_TX", "4")
+
+
+def test_xdp_redirect(cfg):
+ """Check XDP_REDIRECT detection against the advertised capability."""
+ run_feature(cfg, "XDP_REDIRECT", "6")
+
+
+def test_xdp_ndo_xmit(cfg):
+ """Check XDP_NDO_XMIT detection against the advertised capability."""
+ run_feature(cfg, "XDP_NDO_XMIT", "4")
+
+
+def main():
+ """Deploy the helper and run the six feature checks."""
+ with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+ cfg.xdp_features = (Path(__file__).parent / "xdp_features").resolve()
+ cfg.remote_xdp_features = cfg.remote.deploy(
+ cfg.xdp_features.as_posix()
+ )
+
+ ksft_run([
+ test_xdp_pass,
+ test_xdp_drop,
+ test_xdp_aborted,
+ test_xdp_tx,
+ test_xdp_redirect,
+ test_xdp_ndo_xmit,
+ ], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py b/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
index 924addde19a3e..5e2950a7c95fa 100644
--- a/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
+++ b/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
@@ -33,7 +33,7 @@ class Remote:
file_name = self._tmpdir + "/" + self._mktmp() + os.path.basename(what)
if not os.path.isabs(what):
what = os.path.abspath(self.dir_path + "/" + what)
- cmd(f"scp {what} {self.name}:{file_name}")
+ cmd(["scp", what, f"{self.name}:{file_name}"])
return file_name
--
Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown
2026-10-18 14:05 ` [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
@ 2026-10-19 14:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 14:55 UTC (permalink / raw)
To: bochaolucky
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shuah, davem, kuba,
hawk, john.fastabend, sdf, andrew+netdev, edumazet, pabeni, horms,
linux-kernel, bpf, linux-kselftest, netdev, alexis.lothore,
lorenzo, tiozhang, willemdebruijn.kernel, petrm
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The `xdp_attached` guard only records that an attach succeeded at
some point.
Pre-existing issues:
- [Medium] test_xdp_features.sh uses only the tester's exit status, and
tester_run() returns 0 whether or not the capability is DETECTED or…
- [Low] In tester_run(), the settimeo() and connect() failure paths
return directly without close(sockfd), which leaks the control socket.
- [Low] recv_msg() accepts payloads shorter than val_size and copies only
the bytes it received.
--
selftests/bpf: Fix XDP feature test setup and teardown
This commit fixes several setup and teardown problems in the xdp_features
selftest tool. It initializes the DUT state and socket address lengths,
hands the echo socket back to the caller, and stops and joins the echo
thread only if it was started. It also samples the tester counters before
teardown, and it detaches XDP programs only after a successful attach.
> diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
> index a27ed663967c4..3076fdda61fa4 100644
> --- a/tools/testing/selftests/bpf/xdp_features.c
> +++ b/tools/testing/selftests/bpf/xdp_features.c
[ ... ]
> @@ -341,10 +343,14 @@ static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
> size_t val_size)
> {
> struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
> - size_t len;
> + ssize_t len;
>
> len = recv(sockfd, buf, bufsize, 0);
> - if (len != ntohs(tlv->len) || len < sizeof(*tlv))
> + if (len < 0)
> + return -errno;
> + if (!len)
> + return -ECONNRESET;
> + if (len < sizeof(*tlv) || len != ntohs(tlv->len))
> return -EINVAL;
>
> if (val) {
[Severity: Low]
This isn't a bug introduced by this patch, but recv_msg() only rejects
payloads that are larger than val_size:
if (val) {
len -= sizeof(*tlv);
if (len > val_size)
return -ENOMEM;
memcpy(val, tlv->data, len);
}
Can a short payload leave part of the destination unwritten?
In tester_run(), stats and advertised_feature are both declared without
an initial value. Leftover stack bytes could then affect the feature
result that gets printed.
A later patch in this series changes the check to:
if (len != val_size)
return -ENOMEM;
That rejects short payloads before the memcpy().
[ ... ]
> @@ -403,9 +412,12 @@ static int dut_run(struct xdp_features *skel)
> if (err)
> goto out;
>
> + xdp_attached = true;
> +
> err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
[ ... ]
> @@ -422,6 +434,7 @@ static int dut_run(struct xdp_features *skel)
>
> exiting = true;
> bpf_xdp_detach(env.ifindex, flags, NULL);
> + xdp_attached = false;
[Severity: Low]
The xdp_attached flag records that bpf_xdp_attach() succeeded at some
point. However, bpf_xdp_detach(env.ifindex, flags, NULL) removes
whatever program is attached to the device at the time it runs.
If another privileged user replaces the program between the attach and
the detach, would this still remove that other program?
The out: label in tester_run() uses the same pattern:
if (xdp_attached)
bpf_xdp_detach(env.ifindex, flags, NULL);
A later patch in this series appears to fix this. The DUT attaches with
bpf_link_create() and cleans up with close(link_fd). The tester no
longer attaches an XDP program at all, because it uses AF_PACKET.
>
> tlv->type = htons(CMD_ACK);
> tlv->len = htons(sizeof(*tlv));
[ ... ]
> @@ -578,7 +596,7 @@ static int tester_run(struct xdp_features *skel)
> struct bpf_program *prog;
> unsigned int stats;
> int i, err, sockfd;
> - bool detected_cap;
> + bool detected_cap, xdp_attached = false;
>
> sockfd = socket(AF_INET6, SOCK_STREAM, 0);
> if (sockfd < 0) {
[Severity: Low]
This is a pre-existing issue, but the settimeo() and connect() failure
paths just below this return without closing sockfd:
if (settimeo(sockfd, 1000) < 0)
return -EINVAL;
err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
sizeof(env.dut_ctrl_addr));
if (err) {
...
return -errno;
}
Does this leak the control socket? Now that xdp_attached starts out
false, could these paths go to the out: label instead?
A later patch in this series changes them to "err = -errno; goto out;".
[ ... ]
> @@ -636,19 +655,21 @@ static int tester_run(struct xdp_features *skel)
> if (err)
> goto out;
>
> + /* Sample before teardown, which can produce additional echo traffic. */
> + detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
> +
> /* stop the test */
[ ... ]
> fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
> detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
> env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
> : RED("NOT ADVERTISED"));
> out:
> - bpf_xdp_detach(env.ifindex, flags, NULL);
> + if (xdp_attached)
> + bpf_xdp_detach(env.ifindex, flags, NULL);
> close(sockfd);
> return err < 0 ? err : 0;
> }
[Severity: Medium]
This isn't a bug introduced by this patch, but detected_cap and
advertised_feature are only printed here. tester_run() returns 0
whatever their values are.
test_xdp_features.sh checks only the exit status of the tester:
ip netns exec ${NS} ./xdp_features -t -f XDP_PASS \
-D $V1_IP6 -C $V1_IP6 \
-T $V0_IP6 v0
[ $? -ne 0 ] && exit
Could a broken or unadvertised XDP action still pass the selftest?
A later patch in this series ("selftests/bpf: Track XDP test processes
and isolate namespaces") adds:
if (!err && detected_cap != !!(env.feature.drv_feature & advertised_feature))
err = -EINVAL;
The same series also replaces the shell script with
drivers/net/hw/xdp_features.py.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261018-xdp-v5-send-20261008-v6-0-7336e1282ca3%40gmail.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces
2026-10-18 14:05 ` [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
@ 2026-10-19 14:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 14:55 UTC (permalink / raw)
To: bochaolucky
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shuah, davem, kuba,
hawk, john.fastabend, sdf, andrew+netdev, edumazet, pabeni, horms,
linux-kernel, bpf, linux-kselftest, netdev, alexis.lothore,
lorenzo, tiozhang, willemdebruijn.kernel, petrm
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Concern about a signal race in cleanup() and handle_signal() in
tools/testing/selftests/bpf/test_xdp_features.sh.
- [Low] Concern about a check-then-create race (TOCTOU) on namespace
ownership in setup() of…
--
selftests/bpf: Track XDP test processes and isolate namespaces
This commit reworks test_xdp_features.sh so each run gets its own DUT and
tester namespaces. The DUT server is tracked as a shell job with a bounded
readiness wait, and signals are handled during setup and cleanup. It also
filters the tracepoint counters by this run's program and CPU map IDs, and
makes the tester fail when detected and advertised features differ.
> diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
> index 0aa71c4455c05..5b68b20d94ac9 100755
> --- a/tools/testing/selftests/bpf/test_xdp_features.sh
> +++ b/tools/testing/selftests/bpf/test_xdp_features.sh
> @@ -2,105 +2,179 @@
> # SPDX-License-Identifier: GPL-2.0
>
> readonly NS="ns1-$(mktemp -u XXXXXX)"
> +readonly DUT_NS="dut-${NS}"
[ ... ]
> setup() {
> {
> - ip netns add ${NS}
> -
> - ip link add v1 type veth peer name v0 netns ${NS}
> -
> - ip link set v1 up
> - ip addr add $V1_IP4/24 dev v1
> - ip addr add $V1_IP6/64 nodad dev v1
> + [ ! -e "/run/netns/${NS}" ]
> + peer_created=true
> + ip netns add "${NS}"
> + [ ! -e "/run/netns/${DUT_NS}" ]
> + dut_created=true
> + ip netns add "${DUT_NS}"
[Severity: Low]
Can cleanup() end up deleting a namespace that belongs to a different run?
The existence check and the ip netns add are not atomic. The ownership
flag is also set before the create succeeds.
Suppose a concurrent run creates the same name after the [ ! -e ... ]
check but before ip netns add. The add fails and set -e exits. cleanup()
then sees peer_created=true (or dut_created=true):
cleanup() {
...
if "$peer_created"; then
ip netns del "$NS" 2> /dev/null || true
fi
}
and deletes the other run's namespace.
This needs two runs to get the same suffix from mktemp -u XXXXXX and then
hit the window, so it is unlikely. Would setting peer_created and
dut_created only after each ip netns add succeeds close it?
The later patch in this series, "selftests/net: Move the XDP feature test
to drivers/net/hw", deletes test_xdp_features.sh entirely. So this only
affects the intermediate state of the series.
[ ... ]
> cleanup() {
> - ip link del v1 2> /dev/null
> - ip netns del ${NS} 2> /dev/null
> - [ "$(pidof xdp_features)" = "" ] || kill $(pidof xdp_features) 2> /dev/null
> + trap '' HUP INT QUIT ABRT TERM
> + trap - EXIT
> + terminate_dut_server
> + if "$dut_created"; then
> + ip netns del "$DUT_NS" 2> /dev/null || true
> + fi
> + if "$peer_created"; then
> + ip netns del "$NS" 2> /dev/null || true
> + fi
> }
[Severity: Low]
Is there a window where a second signal can cut cleanup() short?
After the first HUP/INT/QUIT/ABRT/TERM, handle_signal() calls exit and
cleanup() starts as the EXIT trap. If a second signal arrives before the
trap '' HUP INT QUIT ABRT TERM line takes effect, handle_signal() still
runs:
handle_signal() {
if "$setup_in_progress"; then
pending_signal=$1
else
exit "$1"
fi
}
That exit would then run from inside the EXIT trap. In that case,
terminate_dut_server() and both ip netns del calls would be skipped,
leaving the DUT xdp_features process and both namespaces behind.
The old script had no TERM or EXIT trap, so this is not worse than
before. The later patch "selftests/net: Move the XDP feature test to
drivers/net/hw" also removes this script.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261018-xdp-v5-send-20261008-v6-0-7336e1282ca3%40gmail.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET
2026-10-18 14:05 ` [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
@ 2026-10-19 14:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 14:55 UTC (permalink / raw)
To: bochaolucky
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shuah, davem, kuba,
hawk, john.fastabend, sdf, andrew+netdev, edumazet, pabeni, horms,
linux-kernel, bpf, linux-kselftest, netdev, alexis.lothore,
lorenzo, tiozhang, willemdebruijn.kernel, petrm
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The hand-written usage line in argp_program_doc still reads
`USAGE: ./xdp-features [-vt] [-f <xdp-feature>] ...` and does not…
--
selftests/bpf: Send XDP feature probes with AF_PACKET
The tester now uses AF_PACKET to send echo probes and count the replies,
so it no longer needs XDP support. The DUT now attaches through an XDP
link, and the patch adds a -q option that queries native XDP support.
The network_helpers dependency is dropped and veth XDP_TX is skipped.
> diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
> index 97c0e871c5b65..be4acb768e0a2 100644
> --- a/tools/testing/selftests/bpf/xdp_features.c
> +++ b/tools/testing/selftests/bpf/xdp_features.c
[ ... ]
> @@ -78,6 +156,7 @@ const char argp_program_doc[] =
> static const struct argp_option opts[] = {
> { "verbose", 'v', NULL, 0, "Verbose debug output" },
> { "tester", 't', NULL, 0, "Tester mode" },
> + { "query", 'q', NULL, 0, "Check native XDP support without attaching" },
[Severity: Low]
This isn't a bug, but should the usage line in argp_program_doc also
list the new option? It still reads:
"USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n"
The -q, --query entry shows up in the argp-generated option list, but
the hand-written usage summary leaves it out. The usage line is still
unchanged at the end of the series.
> { "feature", 'f', "XDP-FEATURE", 0, "XDP feature to test" },
> { "dut_data_ip", 'D', "DUT-DATA-IP", 0, "DUT IP data channel" },
> { "dut_ctrl_ip", 'C', "DUT-CTRL-IP", 0, "DUT IP control channel" },
[ ... ]
> @@ -719,6 +969,20 @@ int main(int argc, char **argv)
> return -ENODEV;
> }
>
> + if (env.query_only) {
> + LIBBPF_OPTS(bpf_xdp_query_opts, opts);
> +
> + err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE, &opts);
> + if (err)
> + return 1;
> + return opts.feature_flags & NETDEV_XDP_ACT_BASIC ? 0 : 4;
> + }
[Severity: Low]
Could the exit codes for query mode be documented somewhere, such as in
argp_program_doc or the -q help text?
Here 0 means NETDEV_XDP_ACT_BASIC is advertised, 4 means it is not, and
1 means bpf_xdp_query() failed. None of this is described in the tool.
Later in the series, run_feature() in drivers/net/hw/xdp_features.py
treats probe.ret == 4 as a skip, so the contract is already in use.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261018-xdp-v5-send-20261008-v6-0-7336e1282ca3%40gmail.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule
2026-10-18 14:05 ` [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
@ 2026-10-19 14:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 14:55 UTC (permalink / raw)
To: bochaolucky
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shuah, davem, kuba,
hawk, john.fastabend, sdf, andrew+netdev, edumazet, pabeni, horms,
linux-kernel, bpf, linux-kselftest, netdev, alexis.lothore,
lorenzo, tiozhang, willemdebruijn.kernel, petrm
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Under LLVM=1, the host bpftool bootstrap rule in
tools/testing/selftests/net/bpf.mk probes one compiler and builds with…
- [Low] The patch makes two changes outside its subject.
--
selftests/net: Add a shared BPF skeleton rule
This commit adds a shared %.skel.h rule to selftests/net/bpf.mk. When
BPFTOOL is not supplied, it also builds a host bootstrap bpftool. The
BPF compiler now takes KHDR_INCLUDES instead of the source-tree
usr/include, and the libbpf sub-make gets an absolute srctree.
> diff --git a/tools/testing/selftests/net/bpf.mk b/tools/testing/selftests/net/bpf.mk
> index a4f6755dd8941..016e32960280a 100644
> --- a/tools/testing/selftests/net/bpf.mk
> +++ b/tools/testing/selftests/net/bpf.mk
> @@ -6,8 +6,8 @@ BUILD_DIR := $(SCRATCH_DIR)/build
> BPFDIR := $(top_srcdir)/tools/lib/bpf
> APIDIR := $(top_srcdir)/tools/include/uapi
>
> +CCINCLUDE += $(KHDR_INCLUDES)
> CCINCLUDE += -I$(selfdir)/bpf
> -CCINCLUDE += -I$(top_srcdir)/usr/include/
> CCINCLUDE += -I$(SCRATCH_DIR)/include
>
> BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
> @@ -46,8 +46,37 @@ $(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
> $(APIDIR)/linux/bpf.h \
> | $(BUILD_DIR)/libbpf
> $(call msg,MAKE,,$@)
> - $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
> + $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) srctree=$(abspath $(top_srcdir)) \
> + OUTPUT=$(BUILD_DIR)/libbpf/ \
> EXTRA_CFLAGS='-g -O0' \
> DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
[Severity: Low]
This isn't a bug, but the patch also changes two existing parts of
bpf.mk. CCINCLUDE now uses $(KHDR_INCLUDES) in place of
-I$(top_srcdir)/usr/include/, and the libbpf sub-make now gets an
absolute srctree.
Both changes fix behaviour that dates back to commit 3f584c211d8c
("selftests: net: extract BPF building logic from the Makefile").
The commit message does describe both changes. Would it be worth moving
them into their own patch, or adding a Fixes: tag, so they can be picked
up separately from the new skeleton rule?
>
> EXTRA_CLEAN += $(SCRATCH_DIR)
> +
> +# Build a host bpftool only when a test needs a generated skeleton.
> +HOSTCC ?= gcc
> +HOSTLD ?= ld
> +BPFTOOL_BUILD := $(BUILD_DIR)/bpftool
[ ... ]
> +$(BPFTOOL_BUILD)/bootstrap/bpftool: \
> + $(wildcard $(top_srcdir)/tools/bpf/bpftool/*.[ch]) \
> + $(top_srcdir)/tools/bpf/bpftool/Makefile \
> + $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile)
> + $(call msg,MAKE,,$@)
> + $(Q)mkdir -p $(BPFTOOL_BUILD)
> + $(Q)$(MAKE) $(submake_extras) -C $(top_srcdir)/tools/bpf/bpftool \
> + OUTPUT=$(BPFTOOL_BUILD)/ srctree=$(abspath $(top_srcdir)) \
> + ARCH= CROSS_COMPILE= CC="$(HOSTCC)" LD="$(HOSTLD)" bootstrap
[Severity: Medium]
Can this bootstrap build fail with LLVM=1?
bpf.mk sets HOSTCC to gcc and HOSTLD to ld whatever LLVM=, LLVM_PREFIX
and LLVM_SUFFIX are, and it only hands them to the bpftool sub-make as
CC and LD. HOSTCC itself is never passed or exported, so inside the
sub-make tools/scripts/Makefile.include sets it from LLVM:
$(call allow-override,HOSTCC,$(LLVM_PREFIX)clang$(LLVM_SUFFIX))
$(call allow-override,HOSTAR,$(LLVM_PREFIX)llvm-ar$(LLVM_SUFFIX))
$(call allow-override,HOSTLD,$(LLVM_PREFIX)ld.lld$(LLVM_SUFFIX))
bpftool then compiles the bootstrap objects and the bootstrap libbpf with
that HOSTCC, which is clang:
tools/bpf/bpftool/Makefile:
$(QUIET_CC)$(HOSTCC) $(HOST_CFLAGS) -c -MMD $< -o $@
...
ARCH= CROSS_COMPILE= CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)" \
CFLAGS="$(LIBBPF_BOOTSTRAP_CFLAGS)" EXTRA_CFLAGS= $@ install_headers
The CC_NO_CLANG probe in Makefile.include, however, runs against $(CC).
That is the gcc forced on the command line, so the probe adds the
GCC-only warning:
ifeq ($(CC_NO_CLANG), 1)
EXTRA_WARNINGS += -Wstrict-aliasing=3
bpftool copies EXTRA_WARNINGS into CFLAGS, HOST_CFLAGS and
LIBBPF_BOOTSTRAP_CFLAGS, and tools/lib/bpf/Makefile adds -Werror. Clang
would then stop with:
error: unknown warning option '-Wstrict-aliasing=3' [-Werror,-Wunknown-warning-option]
Commit 093b75ef5995 ("tools: Suppress request for warning options not
existent in clang") added the CC_NO_CLANG guard to avoid exactly this
error.
On a clang-only host the result is the same, because a missing gcc
leaves CC_NO_CLANG at 1. The libelf-zstd feature probe would also run
with gcc, not with the clang that does the linking.
The following command should hit this once "selftests/net: Move the XDP
feature test to drivers/net/hw" makes xdp_features depend on
xdp_features.skel.h:
make LLVM=1 -C tools/testing/selftests TARGETS=drivers/net/hw
selftests/bpf and selftests/hid include tools/scripts/Makefile.include,
so their HOSTCC follows LLVM=. Could bpf.mk set its HOSTCC/HOSTLD
defaults the same way, or pass HOSTCC, HOSTLD and HOSTAR to the bpftool
sub-make as well as CC and LD?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261018-xdp-v5-send-20261008-v6-0-7336e1282ca3%40gmail.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw
2026-10-18 14:05 ` [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
@ 2026-10-19 14:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 14:55 UTC (permalink / raw)
To: bochaolucky
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shuah, davem, kuba,
hawk, john.fastabend, sdf, andrew+netdev, edumazet, pabeni, horms,
linux-kernel, bpf, linux-kselftest, netdev, alexis.lothore,
lorenzo, tiozhang, willemdebruijn.kernel, petrm
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch changes Remote.deploy() in
tools/testing/selftests/drivers/net/lib/py/remote_ssh.py from…
- [Low] In dut_run()
(tools/testing/selftests/drivers/net/hw/xdp_features.c line 490), the…
--
selftests/net: Move the XDP feature test to drivers/net/hw
This commit replaces the veth-based test_xdp_features.sh script in
selftests/bpf with a NetDrvEpEnv Python test under drivers/net/hw. The
xdp_features helper is now built there with the shared BPF rules. The
commit also changes Remote.deploy() to pass scp arguments as a list.
> diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/drivers/net/hw/xdp_features.c
> similarity index 99%
> rename from tools/testing/selftests/bpf/xdp_features.c
> rename to tools/testing/selftests/drivers/net/hw/xdp_features.c
> index be4acb768e0a2..a0ec7000e444b 100644
> --- a/tools/testing/selftests/bpf/xdp_features.c
> +++ b/tools/testing/selftests/drivers/net/hw/xdp_features.c
[ ... ]
> @@ -484,6 +485,7 @@ static int dut_run(struct xdp_features *skel)
> "Failed creating control socket on device %s\n", env.ifname);
> return -errno;
> }
> + ksft_ready();
>
> /* Allow remote startup and BPF loading on slow test machines. */
[Severity: Low]
This isn't a bug, but is this comment above the accept() retry loop in
dut_run() still accurate?
Nothing loads BPF while this loop waits. tester_run() on the remote side
only uses TCP, UDP and AF_PACKET sockets. On the DUT, main() already runs
xdp_features__open(), xdp_features__load() and xdp_features__attach()
before it calls dut_run().
The README added by this patch also says the remote helper "does not load
BPF".
The comment came in with the earlier series patch "selftests/bpf: Send XDP
feature probes with AF_PACKET". That patch took BPF out of the tester, so
the comment was already stale when it was added, and it stays stale here.
> ctrl_sockfd = -1;
[ ... ]
> diff --git a/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py b/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
> index 924addde19a3e..5e2950a7c95fa 100644
> --- a/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
> +++ b/tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
> @@ -35,5 +35,5 @@ class Remote:
> if not os.path.isabs(what):
> what = os.path.abspath(self.dir_path + "/" + what)
>
> - cmd(f"scp {what} {self.name}:{file_name}")
> + cmd(["scp", what, f"{self.name}:{file_name}"])
[Severity: Low]
This change looks like a standalone fix for the shared Remote.deploy()
helper. It isn't really part of moving the XDP feature test.
When cmd() gets a string with spaces, it splits it in
net/lib/py/utils.py:
if shell is None and isinstance(comm, str) and ' ' in comm:
comm = comm.split()
So a local path that contains spaces reaches scp as several arguments.
Existing callers already hit this, for example main() in
drivers/net/hw/csum.py:
cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
Would it make sense to split this out into its own patch with a Fixes: tag
for commit 1a20a9a0ddef ("selftests: drv-net: define endpoint structures"),
where the scp string was introduced? That would make the fix easier to find
and backport.
> return file_name
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261018-xdp-v5-send-20261008-v6-0-7336e1282ca3%40gmail.com
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-19 14:55 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-18 14:05 [PATCH net-next v6 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
2026-10-18 14:05 ` [PATCH net-next v6 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
2026-10-18 14:05 ` [PATCH net-next v6 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
2026-10-19 14:55 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox