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