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