1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Kselftest framework requirement - SKIP code is 4.
8ksft_skip=4
9
10# Can be overridden by the configuration file.
11PING=${PING:=ping}
12PING6=${PING6:=ping6}
13MZ=${MZ:=mausezahn}
14ARPING=${ARPING:=arping}
15TEAMD=${TEAMD:=teamd}
16WAIT_TIME=${WAIT_TIME:=5}
17PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
18PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
19NETIF_TYPE=${NETIF_TYPE:=veth}
20NETIF_CREATE=${NETIF_CREATE:=yes}
21MCD=${MCD:=smcrouted}
22MC_CLI=${MC_CLI:=smcroutectl}
23PING_COUNT=${PING_COUNT:=10}
24PING_TIMEOUT=${PING_TIMEOUT:=5}
25WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
26INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
27REQUIRE_JQ=${REQUIRE_JQ:=yes}
28REQUIRE_MZ=${REQUIRE_MZ:=yes}
29
30relative_path="${BASH_SOURCE%/*}"
31if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
32	relative_path="."
33fi
34
35if [[ -f $relative_path/forwarding.config ]]; then
36	source "$relative_path/forwarding.config"
37fi
38
39##############################################################################
40# Sanity checks
41
42check_tc_version()
43{
44	tc -j &> /dev/null
45	if [[ $? -ne 0 ]]; then
46		echo "SKIP: iproute2 too old; tc is missing JSON support"
47		exit $ksft_skip
48	fi
49}
50
51# Old versions of tc don't understand "mpls_uc"
52check_tc_mpls_support()
53{
54	local dev=$1; shift
55
56	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
57		matchall action pipe &> /dev/null
58	if [[ $? -ne 0 ]]; then
59		echo "SKIP: iproute2 too old; tc is missing MPLS support"
60		return $ksft_skip
61	fi
62	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
63		matchall
64}
65
66# Old versions of tc produce invalid json output for mpls lse statistics
67check_tc_mpls_lse_stats()
68{
69	local dev=$1; shift
70	local ret;
71
72	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
73		flower mpls lse depth 2                                 \
74		action continue &> /dev/null
75
76	if [[ $? -ne 0 ]]; then
77		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
78		return $ksft_skip
79	fi
80
81	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
82	ret=$?
83	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
84		flower
85
86	if [[ $ret -ne 0 ]]; then
87		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
88		return $ksft_skip
89	fi
90}
91
92check_tc_shblock_support()
93{
94	tc filter help 2>&1 | grep block &> /dev/null
95	if [[ $? -ne 0 ]]; then
96		echo "SKIP: iproute2 too old; tc is missing shared block support"
97		exit $ksft_skip
98	fi
99}
100
101check_tc_chain_support()
102{
103	tc help 2>&1|grep chain &> /dev/null
104	if [[ $? -ne 0 ]]; then
105		echo "SKIP: iproute2 too old; tc is missing chain support"
106		exit $ksft_skip
107	fi
108}
109
110check_tc_action_hw_stats_support()
111{
112	tc actions help 2>&1 | grep -q hw_stats
113	if [[ $? -ne 0 ]]; then
114		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
115		exit $ksft_skip
116	fi
117}
118
119check_ethtool_lanes_support()
120{
121	ethtool --help 2>&1| grep lanes &> /dev/null
122	if [[ $? -ne 0 ]]; then
123		echo "SKIP: ethtool too old; it is missing lanes support"
124		exit $ksft_skip
125	fi
126}
127
128if [[ "$(id -u)" -ne 0 ]]; then
129	echo "SKIP: need root privileges"
130	exit $ksft_skip
131fi
132
133if [[ "$CHECK_TC" = "yes" ]]; then
134	check_tc_version
135fi
136
137require_command()
138{
139	local cmd=$1; shift
140
141	if [[ ! -x "$(command -v "$cmd")" ]]; then
142		echo "SKIP: $cmd not installed"
143		exit $ksft_skip
144	fi
145}
146
147if [[ "$REQUIRE_JQ" = "yes" ]]; then
148	require_command jq
149fi
150if [[ "$REQUIRE_MZ" = "yes" ]]; then
151	require_command $MZ
152fi
153
154if [[ ! -v NUM_NETIFS ]]; then
155	echo "SKIP: importer does not define \"NUM_NETIFS\""
156	exit $ksft_skip
157fi
158
159##############################################################################
160# Command line options handling
161
162count=0
163
164while [[ $# -gt 0 ]]; do
165	if [[ "$count" -eq "0" ]]; then
166		unset NETIFS
167		declare -A NETIFS
168	fi
169	count=$((count + 1))
170	NETIFS[p$count]="$1"
171	shift
172done
173
174##############################################################################
175# Network interfaces configuration
176
177create_netif_veth()
178{
179	local i
180
181	for ((i = 1; i <= NUM_NETIFS; ++i)); do
182		local j=$((i+1))
183
184		ip link show dev ${NETIFS[p$i]} &> /dev/null
185		if [[ $? -ne 0 ]]; then
186			ip link add ${NETIFS[p$i]} type veth \
187				peer name ${NETIFS[p$j]}
188			if [[ $? -ne 0 ]]; then
189				echo "Failed to create netif"
190				exit 1
191			fi
192		fi
193		i=$j
194	done
195}
196
197create_netif()
198{
199	case "$NETIF_TYPE" in
200	veth) create_netif_veth
201	      ;;
202	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
203	   exit 1
204	   ;;
205	esac
206}
207
208if [[ "$NETIF_CREATE" = "yes" ]]; then
209	create_netif
210fi
211
212for ((i = 1; i <= NUM_NETIFS; ++i)); do
213	ip link show dev ${NETIFS[p$i]} &> /dev/null
214	if [[ $? -ne 0 ]]; then
215		echo "SKIP: could not find all required interfaces"
216		exit $ksft_skip
217	fi
218done
219
220##############################################################################
221# Helpers
222
223# Exit status to return at the end. Set in case one of the tests fails.
224EXIT_STATUS=0
225# Per-test return value. Clear at the beginning of each test.
226RET=0
227
228check_err()
229{
230	local err=$1
231	local msg=$2
232
233	if [[ $RET -eq 0 && $err -ne 0 ]]; then
234		RET=$err
235		retmsg=$msg
236	fi
237}
238
239check_fail()
240{
241	local err=$1
242	local msg=$2
243
244	if [[ $RET -eq 0 && $err -eq 0 ]]; then
245		RET=1
246		retmsg=$msg
247	fi
248}
249
250check_err_fail()
251{
252	local should_fail=$1; shift
253	local err=$1; shift
254	local what=$1; shift
255
256	if ((should_fail)); then
257		check_fail $err "$what succeeded, but should have failed"
258	else
259		check_err $err "$what failed"
260	fi
261}
262
263log_test()
264{
265	local test_name=$1
266	local opt_str=$2
267
268	if [[ $# -eq 2 ]]; then
269		opt_str="($opt_str)"
270	fi
271
272	if [[ $RET -ne 0 ]]; then
273		EXIT_STATUS=1
274		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
275		if [[ ! -z "$retmsg" ]]; then
276			printf "\t%s\n" "$retmsg"
277		fi
278		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
279			echo "Hit enter to continue, 'q' to quit"
280			read a
281			[ "$a" = "q" ] && exit 1
282		fi
283		return 1
284	fi
285
286	printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
287	return 0
288}
289
290log_test_skip()
291{
292	local test_name=$1
293	local opt_str=$2
294
295	printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
296	return 0
297}
298
299log_info()
300{
301	local msg=$1
302
303	echo "INFO: $msg"
304}
305
306busywait()
307{
308	local timeout=$1; shift
309
310	local start_time="$(date -u +%s%3N)"
311	while true
312	do
313		local out
314		out=$("$@")
315		local ret=$?
316		if ((!ret)); then
317			echo -n "$out"
318			return 0
319		fi
320
321		local current_time="$(date -u +%s%3N)"
322		if ((current_time - start_time > timeout)); then
323			echo -n "$out"
324			return 1
325		fi
326	done
327}
328
329not()
330{
331	"$@"
332	[[ $? != 0 ]]
333}
334
335get_max()
336{
337	local arr=("$@")
338
339	max=${arr[0]}
340	for cur in ${arr[@]}; do
341		if [[ $cur -gt $max ]]; then
342			max=$cur
343		fi
344	done
345
346	echo $max
347}
348
349grep_bridge_fdb()
350{
351	local addr=$1; shift
352	local word
353	local flag
354
355	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
356		word=$1; shift
357		if [ "$1" == "-v" ]; then
358			flag=$1; shift
359		fi
360	fi
361
362	$@ | grep $addr | grep $flag "$word"
363}
364
365wait_for_port_up()
366{
367	"$@" | grep -q "Link detected: yes"
368}
369
370wait_for_offload()
371{
372	"$@" | grep -q offload
373}
374
375wait_for_trap()
376{
377	"$@" | grep -q trap
378}
379
380until_counter_is()
381{
382	local expr=$1; shift
383	local current=$("$@")
384
385	echo $((current))
386	((current $expr))
387}
388
389busywait_for_counter()
390{
391	local timeout=$1; shift
392	local delta=$1; shift
393
394	local base=$("$@")
395	busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
396}
397
398setup_wait_dev()
399{
400	local dev=$1; shift
401	local wait_time=${1:-$WAIT_TIME}; shift
402
403	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
404
405	if (($?)); then
406		check_err 1
407		log_test setup_wait_dev ": Interface $dev does not come up."
408		exit 1
409	fi
410}
411
412setup_wait_dev_with_timeout()
413{
414	local dev=$1; shift
415	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
416	local wait_time=${1:-$WAIT_TIME}; shift
417	local i
418
419	for ((i = 1; i <= $max_iterations; ++i)); do
420		ip link show dev $dev up \
421			| grep 'state UP' &> /dev/null
422		if [[ $? -ne 0 ]]; then
423			sleep 1
424		else
425			sleep $wait_time
426			return 0
427		fi
428	done
429
430	return 1
431}
432
433setup_wait()
434{
435	local num_netifs=${1:-$NUM_NETIFS}
436	local i
437
438	for ((i = 1; i <= num_netifs; ++i)); do
439		setup_wait_dev ${NETIFS[p$i]} 0
440	done
441
442	# Make sure links are ready.
443	sleep $WAIT_TIME
444}
445
446cmd_jq()
447{
448	local cmd=$1
449	local jq_exp=$2
450	local jq_opts=$3
451	local ret
452	local output
453
454	output="$($cmd)"
455	# it the command fails, return error right away
456	ret=$?
457	if [[ $ret -ne 0 ]]; then
458		return $ret
459	fi
460	output=$(echo $output | jq -r $jq_opts "$jq_exp")
461	ret=$?
462	if [[ $ret -ne 0 ]]; then
463		return $ret
464	fi
465	echo $output
466	# return success only in case of non-empty output
467	[ ! -z "$output" ]
468}
469
470lldpad_app_wait_set()
471{
472	local dev=$1; shift
473
474	while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do
475		echo "$dev: waiting for lldpad to push pending APP updates"
476		sleep 5
477	done
478}
479
480lldpad_app_wait_del()
481{
482	# Give lldpad a chance to push down the changes. If the device is downed
483	# too soon, the updates will be left pending. However, they will have
484	# been struck off the lldpad's DB already, so we won't be able to tell
485	# they are pending. Then on next test iteration this would cause
486	# weirdness as newly-added APP rules conflict with the old ones,
487	# sometimes getting stuck in an "unknown" state.
488	sleep 5
489}
490
491pre_cleanup()
492{
493	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
494		echo "Pausing before cleanup, hit any key to continue"
495		read
496	fi
497}
498
499vrf_prepare()
500{
501	ip -4 rule add pref 32765 table local
502	ip -4 rule del pref 0
503	ip -6 rule add pref 32765 table local
504	ip -6 rule del pref 0
505}
506
507vrf_cleanup()
508{
509	ip -6 rule add pref 0 table local
510	ip -6 rule del pref 32765
511	ip -4 rule add pref 0 table local
512	ip -4 rule del pref 32765
513}
514
515__last_tb_id=0
516declare -A __TB_IDS
517
518__vrf_td_id_assign()
519{
520	local vrf_name=$1
521
522	__last_tb_id=$((__last_tb_id + 1))
523	__TB_IDS[$vrf_name]=$__last_tb_id
524	return $__last_tb_id
525}
526
527__vrf_td_id_lookup()
528{
529	local vrf_name=$1
530
531	return ${__TB_IDS[$vrf_name]}
532}
533
534vrf_create()
535{
536	local vrf_name=$1
537	local tb_id
538
539	__vrf_td_id_assign $vrf_name
540	tb_id=$?
541
542	ip link add dev $vrf_name type vrf table $tb_id
543	ip -4 route add table $tb_id unreachable default metric 4278198272
544	ip -6 route add table $tb_id unreachable default metric 4278198272
545}
546
547vrf_destroy()
548{
549	local vrf_name=$1
550	local tb_id
551
552	__vrf_td_id_lookup $vrf_name
553	tb_id=$?
554
555	ip -6 route del table $tb_id unreachable default metric 4278198272
556	ip -4 route del table $tb_id unreachable default metric 4278198272
557	ip link del dev $vrf_name
558}
559
560__addr_add_del()
561{
562	local if_name=$1
563	local add_del=$2
564	local array
565
566	shift
567	shift
568	array=("${@}")
569
570	for addrstr in "${array[@]}"; do
571		ip address $add_del $addrstr dev $if_name
572	done
573}
574
575__simple_if_init()
576{
577	local if_name=$1; shift
578	local vrf_name=$1; shift
579	local addrs=("${@}")
580
581	ip link set dev $if_name master $vrf_name
582	ip link set dev $if_name up
583
584	__addr_add_del $if_name add "${addrs[@]}"
585}
586
587__simple_if_fini()
588{
589	local if_name=$1; shift
590	local addrs=("${@}")
591
592	__addr_add_del $if_name del "${addrs[@]}"
593
594	ip link set dev $if_name down
595	ip link set dev $if_name nomaster
596}
597
598simple_if_init()
599{
600	local if_name=$1
601	local vrf_name
602	local array
603
604	shift
605	vrf_name=v$if_name
606	array=("${@}")
607
608	vrf_create $vrf_name
609	ip link set dev $vrf_name up
610	__simple_if_init $if_name $vrf_name "${array[@]}"
611}
612
613simple_if_fini()
614{
615	local if_name=$1
616	local vrf_name
617	local array
618
619	shift
620	vrf_name=v$if_name
621	array=("${@}")
622
623	__simple_if_fini $if_name "${array[@]}"
624	vrf_destroy $vrf_name
625}
626
627tunnel_create()
628{
629	local name=$1; shift
630	local type=$1; shift
631	local local=$1; shift
632	local remote=$1; shift
633
634	ip link add name $name type $type \
635	   local $local remote $remote "$@"
636	ip link set dev $name up
637}
638
639tunnel_destroy()
640{
641	local name=$1; shift
642
643	ip link del dev $name
644}
645
646vlan_create()
647{
648	local if_name=$1; shift
649	local vid=$1; shift
650	local vrf=$1; shift
651	local ips=("${@}")
652	local name=$if_name.$vid
653
654	ip link add name $name link $if_name type vlan id $vid
655	if [ "$vrf" != "" ]; then
656		ip link set dev $name master $vrf
657	fi
658	ip link set dev $name up
659	__addr_add_del $name add "${ips[@]}"
660}
661
662vlan_destroy()
663{
664	local if_name=$1; shift
665	local vid=$1; shift
666	local name=$if_name.$vid
667
668	ip link del dev $name
669}
670
671team_create()
672{
673	local if_name=$1; shift
674	local mode=$1; shift
675
676	require_command $TEAMD
677	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
678	for slave in "$@"; do
679		ip link set dev $slave down
680		ip link set dev $slave master $if_name
681		ip link set dev $slave up
682	done
683	ip link set dev $if_name up
684}
685
686team_destroy()
687{
688	local if_name=$1; shift
689
690	$TEAMD -t $if_name -k
691}
692
693master_name_get()
694{
695	local if_name=$1
696
697	ip -j link show dev $if_name | jq -r '.[]["master"]'
698}
699
700link_stats_get()
701{
702	local if_name=$1; shift
703	local dir=$1; shift
704	local stat=$1; shift
705
706	ip -j -s link show dev $if_name \
707		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
708}
709
710link_stats_tx_packets_get()
711{
712	link_stats_get $1 tx packets
713}
714
715link_stats_rx_errors_get()
716{
717	link_stats_get $1 rx errors
718}
719
720tc_rule_stats_get()
721{
722	local dev=$1; shift
723	local pref=$1; shift
724	local dir=$1; shift
725	local selector=${1:-.packets}; shift
726
727	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
728	    | jq ".[1].options.actions[].stats$selector"
729}
730
731tc_rule_handle_stats_get()
732{
733	local id=$1; shift
734	local handle=$1; shift
735	local selector=${1:-.packets}; shift
736
737	tc -j -s filter show $id \
738	    | jq ".[] | select(.options.handle == $handle) | \
739		  .options.actions[0].stats$selector"
740}
741
742ethtool_stats_get()
743{
744	local dev=$1; shift
745	local stat=$1; shift
746
747	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
748}
749
750qdisc_stats_get()
751{
752	local dev=$1; shift
753	local handle=$1; shift
754	local selector=$1; shift
755
756	tc -j -s qdisc show dev "$dev" \
757	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
758}
759
760qdisc_parent_stats_get()
761{
762	local dev=$1; shift
763	local parent=$1; shift
764	local selector=$1; shift
765
766	tc -j -s qdisc show dev "$dev" invisible \
767	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
768}
769
770ipv6_stats_get()
771{
772	local dev=$1; shift
773	local stat=$1; shift
774
775	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
776}
777
778humanize()
779{
780	local speed=$1; shift
781
782	for unit in bps Kbps Mbps Gbps; do
783		if (($(echo "$speed < 1024" | bc))); then
784			break
785		fi
786
787		speed=$(echo "scale=1; $speed / 1024" | bc)
788	done
789
790	echo "$speed${unit}"
791}
792
793rate()
794{
795	local t0=$1; shift
796	local t1=$1; shift
797	local interval=$1; shift
798
799	echo $((8 * (t1 - t0) / interval))
800}
801
802packets_rate()
803{
804	local t0=$1; shift
805	local t1=$1; shift
806	local interval=$1; shift
807
808	echo $(((t1 - t0) / interval))
809}
810
811mac_get()
812{
813	local if_name=$1
814
815	ip -j link show dev $if_name | jq -r '.[]["address"]'
816}
817
818bridge_ageing_time_get()
819{
820	local bridge=$1
821	local ageing_time
822
823	# Need to divide by 100 to convert to seconds.
824	ageing_time=$(ip -j -d link show dev $bridge \
825		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
826	echo $((ageing_time / 100))
827}
828
829declare -A SYSCTL_ORIG
830sysctl_set()
831{
832	local key=$1; shift
833	local value=$1; shift
834
835	SYSCTL_ORIG[$key]=$(sysctl -n $key)
836	sysctl -qw $key=$value
837}
838
839sysctl_restore()
840{
841	local key=$1; shift
842
843	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
844}
845
846forwarding_enable()
847{
848	sysctl_set net.ipv4.conf.all.forwarding 1
849	sysctl_set net.ipv6.conf.all.forwarding 1
850}
851
852forwarding_restore()
853{
854	sysctl_restore net.ipv6.conf.all.forwarding
855	sysctl_restore net.ipv4.conf.all.forwarding
856}
857
858declare -A MTU_ORIG
859mtu_set()
860{
861	local dev=$1; shift
862	local mtu=$1; shift
863
864	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
865	ip link set dev $dev mtu $mtu
866}
867
868mtu_restore()
869{
870	local dev=$1; shift
871
872	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
873}
874
875tc_offload_check()
876{
877	local num_netifs=${1:-$NUM_NETIFS}
878
879	for ((i = 1; i <= num_netifs; ++i)); do
880		ethtool -k ${NETIFS[p$i]} \
881			| grep "hw-tc-offload: on" &> /dev/null
882		if [[ $? -ne 0 ]]; then
883			return 1
884		fi
885	done
886
887	return 0
888}
889
890trap_install()
891{
892	local dev=$1; shift
893	local direction=$1; shift
894
895	# Some devices may not support or need in-hardware trapping of traffic
896	# (e.g. the veth pairs that this library creates for non-existent
897	# loopbacks). Use continue instead, so that there is a filter in there
898	# (some tests check counters), and so that other filters are still
899	# processed.
900	tc filter add dev $dev $direction pref 1 \
901		flower skip_sw action trap 2>/dev/null \
902	    || tc filter add dev $dev $direction pref 1 \
903		       flower action continue
904}
905
906trap_uninstall()
907{
908	local dev=$1; shift
909	local direction=$1; shift
910
911	tc filter del dev $dev $direction pref 1 flower
912}
913
914slow_path_trap_install()
915{
916	# For slow-path testing, we need to install a trap to get to
917	# slow path the packets that would otherwise be switched in HW.
918	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
919		trap_install "$@"
920	fi
921}
922
923slow_path_trap_uninstall()
924{
925	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
926		trap_uninstall "$@"
927	fi
928}
929
930__icmp_capture_add_del()
931{
932	local add_del=$1; shift
933	local pref=$1; shift
934	local vsuf=$1; shift
935	local tundev=$1; shift
936	local filter=$1; shift
937
938	tc filter $add_del dev "$tundev" ingress \
939	   proto ip$vsuf pref $pref \
940	   flower ip_proto icmp$vsuf $filter \
941	   action pass
942}
943
944icmp_capture_install()
945{
946	__icmp_capture_add_del add 100 "" "$@"
947}
948
949icmp_capture_uninstall()
950{
951	__icmp_capture_add_del del 100 "" "$@"
952}
953
954icmp6_capture_install()
955{
956	__icmp_capture_add_del add 100 v6 "$@"
957}
958
959icmp6_capture_uninstall()
960{
961	__icmp_capture_add_del del 100 v6 "$@"
962}
963
964__vlan_capture_add_del()
965{
966	local add_del=$1; shift
967	local pref=$1; shift
968	local dev=$1; shift
969	local filter=$1; shift
970
971	tc filter $add_del dev "$dev" ingress \
972	   proto 802.1q pref $pref \
973	   flower $filter \
974	   action pass
975}
976
977vlan_capture_install()
978{
979	__vlan_capture_add_del add 100 "$@"
980}
981
982vlan_capture_uninstall()
983{
984	__vlan_capture_add_del del 100 "$@"
985}
986
987__dscp_capture_add_del()
988{
989	local add_del=$1; shift
990	local dev=$1; shift
991	local base=$1; shift
992	local dscp;
993
994	for prio in {0..7}; do
995		dscp=$((base + prio))
996		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
997				       "skip_hw ip_tos $((dscp << 2))"
998	done
999}
1000
1001dscp_capture_install()
1002{
1003	local dev=$1; shift
1004	local base=$1; shift
1005
1006	__dscp_capture_add_del add $dev $base
1007}
1008
1009dscp_capture_uninstall()
1010{
1011	local dev=$1; shift
1012	local base=$1; shift
1013
1014	__dscp_capture_add_del del $dev $base
1015}
1016
1017dscp_fetch_stats()
1018{
1019	local dev=$1; shift
1020	local base=$1; shift
1021
1022	for prio in {0..7}; do
1023		local dscp=$((base + prio))
1024		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1025		echo "[$dscp]=$t "
1026	done
1027}
1028
1029matchall_sink_create()
1030{
1031	local dev=$1; shift
1032
1033	tc qdisc add dev $dev clsact
1034	tc filter add dev $dev ingress \
1035	   pref 10000 \
1036	   matchall \
1037	   action drop
1038}
1039
1040tests_run()
1041{
1042	local current_test
1043
1044	for current_test in ${TESTS:-$ALL_TESTS}; do
1045		$current_test
1046	done
1047}
1048
1049multipath_eval()
1050{
1051	local desc="$1"
1052	local weight_rp12=$2
1053	local weight_rp13=$3
1054	local packets_rp12=$4
1055	local packets_rp13=$5
1056	local weights_ratio packets_ratio diff
1057
1058	RET=0
1059
1060	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1061		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1062				| bc -l)
1063	else
1064		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1065				| bc -l)
1066	fi
1067
1068	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1069	       check_err 1 "Packet difference is 0"
1070	       log_test "Multipath"
1071	       log_info "Expected ratio $weights_ratio"
1072	       return
1073	fi
1074
1075	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1076		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1077				| bc -l)
1078	else
1079		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1080				| bc -l)
1081	fi
1082
1083	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1084	diff=${diff#-}
1085
1086	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1087	check_err $? "Too large discrepancy between expected and measured ratios"
1088	log_test "$desc"
1089	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1090}
1091
1092in_ns()
1093{
1094	local name=$1; shift
1095
1096	ip netns exec $name bash <<-EOF
1097		NUM_NETIFS=0
1098		source lib.sh
1099		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1100	EOF
1101}
1102
1103##############################################################################
1104# Tests
1105
1106ping_do()
1107{
1108	local if_name=$1
1109	local dip=$2
1110	local args=$3
1111	local vrf_name
1112
1113	vrf_name=$(master_name_get $if_name)
1114	ip vrf exec $vrf_name \
1115		$PING $args $dip -c $PING_COUNT -i 0.1 \
1116		-w $PING_TIMEOUT &> /dev/null
1117}
1118
1119ping_test()
1120{
1121	RET=0
1122
1123	ping_do $1 $2
1124	check_err $?
1125	log_test "ping$3"
1126}
1127
1128ping6_do()
1129{
1130	local if_name=$1
1131	local dip=$2
1132	local args=$3
1133	local vrf_name
1134
1135	vrf_name=$(master_name_get $if_name)
1136	ip vrf exec $vrf_name \
1137		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1138		-w $PING_TIMEOUT &> /dev/null
1139}
1140
1141ping6_test()
1142{
1143	RET=0
1144
1145	ping6_do $1 $2
1146	check_err $?
1147	log_test "ping6$3"
1148}
1149
1150learning_test()
1151{
1152	local bridge=$1
1153	local br_port1=$2	# Connected to `host1_if`.
1154	local host1_if=$3
1155	local host2_if=$4
1156	local mac=de:ad:be:ef:13:37
1157	local ageing_time
1158
1159	RET=0
1160
1161	bridge -j fdb show br $bridge brport $br_port1 \
1162		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1163	check_fail $? "Found FDB record when should not"
1164
1165	# Disable unknown unicast flooding on `br_port1` to make sure
1166	# packets are only forwarded through the port after a matching
1167	# FDB entry was installed.
1168	bridge link set dev $br_port1 flood off
1169
1170	tc qdisc add dev $host1_if ingress
1171	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1172		flower dst_mac $mac action drop
1173
1174	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1175	sleep 1
1176
1177	tc -j -s filter show dev $host1_if ingress \
1178		| jq -e ".[] | select(.options.handle == 101) \
1179		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1180	check_fail $? "Packet reached second host when should not"
1181
1182	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1183	sleep 1
1184
1185	bridge -j fdb show br $bridge brport $br_port1 \
1186		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1187	check_err $? "Did not find FDB record when should"
1188
1189	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1190	sleep 1
1191
1192	tc -j -s filter show dev $host1_if ingress \
1193		| jq -e ".[] | select(.options.handle == 101) \
1194		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1195	check_err $? "Packet did not reach second host when should"
1196
1197	# Wait for 10 seconds after the ageing time to make sure FDB
1198	# record was aged-out.
1199	ageing_time=$(bridge_ageing_time_get $bridge)
1200	sleep $((ageing_time + 10))
1201
1202	bridge -j fdb show br $bridge brport $br_port1 \
1203		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1204	check_fail $? "Found FDB record when should not"
1205
1206	bridge link set dev $br_port1 learning off
1207
1208	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1209	sleep 1
1210
1211	bridge -j fdb show br $bridge brport $br_port1 \
1212		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1213	check_fail $? "Found FDB record when should not"
1214
1215	bridge link set dev $br_port1 learning on
1216
1217	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1218	tc qdisc del dev $host1_if ingress
1219
1220	bridge link set dev $br_port1 flood on
1221
1222	log_test "FDB learning"
1223}
1224
1225flood_test_do()
1226{
1227	local should_flood=$1
1228	local mac=$2
1229	local ip=$3
1230	local host1_if=$4
1231	local host2_if=$5
1232	local err=0
1233
1234	# Add an ACL on `host2_if` which will tell us whether the packet
1235	# was flooded to it or not.
1236	tc qdisc add dev $host2_if ingress
1237	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1238		flower dst_mac $mac action drop
1239
1240	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1241	sleep 1
1242
1243	tc -j -s filter show dev $host2_if ingress \
1244		| jq -e ".[] | select(.options.handle == 101) \
1245		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1246	if [[ $? -ne 0 && $should_flood == "true" || \
1247	      $? -eq 0 && $should_flood == "false" ]]; then
1248		err=1
1249	fi
1250
1251	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1252	tc qdisc del dev $host2_if ingress
1253
1254	return $err
1255}
1256
1257flood_unicast_test()
1258{
1259	local br_port=$1
1260	local host1_if=$2
1261	local host2_if=$3
1262	local mac=de:ad:be:ef:13:37
1263	local ip=192.0.2.100
1264
1265	RET=0
1266
1267	bridge link set dev $br_port flood off
1268
1269	flood_test_do false $mac $ip $host1_if $host2_if
1270	check_err $? "Packet flooded when should not"
1271
1272	bridge link set dev $br_port flood on
1273
1274	flood_test_do true $mac $ip $host1_if $host2_if
1275	check_err $? "Packet was not flooded when should"
1276
1277	log_test "Unknown unicast flood"
1278}
1279
1280flood_multicast_test()
1281{
1282	local br_port=$1
1283	local host1_if=$2
1284	local host2_if=$3
1285	local mac=01:00:5e:00:00:01
1286	local ip=239.0.0.1
1287
1288	RET=0
1289
1290	bridge link set dev $br_port mcast_flood off
1291
1292	flood_test_do false $mac $ip $host1_if $host2_if
1293	check_err $? "Packet flooded when should not"
1294
1295	bridge link set dev $br_port mcast_flood on
1296
1297	flood_test_do true $mac $ip $host1_if $host2_if
1298	check_err $? "Packet was not flooded when should"
1299
1300	log_test "Unregistered multicast flood"
1301}
1302
1303flood_test()
1304{
1305	# `br_port` is connected to `host2_if`
1306	local br_port=$1
1307	local host1_if=$2
1308	local host2_if=$3
1309
1310	flood_unicast_test $br_port $host1_if $host2_if
1311	flood_multicast_test $br_port $host1_if $host2_if
1312}
1313
1314__start_traffic()
1315{
1316	local proto=$1; shift
1317	local h_in=$1; shift    # Where the traffic egresses the host
1318	local sip=$1; shift
1319	local dip=$1; shift
1320	local dmac=$1; shift
1321
1322	$MZ $h_in -p 8000 -A $sip -B $dip -c 0 \
1323		-a own -b $dmac -t "$proto" -q "$@" &
1324	sleep 1
1325}
1326
1327start_traffic()
1328{
1329	__start_traffic udp "$@"
1330}
1331
1332start_tcp_traffic()
1333{
1334	__start_traffic tcp "$@"
1335}
1336
1337stop_traffic()
1338{
1339	# Suppress noise from killing mausezahn.
1340	{ kill %% && wait %%; } 2>/dev/null
1341}
1342
1343tcpdump_start()
1344{
1345	local if_name=$1; shift
1346	local ns=$1; shift
1347
1348	capfile=$(mktemp)
1349	capout=$(mktemp)
1350
1351	if [ -z $ns ]; then
1352		ns_cmd=""
1353	else
1354		ns_cmd="ip netns exec ${ns}"
1355	fi
1356
1357	if [ -z $SUDO_USER ] ; then
1358		capuser=""
1359	else
1360		capuser="-Z $SUDO_USER"
1361	fi
1362
1363	$ns_cmd tcpdump -e -n -Q in -i $if_name \
1364		-s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 &
1365	cappid=$!
1366
1367	sleep 1
1368}
1369
1370tcpdump_stop()
1371{
1372	$ns_cmd kill $cappid
1373	sleep 1
1374}
1375
1376tcpdump_cleanup()
1377{
1378	rm $capfile $capout
1379}
1380
1381tcpdump_show()
1382{
1383	tcpdump -e -n -r $capfile 2>&1
1384}
1385
1386# return 0 if the packet wasn't seen on host2_if or 1 if it was
1387mcast_packet_test()
1388{
1389	local mac=$1
1390	local src_ip=$2
1391	local ip=$3
1392	local host1_if=$4
1393	local host2_if=$5
1394	local seen=0
1395	local tc_proto="ip"
1396	local mz_v6arg=""
1397
1398	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1399	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1400		tc_proto="ipv6"
1401		mz_v6arg="-6"
1402	fi
1403
1404	# Add an ACL on `host2_if` which will tell us whether the packet
1405	# was received by it or not.
1406	tc qdisc add dev $host2_if ingress
1407	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1408		flower ip_proto udp dst_mac $mac action drop
1409
1410	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1411	sleep 1
1412
1413	tc -j -s filter show dev $host2_if ingress \
1414		| jq -e ".[] | select(.options.handle == 101) \
1415		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1416	if [[ $? -eq 0 ]]; then
1417		seen=1
1418	fi
1419
1420	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1421	tc qdisc del dev $host2_if ingress
1422
1423	return $seen
1424}
1425
1426brmcast_check_sg_entries()
1427{
1428	local report=$1; shift
1429	local slist=("$@")
1430	local sarg=""
1431
1432	for src in "${slist[@]}"; do
1433		sarg="${sarg} and .source_list[].address == \"$src\""
1434	done
1435	bridge -j -d -s mdb show dev br0 \
1436		| jq -e ".[].mdb[] | \
1437			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1438	check_err $? "Wrong *,G entry source list after $report report"
1439
1440	for sgent in "${slist[@]}"; do
1441		bridge -j -d -s mdb show dev br0 \
1442			| jq -e ".[].mdb[] | \
1443				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1444		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1445	done
1446}
1447
1448brmcast_check_sg_fwding()
1449{
1450	local should_fwd=$1; shift
1451	local sources=("$@")
1452
1453	for src in "${sources[@]}"; do
1454		local retval=0
1455
1456		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1457		retval=$?
1458		if [ $should_fwd -eq 1 ]; then
1459			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1460		else
1461			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1462		fi
1463	done
1464}
1465
1466brmcast_check_sg_state()
1467{
1468	local is_blocked=$1; shift
1469	local sources=("$@")
1470	local should_fail=1
1471
1472	if [ $is_blocked -eq 1 ]; then
1473		should_fail=0
1474	fi
1475
1476	for src in "${sources[@]}"; do
1477		bridge -j -d -s mdb show dev br0 \
1478			| jq -e ".[].mdb[] | \
1479				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1480				 .source_list[] |
1481				 select(.address == \"$src\") |
1482				 select(.timer == \"0.00\")" &>/dev/null
1483		check_err_fail $should_fail $? "Entry $src has zero timer"
1484
1485		bridge -j -d -s mdb show dev br0 \
1486			| jq -e ".[].mdb[] | \
1487				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1488				 .flags[] == \"blocked\")" &>/dev/null
1489		check_err_fail $should_fail $? "Entry $src has blocked flag"
1490	done
1491}
1492