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