Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test
@ 2026-10-10 10:22 Bochao Cao via B4 Relay
  2026-10-10 10:22 ` [PATCH net-next v5 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-10 10:22 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 v4:
- Rebase onto net-next 8df0638138d3.
- Split the helper fixes, packet probes, build rules and framework move
  into five patches.
- Move the common BPF rules, including %.skel.h, into net/bpf.mk.
- Use installed kernel headers and register the helper in TEST_GEN_FILES.
- Handle short control messages, EOF and helper timeouts. Use XDP links
  on the DUT to release the test's attachment when the process exits.
- Skip unsupported native XDP and veth XDP_TX without a peer XDP program.

Tested on x86_64 Ubuntu with Linux 6.8.0-124-generic:
- headers_install and the xdp_features build passed.
- Three veth setups: 17 PASS, 1 expected XDP_TX SKIP, no failures.
- Nine error and cleanup checks passed. Existing programs and offload
  settings were unchanged, with no helpers or namespaces left behind.
- Patch 2: two concurrent runs passed all six cases each; repeated TERM
  also left no helpers or namespaces.
- Protocol framing checks, Ruff, pylint and git diff --check passed.

These tests used veth devices. Physical-NIC testing remains unverified.

Previous version:
https://lore.kernel.org/all/20260921-xdp-features-v4-send-v4-0-a0d4ec392cb7@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/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     |   18 +
 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}                     |   42 +-
 .../selftests/drivers/net/hw/xdp_features.c        | 1003 ++++++++++++++++++++
 .../{bpf => drivers/net/hw}/xdp_features.h         |    1 +
 .../selftests/drivers/net/hw/xdp_features.py       |  137 +++
 tools/testing/selftests/net/bpf.mk                 |   25 +
 12 files changed, 1212 insertions(+), 872 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 v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown
  2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
@ 2026-10-10 10:22 ` Bochao Cao via B4 Relay
  2026-10-11 11:17   ` netdev-bot+sashiko
  2026-10-10 10:22 ` [PATCH net-next v5 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-10 10:22 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 fd to the caller, close it in the worker, and join the worker only
if it was started. Stop the worker on error paths.

Read tester counters before stopping the DUT. The echo sent during
teardown can otherwise make XDP_DROP report a false failure.

Fixes: 4dba3e7852b7 ("selftests/bpf: introduce XDP compliance test tool")
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
 tools/testing/selftests/bpf/xdp_features.c | 36 ++++++++++++++++++------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
index a27ed663967c4..5b802b14d8589 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;
 }
@@ -359,14 +361,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;
+	socklen_t addrlen = sizeof(ctrl_addr);
 
 	sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
 					DUT_CTRL_PORT, 0, 1);
 	if (!sockfd) {
 		fprintf(stderr,
@@ -404,10 +407,11 @@ static int dut_run(struct xdp_features *skel)
 				goto out;
 
 			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)
@@ -482,11 +486,13 @@ static int dut_run(struct xdp_features *skel)
 			break;
 		}
 	}
 
 end_thread:
-	pthread_join(dut_thread, NULL);
+	exiting = true;
+	if (thread_started)
+		pthread_join(dut_thread, NULL);
 out:
 	bpf_xdp_detach(env.ifindex, flags, NULL);
 	close(ctrl_sockfd);
 	free_fds(sockfd, 1);
 
@@ -634,16 +640,18 @@ 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 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"));

-- 
Git-155)



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next v5 2/5] selftests/bpf: Track XDP test processes and isolate namespaces
  2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
  2026-10-10 10:22 ` [PATCH net-next v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
@ 2026-10-10 10:22 ` Bochao Cao via B4 Relay
  2026-10-11 11:17   ` netdev-bot+sashiko
  2026-10-10 10:22 ` [PATCH net-next v5 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-10 10:22 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 so cleanup does not kill other test instances.
Wait for readiness while the DUT is alive.

Give each run separate DUT and tester namespaces to avoid name, address
and port conflicts. Handle signals during setup and ignore repeated
signals during cleanup.

Fixes: 4dba3e7852b7 ("selftests/bpf: introduce XDP compliance test tool")
Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
 tools/testing/selftests/bpf/test_xdp_features.sh | 122 ++++++++++++++++++-----
 1 file changed, 95 insertions(+), 27 deletions(-)

diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
index 0aa71c4455c05..7b96222b690a6 100755
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ b/tools/testing/selftests/bpf/test_xdp_features.sh
@@ -1,107 +1,175 @@
 #!/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 netns add "${NS}"
+		peer_created=true
+		ip netns add "${DUT_NS}"
+		dut_created=true
 
-		ip link add v1 type veth peer name v0 netns ${NS}
+		ip -n "$DUT_NS" 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
+		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 - EXIT
+	trap '' HUP INT QUIT ABRT TERM
+	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
+	while true; 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
 }
 
+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

