xref: /linux/tools/testing/selftests/net/forwarding/lib.sh (revision 07c8a2dd69f6102adc12a621b4ef5e17d2a5b40d)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Kselftest framework requirement - SKIP code is 4.
8ksft_skip=4
9
10# Can be overridden by the configuration file.
11PING=${PING:=ping}
12PING6=${PING6:=ping6}
13MZ=${MZ:=mausezahn}
14ARPING=${ARPING:=arping}
15TEAMD=${TEAMD:=teamd}
16WAIT_TIME=${WAIT_TIME:=5}
17PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
18PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
19NETIF_TYPE=${NETIF_TYPE:=veth}
20NETIF_CREATE=${NETIF_CREATE:=yes}
21MCD=${MCD:=smcrouted}
22MC_CLI=${MC_CLI:=smcroutectl}
23PING_COUNT=${PING_COUNT:=10}
24PING_TIMEOUT=${PING_TIMEOUT:=5}
25WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
26INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
27LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
28REQUIRE_JQ=${REQUIRE_JQ:=yes}
29REQUIRE_MZ=${REQUIRE_MZ:=yes}
30REQUIRE_MTOOLS=${REQUIRE_MTOOLS:=no}
31STABLE_MAC_ADDRS=${STABLE_MAC_ADDRS:=no}
32TCPDUMP_EXTRA_FLAGS=${TCPDUMP_EXTRA_FLAGS:=}
33
34relative_path="${BASH_SOURCE%/*}"
35if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
36	relative_path="."
37fi
38
39if [[ -f $relative_path/forwarding.config ]]; then
40	source "$relative_path/forwarding.config"
41fi
42
43##############################################################################
44# Sanity checks
45
46check_tc_version()
47{
48	tc -j &> /dev/null
49	if [[ $? -ne 0 ]]; then
50		echo "SKIP: iproute2 too old; tc is missing JSON support"
51		exit $ksft_skip
52	fi
53}
54
55# Old versions of tc don't understand "mpls_uc"
56check_tc_mpls_support()
57{
58	local dev=$1; shift
59
60	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
61		matchall action pipe &> /dev/null
62	if [[ $? -ne 0 ]]; then
63		echo "SKIP: iproute2 too old; tc is missing MPLS support"
64		return $ksft_skip
65	fi
66	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
67		matchall
68}
69
70# Old versions of tc produce invalid json output for mpls lse statistics
71check_tc_mpls_lse_stats()
72{
73	local dev=$1; shift
74	local ret;
75
76	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
77		flower mpls lse depth 2                                 \
78		action continue &> /dev/null
79
80	if [[ $? -ne 0 ]]; then
81		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
82		return $ksft_skip
83	fi
84
85	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
86	ret=$?
87	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
88		flower
89
90	if [[ $ret -ne 0 ]]; then
91		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
92		return $ksft_skip
93	fi
94}
95
96check_tc_shblock_support()
97{
98	tc filter help 2>&1 | grep block &> /dev/null
99	if [[ $? -ne 0 ]]; then
100		echo "SKIP: iproute2 too old; tc is missing shared block support"
101		exit $ksft_skip
102	fi
103}
104
105check_tc_chain_support()
106{
107	tc help 2>&1|grep chain &> /dev/null
108	if [[ $? -ne 0 ]]; then
109		echo "SKIP: iproute2 too old; tc is missing chain support"
110		exit $ksft_skip
111	fi
112}
113
114check_tc_action_hw_stats_support()
115{
116	tc actions help 2>&1 | grep -q hw_stats
117	if [[ $? -ne 0 ]]; then
118		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
119		exit $ksft_skip
120	fi
121}
122
123check_ethtool_lanes_support()
124{
125	ethtool --help 2>&1| grep lanes &> /dev/null
126	if [[ $? -ne 0 ]]; then
127		echo "SKIP: ethtool too old; it is missing lanes support"
128		exit $ksft_skip
129	fi
130}
131
132check_locked_port_support()
133{
134	if ! bridge -d link show | grep -q " locked"; then
135		echo "SKIP: iproute2 too old; Locked port feature not supported."
136		return $ksft_skip
137	fi
138}
139
140if [[ "$(id -u)" -ne 0 ]]; then
141	echo "SKIP: need root privileges"
142	exit $ksft_skip
143fi
144
145if [[ "$CHECK_TC" = "yes" ]]; then
146	check_tc_version
147fi
148
149require_command()
150{
151	local cmd=$1; shift
152
153	if [[ ! -x "$(command -v "$cmd")" ]]; then
154		echo "SKIP: $cmd not installed"
155		exit $ksft_skip
156	fi
157}
158
159if [[ "$REQUIRE_JQ" = "yes" ]]; then
160	require_command jq
161fi
162if [[ "$REQUIRE_MZ" = "yes" ]]; then
163	require_command $MZ
164fi
165if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then
166	# https://github.com/vladimiroltean/mtools/
167	# patched for IPv6 support
168	require_command msend
169	require_command mreceive
170fi
171
172if [[ ! -v NUM_NETIFS ]]; then
173	echo "SKIP: importer does not define \"NUM_NETIFS\""
174	exit $ksft_skip
175fi
176
177##############################################################################
178# Command line options handling
179
180count=0
181
182while [[ $# -gt 0 ]]; do
183	if [[ "$count" -eq "0" ]]; then
184		unset NETIFS
185		declare -A NETIFS
186	fi
187	count=$((count + 1))
188	NETIFS[p$count]="$1"
189	shift
190done
191
192##############################################################################
193# Network interfaces configuration
194
195create_netif_veth()
196{
197	local i
198
199	for ((i = 1; i <= NUM_NETIFS; ++i)); do
200		local j=$((i+1))
201
202		ip link show dev ${NETIFS[p$i]} &> /dev/null
203		if [[ $? -ne 0 ]]; then
204			ip link add ${NETIFS[p$i]} type veth \
205				peer name ${NETIFS[p$j]}
206			if [[ $? -ne 0 ]]; then
207				echo "Failed to create netif"
208				exit 1
209			fi
210		fi
211		i=$j
212	done
213}
214
215create_netif()
216{
217	case "$NETIF_TYPE" in
218	veth) create_netif_veth
219	      ;;
220	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
221	   exit 1
222	   ;;
223	esac
224}
225
226declare -A MAC_ADDR_ORIG
227mac_addr_prepare()
228{
229	local new_addr=
230	local dev=
231
232	for ((i = 1; i <= NUM_NETIFS; ++i)); do
233		dev=${NETIFS[p$i]}
234		new_addr=$(printf "00:01:02:03:04:%02x" $i)
235
236		MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address')
237		# Strip quotes
238		MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/}
239		ip link set dev $dev address $new_addr
240	done
241}
242
243mac_addr_restore()
244{
245	local dev=
246
247	for ((i = 1; i <= NUM_NETIFS; ++i)); do
248		dev=${NETIFS[p$i]}
249		ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]}
250	done
251}
252
253if [[ "$NETIF_CREATE" = "yes" ]]; then
254	create_netif
255fi
256
257if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
258	mac_addr_prepare
259fi
260
261for ((i = 1; i <= NUM_NETIFS; ++i)); do
262	ip link show dev ${NETIFS[p$i]} &> /dev/null
263	if [[ $? -ne 0 ]]; then
264		echo "SKIP: could not find all required interfaces"
265		exit $ksft_skip
266	fi
267done
268
269##############################################################################
270# Helpers
271
272# Exit status to return at the end. Set in case one of the tests fails.
273EXIT_STATUS=0
274# Per-test return value. Clear at the beginning of each test.
275RET=0
276
277check_err()
278{
279	local err=$1
280	local msg=$2
281
282	if [[ $RET -eq 0 && $err -ne 0 ]]; then
283		RET=$err
284		retmsg=$msg
285	fi
286}
287
288check_fail()
289{
290	local err=$1
291	local msg=$2
292
293	if [[ $RET -eq 0 && $err -eq 0 ]]; then
294		RET=1
295		retmsg=$msg
296	fi
297}
298
299check_err_fail()
300{
301	local should_fail=$1; shift
302	local err=$1; shift
303	local what=$1; shift
304
305	if ((should_fail)); then
306		check_fail $err "$what succeeded, but should have failed"
307	else
308		check_err $err "$what failed"
309	fi
310}
311
312log_test()
313{
314	local test_name=$1
315	local opt_str=$2
316
317	if [[ $# -eq 2 ]]; then
318		opt_str="($opt_str)"
319	fi
320
321	if [[ $RET -ne 0 ]]; then
322		EXIT_STATUS=1
323		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
324		if [[ ! -z "$retmsg" ]]; then
325			printf "\t%s\n" "$retmsg"
326		fi
327		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
328			echo "Hit enter to continue, 'q' to quit"
329			read a
330			[ "$a" = "q" ] && exit 1
331		fi
332		return 1
333	fi
334
335	printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
336	return 0
337}
338
339log_test_skip()
340{
341	local test_name=$1
342	local opt_str=$2
343
344	printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
345	return 0
346}
347
348log_info()
349{
350	local msg=$1
351
352	echo "INFO: $msg"
353}
354
355busywait()
356{
357	local timeout=$1; shift
358
359	local start_time="$(date -u +%s%3N)"
360	while true
361	do
362		local out
363		out=$("$@")
364		local ret=$?
365		if ((!ret)); then
366			echo -n "$out"
367			return 0
368		fi
369
370		local current_time="$(date -u +%s%3N)"
371		if ((current_time - start_time > timeout)); then
372			echo -n "$out"
373			return 1
374		fi
375	done
376}
377
378not()
379{
380	"$@"
381	[[ $? != 0 ]]
382}
383
384get_max()
385{
386	local arr=("$@")
387
388	max=${arr[0]}
389	for cur in ${arr[@]}; do
390		if [[ $cur -gt $max ]]; then
391			max=$cur
392		fi
393	done
394
395	echo $max
396}
397
398grep_bridge_fdb()
399{
400	local addr=$1; shift
401	local word
402	local flag
403
404	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
405		word=$1; shift
406		if [ "$1" == "-v" ]; then
407			flag=$1; shift
408		fi
409	fi
410
411	$@ | grep $addr | grep $flag "$word"
412}
413
414wait_for_port_up()
415{
416	"$@" | grep -q "Link detected: yes"
417}
418
419wait_for_offload()
420{
421	"$@" | grep -q offload
422}
423
424wait_for_trap()
425{
426	"$@" | grep -q trap
427}
428
429until_counter_is()
430{
431	local expr=$1; shift
432	local current=$("$@")
433
434	echo $((current))
435	((current $expr))
436}
437
438busywait_for_counter()
439{
440	local timeout=$1; shift
441	local delta=$1; shift
442
443	local base=$("$@")
444	busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
445}
446
447setup_wait_dev()
448{
449	local dev=$1; shift
450	local wait_time=${1:-$WAIT_TIME}; shift
451
452	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
453
454	if (($?)); then
455		check_err 1
456		log_test setup_wait_dev ": Interface $dev does not come up."
457		exit 1
458	fi
459}
460
461setup_wait_dev_with_timeout()
462{
463	local dev=$1; shift
464	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
465	local wait_time=${1:-$WAIT_TIME}; shift
466	local i
467
468	for ((i = 1; i <= $max_iterations; ++i)); do
469		ip link show dev $dev up \
470			| grep 'state UP' &> /dev/null
471		if [[ $? -ne 0 ]]; then
472			sleep 1
473		else
474			sleep $wait_time
475			return 0
476		fi
477	done
478
479	return 1
480}
481
482setup_wait()
483{
484	local num_netifs=${1:-$NUM_NETIFS}
485	local i
486
487	for ((i = 1; i <= num_netifs; ++i)); do
488		setup_wait_dev ${NETIFS[p$i]} 0
489	done
490
491	# Make sure links are ready.
492	sleep $WAIT_TIME
493}
494
495cmd_jq()
496{
497	local cmd=$1
498	local jq_exp=$2
499	local jq_opts=$3
500	local ret
501	local output
502
503	output="$($cmd)"
504	# it the command fails, return error right away
505	ret=$?
506	if [[ $ret -ne 0 ]]; then
507		return $ret
508	fi
509	output=$(echo $output | jq -r $jq_opts "$jq_exp")
510	ret=$?
511	if [[ $ret -ne 0 ]]; then
512		return $ret
513	fi
514	echo $output
515	# return success only in case of non-empty output
516	[ ! -z "$output" ]
517}
518
519lldpad_app_wait_set()
520{
521	local dev=$1; shift
522
523	while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do
524		echo "$dev: waiting for lldpad to push pending APP updates"
525		sleep 5
526	done
527}
528
529lldpad_app_wait_del()
530{
531	# Give lldpad a chance to push down the changes. If the device is downed
532	# too soon, the updates will be left pending. However, they will have
533	# been struck off the lldpad's DB already, so we won't be able to tell
534	# they are pending. Then on next test iteration this would cause
535	# weirdness as newly-added APP rules conflict with the old ones,
536	# sometimes getting stuck in an "unknown" state.
537	sleep 5
538}
539
540pre_cleanup()
541{
542	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
543		echo "Pausing before cleanup, hit any key to continue"
544		read
545	fi
546
547	if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
548		mac_addr_restore
549	fi
550}
551
552vrf_prepare()
553{
554	ip -4 rule add pref 32765 table local
555	ip -4 rule del pref 0
556	ip -6 rule add pref 32765 table local
557	ip -6 rule del pref 0
558}
559
560vrf_cleanup()
561{
562	ip -6 rule add pref 0 table local
563	ip -6 rule del pref 32765
564	ip -4 rule add pref 0 table local
565	ip -4 rule del pref 32765
566}
567
568__last_tb_id=0
569declare -A __TB_IDS
570
571__vrf_td_id_assign()
572{
573	local vrf_name=$1
574
575	__last_tb_id=$((__last_tb_id + 1))
576	__TB_IDS[$vrf_name]=$__last_tb_id
577	return $__last_tb_id
578}
579
580__vrf_td_id_lookup()
581{
582	local vrf_name=$1
583
584	return ${__TB_IDS[$vrf_name]}
585}
586
587vrf_create()
588{
589	local vrf_name=$1
590	local tb_id
591
592	__vrf_td_id_assign $vrf_name
593	tb_id=$?
594
595	ip link add dev $vrf_name type vrf table $tb_id
596	ip -4 route add table $tb_id unreachable default metric 4278198272
597	ip -6 route add table $tb_id unreachable default metric 4278198272
598}
599
600vrf_destroy()
601{
602	local vrf_name=$1
603	local tb_id
604
605	__vrf_td_id_lookup $vrf_name
606	tb_id=$?
607
608	ip -6 route del table $tb_id unreachable default metric 4278198272
609	ip -4 route del table $tb_id unreachable default metric 4278198272
610	ip link del dev $vrf_name
611}
612
613__addr_add_del()
614{
615	local if_name=$1
616	local add_del=$2
617	local array
618
619	shift
620	shift
621	array=("${@}")
622
623	for addrstr in "${array[@]}"; do
624		ip address $add_del $addrstr dev $if_name
625	done
626}
627
628__simple_if_init()
629{
630	local if_name=$1; shift
631	local vrf_name=$1; shift
632	local addrs=("${@}")
633
634	ip link set dev $if_name master $vrf_name
635	ip link set dev $if_name up
636
637	__addr_add_del $if_name add "${addrs[@]}"
638}
639
640__simple_if_fini()
641{
642	local if_name=$1; shift
643	local addrs=("${@}")
644
645	__addr_add_del $if_name del "${addrs[@]}"
646
647	ip link set dev $if_name down
648	ip link set dev $if_name nomaster
649}
650
651simple_if_init()
652{
653	local if_name=$1
654	local vrf_name
655	local array
656
657	shift
658	vrf_name=v$if_name
659	array=("${@}")
660
661	vrf_create $vrf_name
662	ip link set dev $vrf_name up
663	__simple_if_init $if_name $vrf_name "${array[@]}"
664}
665
666simple_if_fini()
667{
668	local if_name=$1
669	local vrf_name
670	local array
671
672	shift
673	vrf_name=v$if_name
674	array=("${@}")
675
676	__simple_if_fini $if_name "${array[@]}"
677	vrf_destroy $vrf_name
678}
679
680tunnel_create()
681{
682	local name=$1; shift
683	local type=$1; shift
684	local local=$1; shift
685	local remote=$1; shift
686
687	ip link add name $name type $type \
688	   local $local remote $remote "$@"
689	ip link set dev $name up
690}
691
692tunnel_destroy()
693{
694	local name=$1; shift
695
696	ip link del dev $name
697}
698
699vlan_create()
700{
701	local if_name=$1; shift
702	local vid=$1; shift
703	local vrf=$1; shift
704	local ips=("${@}")
705	local name=$if_name.$vid
706
707	ip link add name $name link $if_name type vlan id $vid
708	if [ "$vrf" != "" ]; then
709		ip link set dev $name master $vrf
710	fi
711	ip link set dev $name up
712	__addr_add_del $name add "${ips[@]}"
713}
714
715vlan_destroy()
716{
717	local if_name=$1; shift
718	local vid=$1; shift
719	local name=$if_name.$vid
720
721	ip link del dev $name
722}
723
724team_create()
725{
726	local if_name=$1; shift
727	local mode=$1; shift
728
729	require_command $TEAMD
730	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
731	for slave in "$@"; do
732		ip link set dev $slave down
733		ip link set dev $slave master $if_name
734		ip link set dev $slave up
735	done
736	ip link set dev $if_name up
737}
738
739team_destroy()
740{
741	local if_name=$1; shift
742
743	$TEAMD -t $if_name -k
744}
745
746master_name_get()
747{
748	local if_name=$1
749
750	ip -j link show dev $if_name | jq -r '.[]["master"]'
751}
752
753link_stats_get()
754{
755	local if_name=$1; shift
756	local dir=$1; shift
757	local stat=$1; shift
758
759	ip -j -s link show dev $if_name \
760		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
761}
762
763link_stats_tx_packets_get()
764{
765	link_stats_get $1 tx packets
766}
767
768link_stats_rx_errors_get()
769{
770	link_stats_get $1 rx errors
771}
772
773tc_rule_stats_get()
774{
775	local dev=$1; shift
776	local pref=$1; shift
777	local dir=$1; shift
778	local selector=${1:-.packets}; shift
779
780	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
781	    | jq ".[1].options.actions[].stats$selector"
782}
783
784tc_rule_handle_stats_get()
785{
786	local id=$1; shift
787	local handle=$1; shift
788	local selector=${1:-.packets}; shift
789
790	tc -j -s filter show $id \
791	    | jq ".[] | select(.options.handle == $handle) | \
792		  .options.actions[0].stats$selector"
793}
794
795ethtool_stats_get()
796{
797	local dev=$1; shift
798	local stat=$1; shift
799
800	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
801}
802
803qdisc_stats_get()
804{
805	local dev=$1; shift
806	local handle=$1; shift
807	local selector=$1; shift
808
809	tc -j -s qdisc show dev "$dev" \
810	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
811}
812
813qdisc_parent_stats_get()
814{
815	local dev=$1; shift
816	local parent=$1; shift
817	local selector=$1; shift
818
819	tc -j -s qdisc show dev "$dev" invisible \
820	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
821}
822
823ipv6_stats_get()
824{
825	local dev=$1; shift
826	local stat=$1; shift
827
828	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
829}
830
831humanize()
832{
833	local speed=$1; shift
834
835	for unit in bps Kbps Mbps Gbps; do
836		if (($(echo "$speed < 1024" | bc))); then
837			break
838		fi
839
840		speed=$(echo "scale=1; $speed / 1024" | bc)
841	done
842
843	echo "$speed${unit}"
844}
845
846rate()
847{
848	local t0=$1; shift
849	local t1=$1; shift
850	local interval=$1; shift
851
852	echo $((8 * (t1 - t0) / interval))
853}
854
855packets_rate()
856{
857	local t0=$1; shift
858	local t1=$1; shift
859	local interval=$1; shift
860
861	echo $(((t1 - t0) / interval))
862}
863
864mac_get()
865{
866	local if_name=$1
867
868	ip -j link show dev $if_name | jq -r '.[]["address"]'
869}
870
871ipv6_lladdr_get()
872{
873	local if_name=$1
874
875	ip -j addr show dev $if_name | \
876		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
877		head -1
878}
879
880bridge_ageing_time_get()
881{
882	local bridge=$1
883	local ageing_time
884
885	# Need to divide by 100 to convert to seconds.
886	ageing_time=$(ip -j -d link show dev $bridge \
887		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
888	echo $((ageing_time / 100))
889}
890
891declare -A SYSCTL_ORIG
892sysctl_set()
893{
894	local key=$1; shift
895	local value=$1; shift
896
897	SYSCTL_ORIG[$key]=$(sysctl -n $key)
898	sysctl -qw $key=$value
899}
900
901sysctl_restore()
902{
903	local key=$1; shift
904
905	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
906}
907
908forwarding_enable()
909{
910	sysctl_set net.ipv4.conf.all.forwarding 1
911	sysctl_set net.ipv6.conf.all.forwarding 1
912}
913
914forwarding_restore()
915{
916	sysctl_restore net.ipv6.conf.all.forwarding
917	sysctl_restore net.ipv4.conf.all.forwarding
918}
919
920declare -A MTU_ORIG
921mtu_set()
922{
923	local dev=$1; shift
924	local mtu=$1; shift
925
926	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
927	ip link set dev $dev mtu $mtu
928}
929
930mtu_restore()
931{
932	local dev=$1; shift
933
934	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
935}
936
937tc_offload_check()
938{
939	local num_netifs=${1:-$NUM_NETIFS}
940
941	for ((i = 1; i <= num_netifs; ++i)); do
942		ethtool -k ${NETIFS[p$i]} \
943			| grep "hw-tc-offload: on" &> /dev/null
944		if [[ $? -ne 0 ]]; then
945			return 1
946		fi
947	done
948
949	return 0
950}
951
952trap_install()
953{
954	local dev=$1; shift
955	local direction=$1; shift
956
957	# Some devices may not support or need in-hardware trapping of traffic
958	# (e.g. the veth pairs that this library creates for non-existent
959	# loopbacks). Use continue instead, so that there is a filter in there
960	# (some tests check counters), and so that other filters are still
961	# processed.
962	tc filter add dev $dev $direction pref 1 \
963		flower skip_sw action trap 2>/dev/null \
964	    || tc filter add dev $dev $direction pref 1 \
965		       flower action continue
966}
967
968trap_uninstall()
969{
970	local dev=$1; shift
971	local direction=$1; shift
972
973	tc filter del dev $dev $direction pref 1 flower
974}
975
976slow_path_trap_install()
977{
978	# For slow-path testing, we need to install a trap to get to
979	# slow path the packets that would otherwise be switched in HW.
980	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
981		trap_install "$@"
982	fi
983}
984
985slow_path_trap_uninstall()
986{
987	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
988		trap_uninstall "$@"
989	fi
990}
991
992__icmp_capture_add_del()
993{
994	local add_del=$1; shift
995	local pref=$1; shift
996	local vsuf=$1; shift
997	local tundev=$1; shift
998	local filter=$1; shift
999
1000	tc filter $add_del dev "$tundev" ingress \
1001	   proto ip$vsuf pref $pref \
1002	   flower ip_proto icmp$vsuf $filter \
1003	   action pass
1004}
1005
1006icmp_capture_install()
1007{
1008	__icmp_capture_add_del add 100 "" "$@"
1009}
1010
1011icmp_capture_uninstall()
1012{
1013	__icmp_capture_add_del del 100 "" "$@"
1014}
1015
1016icmp6_capture_install()
1017{
1018	__icmp_capture_add_del add 100 v6 "$@"
1019}
1020
1021icmp6_capture_uninstall()
1022{
1023	__icmp_capture_add_del del 100 v6 "$@"
1024}
1025
1026__vlan_capture_add_del()
1027{
1028	local add_del=$1; shift
1029	local pref=$1; shift
1030	local dev=$1; shift
1031	local filter=$1; shift
1032
1033	tc filter $add_del dev "$dev" ingress \
1034	   proto 802.1q pref $pref \
1035	   flower $filter \
1036	   action pass
1037}
1038
1039vlan_capture_install()
1040{
1041	__vlan_capture_add_del add 100 "$@"
1042}
1043
1044vlan_capture_uninstall()
1045{
1046	__vlan_capture_add_del del 100 "$@"
1047}
1048
1049__dscp_capture_add_del()
1050{
1051	local add_del=$1; shift
1052	local dev=$1; shift
1053	local base=$1; shift
1054	local dscp;
1055
1056	for prio in {0..7}; do
1057		dscp=$((base + prio))
1058		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1059				       "skip_hw ip_tos $((dscp << 2))"
1060	done
1061}
1062
1063dscp_capture_install()
1064{
1065	local dev=$1; shift
1066	local base=$1; shift
1067
1068	__dscp_capture_add_del add $dev $base
1069}
1070
1071dscp_capture_uninstall()
1072{
1073	local dev=$1; shift
1074	local base=$1; shift
1075
1076	__dscp_capture_add_del del $dev $base
1077}
1078
1079dscp_fetch_stats()
1080{
1081	local dev=$1; shift
1082	local base=$1; shift
1083
1084	for prio in {0..7}; do
1085		local dscp=$((base + prio))
1086		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1087		echo "[$dscp]=$t "
1088	done
1089}
1090
1091matchall_sink_create()
1092{
1093	local dev=$1; shift
1094
1095	tc qdisc add dev $dev clsact
1096	tc filter add dev $dev ingress \
1097	   pref 10000 \
1098	   matchall \
1099	   action drop
1100}
1101
1102tests_run()
1103{
1104	local current_test
1105
1106	for current_test in ${TESTS:-$ALL_TESTS}; do
1107		$current_test
1108	done
1109}
1110
1111multipath_eval()
1112{
1113	local desc="$1"
1114	local weight_rp12=$2
1115	local weight_rp13=$3
1116	local packets_rp12=$4
1117	local packets_rp13=$5
1118	local weights_ratio packets_ratio diff
1119
1120	RET=0
1121
1122	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1123		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1124				| bc -l)
1125	else
1126		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1127				| bc -l)
1128	fi
1129
1130	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1131	       check_err 1 "Packet difference is 0"
1132	       log_test "Multipath"
1133	       log_info "Expected ratio $weights_ratio"
1134	       return
1135	fi
1136
1137	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1138		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1139				| bc -l)
1140	else
1141		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1142				| bc -l)
1143	fi
1144
1145	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1146	diff=${diff#-}
1147
1148	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1149	check_err $? "Too large discrepancy between expected and measured ratios"
1150	log_test "$desc"
1151	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1152}
1153
1154in_ns()
1155{
1156	local name=$1; shift
1157
1158	ip netns exec $name bash <<-EOF
1159		NUM_NETIFS=0
1160		source lib.sh
1161		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1162	EOF
1163}
1164
1165##############################################################################
1166# Tests
1167
1168ping_do()
1169{
1170	local if_name=$1
1171	local dip=$2
1172	local args=$3
1173	local vrf_name
1174
1175	vrf_name=$(master_name_get $if_name)
1176	ip vrf exec $vrf_name \
1177		$PING $args $dip -c $PING_COUNT -i 0.1 \
1178		-w $PING_TIMEOUT &> /dev/null
1179}
1180
1181ping_test()
1182{
1183	RET=0
1184
1185	ping_do $1 $2
1186	check_err $?
1187	log_test "ping$3"
1188}
1189
1190ping6_do()
1191{
1192	local if_name=$1
1193	local dip=$2
1194	local args=$3
1195	local vrf_name
1196
1197	vrf_name=$(master_name_get $if_name)
1198	ip vrf exec $vrf_name \
1199		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1200		-w $PING_TIMEOUT &> /dev/null
1201}
1202
1203ping6_test()
1204{
1205	RET=0
1206
1207	ping6_do $1 $2
1208	check_err $?
1209	log_test "ping6$3"
1210}
1211
1212learning_test()
1213{
1214	local bridge=$1
1215	local br_port1=$2	# Connected to `host1_if`.
1216	local host1_if=$3
1217	local host2_if=$4
1218	local mac=de:ad:be:ef:13:37
1219	local ageing_time
1220
1221	RET=0
1222
1223	bridge -j fdb show br $bridge brport $br_port1 \
1224		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1225	check_fail $? "Found FDB record when should not"
1226
1227	# Disable unknown unicast flooding on `br_port1` to make sure
1228	# packets are only forwarded through the port after a matching
1229	# FDB entry was installed.
1230	bridge link set dev $br_port1 flood off
1231
1232	tc qdisc add dev $host1_if ingress
1233	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1234		flower dst_mac $mac action drop
1235
1236	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1237	sleep 1
1238
1239	tc -j -s filter show dev $host1_if ingress \
1240		| jq -e ".[] | select(.options.handle == 101) \
1241		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1242	check_fail $? "Packet reached second host when should not"
1243
1244	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1245	sleep 1
1246
1247	bridge -j fdb show br $bridge brport $br_port1 \
1248		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1249	check_err $? "Did not find FDB record when should"
1250
1251	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1252	sleep 1
1253
1254	tc -j -s filter show dev $host1_if ingress \
1255		| jq -e ".[] | select(.options.handle == 101) \
1256		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1257	check_err $? "Packet did not reach second host when should"
1258
1259	# Wait for 10 seconds after the ageing time to make sure FDB
1260	# record was aged-out.
1261	ageing_time=$(bridge_ageing_time_get $bridge)
1262	sleep $((ageing_time + 10))
1263
1264	bridge -j fdb show br $bridge brport $br_port1 \
1265		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1266	check_fail $? "Found FDB record when should not"
1267
1268	bridge link set dev $br_port1 learning off
1269
1270	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1271	sleep 1
1272
1273	bridge -j fdb show br $bridge brport $br_port1 \
1274		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1275	check_fail $? "Found FDB record when should not"
1276
1277	bridge link set dev $br_port1 learning on
1278
1279	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1280	tc qdisc del dev $host1_if ingress
1281
1282	bridge link set dev $br_port1 flood on
1283
1284	log_test "FDB learning"
1285}
1286
1287flood_test_do()
1288{
1289	local should_flood=$1
1290	local mac=$2
1291	local ip=$3
1292	local host1_if=$4
1293	local host2_if=$5
1294	local err=0
1295
1296	# Add an ACL on `host2_if` which will tell us whether the packet
1297	# was flooded to it or not.
1298	tc qdisc add dev $host2_if ingress
1299	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1300		flower dst_mac $mac action drop
1301
1302	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1303	sleep 1
1304
1305	tc -j -s filter show dev $host2_if ingress \
1306		| jq -e ".[] | select(.options.handle == 101) \
1307		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1308	if [[ $? -ne 0 && $should_flood == "true" || \
1309	      $? -eq 0 && $should_flood == "false" ]]; then
1310		err=1
1311	fi
1312
1313	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1314	tc qdisc del dev $host2_if ingress
1315
1316	return $err
1317}
1318
1319flood_unicast_test()
1320{
1321	local br_port=$1
1322	local host1_if=$2
1323	local host2_if=$3
1324	local mac=de:ad:be:ef:13:37
1325	local ip=192.0.2.100
1326
1327	RET=0
1328
1329	bridge link set dev $br_port flood off
1330
1331	flood_test_do false $mac $ip $host1_if $host2_if
1332	check_err $? "Packet flooded when should not"
1333
1334	bridge link set dev $br_port flood on
1335
1336	flood_test_do true $mac $ip $host1_if $host2_if
1337	check_err $? "Packet was not flooded when should"
1338
1339	log_test "Unknown unicast flood"
1340}
1341
1342flood_multicast_test()
1343{
1344	local br_port=$1
1345	local host1_if=$2
1346	local host2_if=$3
1347	local mac=01:00:5e:00:00:01
1348	local ip=239.0.0.1
1349
1350	RET=0
1351
1352	bridge link set dev $br_port mcast_flood off
1353
1354	flood_test_do false $mac $ip $host1_if $host2_if
1355	check_err $? "Packet flooded when should not"
1356
1357	bridge link set dev $br_port mcast_flood on
1358
1359	flood_test_do true $mac $ip $host1_if $host2_if
1360	check_err $? "Packet was not flooded when should"
1361
1362	log_test "Unregistered multicast flood"
1363}
1364
1365flood_test()
1366{
1367	# `br_port` is connected to `host2_if`
1368	local br_port=$1
1369	local host1_if=$2
1370	local host2_if=$3
1371
1372	flood_unicast_test $br_port $host1_if $host2_if
1373	flood_multicast_test $br_port $host1_if $host2_if
1374}
1375
1376__start_traffic()
1377{
1378	local proto=$1; shift
1379	local h_in=$1; shift    # Where the traffic egresses the host
1380	local sip=$1; shift
1381	local dip=$1; shift
1382	local dmac=$1; shift
1383
1384	$MZ $h_in -p 8000 -A $sip -B $dip -c 0 \
1385		-a own -b $dmac -t "$proto" -q "$@" &
1386	sleep 1
1387}
1388
1389start_traffic()
1390{
1391	__start_traffic udp "$@"
1392}
1393
1394start_tcp_traffic()
1395{
1396	__start_traffic tcp "$@"
1397}
1398
1399stop_traffic()
1400{
1401	# Suppress noise from killing mausezahn.
1402	{ kill %% && wait %%; } 2>/dev/null
1403}
1404
1405declare -A cappid
1406declare -A capfile
1407declare -A capout
1408
1409tcpdump_start()
1410{
1411	local if_name=$1; shift
1412	local ns=$1; shift
1413
1414	capfile[$if_name]=$(mktemp)
1415	capout[$if_name]=$(mktemp)
1416
1417	if [ -z $ns ]; then
1418		ns_cmd=""
1419	else
1420		ns_cmd="ip netns exec ${ns}"
1421	fi
1422
1423	if [ -z $SUDO_USER ] ; then
1424		capuser=""
1425	else
1426		capuser="-Z $SUDO_USER"
1427	fi
1428
1429	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1430		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1431		> "${capout[$if_name]}" 2>&1 &
1432	cappid[$if_name]=$!
1433
1434	sleep 1
1435}
1436
1437tcpdump_stop()
1438{
1439	local if_name=$1
1440	local pid=${cappid[$if_name]}
1441
1442	$ns_cmd kill "$pid" && wait "$pid"
1443	sleep 1
1444}
1445
1446tcpdump_cleanup()
1447{
1448	local if_name=$1
1449
1450	rm ${capfile[$if_name]} ${capout[$if_name]}
1451}
1452
1453tcpdump_show()
1454{
1455	local if_name=$1
1456
1457	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1458}
1459
1460# return 0 if the packet wasn't seen on host2_if or 1 if it was
1461mcast_packet_test()
1462{
1463	local mac=$1
1464	local src_ip=$2
1465	local ip=$3
1466	local host1_if=$4
1467	local host2_if=$5
1468	local seen=0
1469	local tc_proto="ip"
1470	local mz_v6arg=""
1471
1472	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1473	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1474		tc_proto="ipv6"
1475		mz_v6arg="-6"
1476	fi
1477
1478	# Add an ACL on `host2_if` which will tell us whether the packet
1479	# was received by it or not.
1480	tc qdisc add dev $host2_if ingress
1481	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1482		flower ip_proto udp dst_mac $mac action drop
1483
1484	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1485	sleep 1
1486
1487	tc -j -s filter show dev $host2_if ingress \
1488		| jq -e ".[] | select(.options.handle == 101) \
1489		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1490	if [[ $? -eq 0 ]]; then
1491		seen=1
1492	fi
1493
1494	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1495	tc qdisc del dev $host2_if ingress
1496
1497	return $seen
1498}
1499
1500brmcast_check_sg_entries()
1501{
1502	local report=$1; shift
1503	local slist=("$@")
1504	local sarg=""
1505
1506	for src in "${slist[@]}"; do
1507		sarg="${sarg} and .source_list[].address == \"$src\""
1508	done
1509	bridge -j -d -s mdb show dev br0 \
1510		| jq -e ".[].mdb[] | \
1511			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1512	check_err $? "Wrong *,G entry source list after $report report"
1513
1514	for sgent in "${slist[@]}"; do
1515		bridge -j -d -s mdb show dev br0 \
1516			| jq -e ".[].mdb[] | \
1517				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1518		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1519	done
1520}
1521
1522brmcast_check_sg_fwding()
1523{
1524	local should_fwd=$1; shift
1525	local sources=("$@")
1526
1527	for src in "${sources[@]}"; do
1528		local retval=0
1529
1530		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1531		retval=$?
1532		if [ $should_fwd -eq 1 ]; then
1533			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1534		else
1535			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1536		fi
1537	done
1538}
1539
1540brmcast_check_sg_state()
1541{
1542	local is_blocked=$1; shift
1543	local sources=("$@")
1544	local should_fail=1
1545
1546	if [ $is_blocked -eq 1 ]; then
1547		should_fail=0
1548	fi
1549
1550	for src in "${sources[@]}"; do
1551		bridge -j -d -s mdb show dev br0 \
1552			| jq -e ".[].mdb[] | \
1553				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1554				 .source_list[] |
1555				 select(.address == \"$src\") |
1556				 select(.timer == \"0.00\")" &>/dev/null
1557		check_err_fail $should_fail $? "Entry $src has zero timer"
1558
1559		bridge -j -d -s mdb show dev br0 \
1560			| jq -e ".[].mdb[] | \
1561				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1562				 .flags[] == \"blocked\")" &>/dev/null
1563		check_err_fail $should_fail $? "Entry $src has blocked flag"
1564	done
1565}
1566
1567mc_join()
1568{
1569	local if_name=$1
1570	local group=$2
1571	local vrf_name=$(master_name_get $if_name)
1572
1573	# We don't care about actual reception, just about joining the
1574	# IP multicast group and adding the L2 address to the device's
1575	# MAC filtering table
1576	ip vrf exec $vrf_name \
1577		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1578	mreceive_pid=$!
1579
1580	sleep 1
1581}
1582
1583mc_leave()
1584{
1585	kill "$mreceive_pid" && wait "$mreceive_pid"
1586}
1587
1588mc_send()
1589{
1590	local if_name=$1
1591	local groups=$2
1592	local vrf_name=$(master_name_get $if_name)
1593
1594	ip vrf exec $vrf_name \
1595		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1596}
1597
1598start_ip_monitor()
1599{
1600	local mtype=$1; shift
1601	local ip=${1-ip}; shift
1602
1603	# start the monitor in the background
1604	tmpfile=`mktemp /var/run/nexthoptestXXX`
1605	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1606	sleep 0.2
1607	echo "$mpid $tmpfile"
1608}
1609
1610stop_ip_monitor()
1611{
1612	local mpid=$1; shift
1613	local tmpfile=$1; shift
1614	local el=$1; shift
1615	local what=$1; shift
1616
1617	sleep 0.2
1618	kill $mpid
1619	local lines=`grep '^\w' $tmpfile | wc -l`
1620	test $lines -eq $el
1621	check_err $? "$what: $lines lines of events, expected $el"
1622	rm -rf $tmpfile
1623}
1624
1625hw_stats_monitor_test()
1626{
1627	local dev=$1; shift
1628	local type=$1; shift
1629	local make_suitable=$1; shift
1630	local make_unsuitable=$1; shift
1631	local ip=${1-ip}; shift
1632
1633	RET=0
1634
1635	# Expect a notification about enablement.
1636	local ipmout=$(start_ip_monitor stats "$ip")
1637	$ip stats set dev $dev ${type}_stats on
1638	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1639
1640	# Expect a notification about offload.
1641	local ipmout=$(start_ip_monitor stats "$ip")
1642	$make_suitable
1643	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1644
1645	# Expect a notification about loss of offload.
1646	local ipmout=$(start_ip_monitor stats "$ip")
1647	$make_unsuitable
1648	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1649
1650	# Expect a notification about disablement
1651	local ipmout=$(start_ip_monitor stats "$ip")
1652	$ip stats set dev $dev ${type}_stats off
1653	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1654
1655	log_test "${type}_stats notifications"
1656}
1657