* [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 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 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