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