From: Bochao Cao <[email protected]> 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 <[email protected]> --- 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)

