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_on_slow()
504{
505	if [[ $KSFT_MACHINE_SLOW = yes ]]; then
506		FAIL_TO_XFAIL=yes "$@"
507	else
508		"$@"
509	fi
510}
511
512omit_on_slow()
513{
514	if [[ $KSFT_MACHINE_SLOW != yes ]]; then
515		"$@"
516	fi
517}
518
519xfail_on_veth()
520{
521	local dev=$1; shift
522	local kind
523
524	kind=$(ip -j -d link show dev $dev |
525			jq -r '.[].linkinfo.info_kind')
526	if [[ $kind = veth ]]; then
527		FAIL_TO_XFAIL=yes "$@"
528	else
529		"$@"
530	fi
531}
532
533log_test_result()
534{
535	local test_name=$1; shift
536	local opt_str=$1; shift
537	local result=$1; shift
538	local retmsg=$1; shift
539
540	printf "TEST: %-60s  [%s]\n" "$test_name $opt_str" "$result"
541	if [[ $retmsg ]]; then
542		printf "\t%s\n" "$retmsg"
543	fi
544}
545
546pause_on_fail()
547{
548	if [[ $PAUSE_ON_FAIL == yes ]]; then
549		echo "Hit enter to continue, 'q' to quit"
550		read a
551		[[ $a == q ]] && exit 1
552	fi
553}
554
555handle_test_result_pass()
556{
557	local test_name=$1; shift
558	local opt_str=$1; shift
559
560	log_test_result "$test_name" "$opt_str" " OK "
561}
562
563handle_test_result_fail()
564{
565	local test_name=$1; shift
566	local opt_str=$1; shift
567
568	log_test_result "$test_name" "$opt_str" FAIL "$retmsg"
569	pause_on_fail
570}
571
572handle_test_result_xfail()
573{
574	local test_name=$1; shift
575	local opt_str=$1; shift
576
577	log_test_result "$test_name" "$opt_str" XFAIL "$retmsg"
578	pause_on_fail
579}
580
581handle_test_result_skip()
582{
583	local test_name=$1; shift
584	local opt_str=$1; shift
585
586	log_test_result "$test_name" "$opt_str" SKIP "$retmsg"
587}
588
589log_test()
590{
591	local test_name=$1
592	local opt_str=$2
593
594	if [[ $# -eq 2 ]]; then
595		opt_str="($opt_str)"
596	fi
597
598	if ((RET == ksft_pass)); then
599		handle_test_result_pass "$test_name" "$opt_str"
600	elif ((RET == ksft_xfail)); then
601		handle_test_result_xfail "$test_name" "$opt_str"
602	elif ((RET == ksft_skip)); then
603		handle_test_result_skip "$test_name" "$opt_str"
604	else
605		handle_test_result_fail "$test_name" "$opt_str"
606	fi
607
608	EXIT_STATUS=$(ksft_exit_status_merge $EXIT_STATUS $RET)
609	return $RET
610}
611
612log_test_skip()
613{
614	RET=$ksft_skip retmsg= log_test "$@"
615}
616
617log_test_xfail()
618{
619	RET=$ksft_xfail retmsg= log_test "$@"
620}
621
622log_info()
623{
624	local msg=$1
625
626	echo "INFO: $msg"
627}
628
629not()
630{
631	"$@"
632	[[ $? != 0 ]]
633}
634
635get_max()
636{
637	local arr=("$@")
638
639	max=${arr[0]}
640	for cur in ${arr[@]}; do
641		if [[ $cur -gt $max ]]; then
642			max=$cur
643		fi
644	done
645
646	echo $max
647}
648
649grep_bridge_fdb()
650{
651	local addr=$1; shift
652	local word
653	local flag
654
655	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
656		word=$1; shift
657		if [ "$1" == "-v" ]; then
658			flag=$1; shift
659		fi
660	fi
661
662	$@ | grep $addr | grep $flag "$word"
663}
664
665wait_for_port_up()
666{
667	"$@" | grep -q "Link detected: yes"
668}
669
670wait_for_offload()
671{
672	"$@" | grep -q offload
673}
674
675wait_for_trap()
676{
677	"$@" | grep -q trap
678}
679
680setup_wait_dev()
681{
682	local dev=$1; shift
683	local wait_time=${1:-$WAIT_TIME}; shift
684
685	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
686
687	if (($?)); then
688		check_err 1
689		log_test setup_wait_dev ": Interface $dev does not come up."
690		exit 1
691	fi
692}
693
694setup_wait_dev_with_timeout()
695{
696	local dev=$1; shift
697	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
698	local wait_time=${1:-$WAIT_TIME}; shift
699	local i
700
701	for ((i = 1; i <= $max_iterations; ++i)); do
702		ip link show dev $dev up \
703			| grep 'state UP' &> /dev/null
704		if [[ $? -ne 0 ]]; then
705			sleep 1
706		else
707			sleep $wait_time
708			return 0
709		fi
710	done
711
712	return 1
713}
714
715setup_wait()
716{
717	local num_netifs=${1:-$NUM_NETIFS}
718	local i
719
720	for ((i = 1; i <= num_netifs; ++i)); do
721		setup_wait_dev ${NETIFS[p$i]} 0
722	done
723
724	# Make sure links are ready.
725	sleep $WAIT_TIME
726}
727
728wait_for_dev()
729{
730        local dev=$1; shift
731        local timeout=${1:-$WAIT_TIMEOUT}; shift
732
733        slowwait $timeout ip link show dev $dev &> /dev/null
734        if (( $? )); then
735                check_err 1
736                log_test wait_for_dev "Interface $dev did not appear."
737                exit $EXIT_STATUS
738        fi
739}
740
741cmd_jq()
742{
743	local cmd=$1
744	local jq_exp=$2
745	local jq_opts=$3
746	local ret
747	local output
748
749	output="$($cmd)"
750	# it the command fails, return error right away
751	ret=$?
752	if [[ $ret -ne 0 ]]; then
753		return $ret
754	fi
755	output=$(echo $output | jq -r $jq_opts "$jq_exp")
756	ret=$?
757	if [[ $ret -ne 0 ]]; then
758		return $ret
759	fi
760	echo $output
761	# return success only in case of non-empty output
762	[ ! -z "$output" ]
763}
764
765pre_cleanup()
766{
767	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
768		echo "Pausing before cleanup, hit any key to continue"
769		read
770	fi
771
772	if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
773		mac_addr_restore
774	fi
775}
776
777vrf_prepare()
778{
779	ip -4 rule add pref 32765 table local
780	ip -4 rule del pref 0
781	ip -6 rule add pref 32765 table local
782	ip -6 rule del pref 0
783}
784
785vrf_cleanup()
786{
787	ip -6 rule add pref 0 table local
788	ip -6 rule del pref 32765
789	ip -4 rule add pref 0 table local
790	ip -4 rule del pref 32765
791}
792
793__last_tb_id=0
794declare -A __TB_IDS
795
796__vrf_td_id_assign()
797{
798	local vrf_name=$1
799
800	__last_tb_id=$((__last_tb_id + 1))
801	__TB_IDS[$vrf_name]=$__last_tb_id
802	return $__last_tb_id
803}
804
805__vrf_td_id_lookup()
806{
807	local vrf_name=$1
808
809	return ${__TB_IDS[$vrf_name]}
810}
811
812vrf_create()
813{
814	local vrf_name=$1
815	local tb_id
816
817	__vrf_td_id_assign $vrf_name
818	tb_id=$?
819
820	ip link add dev $vrf_name type vrf table $tb_id
821	ip -4 route add table $tb_id unreachable default metric 4278198272
822	ip -6 route add table $tb_id unreachable default metric 4278198272
823}
824
825vrf_destroy()
826{
827	local vrf_name=$1
828	local tb_id
829
830	__vrf_td_id_lookup $vrf_name
831	tb_id=$?
832
833	ip -6 route del table $tb_id unreachable default metric 4278198272
834	ip -4 route del table $tb_id unreachable default metric 4278198272
835	ip link del dev $vrf_name
836}
837
838__addr_add_del()
839{
840	local if_name=$1
841	local add_del=$2
842	local array
843
844	shift
845	shift
846	array=("${@}")
847
848	for addrstr in "${array[@]}"; do
849		ip address $add_del $addrstr dev $if_name
850	done
851}
852
853__simple_if_init()
854{
855	local if_name=$1; shift
856	local vrf_name=$1; shift
857	local addrs=("${@}")
858
859	ip link set dev $if_name master $vrf_name
860	ip link set dev $if_name up
861
862	__addr_add_del $if_name add "${addrs[@]}"
863}
864
865__simple_if_fini()
866{
867	local if_name=$1; shift
868	local addrs=("${@}")
869
870	__addr_add_del $if_name del "${addrs[@]}"
871
872	ip link set dev $if_name down
873	ip link set dev $if_name nomaster
874}
875
876simple_if_init()
877{
878	local if_name=$1
879	local vrf_name
880	local array
881
882	shift
883	vrf_name=v$if_name
884	array=("${@}")
885
886	vrf_create $vrf_name
887	ip link set dev $vrf_name up
888	__simple_if_init $if_name $vrf_name "${array[@]}"
889}
890
891simple_if_fini()
892{
893	local if_name=$1
894	local vrf_name
895	local array
896
897	shift
898	vrf_name=v$if_name
899	array=("${@}")
900
901	__simple_if_fini $if_name "${array[@]}"
902	vrf_destroy $vrf_name
903}
904
905tunnel_create()
906{
907	local name=$1; shift
908	local type=$1; shift
909	local local=$1; shift
910	local remote=$1; shift
911
912	ip link add name $name type $type \
913	   local $local remote $remote "$@"
914	ip link set dev $name up
915}
916
917tunnel_destroy()
918{
919	local name=$1; shift
920
921	ip link del dev $name
922}
923
924vlan_create()
925{
926	local if_name=$1; shift
927	local vid=$1; shift
928	local vrf=$1; shift
929	local ips=("${@}")
930	local name=$if_name.$vid
931
932	ip link add name $name link $if_name type vlan id $vid
933	if [ "$vrf" != "" ]; then
934		ip link set dev $name master $vrf
935	fi
936	ip link set dev $name up
937	__addr_add_del $name add "${ips[@]}"
938}
939
940vlan_destroy()
941{
942	local if_name=$1; shift
943	local vid=$1; shift
944	local name=$if_name.$vid
945
946	ip link del dev $name
947}
948
949team_create()
950{
951	local if_name=$1; shift
952	local mode=$1; shift
953
954	require_command $TEAMD
955	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
956	for slave in "$@"; do
957		ip link set dev $slave down
958		ip link set dev $slave master $if_name
959		ip link set dev $slave up
960	done
961	ip link set dev $if_name up
962}
963
964team_destroy()
965{
966	local if_name=$1; shift
967
968	$TEAMD -t $if_name -k
969}
970
971master_name_get()
972{
973	local if_name=$1
974
975	ip -j link show dev $if_name | jq -r '.[]["master"]'
976}
977
978link_stats_get()
979{
980	local if_name=$1; shift
981	local dir=$1; shift
982	local stat=$1; shift
983
984	ip -j -s link show dev $if_name \
985		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
986}
987
988link_stats_tx_packets_get()
989{
990	link_stats_get $1 tx packets
991}
992
993link_stats_rx_errors_get()
994{
995	link_stats_get $1 rx errors
996}
997
998ethtool_stats_get()
999{
1000	local dev=$1; shift
1001	local stat=$1; shift
1002
1003	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
1004}
1005
1006ethtool_std_stats_get()
1007{
1008	local dev=$1; shift
1009	local grp=$1; shift
1010	local name=$1; shift
1011	local src=$1; shift
1012
1013	ethtool --json -S $dev --groups $grp -- --src $src | \
1014		jq '.[]."'"$grp"'"."'$name'"'
1015}
1016
1017qdisc_stats_get()
1018{
1019	local dev=$1; shift
1020	local handle=$1; shift
1021	local selector=$1; shift
1022
1023	tc -j -s qdisc show dev "$dev" \
1024	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
1025}
1026
1027qdisc_parent_stats_get()
1028{
1029	local dev=$1; shift
1030	local parent=$1; shift
1031	local selector=$1; shift
1032
1033	tc -j -s qdisc show dev "$dev" invisible \
1034	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
1035}
1036
1037ipv6_stats_get()
1038{
1039	local dev=$1; shift
1040	local stat=$1; shift
1041
1042	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
1043}
1044
1045hw_stats_get()
1046{
1047	local suite=$1; shift
1048	local if_name=$1; shift
1049	local dir=$1; shift
1050	local stat=$1; shift
1051
1052	ip -j stats show dev $if_name group offload subgroup $suite |
1053		jq ".[0].stats64.$dir.$stat"
1054}
1055
1056__nh_stats_get()
1057{
1058	local key=$1; shift
1059	local group_id=$1; shift
1060	local member_id=$1; shift
1061
1062	ip -j -s -s nexthop show id $group_id |
1063	    jq --argjson member_id "$member_id" --arg key "$key" \
1064	       '.[].group_stats[] | select(.id == $member_id) | .[$key]'
1065}
1066
1067nh_stats_get()
1068{
1069	local group_id=$1; shift
1070	local member_id=$1; shift
1071
1072	__nh_stats_get packets "$group_id" "$member_id"
1073}
1074
1075nh_stats_get_hw()
1076{
1077	local group_id=$1; shift
1078	local member_id=$1; shift
1079
1080	__nh_stats_get packets_hw "$group_id" "$member_id"
1081}
1082
1083humanize()
1084{
1085	local speed=$1; shift
1086
1087	for unit in bps Kbps Mbps Gbps; do
1088		if (($(echo "$speed < 1024" | bc))); then
1089			break
1090		fi
1091
1092		speed=$(echo "scale=1; $speed / 1024" | bc)
1093	done
1094
1095	echo "$speed${unit}"
1096}
1097
1098rate()
1099{
1100	local t0=$1; shift
1101	local t1=$1; shift
1102	local interval=$1; shift
1103
1104	echo $((8 * (t1 - t0) / interval))
1105}
1106
1107packets_rate()
1108{
1109	local t0=$1; shift
1110	local t1=$1; shift
1111	local interval=$1; shift
1112
1113	echo $(((t1 - t0) / interval))
1114}
1115
1116mac_get()
1117{
1118	local if_name=$1
1119
1120	ip -j link show dev $if_name | jq -r '.[]["address"]'
1121}
1122
1123ipv6_lladdr_get()
1124{
1125	local if_name=$1
1126
1127	ip -j addr show dev $if_name | \
1128		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
1129		head -1
1130}
1131
1132bridge_ageing_time_get()
1133{
1134	local bridge=$1
1135	local ageing_time
1136
1137	# Need to divide by 100 to convert to seconds.
1138	ageing_time=$(ip -j -d link show dev $bridge \
1139		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
1140	echo $((ageing_time / 100))
1141}
1142
1143declare -A SYSCTL_ORIG
1144sysctl_save()
1145{
1146	local key=$1; shift
1147
1148	SYSCTL_ORIG[$key]=$(sysctl -n $key)
1149}
1150
1151sysctl_set()
1152{
1153	local key=$1; shift
1154	local value=$1; shift
1155
1156	sysctl_save "$key"
1157	sysctl -qw $key="$value"
1158}
1159
1160sysctl_restore()
1161{
1162	local key=$1; shift
1163
1164	sysctl -qw $key="${SYSCTL_ORIG[$key]}"
1165}
1166
1167forwarding_enable()
1168{
1169	sysctl_set net.ipv4.conf.all.forwarding 1
1170	sysctl_set net.ipv6.conf.all.forwarding 1
1171}
1172
1173forwarding_restore()
1174{
1175	sysctl_restore net.ipv6.conf.all.forwarding
1176	sysctl_restore net.ipv4.conf.all.forwarding
1177}
1178
1179declare -A MTU_ORIG
1180mtu_set()
1181{
1182	local dev=$1; shift
1183	local mtu=$1; shift
1184
1185	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
1186	ip link set dev $dev mtu $mtu
1187}
1188
1189mtu_restore()
1190{
1191	local dev=$1; shift
1192
1193	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
1194}
1195
1196tc_offload_check()
1197{
1198	local num_netifs=${1:-$NUM_NETIFS}
1199
1200	for ((i = 1; i <= num_netifs; ++i)); do
1201		ethtool -k ${NETIFS[p$i]} \
1202			| grep "hw-tc-offload: on" &> /dev/null
1203		if [[ $? -ne 0 ]]; then
1204			return 1
1205		fi
1206	done
1207
1208	return 0
1209}
1210
1211trap_install()
1212{
1213	local dev=$1; shift
1214	local direction=$1; shift
1215
1216	# Some devices may not support or need in-hardware trapping of traffic
1217	# (e.g. the veth pairs that this library creates for non-existent
1218	# loopbacks). Use continue instead, so that there is a filter in there
1219	# (some tests check counters), and so that other filters are still
1220	# processed.
1221	tc filter add dev $dev $direction pref 1 \
1222		flower skip_sw action trap 2>/dev/null \
1223	    || tc filter add dev $dev $direction pref 1 \
1224		       flower action continue
1225}
1226
1227trap_uninstall()
1228{
1229	local dev=$1; shift
1230	local direction=$1; shift
1231
1232	tc filter del dev $dev $direction pref 1 flower
1233}
1234
1235__icmp_capture_add_del()
1236{
1237	local add_del=$1; shift
1238	local pref=$1; shift
1239	local vsuf=$1; shift
1240	local tundev=$1; shift
1241	local filter=$1; shift
1242
1243	tc filter $add_del dev "$tundev" ingress \
1244	   proto ip$vsuf pref $pref \
1245	   flower ip_proto icmp$vsuf $filter \
1246	   action pass
1247}
1248
1249icmp_capture_install()
1250{
1251	local tundev=$1; shift
1252	local filter=$1; shift
1253
1254	__icmp_capture_add_del add 100 "" "$tundev" "$filter"
1255}
1256
1257icmp_capture_uninstall()
1258{
1259	local tundev=$1; shift
1260	local filter=$1; shift
1261
1262	__icmp_capture_add_del del 100 "" "$tundev" "$filter"
1263}
1264
1265icmp6_capture_install()
1266{
1267	local tundev=$1; shift
1268	local filter=$1; shift
1269
1270	__icmp_capture_add_del add 100 v6 "$tundev" "$filter"
1271}
1272
1273icmp6_capture_uninstall()
1274{
1275	local tundev=$1; shift
1276	local filter=$1; shift
1277
1278	__icmp_capture_add_del del 100 v6 "$tundev" "$filter"
1279}
1280
1281__vlan_capture_add_del()
1282{
1283	local add_del=$1; shift
1284	local pref=$1; shift
1285	local dev=$1; shift
1286	local filter=$1; shift
1287
1288	tc filter $add_del dev "$dev" ingress \
1289	   proto 802.1q pref $pref \
1290	   flower $filter \
1291	   action pass
1292}
1293
1294vlan_capture_install()
1295{
1296	local dev=$1; shift
1297	local filter=$1; shift
1298
1299	__vlan_capture_add_del add 100 "$dev" "$filter"
1300}
1301
1302vlan_capture_uninstall()
1303{
1304	local dev=$1; shift
1305	local filter=$1; shift
1306
1307	__vlan_capture_add_del del 100 "$dev" "$filter"
1308}
1309
1310__dscp_capture_add_del()
1311{
1312	local add_del=$1; shift
1313	local dev=$1; shift
1314	local base=$1; shift
1315	local dscp;
1316
1317	for prio in {0..7}; do
1318		dscp=$((base + prio))
1319		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1320				       "skip_hw ip_tos $((dscp << 2))"
1321	done
1322}
1323
1324dscp_capture_install()
1325{
1326	local dev=$1; shift
1327	local base=$1; shift
1328
1329	__dscp_capture_add_del add $dev $base
1330}
1331
1332dscp_capture_uninstall()
1333{
1334	local dev=$1; shift
1335	local base=$1; shift
1336
1337	__dscp_capture_add_del del $dev $base
1338}
1339
1340dscp_fetch_stats()
1341{
1342	local dev=$1; shift
1343	local base=$1; shift
1344
1345	for prio in {0..7}; do
1346		local dscp=$((base + prio))
1347		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1348		echo "[$dscp]=$t "
1349	done
1350}
1351
1352matchall_sink_create()
1353{
1354	local dev=$1; shift
1355
1356	tc qdisc add dev $dev clsact
1357	tc filter add dev $dev ingress \
1358	   pref 10000 \
1359	   matchall \
1360	   action drop
1361}
1362
1363tests_run()
1364{
1365	local current_test
1366
1367	for current_test in ${TESTS:-$ALL_TESTS}; do
1368		$current_test
1369	done
1370}
1371
1372multipath_eval()
1373{
1374	local desc="$1"
1375	local weight_rp12=$2
1376	local weight_rp13=$3
1377	local packets_rp12=$4
1378	local packets_rp13=$5
1379	local weights_ratio packets_ratio diff
1380
1381	RET=0
1382
1383	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1384		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1385				| bc -l)
1386	else
1387		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1388				| bc -l)
1389	fi
1390
1391	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1392	       check_err 1 "Packet difference is 0"
1393	       log_test "Multipath"
1394	       log_info "Expected ratio $weights_ratio"
1395	       return
1396	fi
1397
1398	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1399		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1400				| bc -l)
1401	else
1402		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1403				| bc -l)
1404	fi
1405
1406	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1407	diff=${diff#-}
1408
1409	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1410	check_err $? "Too large discrepancy between expected and measured ratios"
1411	log_test "$desc"
1412	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1413}
1414
1415in_ns()
1416{
1417	local name=$1; shift
1418
1419	ip netns exec $name bash <<-EOF
1420		NUM_NETIFS=0
1421		source lib.sh
1422		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1423	EOF
1424}
1425
1426##############################################################################
1427# Tests
1428
1429ping_do()
1430{
1431	local if_name=$1
1432	local dip=$2
1433	local args=$3
1434	local vrf_name
1435
1436	vrf_name=$(master_name_get $if_name)
1437	ip vrf exec $vrf_name \
1438		$PING $args $dip -c $PING_COUNT -i 0.1 \
1439		-w $PING_TIMEOUT &> /dev/null
1440}
1441
1442ping_test()
1443{
1444	RET=0
1445
1446	ping_do $1 $2
1447	check_err $?
1448	log_test "ping$3"
1449}
1450
1451ping_test_fails()
1452{
1453	RET=0
1454
1455	ping_do $1 $2
1456	check_fail $?
1457	log_test "ping fails$3"
1458}
1459
1460ping6_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		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1470		-w $PING_TIMEOUT &> /dev/null
1471}
1472
1473ping6_test()
1474{
1475	RET=0
1476
1477	ping6_do $1 $2
1478	check_err $?
1479	log_test "ping6$3"
1480}
1481
1482ping6_test_fails()
1483{
1484	RET=0
1485
1486	ping6_do $1 $2
1487	check_fail $?
1488	log_test "ping6 fails$3"
1489}
1490
1491learning_test()
1492{
1493	local bridge=$1
1494	local br_port1=$2	# Connected to `host1_if`.
1495	local host1_if=$3
1496	local host2_if=$4
1497	local mac=de:ad:be:ef:13:37
1498	local ageing_time
1499
1500	RET=0
1501
1502	bridge -j fdb show br $bridge brport $br_port1 \
1503		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1504	check_fail $? "Found FDB record when should not"
1505
1506	# Disable unknown unicast flooding on `br_port1` to make sure
1507	# packets are only forwarded through the port after a matching
1508	# FDB entry was installed.
1509	bridge link set dev $br_port1 flood off
1510
1511	ip link set $host1_if promisc on
1512	tc qdisc add dev $host1_if ingress
1513	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1514		flower dst_mac $mac action drop
1515
1516	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1517	sleep 1
1518
1519	tc -j -s filter show dev $host1_if ingress \
1520		| jq -e ".[] | select(.options.handle == 101) \
1521		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1522	check_fail $? "Packet reached first host when should not"
1523
1524	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1525	sleep 1
1526
1527	bridge -j fdb show br $bridge brport $br_port1 \
1528		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1529	check_err $? "Did not find FDB record when should"
1530
1531	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1532	sleep 1
1533
1534	tc -j -s filter show dev $host1_if ingress \
1535		| jq -e ".[] | select(.options.handle == 101) \
1536		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1537	check_err $? "Packet did not reach second host when should"
1538
1539	# Wait for 10 seconds after the ageing time to make sure FDB
1540	# record was aged-out.
1541	ageing_time=$(bridge_ageing_time_get $bridge)
1542	sleep $((ageing_time + 10))
1543
1544	bridge -j fdb show br $bridge brport $br_port1 \
1545		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1546	check_fail $? "Found FDB record when should not"
1547
1548	bridge link set dev $br_port1 learning off
1549
1550	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1551	sleep 1
1552
1553	bridge -j fdb show br $bridge brport $br_port1 \
1554		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1555	check_fail $? "Found FDB record when should not"
1556
1557	bridge link set dev $br_port1 learning on
1558
1559	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1560	tc qdisc del dev $host1_if ingress
1561	ip link set $host1_if promisc off
1562
1563	bridge link set dev $br_port1 flood on
1564
1565	log_test "FDB learning"
1566}
1567
1568flood_test_do()
1569{
1570	local should_flood=$1
1571	local mac=$2
1572	local ip=$3
1573	local host1_if=$4
1574	local host2_if=$5
1575	local err=0
1576
1577	# Add an ACL on `host2_if` which will tell us whether the packet
1578	# was flooded to it or not.
1579	ip link set $host2_if promisc on
1580	tc qdisc add dev $host2_if ingress
1581	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1582		flower dst_mac $mac action drop
1583
1584	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1585	sleep 1
1586
1587	tc -j -s filter show dev $host2_if ingress \
1588		| jq -e ".[] | select(.options.handle == 101) \
1589		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1590	if [[ $? -ne 0 && $should_flood == "true" || \
1591	      $? -eq 0 && $should_flood == "false" ]]; then
1592		err=1
1593	fi
1594
1595	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1596	tc qdisc del dev $host2_if ingress
1597	ip link set $host2_if promisc off
1598
1599	return $err
1600}
1601
1602flood_unicast_test()
1603{
1604	local br_port=$1
1605	local host1_if=$2
1606	local host2_if=$3
1607	local mac=de:ad:be:ef:13:37
1608	local ip=192.0.2.100
1609
1610	RET=0
1611
1612	bridge link set dev $br_port flood off
1613
1614	flood_test_do false $mac $ip $host1_if $host2_if
1615	check_err $? "Packet flooded when should not"
1616
1617	bridge link set dev $br_port flood on
1618
1619	flood_test_do true $mac $ip $host1_if $host2_if
1620	check_err $? "Packet was not flooded when should"
1621
1622	log_test "Unknown unicast flood"
1623}
1624
1625flood_multicast_test()
1626{
1627	local br_port=$1
1628	local host1_if=$2
1629	local host2_if=$3
1630	local mac=01:00:5e:00:00:01
1631	local ip=239.0.0.1
1632
1633	RET=0
1634
1635	bridge link set dev $br_port mcast_flood off
1636
1637	flood_test_do false $mac $ip $host1_if $host2_if
1638	check_err $? "Packet flooded when should not"
1639
1640	bridge link set dev $br_port mcast_flood on
1641
1642	flood_test_do true $mac $ip $host1_if $host2_if
1643	check_err $? "Packet was not flooded when should"
1644
1645	log_test "Unregistered multicast flood"
1646}
1647
1648flood_test()
1649{
1650	# `br_port` is connected to `host2_if`
1651	local br_port=$1
1652	local host1_if=$2
1653	local host2_if=$3
1654
1655	flood_unicast_test $br_port $host1_if $host2_if
1656	flood_multicast_test $br_port $host1_if $host2_if
1657}
1658
1659__start_traffic()
1660{
1661	local pktsize=$1; shift
1662	local proto=$1; shift
1663	local h_in=$1; shift    # Where the traffic egresses the host
1664	local sip=$1; shift
1665	local dip=$1; shift
1666	local dmac=$1; shift
1667	local -a mz_args=("$@")
1668
1669	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1670		-a own -b $dmac -t "$proto" -q "${mz_args[@]}" &
1671	sleep 1
1672}
1673
1674start_traffic_pktsize()
1675{
1676	local pktsize=$1; shift
1677	local h_in=$1; shift
1678	local sip=$1; shift
1679	local dip=$1; shift
1680	local dmac=$1; shift
1681	local -a mz_args=("$@")
1682
1683	__start_traffic $pktsize udp "$h_in" "$sip" "$dip" "$dmac" \
1684			"${mz_args[@]}"
1685}
1686
1687start_tcp_traffic_pktsize()
1688{
1689	local pktsize=$1; shift
1690	local h_in=$1; shift
1691	local sip=$1; shift
1692	local dip=$1; shift
1693	local dmac=$1; shift
1694	local -a mz_args=("$@")
1695
1696	__start_traffic $pktsize tcp "$h_in" "$sip" "$dip" "$dmac" \
1697			"${mz_args[@]}"
1698}
1699
1700start_traffic()
1701{
1702	local h_in=$1; shift
1703	local sip=$1; shift
1704	local dip=$1; shift
1705	local dmac=$1; shift
1706	local -a mz_args=("$@")
1707
1708	start_traffic_pktsize 8000 "$h_in" "$sip" "$dip" "$dmac" \
1709			      "${mz_args[@]}"
1710}
1711
1712start_tcp_traffic()
1713{
1714	local h_in=$1; shift
1715	local sip=$1; shift
1716	local dip=$1; shift
1717	local dmac=$1; shift
1718	local -a mz_args=("$@")
1719
1720	start_tcp_traffic_pktsize 8000 "$h_in" "$sip" "$dip" "$dmac" \
1721				  "${mz_args[@]}"
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