xref: /linux/tools/testing/selftests/net/forwarding/lib.sh (revision 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf)
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
574cmd_jq()
575{
576	local cmd=$1
577	local jq_exp=$2
578	local jq_opts=$3
579	local ret
580	local output
581
582	output="$($cmd)"
583	# it the command fails, return error right away
584	ret=$?
585	if [[ $ret -ne 0 ]]; then
586		return $ret
587	fi
588	output=$(echo $output | jq -r $jq_opts "$jq_exp")
589	ret=$?
590	if [[ $ret -ne 0 ]]; then
591		return $ret
592	fi
593	echo $output
594	# return success only in case of non-empty output
595	[ ! -z "$output" ]
596}
597
598pre_cleanup()
599{
600	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
601		echo "Pausing before cleanup, hit any key to continue"
602		read
603	fi
604
605	if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
606		mac_addr_restore
607	fi
608}
609
610vrf_prepare()
611{
612	ip -4 rule add pref 32765 table local
613	ip -4 rule del pref 0
614	ip -6 rule add pref 32765 table local
615	ip -6 rule del pref 0
616}
617
618vrf_cleanup()
619{
620	ip -6 rule add pref 0 table local
621	ip -6 rule del pref 32765
622	ip -4 rule add pref 0 table local
623	ip -4 rule del pref 32765
624}
625
626__last_tb_id=0
627declare -A __TB_IDS
628
629__vrf_td_id_assign()
630{
631	local vrf_name=$1
632
633	__last_tb_id=$((__last_tb_id + 1))
634	__TB_IDS[$vrf_name]=$__last_tb_id
635	return $__last_tb_id
636}
637
638__vrf_td_id_lookup()
639{
640	local vrf_name=$1
641
642	return ${__TB_IDS[$vrf_name]}
643}
644
645vrf_create()
646{
647	local vrf_name=$1
648	local tb_id
649
650	__vrf_td_id_assign $vrf_name
651	tb_id=$?
652
653	ip link add dev $vrf_name type vrf table $tb_id
654	ip -4 route add table $tb_id unreachable default metric 4278198272
655	ip -6 route add table $tb_id unreachable default metric 4278198272
656}
657
658vrf_destroy()
659{
660	local vrf_name=$1
661	local tb_id
662
663	__vrf_td_id_lookup $vrf_name
664	tb_id=$?
665
666	ip -6 route del table $tb_id unreachable default metric 4278198272
667	ip -4 route del table $tb_id unreachable default metric 4278198272
668	ip link del dev $vrf_name
669}
670
671__addr_add_del()
672{
673	local if_name=$1
674	local add_del=$2
675	local array
676
677	shift
678	shift
679	array=("${@}")
680
681	for addrstr in "${array[@]}"; do
682		ip address $add_del $addrstr dev $if_name
683	done
684}
685
686__simple_if_init()
687{
688	local if_name=$1; shift
689	local vrf_name=$1; shift
690	local addrs=("${@}")
691
692	ip link set dev $if_name master $vrf_name
693	ip link set dev $if_name up
694
695	__addr_add_del $if_name add "${addrs[@]}"
696}
697
698__simple_if_fini()
699{
700	local if_name=$1; shift
701	local addrs=("${@}")
702
703	__addr_add_del $if_name del "${addrs[@]}"
704
705	ip link set dev $if_name down
706	ip link set dev $if_name nomaster
707}
708
709simple_if_init()
710{
711	local if_name=$1
712	local vrf_name
713	local array
714
715	shift
716	vrf_name=v$if_name
717	array=("${@}")
718
719	vrf_create $vrf_name
720	ip link set dev $vrf_name up
721	__simple_if_init $if_name $vrf_name "${array[@]}"
722}
723
724simple_if_fini()
725{
726	local if_name=$1
727	local vrf_name
728	local array
729
730	shift
731	vrf_name=v$if_name
732	array=("${@}")
733
734	__simple_if_fini $if_name "${array[@]}"
735	vrf_destroy $vrf_name
736}
737
738tunnel_create()
739{
740	local name=$1; shift
741	local type=$1; shift
742	local local=$1; shift
743	local remote=$1; shift
744
745	ip link add name $name type $type \
746	   local $local remote $remote "$@"
747	ip link set dev $name up
748}
749
750tunnel_destroy()
751{
752	local name=$1; shift
753
754	ip link del dev $name
755}
756
757vlan_create()
758{
759	local if_name=$1; shift
760	local vid=$1; shift
761	local vrf=$1; shift
762	local ips=("${@}")
763	local name=$if_name.$vid
764
765	ip link add name $name link $if_name type vlan id $vid
766	if [ "$vrf" != "" ]; then
767		ip link set dev $name master $vrf
768	fi
769	ip link set dev $name up
770	__addr_add_del $name add "${ips[@]}"
771}
772
773vlan_destroy()
774{
775	local if_name=$1; shift
776	local vid=$1; shift
777	local name=$if_name.$vid
778
779	ip link del dev $name
780}
781
782team_create()
783{
784	local if_name=$1; shift
785	local mode=$1; shift
786
787	require_command $TEAMD
788	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
789	for slave in "$@"; do
790		ip link set dev $slave down
791		ip link set dev $slave master $if_name
792		ip link set dev $slave up
793	done
794	ip link set dev $if_name up
795}
796
797team_destroy()
798{
799	local if_name=$1; shift
800
801	$TEAMD -t $if_name -k
802}
803
804master_name_get()
805{
806	local if_name=$1
807
808	ip -j link show dev $if_name | jq -r '.[]["master"]'
809}
810
811link_stats_get()
812{
813	local if_name=$1; shift
814	local dir=$1; shift
815	local stat=$1; shift
816
817	ip -j -s link show dev $if_name \
818		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
819}
820
821link_stats_tx_packets_get()
822{
823	link_stats_get $1 tx packets
824}
825
826link_stats_rx_errors_get()
827{
828	link_stats_get $1 rx errors
829}
830
831ethtool_stats_get()
832{
833	local dev=$1; shift
834	local stat=$1; shift
835
836	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
837}
838
839ethtool_std_stats_get()
840{
841	local dev=$1; shift
842	local grp=$1; shift
843	local name=$1; shift
844	local src=$1; shift
845
846	ethtool --json -S $dev --groups $grp -- --src $src | \
847		jq '.[]."'"$grp"'"."'$name'"'
848}
849
850qdisc_stats_get()
851{
852	local dev=$1; shift
853	local handle=$1; shift
854	local selector=$1; shift
855
856	tc -j -s qdisc show dev "$dev" \
857	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
858}
859
860qdisc_parent_stats_get()
861{
862	local dev=$1; shift
863	local parent=$1; shift
864	local selector=$1; shift
865
866	tc -j -s qdisc show dev "$dev" invisible \
867	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
868}
869
870ipv6_stats_get()
871{
872	local dev=$1; shift
873	local stat=$1; shift
874
875	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
876}
877
878hw_stats_get()
879{
880	local suite=$1; shift
881	local if_name=$1; shift
882	local dir=$1; shift
883	local stat=$1; shift
884
885	ip -j stats show dev $if_name group offload subgroup $suite |
886		jq ".[0].stats64.$dir.$stat"
887}
888
889__nh_stats_get()
890{
891	local key=$1; shift
892	local group_id=$1; shift
893	local member_id=$1; shift
894
895	ip -j -s -s nexthop show id $group_id |
896	    jq --argjson member_id "$member_id" --arg key "$key" \
897	       '.[].group_stats[] | select(.id == $member_id) | .[$key]'
898}
899
900nh_stats_get()
901{
902	local group_id=$1; shift
903	local member_id=$1; shift
904
905	__nh_stats_get packets "$group_id" "$member_id"
906}
907
908nh_stats_get_hw()
909{
910	local group_id=$1; shift
911	local member_id=$1; shift
912
913	__nh_stats_get packets_hw "$group_id" "$member_id"
914}
915
916humanize()
917{
918	local speed=$1; shift
919
920	for unit in bps Kbps Mbps Gbps; do
921		if (($(echo "$speed < 1024" | bc))); then
922			break
923		fi
924
925		speed=$(echo "scale=1; $speed / 1024" | bc)
926	done
927
928	echo "$speed${unit}"
929}
930
931rate()
932{
933	local t0=$1; shift
934	local t1=$1; shift
935	local interval=$1; shift
936
937	echo $((8 * (t1 - t0) / interval))
938}
939
940packets_rate()
941{
942	local t0=$1; shift
943	local t1=$1; shift
944	local interval=$1; shift
945
946	echo $(((t1 - t0) / interval))
947}
948
949ether_addr_to_u64()
950{
951	local addr="$1"
952	local order="$((1 << 40))"
953	local val=0
954	local byte
955
956	addr="${addr//:/ }"
957
958	for byte in $addr; do
959		byte="0x$byte"
960		val=$((val + order * byte))
961		order=$((order >> 8))
962	done
963
964	printf "0x%x" $val
965}
966
967u64_to_ether_addr()
968{
969	local val=$1
970	local byte
971	local i
972
973	for ((i = 40; i >= 0; i -= 8)); do
974		byte=$(((val & (0xff << i)) >> i))
975		printf "%02x" $byte
976		if [ $i -ne 0 ]; then
977			printf ":"
978		fi
979	done
980}
981
982ipv6_lladdr_get()
983{
984	local if_name=$1
985
986	ip -j addr show dev $if_name | \
987		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
988		head -1
989}
990
991bridge_ageing_time_get()
992{
993	local bridge=$1
994	local ageing_time
995
996	# Need to divide by 100 to convert to seconds.
997	ageing_time=$(ip -j -d link show dev $bridge \
998		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
999	echo $((ageing_time / 100))
1000}
1001
1002declare -A SYSCTL_ORIG
1003sysctl_save()
1004{
1005	local key=$1; shift
1006
1007	SYSCTL_ORIG[$key]=$(sysctl -n $key)
1008}
1009
1010sysctl_set()
1011{
1012	local key=$1; shift
1013	local value=$1; shift
1014
1015	sysctl_save "$key"
1016	sysctl -qw $key="$value"
1017}
1018
1019sysctl_restore()
1020{
1021	local key=$1; shift
1022
1023	sysctl -qw $key="${SYSCTL_ORIG[$key]}"
1024}
1025
1026forwarding_enable()
1027{
1028	sysctl_set net.ipv4.conf.all.forwarding 1
1029	sysctl_set net.ipv6.conf.all.forwarding 1
1030}
1031
1032forwarding_restore()
1033{
1034	sysctl_restore net.ipv6.conf.all.forwarding
1035	sysctl_restore net.ipv4.conf.all.forwarding
1036}
1037
1038declare -A MTU_ORIG
1039mtu_set()
1040{
1041	local dev=$1; shift
1042	local mtu=$1; shift
1043
1044	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
1045	ip link set dev $dev mtu $mtu
1046}
1047
1048mtu_restore()
1049{
1050	local dev=$1; shift
1051
1052	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
1053}
1054
1055tc_offload_check()
1056{
1057	local num_netifs=${1:-$NUM_NETIFS}
1058
1059	for ((i = 1; i <= num_netifs; ++i)); do
1060		ethtool -k ${NETIFS[p$i]} \
1061			| grep "hw-tc-offload: on" &> /dev/null
1062		if [[ $? -ne 0 ]]; then
1063			return 1
1064		fi
1065	done
1066
1067	return 0
1068}
1069
1070trap_install()
1071{
1072	local dev=$1; shift
1073	local direction=$1; shift
1074
1075	# Some devices may not support or need in-hardware trapping of traffic
1076	# (e.g. the veth pairs that this library creates for non-existent
1077	# loopbacks). Use continue instead, so that there is a filter in there
1078	# (some tests check counters), and so that other filters are still
1079	# processed.
1080	tc filter add dev $dev $direction pref 1 \
1081		flower skip_sw action trap 2>/dev/null \
1082	    || tc filter add dev $dev $direction pref 1 \
1083		       flower action continue
1084}
1085
1086trap_uninstall()
1087{
1088	local dev=$1; shift
1089	local direction=$1; shift
1090
1091	tc filter del dev $dev $direction pref 1 flower
1092}
1093
1094__icmp_capture_add_del()
1095{
1096	local add_del=$1; shift
1097	local pref=$1; shift
1098	local vsuf=$1; shift
1099	local tundev=$1; shift
1100	local filter=$1; shift
1101
1102	tc filter $add_del dev "$tundev" ingress \
1103	   proto ip$vsuf pref $pref \
1104	   flower ip_proto icmp$vsuf $filter \
1105	   action pass
1106}
1107
1108icmp_capture_install()
1109{
1110	local tundev=$1; shift
1111	local filter=$1; shift
1112
1113	__icmp_capture_add_del add 100 "" "$tundev" "$filter"
1114}
1115
1116icmp_capture_uninstall()
1117{
1118	local tundev=$1; shift
1119	local filter=$1; shift
1120
1121	__icmp_capture_add_del del 100 "" "$tundev" "$filter"
1122}
1123
1124icmp6_capture_install()
1125{
1126	local tundev=$1; shift
1127	local filter=$1; shift
1128
1129	__icmp_capture_add_del add 100 v6 "$tundev" "$filter"
1130}
1131
1132icmp6_capture_uninstall()
1133{
1134	local tundev=$1; shift
1135	local filter=$1; shift
1136
1137	__icmp_capture_add_del del 100 v6 "$tundev" "$filter"
1138}
1139
1140__vlan_capture_add_del()
1141{
1142	local add_del=$1; shift
1143	local pref=$1; shift
1144	local dev=$1; shift
1145	local filter=$1; shift
1146
1147	tc filter $add_del dev "$dev" ingress \
1148	   proto 802.1q pref $pref \
1149	   flower $filter \
1150	   action pass
1151}
1152
1153vlan_capture_install()
1154{
1155	local dev=$1; shift
1156	local filter=$1; shift
1157
1158	__vlan_capture_add_del add 100 "$dev" "$filter"
1159}
1160
1161vlan_capture_uninstall()
1162{
1163	local dev=$1; shift
1164	local filter=$1; shift
1165
1166	__vlan_capture_add_del del 100 "$dev" "$filter"
1167}
1168
1169__dscp_capture_add_del()
1170{
1171	local add_del=$1; shift
1172	local dev=$1; shift
1173	local base=$1; shift
1174	local dscp;
1175
1176	for prio in {0..7}; do
1177		dscp=$((base + prio))
1178		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1179				       "skip_hw ip_tos $((dscp << 2))"
1180	done
1181}
1182
1183dscp_capture_install()
1184{
1185	local dev=$1; shift
1186	local base=$1; shift
1187
1188	__dscp_capture_add_del add $dev $base
1189}
1190
1191dscp_capture_uninstall()
1192{
1193	local dev=$1; shift
1194	local base=$1; shift
1195
1196	__dscp_capture_add_del del $dev $base
1197}
1198
1199dscp_fetch_stats()
1200{
1201	local dev=$1; shift
1202	local base=$1; shift
1203
1204	for prio in {0..7}; do
1205		local dscp=$((base + prio))
1206		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1207		echo "[$dscp]=$t "
1208	done
1209}
1210
1211matchall_sink_create()
1212{
1213	local dev=$1; shift
1214
1215	tc qdisc add dev $dev clsact
1216	tc filter add dev $dev ingress \
1217	   pref 10000 \
1218	   matchall \
1219	   action drop
1220}
1221
1222cleanup()
1223{
1224	pre_cleanup
1225	defer_scopes_cleanup
1226}
1227
1228multipath_eval()
1229{
1230	local desc="$1"
1231	local weight_rp12=$2
1232	local weight_rp13=$3
1233	local packets_rp12=$4
1234	local packets_rp13=$5
1235	local weights_ratio packets_ratio diff
1236
1237	RET=0
1238
1239	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1240		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1241				| bc -l)
1242	else
1243		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1244				| bc -l)
1245	fi
1246
1247	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1248	       check_err 1 "Packet difference is 0"
1249	       log_test "Multipath"
1250	       log_info "Expected ratio $weights_ratio"
1251	       return
1252	fi
1253
1254	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1255		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1256				| bc -l)
1257	else
1258		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1259				| bc -l)
1260	fi
1261
1262	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1263	diff=${diff#-}
1264
1265	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1266	check_err $? "Too large discrepancy between expected and measured ratios"
1267	log_test "$desc"
1268	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1269}
1270
1271in_ns()
1272{
1273	local name=$1; shift
1274
1275	ip netns exec $name bash <<-EOF
1276		NUM_NETIFS=0
1277		source lib.sh
1278		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1279	EOF
1280}
1281
1282##############################################################################
1283# Tests
1284
1285ping_do()
1286{
1287	local if_name=$1
1288	local dip=$2
1289	local args=$3
1290	local vrf_name
1291
1292	vrf_name=$(master_name_get $if_name)
1293	ip vrf exec $vrf_name \
1294		$PING $args $dip -c $PING_COUNT -i 0.1 \
1295		-w $PING_TIMEOUT &> /dev/null
1296}
1297
1298ping_test()
1299{
1300	RET=0
1301
1302	ping_do $1 $2
1303	check_err $?
1304	log_test "ping$3"
1305}
1306
1307ping_test_fails()
1308{
1309	RET=0
1310
1311	ping_do $1 $2
1312	check_fail $?
1313	log_test "ping fails$3"
1314}
1315
1316ping6_do()
1317{
1318	local if_name=$1
1319	local dip=$2
1320	local args=$3
1321	local vrf_name
1322
1323	vrf_name=$(master_name_get $if_name)
1324	ip vrf exec $vrf_name \
1325		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1326		-w $PING_TIMEOUT &> /dev/null
1327}
1328
1329ping6_test()
1330{
1331	RET=0
1332
1333	ping6_do $1 $2
1334	check_err $?
1335	log_test "ping6$3"
1336}
1337
1338ping6_test_fails()
1339{
1340	RET=0
1341
1342	ping6_do $1 $2
1343	check_fail $?
1344	log_test "ping6 fails$3"
1345}
1346
1347learning_test()
1348{
1349	local bridge=$1
1350	local br_port1=$2	# Connected to `host1_if`.
1351	local host1_if=$3
1352	local host2_if=$4
1353	local mac=de:ad:be:ef:13:37
1354	local ageing_time
1355
1356	RET=0
1357
1358	bridge -j fdb show br $bridge brport $br_port1 \
1359		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1360	check_fail $? "Found FDB record when should not"
1361
1362	# Disable unknown unicast flooding on `br_port1` to make sure
1363	# packets are only forwarded through the port after a matching
1364	# FDB entry was installed.
1365	bridge link set dev $br_port1 flood off
1366
1367	ip link set $host1_if promisc on
1368	tc qdisc add dev $host1_if ingress
1369	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1370		flower dst_mac $mac action drop
1371
1372	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1373	sleep 1
1374
1375	tc -j -s filter show dev $host1_if ingress \
1376		| jq -e ".[] | select(.options.handle == 101) \
1377		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1378	check_fail $? "Packet reached first host when should not"
1379
1380	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1381	sleep 1
1382
1383	bridge -j fdb show br $bridge brport $br_port1 \
1384		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1385	check_err $? "Did not find FDB record when should"
1386
1387	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1388	sleep 1
1389
1390	tc -j -s filter show dev $host1_if ingress \
1391		| jq -e ".[] | select(.options.handle == 101) \
1392		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1393	check_err $? "Packet did not reach second host when should"
1394
1395	# Wait for 10 seconds after the ageing time to make sure FDB
1396	# record was aged-out.
1397	ageing_time=$(bridge_ageing_time_get $bridge)
1398	sleep $((ageing_time + 10))
1399
1400	bridge -j fdb show br $bridge brport $br_port1 \
1401		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1402	check_fail $? "Found FDB record when should not"
1403
1404	bridge link set dev $br_port1 learning off
1405
1406	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1407	sleep 1
1408
1409	bridge -j fdb show br $bridge brport $br_port1 \
1410		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1411	check_fail $? "Found FDB record when should not"
1412
1413	bridge link set dev $br_port1 learning on
1414
1415	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1416	tc qdisc del dev $host1_if ingress
1417	ip link set $host1_if promisc off
1418
1419	bridge link set dev $br_port1 flood on
1420
1421	log_test "FDB learning"
1422}
1423
1424flood_test_do()
1425{
1426	local should_flood=$1
1427	local mac=$2
1428	local ip=$3
1429	local host1_if=$4
1430	local host2_if=$5
1431	local err=0
1432
1433	# Add an ACL on `host2_if` which will tell us whether the packet
1434	# was flooded to it or not.
1435	ip link set $host2_if promisc on
1436	tc qdisc add dev $host2_if ingress
1437	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1438		flower dst_mac $mac action drop
1439
1440	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1441	sleep 1
1442
1443	tc -j -s filter show dev $host2_if ingress \
1444		| jq -e ".[] | select(.options.handle == 101) \
1445		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1446	if [[ $? -ne 0 && $should_flood == "true" || \
1447	      $? -eq 0 && $should_flood == "false" ]]; then
1448		err=1
1449	fi
1450
1451	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1452	tc qdisc del dev $host2_if ingress
1453	ip link set $host2_if promisc off
1454
1455	return $err
1456}
1457
1458flood_unicast_test()
1459{
1460	local br_port=$1
1461	local host1_if=$2
1462	local host2_if=$3
1463	local mac=de:ad:be:ef:13:37
1464	local ip=192.0.2.100
1465
1466	RET=0
1467
1468	bridge link set dev $br_port flood off
1469
1470	flood_test_do false $mac $ip $host1_if $host2_if
1471	check_err $? "Packet flooded when should not"
1472
1473	bridge link set dev $br_port flood on
1474
1475	flood_test_do true $mac $ip $host1_if $host2_if
1476	check_err $? "Packet was not flooded when should"
1477
1478	log_test "Unknown unicast flood"
1479}
1480
1481flood_multicast_test()
1482{
1483	local br_port=$1
1484	local host1_if=$2
1485	local host2_if=$3
1486	local mac=01:00:5e:00:00:01
1487	local ip=239.0.0.1
1488
1489	RET=0
1490
1491	bridge link set dev $br_port mcast_flood off
1492
1493	flood_test_do false $mac $ip $host1_if $host2_if
1494	check_err $? "Packet flooded when should not"
1495
1496	bridge link set dev $br_port mcast_flood on
1497
1498	flood_test_do true $mac $ip $host1_if $host2_if
1499	check_err $? "Packet was not flooded when should"
1500
1501	log_test "Unregistered multicast flood"
1502}
1503
1504flood_test()
1505{
1506	# `br_port` is connected to `host2_if`
1507	local br_port=$1
1508	local host1_if=$2
1509	local host2_if=$3
1510
1511	flood_unicast_test $br_port $host1_if $host2_if
1512	flood_multicast_test $br_port $host1_if $host2_if
1513}
1514
1515__start_traffic()
1516{
1517	local pktsize=$1; shift
1518	local proto=$1; shift
1519	local h_in=$1; shift    # Where the traffic egresses the host
1520	local sip=$1; shift
1521	local dip=$1; shift
1522	local dmac=$1; shift
1523	local -a mz_args=("$@")
1524
1525	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1526		-a own -b $dmac -t "$proto" -q "${mz_args[@]}" &
1527	sleep 1
1528}
1529
1530start_traffic_pktsize()
1531{
1532	local pktsize=$1; shift
1533	local h_in=$1; shift
1534	local sip=$1; shift
1535	local dip=$1; shift
1536	local dmac=$1; shift
1537	local -a mz_args=("$@")
1538
1539	__start_traffic $pktsize udp "$h_in" "$sip" "$dip" "$dmac" \
1540			"${mz_args[@]}"
1541}
1542
1543start_tcp_traffic_pktsize()
1544{
1545	local pktsize=$1; shift
1546	local h_in=$1; shift
1547	local sip=$1; shift
1548	local dip=$1; shift
1549	local dmac=$1; shift
1550	local -a mz_args=("$@")
1551
1552	__start_traffic $pktsize tcp "$h_in" "$sip" "$dip" "$dmac" \
1553			"${mz_args[@]}"
1554}
1555
1556start_traffic()
1557{
1558	local h_in=$1; shift
1559	local sip=$1; shift
1560	local dip=$1; shift
1561	local dmac=$1; shift
1562	local -a mz_args=("$@")
1563
1564	start_traffic_pktsize 8000 "$h_in" "$sip" "$dip" "$dmac" \
1565			      "${mz_args[@]}"
1566}
1567
1568start_tcp_traffic()
1569{
1570	local h_in=$1; shift
1571	local sip=$1; shift
1572	local dip=$1; shift
1573	local dmac=$1; shift
1574	local -a mz_args=("$@")
1575
1576	start_tcp_traffic_pktsize 8000 "$h_in" "$sip" "$dip" "$dmac" \
1577				  "${mz_args[@]}"
1578}
1579
1580stop_traffic()
1581{
1582	local pid=${1-%%}; shift
1583
1584	kill_process "$pid"
1585}
1586
1587declare -A cappid
1588declare -A capfile
1589declare -A capout
1590
1591tcpdump_start()
1592{
1593	local if_name=$1; shift
1594	local ns=$1; shift
1595
1596	capfile[$if_name]=$(mktemp)
1597	capout[$if_name]=$(mktemp)
1598
1599	if [ -z $ns ]; then
1600		ns_cmd=""
1601	else
1602		ns_cmd="ip netns exec ${ns}"
1603	fi
1604
1605	if [ -z $SUDO_USER ] ; then
1606		capuser=""
1607	else
1608		capuser="-Z $SUDO_USER"
1609	fi
1610
1611	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1612		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1613		> "${capout[$if_name]}" 2>&1 &
1614	cappid[$if_name]=$!
1615
1616	sleep 1
1617}
1618
1619tcpdump_stop()
1620{
1621	local if_name=$1
1622	local pid=${cappid[$if_name]}
1623
1624	$ns_cmd kill "$pid" && wait "$pid"
1625	sleep 1
1626}
1627
1628tcpdump_cleanup()
1629{
1630	local if_name=$1
1631
1632	rm ${capfile[$if_name]} ${capout[$if_name]}
1633}
1634
1635tcpdump_show()
1636{
1637	local if_name=$1
1638
1639	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1640}
1641
1642# return 0 if the packet wasn't seen on host2_if or 1 if it was
1643mcast_packet_test()
1644{
1645	local mac=$1
1646	local src_ip=$2
1647	local ip=$3
1648	local host1_if=$4
1649	local host2_if=$5
1650	local seen=0
1651	local tc_proto="ip"
1652	local mz_v6arg=""
1653
1654	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1655	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1656		tc_proto="ipv6"
1657		mz_v6arg="-6"
1658	fi
1659
1660	# Add an ACL on `host2_if` which will tell us whether the packet
1661	# was received by it or not.
1662	tc qdisc add dev $host2_if ingress
1663	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1664		flower ip_proto udp dst_mac $mac action drop
1665
1666	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1667	sleep 1
1668
1669	tc -j -s filter show dev $host2_if ingress \
1670		| jq -e ".[] | select(.options.handle == 101) \
1671		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1672	if [[ $? -eq 0 ]]; then
1673		seen=1
1674	fi
1675
1676	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1677	tc qdisc del dev $host2_if ingress
1678
1679	return $seen
1680}
1681
1682brmcast_check_sg_entries()
1683{
1684	local report=$1; shift
1685	local slist=("$@")
1686	local sarg=""
1687
1688	for src in "${slist[@]}"; do
1689		sarg="${sarg} and .source_list[].address == \"$src\""
1690	done
1691	bridge -j -d -s mdb show dev br0 \
1692		| jq -e ".[].mdb[] | \
1693			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1694	check_err $? "Wrong *,G entry source list after $report report"
1695
1696	for sgent in "${slist[@]}"; do
1697		bridge -j -d -s mdb show dev br0 \
1698			| jq -e ".[].mdb[] | \
1699				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1700		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1701	done
1702}
1703
1704brmcast_check_sg_fwding()
1705{
1706	local should_fwd=$1; shift
1707	local sources=("$@")
1708
1709	for src in "${sources[@]}"; do
1710		local retval=0
1711
1712		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1713		retval=$?
1714		if [ $should_fwd -eq 1 ]; then
1715			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1716		else
1717			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1718		fi
1719	done
1720}
1721
1722brmcast_check_sg_state()
1723{
1724	local is_blocked=$1; shift
1725	local sources=("$@")
1726	local should_fail=1
1727
1728	if [ $is_blocked -eq 1 ]; then
1729		should_fail=0
1730	fi
1731
1732	for src in "${sources[@]}"; do
1733		bridge -j -d -s mdb show dev br0 \
1734			| jq -e ".[].mdb[] | \
1735				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1736				 .source_list[] |
1737				 select(.address == \"$src\") |
1738				 select(.timer == \"0.00\")" &>/dev/null
1739		check_err_fail $should_fail $? "Entry $src has zero timer"
1740
1741		bridge -j -d -s mdb show dev br0 \
1742			| jq -e ".[].mdb[] | \
1743				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1744				 .flags[] == \"blocked\")" &>/dev/null
1745		check_err_fail $should_fail $? "Entry $src has blocked flag"
1746	done
1747}
1748
1749mc_join()
1750{
1751	local if_name=$1
1752	local group=$2
1753	local vrf_name=$(master_name_get $if_name)
1754
1755	# We don't care about actual reception, just about joining the
1756	# IP multicast group and adding the L2 address to the device's
1757	# MAC filtering table
1758	ip vrf exec $vrf_name \
1759		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1760	mreceive_pid=$!
1761
1762	sleep 1
1763}
1764
1765mc_leave()
1766{
1767	kill "$mreceive_pid" && wait "$mreceive_pid"
1768}
1769
1770mc_send()
1771{
1772	local if_name=$1
1773	local groups=$2
1774	local vrf_name=$(master_name_get $if_name)
1775
1776	ip vrf exec $vrf_name \
1777		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1778}
1779
1780adf_mcd_start()
1781{
1782	local ifs=("$@")
1783
1784	local table_name="$MCD_TABLE_NAME"
1785	local smcroutedir
1786	local pid
1787	local if
1788	local i
1789
1790	check_command "$MCD" || return 1
1791	check_command "$MC_CLI" || return 1
1792
1793	smcroutedir=$(mktemp -d)
1794	defer rm -rf "$smcroutedir"
1795
1796	for ((i = 1; i <= NUM_NETIFS; ++i)); do
1797		echo "phyint ${NETIFS[p$i]} enable" >> \
1798			"$smcroutedir/$table_name.conf"
1799	done
1800
1801	for if in "${ifs[@]}"; do
1802		if ! ip_link_has_flag "$if" MULTICAST; then
1803			ip link set dev "$if" multicast on
1804			defer ip link set dev "$if" multicast off
1805		fi
1806
1807		echo "phyint $if enable" >> \
1808			"$smcroutedir/$table_name.conf"
1809	done
1810
1811	"$MCD" -N -I "$table_name" -f "$smcroutedir/$table_name.conf" \
1812		-P "$smcroutedir/$table_name.pid"
1813	busywait "$BUSYWAIT_TIMEOUT" test -e "$smcroutedir/$table_name.pid"
1814	pid=$(cat "$smcroutedir/$table_name.pid")
1815	defer kill_process "$pid"
1816}
1817
1818mc_cli()
1819{
1820	local table_name="$MCD_TABLE_NAME"
1821
1822        "$MC_CLI" -I "$table_name" "$@"
1823}
1824
1825start_ip_monitor()
1826{
1827	local mtype=$1; shift
1828	local ip=${1-ip}; shift
1829
1830	# start the monitor in the background
1831	tmpfile=`mktemp /var/run/nexthoptestXXX`
1832	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1833	sleep 0.2
1834	echo "$mpid $tmpfile"
1835}
1836
1837stop_ip_monitor()
1838{
1839	local mpid=$1; shift
1840	local tmpfile=$1; shift
1841	local el=$1; shift
1842	local what=$1; shift
1843
1844	sleep 0.2
1845	kill $mpid
1846	local lines=`grep '^\w' $tmpfile | wc -l`
1847	test $lines -eq $el
1848	check_err $? "$what: $lines lines of events, expected $el"
1849	rm -rf $tmpfile
1850}
1851
1852hw_stats_monitor_test()
1853{
1854	local dev=$1; shift
1855	local type=$1; shift
1856	local make_suitable=$1; shift
1857	local make_unsuitable=$1; shift
1858	local ip=${1-ip}; shift
1859
1860	RET=0
1861
1862	# Expect a notification about enablement.
1863	local ipmout=$(start_ip_monitor stats "$ip")
1864	$ip stats set dev $dev ${type}_stats on
1865	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1866
1867	# Expect a notification about offload.
1868	local ipmout=$(start_ip_monitor stats "$ip")
1869	$make_suitable
1870	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1871
1872	# Expect a notification about loss of offload.
1873	local ipmout=$(start_ip_monitor stats "$ip")
1874	$make_unsuitable
1875	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1876
1877	# Expect a notification about disablement
1878	local ipmout=$(start_ip_monitor stats "$ip")
1879	$ip stats set dev $dev ${type}_stats off
1880	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1881
1882	log_test "${type}_stats notifications"
1883}
1884
1885ipv4_to_bytes()
1886{
1887	local IP=$1; shift
1888
1889	printf '%02x:' ${IP//./ } |
1890	    sed 's/:$//'
1891}
1892
1893# Convert a given IPv6 address, `IP' such that the :: token, if present, is
1894# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1895# digits. An optional `BYTESEP' parameter can be given to further separate
1896# individual bytes of each 16-bit group.
1897expand_ipv6()
1898{
1899	local IP=$1; shift
1900	local bytesep=$1; shift
1901
1902	local cvt_ip=${IP/::/_}
1903	local colons=${cvt_ip//[^:]/}
1904	local allcol=:::::::
1905	# IP where :: -> the appropriate number of colons:
1906	local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1907
1908	echo $allcol_ip | tr : '\n' |
1909	    sed s/^/0000/ |
1910	    sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1911	    tr '\n' : |
1912	    sed 's/:$//'
1913}
1914
1915ipv6_to_bytes()
1916{
1917	local IP=$1; shift
1918
1919	expand_ipv6 "$IP" :
1920}
1921
1922u16_to_bytes()
1923{
1924	local u16=$1; shift
1925
1926	printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1927}
1928
1929# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1930# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1931# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1932# stands for 00:00.
1933payload_template_calc_checksum()
1934{
1935	local payload=$1; shift
1936
1937	(
1938	    # Set input radix.
1939	    echo "16i"
1940	    # Push zero for the initial checksum.
1941	    echo 0
1942
1943	    # Pad the payload with a terminating 00: in case we get an odd
1944	    # number of bytes.
1945	    echo "${payload%:}:00:" |
1946		sed 's/CHECKSUM/00:00/g' |
1947		tr '[:lower:]' '[:upper:]' |
1948		# Add the word to the checksum.
1949		sed 's/\(..\):\(..\):/\1\2+\n/g' |
1950		# Strip the extra odd byte we pushed if left unconverted.
1951		sed 's/\(..\):$//'
1952
1953	    echo "10000 ~ +"	# Calculate and add carry.
1954	    echo "FFFF r - p"	# Bit-flip and print.
1955	) |
1956	    dc |
1957	    tr '[:upper:]' '[:lower:]'
1958}
1959
1960payload_template_expand_checksum()
1961{
1962	local payload=$1; shift
1963	local checksum=$1; shift
1964
1965	local ckbytes=$(u16_to_bytes $checksum)
1966
1967	echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1968}
1969
1970payload_template_nbytes()
1971{
1972	local payload=$1; shift
1973
1974	payload_template_expand_checksum "${payload%:}" 0 |
1975		sed 's/:/\n/g' | wc -l
1976}
1977
1978igmpv3_is_in_get()
1979{
1980	local GRP=$1; shift
1981	local sources=("$@")
1982
1983	local igmpv3
1984	local nsources=$(u16_to_bytes ${#sources[@]})
1985
1986	# IS_IN ( $sources )
1987	igmpv3=$(:
1988		)"22:"$(			: Type - Membership Report
1989		)"00:"$(			: Reserved
1990		)"CHECKSUM:"$(			: Checksum
1991		)"00:00:"$(			: Reserved
1992		)"00:01:"$(			: Number of Group Records
1993		)"01:"$(			: Record Type - IS_IN
1994		)"00:"$(			: Aux Data Len
1995		)"${nsources}:"$(		: Number of Sources
1996		)"$(ipv4_to_bytes $GRP):"$(	: Multicast Address
1997		)"$(for src in "${sources[@]}"; do
1998			ipv4_to_bytes $src
1999			echo -n :
2000		    done)"$(			: Source Addresses
2001		)
2002	local checksum=$(payload_template_calc_checksum "$igmpv3")
2003
2004	payload_template_expand_checksum "$igmpv3" $checksum
2005}
2006
2007igmpv2_leave_get()
2008{
2009	local GRP=$1; shift
2010
2011	local payload=$(:
2012		)"17:"$(			: Type - Leave Group
2013		)"00:"$(			: Max Resp Time - not meaningful
2014		)"CHECKSUM:"$(			: Checksum
2015		)"$(ipv4_to_bytes $GRP)"$(	: Group Address
2016		)
2017	local checksum=$(payload_template_calc_checksum "$payload")
2018
2019	payload_template_expand_checksum "$payload" $checksum
2020}
2021
2022mldv2_is_in_get()
2023{
2024	local SIP=$1; shift
2025	local GRP=$1; shift
2026	local sources=("$@")
2027
2028	local hbh
2029	local icmpv6
2030	local nsources=$(u16_to_bytes ${#sources[@]})
2031
2032	hbh=$(:
2033		)"3a:"$(			: Next Header - ICMPv6
2034		)"00:"$(			: Hdr Ext Len
2035		)"00:00:00:00:00:00:"$(		: Options and Padding
2036		)
2037
2038	icmpv6=$(:
2039		)"8f:"$(			: Type - MLDv2 Report
2040		)"00:"$(			: Code
2041		)"CHECKSUM:"$(			: Checksum
2042		)"00:00:"$(			: Reserved
2043		)"00:01:"$(			: Number of Group Records
2044		)"01:"$(			: Record Type - IS_IN
2045		)"00:"$(			: Aux Data Len
2046		)"${nsources}:"$(		: Number of Sources
2047		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
2048		)"$(for src in "${sources[@]}"; do
2049			ipv6_to_bytes $src
2050			echo -n :
2051		    done)"$(			: Source Addresses
2052		)
2053
2054	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
2055	local sudohdr=$(:
2056		)"$(ipv6_to_bytes $SIP):"$(	: SIP
2057		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
2058	        )"${len}:"$(			: Upper-layer length
2059	        )"00:3a:"$(			: Zero and next-header
2060	        )
2061	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
2062
2063	payload_template_expand_checksum "$hbh$icmpv6" $checksum
2064}
2065
2066mldv1_done_get()
2067{
2068	local SIP=$1; shift
2069	local GRP=$1; shift
2070
2071	local hbh
2072	local icmpv6
2073
2074	hbh=$(:
2075		)"3a:"$(			: Next Header - ICMPv6
2076		)"00:"$(			: Hdr Ext Len
2077		)"00:00:00:00:00:00:"$(		: Options and Padding
2078		)
2079
2080	icmpv6=$(:
2081		)"84:"$(			: Type - MLDv1 Done
2082		)"00:"$(			: Code
2083		)"CHECKSUM:"$(			: Checksum
2084		)"00:00:"$(			: Max Resp Delay - not meaningful
2085		)"00:00:"$(			: Reserved
2086		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
2087		)
2088
2089	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
2090	local sudohdr=$(:
2091		)"$(ipv6_to_bytes $SIP):"$(	: SIP
2092		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
2093	        )"${len}:"$(			: Upper-layer length
2094	        )"00:3a:"$(			: Zero and next-header
2095	        )
2096	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
2097
2098	payload_template_expand_checksum "$hbh$icmpv6" $checksum
2099}
2100
2101bail_on_lldpad()
2102{
2103	local reason1="$1"; shift
2104	local reason2="$1"; shift
2105	local caller=${FUNCNAME[1]}
2106	local src=${BASH_SOURCE[1]}
2107
2108	if systemctl is-active --quiet lldpad; then
2109
2110		cat >/dev/stderr <<-EOF
2111		WARNING: lldpad is running
2112
2113			lldpad will likely $reason1, and this test will
2114			$reason2. Both are not supported at the same time,
2115			one of them is arbitrarily going to overwrite the
2116			other. That will cause spurious failures (or, unlikely,
2117			passes) of this test.
2118		EOF
2119
2120		if [[ -z $ALLOW_LLDPAD ]]; then
2121			cat >/dev/stderr <<-EOF
2122
2123				If you want to run the test anyway, please set
2124				an environment variable ALLOW_LLDPAD to a
2125				non-empty string.
2126			EOF
2127			log_test_skip $src:$caller
2128			exit $EXIT_STATUS
2129		else
2130			return
2131		fi
2132	fi
2133}
2134
2135absval()
2136{
2137	local v=$1; shift
2138
2139	echo $((v > 0 ? v : -v))
2140}
2141
2142has_unicast_flt()
2143{
2144	local dev=$1; shift
2145	local mac_addr=$(mac_get $dev)
2146	local tmp=$(ether_addr_to_u64 $mac_addr)
2147	local promisc
2148
2149	ip link set $dev up
2150	ip link add link $dev name macvlan-tmp type macvlan mode private
2151	ip link set macvlan-tmp address $(u64_to_ether_addr $((tmp + 1)))
2152	ip link set macvlan-tmp up
2153
2154	promisc=$(ip -j -d link show dev $dev | jq -r '.[].promiscuity')
2155
2156	ip link del macvlan-tmp
2157
2158	[[ $promisc == 1 ]] && echo "no" || echo "yes"
2159}
2160