-- 
Git-155)



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next v5 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET
  2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
  2026-10-10 10:22 ` [PATCH net-next v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
  2026-10-10 10:22 ` [PATCH net-next v5 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
@ 2026-10-10 10:22 ` Bochao Cao via B4 Relay
  2026-10-11 11:17   ` netdev-bot+sashiko
  2026-10-10 10:22 ` [PATCH net-next v5 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
  2026-10-10 10:22 ` [PATCH net-next v5 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-10 10:22 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 filter replies by address,
port and echo header. Get the DUT MAC address over the control connection.

Use an XDP link on the DUT so process exit releases only the test's
attachment. Check control peers and message lengths, handle partial
transfers and EOF, and bound helper runtime.

Add a native-XDP query for the runner and drop the network_helpers
dependency. Skip veth XDP_TX in the shell test: it needs a peer XDP program
to flush the receive ring.

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       | 612 ++++++++++++++++-------
 tools/testing/selftests/bpf/xdp_features.h       |   1 +
 5 files changed, 455 insertions(+), 216 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 67424084a38a1..a3e01bb2b8020 100644
--- a/tools/testing/selftests/bpf/progs/xdp_features.c
+++ b/tools/testing/selftests/bpf/progs/xdp_features.c
@@ -28,17 +28,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);
@@ -123,67 +116,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;
 }
 
