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