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