xref: /linux/tools/testing/selftests/net/test_neigh.sh (revision 37816488247ddddbc3de113c78c83572274b1e2e)
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