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