Add a test exercising the HSR forward path with GSO super-packets: a TSO-enabled SAN behind the interlink streams TCP through an HSR DUT to a peer node. The test verifies that:
* enslaved devices have GRO and HW-GRO disabled automatically, * the HSR master does not advertise GSO/TSO features, * the stream completes with zero retransmits, i.e. super-packets are unfolded per-frame at the forward entry instead of being tagged and forwarded as single oversized frames (the stream is rate-capped so the zero-retransmit discriminator is stable on CI-class hosts). The one-shot iperf3 server runs inside a wrapper shell with its exact PID recorded (pidfile appearance is polled, no startup race): cleanup kills the server and wait(1)s the wrapper on every exit path, and the success path waits and checks the server exit status, so neither the server process nor the namespaces can leak. Environments whose iproute2 lacks the HSR interlink syntax are skipped with ksft_skip. Without the fixes the feature checks fail, and on drivers that hand GRO super-packets to the HSR receive path the stream degrades or stalls. Signed-off-by: Xin Xie <[email protected]> --- tools/testing/selftests/net/hsr/Makefile | 1 + .../selftests/net/hsr/hsr_gro_superpacket.sh | 249 ++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100755 tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile index 31fb9326c..0d105476e 100644 --- a/tools/testing/selftests/net/hsr/Makefile +++ b/tools/testing/selftests/net/hsr/Makefile @@ -3,6 +3,7 @@ top_srcdir = ../../../../.. TEST_PROGS := \ + hsr_gro_superpacket.sh \ hsr_ping.sh \ hsr_redbox.sh \ link_faults.sh \ diff --git a/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh new file mode 100755 index 000000000..4b0a9ceea --- /dev/null +++ b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh @@ -0,0 +1,249 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test HSR handling of GRO/GSO super-packets: +# +# 1. Enslaving a device to an HSR master disables GRO on it +# (dev_disable_gro()). +# 2. The HSR master does not advertise GSO/TSO features. +# 3. A TCP stream from a TSO-enabled SAN (which therefore emits GSO +# super-packets) is unfolded at the HSR forward entry and delivered +# per-frame: the transfer completes with zero retransmits. +# +# Topology (100.64.0.0/24): +# +# ns_san ns_dut ns_peer +# +-----------+ interlink +---------------+ LAN A/B +-----------+ +# | s0 [0.1] |-------------| d_il hsr0 |-----------| hsr1 [0.3]| +# +-----------+ | d_a / d_b | | p_a / p_b | +# +---------------+ +-----------+ +# +# SAN traffic reaches ns_peer only through hsr0's forward path +# (interlink RX -> LAN A/B TX), so every SAN frame is tagged and +# forwarded by the DUT. + +source ./hsr_common.sh + +ns_dut="hsr-gro-dut" +ns_san="hsr-gro-san" +ns_peer="hsr-gro-peer" +san_ip="100.64.0.1" +peer_ip="100.64.0.3" +iperf_pid="" +server_wrapper="" +iperf_pidfile="/tmp/hsr_gro_iperf.pid" + +cleanup() +{ + if [ -n "${iperf_pid}" ] && [[ "${iperf_pid}" =~ ^[0-9]+$ ]]; then + kill "${iperf_pid}" 2>/dev/null + fi + if [ -n "${server_wrapper}" ]; then + # the wrapper waits on the server; reap it with a 5s bound so a + # live-but-unpublished server can never hang cleanup + for _ in $(seq 1 50); do + kill -0 "${server_wrapper}" 2>/dev/null || break + sleep 0.1 + done + kill "${server_wrapper}" 2>/dev/null + wait "${server_wrapper}" 2>/dev/null + server_wrapper="" + fi + # last resort, namespace-scoped only: kill iperf3 processes that + # actually live in the peer netns. netns != pidns: a blind pkill + # would scan the host PID space and hit unrelated tests. + for _p in $(ip netns pids "$ns_peer" 2>/dev/null); do + if [ "$(cat /proc/$_p/comm 2>/dev/null)" = "iperf3" ]; then + kill "$_p" 2>/dev/null + fi + done + iperf_pid="" + rm -f "${iperf_pidfile}" + ip netns del "$ns_dut" 2>/dev/null + ip netns del "$ns_san" 2>/dev/null + ip netns del "$ns_peer" 2>/dev/null +} + +trap cleanup EXIT + +check_tool() +{ + if ! command -v "$1" > /dev/null 2>&1; then + echo "SKIP: Could not run test without $1" + exit $ksft_skip + fi +} + +nsx() +{ + ip netns exec "$1" bash -c "$2" +} + +setup_topo() +{ + local ns + + cleanup + for ns in "$ns_dut" "$ns_san" "$ns_peer"; do + ip netns add "$ns" + done + + ip link add d_a netns "$ns_dut" type veth peer name p_a netns "$ns_peer" + ip link add d_b netns "$ns_dut" type veth peer name p_b netns "$ns_peer" + ip link add d_il netns "$ns_dut" type veth peer name s0 netns "$ns_san" + + # HSR tags add 6 bytes per frame; give the LAN legs headroom. + for iface in d_a d_b; do + nsx "$ns_dut" "ip link set $iface mtu 1600; ip link set $iface up" + done + for iface in p_a p_b; do + nsx "$ns_peer" "ip link set $iface mtu 1600; ip link set $iface up" + done + + nsx "$ns_dut" "ip link set d_il up" + nsx "$ns_san" "ip link set s0 up; ip addr add $san_ip/24 dev s0" + + nsx "$ns_dut" "ip link add hsr0 type hsr \ + slave1 d_a slave2 d_b interlink d_il proto 0; \ + ip link set hsr0 up" + nsx "$ns_peer" "ip link add hsr1 type hsr \ + slave1 p_a slave2 p_b proto 0; \ + ip link set hsr1 up; ip addr add $peer_ip/24 dev hsr1" + + # Let the nodes see each other's supervision frames. + sleep 2 +} + +check_feature() +{ + local ns="$1" + local iface="$2" + local feature="$3" + local want="$4" + + if nsx "$ns" "ethtool -k $iface" | grep -q "^$feature: $want"; then + echo "INFO: $ns/$iface $feature is $want [ OK ]" + else + echo "FAIL: $ns/$iface $feature is not $want" 1>&2 + ret=1 + fi +} + +do_gro_feature_checks() +{ + echo "INFO: Checking that enslavement disabled GRO." + check_feature "$ns_dut" d_a generic-receive-offload off + check_feature "$ns_dut" d_b generic-receive-offload off + check_feature "$ns_dut" d_il generic-receive-offload off + stop_if_error "GRO not disabled on enslaved devices." + + echo "INFO: Checking that enslavement disabled HW-GRO." + check_feature "$ns_dut" d_a rx-gro-hw off + check_feature "$ns_dut" d_b rx-gro-hw off + check_feature "$ns_dut" d_il rx-gro-hw off + stop_if_error "HW-GRO not disabled on enslaved devices." + + echo "INFO: Checking that the HSR master does not advertise GSO/TSO." + check_feature "$ns_dut" hsr0 generic-segmentation-offload off + check_feature "$ns_dut" hsr0 tcp-segmentation-offload off + stop_if_error "HSR master still advertises GSO/TSO." +} + +start_iperf_server() +{ + # One-shot server, no -D: record its exact PID (netns shares the PID + # namespace). The wrapping shell waits on the server so the script can + # really wait(1) for the whole tree, on success and failure alike. + rm -f "${iperf_pidfile}" + ( nsx "$ns_peer" "iperf3 -s -1 > /dev/null 2>&1 & echo \$! > ${iperf_pidfile}; wait" ) & + server_wrapper=$! + # the wrapper writes the pidfile asynchronously; wait for it to + # appear instead of racing the read + for _ in $(seq 1 50); do + [ -s "${iperf_pidfile}" ] && break + sleep 0.1 + done + if [ ! -s "${iperf_pidfile}" ]; then + echo "FAIL: iperf3 server did not publish a pid (no pidfile)" 1>&2 + ret=1 + return 1 + fi + iperf_pid=$(cat "${iperf_pidfile}") + if ! [[ "${iperf_pid}" =~ ^[0-9]+$ ]] || ! kill -0 "${iperf_pid}" 2>/dev/null; then + echo "FAIL: iperf3 server pid '${iperf_pid}' invalid or not alive" 1>&2 + ret=1 + return 1 + fi + sleep 1 + return 0 +} + +do_tso_stream_test() +{ + local out sender_retr + + echo "INFO: Enabling TSO/GSO on the SAN interface." + nsx "$ns_san" "ethtool -K s0 tso on gso on" + check_feature "$ns_san" s0 tcp-segmentation-offload on + stop_if_error "Could not enable TSO on the SAN interface." + + echo "INFO: Running 10s TCP stream SAN -> peer through the HSR DUT." + start_iperf_server || return + # rate-capped: the discriminator is per-frame unfold (completion with + # 0 retransmits), not max throughput; uncapped runs flap at VM/CI + # edge rates without indicating a functional problem. + out=$(nsx "$ns_san" "timeout 60 iperf3 -c $peer_ip -M 1446 -b 2G -t 10" 2>&1) + if [ $? -ne 0 ]; then + echo "FAIL: iperf3 client failed:" 1>&2 + echo "$out" 1>&2 + ret=1 + return + fi + + # success path: the one-shot server exits by itself; really wait for + # the wrapper to reap it, and check its exit status + wait "${server_wrapper}" + server_rc=$? + server_wrapper="" + iperf_pid="" + if [ "$server_rc" -ne 0 ]; then + echo "FAIL: iperf3 server exited with rc=$server_rc" 1>&2 + ret=1 + return + fi + + sender_retr=$(echo "$out" | awk '/sender/ {print $(NF-1)}') + if [ -z "$sender_retr" ] || [ "$sender_retr" -ne 0 ]; then + echo "FAIL: retransmits during GSO unfold: ${sender_retr:-unknown}" 1>&2 + echo "$out" 1>&2 + ret=1 + return + fi + + echo "INFO: TCP stream completed with 0 retransmits [ OK ]" + echo "$out" | grep -E "sender|receiver" +} + +check_prerequisites +check_tool ethtool +check_tool iperf3 +check_tool timeout + +# iproute2 must know the HSR interlink syntax. +if ! ip link help hsr 2>&1 | grep -qi interlink; then + echo "SKIP: iproute2 has no HSR interlink support" + exit $ksft_skip +fi + +setup_topo + +echo "INFO: Initial validation ping (SAN -> peer through the DUT)." +do_ping "$ns_san" "$peer_ip" +stop_if_error "Initial validation failed." + +do_gro_feature_checks +do_tso_stream_test +stop_if_error "GSO super-packet stream test failed." + +echo "INFO: All good." +exit $ret -- 2.43.0

