Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: drv-net: add a Python version of the GRO test

Rewrite the existing gro.sh test in Python. The conversion
not exact, the changes are related to integrating the test
with our "remote endpoint" paradigm. The test now reads
the IP addresses from the user config. It resolves the MAC
address (including running over Layer 3 networks).

Reviewed-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20251120021024.2944527-10-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+168 -106
+1 -1
tools/testing/selftests/drivers/net/Makefile
··· 11 11 # end of TEST_GEN_FILES 12 12 13 13 TEST_PROGS := \ 14 - gro.sh \ 14 + gro.py \ 15 15 hds.py \ 16 16 napi_id.py \ 17 17 napi_threaded.py \
+3
tools/testing/selftests/drivers/net/gro.c
··· 58 58 #include <unistd.h> 59 59 60 60 #include "../../kselftest.h" 61 + #include "../../net/lib/ksft.h" 61 62 62 63 #define DPORT 8000 63 64 #define SPORT 1500 ··· 1127 1126 setup_sock_filter(rxfd); 1128 1127 set_timeout(rxfd); 1129 1128 bind_packetsocket(rxfd); 1129 + 1130 + ksft_ready(); 1130 1131 1131 1132 memset(correct_payload, 0, sizeof(correct_payload)); 1132 1133
+164
tools/testing/selftests/drivers/net/gro.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + """ 5 + GRO (Generic Receive Offload) conformance tests. 6 + 7 + Validates that GRO coalescing works correctly by running the gro 8 + binary in different configurations and checking for correct packet 9 + coalescing behavior. 10 + 11 + Test cases: 12 + - data: Data packets with same size/headers and correct seq numbers coalesce 13 + - ack: Pure ACK packets do not coalesce 14 + - flags: Packets with PSH, SYN, URG, RST flags do not coalesce 15 + - tcp: Packets with incorrect checksum, non-consecutive seqno don't coalesce 16 + - ip: Packets with different ECN, TTL, TOS, or IP options don't coalesce 17 + - large: Packets larger than GRO_MAX_SIZE don't coalesce 18 + """ 19 + 20 + import os 21 + from lib.py import ksft_run, ksft_exit, ksft_pr 22 + from lib.py import NetDrvEpEnv, KsftXfailEx 23 + from lib.py import cmd, defer, bkg, ip 24 + from lib.py import ksft_variants 25 + 26 + 27 + def _resolve_dmac(cfg, ipver): 28 + """ 29 + Find the destination MAC address remote host should use to send packets 30 + towards the local host. It may be a router / gateway address. 31 + """ 32 + 33 + attr = "dmac" + ipver 34 + # Cache the response across test cases 35 + if hasattr(cfg, attr): 36 + return getattr(cfg, attr) 37 + 38 + route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}", 39 + json=True, host=cfg.remote)[0] 40 + gw = route.get("gateway") 41 + # Local L2 segment, address directly 42 + if not gw: 43 + setattr(cfg, attr, cfg.dev['address']) 44 + return getattr(cfg, attr) 45 + 46 + # ping to make sure neighbor is resolved, 47 + # bind to an interface, for v6 the GW is likely link local 48 + cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote) 49 + 50 + neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}", 51 + json=True, host=cfg.remote)[0] 52 + setattr(cfg, attr, neigh['lladdr']) 53 + return getattr(cfg, attr) 54 + 55 + 56 + def _write_defer_restore(cfg, path, val, defer_undo=False): 57 + with open(path, "r", encoding="utf-8") as fp: 58 + orig_val = fp.read().strip() 59 + if str(val) == orig_val: 60 + return 61 + with open(path, "w", encoding="utf-8") as fp: 62 + fp.write(val) 63 + if defer_undo: 64 + defer(_write_defer_restore, cfg, path, orig_val) 65 + 66 + 67 + def _set_mtu_restore(dev, mtu, host): 68 + if dev['mtu'] < mtu: 69 + ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host) 70 + defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host) 71 + 72 + 73 + def _setup(cfg, test_name): 74 + """ Setup hardware loopback mode for GRO testing. """ 75 + 76 + if not hasattr(cfg, "bin_remote"): 77 + cfg.bin_local = cfg.test_dir / "gro" 78 + cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) 79 + 80 + # "large" test needs at least 4k MTU 81 + if test_name == "large": 82 + _set_mtu_restore(cfg.dev, 4096, None) 83 + _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote) 84 + 85 + flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout" 86 + irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs" 87 + 88 + _write_defer_restore(cfg, flush_path, "200000", defer_undo=True) 89 + _write_defer_restore(cfg, irq_path, "10", defer_undo=True) 90 + 91 + try: 92 + # Disable TSO for local tests 93 + cfg.require_nsim() # will raise KsftXfailEx if not running on nsim 94 + 95 + cmd(f"ethtool -K {cfg.ifname} gro on tso off") 96 + cmd(f"ethtool -K {cfg.remote_ifname} gro on tso off", host=cfg.remote) 97 + except KsftXfailEx: 98 + pass 99 + 100 + def _gro_variants(): 101 + """Generator that yields all combinations of protocol and test types.""" 102 + 103 + for protocol in ["ipv4", "ipv6", "ipip"]: 104 + for test_name in ["data", "ack", "flags", "tcp", "ip", "large"]: 105 + yield protocol, test_name 106 + 107 + 108 + @ksft_variants(_gro_variants()) 109 + def test(cfg, protocol, test_name): 110 + """Run a single GRO test with retries.""" 111 + 112 + ipver = "6" if protocol[-1] == "6" else "4" 113 + cfg.require_ipver(ipver) 114 + 115 + _setup(cfg, test_name) 116 + 117 + base_cmd_args = [ 118 + f"--{protocol}", 119 + f"--dmac {_resolve_dmac(cfg, ipver)}", 120 + f"--smac {cfg.remote_dev['address']}", 121 + f"--daddr {cfg.addr_v[ipver]}", 122 + f"--saddr {cfg.remote_addr_v[ipver]}", 123 + f"--test {test_name}", 124 + "--verbose" 125 + ] 126 + base_args = " ".join(base_cmd_args) 127 + 128 + # Each test is run 6 times to deflake, because given the receive timing, 129 + # not all packets that should coalesce will be considered in the same flow 130 + # on every try. 131 + max_retries = 6 132 + for attempt in range(max_retries): 133 + rx_cmd = f"{cfg.bin_local} {base_args} --rx --iface {cfg.ifname}" 134 + tx_cmd = f"{cfg.bin_remote} {base_args} --iface {cfg.remote_ifname}" 135 + 136 + fail_now = attempt >= max_retries - 1 137 + 138 + with bkg(rx_cmd, ksft_ready=True, exit_wait=True, 139 + fail=fail_now) as rx_proc: 140 + cmd(tx_cmd, host=cfg.remote) 141 + 142 + if rx_proc.ret == 0: 143 + return 144 + 145 + ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# ')) 146 + ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# ')) 147 + 148 + if test_name == "large" and os.environ.get("KSFT_MACHINE_SLOW"): 149 + ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment") 150 + return 151 + 152 + ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...") 153 + 154 + 155 + def main() -> None: 156 + """ Ksft boiler plate main """ 157 + 158 + with NetDrvEpEnv(__file__) as cfg: 159 + ksft_run(cases=[test], args=(cfg,)) 160 + ksft_exit() 161 + 162 + 163 + if __name__ == "__main__": 164 + main()
-105
tools/testing/selftests/drivers/net/gro.sh
··· 1 - #!/bin/bash 2 - # SPDX-License-Identifier: GPL-2.0 3 - 4 - readonly SERVER_MAC="aa:00:00:00:00:02" 5 - readonly CLIENT_MAC="aa:00:00:00:00:01" 6 - readonly TESTS=("data" "ack" "flags" "tcp" "ip" "large") 7 - readonly PROTOS=("ipv4" "ipv6" "ipip") 8 - dev="" 9 - test="all" 10 - proto="ipv4" 11 - 12 - run_test() { 13 - local server_pid=0 14 - local exit_code=0 15 - local protocol=$1 16 - local test=$2 17 - local ARGS=( "--${protocol}" "--dmac" "${SERVER_MAC}" \ 18 - "--smac" "${CLIENT_MAC}" "--test" "${test}" "--verbose" ) 19 - 20 - setup_ns 21 - # Each test is run 6 times to deflake, because given the receive timing, 22 - # not all packets that should coalesce will be considered in the same flow 23 - # on every try. 24 - for tries in {1..6}; do 25 - # Actual test starts here 26 - ip netns exec $server_ns ./gro "${ARGS[@]}" "--rx" "--iface" "server" \ 27 - 1>>log.txt & 28 - server_pid=$! 29 - sleep 0.5 # to allow for socket init 30 - ip netns exec $client_ns ./gro "${ARGS[@]}" "--iface" "client" \ 31 - 1>>log.txt 32 - wait "${server_pid}" 33 - exit_code=$? 34 - if [[ ${test} == "large" && -n "${KSFT_MACHINE_SLOW}" && \ 35 - ${exit_code} -ne 0 ]]; then 36 - echo "Ignoring errors due to slow environment" 1>&2 37 - exit_code=0 38 - fi 39 - if [[ "${exit_code}" -eq 0 ]]; then 40 - break; 41 - fi 42 - done 43 - cleanup_ns 44 - echo ${exit_code} 45 - } 46 - 47 - run_all_tests() { 48 - local failed_tests=() 49 - for proto in "${PROTOS[@]}"; do 50 - for test in "${TESTS[@]}"; do 51 - echo "running test ${proto} ${test}" >&2 52 - exit_code=$(run_test $proto $test) 53 - if [[ "${exit_code}" -ne 0 ]]; then 54 - failed_tests+=("${proto}_${test}") 55 - fi; 56 - done; 57 - done 58 - if [[ ${#failed_tests[@]} -ne 0 ]]; then 59 - echo "failed tests: ${failed_tests[*]}. \ 60 - Please see log.txt for more logs" 61 - exit 1 62 - else 63 - echo "All Tests Succeeded!" 64 - fi; 65 - } 66 - 67 - usage() { 68 - echo "Usage: $0 \ 69 - [-i <DEV>] \ 70 - [-t data|ack|flags|tcp|ip|large] \ 71 - [-p <ipv4|ipv6>]" 1>&2; 72 - exit 1; 73 - } 74 - 75 - while getopts "i:t:p:" opt; do 76 - case "${opt}" in 77 - i) 78 - dev="${OPTARG}" 79 - ;; 80 - t) 81 - test="${OPTARG}" 82 - ;; 83 - p) 84 - proto="${OPTARG}" 85 - ;; 86 - *) 87 - usage 88 - ;; 89 - esac 90 - done 91 - 92 - if [ -n "$dev" ]; then 93 - source $(dirname $0)/../../net/lib/setup_loopback.sh 94 - else 95 - source $(dirname $0)/../../net/lib/setup_veth.sh 96 - fi 97 - 98 - setup 99 - trap cleanup EXIT 100 - if [[ "${test}" == "all" ]]; then 101 - run_all_tests 102 - else 103 - exit_code=$(run_test "${proto}" "${test}") 104 - exit $exit_code 105 - fi;