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