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