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