xref: /linux/tools/testing/selftests/net/forwarding/lib.sh (revision ef347a340b1a8507c22ee3cf981cd5cd64188431)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Can be overridden by the configuration file.
8PING=${PING:=ping}
9PING6=${PING6:=ping6}
10MZ=${MZ:=mausezahn}
11WAIT_TIME=${WAIT_TIME:=5}
12PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
13PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
14NETIF_TYPE=${NETIF_TYPE:=veth}
15NETIF_CREATE=${NETIF_CREATE:=yes}
16
17if [[ -f forwarding.config ]]; then
18	source forwarding.config
19fi
20
21##############################################################################
22# Sanity checks
23
24check_tc_version()
25{
26	tc -j &> /dev/null
27	if [[ $? -ne 0 ]]; then
28		echo "SKIP: iproute2 too old; tc is missing JSON support"
29		exit 1
30	fi
31
32	tc filter help 2>&1 | grep block &> /dev/null
33	if [[ $? -ne 0 ]]; then
34		echo "SKIP: iproute2 too old; tc is missing shared block support"
35		exit 1
36	fi
37}
38
39if [[ "$(id -u)" -ne 0 ]]; then
40	echo "SKIP: need root privileges"
41	exit 0
42fi
43
44if [[ "$CHECK_TC" = "yes" ]]; then
45	check_tc_version
46fi
47
48if [[ ! -x "$(command -v jq)" ]]; then
49	echo "SKIP: jq not installed"
50	exit 1
51fi
52
53if [[ ! -x "$(command -v $MZ)" ]]; then
54	echo "SKIP: $MZ not installed"
55	exit 1
56fi
57
58if [[ ! -v NUM_NETIFS ]]; then
59	echo "SKIP: importer does not define \"NUM_NETIFS\""
60	exit 1
61fi
62
63##############################################################################
64# Command line options handling
65
66count=0
67
68while [[ $# -gt 0 ]]; do
69	if [[ "$count" -eq "0" ]]; then
70		unset NETIFS
71		declare -A NETIFS
72	fi
73	count=$((count + 1))
74	NETIFS[p$count]="$1"
75	shift
76done
77
78##############################################################################
79# Network interfaces configuration
80
81create_netif_veth()
82{
83	local i
84
85	for i in $(eval echo {1..$NUM_NETIFS}); do
86		local j=$((i+1))
87
88		ip link show dev ${NETIFS[p$i]} &> /dev/null
89		if [[ $? -ne 0 ]]; then
90			ip link add ${NETIFS[p$i]} type veth \
91				peer name ${NETIFS[p$j]}
92			if [[ $? -ne 0 ]]; then
93				echo "Failed to create netif"
94				exit 1
95			fi
96		fi
97		i=$j
98	done
99}
100
101create_netif()
102{
103	case "$NETIF_TYPE" in
104	veth) create_netif_veth
105	      ;;
106	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
107	   exit 1
108	   ;;
109	esac
110}
111
112if [[ "$NETIF_CREATE" = "yes" ]]; then
113	create_netif
114fi
115
116for i in $(eval echo {1..$NUM_NETIFS}); do
117	ip link show dev ${NETIFS[p$i]} &> /dev/null
118	if [[ $? -ne 0 ]]; then
119		echo "SKIP: could not find all required interfaces"
120		exit 1
121	fi
122done
123
124##############################################################################
125# Helpers
126
127# Exit status to return at the end. Set in case one of the tests fails.
128EXIT_STATUS=0
129# Per-test return value. Clear at the beginning of each test.
130RET=0
131
132check_err()
133{
134	local err=$1
135	local msg=$2
136
137	if [[ $RET -eq 0 && $err -ne 0 ]]; then
138		RET=$err
139		retmsg=$msg
140	fi
141}
142
143check_fail()
144{
145	local err=$1
146	local msg=$2
147
148	if [[ $RET -eq 0 && $err -eq 0 ]]; then
149		RET=1
150		retmsg=$msg
151	fi
152}
153
154log_test()
155{
156	local test_name=$1
157	local opt_str=$2
158
159	if [[ $# -eq 2 ]]; then
160		opt_str="($opt_str)"
161	fi
162
163	if [[ $RET -ne 0 ]]; then
164		EXIT_STATUS=1
165		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
166		if [[ ! -z "$retmsg" ]]; then
167			printf "\t%s\n" "$retmsg"
168		fi
169		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
170			echo "Hit enter to continue, 'q' to quit"
171			read a
172			[ "$a" = "q" ] && exit 1
173		fi
174		return 1
175	fi
176
177	printf "TEST: %-60s  [PASS]\n" "$test_name $opt_str"
178	return 0
179}
180
181log_info()
182{
183	local msg=$1
184
185	echo "INFO: $msg"
186}
187
188setup_wait()
189{
190	for i in $(eval echo {1..$NUM_NETIFS}); do
191		while true; do
192			ip link show dev ${NETIFS[p$i]} up \
193				| grep 'state UP' &> /dev/null
194			if [[ $? -ne 0 ]]; then
195				sleep 1
196			else
197				break
198			fi
199		done
200	done
201
202	# Make sure links are ready.
203	sleep $WAIT_TIME
204}
205
206pre_cleanup()
207{
208	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
209		echo "Pausing before cleanup, hit any key to continue"
210		read
211	fi
212}
213
214vrf_prepare()
215{
216	ip -4 rule add pref 32765 table local
217	ip -4 rule del pref 0
218	ip -6 rule add pref 32765 table local
219	ip -6 rule del pref 0
220}
221
222vrf_cleanup()
223{
224	ip -6 rule add pref 0 table local
225	ip -6 rule del pref 32765
226	ip -4 rule add pref 0 table local
227	ip -4 rule del pref 32765
228}
229
230__last_tb_id=0
231declare -A __TB_IDS
232
233__vrf_td_id_assign()
234{
235	local vrf_name=$1
236
237	__last_tb_id=$((__last_tb_id + 1))
238	__TB_IDS[$vrf_name]=$__last_tb_id
239	return $__last_tb_id
240}
241
242__vrf_td_id_lookup()
243{
244	local vrf_name=$1
245
246	return ${__TB_IDS[$vrf_name]}
247}
248
249vrf_create()
250{
251	local vrf_name=$1
252	local tb_id
253
254	__vrf_td_id_assign $vrf_name
255	tb_id=$?
256
257	ip link add dev $vrf_name type vrf table $tb_id
258	ip -4 route add table $tb_id unreachable default metric 4278198272
259	ip -6 route add table $tb_id unreachable default metric 4278198272
260}
261
262vrf_destroy()
263{
264	local vrf_name=$1
265	local tb_id
266
267	__vrf_td_id_lookup $vrf_name
268	tb_id=$?
269
270	ip -6 route del table $tb_id unreachable default metric 4278198272
271	ip -4 route del table $tb_id unreachable default metric 4278198272
272	ip link del dev $vrf_name
273}
274
275__addr_add_del()
276{
277	local if_name=$1
278	local add_del=$2
279	local array
280
281	shift
282	shift
283	array=("${@}")
284
285	for addrstr in "${array[@]}"; do
286		ip address $add_del $addrstr dev $if_name
287	done
288}
289
290__simple_if_init()
291{
292	local if_name=$1; shift
293	local vrf_name=$1; shift
294	local addrs=("${@}")
295
296	ip link set dev $if_name master $vrf_name
297	ip link set dev $if_name up
298
299	__addr_add_del $if_name add "${addrs[@]}"
300}
301
302__simple_if_fini()
303{
304	local if_name=$1; shift
305	local addrs=("${@}")
306
307	__addr_add_del $if_name del "${addrs[@]}"
308
309	ip link set dev $if_name down
310	ip link set dev $if_name nomaster
311}
312
313simple_if_init()
314{
315	local if_name=$1
316	local vrf_name
317	local array
318
319	shift
320	vrf_name=v$if_name
321	array=("${@}")
322
323	vrf_create $vrf_name
324	ip link set dev $vrf_name up
325	__simple_if_init $if_name $vrf_name "${array[@]}"
326}
327
328simple_if_fini()
329{
330	local if_name=$1
331	local vrf_name
332	local array
333
334	shift
335	vrf_name=v$if_name
336	array=("${@}")
337
338	__simple_if_fini $if_name "${array[@]}"
339	vrf_destroy $vrf_name
340}
341
342tunnel_create()
343{
344	local name=$1; shift
345	local type=$1; shift
346	local local=$1; shift
347	local remote=$1; shift
348
349	ip link add name $name type $type \
350	   local $local remote $remote "$@"
351	ip link set dev $name up
352}
353
354tunnel_destroy()
355{
356	local name=$1; shift
357
358	ip link del dev $name
359}
360
361vlan_create()
362{
363	local if_name=$1; shift
364	local vid=$1; shift
365	local vrf=$1; shift
366	local ips=("${@}")
367	local name=$if_name.$vid
368
369	ip link add name $name link $if_name type vlan id $vid
370	if [ "$vrf" != "" ]; then
371		ip link set dev $name master $vrf
372	fi
373	ip link set dev $name up
374	__addr_add_del $name add "${ips[@]}"
375}
376
377vlan_destroy()
378{
379	local if_name=$1; shift
380	local vid=$1; shift
381	local name=$if_name.$vid
382
383	ip link del dev $name
384}
385
386master_name_get()
387{
388	local if_name=$1
389
390	ip -j link show dev $if_name | jq -r '.[]["master"]'
391}
392
393link_stats_tx_packets_get()
394{
395       local if_name=$1
396
397       ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]'
398}
399
400tc_rule_stats_get()
401{
402	local dev=$1; shift
403	local pref=$1; shift
404	local dir=$1; shift
405
406	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
407	    | jq '.[1].options.actions[].stats.packets'
408}
409
410mac_get()
411{
412	local if_name=$1
413
414	ip -j link show dev $if_name | jq -r '.[]["address"]'
415}
416
417bridge_ageing_time_get()
418{
419	local bridge=$1
420	local ageing_time
421
422	# Need to divide by 100 to convert to seconds.
423	ageing_time=$(ip -j -d link show dev $bridge \
424		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
425	echo $((ageing_time / 100))
426}
427
428declare -A SYSCTL_ORIG
429sysctl_set()
430{
431	local key=$1; shift
432	local value=$1; shift
433
434	SYSCTL_ORIG[$key]=$(sysctl -n $key)
435	sysctl -qw $key=$value
436}
437
438sysctl_restore()
439{
440	local key=$1; shift
441
442	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
443}
444
445forwarding_enable()
446{
447	sysctl_set net.ipv4.conf.all.forwarding 1
448	sysctl_set net.ipv6.conf.all.forwarding 1
449}
450
451forwarding_restore()
452{
453	sysctl_restore net.ipv6.conf.all.forwarding
454	sysctl_restore net.ipv4.conf.all.forwarding
455}
456
457tc_offload_check()
458{
459	for i in $(eval echo {1..$NUM_NETIFS}); do
460		ethtool -k ${NETIFS[p$i]} \
461			| grep "hw-tc-offload: on" &> /dev/null
462		if [[ $? -ne 0 ]]; then
463			return 1
464		fi
465	done
466
467	return 0
468}
469
470trap_install()
471{
472	local dev=$1; shift
473	local direction=$1; shift
474
475	# For slow-path testing, we need to install a trap to get to
476	# slow path the packets that would otherwise be switched in HW.
477	tc filter add dev $dev $direction pref 1 flower skip_sw action trap
478}
479
480trap_uninstall()
481{
482	local dev=$1; shift
483	local direction=$1; shift
484
485	tc filter del dev $dev $direction pref 1 flower skip_sw
486}
487
488slow_path_trap_install()
489{
490	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
491		trap_install "$@"
492	fi
493}
494
495slow_path_trap_uninstall()
496{
497	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
498		trap_uninstall "$@"
499	fi
500}
501
502__icmp_capture_add_del()
503{
504	local add_del=$1; shift
505	local pref=$1; shift
506	local vsuf=$1; shift
507	local tundev=$1; shift
508	local filter=$1; shift
509
510	tc filter $add_del dev "$tundev" ingress \
511	   proto ip$vsuf pref $pref \
512	   flower ip_proto icmp$vsuf $filter \
513	   action pass
514}
515
516icmp_capture_install()
517{
518	__icmp_capture_add_del add 100 "" "$@"
519}
520
521icmp_capture_uninstall()
522{
523	__icmp_capture_add_del del 100 "" "$@"
524}
525
526icmp6_capture_install()
527{
528	__icmp_capture_add_del add 100 v6 "$@"
529}
530
531icmp6_capture_uninstall()
532{
533	__icmp_capture_add_del del 100 v6 "$@"
534}
535
536__vlan_capture_add_del()
537{
538	local add_del=$1; shift
539	local pref=$1; shift
540	local dev=$1; shift
541	local filter=$1; shift
542
543	tc filter $add_del dev "$dev" ingress \
544	   proto 802.1q pref $pref \
545	   flower $filter \
546	   action pass
547}
548
549vlan_capture_install()
550{
551	__vlan_capture_add_del add 100 "$@"
552}
553
554vlan_capture_uninstall()
555{
556	__vlan_capture_add_del del 100 "$@"
557}
558
559matchall_sink_create()
560{
561	local dev=$1; shift
562
563	tc qdisc add dev $dev clsact
564	tc filter add dev $dev ingress \
565	   pref 10000 \
566	   matchall \
567	   action drop
568}
569
570tests_run()
571{
572	local current_test
573
574	for current_test in ${TESTS:-$ALL_TESTS}; do
575		$current_test
576	done
577}
578
579multipath_eval()
580{
581	local desc="$1"
582	local weight_rp12=$2
583	local weight_rp13=$3
584	local packets_rp12=$4
585	local packets_rp13=$5
586	local weights_ratio packets_ratio diff
587
588	RET=0
589
590	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
591		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
592				| bc -l)
593	else
594		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
595				| bc -l)
596	fi
597
598	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
599	       check_err 1 "Packet difference is 0"
600	       log_test "Multipath"
601	       log_info "Expected ratio $weights_ratio"
602	       return
603	fi
604
605	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
606		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
607				| bc -l)
608	else
609		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
610				| bc -l)
611	fi
612
613	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
614	diff=${diff#-}
615
616	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
617	check_err $? "Too large discrepancy between expected and measured ratios"
618	log_test "$desc"
619	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
620}
621
622##############################################################################
623# Tests
624
625ping_test()
626{
627	local if_name=$1
628	local dip=$2
629	local vrf_name
630
631	RET=0
632
633	vrf_name=$(master_name_get $if_name)
634	ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null
635	check_err $?
636	log_test "ping"
637}
638
639ping6_test()
640{
641	local if_name=$1
642	local dip=$2
643	local vrf_name
644
645	RET=0
646
647	vrf_name=$(master_name_get $if_name)
648	ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null
649	check_err $?
650	log_test "ping6"
651}
652
653learning_test()
654{
655	local bridge=$1
656	local br_port1=$2	# Connected to `host1_if`.
657	local host1_if=$3
658	local host2_if=$4
659	local mac=de:ad:be:ef:13:37
660	local ageing_time
661
662	RET=0
663
664	bridge -j fdb show br $bridge brport $br_port1 \
665		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
666	check_fail $? "Found FDB record when should not"
667
668	# Disable unknown unicast flooding on `br_port1` to make sure
669	# packets are only forwarded through the port after a matching
670	# FDB entry was installed.
671	bridge link set dev $br_port1 flood off
672
673	tc qdisc add dev $host1_if ingress
674	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
675		flower dst_mac $mac action drop
676
677	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
678	sleep 1
679
680	tc -j -s filter show dev $host1_if ingress \
681		| jq -e ".[] | select(.options.handle == 101) \
682		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
683	check_fail $? "Packet reached second host when should not"
684
685	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
686	sleep 1
687
688	bridge -j fdb show br $bridge brport $br_port1 \
689		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
690	check_err $? "Did not find FDB record when should"
691
692	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
693	sleep 1
694
695	tc -j -s filter show dev $host1_if ingress \
696		| jq -e ".[] | select(.options.handle == 101) \
697		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
698	check_err $? "Packet did not reach second host when should"
699
700	# Wait for 10 seconds after the ageing time to make sure FDB
701	# record was aged-out.
702	ageing_time=$(bridge_ageing_time_get $bridge)
703	sleep $((ageing_time + 10))
704
705	bridge -j fdb show br $bridge brport $br_port1 \
706		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
707	check_fail $? "Found FDB record when should not"
708
709	bridge link set dev $br_port1 learning off
710
711	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
712	sleep 1
713
714	bridge -j fdb show br $bridge brport $br_port1 \
715		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
716	check_fail $? "Found FDB record when should not"
717
718	bridge link set dev $br_port1 learning on
719
720	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
721	tc qdisc del dev $host1_if ingress
722
723	bridge link set dev $br_port1 flood on
724
725	log_test "FDB learning"
726}
727
728flood_test_do()
729{
730	local should_flood=$1
731	local mac=$2
732	local ip=$3
733	local host1_if=$4
734	local host2_if=$5
735	local err=0
736
737	# Add an ACL on `host2_if` which will tell us whether the packet
738	# was flooded to it or not.
739	tc qdisc add dev $host2_if ingress
740	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
741		flower dst_mac $mac action drop
742
743	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
744	sleep 1
745
746	tc -j -s filter show dev $host2_if ingress \
747		| jq -e ".[] | select(.options.handle == 101) \
748		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
749	if [[ $? -ne 0 && $should_flood == "true" || \
750	      $? -eq 0 && $should_flood == "false" ]]; then
751		err=1
752	fi
753
754	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
755	tc qdisc del dev $host2_if ingress
756
757	return $err
758}
759
760flood_unicast_test()
761{
762	local br_port=$1
763	local host1_if=$2
764	local host2_if=$3
765	local mac=de:ad:be:ef:13:37
766	local ip=192.0.2.100
767
768	RET=0
769
770	bridge link set dev $br_port flood off
771
772	flood_test_do false $mac $ip $host1_if $host2_if
773	check_err $? "Packet flooded when should not"
774
775	bridge link set dev $br_port flood on
776
777	flood_test_do true $mac $ip $host1_if $host2_if
778	check_err $? "Packet was not flooded when should"
779
780	log_test "Unknown unicast flood"
781}
782
783flood_multicast_test()
784{
785	local br_port=$1
786	local host1_if=$2
787	local host2_if=$3
788	local mac=01:00:5e:00:00:01
789	local ip=239.0.0.1
790
791	RET=0
792
793	bridge link set dev $br_port mcast_flood off
794
795	flood_test_do false $mac $ip $host1_if $host2_if
796	check_err $? "Packet flooded when should not"
797
798	bridge link set dev $br_port mcast_flood on
799
800	flood_test_do true $mac $ip $host1_if $host2_if
801	check_err $? "Packet was not flooded when should"
802
803	log_test "Unregistered multicast flood"
804}
805
806flood_test()
807{
808	# `br_port` is connected to `host2_if`
809	local br_port=$1
810	local host1_if=$2
811	local host2_if=$3
812
813	flood_unicast_test $br_port $host1_if $host2_if
814	flood_multicast_test $br_port $host1_if $host2_if
815}
816