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