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