1*ae57a553SNeil Spring#!/bin/bash 2*ae57a553SNeil Spring# SPDX-License-Identifier: GPL-2.0 3*ae57a553SNeil Spring# 4*ae57a553SNeil Spring# Test local ECMP path re-selection on TCP retransmission timeout and PLB. 5*ae57a553SNeil Spring# 6*ae57a553SNeil Spring# Two namespaces connected by two parallel veth pairs with a 2-way ECMP 7*ae57a553SNeil Spring# route. When a TCP path is blocked (via tc drop) or congested (via 8*ae57a553SNeil Spring# netem ECN marking), the kernel rehashes the connection via 9*ae57a553SNeil Spring# sk_rethink_txhash() + __sk_dst_reset(), causing the next route lookup 10*ae57a553SNeil Spring# to select the other ECMP path. 11*ae57a553SNeil Spring# 12*ae57a553SNeil Spring# Expected runtime: ~60 seconds. Most time is spent waiting for TCP 13*ae57a553SNeil Spring# retransmission timeouts (1-7s per test) and running multi-round 14*ae57a553SNeil Spring# consistency checks (10 rounds each). The large slowwait/connect-timeout 15*ae57a553SNeil Spring# values (30-120s) are worst-case bounds for CI; a correctly functioning 16*ae57a553SNeil Spring# kernel reaches each check well before the timeout expires. 17*ae57a553SNeil Spring 18*ae57a553SNeil Springsource lib.sh 19*ae57a553SNeil Spring 20*ae57a553SNeil SpringSUBNETS=(a b) 21*ae57a553SNeil SpringPORT=9900 22*ae57a553SNeil Spring: "${ECMP_REBUILD_ROUNDS:=10}" 23*ae57a553SNeil Spring 24*ae57a553SNeil Spring# alloc_ports NAME [COUNT]: set NAME to the next free port and reserve 25*ae57a553SNeil Spring# COUNT ports (default 1) from a shared counter. Each test allocates its 26*ae57a553SNeil Spring# own port(s) where it runs, so a retry or a newly added test never 27*ae57a553SNeil Spring# collides; the per-round tests reserve ECMP_REBUILD_ROUNDS each. 28*ae57a553SNeil SpringNEXT_PORT=$PORT 29*ae57a553SNeil Springalloc_ports() 30*ae57a553SNeil Spring{ 31*ae57a553SNeil Spring printf -v "$1" '%d' "$NEXT_PORT" 32*ae57a553SNeil Spring NEXT_PORT=$((NEXT_PORT + ${2:-1})) 33*ae57a553SNeil Spring} 34*ae57a553SNeil Spring 35*ae57a553SNeil SpringALL_TESTS=" 36*ae57a553SNeil Spring test_ecmp_syn_rehash 37*ae57a553SNeil Spring test_ecmp_synack_rehash 38*ae57a553SNeil Spring test_ecmp_midstream_rehash 39*ae57a553SNeil Spring test_ecmp_midstream_ack_rehash 40*ae57a553SNeil Spring test_ecmp_plb_rehash 41*ae57a553SNeil Spring test_ecmp_hash_policy1_no_rehash 42*ae57a553SNeil Spring test_ecmp_no_flowlabel_leak 43*ae57a553SNeil Spring test_ecmp_dst_rebuild_consistency 44*ae57a553SNeil Spring test_ecmp_syncookie_path_consistency 45*ae57a553SNeil Spring" 46*ae57a553SNeil Spring 47*ae57a553SNeil Springlink_tx_packets_get() 48*ae57a553SNeil Spring{ 49*ae57a553SNeil Spring local ns=$1; shift 50*ae57a553SNeil Spring local dev=$1; shift 51*ae57a553SNeil Spring 52*ae57a553SNeil Spring ip netns exec "$ns" cat "/sys/class/net/$dev/statistics/tx_packets" 53*ae57a553SNeil Spring} 54*ae57a553SNeil Spring 55*ae57a553SNeil Spring# Return the number of packets matched by the tc filter action on a device. 56*ae57a553SNeil Spring# When tc drops packets via "action drop", the device's tx_packets is not 57*ae57a553SNeil Spring# incremented (packet never reaches veth_xmit), but the tc action maintains 58*ae57a553SNeil Spring# its own counter. 59*ae57a553SNeil Springtc_filter_pkt_count() 60*ae57a553SNeil Spring{ 61*ae57a553SNeil Spring local ns=$1; shift 62*ae57a553SNeil Spring local dev=$1; shift 63*ae57a553SNeil Spring 64*ae57a553SNeil Spring ip netns exec "$ns" tc -s filter show dev "$dev" parent 1: 2>/dev/null | 65*ae57a553SNeil Spring awk '/Sent .* pkt/ { 66*ae57a553SNeil Spring for (i=1; i<=NF; i++) 67*ae57a553SNeil Spring if ($i == "pkt") { print $(i-1); exit } 68*ae57a553SNeil Spring }' 69*ae57a553SNeil Spring} 70*ae57a553SNeil Spring 71*ae57a553SNeil Spring# Read a TcpExt counter from /proc/net/netstat in a namespace. 72*ae57a553SNeil Spring# Returns 0 if the counter is not found. 73*ae57a553SNeil Springget_netstat_counter() 74*ae57a553SNeil Spring{ 75*ae57a553SNeil Spring local ns=$1; shift 76*ae57a553SNeil Spring local field=$1; shift 77*ae57a553SNeil Spring local val 78*ae57a553SNeil Spring 79*ae57a553SNeil Spring # shellcheck disable=SC2016 80*ae57a553SNeil Spring val=$(ip netns exec "$ns" awk -v key="$field" ' 81*ae57a553SNeil Spring /^TcpExt:/ { 82*ae57a553SNeil Spring if (!h) { split($0, n); h=1 } 83*ae57a553SNeil Spring else { 84*ae57a553SNeil Spring split($0, v) 85*ae57a553SNeil Spring for (i in n) 86*ae57a553SNeil Spring if (n[i] == key) print v[i] 87*ae57a553SNeil Spring } 88*ae57a553SNeil Spring } 89*ae57a553SNeil Spring ' /proc/net/netstat) 90*ae57a553SNeil Spring echo "${val:-0}" 91*ae57a553SNeil Spring} 92*ae57a553SNeil Spring 93*ae57a553SNeil Spring# Apply netem ECN marking: CE-mark all ECT packets instead of dropping them. 94*ae57a553SNeil Springmark_ecn() 95*ae57a553SNeil Spring{ 96*ae57a553SNeil Spring local ns=$1; shift 97*ae57a553SNeil Spring local dev=$1; shift 98*ae57a553SNeil Spring 99*ae57a553SNeil Spring ip netns exec "$ns" tc qdisc add dev "$dev" root netem loss 100% ecn 100*ae57a553SNeil Spring} 101*ae57a553SNeil Spring 102*ae57a553SNeil Spring# Block TCP (IPv6 next-header = 6) egress, allowing ICMPv6 through. 103*ae57a553SNeil Springblock_tcp() 104*ae57a553SNeil Spring{ 105*ae57a553SNeil Spring local ns=$1; shift 106*ae57a553SNeil Spring local dev=$1; shift 107*ae57a553SNeil Spring 108*ae57a553SNeil Spring ip netns exec "$ns" tc qdisc add dev "$dev" root handle 1: prio 109*ae57a553SNeil Spring ip netns exec "$ns" tc filter add dev "$dev" parent 1: \ 110*ae57a553SNeil Spring protocol ipv6 prio 1 u32 match u8 0x06 0xff at 6 action drop 111*ae57a553SNeil Spring} 112*ae57a553SNeil Spring 113*ae57a553SNeil Springunblock_tcp() 114*ae57a553SNeil Spring{ 115*ae57a553SNeil Spring local ns=$1; shift 116*ae57a553SNeil Spring local dev=$1; shift 117*ae57a553SNeil Spring 118*ae57a553SNeil Spring ip netns exec "$ns" tc qdisc del dev "$dev" root 2>/dev/null 119*ae57a553SNeil Spring} 120*ae57a553SNeil Spring 121*ae57a553SNeil Spring# Return success when a device's TX counter exceeds a baseline value. 122*ae57a553SNeil Springdev_tx_packets_above() 123*ae57a553SNeil Spring{ 124*ae57a553SNeil Spring local ns=$1; shift 125*ae57a553SNeil Spring local dev=$1; shift 126*ae57a553SNeil Spring local baseline=$1; shift 127*ae57a553SNeil Spring 128*ae57a553SNeil Spring local cur 129*ae57a553SNeil Spring cur=$(link_tx_packets_get "$ns" "$dev") 130*ae57a553SNeil Spring [ "$cur" -gt "$baseline" ] 131*ae57a553SNeil Spring} 132*ae57a553SNeil Spring 133*ae57a553SNeil Spring# Return success when both devices have dropped at least one TCP packet. 134*ae57a553SNeil Springboth_devs_attempted() 135*ae57a553SNeil Spring{ 136*ae57a553SNeil Spring local ns=$1; shift 137*ae57a553SNeil Spring local dev0=$1; shift 138*ae57a553SNeil Spring local dev1=$1; shift 139*ae57a553SNeil Spring 140*ae57a553SNeil Spring local c0 c1 141*ae57a553SNeil Spring c0=$(tc_filter_pkt_count "$ns" "$dev0") 142*ae57a553SNeil Spring c1=$(tc_filter_pkt_count "$ns" "$dev1") 143*ae57a553SNeil Spring [ "${c0:-0}" -ge 1 ] && [ "${c1:-0}" -ge 1 ] 144*ae57a553SNeil Spring} 145*ae57a553SNeil Spring 146*ae57a553SNeil Springlink_tx_packets_total() 147*ae57a553SNeil Spring{ 148*ae57a553SNeil Spring local ns=$1; shift 149*ae57a553SNeil Spring local dev0=${1:-veth0a}; shift 2>/dev/null 150*ae57a553SNeil Spring local dev1=${1:-veth1a} 151*ae57a553SNeil Spring 152*ae57a553SNeil Spring echo $(( $(link_tx_packets_get "$ns" "$dev0") + 153*ae57a553SNeil Spring $(link_tx_packets_get "$ns" "$dev1") )) 154*ae57a553SNeil Spring} 155*ae57a553SNeil Spring 156*ae57a553SNeil Spring# (Re)install the ECMP multipath routes between NS1 and NS2. $1 is the 157*ae57a553SNeil Spring# ip route operation ("add" to create, "change" to replace). If $2 is 158*ae57a553SNeil Spring# given it names a congestion control to pin on both routes via "congctl"; 159*ae57a553SNeil Spring# because dctcp carries TCP_CONG_NEEDS_ECN, this also tags the route with 160*ae57a553SNeil Spring# DST_FEATURE_ECN_CA, which makes the server negotiate ECN without the 161*ae57a553SNeil Spring# listener itself having to run dctcp. The nexthop topology lives here 162*ae57a553SNeil Spring# only, so a test can re-pin the routes and restore them with one call. 163*ae57a553SNeil Springinstall_ecmp_routes() 164*ae57a553SNeil Spring{ 165*ae57a553SNeil Spring local op=$1 cc=$2 166*ae57a553SNeil Spring local -a cc_attr=() 167*ae57a553SNeil Spring 168*ae57a553SNeil Spring [ -n "$cc" ] && cc_attr=(congctl "$cc") 169*ae57a553SNeil Spring 170*ae57a553SNeil Spring ip -n "$NS1" -6 route "$op" fd00:ff::2/128 "${cc_attr[@]}" \ 171*ae57a553SNeil Spring nexthop via fd00:a::2 dev veth0a \ 172*ae57a553SNeil Spring nexthop via fd00:b::2 dev veth1a 173*ae57a553SNeil Spring 174*ae57a553SNeil Spring ip -n "$NS2" -6 route "$op" fd00:ff::1/128 "${cc_attr[@]}" \ 175*ae57a553SNeil Spring nexthop via fd00:a::1 dev veth0b \ 176*ae57a553SNeil Spring nexthop via fd00:b::1 dev veth1b 177*ae57a553SNeil Spring} 178*ae57a553SNeil Spring 179*ae57a553SNeil Springsetup() 180*ae57a553SNeil Spring{ 181*ae57a553SNeil Spring setup_ns NS1 NS2 182*ae57a553SNeil Spring 183*ae57a553SNeil Spring local ns 184*ae57a553SNeil Spring for ns in "$NS1" "$NS2"; do 185*ae57a553SNeil Spring ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.accept_dad=0 186*ae57a553SNeil Spring ip netns exec "$ns" sysctl -qw net.ipv6.conf.default.accept_dad=0 187*ae57a553SNeil Spring ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.forwarding=1 188*ae57a553SNeil Spring ip netns exec "$ns" sysctl -qw net.core.txrehash=1 189*ae57a553SNeil Spring done 190*ae57a553SNeil Spring 191*ae57a553SNeil Spring local i sub 192*ae57a553SNeil Spring for i in 0 1; do 193*ae57a553SNeil Spring sub=${SUBNETS[$i]} 194*ae57a553SNeil Spring ip link add "veth${i}a" type veth peer name "veth${i}b" 195*ae57a553SNeil Spring ip link set "veth${i}a" netns "$NS1" 196*ae57a553SNeil Spring ip link set "veth${i}b" netns "$NS2" 197*ae57a553SNeil Spring ip -n "$NS1" addr add "fd00:${sub}::1/64" dev "veth${i}a" 198*ae57a553SNeil Spring ip -n "$NS2" addr add "fd00:${sub}::2/64" dev "veth${i}b" 199*ae57a553SNeil Spring ip -n "$NS1" link set "veth${i}a" up 200*ae57a553SNeil Spring ip -n "$NS2" link set "veth${i}b" up 201*ae57a553SNeil Spring done 202*ae57a553SNeil Spring 203*ae57a553SNeil Spring ip -n "$NS1" addr add fd00:ff::1/128 dev lo 204*ae57a553SNeil Spring ip -n "$NS2" addr add fd00:ff::2/128 dev lo 205*ae57a553SNeil Spring 206*ae57a553SNeil Spring # Allow many SYN retries at 1-second intervals (linear, no 207*ae57a553SNeil Spring # exponential backoff) so the rehash test has enough attempts 208*ae57a553SNeil Spring # to exercise both ECMP paths. 209*ae57a553SNeil Spring if ! ip netns exec "$NS1" sysctl -qw \ 210*ae57a553SNeil Spring net.ipv4.tcp_syn_linear_timeouts=25; then 211*ae57a553SNeil Spring echo "SKIP: tcp_syn_linear_timeouts not supported" 212*ae57a553SNeil Spring return "$ksft_skip" 213*ae57a553SNeil Spring fi 214*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_retries=25 215*ae57a553SNeil Spring 216*ae57a553SNeil Spring # Keep the server's request socket alive during the blocking 217*ae57a553SNeil Spring # period so SYN/ACK retransmits continue. 218*ae57a553SNeil Spring ip netns exec "$NS2" sysctl -qw net.ipv4.tcp_synack_retries=25 219*ae57a553SNeil Spring 220*ae57a553SNeil Spring install_ecmp_routes add 221*ae57a553SNeil Spring 222*ae57a553SNeil Spring for i in 0 1; do 223*ae57a553SNeil Spring sub=${SUBNETS[$i]} 224*ae57a553SNeil Spring ip netns exec "$NS1" \ 225*ae57a553SNeil Spring ping -6 -c1 -W5 "fd00:${sub}::2" &>/dev/null 226*ae57a553SNeil Spring ip netns exec "$NS2" \ 227*ae57a553SNeil Spring ping -6 -c1 -W5 "fd00:${sub}::1" &>/dev/null 228*ae57a553SNeil Spring done 229*ae57a553SNeil Spring 230*ae57a553SNeil Spring if ! ip netns exec "$NS1" ping -6 -c1 -W5 fd00:ff::2 &>/dev/null; then 231*ae57a553SNeil Spring echo "Basic connectivity check failed" 232*ae57a553SNeil Spring return "$ksft_skip" 233*ae57a553SNeil Spring fi 234*ae57a553SNeil Spring} 235*ae57a553SNeil Spring 236*ae57a553SNeil Spring# Block ALL paths, start a connection, wait until SYNs have been dropped 237*ae57a553SNeil Spring# on both interfaces (proving rehash steered the SYN to a new path), then 238*ae57a553SNeil Spring# unblock so the connection completes. 239*ae57a553SNeil Springtest_ecmp_syn_rehash() 240*ae57a553SNeil Spring{ 241*ae57a553SNeil Spring RET=0 242*ae57a553SNeil Spring local port 243*ae57a553SNeil Spring alloc_ports port 244*ae57a553SNeil Spring 245*ae57a553SNeil Spring block_tcp "$NS1" veth0a 246*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth0a 247*ae57a553SNeil Spring block_tcp "$NS1" veth1a 248*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth1a 249*ae57a553SNeil Spring 250*ae57a553SNeil Spring ip netns exec "$NS2" socat \ 251*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \ 252*ae57a553SNeil Spring EXEC:"echo ESTABLISH_OK" & 253*ae57a553SNeil Spring defer kill_process $! 254*ae57a553SNeil Spring 255*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 256*ae57a553SNeil Spring 257*ae57a553SNeil Spring local rehash_before 258*ae57a553SNeil Spring rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 259*ae57a553SNeil Spring 260*ae57a553SNeil Spring # Start the connection in the background; it will retry SYNs at 261*ae57a553SNeil Spring # 1-second intervals until an unblocked path is found. 262*ae57a553SNeil Spring # Use -u (unidirectional) to only receive from the server; 263*ae57a553SNeil Spring # sending data back would risk SIGPIPE if the server's EXEC 264*ae57a553SNeil Spring # child has already exited. 265*ae57a553SNeil Spring local tmpfile 266*ae57a553SNeil Spring tmpfile=$(mktemp) 267*ae57a553SNeil Spring defer rm -f "$tmpfile" 268*ae57a553SNeil Spring 269*ae57a553SNeil Spring ip netns exec "$NS1" socat -u \ 270*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60" \ 271*ae57a553SNeil Spring STDOUT >"$tmpfile" 2>&1 & 272*ae57a553SNeil Spring local client_pid=$! 273*ae57a553SNeil Spring defer kill_process "$client_pid" 274*ae57a553SNeil Spring 275*ae57a553SNeil Spring # Wait until both paths have seen at least one dropped SYN. 276*ae57a553SNeil Spring # This proves sk_rethink_txhash() rehashed the connection from 277*ae57a553SNeil Spring # one ECMP path to the other. 278*ae57a553SNeil Spring slowwait 30 both_devs_attempted "$NS1" veth0a veth1a > /dev/null 279*ae57a553SNeil Spring check_err $? "SYNs did not appear on both paths (rehash not working)" 280*ae57a553SNeil Spring if [ "$RET" -ne 0 ]; then 281*ae57a553SNeil Spring log_test "Local ECMP SYN rehash: establish with blocked paths" 282*ae57a553SNeil Spring return 283*ae57a553SNeil Spring fi 284*ae57a553SNeil Spring 285*ae57a553SNeil Spring # Unblock both paths and let the next SYN retransmit succeed. 286*ae57a553SNeil Spring unblock_tcp "$NS1" veth0a 287*ae57a553SNeil Spring unblock_tcp "$NS1" veth1a 288*ae57a553SNeil Spring 289*ae57a553SNeil Spring local rc=0 290*ae57a553SNeil Spring wait "$client_pid" || rc=$? 291*ae57a553SNeil Spring 292*ae57a553SNeil Spring local result 293*ae57a553SNeil Spring result=$(cat "$tmpfile" 2>/dev/null) 294*ae57a553SNeil Spring 295*ae57a553SNeil Spring if [[ "$result" != *"ESTABLISH_OK"* ]]; then 296*ae57a553SNeil Spring check_err 1 "connection failed after unblocking (rc=$rc): $result" 297*ae57a553SNeil Spring fi 298*ae57a553SNeil Spring 299*ae57a553SNeil Spring local rehash_after 300*ae57a553SNeil Spring rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 301*ae57a553SNeil Spring if [ "$rehash_after" -le "$rehash_before" ]; then 302*ae57a553SNeil Spring check_err 1 "TcpTimeoutRehash counter did not increment" 303*ae57a553SNeil Spring fi 304*ae57a553SNeil Spring 305*ae57a553SNeil Spring log_test "Local ECMP SYN rehash: establish with blocked paths" 306*ae57a553SNeil Spring} 307*ae57a553SNeil Spring 308*ae57a553SNeil Spring# Block the server's return paths so SYN/ACKs are dropped. The client 309*ae57a553SNeil Spring# retransmits SYNs at 1-second intervals; each duplicate SYN arriving at 310*ae57a553SNeil Spring# the server triggers tcp_rtx_synack() which re-rolls txhash, so the 311*ae57a553SNeil Spring# retransmitted SYN/ACK selects a different ECMP return path. 312*ae57a553SNeil Springtest_ecmp_synack_rehash() 313*ae57a553SNeil Spring{ 314*ae57a553SNeil Spring RET=0 315*ae57a553SNeil Spring local port 316*ae57a553SNeil Spring alloc_ports port 317*ae57a553SNeil Spring 318*ae57a553SNeil Spring block_tcp "$NS2" veth0b 319*ae57a553SNeil Spring defer unblock_tcp "$NS2" veth0b 320*ae57a553SNeil Spring block_tcp "$NS2" veth1b 321*ae57a553SNeil Spring defer unblock_tcp "$NS2" veth1b 322*ae57a553SNeil Spring 323*ae57a553SNeil Spring ip netns exec "$NS2" socat \ 324*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \ 325*ae57a553SNeil Spring EXEC:"echo SYNACK_OK" & 326*ae57a553SNeil Spring defer kill_process $! 327*ae57a553SNeil Spring 328*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 329*ae57a553SNeil Spring 330*ae57a553SNeil Spring # Start the connection; SYNs reach the server (client egress is 331*ae57a553SNeil Spring # open) but SYN/ACKs are dropped on the server's return path. 332*ae57a553SNeil Spring local tmpfile 333*ae57a553SNeil Spring tmpfile=$(mktemp) 334*ae57a553SNeil Spring defer rm -f "$tmpfile" 335*ae57a553SNeil Spring 336*ae57a553SNeil Spring ip netns exec "$NS1" socat -u \ 337*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60" \ 338*ae57a553SNeil Spring STDOUT >"$tmpfile" 2>&1 & 339*ae57a553SNeil Spring local client_pid=$! 340*ae57a553SNeil Spring defer kill_process "$client_pid" 341*ae57a553SNeil Spring 342*ae57a553SNeil Spring # Wait until both server-side interfaces have dropped at least 343*ae57a553SNeil Spring # one SYN/ACK, proving the server rehashed its return path. 344*ae57a553SNeil Spring slowwait 30 both_devs_attempted "$NS2" veth0b veth1b > /dev/null 345*ae57a553SNeil Spring check_err $? "SYN/ACKs did not appear on both return paths" 346*ae57a553SNeil Spring if [ "$RET" -ne 0 ]; then 347*ae57a553SNeil Spring log_test "Local ECMP SYN/ACK rehash: blocked return path" 348*ae57a553SNeil Spring return 349*ae57a553SNeil Spring fi 350*ae57a553SNeil Spring 351*ae57a553SNeil Spring # Unblock and let the connection complete. 352*ae57a553SNeil Spring unblock_tcp "$NS2" veth0b 353*ae57a553SNeil Spring unblock_tcp "$NS2" veth1b 354*ae57a553SNeil Spring 355*ae57a553SNeil Spring local rc=0 356*ae57a553SNeil Spring wait "$client_pid" || rc=$? 357*ae57a553SNeil Spring 358*ae57a553SNeil Spring local result 359*ae57a553SNeil Spring result=$(cat "$tmpfile" 2>/dev/null) 360*ae57a553SNeil Spring 361*ae57a553SNeil Spring if [[ "$result" != *"SYNACK_OK"* ]]; then 362*ae57a553SNeil Spring check_err 1 "connection failed after unblocking (rc=$rc): $result" 363*ae57a553SNeil Spring fi 364*ae57a553SNeil Spring 365*ae57a553SNeil Spring log_test "Local ECMP SYN/ACK rehash: blocked return path" 366*ae57a553SNeil Spring} 367*ae57a553SNeil Spring 368*ae57a553SNeil Spring# Establish a data transfer with both paths open, then block the 369*ae57a553SNeil Spring# active path. Verify that data appears on the previously inactive 370*ae57a553SNeil Spring# path (proving RTO triggered a rehash) and that TcpTimeoutRehash 371*ae57a553SNeil Spring# incremented. 372*ae57a553SNeil Spring# 373*ae57a553SNeil Spring# With 2-way ECMP each rehash may pick the same path, so a single 374*ae57a553SNeil Spring# attempt can occasionally fail. Retry once for robustness. 375*ae57a553SNeil Spring 376*ae57a553SNeil Spring# Single attempt at the midstream rehash check. Returns 0 on success. 377*ae57a553SNeil Springecmp_midstream_rehash_attempt() 378*ae57a553SNeil Spring{ 379*ae57a553SNeil Spring local port=$1; shift 380*ae57a553SNeil Spring local reason="" 381*ae57a553SNeil Spring 382*ae57a553SNeil Spring ip netns exec "$NS2" socat -u \ 383*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null & 384*ae57a553SNeil Spring local server_pid=$! 385*ae57a553SNeil Spring 386*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 387*ae57a553SNeil Spring 388*ae57a553SNeil Spring local base_tx0 base_tx1 389*ae57a553SNeil Spring base_tx0=$(link_tx_packets_get "$NS1" veth0a) 390*ae57a553SNeil Spring base_tx1=$(link_tx_packets_get "$NS1" veth1a) 391*ae57a553SNeil Spring 392*ae57a553SNeil Spring # Continuous data source; timeout caps overall test duration and 393*ae57a553SNeil Spring # must exceed the slowwait below so data keeps flowing. 394*ae57a553SNeil Spring ip netns exec "$NS1" timeout 90 socat -u \ 395*ae57a553SNeil Spring OPEN:/dev/zero \ 396*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" &>/dev/null & 397*ae57a553SNeil Spring local client_pid=$! 398*ae57a553SNeil Spring 399*ae57a553SNeil Spring # Wait for enough packets to identify the active path. 400*ae57a553SNeil Spring if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 401*ae57a553SNeil Spring ">= $((base_tx0 + base_tx1 + 10))" \ 402*ae57a553SNeil Spring link_tx_packets_total "$NS1" > /dev/null; then 403*ae57a553SNeil Spring kill "$client_pid" "$server_pid" 2>/dev/null 404*ae57a553SNeil Spring wait "$client_pid" "$server_pid" 2>/dev/null 405*ae57a553SNeil Spring echo "no TX activity" 406*ae57a553SNeil Spring return 1 407*ae57a553SNeil Spring fi 408*ae57a553SNeil Spring 409*ae57a553SNeil Spring # Find the active path and block it. 410*ae57a553SNeil Spring local current_tx0 current_tx1 active_idx inactive_idx 411*ae57a553SNeil Spring current_tx0=$(link_tx_packets_get "$NS1" veth0a) 412*ae57a553SNeil Spring current_tx1=$(link_tx_packets_get "$NS1" veth1a) 413*ae57a553SNeil Spring if [ $((current_tx0 - base_tx0)) -ge $((current_tx1 - base_tx1)) ]; then 414*ae57a553SNeil Spring active_idx=0; inactive_idx=1 415*ae57a553SNeil Spring else 416*ae57a553SNeil Spring active_idx=1; inactive_idx=0 417*ae57a553SNeil Spring fi 418*ae57a553SNeil Spring 419*ae57a553SNeil Spring local rehash_before 420*ae57a553SNeil Spring rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 421*ae57a553SNeil Spring # Suppress __dst_negative_advice() in tcp_write_timeout() so 422*ae57a553SNeil Spring # that __sk_dst_reset() is the only dst-invalidation mechanism 423*ae57a553SNeil Spring # on the RTO path. 424*ae57a553SNeil Spring local saved_retries1 425*ae57a553SNeil Spring saved_retries1=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_retries1) 426*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_retries1=255 427*ae57a553SNeil Spring 428*ae57a553SNeil Spring block_tcp "$NS1" "veth${active_idx}a" 429*ae57a553SNeil Spring 430*ae57a553SNeil Spring # Capture baseline after block_tcp returns. block_tcp adds a 431*ae57a553SNeil Spring # prio qdisc then a tc filter; between those two steps the 432*ae57a553SNeil Spring # qdisc's CAN_BYPASS fast-path lets packets through unfiltered. 433*ae57a553SNeil Spring local inactive_before 434*ae57a553SNeil Spring inactive_before=$(link_tx_packets_get "$NS1" "veth${inactive_idx}a") 435*ae57a553SNeil Spring 436*ae57a553SNeil Spring # Wait for meaningful data on the previously inactive path, 437*ae57a553SNeil Spring # proving RTO triggered a rehash and data actually moved. 438*ae57a553SNeil Spring if ! slowwait 60 dev_tx_packets_above \ 439*ae57a553SNeil Spring "$NS1" "veth${inactive_idx}a" "$((inactive_before + 100))" \ 440*ae57a553SNeil Spring > /dev/null; then 441*ae57a553SNeil Spring reason="no data on alternate path" 442*ae57a553SNeil Spring fi 443*ae57a553SNeil Spring 444*ae57a553SNeil Spring local rehash_after 445*ae57a553SNeil Spring rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 446*ae57a553SNeil Spring if [ "$rehash_after" -le "$rehash_before" ]; then 447*ae57a553SNeil Spring reason="${reason:+$reason; }TcpTimeoutRehash did not increment" 448*ae57a553SNeil Spring fi 449*ae57a553SNeil Spring 450*ae57a553SNeil Spring unblock_tcp "$NS1" "veth${active_idx}a" 451*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw \ 452*ae57a553SNeil Spring net.ipv4.tcp_retries1="$saved_retries1" 453*ae57a553SNeil Spring kill "$client_pid" "$server_pid" 2>/dev/null 454*ae57a553SNeil Spring wait "$client_pid" "$server_pid" 2>/dev/null 455*ae57a553SNeil Spring if [ -n "$reason" ]; then 456*ae57a553SNeil Spring echo "$reason" 457*ae57a553SNeil Spring return 1 458*ae57a553SNeil Spring fi 459*ae57a553SNeil Spring return 0 460*ae57a553SNeil Spring} 461*ae57a553SNeil Spring 462*ae57a553SNeil Springtest_ecmp_midstream_rehash() 463*ae57a553SNeil Spring{ 464*ae57a553SNeil Spring RET=0 465*ae57a553SNeil Spring local port retry_port 466*ae57a553SNeil Spring alloc_ports port 467*ae57a553SNeil Spring alloc_ports retry_port 468*ae57a553SNeil Spring 469*ae57a553SNeil Spring local fail_reason 470*ae57a553SNeil Spring if ! ecmp_midstream_rehash_attempt "$port" >/dev/null; then 471*ae57a553SNeil Spring fail_reason=$(ecmp_midstream_rehash_attempt "$retry_port") 472*ae57a553SNeil Spring check_err $? "$fail_reason" 473*ae57a553SNeil Spring fi 474*ae57a553SNeil Spring 475*ae57a553SNeil Spring log_test "Local ECMP midstream rehash: block active path" 476*ae57a553SNeil Spring} 477*ae57a553SNeil Spring 478*ae57a553SNeil Spring# Single attempt at the ACK rehash check. Returns 0 on success. 479*ae57a553SNeil Springecmp_ack_rehash_attempt() 480*ae57a553SNeil Spring{ 481*ae57a553SNeil Spring local port=$1; shift 482*ae57a553SNeil Spring local reason="" 483*ae57a553SNeil Spring 484*ae57a553SNeil Spring ip netns exec "$NS2" socat -u \ 485*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null & 486*ae57a553SNeil Spring local server_pid=$! 487*ae57a553SNeil Spring 488*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 489*ae57a553SNeil Spring 490*ae57a553SNeil Spring local base_tx0 base_tx1 491*ae57a553SNeil Spring base_tx0=$(link_tx_packets_get "$NS2" veth0b) 492*ae57a553SNeil Spring base_tx1=$(link_tx_packets_get "$NS2" veth1b) 493*ae57a553SNeil Spring 494*ae57a553SNeil Spring # Continuous data source from NS1 to NS2. Cap the send buffer 495*ae57a553SNeil Spring # so in-flight data stays below the receiver's advertised window. 496*ae57a553SNeil Spring # Without this, the sender can exhaust the receiver's window and 497*ae57a553SNeil Spring # enter persist mode (zero-window probing) instead of RTO when 498*ae57a553SNeil Spring # ACKs are blocked, and persist probes do not trigger flowlabel 499*ae57a553SNeil Spring # rehash. 500*ae57a553SNeil Spring ip netns exec "$NS1" timeout 120 socat -u \ 501*ae57a553SNeil Spring OPEN:/dev/zero \ 502*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],sndbuf=16384" \ 503*ae57a553SNeil Spring &>/dev/null & 504*ae57a553SNeil Spring local client_pid=$! 505*ae57a553SNeil Spring 506*ae57a553SNeil Spring # Wait for enough server TX (ACKs) to identify the active return path. 507*ae57a553SNeil Spring if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 508*ae57a553SNeil Spring ">= $((base_tx0 + base_tx1 + 10))" \ 509*ae57a553SNeil Spring link_tx_packets_total "$NS2" veth0b veth1b > /dev/null; then 510*ae57a553SNeil Spring kill "$client_pid" "$server_pid" 2>/dev/null 511*ae57a553SNeil Spring wait "$client_pid" "$server_pid" 2>/dev/null 512*ae57a553SNeil Spring echo "no server TX activity" 513*ae57a553SNeil Spring return 1 514*ae57a553SNeil Spring fi 515*ae57a553SNeil Spring 516*ae57a553SNeil Spring local cur_tx0 cur_tx1 active_dev inactive_dev 517*ae57a553SNeil Spring cur_tx0=$(link_tx_packets_get "$NS2" veth0b) 518*ae57a553SNeil Spring cur_tx1=$(link_tx_packets_get "$NS2" veth1b) 519*ae57a553SNeil Spring if [ $((cur_tx0 - base_tx0)) -ge $((cur_tx1 - base_tx1)) ]; then 520*ae57a553SNeil Spring active_dev=veth0b; inactive_dev=veth1b 521*ae57a553SNeil Spring else 522*ae57a553SNeil Spring active_dev=veth1b; inactive_dev=veth0b 523*ae57a553SNeil Spring fi 524*ae57a553SNeil Spring 525*ae57a553SNeil Spring local rehash_before 526*ae57a553SNeil Spring rehash_before=$(get_netstat_counter "$NS2" TcpDuplicateDataRehash) 527*ae57a553SNeil Spring 528*ae57a553SNeil Spring # Block the inactive return path first (no effect on current 529*ae57a553SNeil Spring # ACK flow), then block the active path. This avoids counting 530*ae57a553SNeil Spring # normal ACK drops as rehash evidence. 531*ae57a553SNeil Spring block_tcp "$NS2" "$inactive_dev" 532*ae57a553SNeil Spring local inactive_before 533*ae57a553SNeil Spring inactive_before=$(tc_filter_pkt_count "$NS2" "$inactive_dev") 534*ae57a553SNeil Spring block_tcp "$NS2" "$active_dev" 535*ae57a553SNeil Spring 536*ae57a553SNeil Spring # NS1 will RTO (no ACKs), retransmit with new flowlabel. 537*ae57a553SNeil Spring # NS2 detects the flowlabel change via tcp_rcv_spurious_retrans(), 538*ae57a553SNeil Spring # rehashes, and NS2's ACKs try the previously inactive return 539*ae57a553SNeil Spring # path. One successful rehash is sufficient. 540*ae57a553SNeil Spring if ! slowwait 60 until_counter_is \ 541*ae57a553SNeil Spring ">= $((${inactive_before:-0} + 1))" \ 542*ae57a553SNeil Spring tc_filter_pkt_count "$NS2" "$inactive_dev" > /dev/null; then 543*ae57a553SNeil Spring reason="no ACKs on alternate return path after blocking" 544*ae57a553SNeil Spring fi 545*ae57a553SNeil Spring 546*ae57a553SNeil Spring local rehash_after 547*ae57a553SNeil Spring rehash_after=$(get_netstat_counter "$NS2" TcpDuplicateDataRehash) 548*ae57a553SNeil Spring if [ "$rehash_after" -le "$rehash_before" ]; then 549*ae57a553SNeil Spring reason="${reason:+$reason; }TcpDuplicateDataRehash did not increment" 550*ae57a553SNeil Spring fi 551*ae57a553SNeil Spring 552*ae57a553SNeil Spring unblock_tcp "$NS2" "$active_dev" 553*ae57a553SNeil Spring unblock_tcp "$NS2" "$inactive_dev" 554*ae57a553SNeil Spring kill "$client_pid" "$server_pid" 2>/dev/null 555*ae57a553SNeil Spring wait "$client_pid" "$server_pid" 2>/dev/null 556*ae57a553SNeil Spring if [ -n "$reason" ]; then 557*ae57a553SNeil Spring echo "$reason" 558*ae57a553SNeil Spring return 1 559*ae57a553SNeil Spring fi 560*ae57a553SNeil Spring return 0 561*ae57a553SNeil Spring} 562*ae57a553SNeil Spring 563*ae57a553SNeil Spring# Block the receiver's (NS2) ACK return paths while data flows from 564*ae57a553SNeil Spring# NS1 to NS2. The sender (NS1) times out and retransmits with a new 565*ae57a553SNeil Spring# flowlabel; the receiver detects the changed flowlabel via 566*ae57a553SNeil Spring# tcp_rcv_spurious_retrans() and rehashes its own txhash so that its 567*ae57a553SNeil Spring# ACKs try a different ECMP return path. 568*ae57a553SNeil Spring# 569*ae57a553SNeil Spring# With 2-way ECMP each rehash may pick the same path, so a single 570*ae57a553SNeil Spring# attempt can occasionally fail. Retry once for robustness. 571*ae57a553SNeil Springtest_ecmp_midstream_ack_rehash() 572*ae57a553SNeil Spring{ 573*ae57a553SNeil Spring RET=0 574*ae57a553SNeil Spring local port retry_port 575*ae57a553SNeil Spring alloc_ports port 576*ae57a553SNeil Spring alloc_ports retry_port 577*ae57a553SNeil Spring 578*ae57a553SNeil Spring local fail_reason 579*ae57a553SNeil Spring if ! ecmp_ack_rehash_attempt "$port" >/dev/null; then 580*ae57a553SNeil Spring fail_reason=$(ecmp_ack_rehash_attempt "$retry_port") 581*ae57a553SNeil Spring check_err $? "$fail_reason" 582*ae57a553SNeil Spring fi 583*ae57a553SNeil Spring 584*ae57a553SNeil Spring log_test "Local ECMP midstream ACK rehash: blocked return path" 585*ae57a553SNeil Spring} 586*ae57a553SNeil Spring 587*ae57a553SNeil Spring# Establish a DCTCP data transfer with PLB enabled, then ECN-mark both 588*ae57a553SNeil Spring# paths. Sustained CE marking triggers PLB to call sk_rethink_txhash() 589*ae57a553SNeil Spring# + __sk_dst_reset(), bouncing the connection between ECMP paths. 590*ae57a553SNeil Spring# Verify data appears on both paths and that TCPPLBRehash incremented. 591*ae57a553SNeil Springtest_ecmp_plb_rehash() 592*ae57a553SNeil Spring{ 593*ae57a553SNeil Spring RET=0 594*ae57a553SNeil Spring local port 595*ae57a553SNeil Spring alloc_ports port 596*ae57a553SNeil Spring 597*ae57a553SNeil Spring # PLB needs DCTCP, a restricted congestion control. Adding it to 598*ae57a553SNeil Spring # the host-global tcp_allowed_congestion_control would relax the 599*ae57a553SNeil Spring # restricted-CC policy for the whole host (there is no per-netns 600*ae57a553SNeil Spring # allowed set). Instead pin dctcp on the test routes with 601*ae57a553SNeil Spring # "congctl": the route's RTAX_CC_ALGO is honoured on both the 602*ae57a553SNeil Spring # connect and accept paths without the restricted-CC check, and a 603*ae57a553SNeil Spring # dctcp route also carries DST_FEATURE_ECN_CA so the server 604*ae57a553SNeil Spring # negotiates ECN -- all confined to the test namespaces. 605*ae57a553SNeil Spring local available 606*ae57a553SNeil Spring available=$(ip netns exec "$NS1" sysctl -n \ 607*ae57a553SNeil Spring net.ipv4.tcp_available_congestion_control) 608*ae57a553SNeil Spring if ! echo "$available" | grep -qw dctcp; then 609*ae57a553SNeil Spring log_test_skip "Local ECMP PLB rehash: DCTCP not available" 610*ae57a553SNeil Spring return "$ksft_skip" 611*ae57a553SNeil Spring fi 612*ae57a553SNeil Spring install_ecmp_routes change dctcp 613*ae57a553SNeil Spring defer install_ecmp_routes change 614*ae57a553SNeil Spring 615*ae57a553SNeil Spring # Save NS1 sysctls before modifying them. 616*ae57a553SNeil Spring local saved_ecn1 saved_plb_enabled saved_plb_rounds 617*ae57a553SNeil Spring local saved_plb_thresh saved_plb_suspend 618*ae57a553SNeil Spring saved_ecn1=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_ecn) 619*ae57a553SNeil Spring saved_plb_enabled=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_enabled) 620*ae57a553SNeil Spring saved_plb_rounds=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_rehash_rounds) 621*ae57a553SNeil Spring saved_plb_thresh=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_cong_thresh) 622*ae57a553SNeil Spring saved_plb_suspend=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_suspend_rto_sec) 623*ae57a553SNeil Spring 624*ae57a553SNeil Spring # Enable ECN and PLB on the sender; dctcp comes from the route. 625*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_ecn=1 626*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_enabled=1 627*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_rehash_rounds=3 628*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_cong_thresh=1 629*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_suspend_rto_sec=0 630*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_ecn="$saved_ecn1" 631*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_enabled="$saved_plb_enabled" 632*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_rehash_rounds="$saved_plb_rounds" 633*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_cong_thresh="$saved_plb_thresh" 634*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_suspend_rto_sec="$saved_plb_suspend" 635*ae57a553SNeil Spring 636*ae57a553SNeil Spring ip netns exec "$NS2" socat -u \ 637*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null & 638*ae57a553SNeil Spring defer kill_process $! 639*ae57a553SNeil Spring 640*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 641*ae57a553SNeil Spring 642*ae57a553SNeil Spring local base_tx0 base_tx1 643*ae57a553SNeil Spring base_tx0=$(link_tx_packets_get "$NS1" veth0a) 644*ae57a553SNeil Spring base_tx1=$(link_tx_packets_get "$NS1" veth1a) 645*ae57a553SNeil Spring 646*ae57a553SNeil Spring ip netns exec "$NS1" timeout 90 socat -u \ 647*ae57a553SNeil Spring OPEN:/dev/zero \ 648*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" &>/dev/null & 649*ae57a553SNeil Spring local client_pid=$! 650*ae57a553SNeil Spring defer kill_process "$client_pid" 651*ae57a553SNeil Spring 652*ae57a553SNeil Spring # Wait for data to start flowing before applying ECN marking. 653*ae57a553SNeil Spring busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 654*ae57a553SNeil Spring ">= $((base_tx0 + base_tx1 + 10))" \ 655*ae57a553SNeil Spring link_tx_packets_total "$NS1" > /dev/null 656*ae57a553SNeil Spring check_err $? "no TX activity detected" 657*ae57a553SNeil Spring if [ "$RET" -ne 0 ]; then 658*ae57a553SNeil Spring log_test "Local ECMP PLB rehash: ECN-marked path" 659*ae57a553SNeil Spring return 660*ae57a553SNeil Spring fi 661*ae57a553SNeil Spring 662*ae57a553SNeil Spring # Snapshot TX counters and rehash stats before ECN marking. 663*ae57a553SNeil Spring local pre_ecn_tx0 pre_ecn_tx1 664*ae57a553SNeil Spring pre_ecn_tx0=$(link_tx_packets_get "$NS1" veth0a) 665*ae57a553SNeil Spring pre_ecn_tx1=$(link_tx_packets_get "$NS1" veth1a) 666*ae57a553SNeil Spring 667*ae57a553SNeil Spring local plb_before rto_before 668*ae57a553SNeil Spring plb_before=$(get_netstat_counter "$NS1" TCPPLBRehash) 669*ae57a553SNeil Spring rto_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 670*ae57a553SNeil Spring 671*ae57a553SNeil Spring # CE-mark all data on both paths. PLB detects sustained 672*ae57a553SNeil Spring # congestion and rehashes, bouncing traffic between paths. 673*ae57a553SNeil Spring mark_ecn "$NS1" veth0a 674*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth0a # removes the marking rule 675*ae57a553SNeil Spring mark_ecn "$NS1" veth1a 676*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth1a # removes the marking rule 677*ae57a553SNeil Spring 678*ae57a553SNeil Spring # Wait for meaningful data on both paths, proving PLB rehashed 679*ae57a553SNeil Spring # the connection and traffic actually moved. Require at least 680*ae57a553SNeil Spring # 100 packets beyond the baseline to rule out stray control 681*ae57a553SNeil Spring # packets (ND, etc.) satisfying the check. 682*ae57a553SNeil Spring slowwait 60 dev_tx_packets_above \ 683*ae57a553SNeil Spring "$NS1" veth0a "$((pre_ecn_tx0 + 100))" > /dev/null 684*ae57a553SNeil Spring check_err $? "no data on veth0a after ECN marking" 685*ae57a553SNeil Spring 686*ae57a553SNeil Spring slowwait 60 dev_tx_packets_above \ 687*ae57a553SNeil Spring "$NS1" veth1a "$((pre_ecn_tx1 + 100))" > /dev/null 688*ae57a553SNeil Spring check_err $? "no data on veth1a after ECN marking" 689*ae57a553SNeil Spring 690*ae57a553SNeil Spring local plb_after rto_after 691*ae57a553SNeil Spring plb_after=$(get_netstat_counter "$NS1" TCPPLBRehash) 692*ae57a553SNeil Spring rto_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 693*ae57a553SNeil Spring if [ "$plb_after" -le "$plb_before" ]; then 694*ae57a553SNeil Spring check_err 1 "TCPPLBRehash counter did not increment" 695*ae57a553SNeil Spring fi 696*ae57a553SNeil Spring if [ "$rto_after" -gt "$rto_before" ]; then 697*ae57a553SNeil Spring check_err 1 "TcpTimeoutRehash incremented; rehash was RTO-driven, not PLB" 698*ae57a553SNeil Spring fi 699*ae57a553SNeil Spring 700*ae57a553SNeil Spring log_test "Local ECMP PLB rehash: ECN-marked path" 701*ae57a553SNeil Spring} 702*ae57a553SNeil Spring 703*ae57a553SNeil Spring# Verify that hash policy 1 (L3+L4 symmetric) preserves the ECMP path 704*ae57a553SNeil Spring# across rehash. Policy 1 computes a deterministic hash from the 705*ae57a553SNeil Spring# 5-tuple, so mp_hash stays 0 and rt6_multipath_hash() always selects 706*ae57a553SNeil Spring# the same path regardless of txhash changes. 707*ae57a553SNeil Springtest_ecmp_hash_policy1_no_rehash() 708*ae57a553SNeil Spring{ 709*ae57a553SNeil Spring RET=0 710*ae57a553SNeil Spring local port 711*ae57a553SNeil Spring alloc_ports port 712*ae57a553SNeil Spring 713*ae57a553SNeil Spring local saved_policy 714*ae57a553SNeil Spring saved_policy=$(ip netns exec "$NS1" sysctl -n \ 715*ae57a553SNeil Spring net.ipv6.fib_multipath_hash_policy) 716*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv6.fib_multipath_hash_policy=1 717*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw \ 718*ae57a553SNeil Spring net.ipv6.fib_multipath_hash_policy="$saved_policy" 719*ae57a553SNeil Spring 720*ae57a553SNeil Spring block_tcp "$NS1" veth0a 721*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth0a 722*ae57a553SNeil Spring block_tcp "$NS1" veth1a 723*ae57a553SNeil Spring defer unblock_tcp "$NS1" veth1a 724*ae57a553SNeil Spring 725*ae57a553SNeil Spring ip netns exec "$NS2" socat \ 726*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \ 727*ae57a553SNeil Spring EXEC:"echo POLICY1_OK" & 728*ae57a553SNeil Spring defer kill_process $! 729*ae57a553SNeil Spring 730*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 731*ae57a553SNeil Spring 732*ae57a553SNeil Spring local rehash_before 733*ae57a553SNeil Spring rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 734*ae57a553SNeil Spring 735*ae57a553SNeil Spring ip netns exec "$NS1" timeout 10 socat -u \ 736*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=8" \ 737*ae57a553SNeil Spring STDOUT >/dev/null 2>&1 & 738*ae57a553SNeil Spring local client_pid=$! 739*ae57a553SNeil Spring defer kill_process "$client_pid" 740*ae57a553SNeil Spring 741*ae57a553SNeil Spring # With policy 1, the deterministic 5-tuple hash always selects 742*ae57a553SNeil Spring # the same path. Wait for multiple SYN retransmits (proving 743*ae57a553SNeil Spring # rehash was attempted), then verify all SYNs landed on the 744*ae57a553SNeil Spring # same interface. 745*ae57a553SNeil Spring local rehash_after 746*ae57a553SNeil Spring slowwait 8 until_counter_is ">= $((rehash_before + 3))" \ 747*ae57a553SNeil Spring get_netstat_counter "$NS1" TcpTimeoutRehash > /dev/null 748*ae57a553SNeil Spring rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash) 749*ae57a553SNeil Spring if [ "$rehash_after" -le "$rehash_before" ]; then 750*ae57a553SNeil Spring check_err 1 "TcpTimeoutRehash counter did not increment" 751*ae57a553SNeil Spring fi 752*ae57a553SNeil Spring 753*ae57a553SNeil Spring local c0 c1 754*ae57a553SNeil Spring c0=$(tc_filter_pkt_count "$NS1" veth0a) 755*ae57a553SNeil Spring c1=$(tc_filter_pkt_count "$NS1" veth1a) 756*ae57a553SNeil Spring if [ "${c0:-0}" -ge 1 ] && [ "${c1:-0}" -ge 1 ]; then 757*ae57a553SNeil Spring check_err 1 "SYNs appeared on both paths despite policy 1" 758*ae57a553SNeil Spring fi 759*ae57a553SNeil Spring if [ "${c0:-0}" -eq 0 ] && [ "${c1:-0}" -eq 0 ]; then 760*ae57a553SNeil Spring check_err 1 "no SYNs observed on either path" 761*ae57a553SNeil Spring fi 762*ae57a553SNeil Spring 763*ae57a553SNeil Spring log_test "Local ECMP policy 1: no path change on rehash" 764*ae57a553SNeil Spring} 765*ae57a553SNeil Spring 766*ae57a553SNeil Spring# Verify that mp_hash does not leak into the on-wire flowlabel. 767*ae57a553SNeil Spring# With auto_flowlabels=0, the wire flowlabel must be 0. Install tc 768*ae57a553SNeil Spring# filters that pass TCP with flowlabel=0 but drop TCP with nonzero 769*ae57a553SNeil Spring# flowlabel, then establish a connection and transfer data. If 770*ae57a553SNeil Spring# mp_hash leaked into fl6->flowlabel, the SYN or data packets would 771*ae57a553SNeil Spring# be dropped and the connection would fail. 772*ae57a553SNeil Springtest_ecmp_no_flowlabel_leak() 773*ae57a553SNeil Spring{ 774*ae57a553SNeil Spring RET=0 775*ae57a553SNeil Spring local port 776*ae57a553SNeil Spring alloc_ports port 777*ae57a553SNeil Spring 778*ae57a553SNeil Spring local saved_afl 779*ae57a553SNeil Spring saved_afl=$(ip netns exec "$NS1" sysctl -n \ 780*ae57a553SNeil Spring net.ipv6.auto_flowlabels) 781*ae57a553SNeil Spring ip netns exec "$NS1" sysctl -qw net.ipv6.auto_flowlabels=0 782*ae57a553SNeil Spring defer ip netns exec "$NS1" sysctl -qw \ 783*ae57a553SNeil Spring net.ipv6.auto_flowlabels="$saved_afl" 784*ae57a553SNeil Spring 785*ae57a553SNeil Spring # On both egress interfaces: pass TCP with flowlabel=0 (prio 1), 786*ae57a553SNeil Spring # drop any remaining TCP (nonzero flowlabel, prio 2). ICMPv6 787*ae57a553SNeil Spring # matches neither filter and passes through normally. 788*ae57a553SNeil Spring local dev 789*ae57a553SNeil Spring for dev in veth0a veth1a; do 790*ae57a553SNeil Spring ip netns exec "$NS1" tc qdisc add dev "$dev" \ 791*ae57a553SNeil Spring root handle 1: prio 792*ae57a553SNeil Spring ip netns exec "$NS1" tc filter add dev "$dev" parent 1: \ 793*ae57a553SNeil Spring protocol ipv6 prio 1 u32 \ 794*ae57a553SNeil Spring match u32 0x00000000 0x000FFFFF at 0 \ 795*ae57a553SNeil Spring match u8 0x06 0xff at 6 \ 796*ae57a553SNeil Spring action ok 797*ae57a553SNeil Spring ip netns exec "$NS1" tc filter add dev "$dev" parent 1: \ 798*ae57a553SNeil Spring protocol ipv6 prio 2 u32 \ 799*ae57a553SNeil Spring match u8 0x06 0xff at 6 \ 800*ae57a553SNeil Spring action drop 801*ae57a553SNeil Spring defer unblock_tcp "$NS1" "$dev" 802*ae57a553SNeil Spring done 803*ae57a553SNeil Spring 804*ae57a553SNeil Spring ip netns exec "$NS2" socat \ 805*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \ 806*ae57a553SNeil Spring EXEC:"echo FLOWLABEL_OK" & 807*ae57a553SNeil Spring defer kill_process $! 808*ae57a553SNeil Spring 809*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 810*ae57a553SNeil Spring 811*ae57a553SNeil Spring local tmpfile 812*ae57a553SNeil Spring tmpfile=$(mktemp) 813*ae57a553SNeil Spring defer rm -f "$tmpfile" 814*ae57a553SNeil Spring 815*ae57a553SNeil Spring ip netns exec "$NS1" socat -u \ 816*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=10" \ 817*ae57a553SNeil Spring STDOUT >"$tmpfile" 2>&1 818*ae57a553SNeil Spring 819*ae57a553SNeil Spring local result 820*ae57a553SNeil Spring result=$(cat "$tmpfile" 2>/dev/null) 821*ae57a553SNeil Spring if [[ "$result" != *"FLOWLABEL_OK"* ]]; then 822*ae57a553SNeil Spring check_err 1 "connection failed: mp_hash may have leaked into wire flowlabel" 823*ae57a553SNeil Spring fi 824*ae57a553SNeil Spring 825*ae57a553SNeil Spring log_test "No flowlabel leak with auto_flowlabels=0" 826*ae57a553SNeil Spring} 827*ae57a553SNeil Spring 828*ae57a553SNeil Spring# Helper: stream data, invalidate the cached dst by adding and 829*ae57a553SNeil Spring# removing a dummy route (bumps fib6_node sernum), then check that 830*ae57a553SNeil Spring# traffic stays on the same ECMP path. Used by both the normal 831*ae57a553SNeil Spring# tcp_v6_connect and syncookie variants. 832*ae57a553SNeil Springecmp_dst_rebuild_check() 833*ae57a553SNeil Spring{ 834*ae57a553SNeil Spring local ns_client=$1; shift 835*ae57a553SNeil Spring local port=$1; shift 836*ae57a553SNeil Spring local rc=0 837*ae57a553SNeil Spring 838*ae57a553SNeil Spring # Suppress __dst_negative_advice() during the test so that a 839*ae57a553SNeil Spring # real TCP timeout cannot trigger an additional dst 840*ae57a553SNeil Spring # invalidation via a different code path. 841*ae57a553SNeil Spring local saved_retries1 842*ae57a553SNeil Spring saved_retries1=$(ip netns exec "$ns_client" sysctl -n \ 843*ae57a553SNeil Spring net.ipv4.tcp_retries1) 844*ae57a553SNeil Spring ip netns exec "$ns_client" sysctl -qw net.ipv4.tcp_retries1=255 845*ae57a553SNeil Spring 846*ae57a553SNeil Spring local base0 base1 847*ae57a553SNeil Spring base0=$(link_tx_packets_get "$ns_client" veth0a) 848*ae57a553SNeil Spring base1=$(link_tx_packets_get "$ns_client" veth1a) 849*ae57a553SNeil Spring 850*ae57a553SNeil Spring ip netns exec "$ns_client" timeout 15 socat -u \ 851*ae57a553SNeil Spring OPEN:/dev/zero \ 852*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" \ 853*ae57a553SNeil Spring &>/dev/null & 854*ae57a553SNeil Spring local client_pid=$! 855*ae57a553SNeil Spring 856*ae57a553SNeil Spring # Wait for enough packets to identify the active path. 857*ae57a553SNeil Spring # Return 2 for setup failure (distinct from 1 = path changed). 858*ae57a553SNeil Spring if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 859*ae57a553SNeil Spring ">= $((base0 + base1 + 50))" \ 860*ae57a553SNeil Spring link_tx_packets_total "$ns_client" > /dev/null; then 861*ae57a553SNeil Spring ip netns exec "$ns_client" sysctl -qw \ 862*ae57a553SNeil Spring net.ipv4.tcp_retries1="$saved_retries1" 863*ae57a553SNeil Spring kill "$client_pid" 2>/dev/null 864*ae57a553SNeil Spring wait "$client_pid" 2>/dev/null 865*ae57a553SNeil Spring return 2 866*ae57a553SNeil Spring fi 867*ae57a553SNeil Spring 868*ae57a553SNeil Spring local mid0 mid1 active_dev inactive_dev 869*ae57a553SNeil Spring mid0=$(link_tx_packets_get "$ns_client" veth0a) 870*ae57a553SNeil Spring mid1=$(link_tx_packets_get "$ns_client" veth1a) 871*ae57a553SNeil Spring if [ $((mid0 - base0)) -ge $((mid1 - base1)) ]; then 872*ae57a553SNeil Spring active_dev=veth0a; inactive_dev=veth1a 873*ae57a553SNeil Spring else 874*ae57a553SNeil Spring active_dev=veth1a; inactive_dev=veth0a 875*ae57a553SNeil Spring fi 876*ae57a553SNeil Spring 877*ae57a553SNeil Spring local active_before inactive_before 878*ae57a553SNeil Spring active_before=$(link_tx_packets_get "$ns_client" "$active_dev") 879*ae57a553SNeil Spring inactive_before=$(link_tx_packets_get "$ns_client" "$inactive_dev") 880*ae57a553SNeil Spring 881*ae57a553SNeil Spring # Invalidate the cached dst by bumping the fib6_node sernum. 882*ae57a553SNeil Spring # Adding and removing a high-metric dummy route achieves this 883*ae57a553SNeil Spring # without touching the ECMP nexthops, avoiding a transient 884*ae57a553SNeil Spring # single-nexthop state during multipath route replace. 885*ae57a553SNeil Spring ip -n "$ns_client" -6 route add fd00:ff::2/128 dev lo metric 9999 886*ae57a553SNeil Spring ip -n "$ns_client" -6 route del fd00:ff::2/128 dev lo metric 9999 887*ae57a553SNeil Spring 888*ae57a553SNeil Spring # Wait for enough post-rebuild traffic to detect a path change. 889*ae57a553SNeil Spring if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 890*ae57a553SNeil Spring ">= $((active_before + inactive_before + 50))" \ 891*ae57a553SNeil Spring link_tx_packets_total "$ns_client" > /dev/null; then 892*ae57a553SNeil Spring ip netns exec "$ns_client" sysctl -qw \ 893*ae57a553SNeil Spring net.ipv4.tcp_retries1="$saved_retries1" 894*ae57a553SNeil Spring kill "$client_pid" 2>/dev/null 895*ae57a553SNeil Spring wait "$client_pid" 2>/dev/null 896*ae57a553SNeil Spring return 2 897*ae57a553SNeil Spring fi 898*ae57a553SNeil Spring 899*ae57a553SNeil Spring local active_after inactive_after 900*ae57a553SNeil Spring active_after=$(link_tx_packets_get "$ns_client" "$active_dev") 901*ae57a553SNeil Spring inactive_after=$(link_tx_packets_get "$ns_client" "$inactive_dev") 902*ae57a553SNeil Spring 903*ae57a553SNeil Spring local active_delta=$((active_after - active_before)) 904*ae57a553SNeil Spring local inactive_delta=$((inactive_after - inactive_before)) 905*ae57a553SNeil Spring 906*ae57a553SNeil Spring if [ "$inactive_delta" -gt "$active_delta" ]; then 907*ae57a553SNeil Spring rc=1 908*ae57a553SNeil Spring fi 909*ae57a553SNeil Spring 910*ae57a553SNeil Spring ip netns exec "$ns_client" sysctl -qw \ 911*ae57a553SNeil Spring net.ipv4.tcp_retries1="$saved_retries1" 912*ae57a553SNeil Spring kill "$client_pid" 2>/dev/null 913*ae57a553SNeil Spring wait "$client_pid" 2>/dev/null 914*ae57a553SNeil Spring return "$rc" 915*ae57a553SNeil Spring} 916*ae57a553SNeil Spring 917*ae57a553SNeil Spring# Run ecmp_dst_rebuild_check for ECMP_REBUILD_ROUNDS rounds, each with 918*ae57a553SNeil Spring# a fresh server and connection. With a correct kernel the path is 919*ae57a553SNeil Spring# deterministic (same txhash always selects the same ECMP nexthop), 920*ae57a553SNeil Spring# so any path change is a bug. Multiple rounds catch a buggy kernel 921*ae57a553SNeil Spring# that picks a random path: each round has 50% chance of accidentally 922*ae57a553SNeil Spring# matching, so 10 rounds gives < 0.1% false-pass probability. 923*ae57a553SNeil Springecmp_dst_rebuild_loop() 924*ae57a553SNeil Spring{ 925*ae57a553SNeil Spring local base_port=$1; shift 926*ae57a553SNeil Spring local label=$1; shift 927*ae57a553SNeil Spring local path_changes=0 928*ae57a553SNeil Spring local r 929*ae57a553SNeil Spring 930*ae57a553SNeil Spring for r in $(seq 1 "$ECMP_REBUILD_ROUNDS"); do 931*ae57a553SNeil Spring local port=$((base_port + r - 1)) 932*ae57a553SNeil Spring 933*ae57a553SNeil Spring ip netns exec "$NS2" socat -u \ 934*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \ 935*ae57a553SNeil Spring - >/dev/null & 936*ae57a553SNeil Spring local server_pid=$! 937*ae57a553SNeil Spring 938*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 939*ae57a553SNeil Spring 940*ae57a553SNeil Spring local check_rc=0 941*ae57a553SNeil Spring ecmp_dst_rebuild_check "$NS1" "$port" || check_rc=$? 942*ae57a553SNeil Spring 943*ae57a553SNeil Spring kill "$server_pid" 2>/dev/null 944*ae57a553SNeil Spring wait "$server_pid" 2>/dev/null 945*ae57a553SNeil Spring 946*ae57a553SNeil Spring busywait "$BUSYWAIT_TIMEOUT" \ 947*ae57a553SNeil Spring port_has_no_active_tcp "$NS1" "$port" > /dev/null 948*ae57a553SNeil Spring busywait "$BUSYWAIT_TIMEOUT" \ 949*ae57a553SNeil Spring port_has_no_active_tcp "$NS2" "$port" > /dev/null 950*ae57a553SNeil Spring 951*ae57a553SNeil Spring if [ "$check_rc" -eq 2 ]; then 952*ae57a553SNeil Spring check_err 1 "no TX activity in round $r" 953*ae57a553SNeil Spring break 954*ae57a553SNeil Spring elif [ "$check_rc" -eq 1 ]; then 955*ae57a553SNeil Spring path_changes=$((path_changes + 1)) 956*ae57a553SNeil Spring fi 957*ae57a553SNeil Spring done 958*ae57a553SNeil Spring 959*ae57a553SNeil Spring if [ "$path_changes" -gt 0 ]; then 960*ae57a553SNeil Spring check_err 1 "$path_changes/$ECMP_REBUILD_ROUNDS changed path" 961*ae57a553SNeil Spring fi 962*ae57a553SNeil Spring 963*ae57a553SNeil Spring log_test "$label" 964*ae57a553SNeil Spring} 965*ae57a553SNeil Spring 966*ae57a553SNeil Spring# Verify that a dst invalidation does not cause the connection to 967*ae57a553SNeil Spring# switch ECMP paths. With the fix, both the initial route lookup 968*ae57a553SNeil Spring# (tcp_v6_connect) and subsequent rebuilds (inet6_csk_route_socket) 969*ae57a553SNeil Spring# use sk_txhash >> 1, so the path is stable. 970*ae57a553SNeil Springtest_ecmp_dst_rebuild_consistency() 971*ae57a553SNeil Spring{ 972*ae57a553SNeil Spring RET=0 973*ae57a553SNeil Spring local base_port 974*ae57a553SNeil Spring alloc_ports base_port "$ECMP_REBUILD_ROUNDS" 975*ae57a553SNeil Spring 976*ae57a553SNeil Spring ecmp_dst_rebuild_loop "$base_port" \ 977*ae57a553SNeil Spring "ECMP path stable after dst invalidation" 978*ae57a553SNeil Spring} 979*ae57a553SNeil Spring 980*ae57a553SNeil Spring# Return 0 (true) when no active TCP sockets remain on a port. 981*ae57a553SNeil Spring# TIME_WAIT is excluded because it does not generate outgoing traffic. 982*ae57a553SNeil Springport_has_no_active_tcp() 983*ae57a553SNeil Spring{ 984*ae57a553SNeil Spring local ns=$1; shift 985*ae57a553SNeil Spring local port=$1; shift 986*ae57a553SNeil Spring 987*ae57a553SNeil Spring ! ip netns exec "$ns" ss -tnH \ 988*ae57a553SNeil Spring state established \ 989*ae57a553SNeil Spring state fin-wait-1 \ 990*ae57a553SNeil Spring state fin-wait-2 \ 991*ae57a553SNeil Spring state close-wait \ 992*ae57a553SNeil Spring state last-ack \ 993*ae57a553SNeil Spring state closing \ 994*ae57a553SNeil Spring state syn-sent \ 995*ae57a553SNeil Spring state syn-recv \ 996*ae57a553SNeil Spring "sport = :$port or dport = :$port" | grep -q . 997*ae57a553SNeil Spring} 998*ae57a553SNeil Spring 999*ae57a553SNeil Spring# Count TCP packets on server egress without blocking them. 1000*ae57a553SNeil Spring# Uses tc filters with "action ok" so packets are counted and passed. 1001*ae57a553SNeil Springcount_tcp() 1002*ae57a553SNeil Spring{ 1003*ae57a553SNeil Spring local ns=$1; shift 1004*ae57a553SNeil Spring local dev=$1; shift 1005*ae57a553SNeil Spring 1006*ae57a553SNeil Spring ip netns exec "$ns" tc qdisc add dev "$dev" root handle 1: prio 1007*ae57a553SNeil Spring ip netns exec "$ns" tc filter add dev "$dev" parent 1: \ 1008*ae57a553SNeil Spring protocol ipv6 prio 1 u32 match u8 0x06 0xff at 6 action ok 1009*ae57a553SNeil Spring} 1010*ae57a553SNeil Spring 1011*ae57a553SNeil Spring# Verify that the server's SYN-ACK (sent from the request socket) and 1012*ae57a553SNeil Spring# subsequent ACKs (sent from the full socket created in cookie_v6_check) 1013*ae57a553SNeil Spring# use the same ECMP path. With syncookies the request socket is freed 1014*ae57a553SNeil Spring# after the SYN-ACK and a new one is created during cookie validation; 1015*ae57a553SNeil Spring# this test catches the case where the two request sockets pick 1016*ae57a553SNeil Spring# different ECMP paths due to independent txhash values. 1017*ae57a553SNeil Springtest_ecmp_syncookie_path_consistency() 1018*ae57a553SNeil Spring{ 1019*ae57a553SNeil Spring RET=0 1020*ae57a553SNeil Spring 1021*ae57a553SNeil Spring local saved_syncookies 1022*ae57a553SNeil Spring saved_syncookies=$(ip netns exec "$NS2" sysctl -n \ 1023*ae57a553SNeil Spring net.ipv4.tcp_syncookies 2>/dev/null) 1024*ae57a553SNeil Spring if [ -z "$saved_syncookies" ]; then 1025*ae57a553SNeil Spring log_test_skip "Syncookie server ECMP path consistent" 1026*ae57a553SNeil Spring return "$ksft_skip" 1027*ae57a553SNeil Spring fi 1028*ae57a553SNeil Spring ip netns exec "$NS2" sysctl -qw net.ipv4.tcp_syncookies=2 1029*ae57a553SNeil Spring defer ip netns exec "$NS2" sysctl -qw \ 1030*ae57a553SNeil Spring net.ipv4.tcp_syncookies="$saved_syncookies" 1031*ae57a553SNeil Spring 1032*ae57a553SNeil Spring count_tcp "$NS2" veth0b 1033*ae57a553SNeil Spring defer unblock_tcp "$NS2" veth0b 1034*ae57a553SNeil Spring count_tcp "$NS2" veth1b 1035*ae57a553SNeil Spring defer unblock_tcp "$NS2" veth1b 1036*ae57a553SNeil Spring 1037*ae57a553SNeil Spring local path_splits=0 1038*ae57a553SNeil Spring local r base_port 1039*ae57a553SNeil Spring alloc_ports base_port "$ECMP_REBUILD_ROUNDS" 1040*ae57a553SNeil Spring 1041*ae57a553SNeil Spring for r in $(seq 1 "$ECMP_REBUILD_ROUNDS"); do 1042*ae57a553SNeil Spring local port=$((base_port + r - 1)) 1043*ae57a553SNeil Spring 1044*ae57a553SNeil Spring ip netns exec "$NS2" socat -u \ 1045*ae57a553SNeil Spring "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \ 1046*ae57a553SNeil Spring - >/dev/null & 1047*ae57a553SNeil Spring local server_pid=$! 1048*ae57a553SNeil Spring 1049*ae57a553SNeil Spring wait_local_port_listen "$NS2" "$port" tcp 1050*ae57a553SNeil Spring 1051*ae57a553SNeil Spring local srv_base0 srv_base1 1052*ae57a553SNeil Spring srv_base0=$(tc_filter_pkt_count "$NS2" veth0b) 1053*ae57a553SNeil Spring srv_base1=$(tc_filter_pkt_count "$NS2" veth1b) 1054*ae57a553SNeil Spring 1055*ae57a553SNeil Spring ip netns exec "$NS1" timeout 5 socat -u \ 1056*ae57a553SNeil Spring OPEN:/dev/zero \ 1057*ae57a553SNeil Spring "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" \ 1058*ae57a553SNeil Spring &>/dev/null & 1059*ae57a553SNeil Spring local client_pid=$! 1060*ae57a553SNeil Spring 1061*ae57a553SNeil Spring local cli_base 1062*ae57a553SNeil Spring cli_base=$(link_tx_packets_total "$NS1") 1063*ae57a553SNeil Spring if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \ 1064*ae57a553SNeil Spring ">= $((cli_base + 200))" \ 1065*ae57a553SNeil Spring link_tx_packets_total "$NS1" > /dev/null; then 1066*ae57a553SNeil Spring check_err 1 "no TX activity in round $r" 1067*ae57a553SNeil Spring kill "$client_pid" 2>/dev/null 1068*ae57a553SNeil Spring wait "$client_pid" 2>/dev/null 1069*ae57a553SNeil Spring kill "$server_pid" 2>/dev/null 1070*ae57a553SNeil Spring wait "$server_pid" 2>/dev/null 1071*ae57a553SNeil Spring break 1072*ae57a553SNeil Spring fi 1073*ae57a553SNeil Spring 1074*ae57a553SNeil Spring local srv_tcp0 srv_tcp1 1075*ae57a553SNeil Spring srv_tcp0=$(tc_filter_pkt_count "$NS2" veth0b) 1076*ae57a553SNeil Spring srv_tcp1=$(tc_filter_pkt_count "$NS2" veth1b) 1077*ae57a553SNeil Spring local srv_delta0=$(( ${srv_tcp0:-0} - ${srv_base0:-0} )) 1078*ae57a553SNeil Spring local srv_delta1=$(( ${srv_tcp1:-0} - ${srv_base1:-0} )) 1079*ae57a553SNeil Spring 1080*ae57a553SNeil Spring if [ "$srv_delta0" -gt 0 ] && [ "$srv_delta1" -gt 0 ]; then 1081*ae57a553SNeil Spring path_splits=$((path_splits + 1)) 1082*ae57a553SNeil Spring fi 1083*ae57a553SNeil Spring 1084*ae57a553SNeil Spring kill "$client_pid" 2>/dev/null 1085*ae57a553SNeil Spring wait "$client_pid" 2>/dev/null 1086*ae57a553SNeil Spring kill "$server_pid" 2>/dev/null 1087*ae57a553SNeil Spring wait "$server_pid" 2>/dev/null 1088*ae57a553SNeil Spring 1089*ae57a553SNeil Spring # Wait for TCP teardown packets (FIN/RST) to finish so 1090*ae57a553SNeil Spring # they do not pollute the next round's tc filter counters. 1091*ae57a553SNeil Spring busywait "$BUSYWAIT_TIMEOUT" \ 1092*ae57a553SNeil Spring port_has_no_active_tcp "$NS1" "$port" > /dev/null 1093*ae57a553SNeil Spring busywait "$BUSYWAIT_TIMEOUT" \ 1094*ae57a553SNeil Spring port_has_no_active_tcp "$NS2" "$port" > /dev/null 1095*ae57a553SNeil Spring done 1096*ae57a553SNeil Spring 1097*ae57a553SNeil Spring if [ "$path_splits" -gt 0 ]; then 1098*ae57a553SNeil Spring check_err 1 "$path_splits/$ECMP_REBUILD_ROUNDS had split server path" 1099*ae57a553SNeil Spring fi 1100*ae57a553SNeil Spring 1101*ae57a553SNeil Spring log_test "Syncookie server ECMP path consistent" 1102*ae57a553SNeil Spring} 1103*ae57a553SNeil Spring 1104*ae57a553SNeil Springrequire_command socat 1105*ae57a553SNeil Spring 1106*ae57a553SNeil Springtrap 'defer_scopes_cleanup; cleanup_all_ns' EXIT 1107*ae57a553SNeil Springsetup || exit $? 1108*ae57a553SNeil Springtests_run 1109*ae57a553SNeil Springexit "$EXIT_STATUS" 1110