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