1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# redirect test
5#
6#                     .253 +----+
7#                     +----| r1 |
8#                     |    +----+
9# +----+              |       |.1
10# | h1 |--------------+       |   10.1.1.0/30 2001:db8:1::0/126
11# +----+ .1           |       |.2
12#         172.16.1/24 |    +----+                   +----+
13#    2001:db8:16:1/64 +----| r2 |-------------------| h2 |
14#                     .254 +----+ .254           .2 +----+
15#                                    172.16.2/24
16#                                  2001:db8:16:2/64
17#
18# Route from h1 to h2 goes through r1, eth1 - connection between r1 and r2.
19# Route on r1 changed to go to r2 via eth0. This causes a redirect to be sent
20# from r1 to h1 telling h1 to use r2 when talking to h2.
21
22source lib.sh
23VERBOSE=0
24PAUSE_ON_FAIL=no
25
26H1_N1_IP=172.16.1.1
27R1_N1_IP=172.16.1.253
28R2_N1_IP=172.16.1.254
29
30H1_N1_IP6=2001:db8:16:1::1
31R1_N1_IP6=2001:db8:16:1::253
32R2_N1_IP6=2001:db8:16:1::254
33
34R1_R2_N1_IP=10.1.1.1
35R2_R1_N1_IP=10.1.1.2
36
37R1_R2_N1_IP6=2001:db8:1::1
38R2_R1_N1_IP6=2001:db8:1::2
39
40H2_N2=172.16.2.0/24
41H2_N2_6=2001:db8:16:2::/64
42H2_N2_IP=172.16.2.2
43R2_N2_IP=172.16.2.254
44H2_N2_IP6=2001:db8:16:2::2
45R2_N2_IP6=2001:db8:16:2::254
46
47VRF=red
48VRF_TABLE=1111
49
50################################################################################
51# helpers
52
53log_section()
54{
55	echo
56	echo "###########################################################################"
57	echo "$*"
58	echo "###########################################################################"
59	echo
60}
61
62log_test()
63{
64	local rc=$1
65	local expected=$2
66	local msg="$3"
67	local xfail=$4
68
69	if [ ${rc} -eq ${expected} ]; then
70		printf "TEST: %-60s  [ OK ]\n" "${msg}"
71		nsuccess=$((nsuccess+1))
72	elif [ ${rc} -eq ${xfail} ]; then
73		printf "TEST: %-60s  [XFAIL]\n" "${msg}"
74		nxfail=$((nxfail+1))
75	else
76		ret=1
77		nfail=$((nfail+1))
78		printf "TEST: %-60s  [FAIL]\n" "${msg}"
79		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
80			echo
81			echo "hit enter to continue, 'q' to quit"
82			read a
83			[ "$a" = "q" ] && exit 1
84		fi
85	fi
86}
87
88log_debug()
89{
90	if [ "$VERBOSE" = "1" ]; then
91		echo "$*"
92	fi
93}
94
95run_cmd()
96{
97	local cmd="$*"
98	local out
99	local rc
100
101	if [ "$VERBOSE" = "1" ]; then
102		echo "COMMAND: $cmd"
103	fi
104
105	out=$(eval $cmd 2>&1)
106	rc=$?
107	if [ "$VERBOSE" = "1" -a -n "$out" ]; then
108		echo "$out"
109	fi
110
111	[ "$VERBOSE" = "1" ] && echo
112
113	return $rc
114}
115
116get_linklocal()
117{
118	local ns=$1
119	local dev=$2
120	local addr
121
122	addr=$(ip -netns $ns -6 -br addr show dev ${dev} | \
123	awk '{
124		for (i = 3; i <= NF; ++i) {
125			if ($i ~ /^fe80/)
126				print $i
127		}
128	}'
129	)
130	addr=${addr/\/*}
131
132	[ -z "$addr" ] && return 1
133
134	echo $addr
135
136	return 0
137}
138
139################################################################################
140# setup and teardown
141
142cleanup()
143{
144	cleanup_ns $h1 $h2 $r1 $r2
145}
146
147create_vrf()
148{
149	local ns=$1
150
151	ip -netns ${ns} link add ${VRF} type vrf table ${VRF_TABLE}
152	ip -netns ${ns} link set ${VRF} up
153	ip -netns ${ns} route add vrf ${VRF} unreachable default metric 8192
154	ip -netns ${ns} -6 route add vrf ${VRF} unreachable default metric 8192
155
156	ip -netns ${ns} addr add 127.0.0.1/8 dev ${VRF}
157	ip -netns ${ns} -6 addr add ::1 dev ${VRF} nodad
158
159	ip -netns ${ns} ru del pref 0
160	ip -netns ${ns} ru add pref 32765 from all lookup local
161	ip -netns ${ns} -6 ru del pref 0
162	ip -netns ${ns} -6 ru add pref 32765 from all lookup local
163}
164
165setup()
166{
167	local ns
168
169	#
170	# create nodes as namespaces
171	setup_ns h1 h2 r1 r2
172	for ns in $h1 $h2 $r1 $r2; do
173		if echo $ns | grep -q h[12]-; then
174			ip netns exec $ns sysctl -q -w net.ipv4.conf.all.accept_redirects=1
175			ip netns exec $ns sysctl -q -w net.ipv6.conf.all.forwarding=0
176			ip netns exec $ns sysctl -q -w net.ipv6.conf.all.accept_redirects=1
177			ip netns exec $ns sysctl -q -w net.ipv6.conf.all.keep_addr_on_down=1
178		else
179			ip netns exec $ns sysctl -q -w net.ipv4.ip_forward=1
180			ip netns exec $ns sysctl -q -w net.ipv4.conf.all.send_redirects=1
181			ip netns exec $ns sysctl -q -w net.ipv4.conf.default.rp_filter=0
182			ip netns exec $ns sysctl -q -w net.ipv4.conf.all.rp_filter=0
183
184			ip netns exec $ns sysctl -q -w net.ipv6.conf.all.forwarding=1
185			ip netns exec $ns sysctl -q -w net.ipv6.route.mtu_expires=10
186		fi
187	done
188
189	#
190	# create interconnects
191	#
192	ip -netns $h1 li add eth0 type veth peer name r1h1
193	ip -netns $h1 li set r1h1 netns $r1 name eth0 up
194
195	ip -netns $h1 li add eth1 type veth peer name r2h1
196	ip -netns $h1 li set r2h1 netns $r2 name eth0 up
197
198	ip -netns $h2 li add eth0 type veth peer name r2h2
199	ip -netns $h2 li set eth0 up
200	ip -netns $h2 li set r2h2 netns $r2 name eth2 up
201
202	ip -netns $r1 li add eth1 type veth peer name r2r1
203	ip -netns $r1 li set eth1 up
204	ip -netns $r1 li set r2r1 netns $r2 name eth1 up
205
206	#
207	# h1
208	#
209	if [ "${WITH_VRF}" = "yes" ]; then
210		create_vrf "$h1"
211		H1_VRF_ARG="vrf ${VRF}"
212		H1_PING_ARG="-I ${VRF}"
213	else
214		H1_VRF_ARG=
215		H1_PING_ARG=
216	fi
217	ip -netns $h1 li add br0 type bridge
218	if [ "${WITH_VRF}" = "yes" ]; then
219		ip -netns $h1 li set br0 vrf ${VRF} up
220	else
221		ip -netns $h1 li set br0 up
222	fi
223	ip -netns $h1 addr add dev br0 ${H1_N1_IP}/24
224	ip -netns $h1 -6 addr add dev br0 ${H1_N1_IP6}/64 nodad
225	ip -netns $h1 li set eth0 master br0 up
226	ip -netns $h1 li set eth1 master br0 up
227
228	#
229	# h2
230	#
231	ip -netns $h2 addr add dev eth0 ${H2_N2_IP}/24
232	ip -netns $h2 ro add default via ${R2_N2_IP} dev eth0
233	ip -netns $h2 -6 addr add dev eth0 ${H2_N2_IP6}/64 nodad
234	ip -netns $h2 -6 ro add default via ${R2_N2_IP6} dev eth0
235
236	#
237	# r1
238	#
239	ip -netns $r1 addr add dev eth0 ${R1_N1_IP}/24
240	ip -netns $r1 -6 addr add dev eth0 ${R1_N1_IP6}/64 nodad
241	ip -netns $r1 addr add dev eth1 ${R1_R2_N1_IP}/30
242	ip -netns $r1 -6 addr add dev eth1 ${R1_R2_N1_IP6}/126 nodad
243
244	#
245	# r2
246	#
247	ip -netns $r2 addr add dev eth0 ${R2_N1_IP}/24
248	ip -netns $r2 -6 addr add dev eth0 ${R2_N1_IP6}/64 nodad
249	ip -netns $r2 addr add dev eth1 ${R2_R1_N1_IP}/30
250	ip -netns $r2 -6 addr add dev eth1 ${R2_R1_N1_IP6}/126 nodad
251	ip -netns $r2 addr add dev eth2 ${R2_N2_IP}/24
252	ip -netns $r2 -6 addr add dev eth2 ${R2_N2_IP6}/64 nodad
253
254	sleep 2
255
256	R1_LLADDR=$(get_linklocal $r1 eth0)
257	if [ $? -ne 0 ]; then
258		echo "Error: Failed to get link-local address of r1's eth0"
259		exit 1
260	fi
261	log_debug "initial gateway is R1's lladdr = ${R1_LLADDR}"
262
263	R2_LLADDR=$(get_linklocal $r2 eth0)
264	if [ $? -ne 0 ]; then
265		echo "Error: Failed to get link-local address of r2's eth0"
266		exit 1
267	fi
268	log_debug "initial gateway is R2's lladdr = ${R2_LLADDR}"
269}
270
271change_h2_mtu()
272{
273	local mtu=$1
274
275	run_cmd ip -netns $h2 li set eth0 mtu ${mtu}
276	run_cmd ip -netns $r2 li set eth2 mtu ${mtu}
277}
278
279check_exception()
280{
281	local mtu="$1"
282	local with_redirect="$2"
283	local desc="$3"
284
285	# From 172.16.1.101: icmp_seq=1 Redirect Host(New nexthop: 172.16.1.102)
286	if [ "$VERBOSE" = "1" ]; then
287		echo "Commands to check for exception:"
288		run_cmd ip -netns $h1 ro get ${H1_VRF_ARG} ${H2_N2_IP}
289		run_cmd ip -netns $h1 -6 ro get ${H1_VRF_ARG} ${H2_N2_IP6}
290	fi
291
292	if [ -n "${mtu}" ]; then
293		mtu=" mtu ${mtu}"
294	fi
295	if [ "$with_redirect" = "yes" ]; then
296		ip -netns $h1 ro get ${H1_VRF_ARG} ${H2_N2_IP} | \
297		grep -q "cache <redirected> expires [0-9]*sec${mtu}"
298	elif [ -n "${mtu}" ]; then
299		ip -netns $h1 ro get ${H1_VRF_ARG} ${H2_N2_IP} | \
300		grep -q "cache expires [0-9]*sec${mtu}"
301	else
302		# want to verify that neither mtu nor redirected appears in
303		# the route get output. The -v will wipe out the cache line
304		# if either are set so the last grep -q will not find a match
305		ip -netns $h1 ro get ${H1_VRF_ARG} ${H2_N2_IP} | \
306		grep -E -v 'mtu|redirected' | grep -q "cache"
307	fi
308	log_test $? 0 "IPv4: ${desc}" 0
309
310	# No PMTU info for test "redirect" and "mtu exception plus redirect"
311	if [ "$with_redirect" = "yes" ] && [ "$desc" != "redirect exception plus mtu" ]; then
312		ip -netns $h1 -6 ro get ${H1_VRF_ARG} ${H2_N2_IP6} | \
313		grep -v "mtu" | grep -q "${H2_N2_IP6} .*via ${R2_LLADDR} dev br0"
314	elif [ -n "${mtu}" ]; then
315		ip -netns $h1 -6 ro get ${H1_VRF_ARG} ${H2_N2_IP6} | \
316		grep -q "${mtu}"
317	else
318		# IPv6 is a bit harder. First strip out the match if it
319		# contains an mtu exception and then look for the first
320		# gateway - R1's lladdr
321		ip -netns $h1 -6 ro get ${H1_VRF_ARG} ${H2_N2_IP6} | \
322		grep -v "mtu" | grep -q "${R1_LLADDR}"
323	fi
324	log_test $? 0 "IPv6: ${desc}" 1
325}
326
327run_ping()
328{
329	local sz=$1
330
331	run_cmd ip netns exec $h1 ping -q -M want -i 0.5 -c 10 -w 2 -s ${sz} ${H1_PING_ARG} ${H2_N2_IP}
332	run_cmd ip netns exec $h1 ${ping6} -q -M want -i 0.5 -c 10 -w 2 -s ${sz} ${H1_PING_ARG} ${H2_N2_IP6}
333}
334
335replace_route_new()
336{
337	# r1 to h2 via r2 and eth0
338	run_cmd ip -netns $r1 nexthop replace id 1 via ${R2_N1_IP} dev eth0
339	run_cmd ip -netns $r1 nexthop replace id 2 via ${R2_LLADDR} dev eth0
340}
341
342reset_route_new()
343{
344	run_cmd ip -netns $r1 nexthop flush
345	run_cmd ip -netns $h1 nexthop flush
346
347	initial_route_new
348}
349
350initial_route_new()
351{
352	# r1 to h2 via r2 and eth1
353	run_cmd ip -netns $r1 nexthop add id 1 via ${R2_R1_N1_IP} dev eth1
354	run_cmd ip -netns $r1 ro add ${H2_N2} nhid 1
355
356	run_cmd ip -netns $r1 nexthop add id 2 via ${R2_R1_N1_IP6} dev eth1
357	run_cmd ip -netns $r1 -6 ro add ${H2_N2_6} nhid 2
358
359	# h1 to h2 via r1
360	run_cmd ip -netns $h1 nexthop add id 1 via ${R1_N1_IP} dev br0
361	run_cmd ip -netns $h1 ro add ${H1_VRF_ARG} ${H2_N2} nhid 1
362
363	run_cmd ip -netns $h1 nexthop add id 2 via ${R1_LLADDR} dev br0
364	run_cmd ip -netns $h1 -6 ro add ${H1_VRF_ARG} ${H2_N2_6} nhid 2
365}
366
367replace_route_legacy()
368{
369	# r1 to h2 via r2 and eth0
370	run_cmd ip -netns $r1    ro replace ${H2_N2}   via ${R2_N1_IP}  dev eth0
371	run_cmd ip -netns $r1 -6 ro replace ${H2_N2_6} via ${R2_LLADDR} dev eth0
372}
373
374reset_route_legacy()
375{
376	run_cmd ip -netns $r1    ro del ${H2_N2}
377	run_cmd ip -netns $r1 -6 ro del ${H2_N2_6}
378
379	run_cmd ip -netns $h1    ro del ${H1_VRF_ARG} ${H2_N2}
380	run_cmd ip -netns $h1 -6 ro del ${H1_VRF_ARG} ${H2_N2_6}
381
382	initial_route_legacy
383}
384
385initial_route_legacy()
386{
387	# r1 to h2 via r2 and eth1
388	run_cmd ip -netns $r1    ro add ${H2_N2}   via ${R2_R1_N1_IP}  dev eth1
389	run_cmd ip -netns $r1 -6 ro add ${H2_N2_6} via ${R2_R1_N1_IP6} dev eth1
390
391	# h1 to h2 via r1
392	# - IPv6 redirect only works if gateway is the LLA
393	run_cmd ip -netns $h1    ro add ${H1_VRF_ARG} ${H2_N2} via ${R1_N1_IP} dev br0
394	run_cmd ip -netns $h1 -6 ro add ${H1_VRF_ARG} ${H2_N2_6} via ${R1_LLADDR} dev br0
395}
396
397check_connectivity()
398{
399	local rc
400
401	run_cmd ip netns exec $h1 ping -c1 -w1 ${H1_PING_ARG} ${H2_N2_IP}
402	rc=$?
403	run_cmd ip netns exec $h1 ${ping6} -c1 -w1 ${H1_PING_ARG} ${H2_N2_IP6}
404	[ $? -ne 0 ] && rc=$?
405
406	return $rc
407}
408
409do_test()
410{
411	local ttype="$1"
412
413	eval initial_route_${ttype}
414
415	# verify connectivity
416	check_connectivity
417	if [ $? -ne 0 ]; then
418		echo "Error: Basic connectivity is broken"
419		ret=1
420		return
421	fi
422
423	# redirect exception followed by mtu
424	eval replace_route_${ttype}
425	run_ping 64
426	check_exception "" "yes" "redirect exception"
427
428	check_connectivity
429	if [ $? -ne 0 ]; then
430		echo "Error: Basic connectivity is broken after redirect"
431		ret=1
432		return
433	fi
434
435	change_h2_mtu 1300
436	run_ping 1350
437	check_exception "1300" "yes" "redirect exception plus mtu"
438
439	# remove exceptions and restore routing
440	change_h2_mtu 1500
441	eval reset_route_${ttype}
442
443	check_connectivity
444	if [ $? -ne 0 ]; then
445		echo "Error: Basic connectivity is broken after reset"
446		ret=1
447		return
448	fi
449	check_exception "" "no" "routing reset"
450
451	# MTU exception followed by redirect
452	change_h2_mtu 1300
453	run_ping 1350
454	check_exception "1300" "no" "mtu exception"
455
456	eval replace_route_${ttype}
457	run_ping 64
458	check_exception "1300" "yes" "mtu exception plus redirect"
459
460	check_connectivity
461	if [ $? -ne 0 ]; then
462		echo "Error: Basic connectivity is broken after redirect"
463		ret=1
464		return
465	fi
466}
467
468################################################################################
469# usage
470
471usage()
472{
473        cat <<EOF
474usage: ${0##*/} OPTS
475
476	-p          Pause on fail
477	-v          verbose mode (show commands and output)
478EOF
479}
480
481################################################################################
482# main
483
484# Some systems don't have a ping6 binary anymore
485which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
486
487ret=0
488nsuccess=0
489nfail=0
490nxfail=0
491
492while getopts :pv o
493do
494	case $o in
495                p) PAUSE_ON_FAIL=yes;;
496                v) VERBOSE=$(($VERBOSE + 1));;
497                *) usage; exit 1;;
498	esac
499done
500
501trap cleanup EXIT
502
503cleanup
504WITH_VRF=no
505setup
506
507log_section "Legacy routing"
508do_test "legacy"
509
510cleanup
511log_section "Legacy routing with VRF"
512WITH_VRF=yes
513setup
514do_test "legacy"
515
516cleanup
517log_section "Routing with nexthop objects"
518ip nexthop ls >/dev/null 2>&1
519if [ $? -eq 0 ]; then
520	WITH_VRF=no
521	setup
522	do_test "new"
523
524	cleanup
525	log_section "Routing with nexthop objects and VRF"
526	WITH_VRF=yes
527	setup
528	do_test "new"
529else
530	echo "Nexthop objects not supported; skipping tests"
531fi
532
533printf "\nTests passed: %3d\n" ${nsuccess}
534printf "Tests failed: %3d\n"   ${nfail}
535printf "Tests xfailed: %3d\n"  ${nxfail}
536
537exit $ret
538