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