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