1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3 4source lib.sh 5TESTS=" 6 extern_valid_ipv4 7 extern_valid_ipv6 8" 9VERBOSE=0 10 11################################################################################ 12# Utilities 13 14run_cmd() 15{ 16 local cmd="$1" 17 local out 18 local stderr="2>/dev/null" 19 20 if [ "$VERBOSE" = "1" ]; then 21 echo "COMMAND: $cmd" 22 stderr= 23 fi 24 25 out=$(eval "$cmd" "$stderr") 26 rc=$? 27 if [ "$VERBOSE" -eq 1 ] && [ -n "$out" ]; then 28 echo " $out" 29 fi 30 31 return $rc 32} 33 34################################################################################ 35# Setup 36 37setup() 38{ 39 set -e 40 41 setup_ns ns1 ns2 42 43 ip -n "$ns1" link add veth0 type veth peer name veth1 netns "$ns2" 44 ip -n "$ns1" link set dev veth0 up 45 ip -n "$ns2" link set dev veth1 up 46 47 ip -n "$ns1" address add 192.0.2.1/24 dev veth0 48 ip -n "$ns1" address add 2001:db8:1::1/64 dev veth0 nodad 49 ip -n "$ns2" address add 192.0.2.2/24 dev veth1 50 ip -n "$ns2" address add 2001:db8:1::2/64 dev veth1 nodad 51 52 ip netns exec "$ns1" sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1 53 ip netns exec "$ns2" sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1 54 55 sleep 5 56 57 set +e 58} 59 60exit_cleanup_all() 61{ 62 cleanup_all_ns 63 exit "${EXIT_STATUS}" 64} 65 66################################################################################ 67# Tests 68 69extern_valid_common() 70{ 71 local af_str=$1; shift 72 local ip_addr=$1; shift 73 local tbl_name=$1; shift 74 local subnet=$1; shift 75 local mac 76 77 mac=$(ip -n "$ns2" -j link show dev veth1 | jq -r '.[]["address"]') 78 79 RET=0 80 81 # Check that simple addition works. 82 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 83 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 84 check_err $? "No \"extern_valid\" flag after addition" 85 86 log_test "$af_str \"extern_valid\" flag: Add entry" 87 88 RET=0 89 90 # Check that an entry cannot be added with "extern_valid" flag and an 91 # invalid state. 92 run_cmd "ip -n $ns1 neigh flush dev veth0" 93 run_cmd "ip -n $ns1 neigh add $ip_addr nud none dev veth0 extern_valid" 94 check_fail $? "Managed to add an entry with \"extern_valid\" flag and an invalid state" 95 96 log_test "$af_str \"extern_valid\" flag: Add with an invalid state" 97 98 RET=0 99 100 # Check that entry cannot be added with both "extern_valid" flag and 101 # "use" / "managed" flag. 102 run_cmd "ip -n $ns1 neigh flush dev veth0" 103 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid use" 104 check_fail $? "Managed to add an entry with \"extern_valid\" flag and \"use\" flag" 105 106 log_test "$af_str \"extern_valid\" flag: Add with \"use\" flag" 107 108 RET=0 109 110 # Check that "extern_valid" flag can be toggled using replace. 111 run_cmd "ip -n $ns1 neigh flush dev veth0" 112 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0" 113 run_cmd "ip -n $ns1 neigh replace $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 114 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 115 check_err $? "Did not manage to set \"extern_valid\" flag with replace" 116 run_cmd "ip -n $ns1 neigh replace $ip_addr lladdr $mac nud stale dev veth0" 117 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 118 check_fail $? "Did not manage to clear \"extern_valid\" flag with replace" 119 120 log_test "$af_str \"extern_valid\" flag: Replace entry" 121 122 RET=0 123 124 # Check that an existing "extern_valid" entry can be marked as 125 # "managed". 126 run_cmd "ip -n $ns1 neigh flush dev veth0" 127 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 128 run_cmd "ip -n $ns1 neigh replace $ip_addr lladdr $mac nud stale dev veth0 extern_valid managed" 129 check_err $? "Did not manage to add \"managed\" flag to an existing \"extern_valid\" entry" 130 131 log_test "$af_str \"extern_valid\" flag: Replace entry with \"managed\" flag" 132 133 RET=0 134 135 # Check that entry cannot be replaced with "extern_valid" flag and an 136 # invalid state. 137 run_cmd "ip -n $ns1 neigh flush dev veth0" 138 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 139 run_cmd "ip -n $ns1 neigh replace $ip_addr nud none dev veth0 extern_valid" 140 check_fail $? "Managed to replace an entry with \"extern_valid\" flag and an invalid state" 141 142 log_test "$af_str \"extern_valid\" flag: Replace with an invalid state" 143 144 RET=0 145 146 # Check that an "extern_valid" entry is flushed when the interface is 147 # put administratively down. 148 run_cmd "ip -n $ns1 neigh flush dev veth0" 149 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 150 run_cmd "ip -n $ns1 link set dev veth0 down" 151 run_cmd "ip -n $ns1 link set dev veth0 up" 152 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0" 153 check_fail $? "\"extern_valid\" entry not flushed upon interface down" 154 155 log_test "$af_str \"extern_valid\" flag: Interface down" 156 157 RET=0 158 159 # Check that an "extern_valid" entry is not flushed when the interface 160 # loses its carrier. 161 run_cmd "ip -n $ns1 neigh flush dev veth0" 162 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 163 run_cmd "ip -n $ns2 link set dev veth1 down" 164 run_cmd "ip -n $ns2 link set dev veth1 up" 165 run_cmd "sleep 2" 166 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0" 167 check_err $? "\"extern_valid\" entry flushed upon carrier down" 168 169 log_test "$af_str \"extern_valid\" flag: Carrier down" 170 171 RET=0 172 173 # Check that when entry transitions to "reachable" state it maintains 174 # the "extern_valid" flag. Wait "delay_probe" seconds for ARP request / 175 # NS to be sent. 176 local delay_probe 177 178 delay_probe=$(ip -n "$ns1" -j ntable show dev veth0 name "$tbl_name" | jq '.[]["delay_probe"]') 179 run_cmd "ip -n $ns1 neigh flush dev veth0" 180 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 181 run_cmd "ip -n $ns1 neigh replace $ip_addr lladdr $mac nud stale dev veth0 extern_valid use" 182 run_cmd "sleep $((delay_probe / 1000 + 2))" 183 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"REACHABLE\"" 184 check_err $? "Entry did not transition to \"reachable\" state" 185 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 186 check_err $? "Entry did not maintain \"extern_valid\" flag after transition to \"reachable\" state" 187 188 log_test "$af_str \"extern_valid\" flag: Transition to \"reachable\" state" 189 190 RET=0 191 192 # Drop all packets, trigger resolution and check that entry goes back 193 # to "stale" state instead of "failed". 194 local mcast_reprobes 195 local retrans_time 196 local ucast_probes 197 local app_probes 198 local probes 199 local delay 200 201 run_cmd "ip -n $ns1 neigh flush dev veth0" 202 run_cmd "tc -n $ns2 qdisc add dev veth1 clsact" 203 run_cmd "tc -n $ns2 filter add dev veth1 ingress proto all matchall action drop" 204 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 205 run_cmd "ip -n $ns1 neigh replace $ip_addr lladdr $mac nud stale dev veth0 extern_valid use" 206 retrans_time=$(ip -n "$ns1" -j ntable show dev veth0 name "$tbl_name" | jq '.[]["retrans"]') 207 ucast_probes=$(ip -n "$ns1" -j ntable show dev veth0 name "$tbl_name" | jq '.[]["ucast_probes"]') 208 app_probes=$(ip -n "$ns1" -j ntable show dev veth0 name "$tbl_name" | jq '.[]["app_probes"]') 209 mcast_reprobes=$(ip -n "$ns1" -j ntable show dev veth0 name "$tbl_name" | jq '.[]["mcast_reprobes"]') 210 delay=$((delay_probe + (ucast_probes + app_probes + mcast_reprobes) * retrans_time)) 211 run_cmd "sleep $((delay / 1000 + 2))" 212 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"STALE\"" 213 check_err $? "Entry did not return to \"stale\" state" 214 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 215 check_err $? "Entry did not maintain \"extern_valid\" flag after returning to \"stale\" state" 216 probes=$(ip -n "$ns1" -j -s neigh get "$ip_addr" dev veth0 | jq '.[]["probes"]') 217 if [[ $probes -eq 0 ]]; then 218 check_err 1 "No probes were sent" 219 fi 220 221 log_test "$af_str \"extern_valid\" flag: Transition back to \"stale\" state" 222 223 run_cmd "tc -n $ns2 qdisc del dev veth1 clsact" 224 225 RET=0 226 227 # Forced garbage collection runs whenever the number of entries is 228 # larger than "thresh3" and deletes stale entries that have not been 229 # updated in the last 5 seconds. 230 # 231 # Check that an "extern_valid" entry survives a forced garbage 232 # collection. Add an entry, wait 5 seconds and add more entries than 233 # "thresh3" so that forced garbage collection will run. 234 # 235 # Note that the garbage collection thresholds are global resources and 236 # that changes in the initial namespace affect all the namespaces. 237 local forced_gc_runs_t0 238 local forced_gc_runs_t1 239 local orig_thresh1 240 local orig_thresh2 241 local orig_thresh3 242 243 run_cmd "ip -n $ns1 neigh flush dev veth0" 244 orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]') 245 orig_thresh2=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh2")) | .["thresh2"]') 246 orig_thresh3=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh3")) | .["thresh3"]') 247 run_cmd "ip ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8" 248 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 249 run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0" 250 run_cmd "sleep 5" 251 forced_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]') 252 for i in {1..20}; do 253 run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0" 254 done 255 forced_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]') 256 if [[ $forced_gc_runs_t1 -eq $forced_gc_runs_t0 ]]; then 257 check_err 1 "Forced garbage collection did not run" 258 fi 259 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 260 check_err $? "Entry with \"extern_valid\" flag did not survive forced garbage collection" 261 run_cmd "ip -n $ns1 neigh get ${subnet}3 dev veth0" 262 check_fail $? "Entry without \"extern_valid\" flag survived forced garbage collection" 263 264 log_test "$af_str \"extern_valid\" flag: Forced garbage collection" 265 266 run_cmd "ip ntable change name $tbl_name thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1" 267 268 RET=0 269 270 # Periodic garbage collection runs every "base_reachable"/2 seconds and 271 # if the number of entries is larger than "thresh1", then it deletes 272 # stale entries that have not been used in the last "gc_stale" seconds. 273 # 274 # Check that an "extern_valid" entry survives a periodic garbage 275 # collection. Add an "extern_valid" entry, add more than "thresh1" 276 # regular entries, wait "base_reachable" (longer than "gc_stale") 277 # seconds and check that the "extern_valid" entry was not deleted. 278 # 279 # Note that the garbage collection thresholds and "base_reachable" are 280 # global resources and that changes in the initial namespace affect all 281 # the namespaces. 282 local periodic_gc_runs_t0 283 local periodic_gc_runs_t1 284 local orig_base_reachable 285 local orig_gc_stale 286 287 run_cmd "ip -n $ns1 neigh flush dev veth0" 288 orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]') 289 orig_base_reachable=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["base_reachable"]') 290 run_cmd "ip ntable change name $tbl_name thresh1 10 base_reachable 10000" 291 orig_gc_stale=$(ip -n "$ns1" -j ntable show name "$tbl_name" dev veth0 | jq '.[]["gc_stale"]') 292 run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale 1000" 293 run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid" 294 run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0" 295 # Wait orig_base_reachable/2 for the new interval to take effect. 296 run_cmd "sleep $(((orig_base_reachable / 1000) / 2 + 2))" 297 for i in {1..20}; do 298 run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0" 299 done 300 periodic_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]') 301 run_cmd "sleep 10" 302 periodic_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]') 303 [[ $periodic_gc_runs_t1 -ne $periodic_gc_runs_t0 ]] 304 check_err $? "Periodic garbage collection did not run" 305 run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\"" 306 check_err $? "Entry with \"extern_valid\" flag did not survive periodic garbage collection" 307 run_cmd "ip -n $ns1 neigh get ${subnet}3 dev veth0" 308 check_fail $? "Entry without \"extern_valid\" flag survived periodic garbage collection" 309 310 log_test "$af_str \"extern_valid\" flag: Periodic garbage collection" 311 312 run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale $orig_gc_stale" 313 run_cmd "ip ntable change name $tbl_name thresh1 $orig_thresh1 base_reachable $orig_base_reachable" 314} 315 316extern_valid_ipv4() 317{ 318 extern_valid_common "IPv4" 192.0.2.2 "arp_cache" 192.0.2. 319} 320 321extern_valid_ipv6() 322{ 323 extern_valid_common "IPv6" 2001:db8:1::2 "ndisc_cache" 2001:db8:1:: 324} 325 326################################################################################ 327# Usage 328 329usage() 330{ 331 cat <<EOF 332usage: ${0##*/} OPTS 333 334 -t <test> Test(s) to run (default: all) 335 (options: $TESTS) 336 -p Pause on fail 337 -v Verbose mode (show commands and output) 338EOF 339} 340 341################################################################################ 342# Main 343 344while getopts ":t:pvh" opt; do 345 case $opt in 346 t) TESTS=$OPTARG;; 347 p) PAUSE_ON_FAIL=yes;; 348 v) VERBOSE=$((VERBOSE + 1));; 349 h) usage; exit 0;; 350 *) usage; exit 1;; 351 esac 352done 353 354require_command jq 355 356if ! ip neigh help 2>&1 | grep -q "extern_valid"; then 357 echo "SKIP: iproute2 ip too old, missing \"extern_valid\" support" 358 exit "$ksft_skip" 359fi 360 361trap exit_cleanup_all EXIT 362 363for t in $TESTS 364do 365 setup; $t; cleanup_all_ns; 366done 367