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
228setup_wait_dev()
229{
230	local dev=$1; shift
231	local wait_time=${1:-$WAIT_TIME}; shift
232
233	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
234
235	if (($?)); then
236		check_err 1
237		log_test setup_wait_dev ": Interface $dev does not come up."
238		exit 1
239	fi
240}
241
242setup_wait_dev_with_timeout()
243{
244	local dev=$1; shift
245	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
246	local wait_time=${1:-$WAIT_TIME}; shift
247	local i
248
249	for ((i = 1; i <= $max_iterations; ++i)); do
250		ip link show dev $dev up \
251			| grep 'state UP' &> /dev/null
252		if [[ $? -ne 0 ]]; then
253			sleep 1
254		else
255			sleep $wait_time
256			return 0
257		fi
258	done
259
260	return 1
261}
262
263setup_wait()
264{
265	local num_netifs=${1:-$NUM_NETIFS}
266	local i
267
268	for ((i = 1; i <= num_netifs; ++i)); do
269		setup_wait_dev ${NETIFS[p$i]} 0
270	done
271
272	# Make sure links are ready.
273	sleep $WAIT_TIME
274}
275
276cmd_jq()
277{
278	local cmd=$1
279	local jq_exp=$2
280	local jq_opts=$3
281	local ret
282	local output
283
284	output="$($cmd)"
285	# it the command fails, return error right away
286	ret=$?
287	if [[ $ret -ne 0 ]]; then
288		return $ret
289	fi
290	output=$(echo $output | jq -r $jq_opts "$jq_exp")
291	ret=$?
292	if [[ $ret -ne 0 ]]; then
293		return $ret
294	fi
295	echo $output
296	# return success only in case of non-empty output
297	[ ! -z "$output" ]
298}
299
300lldpad_app_wait_set()
301{
302	local dev=$1; shift
303
304	while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do
305		echo "$dev: waiting for lldpad to push pending APP updates"
306		sleep 5
307	done
308}
309
310lldpad_app_wait_del()
311{
312	# Give lldpad a chance to push down the changes. If the device is downed
313	# too soon, the updates will be left pending. However, they will have
314	# been struck off the lldpad's DB already, so we won't be able to tell
315	# they are pending. Then on next test iteration this would cause
316	# weirdness as newly-added APP rules conflict with the old ones,
317	# sometimes getting stuck in an "unknown" state.
318	sleep 5
319}
320
321pre_cleanup()
322{
323	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
324		echo "Pausing before cleanup, hit any key to continue"
325		read
326	fi
327}
328
329vrf_prepare()
330{
331	ip -4 rule add pref 32765 table local
332	ip -4 rule del pref 0
333	ip -6 rule add pref 32765 table local
334	ip -6 rule del pref 0
335}
336
337vrf_cleanup()
338{
339	ip -6 rule add pref 0 table local
340	ip -6 rule del pref 32765
341	ip -4 rule add pref 0 table local
342	ip -4 rule del pref 32765
343}
344
345__last_tb_id=0
346declare -A __TB_IDS
347
348__vrf_td_id_assign()
349{
350	local vrf_name=$1
351
352	__last_tb_id=$((__last_tb_id + 1))
353	__TB_IDS[$vrf_name]=$__last_tb_id
354	return $__last_tb_id
355}
356
357__vrf_td_id_lookup()
358{
359	local vrf_name=$1
360
361	return ${__TB_IDS[$vrf_name]}
362}
363
364vrf_create()
365{
366	local vrf_name=$1
367	local tb_id
368
369	__vrf_td_id_assign $vrf_name
370	tb_id=$?
371
372	ip link add dev $vrf_name type vrf table $tb_id
373	ip -4 route add table $tb_id unreachable default metric 4278198272
374	ip -6 route add table $tb_id unreachable default metric 4278198272
375}
376
377vrf_destroy()
378{
379	local vrf_name=$1
380	local tb_id
381
382	__vrf_td_id_lookup $vrf_name
383	tb_id=$?
384
385	ip -6 route del table $tb_id unreachable default metric 4278198272
386	ip -4 route del table $tb_id unreachable default metric 4278198272
387	ip link del dev $vrf_name
388}
389
390__addr_add_del()
391{
392	local if_name=$1
393	local add_del=$2
394	local array
395
396	shift
397	shift
398	array=("${@}")
399
400	for addrstr in "${array[@]}"; do
401		ip address $add_del $addrstr dev $if_name
402	done
403}
404
405__simple_if_init()
406{
407	local if_name=$1; shift
408	local vrf_name=$1; shift
409	local addrs=("${@}")
410
411	ip link set dev $if_name master $vrf_name
412	ip link set dev $if_name up
413
414	__addr_add_del $if_name add "${addrs[@]}"
415}
416
417__simple_if_fini()
418{
419	local if_name=$1; shift
420	local addrs=("${@}")
421
422	__addr_add_del $if_name del "${addrs[@]}"
423
424	ip link set dev $if_name down
425	ip link set dev $if_name nomaster
426}
427
428simple_if_init()
429{
430	local if_name=$1
431	local vrf_name
432	local array
433
434	shift
435	vrf_name=v$if_name
436	array=("${@}")
437
438	vrf_create $vrf_name
439	ip link set dev $vrf_name up
440	__simple_if_init $if_name $vrf_name "${array[@]}"
441}
442
443simple_if_fini()
444{
445	local if_name=$1
446	local vrf_name
447	local array
448
449	shift
450	vrf_name=v$if_name
451	array=("${@}")
452
453	__simple_if_fini $if_name "${array[@]}"
454	vrf_destroy $vrf_name
455}
456
457tunnel_create()
458{
459	local name=$1; shift
460	local type=$1; shift
461	local local=$1; shift
462	local remote=$1; shift
463
464	ip link add name $name type $type \
465	   local $local remote $remote "$@"
466	ip link set dev $name up
467}
468
469tunnel_destroy()
470{
471	local name=$1; shift
472
473	ip link del dev $name
474}
475
476vlan_create()
477{
478	local if_name=$1; shift
479	local vid=$1; shift
480	local vrf=$1; shift
481	local ips=("${@}")
482	local name=$if_name.$vid
483
484	ip link add name $name link $if_name type vlan id $vid
485	if [ "$vrf" != "" ]; then
486		ip link set dev $name master $vrf
487	fi
488	ip link set dev $name up
489	__addr_add_del $name add "${ips[@]}"
490}
491
492vlan_destroy()
493{
494	local if_name=$1; shift
495	local vid=$1; shift
496	local name=$if_name.$vid
497
498	ip link del dev $name
499}
500
501team_create()
502{
503	local if_name=$1; shift
504	local mode=$1; shift
505
506	require_command $TEAMD
507	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
508	for slave in "$@"; do
509		ip link set dev $slave down
510		ip link set dev $slave master $if_name
511		ip link set dev $slave up
512	done
513	ip link set dev $if_name up
514}
515
516team_destroy()
517{
518	local if_name=$1; shift
519
520	$TEAMD -t $if_name -k
521}
522
523master_name_get()
524{
525	local if_name=$1
526
527	ip -j link show dev $if_name | jq -r '.[]["master"]'
528}
529
530link_stats_get()
531{
532	local if_name=$1; shift
533	local dir=$1; shift
534	local stat=$1; shift
535
536	ip -j -s link show dev $if_name \
537		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
538}
539
540link_stats_tx_packets_get()
541{
542	link_stats_get $1 tx packets
543}
544
545link_stats_rx_errors_get()
546{
547	link_stats_get $1 rx errors
548}
549
550tc_rule_stats_get()
551{
552	local dev=$1; shift
553	local pref=$1; shift
554	local dir=$1; shift
555
556	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
557	    | jq '.[1].options.actions[].stats.packets'
558}
559
560ethtool_stats_get()
561{
562	local dev=$1; shift
563	local stat=$1; shift
564
565	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
566}
567
568mac_get()
569{
570	local if_name=$1
571
572	ip -j link show dev $if_name | jq -r '.[]["address"]'
573}
574
575bridge_ageing_time_get()
576{
577	local bridge=$1
578	local ageing_time
579
580	# Need to divide by 100 to convert to seconds.
581	ageing_time=$(ip -j -d link show dev $bridge \
582		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
583	echo $((ageing_time / 100))
584}
585
586declare -A SYSCTL_ORIG
587sysctl_set()
588{
589	local key=$1; shift
590	local value=$1; shift
591
592	SYSCTL_ORIG[$key]=$(sysctl -n $key)
593	sysctl -qw $key=$value
594}
595
596sysctl_restore()
597{
598	local key=$1; shift
599
600	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
601}
602
603forwarding_enable()
604{
605	sysctl_set net.ipv4.conf.all.forwarding 1
606	sysctl_set net.ipv6.conf.all.forwarding 1
607}
608
609forwarding_restore()
610{
611	sysctl_restore net.ipv6.conf.all.forwarding
612	sysctl_restore net.ipv4.conf.all.forwarding
613}
614
615declare -A MTU_ORIG
616mtu_set()
617{
618	local dev=$1; shift
619	local mtu=$1; shift
620
621	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
622	ip link set dev $dev mtu $mtu
623}
624
625mtu_restore()
626{
627	local dev=$1; shift
628
629	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
630}
631
632tc_offload_check()
633{
634	local num_netifs=${1:-$NUM_NETIFS}
635
636	for ((i = 1; i <= num_netifs; ++i)); do
637		ethtool -k ${NETIFS[p$i]} \
638			| grep "hw-tc-offload: on" &> /dev/null
639		if [[ $? -ne 0 ]]; then
640			return 1
641		fi
642	done
643
644	return 0
645}
646
647trap_install()
648{
649	local dev=$1; shift
650	local direction=$1; shift
651
652	# Some devices may not support or need in-hardware trapping of traffic
653	# (e.g. the veth pairs that this library creates for non-existent
654	# loopbacks). Use continue instead, so that there is a filter in there
655	# (some tests check counters), and so that other filters are still
656	# processed.
657	tc filter add dev $dev $direction pref 1 \
658		flower skip_sw action trap 2>/dev/null \
659	    || tc filter add dev $dev $direction pref 1 \
660		       flower action continue
661}
662
663trap_uninstall()
664{
665	local dev=$1; shift
666	local direction=$1; shift
667
668	tc filter del dev $dev $direction pref 1 flower
669}
670
671slow_path_trap_install()
672{
673	# For slow-path testing, we need to install a trap to get to
674	# slow path the packets that would otherwise be switched in HW.
675	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
676		trap_install "$@"
677	fi
678}
679
680slow_path_trap_uninstall()
681{
682	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
683		trap_uninstall "$@"
684	fi
685}
686
687__icmp_capture_add_del()
688{
689	local add_del=$1; shift
690	local pref=$1; shift
691	local vsuf=$1; shift
692	local tundev=$1; shift
693	local filter=$1; shift
694
695	tc filter $add_del dev "$tundev" ingress \
696	   proto ip$vsuf pref $pref \
697	   flower ip_proto icmp$vsuf $filter \
698	   action pass
699}
700
701icmp_capture_install()
702{
703	__icmp_capture_add_del add 100 "" "$@"
704}
705
706icmp_capture_uninstall()
707{
708	__icmp_capture_add_del del 100 "" "$@"
709}
710
711icmp6_capture_install()
712{
713	__icmp_capture_add_del add 100 v6 "$@"
714}
715
716icmp6_capture_uninstall()
717{
718	__icmp_capture_add_del del 100 v6 "$@"
719}
720
721__vlan_capture_add_del()
722{
723	local add_del=$1; shift
724	local pref=$1; shift
725	local dev=$1; shift
726	local filter=$1; shift
727
728	tc filter $add_del dev "$dev" ingress \
729	   proto 802.1q pref $pref \
730	   flower $filter \
731	   action pass
732}
733
734vlan_capture_install()
735{
736	__vlan_capture_add_del add 100 "$@"
737}
738
739vlan_capture_uninstall()
740{
741	__vlan_capture_add_del del 100 "$@"
742}
743
744__dscp_capture_add_del()
745{
746	local add_del=$1; shift
747	local dev=$1; shift
748	local base=$1; shift
749	local dscp;
750
751	for prio in {0..7}; do
752		dscp=$((base + prio))
753		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
754				       "skip_hw ip_tos $((dscp << 2))"
755	done
756}
757
758dscp_capture_install()
759{
760	local dev=$1; shift
761	local base=$1; shift
762
763	__dscp_capture_add_del add $dev $base
764}
765
766dscp_capture_uninstall()
767{
768	local dev=$1; shift
769	local base=$1; shift
770
771	__dscp_capture_add_del del $dev $base
772}
773
774dscp_fetch_stats()
775{
776	local dev=$1; shift
777	local base=$1; shift
778
779	for prio in {0..7}; do
780		local dscp=$((base + prio))
781		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
782		echo "[$dscp]=$t "
783	done
784}
785
786matchall_sink_create()
787{
788	local dev=$1; shift
789
790	tc qdisc add dev $dev clsact
791	tc filter add dev $dev ingress \
792	   pref 10000 \
793	   matchall \
794	   action drop
795}
796
797tests_run()
798{
799	local current_test
800
801	for current_test in ${TESTS:-$ALL_TESTS}; do
802		$current_test
803	done
804}
805
806multipath_eval()
807{
808	local desc="$1"
809	local weight_rp12=$2
810	local weight_rp13=$3
811	local packets_rp12=$4
812	local packets_rp13=$5
813	local weights_ratio packets_ratio diff
814
815	RET=0
816
817	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
818		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
819				| bc -l)
820	else
821		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
822				| bc -l)
823	fi
824
825	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
826	       check_err 1 "Packet difference is 0"
827	       log_test "Multipath"
828	       log_info "Expected ratio $weights_ratio"
829	       return
830	fi
831
832	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
833		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
834				| bc -l)
835	else
836		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
837				| bc -l)
838	fi
839
840	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
841	diff=${diff#-}
842
843	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
844	check_err $? "Too large discrepancy between expected and measured ratios"
845	log_test "$desc"
846	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
847}
848
849in_ns()
850{
851	local name=$1; shift
852
853	ip netns exec $name bash <<-EOF
854		NUM_NETIFS=0
855		source lib.sh
856		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
857	EOF
858}
859
860##############################################################################
861# Tests
862
863ping_do()
864{
865	local if_name=$1
866	local dip=$2
867	local args=$3
868	local vrf_name
869
870	vrf_name=$(master_name_get $if_name)
871	ip vrf exec $vrf_name \
872		$PING $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
873}
874
875ping_test()
876{
877	RET=0
878
879	ping_do $1 $2
880	check_err $?
881	log_test "ping$3"
882}
883
884ping6_do()
885{
886	local if_name=$1
887	local dip=$2
888	local args=$3
889	local vrf_name
890
891	vrf_name=$(master_name_get $if_name)
892	ip vrf exec $vrf_name \
893		$PING6 $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
894}
895
896ping6_test()
897{
898	RET=0
899
900	ping6_do $1 $2
901	check_err $?
902	log_test "ping6$3"
903}
904
905learning_test()
906{
907	local bridge=$1
908	local br_port1=$2	# Connected to `host1_if`.
909	local host1_if=$3
910	local host2_if=$4
911	local mac=de:ad:be:ef:13:37
912	local ageing_time
913
914	RET=0
915
916	bridge -j fdb show br $bridge brport $br_port1 \
917		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
918	check_fail $? "Found FDB record when should not"
919
920	# Disable unknown unicast flooding on `br_port1` to make sure
921	# packets are only forwarded through the port after a matching
922	# FDB entry was installed.
923	bridge link set dev $br_port1 flood off
924
925	tc qdisc add dev $host1_if ingress
926	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
927		flower dst_mac $mac action drop
928
929	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
930	sleep 1
931
932	tc -j -s filter show dev $host1_if ingress \
933		| jq -e ".[] | select(.options.handle == 101) \
934		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
935	check_fail $? "Packet reached second host when should not"
936
937	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
938	sleep 1
939
940	bridge -j fdb show br $bridge brport $br_port1 \
941		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
942	check_err $? "Did not find FDB record when should"
943
944	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
945	sleep 1
946
947	tc -j -s filter show dev $host1_if ingress \
948		| jq -e ".[] | select(.options.handle == 101) \
949		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
950	check_err $? "Packet did not reach second host when should"
951
952	# Wait for 10 seconds after the ageing time to make sure FDB
953	# record was aged-out.
954	ageing_time=$(bridge_ageing_time_get $bridge)
955	sleep $((ageing_time + 10))
956
957	bridge -j fdb show br $bridge brport $br_port1 \
958		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
959	check_fail $? "Found FDB record when should not"
960
961	bridge link set dev $br_port1 learning off
962
963	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
964	sleep 1
965
966	bridge -j fdb show br $bridge brport $br_port1 \
967		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
968	check_fail $? "Found FDB record when should not"
969
970	bridge link set dev $br_port1 learning on
971
972	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
973	tc qdisc del dev $host1_if ingress
974
975	bridge link set dev $br_port1 flood on
976
977	log_test "FDB learning"
978}
979
980flood_test_do()
981{
982	local should_flood=$1
983	local mac=$2
984	local ip=$3
985	local host1_if=$4
986	local host2_if=$5
987	local err=0
988
989	# Add an ACL on `host2_if` which will tell us whether the packet
990	# was flooded to it or not.
991	tc qdisc add dev $host2_if ingress
992	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
993		flower dst_mac $mac action drop
994
995	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
996	sleep 1
997
998	tc -j -s filter show dev $host2_if ingress \
999		| jq -e ".[] | select(.options.handle == 101) \
1000		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1001	if [[ $? -ne 0 && $should_flood == "true" || \
1002	      $? -eq 0 && $should_flood == "false" ]]; then
1003		err=1
1004	fi
1005
1006	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1007	tc qdisc del dev $host2_if ingress
1008
1009	return $err
1010}
1011
1012flood_unicast_test()
1013{
1014	local br_port=$1
1015	local host1_if=$2
1016	local host2_if=$3
1017	local mac=de:ad:be:ef:13:37
1018	local ip=192.0.2.100
1019
1020	RET=0
1021
1022	bridge link set dev $br_port flood off
1023
1024	flood_test_do false $mac $ip $host1_if $host2_if
1025	check_err $? "Packet flooded when should not"
1026
1027	bridge link set dev $br_port flood on
1028
1029	flood_test_do true $mac $ip $host1_if $host2_if
1030	check_err $? "Packet was not flooded when should"
1031
1032	log_test "Unknown unicast flood"
1033}
1034
1035flood_multicast_test()
1036{
1037	local br_port=$1
1038	local host1_if=$2
1039	local host2_if=$3
1040	local mac=01:00:5e:00:00:01
1041	local ip=239.0.0.1
1042
1043	RET=0
1044
1045	bridge link set dev $br_port mcast_flood off
1046
1047	flood_test_do false $mac $ip $host1_if $host2_if
1048	check_err $? "Packet flooded when should not"
1049
1050	bridge link set dev $br_port mcast_flood on
1051
1052	flood_test_do true $mac $ip $host1_if $host2_if
1053	check_err $? "Packet was not flooded when should"
1054
1055	log_test "Unregistered multicast flood"
1056}
1057
1058flood_test()
1059{
1060	# `br_port` is connected to `host2_if`
1061	local br_port=$1
1062	local host1_if=$2
1063	local host2_if=$3
1064
1065	flood_unicast_test $br_port $host1_if $host2_if
1066	flood_multicast_test $br_port $host1_if $host2_if
1067}
1068