@@ -201,11 +173,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 7b96222b690a6..a28021ba74726 100755
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ b/tools/testing/selftests/bpf/test_xdp_features.sh
@@ -125,18 +125,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 5b802b14d8589..521c0e24a27ca 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,86 @@ 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;
+
+	((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 volatile sig_atomic_t stop_signal;
+static atomic_bool exiting;
+
+static bool should_exit(void)
+{
+	return stop_signal || atomic_load(&exiting);
+}
 
 static void sig_handler(int sig)
 {
-	exiting = true;
+	stop_signal = sig;
 }
 
 const char *argp_program_version = "xdp-features 0.0";
 const char argp_program_doc[] =
 "XDP features detection application.\n"
@@ -76,10 +150,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 +213,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 +227,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 +287,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 +324,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 +347,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,109 +402,179 @@ 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 received;
 	size_t len;
 
-	len = recv(sockfd, buf, bufsize, 0);
-	if (len != ntohs(tlv->len) || len < sizeof(*tlv))
+	received = recv(sockfd, buf, sizeof(*tlv), MSG_WAITALL);
+	if (received < 0)
+		return -errno;
+	if (received != sizeof(*tlv))
+		return -ECONNRESET;
+	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;
+	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 (memcmp(&((struct sockaddr_in6 *)&ctrl_addr)->sin6_addr,
+		   &((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr,
+		   sizeof(struct in6_addr))) {
+		err = -EPERM;
+		goto out;
+	}
+
+	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)
+		if (err == -EAGAIN || err == -EINTR)
 			continue;
+		if (err)
+			goto end_thread;
 
 		switch (ntohs(tlv->type)) {
 		case CMD_START: {
 			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;
+			}
 
 			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);
-
 			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;
 
@@ -450,11 +592,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: {
@@ -475,11 +617,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:
@@ -490,177 +632,300 @@ static int dut_run(struct xdp_features *skel)
 end_thread:
 	exiting = true;
 	if (thread_started)
 		pthread_join(dut_thread, NULL);
 out:
-	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 long long monotonic_ms(void)
+{
+	struct timespec ts;
+
+	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;
 }
 
-static int tester_run(struct xdp_features *skel)
+static int tester_run(void)
 {
-	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
+	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;
-	struct bpf_program *prog;
-	unsigned int stats;
+	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;
 
 	sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-	if (sockfd < 0) {
-		fprintf(stderr,
-			"Failed creating tester service control socket\n");
-		return -errno;
-	}
-
-	if (settimeo(sockfd, 1000) < 0)
-		return -EINVAL;
-
-	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");
+	if (sockfd < 0)
 		return -errno;
+	if (settimeo(sockfd, 30000) ||
+	    bind(sockfd, (struct sockaddr *)&local, sizeof(local)) ||
+	    connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
+		    sizeof(struct sockaddr_in6))) {
+		err = -errno;
+		goto out;
 	}
-
 	err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
 				sizeof(advertised_feature));
-	if (err < 0) {
-		close(sockfd);
-		return err;
-	}
-
+	if (err)
+		goto out;
 	advertised_feature = be64toh(advertised_feature);
+	err = send_and_recv_msg(sockfd, CMD_GET_MAC, peer.sll_addr, ETH_ALEN);
+	if (err)
+		goto out;
 
-	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);
+	/* 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;
 	}
-
+	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;
+	}
+	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;
+	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 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"));
 out:
-	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;
 }
 
 int main(int argc, char **argv)
 {
 	struct xdp_features *skel;
@@ -669,10 +934,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);
@@ -682,10 +949,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 stop_signal ? EINTR : (err < 0 ? -err : 0);
+	}
+
 	/* Load and verify BPF application */
 	skel = xdp_features__open();
 	if (!skel) {
 		fprintf(stderr, "Failed to open and load BPF skeleton\n");
 		return -EINVAL;
@@ -707,21 +988,14 @@ 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);
 
+	if (stop_signal)
+		return EINTR;
 	return err < 0 ? -err : 0;
 }
diff --git a/tools/testing/selftests/bpf/xdp_features.h b/tools/testing/selftests/bpf/xdp_features.h
index 2670c541713b6..ba776e5ce7ebb 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 + data */
 };
 
 #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 v5 4/5] selftests/net: Add a shared BPF skeleton rule
  2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
                   ` (2 preceding siblings ...)
  2026-10-10 10:22 ` [PATCH net-next v5 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
@ 2026-10-10 10:22 ` Bochao Cao via B4 Relay
  2026-10-11 11:17   ` netdev-bot+sashiko
  2026-10-10 10:22 ` [PATCH net-next v5 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-10 10:22 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 %.skel.h to net/bpf.mk. Build a host bpftool bootstrap when BPFTOOL is
not supplied.

Write the skeleton to a temporary file and rename it on success, so a
failed command does not leave a partial header.

Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
 tools/testing/selftests/net/bpf.mk | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/tools/testing/selftests/net/bpf.mk b/tools/testing/selftests/net/bpf.mk
index a4f6755dd8941..2ded0f65dafa9 100644
--- a/tools/testing/selftests/net/bpf.mk
+++ b/tools/testing/selftests/net/bpf.mk
@@ -49,5 +49,30 @@ $(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile)		       \
 	$(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) 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.
+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)/ ARCH= CROSS_COMPILE= 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 v5 5/5] selftests/net: Move the XDP feature test to drivers/net/hw
  2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
                   ` (3 preceding siblings ...)
  2026-10-10 10:22 ` [PATCH net-next v5 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
@ 2026-10-10 10:22 ` Bochao Cao via B4 Relay
  2026-10-11 11:17   ` netdev-bot+sashiko
  4 siblings, 1 reply; 11+ messages in thread
From: Bochao Cao via B4 Relay @ 2026-10-10 10:22 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.

Check detected features against advertised support. Skip unsupported
native XDP and veth XDP_TX without a peer XDP program. Wait for DUT
readiness and run the remote helper under timeout.

Signed-off-by: Bochao Cao <bochaolucky@gmail.com>
---
 tools/testing/selftests/bpf/Makefile               |   9 +-
 tools/testing/selftests/bpf/test_xdp_features.sh   | 169 ---------------------
 tools/testing/selftests/drivers/net/README.rst     |  18 +++
 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       | 137 +++++++++++++++++
 10 files changed, 178 insertions(+), 178 deletions(-)

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 a28021ba74726..0000000000000
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ /dev/null
@@ -1,169 +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() {
-	{
-		ip netns add "${NS}"
-		peer_created=true
-		ip netns add "${DUT_NS}"
-		dut_created=true
-
-		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 - EXIT
-	trap '' HUP INT QUIT ABRT TERM
-	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 true; 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
-}
-
-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..0791f02ba44b4 100644
--- a/tools/testing/selftests/drivers/net/README.rst
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -308,5 +308,23 @@ 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`` additionally requires an existing native XDP program on the
+peer, because veth only flushes its peer receive ring in that case.
+Without this prerequisite the TX subtest is skipped; the test does not
+change the shared remote endpoint to enable it. Other 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 521c0e24a27ca..6aa5ac902448a 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"
@@ -478,10 +479,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..1d59772b2a4d0
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/xdp_features.py
@@ -0,0 +1,137 @@
+#!/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, ip, ksft_eq, ksft_exit, ksft_run, NetDrvEpEnv
+from lib.py import KsftFailEx, KsftSkipEx
+
+
+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 shlex.join(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(shlex.join([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]
+            # veth_xdp_flush() only schedules a peer with native XDP.
+            if peer.get("xdp", {}).get("mode") != 1:
+                raise KsftSkipEx("veth XDP_TX needs an existing peer native "
+                                 "XDP program to flush its receive ring")
+    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.
+        result = cmd(f"timeout --signal=TERM --kill-after=5 90 {tester_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()

-- 
Git-155)



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown
  2026-10-10 10:22 ` [PATCH net-next v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
@ 2026-10-11 11:17   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 11:17 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] In tester_run(), the new comment `/* Sample before teardown can
  produce additional echo traffic.

Pre-existing issues:
- [Medium] In this commit, dut_run() spins forever if the tester closes
  the control connection without sending CMD_STOP.

--

selftests/bpf: Fix XDP feature test setup and teardown

This patch fixes setup and teardown in the XDP features test tool. It
initializes the DUT state and socket address lengths, returns the echo
socket fd to the caller, and joins the echo worker only if it was
started. It also reads the tester counters before sending CMD_STOP, so
the teardown echo does not cause a false XDP_DROP failure.

> diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
> index a27ed663967c4..5b802b14d8589 100644
> --- a/tools/testing/selftests/bpf/xdp_features.c
> +++ b/tools/testing/selftests/bpf/xdp_features.c

[ ... ]

> @@ -484,7 +488,9 @@ static int dut_run(struct xdp_features *skel)
>  	}
>  
>  end_thread:
> -	pthread_join(dut_thread, NULL);
> +	exiting = true;
> +	if (thread_started)
> +		pthread_join(dut_thread, NULL);
>  out:
>  	bpf_xdp_detach(env.ifindex, flags, NULL);
>  	close(ctrl_sockfd);

[Severity: Medium]
This is a pre-existing issue, not one introduced by this patch. The
commit message says "Stop the worker on error paths". Can the new
exiting = true and pthread_join() be reached if the tester closes the
control connection without sending CMD_STOP?

At EOF, recv() in recv_msg() returns 0. That gets the same -EINVAL as a
malformed message:

	len = recv(sockfd, buf, bufsize, 0);
	if (len != ntohs(tlv->len) || len < sizeof(*tlv))
		return -EINVAL;

dut_run() then just loops again:

		err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
		if (err)
			continue;

exiting is still false, so the next recv() returns 0 at once. The DUT
spins at full CPU and never reaches end_thread or out. Meanwhile the
echo worker keeps running and the XDP program stays attached until
SIGINT or SIGTERM.

After CMD_START, tester_run() can close the socket without sending
CMD_STOP in several ways:

- send_echo_msg() fails.
- send_and_recv_msg(CMD_GET_STATS) fails or times out.
- The tester is killed.

The later patch in this series, "selftests/bpf: Send XDP feature probes
with AF_PACKET", appears to fix this. In that patch, recv_msg() returns
-ECONNRESET on a short or zero-length read. dut_run() then jumps to
end_thread for any error other than -EAGAIN or -EINTR.

[ ... ]

> @@ -636,12 +642,14 @@ static int tester_run(struct xdp_features *skel)
>  	if (err)
>  		goto out;
>  
> +	/* Sample before teardown can produce additional echo traffic. */

[Severity: Low]
This isn't a bug, but the comment reads as if sampling causes the extra
echo traffic, when it is the teardown that does. Would something like
"Sample before teardown, which can produce additional echo traffic" be
clearer?

Also, moving the detected_cap assignment leaves two blank lines in a row
after send_echo_msg() below. checkpatch warns about this.

Both go away when the later patch "selftests/bpf: Send XDP feature
probes with AF_PACKET" rewrites tester_run().

> +	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"),

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v5 2/5] selftests/bpf: Track XDP test processes and isolate namespaces
  2026-10-10 10:22 ` [PATCH net-next v5 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
@ 2026-10-11 11:17   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 11:17 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 3 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 (1) · Low: 1 (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] cleanup() runs `trap - EXIT` (line 61) before `trap '' HUP INT
  QUIT ABRT TERM` (line 62).
- [Medium] The liveness check in wait_for_dut_server() uses `jobs -pr
  %%`.
- [Low] cleanup() deletes a namespace only if peer_created or dut_created
  is true, and each flag is set only after its `ip netns add` returns 0.

Pre-existing issues:
- [Medium] test_xdp_features.sh decides pass or fail only from the
  tester's exit status.
- [Low] Each xdp_features process attaches global tp_btf/xdp_exception
  and tp_btf/xdp_cpumap_kthread programs.

--

selftests/bpf: Track XDP test processes and isolate namespaces

This patch reworks test_xdp_features.sh so the DUT xdp_features server
runs as a tracked shell job in its own network namespace, separate from
the tester's namespace. The pidof based kill in cleanup() is replaced by
job based termination. EXIT and signal traps are added, and signals that
arrive during setup are deferred.

> diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
> index 0aa71c4455c05..7b96222b690a6 100755
> --- a/tools/testing/selftests/bpf/test_xdp_features.sh
> +++ b/tools/testing/selftests/bpf/test_xdp_features.sh
> @@ -2,105 +2,173 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
>  readonly NS="ns1-$(mktemp -u XXXXXX)"
> +readonly DUT_NS="dut-${NS}"

[Severity: Low]
This is a pre-existing issue, not one introduced by this patch. The
per-run namespaces don't isolate the tracepoint programs that each
xdp_features instance attaches. In
tools/testing/selftests/bpf/progs/xdp_features.c:

SEC("tp_btf/xdp_exception")
int BPF_PROG(xdp_exception, const struct net_device *dev,
	     const struct bpf_prog *xdp, __u32 act)
{
	...
	val = bpf_map_lookup_elem(&dut_stats, &key);
	if (val)
		__sync_add_and_fetch(val, 1);
	...
}

tp_xdp_cpumap_kthread does the same. Neither handler filters by ifindex
or netns. Any XDP exception or cpumap kthread event on the system
therefore increments this instance's dut_stats.

With concurrent runs or other XDP users on the host, can
tester_collect_detected_cap() print the wrong DETECTED or NOT DETECTED
result for a given run? The script's pass/fail doesn't change, because
tester_run() returns err < 0 ? err : 0 whatever detected_cap is.

[ ... ]

>  setup() {
>  	{
> -		ip netns add ${NS}
> +		ip netns add "${NS}"
> +		peer_created=true
> +		ip netns add "${DUT_NS}"
> +		dut_created=true

[Severity: Low]
What happens if a group-directed signal, such as Ctrl-C, kills
ip netns add after it has created and bind-mounted
/var/run/netns/<name> but before it returns?

set -e would exit setup() with peer_created or dut_created still false.
The EXIT trap would then skip the matching delete in cleanup():

	if "$dut_created"; then
		ip netns del "$DUT_NS" 2> /dev/null || true
	fi

Does this leak an empty namespace? The old cleanup() deleted ${NS}
unconditionally.

A later commit in this series, "selftests/net: Move the XDP feature test
to drivers/net/hw", deletes test_xdp_features.sh, so this code is gone by
the end 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 - EXIT
> +	trap '' HUP INT QUIT ABRT TERM
> +	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: Medium]
Can a trapped signal still cut this cleanup short? trap - EXIT runs
before the HUP/INT/QUIT/ABRT/TERM traps are set to '', and once setup has
finished handle_signal() always exits:

handle_signal() {
	if "$setup_in_progress"; then
		pending_signal=$1
	else
		exit "$1"
	fi
}

Suppose a signal is already pending when cleanup() is entered from the
EXIT trap. For example, a harness sends INT and then TERM while the shell
waits on the foreground tester. A signal could also arrive just after
set -e starts the exit. Either way, handle_signal() runs exit from inside
the EXIT trap.

Bash then exits at once, and terminate_dut_server and both ip netns del
calls never run. Would that leak the DUT xdp_features process and both
namespaces?

A later commit in this series, "selftests/net: Move the XDP feature test
to drivers/net/hw", deletes test_xdp_features.sh, so this is not in the
final state of the series.

>  wait_for_dut_server() {
> -	while sleep 1; do
> -		ss -tlp | grep -q xdp_features
> -		[ $? -eq 0 ] && break
> +	while true; do
> +		if [ "$(jobs -pr %% 2> /dev/null)" != "$dut_pid" ]; then
> +			echo "xdp_features server $dut_pid exited before readiness" >&2
> +			return 1
> +		fi

[Severity: Medium]
Does this check notice a DUT that has already exited? In bash, the -r
state filter of jobs only applies when no jobspec is given. With an
explicit %% jobspec, bash prints the job's PID whatever its state.

The jobs call also runs in a command substitution subshell. The parent
shell never reports or reaps the finished job, so %% keeps resolving to
it.

Suppose xdp_features exits normally before it starts listening, for
example because xdp_features__load() or xdp_features__attach() fails in
main(). This test then keeps comparing $dut_pid with itself and never
fires. ss never finds a socket with pid=$dut_pid, so the loop sleeps
forever.

Would the selftest then hang until an external timeout? That would not
match this part of the commit message:

  Wait for readiness while the DUT is alive.

The later commit "selftests/net: Move the XDP feature test to
drivers/net/hw" deletes this script. The replacement xdp_features.py uses
bkg(dut_cmd, exit_wait=True, ksft_ready=True) instead.

> +
> +		if ip netns exec "$DUT_NS" ss -tlp 2> /dev/null |
> +		   grep -q "pid=$dut_pid,"; then
> +			return 0
> +		fi
> +
> +		sleep 1
>  	done
>  }

[ ... ]

>  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

[Severity: Medium]
This isn't a bug introduced by this patch, but the script only checks
the tester's exit status. tester_run() in
tools/testing/selftests/bpf/xdp_features.c computes detected_cap and the
advertised state and prints them. It then returns:

out:
	bpf_xdp_detach(env.ifindex, flags, NULL);
	close(sockfd);
	return err < 0 ? err : 0;

So the selftest can pass even when every action is NOT DETECTED, or when
detection doesn't match what the driver advertises.

The later commit "selftests/net: Move the XDP feature test to
drivers/net/hw" fixes this. It replaces the script with
drivers/net/hw/xdp_features.py, which checks
ksft_eq(detected, advertised, ...).

> +	reap_dut_server

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v5 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET
  2026-10-10 10:22 ` [PATCH net-next v5 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
@ 2026-10-11 11:17   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 11:17 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 4 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 3 (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] The patch makes the documented separate control-channel
  address unusable.
- [Low] At this commit, main() returns EINTR (4) whenever stop_signal is
  set, including when the new alarm(90) watchdog fires (lines 939-940,…
- [Low] xdp_features.h line 11 annotates the new CMD_GET_MAC as `/* CMD +
  data */`, but the request carries no payload.
- [Low] stop_signal is a `volatile sig_atomic_t` (line 121) that
  sig_handler() writes (line 131) and should_exit() reads from both the…

Pre-existing issues:
- [Low] tester_run() computes detected_cap (line 907) and only prints
  DETECTED/ADVERTISED.

--

selftests/bpf: Send XDP feature probes with AF_PACKET

This commit reworks the xdp_features tester to send echo probes and count
replies through an AF_PACKET socket, so the tester side needs no XDP
support. The DUT now attaches its program through an XDP link, returns its
MAC address over the control connection, and checks control peers and
message lengths. A native-XDP query mode is added and the network_helpers
dependency is dropped.

> diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
> index 5b802b14d8589..521c0e24a27ca 100644
> --- a/tools/testing/selftests/bpf/xdp_features.c
> +++ b/tools/testing/selftests/bpf/xdp_features.c

[ ... ]

> @@ -50,11 +118,17 @@ static int libbpf_print_fn(enum libbpf_print_level level,
>  	return vfprintf(stderr, format, args);
>  }
>  
> -static volatile bool exiting;
> +static volatile sig_atomic_t stop_signal;
> +static atomic_bool exiting;
> +
> +static bool should_exit(void)
> +{
> +	return stop_signal || atomic_load(&exiting);
> +}
>  
>  static void sig_handler(int sig)
>  {
> -	exiting = true;
> +	stop_signal = sig;
>  }

[Severity: Low]
This isn't a bug, but stop_signal is a volatile sig_atomic_t. sig_handler()
writes it, and should_exit() reads it from both the dut_run() control loop
and dut_echo_thread().

If the signal is handled on the other thread, that is formally a data race
under C11. In practice it is a single word that is re-read on every
iteration and orders no other data. Both loops also recheck at least every
3 seconds because of SO_RCVTIMEO, and the old volatile bool had the same
property.

Since exiting is already an atomic_bool, would it be cleaner to use an
atomic for both?

[ ... ]

> @@ -362,38 +466,61 @@ static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
>  
>  static int dut_run(struct xdp_features *skel)
>  {

[ ... ]

> -	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) {

[ ... ]

> +	if (memcmp(&((struct sockaddr_in6 *)&ctrl_addr)->sin6_addr,
> +		   &((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr,
> +		   sizeof(struct in6_addr))) {
> +		err = -EPERM;
> +		goto out;
> +	}

[Severity: Medium]
Does this make the -C option unusable when it differs from -D?

The new start_server() takes its bind address from the -D data address:

    static int start_server(int type, __u16 port)
    {
        struct sockaddr_storage addr = env.dut_addr;

As a result, the TCP control listener in dut_run() binds only to
<-D>:12345, and env.dut_ctrl_addr is never read on the DUT. The old
start_reuseport_server(..., NULL, DUT_CTRL_PORT, 0, 1) call bound the
listener to the wildcard address.

On the other side, tester_run() binds its control socket to the -T data
address before it connects to -C:

    if (settimeo(sockfd, 30000) ||
        bind(sockfd, (struct sockaddr *)&local, sizeof(local)) ||
        connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
                sizeof(struct sockaddr_in6))) {

If -C is not the same as -D, the tester's connect() goes to an address
where nothing is listening. The DUT then eventually gives up in its
accept() retry loop.

A setup with a separate management network also can't work, because
the memcmp() above requires control traffic to come from the -T data
address. That -EPERM path also exits without printing anything.

The -C option is still documented as "DUT IP control channel", and the
commit message only says "Check control peers". Should the control socket
bind to env.dut_ctrl_addr or to the wildcard address? Alternatively, should
the usage text say that -C must match -D?

The later commit "selftests/net: Move the XDP feature test to
drivers/net/hw" only adds ksft_ready() to this code. Its Python runner
passes the DUT data address as -C, so the in-tree runners hide this.

[ ... ]

> @@ -553,112 +663,267 @@ static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val,

[ ... ]

> +	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"));
>  out:

[ ... ]

> -	return err < 0 ? err : 0;
> +	return err;
>  }

[Severity: Low]
This is a pre-existing issue and was not introduced by this patch, but
detected_cap is only used for the printed DETECTED/ADVERTISED line.

The return value of tester_run() doesn't depend on whether the detected
capability matches the advertised one. Because of that,
test_xdp_features.sh passes even when a feature is broken or misadvertised.

This is addressed by the end of the series. In the later commit
"selftests/net: Move the XDP feature test to drivers/net/hw",
xdp_features.py parses the result line and checks
ksft_eq(detected, advertised).

> @@ -671,6 +936,8 @@ int main(int argc, char **argv)
>  
>  	signal(SIGINT, sig_handler);
>  	signal(SIGTERM, sig_handler);
> +	signal(SIGALRM, sig_handler);
> +	alarm(90);
>  
>  	set_env_default();

[ ... ]

> +	if (env.is_tester) {
> +		err = tester_run();
> +		return stop_signal ? EINTR : (err < 0 ? -err : 0);
> +	}

[Severity: Low]
EINTR is 4, which is the same value as KSFT_SKIP. The DUT path does the
same thing with:

    if (stop_signal)
        return EINTR;

If the alarm(90) watchdog fires during a stalled run, main() returns 4.
test_xdp_features.sh runs under set -e and passes that status through.
Would a hung run then be reported as skipped instead of failed?

At the end of the series this only affects this intermediate commit. The
later commit "selftests/net: Move the XDP feature test to drivers/net/hw"
deletes test_xdp_features.sh. Its Python runner treats any non-zero exit
from the tester (cmd()) and from the DUT (bkg(..., exit_wait=True)) as a
failure, and only the explicit -q probe maps 4 to a skip.

[ ... ]

> diff --git a/tools/testing/selftests/bpf/xdp_features.h b/tools/testing/selftests/bpf/xdp_features.h
> index 2670c541713b6..ba776e5ce7ebb 100644
> --- a/tools/testing/selftests/bpf/xdp_features.h
> +++ b/tools/testing/selftests/bpf/xdp_features.h
> @@ -8,6 +8,7 @@ enum test_commands {
>  	CMD_ACK,		/* CMD + data */
>  	CMD_GET_XDP_CAP,	/* CMD */
>  	CMD_GET_STATS,		/* CMD */
> +	CMD_GET_MAC,		/* CMD + data */
>  };

[Severity: Low]
This isn't a bug, but should CMD_GET_MAC be annotated /* CMD */, like
CMD_GET_XDP_CAP and CMD_GET_STATS?

tester_run() sends the request as a header-only TLV through
send_and_recv_msg(), and dut_run() reads it with recv_msg(..., NULL, 0).
The MAC address comes back in the CMD_ACK.

Later in the series this header moves unchanged to
drivers/net/hw/xdp_features.h, so the comment stays wrong there.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v5 4/5] selftests/net: Add a shared BPF skeleton rule
  2026-10-10 10:22 ` [PATCH net-next v5 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
@ 2026-10-11 11:17   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 11:17 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] The new bootstrap bpftool rule in
  tools/testing/selftests/net/bpf.mk…
- [Low] The new host bpftool bootstrap rule in
  tools/testing/selftests/net/bpf.mk (lines 70-71) clears only ARCH and…

--

selftests/net: Add a shared BPF skeleton rule

This commit adds a shared %.skel.h rule to tools/testing/selftests/net/bpf.mk.
If BPFTOOL is not supplied, the rule builds a host bpftool bootstrap. The
skeleton is written to a temporary file and renamed into place only on
success.

> diff --git a/tools/testing/selftests/net/bpf.mk b/tools/testing/selftests/net/bpf.mk
> index a4f6755dd8941..2ded0f65dafa9 100644
> --- a/tools/testing/selftests/net/bpf.mk
> +++ b/tools/testing/selftests/net/bpf.mk
> @@ -51,3 +51,28 @@ $(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile)		       \

[ ... ]

> +$(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)/ ARCH= CROSS_COMPILE= bootstrap

[Severity: Medium]
Does this sub-make work when srctree is inherited from the top-level kernel
Makefile?

For an in-tree build, the top-level Makefile exports a relative srctree:

Makefile:
    export srctree := $(if $(KBUILD_EXTMOD),$(abs_srctree),$(srcroot))

That value is "." in-tree, or ".." for O= set to a source subdirectory. The
kselftest and kselftest-% targets recurse into tools/testing/selftests with
it still set. Nothing along the way resets it.

The bpftool Makefile only computes the source root when srctree is empty:

tools/bpf/bpftool/Makefile:
    ifeq ($(srctree),)
    srctree := $(patsubst %/,%,$(dir $(CURDIR)))
    ...
    include $(srctree)/tools/build/Makefile.feature

With srctree=".", that include resolves relative to tools/bpf/bpftool. The
file does not exist there, so make stops with a fatal error before the
skeleton is generated.

libbpf has an explicit workaround for this case in tools/lib/bpf/Makefile:

    # This will work when bpf is built in tools env. where srctree
    # isn't set and when invoked from selftests build, where srctree
    # is a ".".

bpftool has no equivalent.

A later commit in this series, "selftests/net: Move the XDP feature test to
drivers/net/hw", makes xdp_features depend on xdp_features.skel.h. After
that, would "make kselftest-all TARGETS=drivers/net/hw" from an in-tree
kernel build fail here?

Direct "make -C tools/testing/selftests" invocations leave srctree unset and
are not affected.

Would passing srctree=$(abspath $(top_srcdir)), or an empty srctree=, to
this sub-make avoid the problem?

[Severity: Low]
Should this also force the host toolchain with CC and LD, the way the other
host bpftool rules in the tree do?

tools/testing/selftests/bpf/Makefile:
    ARCH= CROSS_COMPILE= CC="$(HOSTCC)" LD="$(HOSTLD)"

hid/Makefile and sched_ext/Makefile use the same pattern.

A target CC can reach this sub-make in two ways:

  - The top-level Makefile exports CC for
    "make ARCH=... CROSS_COMPILE=... kselftest-*".
  - The user passes CC on the command line.

tools/scripts/Makefile.include keeps that CC through allow-override.
tools/build/Makefile.feature then runs the bpftool feature probes with
CC="$(CC)", which is the cross compiler.

The libelf-zstd probe result decides the host link line:

tools/bpf/bpftool/Makefile:
    ifeq ($(feature-libelf-zstd),1)
    LIBS += -lzstd
    LIBS_BOOTSTRAP += -lzstd
    ...
    $(QUIET_LINK)$(HOSTCC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $(BOOTSTRAP_OBJS) $(LIBS_BOOTSTRAP) -o $@

Suppose the target sysroot has libelf and libzstd, but the host has no
libzstd development files. Would the bootstrap link then fail with "cannot
find -lzstd" and break skeleton generation?

If CC="$(HOSTCC)" LD="$(HOSTLD)" is added, HOSTCC and HOSTLD may also need
defaults in bpf.mk, because lib.mk does not define them.

> +
> +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o $(BPFTOOL_DEP)

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v5 5/5] selftests/net: Move the XDP feature test to drivers/net/hw
  2026-10-10 10:22 ` [PATCH net-next v5 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
@ 2026-10-11 11:17   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 11:17 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 5 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 4

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] The XDP_NDO_XMIT subtest fails on many drivers even when they
  work correctly, because the advertised mask is read before the XDP…
- [Low] The moved BPF object xdp_features.bpf.o is built by the shared
  rule in tools/testing/selftests/net/bpf.mk.
- [Low] run_feature() builds the local commands with shlex.join() (the
  probe at line 45, and the DUT command from feature_command() at line…
- [Low] tools/testing/selftests/bpf/.gitignore still has an
  `xdp_features` entry at line 44.
- [Low] The new README section heading 'XDP feature test' (16 characters)
  at tools/testing/selftests/drivers/net/README.rst:314 has a…

--

selftests/net: Move the XDP feature test to drivers/net/hw

This moves the xdp_features helper and its BPF program from selftests/bpf
to drivers/net/hw. It replaces the private veth shell script with a
NetDrvEpEnv Python test that runs the DUT locally and the tester remotely.
The new test compares detected features against the advertised mask with
ksft_eq().

> 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

[ ... ]

> @@ -142,7 +142,6 @@ TEST_GEN_PROGS_EXTENDED = \
>  	test_cpp \
>  	test_lirc_mode2_user \
>  	veristat \
> -	xdp_features \
>  	xdp_hw_metadata \
>  	xdp_synproxy \
>  	xskxceiver

[Severity: Low]
This isn't a bug, but tools/testing/selftests/bpf/.gitignore still has an
xdp_features entry. This patch removes the binary and its build rule from
this Makefile, and the ignore entry is now in drivers/net/hw/.gitignore.
Should the old entry be removed as well?

[ ... ]

> diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
> index 3fe49bce4f3a5..0791f02ba44b4 100644
> --- a/tools/testing/selftests/drivers/net/README.rst
> +++ b/tools/testing/selftests/drivers/net/README.rst
> @@ -310,3 +310,21 @@ 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
> +===============

[Severity: Low]
This isn't a bug, but the underline is one character shorter than the
title. "XDP feature test" is 16 characters and the underline is 15.
docutils warns "Title underline too short" for this.

[ ... ]

> 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

[ ... ]

> @@ -94,6 +95,18 @@ 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 $@

[Severity: Low]
Only the userspace helper gets KHDR_INCLUDES here. Can xdp_features.bpf.o
fail to build in an O= build?

The BPF object is built by the shared rule in
tools/testing/selftests/net/bpf.mk:

CCINCLUDE += -I$(selfdir)/bpf
CCINCLUDE += -I$(top_srcdir)/usr/include/
CCINCLUDE += -I$(SCRATCH_DIR)/include
...
	$(Q)$(CLANG) -O2 -g --target=bpf $(CCINCLUDE) $(CLANG_SYS_INCLUDES) \
	-c $< -o $@

With make O=<objdir> headers, tools/testing/selftests/Makefile sets
KHDR_INCLUDES to ${abs_objtree}/usr/include. That leaves
$(top_srcdir)/usr/include empty in a clean source tree.

The #include <linux/netdev.h> in xdp_features.bpf.c then falls back to the
system headers. Those don't have linux/netdev.h on distros whose UAPI
headers predate 6.3, for example Ubuntu 22.04.

The old selftests/bpf build passed -I$(APIDIR), and that directory has
tools/include/uapi/linux/netdev.h. Should the BPF compile for this object
also get KHDR_INCLUDES or -I$(APIDIR)?

[ ... ]

> 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..1d59772b2a4d0
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/hw/xdp_features.py

[ ... ]

> +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 shlex.join(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(shlex.join([str(cfg.xdp_features), "-q", cfg.ifname]),
> +                fail=False)

[ ... ]

> +    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):

[Severity: Low]
Is shlex.join() right for the local probe and dut_cmd? The local path of
cmd() in tools/testing/selftests/net/lib/py/utils.py does not use a shell:

            if shell is None and isinstance(comm, str) and ' ' in comm:
                comm = comm.split()

Suppose the resolved path to xdp_features contains a character that
shlex.quote() escapes, such as a space, '~', '$', '(' or '!'. The added
quotes are then passed to execve() as literal characters.

Would Popen() then raise FileNotFoundError and crash every subtest?

Only tester_cmd goes through a shell, over ssh. Could the local commands
pass an argument list instead?

> +        # Bound the remote process itself, not just the local SSH wait.
> +        result = cmd(f"timeout --signal=TERM --kill-after=5 90 {tester_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")

[Severity: Medium]
Can this report a false failure for XDP_NDO_XMIT on drivers that only
advertise it after an XDP program is attached?

tester_run() in xdp_features.c reads the advertised mask before it asks
the DUT to attach its program:

	err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
				sizeof(advertised_feature));
	...
	err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);

dut_run() answers CMD_GET_XDP_CAP with a live bpf_xdp_query(). It only
calls dut_attach_xdp_prog() when CMD_START arrives.

Several drivers set NETDEV_XDP_ACT_NDO_XMIT in their XDP attach path. One
example is virtnet_xdp_set() in drivers/net/virtio_net.c:

		if (!old_prog)
			xdp_features_set_redirect_target(dev, true);

ixgbe, i40e, ice, igb, igc, stmmac, funeth, libeth and bnxt do something
similar.

On those drivers the tester sees NOT ADVERTISED. Once the program is
attached, though, the redirected frames still come back through
ndo_xdp_xmit, so the output is:

Feature XDP_NDO_XMIT: [DETECTED][NOT ADVERTISED]

The removed shell script only checked the helper's exit code. Wouldn't
this ksft_eq() now fail on a driver that works correctly? Could the
capability query be moved after CMD_START?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010-xdp-v5-send-20261008-v5-0-de687cb67c40%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-10-11 11:17 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-10 10:22 [PATCH net-next v5 0/5] selftests: Fix and migrate the XDP feature test Bochao Cao via B4 Relay
2026-10-10 10:22 ` [PATCH net-next v5 1/5] selftests/bpf: Fix XDP feature test setup and teardown Bochao Cao via B4 Relay
2026-10-11 11:17   ` netdev-bot+sashiko
2026-10-10 10:22 ` [PATCH net-next v5 2/5] selftests/bpf: Track XDP test processes and isolate namespaces Bochao Cao via B4 Relay
2026-10-11 11:17   ` netdev-bot+sashiko
2026-10-10 10:22 ` [PATCH net-next v5 3/5] selftests/bpf: Send XDP feature probes with AF_PACKET Bochao Cao via B4 Relay
2026-10-11 11:17   ` netdev-bot+sashiko
2026-10-10 10:22 ` [PATCH net-next v5 4/5] selftests/net: Add a shared BPF skeleton rule Bochao Cao via B4 Relay
2026-10-11 11:17   ` netdev-bot+sashiko
2026-10-10 10:22 ` [PATCH net-next v5 5/5] selftests/net: Move the XDP feature test to drivers/net/hw Bochao Cao via B4 Relay
2026-10-11 11:17   ` 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