xref: /linux/tools/testing/selftests/net/forwarding/lib.sh (revision 48ba00da2eb4b54a7e6ed2ca3a9f2e575dff48c9)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Topology description. p1 looped back to p2, p3 to p4 and so on.
6
7declare -A NETIFS=(
8    [p1]=veth0
9    [p2]=veth1
10    [p3]=veth2
11    [p4]=veth3
12    [p5]=veth4
13    [p6]=veth5
14    [p7]=veth6
15    [p8]=veth7
16    [p9]=veth8
17    [p10]=veth9
18)
19
20# Port that does not have a cable connected.
21: "${NETIF_NO_CABLE:=eth8}"
22
23##############################################################################
24# Defines
25
26# Networking utilities.
27: "${PING:=ping}"
28: "${PING6:=ping6}"	# Some distros just use ping.
29: "${ARPING:=arping}"
30: "${TROUTE6:=traceroute6}"
31
32# Packet generator.
33: "${MZ:=mausezahn}"	# Some distributions use 'mz'.
34: "${MZ_DELAY:=0}"
35
36# Host configuration tools.
37: "${TEAMD:=teamd}"
38: "${MCD:=smcrouted}"
39: "${MC_CLI:=smcroutectl}"
40
41# Constants for netdevice bring-up:
42# Default time in seconds to wait for an interface to come up before giving up
43# and bailing out. Used during initial setup.
44: "${INTERFACE_TIMEOUT:=600}"
45# Like INTERFACE_TIMEOUT, but default for ad-hoc waiting in testing scripts.
46: "${WAIT_TIMEOUT:=20}"
47# Time to wait after interfaces participating in the test are all UP.
48: "${WAIT_TIME:=5}"
49
50# Whether to pause on, respectively, after a failure and before cleanup.
51: "${PAUSE_ON_FAIL:=no}"
52: "${PAUSE_ON_CLEANUP:=no}"
53
54# Whether to create virtual interfaces, and what netdevice type they should be.
55: "${NETIF_CREATE:=yes}"
56: "${NETIF_TYPE:=veth}"
57
58# Constants for ping tests:
59# How many packets should be sent.
60: "${PING_COUNT:=10}"
61# Timeout (in seconds) before ping exits regardless of how many packets have
62# been sent or received
63: "${PING_TIMEOUT:=5}"
64
65# Minimum ageing_time (in centiseconds) supported by hardware
66: "${LOW_AGEING_TIME:=1000}"
67
68# Whether to check for availability of certain tools.
69: "${REQUIRE_JQ:=yes}"
70: "${REQUIRE_MZ:=yes}"
71: "${REQUIRE_MTOOLS:=no}"
72
73# Whether to override MAC addresses on interfaces participating in the test.
74: "${STABLE_MAC_ADDRS:=no}"
75
76# Flags for tcpdump
77: "${TCPDUMP_EXTRA_FLAGS:=}"
78
79# Flags for TC filters.
80: "${TC_FLAG:=skip_hw}"
81
82# Whether the machine is "slow" -- i.e. might be incapable of running tests
83# involving heavy traffic. This might be the case on a debug kernel, a VM, or
84# e.g. a low-power board.
85: "${KSFT_MACHINE_SLOW:=no}"
86
87net_forwarding_dir=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")
88
89if [[ -f $net_forwarding_dir/forwarding.config ]]; then
90	source "$net_forwarding_dir/forwarding.config"
91fi
92
93source "$net_forwarding_dir/../lib.sh"
94
95# timeout in seconds
96slowwait()
97{
98	local timeout=$1; shift
99
100	local start_time="$(date -u +%s)"
101	while true
102	do
103		local out
104		out=$("$@")
105		local ret=$?
106		if ((!ret)); then
107			echo -n "$out"
108			return 0
109		fi
110
111		local current_time="$(date -u +%s)"
112		if ((current_time - start_time > timeout)); then
113			echo -n "$out"
114			return 1
115		fi
116
117		sleep 0.1
118	done
119}
120
121##############################################################################
122# Sanity checks
123
124check_tc_version()
125{
126	tc -j &> /dev/null
127	if [[ $? -ne 0 ]]; then
128		echo "SKIP: iproute2 too old; tc is missing JSON support"
129		exit $ksft_skip
130	fi
131}
132
133# Old versions of tc don't understand "mpls_uc"
134check_tc_mpls_support()
135{
136	local dev=$1; shift
137
138	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
139		matchall action pipe &> /dev/null
140	if [[ $? -ne 0 ]]; then
141		echo "SKIP: iproute2 too old; tc is missing MPLS support"
142		return $ksft_skip
143	fi
144	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
145		matchall
146}
147
148# Old versions of tc produce invalid json output for mpls lse statistics
149check_tc_mpls_lse_stats()
150{
151	local dev=$1; shift
152	local ret;
153
154	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
155		flower mpls lse depth 2                                 \
156		action continue &> /dev/null
157
158	if [[ $? -ne 0 ]]; then
159		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
160		return $ksft_skip
161	fi
162
163	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
164	ret=$?
165	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
166		flower
167
168	if [[ $ret -ne 0 ]]; then
169		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
170		return $ksft_skip
171	fi
172}
173
174check_tc_shblock_support()
175{
176	tc filter help 2>&1 | grep block &> /dev/null
177	if [[ $? -ne 0 ]]; then
178		echo "SKIP: iproute2 too old; tc is missing shared block support"
179		exit $ksft_skip
180	fi
181}
182
183check_tc_chain_support()
184{
185	tc help 2>&1|grep chain &> /dev/null
186	if [[ $? -ne 0 ]]; then
187		echo "SKIP: iproute2 too old; tc is missing chain support"
188		exit $ksft_skip
189	fi
190}
191
192check_tc_action_hw_stats_support()
193{
194	tc actions help 2>&1 | grep -q hw_stats
195	if [[ $? -ne 0 ]]; then
196		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
197		exit $ksft_skip
198	fi
199}
200
201check_tc_fp_support()
202{
203	tc qdisc add dev lo mqprio help 2>&1 | grep -q "fp "
204	if [[ $? -ne 0 ]]; then
205		echo "SKIP: iproute2 too old; tc is missing frame preemption support"
206		exit $ksft_skip
207	fi
208}
209
210check_ethtool_lanes_support()
211{
212	ethtool --help 2>&1| grep lanes &> /dev/null
213	if [[ $? -ne 0 ]]; then
214		echo "SKIP: ethtool too old; it is missing lanes support"
215		exit $ksft_skip
216	fi
217}
218
219check_ethtool_mm_support()
220{
221	ethtool --help 2>&1| grep -- '--show-mm' &> /dev/null
222	if [[ $? -ne 0 ]]; then
223		echo "SKIP: ethtool too old; it is missing MAC Merge layer support"
224		exit $ksft_skip
225	fi
226}
227
228check_ethtool_counter_group_support()
229{
230	ethtool --help 2>&1| grep -- '--all-groups' &> /dev/null
231	if [[ $? -ne 0 ]]; then
232		echo "SKIP: ethtool too old; it is missing standard counter group support"
233		exit $ksft_skip
234	fi
235}
236
237check_ethtool_pmac_std_stats_support()
238{
239	local dev=$1; shift
240	local grp=$1; shift
241
242	[ 0 -ne $(ethtool --json -S $dev --all-groups --src pmac 2>/dev/null \
243		| jq ".[].\"$grp\" | length") ]
244}
245
246check_locked_port_support()
247{
248	if ! bridge -d link show | grep -q " locked"; then
249		echo "SKIP: iproute2 too old; Locked port feature not supported."
250		return $ksft_skip
251	fi
252}
253
254check_port_mab_support()
255{
256	if ! bridge -d link show | grep -q "mab"; then
257		echo "SKIP: iproute2 too old; MacAuth feature not supported."
258		return $ksft_skip
259	fi
260}
261
262if [[ "$(id -u)" -ne 0 ]]; then
263	echo "SKIP: need root privileges"
264	exit $ksft_skip
265fi
266
267if [[ "$CHECK_TC" = "yes" ]]; then
268	check_tc_version
269fi
270
271require_command()
272{
273	local cmd=$1; shift
274
275	if [[ ! -x "$(command -v "$cmd")" ]]; then
276		echo "SKIP: $cmd not installed"
277		exit $ksft_skip
278	fi
279}
280
281if [[ "$REQUIRE_JQ" = "yes" ]]; then
282	require_command jq
283fi
284if [[ "$REQUIRE_MZ" = "yes" ]]; then
285	require_command $MZ
286fi
287if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then
288	# https://github.com/vladimiroltean/mtools/
289	# patched for IPv6 support
290	require_command msend
291	require_command mreceive
292fi
293
294if [[ ! -v NUM_NETIFS ]]; then
295	echo "SKIP: importer does not define \"NUM_NETIFS\""
296	exit $ksft_skip
297fi
298
299##############################################################################
300# Command line options handling
301
302count=0
303
304while [[ $# -gt 0 ]]; do
305	if [[ "$count" -eq "0" ]]; then
306		unset NETIFS
307		declare -A NETIFS
308	fi
309	count=$((count + 1))
310	NETIFS[p$count]="$1"
311	shift
312done
313
314##############################################################################
315# Network interfaces configuration
316
317create_netif_veth()
318{
319	local i
320
321	for ((i = 1; i <= NUM_NETIFS; ++i)); do
322		local j=$((i+1))
323
324		if [ -z ${NETIFS[p$i]} ]; then
325			echo "SKIP: Cannot create interface. Name not specified"
326			exit $ksft_skip
327		fi
328
329		ip link show dev ${NETIFS[p$i]} &> /dev/null
330		if [[ $? -ne 0 ]]; then
331			ip link add ${NETIFS[p$i]} type veth \
332				peer name ${NETIFS[p$j]}
333			if [[ $? -ne 0 ]]; then
334				echo "Failed to create netif"
335				exit 1
336			fi
337		fi
338		i=$j
339	done
340}
341
342create_netif()
343{
344	case "$NETIF_TYPE" in
345	veth) create_netif_veth
346	      ;;
347	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
348	   exit 1
349	   ;;
350	esac
351}
352
353declare -A MAC_ADDR_ORIG
354mac_addr_prepare()
355{
356	local new_addr=
357	local dev=
358
359	for ((i = 1; i <= NUM_NETIFS; ++i)); do
360		dev=${NETIFS[p$i]}
361		new_addr=$(printf "00:01:02:03:04:%02x" $i)
362
363		MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address')
364		# Strip quotes
365		MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/}
366		ip link set dev $dev address $new_addr
367	done
368}
369
370mac_addr_restore()
371{
372	local dev=
373
374	for ((i = 1; i <= NUM_NETIFS; ++i)); do
375		dev=${NETIFS[p$i]}
376		ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]}
377	done
378}
379
380if [[ "$NETIF_CREATE" = "yes" ]]; then
381	create_netif
382fi
383
384if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
385	mac_addr_prepare
386fi
387
388for ((i = 1; i <= NUM_NETIFS; ++i)); do
389	ip link show dev ${NETIFS[p$i]} &> /dev/null
390	if [[ $? -ne 0 ]]; then
391		echo "SKIP: could not find all required interfaces"
392		exit $ksft_skip
393	fi
394done
395
396##############################################################################
397# Helpers
398
399# Exit status to return at the end. Set in case one of the tests fails.
400EXIT_STATUS=0
401# Per-test return value. Clear at the beginning of each test.
402RET=0
403
404ret_set_ksft_status()
405{
406	local ksft_status=$1; shift
407	local msg=$1; shift
408
409	RET=$(ksft_status_merge $RET $ksft_status)
410	if (( $? )); then
411		retmsg=$msg
412	fi
413}
414
415# Whether FAILs should be interpreted as XFAILs. Internal.
416FAIL_TO_XFAIL=
417
418check_err()
419{
420	local err=$1
421	local msg=$2
422
423	if ((err)); then
424		if [[ $FAIL_TO_XFAIL = yes ]]; then
425			ret_set_ksft_status $ksft_xfail "$msg"
426		else
427			ret_set_ksft_status $ksft_fail "$msg"
428		fi
429	fi
430}
431
432check_fail()
433{
434	local err=$1
435	local msg=$2
436
437	check_err $((!err)) "$msg"
438}
439
440check_err_fail()
441{
442	local should_fail=$1; shift
443	local err=$1; shift
444	local what=$1; shift
445
446	if ((should_fail)); then
447		check_fail $err "$what succeeded, but should have failed"
448	else
449		check_err $err "$what failed"
450	fi
451}
452
453xfail_on_slow()
454{
455	if [[ $KSFT_MACHINE_SLOW = yes ]]; then
456		FAIL_TO_XFAIL=yes "$@"
457	else
458		"$@"
459	fi
460}
461
462xfail_on_veth()
463{
464	local dev=$1; shift
465	local kind
466
467	kind=$(ip -j -d link show dev $dev |
468			jq -r '.[].linkinfo.info_kind')
469	if [[ $kind = veth ]]; then
470		FAIL_TO_XFAIL=yes "$@"
471	else
472		"$@"
473	fi
474}
475
476log_test_result()
477{
478	local test_name=$1; shift
479	local opt_str=$1; shift
480	local result=$1; shift
481	local retmsg=$1; shift
482
483	printf "TEST: %-60s  [%s]\n" "$test_name $opt_str" "$result"
484	if [[ $retmsg ]]; then
485		printf "\t%s\n" "$retmsg"
486	fi
487}
488
489pause_on_fail()
490{
491	if [[ $PAUSE_ON_FAIL == yes ]]; then
492		echo "Hit enter to continue, 'q' to quit"
493		read a
494		[[ $a == q ]] && exit 1
495	fi
496}
497
498handle_test_result_pass()
499{
500	local test_name=$1; shift
501	local opt_str=$1; shift
502
503	log_test_result "$test_name" "$opt_str" " OK "
504}
505
506handle_test_result_fail()
507{
508	local test_name=$1; shift
509	local opt_str=$1; shift
510
511	log_test_result "$test_name" "$opt_str" FAIL "$retmsg"
512	pause_on_fail
513}
514
515handle_test_result_xfail()
516{
517	local test_name=$1; shift
518	local opt_str=$1; shift
519
520	log_test_result "$test_name" "$opt_str" XFAIL "$retmsg"
521	pause_on_fail
522}
523
524handle_test_result_skip()
525{
526	local test_name=$1; shift
527	local opt_str=$1; shift
528
529	log_test_result "$test_name" "$opt_str" SKIP "$retmsg"
530}
531
532log_test()
533{
534	local test_name=$1
535	local opt_str=$2
536
537	if [[ $# -eq 2 ]]; then
538		opt_str="($opt_str)"
539	fi
540
541	if ((RET == ksft_pass)); then
542		handle_test_result_pass "$test_name" "$opt_str"
543	elif ((RET == ksft_xfail)); then
544		handle_test_result_xfail "$test_name" "$opt_str"
545	elif ((RET == ksft_skip)); then
546		handle_test_result_skip "$test_name" "$opt_str"
547	else
548		handle_test_result_fail "$test_name" "$opt_str"
549	fi
550
551	EXIT_STATUS=$(ksft_exit_status_merge $EXIT_STATUS $RET)
552	return $RET
553}
554
555log_test_skip()
556{
557	RET=$ksft_skip retmsg= log_test "$@"
558}
559
560log_test_xfail()
561{
562	RET=$ksft_xfail retmsg= log_test "$@"
563}
564
565log_info()
566{
567	local msg=$1
568
569	echo "INFO: $msg"
570}
571
572not()
573{
574	"$@"
575	[[ $? != 0 ]]
576}
577
578get_max()
579{
580	local arr=("$@")
581
582	max=${arr[0]}
583	for cur in ${arr[@]}; do
584		if [[ $cur -gt $max ]]; then
585			max=$cur
586		fi
587	done
588
589	echo $max
590}
591
592grep_bridge_fdb()
593{
594	local addr=$1; shift
595	local word
596	local flag
597
598	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
599		word=$1; shift
600		if [ "$1" == "-v" ]; then
601			flag=$1; shift
602		fi
603	fi
604
605	$@ | grep $addr | grep $flag "$word"
606}
607
608wait_for_port_up()
609{
610	"$@" | grep -q "Link detected: yes"
611}
612
613wait_for_offload()
614{
615	"$@" | grep -q offload
616}
617
618wait_for_trap()
619{
620	"$@" | grep -q trap
621}
622
623until_counter_is()
624{
625	local expr=$1; shift
626	local current=$("$@")
627
628	echo $((current))
629	((current $expr))
630}
631
632busywait_for_counter()
633{
634	local timeout=$1; shift
635	local delta=$1; shift
636
637	local base=$("$@")
638	busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
639}
640
641slowwait_for_counter()
642{
643	local timeout=$1; shift
644	local delta=$1; shift
645
646	local base=$("$@")
647	slowwait "$timeout" until_counter_is ">= $((base + delta))" "$@"
648}
649
650setup_wait_dev()
651{
652	local dev=$1; shift
653	local wait_time=${1:-$WAIT_TIME}; shift
654
655	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
656
657	if (($?)); then
658		check_err 1
659		log_test setup_wait_dev ": Interface $dev does not come up."
660		exit 1
661	fi
662}
663
664setup_wait_dev_with_timeout()
665{
666	local dev=$1; shift
667	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
668	local wait_time=${1:-$WAIT_TIME}; shift
669	local i
670
671	for ((i = 1; i <= $max_iterations; ++i)); do
672		ip link show dev $dev up \
673			| grep 'state UP' &> /dev/null
674		if [[ $? -ne 0 ]]; then
675			sleep 1
676		else
677			sleep $wait_time
678			return 0
679		fi
680	done
681
682	return 1
683}
684
685setup_wait()
686{
687	local num_netifs=${1:-$NUM_NETIFS}
688	local i
689
690	for ((i = 1; i <= num_netifs; ++i)); do
691		setup_wait_dev ${NETIFS[p$i]} 0
692	done
693
694	# Make sure links are ready.
695	sleep $WAIT_TIME
696}
697
698cmd_jq()
699{
700	local cmd=$1
701	local jq_exp=$2
702	local jq_opts=$3
703	local ret
704	local output
705
706	output="$($cmd)"
707	# it the command fails, return error right away
708	ret=$?
709	if [[ $ret -ne 0 ]]; then
710		return $ret
711	fi
712	output=$(echo $output | jq -r $jq_opts "$jq_exp")
713	ret=$?
714	if [[ $ret -ne 0 ]]; then
715		return $ret
716	fi
717	echo $output
718	# return success only in case of non-empty output
719	[ ! -z "$output" ]
720}
721
722pre_cleanup()
723{
724	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
725		echo "Pausing before cleanup, hit any key to continue"
726		read
727	fi
728
729	if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
730		mac_addr_restore
731	fi
732}
733
734vrf_prepare()
735{
736	ip -4 rule add pref 32765 table local
737	ip -4 rule del pref 0
738	ip -6 rule add pref 32765 table local
739	ip -6 rule del pref 0
740}
741
742vrf_cleanup()
743{
744	ip -6 rule add pref 0 table local
745	ip -6 rule del pref 32765
746	ip -4 rule add pref 0 table local
747	ip -4 rule del pref 32765
748}
749
750__last_tb_id=0
751declare -A __TB_IDS
752
753__vrf_td_id_assign()
754{
755	local vrf_name=$1
756
757	__last_tb_id=$((__last_tb_id + 1))
758	__TB_IDS[$vrf_name]=$__last_tb_id
759	return $__last_tb_id
760}
761
762__vrf_td_id_lookup()
763{
764	local vrf_name=$1
765
766	return ${__TB_IDS[$vrf_name]}
767}
768
769vrf_create()
770{
771	local vrf_name=$1
772	local tb_id
773
774	__vrf_td_id_assign $vrf_name
775	tb_id=$?
776
777	ip link add dev $vrf_name type vrf table $tb_id
778	ip -4 route add table $tb_id unreachable default metric 4278198272
779	ip -6 route add table $tb_id unreachable default metric 4278198272
780}
781
782vrf_destroy()
783{
784	local vrf_name=$1
785	local tb_id
786
787	__vrf_td_id_lookup $vrf_name
788	tb_id=$?
789
790	ip -6 route del table $tb_id unreachable default metric 4278198272
791	ip -4 route del table $tb_id unreachable default metric 4278198272
792	ip link del dev $vrf_name
793}
794
795__addr_add_del()
796{
797	local if_name=$1
798	local add_del=$2
799	local array
800
801	shift
802	shift
803	array=("${@}")
804
805	for addrstr in "${array[@]}"; do
806		ip address $add_del $addrstr dev $if_name
807	done
808}
809
810__simple_if_init()
811{
812	local if_name=$1; shift
813	local vrf_name=$1; shift
814	local addrs=("${@}")
815
816	ip link set dev $if_name master $vrf_name
817	ip link set dev $if_name up
818
819	__addr_add_del $if_name add "${addrs[@]}"
820}
821
822__simple_if_fini()
823{
824	local if_name=$1; shift
825	local addrs=("${@}")
826
827	__addr_add_del $if_name del "${addrs[@]}"
828
829	ip link set dev $if_name down
830	ip link set dev $if_name nomaster
831}
832
833simple_if_init()
834{
835	local if_name=$1
836	local vrf_name
837	local array
838
839	shift
840	vrf_name=v$if_name
841	array=("${@}")
842
843	vrf_create $vrf_name
844	ip link set dev $vrf_name up
845	__simple_if_init $if_name $vrf_name "${array[@]}"
846}
847
848simple_if_fini()
849{
850	local if_name=$1
851	local vrf_name
852	local array
853
854	shift
855	vrf_name=v$if_name
856	array=("${@}")
857
858	__simple_if_fini $if_name "${array[@]}"
859	vrf_destroy $vrf_name
860}
861
862tunnel_create()
863{
864	local name=$1; shift
865	local type=$1; shift
866	local local=$1; shift
867	local remote=$1; shift
868
869	ip link add name $name type $type \
870	   local $local remote $remote "$@"
871	ip link set dev $name up
872}
873
874tunnel_destroy()
875{
876	local name=$1; shift
877
878	ip link del dev $name
879}
880
881vlan_create()
882{
883	local if_name=$1; shift
884	local vid=$1; shift
885	local vrf=$1; shift
886	local ips=("${@}")
887	local name=$if_name.$vid
888
889	ip link add name $name link $if_name type vlan id $vid
890	if [ "$vrf" != "" ]; then
891		ip link set dev $name master $vrf
892	fi
893	ip link set dev $name up
894	__addr_add_del $name add "${ips[@]}"
895}
896
897vlan_destroy()
898{
899	local if_name=$1; shift
900	local vid=$1; shift
901	local name=$if_name.$vid
902
903	ip link del dev $name
904}
905
906team_create()
907{
908	local if_name=$1; shift
909	local mode=$1; shift
910
911	require_command $TEAMD
912	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
913	for slave in "$@"; do
914		ip link set dev $slave down
915		ip link set dev $slave master $if_name
916		ip link set dev $slave up
917	done
918	ip link set dev $if_name up
919}
920
921team_destroy()
922{
923	local if_name=$1; shift
924
925	$TEAMD -t $if_name -k
926}
927
928master_name_get()
929{
930	local if_name=$1
931
932	ip -j link show dev $if_name | jq -r '.[]["master"]'
933}
934
935link_stats_get()
936{
937	local if_name=$1; shift
938	local dir=$1; shift
939	local stat=$1; shift
940
941	ip -j -s link show dev $if_name \
942		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
943}
944
945link_stats_tx_packets_get()
946{
947	link_stats_get $1 tx packets
948}
949
950link_stats_rx_errors_get()
951{
952	link_stats_get $1 rx errors
953}
954
955tc_rule_stats_get()
956{
957	local dev=$1; shift
958	local pref=$1; shift
959	local dir=$1; shift
960	local selector=${1:-.packets}; shift
961
962	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
963	    | jq ".[1].options.actions[].stats$selector"
964}
965
966tc_rule_handle_stats_get()
967{
968	local id=$1; shift
969	local handle=$1; shift
970	local selector=${1:-.packets}; shift
971	local netns=${1:-""}; shift
972
973	tc $netns -j -s filter show $id \
974	    | jq ".[] | select(.options.handle == $handle) | \
975		  .options.actions[0].stats$selector"
976}
977
978ethtool_stats_get()
979{
980	local dev=$1; shift
981	local stat=$1; shift
982
983	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
984}
985
986ethtool_std_stats_get()
987{
988	local dev=$1; shift
989	local grp=$1; shift
990	local name=$1; shift
991	local src=$1; shift
992
993	ethtool --json -S $dev --groups $grp -- --src $src | \
994		jq '.[]."'"$grp"'"."'$name'"'
995}
996
997qdisc_stats_get()
998{
999	local dev=$1; shift
1000	local handle=$1; shift
1001	local selector=$1; shift
1002
1003	tc -j -s qdisc show dev "$dev" \
1004	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
1005}
1006
1007qdisc_parent_stats_get()
1008{
1009	local dev=$1; shift
1010	local parent=$1; shift
1011	local selector=$1; shift
1012
1013	tc -j -s qdisc show dev "$dev" invisible \
1014	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
1015}
1016
1017ipv6_stats_get()
1018{
1019	local dev=$1; shift
1020	local stat=$1; shift
1021
1022	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
1023}
1024
1025hw_stats_get()
1026{
1027	local suite=$1; shift
1028	local if_name=$1; shift
1029	local dir=$1; shift
1030	local stat=$1; shift
1031
1032	ip -j stats show dev $if_name group offload subgroup $suite |
1033		jq ".[0].stats64.$dir.$stat"
1034}
1035
1036__nh_stats_get()
1037{
1038	local key=$1; shift
1039	local group_id=$1; shift
1040	local member_id=$1; shift
1041
1042	ip -j -s -s nexthop show id $group_id |
1043	    jq --argjson member_id "$member_id" --arg key "$key" \
1044	       '.[].group_stats[] | select(.id == $member_id) | .[$key]'
1045}
1046
1047nh_stats_get()
1048{
1049	local group_id=$1; shift
1050	local member_id=$1; shift
1051
1052	__nh_stats_get packets "$group_id" "$member_id"
1053}
1054
1055nh_stats_get_hw()
1056{
1057	local group_id=$1; shift
1058	local member_id=$1; shift
1059
1060	__nh_stats_get packets_hw "$group_id" "$member_id"
1061}
1062
1063humanize()
1064{
1065	local speed=$1; shift
1066
1067	for unit in bps Kbps Mbps Gbps; do
1068		if (($(echo "$speed < 1024" | bc))); then
1069			break
1070		fi
1071
1072		speed=$(echo "scale=1; $speed / 1024" | bc)
1073	done
1074
1075	echo "$speed${unit}"
1076}
1077
1078rate()
1079{
1080	local t0=$1; shift
1081	local t1=$1; shift
1082	local interval=$1; shift
1083
1084	echo $((8 * (t1 - t0) / interval))
1085}
1086
1087packets_rate()
1088{
1089	local t0=$1; shift
1090	local t1=$1; shift
1091	local interval=$1; shift
1092
1093	echo $(((t1 - t0) / interval))
1094}
1095
1096mac_get()
1097{
1098	local if_name=$1
1099
1100	ip -j link show dev $if_name | jq -r '.[]["address"]'
1101}
1102
1103ipv6_lladdr_get()
1104{
1105	local if_name=$1
1106
1107	ip -j addr show dev $if_name | \
1108		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
1109		head -1
1110}
1111
1112bridge_ageing_time_get()
1113{
1114	local bridge=$1
1115	local ageing_time
1116
1117	# Need to divide by 100 to convert to seconds.
1118	ageing_time=$(ip -j -d link show dev $bridge \
1119		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
1120	echo $((ageing_time / 100))
1121}
1122
1123declare -A SYSCTL_ORIG
1124sysctl_set()
1125{
1126	local key=$1; shift
1127	local value=$1; shift
1128
1129	SYSCTL_ORIG[$key]=$(sysctl -n $key)
1130	sysctl -qw $key="$value"
1131}
1132
1133sysctl_restore()
1134{
1135	local key=$1; shift
1136
1137	sysctl -qw $key="${SYSCTL_ORIG[$key]}"
1138}
1139
1140forwarding_enable()
1141{
1142	sysctl_set net.ipv4.conf.all.forwarding 1
1143	sysctl_set net.ipv6.conf.all.forwarding 1
1144}
1145
1146forwarding_restore()
1147{
1148	sysctl_restore net.ipv6.conf.all.forwarding
1149	sysctl_restore net.ipv4.conf.all.forwarding
1150}
1151
1152declare -A MTU_ORIG
1153mtu_set()
1154{
1155	local dev=$1; shift
1156	local mtu=$1; shift
1157
1158	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
1159	ip link set dev $dev mtu $mtu
1160}
1161
1162mtu_restore()
1163{
1164	local dev=$1; shift
1165
1166	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
1167}
1168
1169tc_offload_check()
1170{
1171	local num_netifs=${1:-$NUM_NETIFS}
1172
1173	for ((i = 1; i <= num_netifs; ++i)); do
1174		ethtool -k ${NETIFS[p$i]} \
1175			| grep "hw-tc-offload: on" &> /dev/null
1176		if [[ $? -ne 0 ]]; then
1177			return 1
1178		fi
1179	done
1180
1181	return 0
1182}
1183
1184trap_install()
1185{
1186	local dev=$1; shift
1187	local direction=$1; shift
1188
1189	# Some devices may not support or need in-hardware trapping of traffic
1190	# (e.g. the veth pairs that this library creates for non-existent
1191	# loopbacks). Use continue instead, so that there is a filter in there
1192	# (some tests check counters), and so that other filters are still
1193	# processed.
1194	tc filter add dev $dev $direction pref 1 \
1195		flower skip_sw action trap 2>/dev/null \
1196	    || tc filter add dev $dev $direction pref 1 \
1197		       flower action continue
1198}
1199
1200trap_uninstall()
1201{
1202	local dev=$1; shift
1203	local direction=$1; shift
1204
1205	tc filter del dev $dev $direction pref 1 flower
1206}
1207
1208slow_path_trap_install()
1209{
1210	# For slow-path testing, we need to install a trap to get to
1211	# slow path the packets that would otherwise be switched in HW.
1212	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1213		trap_install "$@"
1214	fi
1215}
1216
1217slow_path_trap_uninstall()
1218{
1219	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1220		trap_uninstall "$@"
1221	fi
1222}
1223
1224__icmp_capture_add_del()
1225{
1226	local add_del=$1; shift
1227	local pref=$1; shift
1228	local vsuf=$1; shift
1229	local tundev=$1; shift
1230	local filter=$1; shift
1231
1232	tc filter $add_del dev "$tundev" ingress \
1233	   proto ip$vsuf pref $pref \
1234	   flower ip_proto icmp$vsuf $filter \
1235	   action pass
1236}
1237
1238icmp_capture_install()
1239{
1240	__icmp_capture_add_del add 100 "" "$@"
1241}
1242
1243icmp_capture_uninstall()
1244{
1245	__icmp_capture_add_del del 100 "" "$@"
1246}
1247
1248icmp6_capture_install()
1249{
1250	__icmp_capture_add_del add 100 v6 "$@"
1251}
1252
1253icmp6_capture_uninstall()
1254{
1255	__icmp_capture_add_del del 100 v6 "$@"
1256}
1257
1258__vlan_capture_add_del()
1259{
1260	local add_del=$1; shift
1261	local pref=$1; shift
1262	local dev=$1; shift
1263	local filter=$1; shift
1264
1265	tc filter $add_del dev "$dev" ingress \
1266	   proto 802.1q pref $pref \
1267	   flower $filter \
1268	   action pass
1269}
1270
1271vlan_capture_install()
1272{
1273	__vlan_capture_add_del add 100 "$@"
1274}
1275
1276vlan_capture_uninstall()
1277{
1278	__vlan_capture_add_del del 100 "$@"
1279}
1280
1281__dscp_capture_add_del()
1282{
1283	local add_del=$1; shift
1284	local dev=$1; shift
1285	local base=$1; shift
1286	local dscp;
1287
1288	for prio in {0..7}; do
1289		dscp=$((base + prio))
1290		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1291				       "skip_hw ip_tos $((dscp << 2))"
1292	done
1293}
1294
1295dscp_capture_install()
1296{
1297	local dev=$1; shift
1298	local base=$1; shift
1299
1300	__dscp_capture_add_del add $dev $base
1301}
1302
1303dscp_capture_uninstall()
1304{
1305	local dev=$1; shift
1306	local base=$1; shift
1307
1308	__dscp_capture_add_del del $dev $base
1309}
1310
1311dscp_fetch_stats()
1312{
1313	local dev=$1; shift
1314	local base=$1; shift
1315
1316	for prio in {0..7}; do
1317		local dscp=$((base + prio))
1318		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1319		echo "[$dscp]=$t "
1320	done
1321}
1322
1323matchall_sink_create()
1324{
1325	local dev=$1; shift
1326
1327	tc qdisc add dev $dev clsact
1328	tc filter add dev $dev ingress \
1329	   pref 10000 \
1330	   matchall \
1331	   action drop
1332}
1333
1334tests_run()
1335{
1336	local current_test
1337
1338	for current_test in ${TESTS:-$ALL_TESTS}; do
1339		$current_test
1340	done
1341}
1342
1343multipath_eval()
1344{
1345	local desc="$1"
1346	local weight_rp12=$2
1347	local weight_rp13=$3
1348	local packets_rp12=$4
1349	local packets_rp13=$5
1350	local weights_ratio packets_ratio diff
1351
1352	RET=0
1353
1354	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1355		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1356				| bc -l)
1357	else
1358		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1359				| bc -l)
1360	fi
1361
1362	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1363	       check_err 1 "Packet difference is 0"
1364	       log_test "Multipath"
1365	       log_info "Expected ratio $weights_ratio"
1366	       return
1367	fi
1368
1369	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1370		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1371				| bc -l)
1372	else
1373		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1374				| bc -l)
1375	fi
1376
1377	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1378	diff=${diff#-}
1379
1380	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1381	check_err $? "Too large discrepancy between expected and measured ratios"
1382	log_test "$desc"
1383	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1384}
1385
1386in_ns()
1387{
1388	local name=$1; shift
1389
1390	ip netns exec $name bash <<-EOF
1391		NUM_NETIFS=0
1392		source lib.sh
1393		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1394	EOF
1395}
1396
1397##############################################################################
1398# Tests
1399
1400ping_do()
1401{
1402	local if_name=$1
1403	local dip=$2
1404	local args=$3
1405	local vrf_name
1406
1407	vrf_name=$(master_name_get $if_name)
1408	ip vrf exec $vrf_name \
1409		$PING $args $dip -c $PING_COUNT -i 0.1 \
1410		-w $PING_TIMEOUT &> /dev/null
1411}
1412
1413ping_test()
1414{
1415	RET=0
1416
1417	ping_do $1 $2
1418	check_err $?
1419	log_test "ping$3"
1420}
1421
1422ping_test_fails()
1423{
1424	RET=0
1425
1426	ping_do $1 $2
1427	check_fail $?
1428	log_test "ping fails$3"
1429}
1430
1431ping6_do()
1432{
1433	local if_name=$1
1434	local dip=$2
1435	local args=$3
1436	local vrf_name
1437
1438	vrf_name=$(master_name_get $if_name)
1439	ip vrf exec $vrf_name \
1440		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1441		-w $PING_TIMEOUT &> /dev/null
1442}
1443
1444ping6_test()
1445{
1446	RET=0
1447
1448	ping6_do $1 $2
1449	check_err $?
1450	log_test "ping6$3"
1451}
1452
1453ping6_test_fails()
1454{
1455	RET=0
1456
1457	ping6_do $1 $2
1458	check_fail $?
1459	log_test "ping6 fails$3"
1460}
1461
1462learning_test()
1463{
1464	local bridge=$1
1465	local br_port1=$2	# Connected to `host1_if`.
1466	local host1_if=$3
1467	local host2_if=$4
1468	local mac=de:ad:be:ef:13:37
1469	local ageing_time
1470
1471	RET=0
1472
1473	bridge -j fdb show br $bridge brport $br_port1 \
1474		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1475	check_fail $? "Found FDB record when should not"
1476
1477	# Disable unknown unicast flooding on `br_port1` to make sure
1478	# packets are only forwarded through the port after a matching
1479	# FDB entry was installed.
1480	bridge link set dev $br_port1 flood off
1481
1482	ip link set $host1_if promisc on
1483	tc qdisc add dev $host1_if ingress
1484	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1485		flower dst_mac $mac action drop
1486
1487	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1488	sleep 1
1489
1490	tc -j -s filter show dev $host1_if ingress \
1491		| jq -e ".[] | select(.options.handle == 101) \
1492		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1493	check_fail $? "Packet reached first host when should not"
1494
1495	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1496	sleep 1
1497
1498	bridge -j fdb show br $bridge brport $br_port1 \
1499		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1500	check_err $? "Did not find FDB record when should"
1501
1502	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1503	sleep 1
1504
1505	tc -j -s filter show dev $host1_if ingress \
1506		| jq -e ".[] | select(.options.handle == 101) \
1507		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1508	check_err $? "Packet did not reach second host when should"
1509
1510	# Wait for 10 seconds after the ageing time to make sure FDB
1511	# record was aged-out.
1512	ageing_time=$(bridge_ageing_time_get $bridge)
1513	sleep $((ageing_time + 10))
1514
1515	bridge -j fdb show br $bridge brport $br_port1 \
1516		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1517	check_fail $? "Found FDB record when should not"
1518
1519	bridge link set dev $br_port1 learning off
1520
1521	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1522	sleep 1
1523
1524	bridge -j fdb show br $bridge brport $br_port1 \
1525		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1526	check_fail $? "Found FDB record when should not"
1527
1528	bridge link set dev $br_port1 learning on
1529
1530	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1531	tc qdisc del dev $host1_if ingress
1532	ip link set $host1_if promisc off
1533
1534	bridge link set dev $br_port1 flood on
1535
1536	log_test "FDB learning"
1537}
1538
1539flood_test_do()
1540{
1541	local should_flood=$1
1542	local mac=$2
1543	local ip=$3
1544	local host1_if=$4
1545	local host2_if=$5
1546	local err=0
1547
1548	# Add an ACL on `host2_if` which will tell us whether the packet
1549	# was flooded to it or not.
1550	ip link set $host2_if promisc on
1551	tc qdisc add dev $host2_if ingress
1552	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1553		flower dst_mac $mac action drop
1554
1555	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1556	sleep 1
1557
1558	tc -j -s filter show dev $host2_if ingress \
1559		| jq -e ".[] | select(.options.handle == 101) \
1560		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1561	if [[ $? -ne 0 && $should_flood == "true" || \
1562	      $? -eq 0 && $should_flood == "false" ]]; then
1563		err=1
1564	fi
1565
1566	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1567	tc qdisc del dev $host2_if ingress
1568	ip link set $host2_if promisc off
1569
1570	return $err
1571}
1572
1573flood_unicast_test()
1574{
1575	local br_port=$1
1576	local host1_if=$2
1577	local host2_if=$3
1578	local mac=de:ad:be:ef:13:37
1579	local ip=192.0.2.100
1580
1581	RET=0
1582
1583	bridge link set dev $br_port flood off
1584
1585	flood_test_do false $mac $ip $host1_if $host2_if
1586	check_err $? "Packet flooded when should not"
1587
1588	bridge link set dev $br_port flood on
1589
1590	flood_test_do true $mac $ip $host1_if $host2_if
1591	check_err $? "Packet was not flooded when should"
1592
1593	log_test "Unknown unicast flood"
1594}
1595
1596flood_multicast_test()
1597{
1598	local br_port=$1
1599	local host1_if=$2
1600	local host2_if=$3
1601	local mac=01:00:5e:00:00:01
1602	local ip=239.0.0.1
1603
1604	RET=0
1605
1606	bridge link set dev $br_port mcast_flood off
1607
1608	flood_test_do false $mac $ip $host1_if $host2_if
1609	check_err $? "Packet flooded when should not"
1610
1611	bridge link set dev $br_port mcast_flood on
1612
1613	flood_test_do true $mac $ip $host1_if $host2_if
1614	check_err $? "Packet was not flooded when should"
1615
1616	log_test "Unregistered multicast flood"
1617}
1618
1619flood_test()
1620{
1621	# `br_port` is connected to `host2_if`
1622	local br_port=$1
1623	local host1_if=$2
1624	local host2_if=$3
1625
1626	flood_unicast_test $br_port $host1_if $host2_if
1627	flood_multicast_test $br_port $host1_if $host2_if
1628}
1629
1630__start_traffic()
1631{
1632	local pktsize=$1; shift
1633	local proto=$1; shift
1634	local h_in=$1; shift    # Where the traffic egresses the host
1635	local sip=$1; shift
1636	local dip=$1; shift
1637	local dmac=$1; shift
1638
1639	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1640		-a own -b $dmac -t "$proto" -q "$@" &
1641	sleep 1
1642}
1643
1644start_traffic_pktsize()
1645{
1646	local pktsize=$1; shift
1647
1648	__start_traffic $pktsize udp "$@"
1649}
1650
1651start_tcp_traffic_pktsize()
1652{
1653	local pktsize=$1; shift
1654
1655	__start_traffic $pktsize tcp "$@"
1656}
1657
1658start_traffic()
1659{
1660	start_traffic_pktsize 8000 "$@"
1661}
1662
1663start_tcp_traffic()
1664{
1665	start_tcp_traffic_pktsize 8000 "$@"
1666}
1667
1668stop_traffic()
1669{
1670	# Suppress noise from killing mausezahn.
1671	{ kill %% && wait %%; } 2>/dev/null
1672}
1673
1674declare -A cappid
1675declare -A capfile
1676declare -A capout
1677
1678tcpdump_start()
1679{
1680	local if_name=$1; shift
1681	local ns=$1; shift
1682
1683	capfile[$if_name]=$(mktemp)
1684	capout[$if_name]=$(mktemp)
1685
1686	if [ -z $ns ]; then
1687		ns_cmd=""
1688	else
1689		ns_cmd="ip netns exec ${ns}"
1690	fi
1691
1692	if [ -z $SUDO_USER ] ; then
1693		capuser=""
1694	else
1695		capuser="-Z $SUDO_USER"
1696	fi
1697
1698	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1699		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1700		> "${capout[$if_name]}" 2>&1 &
1701	cappid[$if_name]=$!
1702
1703	sleep 1
1704}
1705
1706tcpdump_stop()
1707{
1708	local if_name=$1
1709	local pid=${cappid[$if_name]}
1710
1711	$ns_cmd kill "$pid" && wait "$pid"
1712	sleep 1
1713}
1714
1715tcpdump_cleanup()
1716{
1717	local if_name=$1
1718
1719	rm ${capfile[$if_name]} ${capout[$if_name]}
1720}
1721
1722tcpdump_show()
1723{
1724	local if_name=$1
1725
1726	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1727}
1728
1729# return 0 if the packet wasn't seen on host2_if or 1 if it was
1730mcast_packet_test()
1731{
1732	local mac=$1
1733	local src_ip=$2
1734	local ip=$3
1735	local host1_if=$4
1736	local host2_if=$5
1737	local seen=0
1738	local tc_proto="ip"
1739	local mz_v6arg=""
1740
1741	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1742	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1743		tc_proto="ipv6"
1744		mz_v6arg="-6"
1745	fi
1746
1747	# Add an ACL on `host2_if` which will tell us whether the packet
1748	# was received by it or not.
1749	tc qdisc add dev $host2_if ingress
1750	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1751		flower ip_proto udp dst_mac $mac action drop
1752
1753	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1754	sleep 1
1755
1756	tc -j -s filter show dev $host2_if ingress \
1757		| jq -e ".[] | select(.options.handle == 101) \
1758		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1759	if [[ $? -eq 0 ]]; then
1760		seen=1
1761	fi
1762
1763	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1764	tc qdisc del dev $host2_if ingress
1765
1766	return $seen
1767}
1768
1769brmcast_check_sg_entries()
1770{
1771	local report=$1; shift
1772	local slist=("$@")
1773	local sarg=""
1774
1775	for src in "${slist[@]}"; do
1776		sarg="${sarg} and .source_list[].address == \"$src\""
1777	done
1778	bridge -j -d -s mdb show dev br0 \
1779		| jq -e ".[].mdb[] | \
1780			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1781	check_err $? "Wrong *,G entry source list after $report report"
1782
1783	for sgent in "${slist[@]}"; do
1784		bridge -j -d -s mdb show dev br0 \
1785			| jq -e ".[].mdb[] | \
1786				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1787		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1788	done
1789}
1790
1791brmcast_check_sg_fwding()
1792{
1793	local should_fwd=$1; shift
1794	local sources=("$@")
1795
1796	for src in "${sources[@]}"; do
1797		local retval=0
1798
1799		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1800		retval=$?
1801		if [ $should_fwd -eq 1 ]; then
1802			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1803		else
1804			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1805		fi
1806	done
1807}
1808
1809brmcast_check_sg_state()
1810{
1811	local is_blocked=$1; shift
1812	local sources=("$@")
1813	local should_fail=1
1814
1815	if [ $is_blocked -eq 1 ]; then
1816		should_fail=0
1817	fi
1818
1819	for src in "${sources[@]}"; do
1820		bridge -j -d -s mdb show dev br0 \
1821			| jq -e ".[].mdb[] | \
1822				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1823				 .source_list[] |
1824				 select(.address == \"$src\") |
1825				 select(.timer == \"0.00\")" &>/dev/null
1826		check_err_fail $should_fail $? "Entry $src has zero timer"
1827
1828		bridge -j -d -s mdb show dev br0 \
1829			| jq -e ".[].mdb[] | \
1830				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1831				 .flags[] == \"blocked\")" &>/dev/null
1832		check_err_fail $should_fail $? "Entry $src has blocked flag"
1833	done
1834}
1835
1836mc_join()
1837{
1838	local if_name=$1
1839	local group=$2
1840	local vrf_name=$(master_name_get $if_name)
1841
1842	# We don't care about actual reception, just about joining the
1843	# IP multicast group and adding the L2 address to the device's
1844	# MAC filtering table
1845	ip vrf exec $vrf_name \
1846		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1847	mreceive_pid=$!
1848
1849	sleep 1
1850}
1851
1852mc_leave()
1853{
1854	kill "$mreceive_pid" && wait "$mreceive_pid"
1855}
1856
1857mc_send()
1858{
1859	local if_name=$1
1860	local groups=$2
1861	local vrf_name=$(master_name_get $if_name)
1862
1863	ip vrf exec $vrf_name \
1864		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1865}
1866
1867start_ip_monitor()
1868{
1869	local mtype=$1; shift
1870	local ip=${1-ip}; shift
1871
1872	# start the monitor in the background
1873	tmpfile=`mktemp /var/run/nexthoptestXXX`
1874	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1875	sleep 0.2
1876	echo "$mpid $tmpfile"
1877}
1878
1879stop_ip_monitor()
1880{
1881	local mpid=$1; shift
1882	local tmpfile=$1; shift
1883	local el=$1; shift
1884	local what=$1; shift
1885
1886	sleep 0.2
1887	kill $mpid
1888	local lines=`grep '^\w' $tmpfile | wc -l`
1889	test $lines -eq $el
1890	check_err $? "$what: $lines lines of events, expected $el"
1891	rm -rf $tmpfile
1892}
1893
1894hw_stats_monitor_test()
1895{
1896	local dev=$1; shift
1897	local type=$1; shift
1898	local make_suitable=$1; shift
1899	local make_unsuitable=$1; shift
1900	local ip=${1-ip}; shift
1901
1902	RET=0
1903
1904	# Expect a notification about enablement.
1905	local ipmout=$(start_ip_monitor stats "$ip")
1906	$ip stats set dev $dev ${type}_stats on
1907	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1908
1909	# Expect a notification about offload.
1910	local ipmout=$(start_ip_monitor stats "$ip")
1911	$make_suitable
1912	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1913
1914	# Expect a notification about loss of offload.
1915	local ipmout=$(start_ip_monitor stats "$ip")
1916	$make_unsuitable
1917	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1918
1919	# Expect a notification about disablement
1920	local ipmout=$(start_ip_monitor stats "$ip")
1921	$ip stats set dev $dev ${type}_stats off
1922	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1923
1924	log_test "${type}_stats notifications"
1925}
1926
1927ipv4_to_bytes()
1928{
1929	local IP=$1; shift
1930
1931	printf '%02x:' ${IP//./ } |
1932	    sed 's/:$//'
1933}
1934
1935# Convert a given IPv6 address, `IP' such that the :: token, if present, is
1936# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1937# digits. An optional `BYTESEP' parameter can be given to further separate
1938# individual bytes of each 16-bit group.
1939expand_ipv6()
1940{
1941	local IP=$1; shift
1942	local bytesep=$1; shift
1943
1944	local cvt_ip=${IP/::/_}
1945	local colons=${cvt_ip//[^:]/}
1946	local allcol=:::::::
1947	# IP where :: -> the appropriate number of colons:
1948	local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1949
1950	echo $allcol_ip | tr : '\n' |
1951	    sed s/^/0000/ |
1952	    sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1953	    tr '\n' : |
1954	    sed 's/:$//'
1955}
1956
1957ipv6_to_bytes()
1958{
1959	local IP=$1; shift
1960
1961	expand_ipv6 "$IP" :
1962}
1963
1964u16_to_bytes()
1965{
1966	local u16=$1; shift
1967
1968	printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1969}
1970
1971# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1972# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1973# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1974# stands for 00:00.
1975payload_template_calc_checksum()
1976{
1977	local payload=$1; shift
1978
1979	(
1980	    # Set input radix.
1981	    echo "16i"
1982	    # Push zero for the initial checksum.
1983	    echo 0
1984
1985	    # Pad the payload with a terminating 00: in case we get an odd
1986	    # number of bytes.
1987	    echo "${payload%:}:00:" |
1988		sed 's/CHECKSUM/00:00/g' |
1989		tr '[:lower:]' '[:upper:]' |
1990		# Add the word to the checksum.
1991		sed 's/\(..\):\(..\):/\1\2+\n/g' |
1992		# Strip the extra odd byte we pushed if left unconverted.
1993		sed 's/\(..\):$//'
1994
1995	    echo "10000 ~ +"	# Calculate and add carry.
1996	    echo "FFFF r - p"	# Bit-flip and print.
1997	) |
1998	    dc |
1999	    tr '[:upper:]' '[:lower:]'
2000}
2001
2002payload_template_expand_checksum()
2003{
2004	local payload=$1; shift
2005	local checksum=$1; shift
2006
2007	local ckbytes=$(u16_to_bytes $checksum)
2008
2009	echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
2010}
2011
2012payload_template_nbytes()
2013{
2014	local payload=$1; shift
2015
2016	payload_template_expand_checksum "${payload%:}" 0 |
2017		sed 's/:/\n/g' | wc -l
2018}
2019
2020igmpv3_is_in_get()
2021{
2022	local GRP=$1; shift
2023	local sources=("$@")
2024
2025	local igmpv3
2026	local nsources=$(u16_to_bytes ${#sources[@]})
2027
2028	# IS_IN ( $sources )
2029	igmpv3=$(:
2030		)"22:"$(			: Type - Membership Report
2031		)"00:"$(			: Reserved
2032		)"CHECKSUM:"$(			: Checksum
2033		)"00:00:"$(			: Reserved
2034		)"00:01:"$(			: Number of Group Records
2035		)"01:"$(			: Record Type - IS_IN
2036		)"00:"$(			: Aux Data Len
2037		)"${nsources}:"$(		: Number of Sources
2038		)"$(ipv4_to_bytes $GRP):"$(	: Multicast Address
2039		)"$(for src in "${sources[@]}"; do
2040			ipv4_to_bytes $src
2041			echo -n :
2042		    done)"$(			: Source Addresses
2043		)
2044	local checksum=$(payload_template_calc_checksum "$igmpv3")
2045
2046	payload_template_expand_checksum "$igmpv3" $checksum
2047}
2048
2049igmpv2_leave_get()
2050{
2051	local GRP=$1; shift
2052
2053	local payload=$(:
2054		)"17:"$(			: Type - Leave Group
2055		)"00:"$(			: Max Resp Time - not meaningful
2056		)"CHECKSUM:"$(			: Checksum
2057		)"$(ipv4_to_bytes $GRP)"$(	: Group Address
2058		)
2059	local checksum=$(payload_template_calc_checksum "$payload")
2060
2061	payload_template_expand_checksum "$payload" $checksum
2062}
2063
2064mldv2_is_in_get()
2065{
2066	local SIP=$1; shift
2067	local GRP=$1; shift
2068	local sources=("$@")
2069
2070	local hbh
2071	local icmpv6
2072	local nsources=$(u16_to_bytes ${#sources[@]})
2073
2074	hbh=$(:
2075		)"3a:"$(			: Next Header - ICMPv6
2076		)"00:"$(			: Hdr Ext Len
2077		)"00:00:00:00:00:00:"$(		: Options and Padding
2078		)
2079
2080	icmpv6=$(:
2081		)"8f:"$(			: Type - MLDv2 Report
2082		)"00:"$(			: Code
2083		)"CHECKSUM:"$(			: Checksum
2084		)"00:00:"$(			: Reserved
2085		)"00:01:"$(			: Number of Group Records
2086		)"01:"$(			: Record Type - IS_IN
2087		)"00:"$(			: Aux Data Len
2088		)"${nsources}:"$(		: Number of Sources
2089		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
2090		)"$(for src in "${sources[@]}"; do
2091			ipv6_to_bytes $src
2092			echo -n :
2093		    done)"$(			: Source Addresses
2094		)
2095
2096	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
2097	local sudohdr=$(:
2098		)"$(ipv6_to_bytes $SIP):"$(	: SIP
2099		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
2100	        )"${len}:"$(			: Upper-layer length
2101	        )"00:3a:"$(			: Zero and next-header
2102	        )
2103	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
2104
2105	payload_template_expand_checksum "$hbh$icmpv6" $checksum
2106}
2107
2108mldv1_done_get()
2109{
2110	local SIP=$1; shift
2111	local GRP=$1; shift
2112
2113	local hbh
2114	local icmpv6
2115
2116	hbh=$(:
2117		)"3a:"$(			: Next Header - ICMPv6
2118		)"00:"$(			: Hdr Ext Len
2119		)"00:00:00:00:00:00:"$(		: Options and Padding
2120		)
2121
2122	icmpv6=$(:
2123		)"84:"$(			: Type - MLDv1 Done
2124		)"00:"$(			: Code
2125		)"CHECKSUM:"$(			: Checksum
2126		)"00:00:"$(			: Max Resp Delay - not meaningful
2127		)"00:00:"$(			: Reserved
2128		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
2129		)
2130
2131	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
2132	local sudohdr=$(:
2133		)"$(ipv6_to_bytes $SIP):"$(	: SIP
2134		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
2135	        )"${len}:"$(			: Upper-layer length
2136	        )"00:3a:"$(			: Zero and next-header
2137	        )
2138	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
2139
2140	payload_template_expand_checksum "$hbh$icmpv6" $checksum
2141}
2142
2143bail_on_lldpad()
2144{
2145	local reason1="$1"; shift
2146	local reason2="$1"; shift
2147
2148	if systemctl is-active --quiet lldpad; then
2149
2150		cat >/dev/stderr <<-EOF
2151		WARNING: lldpad is running
2152
2153			lldpad will likely $reason1, and this test will
2154			$reason2. Both are not supported at the same time,
2155			one of them is arbitrarily going to overwrite the
2156			other. That will cause spurious failures (or, unlikely,
2157			passes) of this test.
2158		EOF
2159
2160		if [[ -z $ALLOW_LLDPAD ]]; then
2161			cat >/dev/stderr <<-EOF
2162
2163				If you want to run the test anyway, please set
2164				an environment variable ALLOW_LLDPAD to a
2165				non-empty string.
2166			EOF
2167			exit 1
2168		else
2169			return
2170		fi
2171	fi
2172}
2173
2174absval()
2175{
2176	local v=$1; shift
2177
2178	echo $((v > 0 ? v : -v))
2179}
2180