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