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