1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Kselftest framework requirement - SKIP code is 4.
8ksft_skip=4
9
10# Can be overridden by the configuration file.
11PING=${PING:=ping}
12PING6=${PING6:=ping6}
13MZ=${MZ:=mausezahn}
14ARPING=${ARPING:=arping}
15TEAMD=${TEAMD:=teamd}
16WAIT_TIME=${WAIT_TIME:=5}
17PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
18PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
19NETIF_TYPE=${NETIF_TYPE:=veth}
20NETIF_CREATE=${NETIF_CREATE:=yes}
21MCD=${MCD:=smcrouted}
22MC_CLI=${MC_CLI:=smcroutectl}
23PING_COUNT=${PING_COUNT:=10}
24PING_TIMEOUT=${PING_TIMEOUT:=5}
25WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
26INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
27LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
28REQUIRE_JQ=${REQUIRE_JQ:=yes}
29REQUIRE_MZ=${REQUIRE_MZ:=yes}
30
31relative_path="${BASH_SOURCE%/*}"
32if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
33	relative_path="."
34fi
35
36if [[ -f $relative_path/forwarding.config ]]; then
37	source "$relative_path/forwarding.config"
38fi
39
40##############################################################################
41# Sanity checks
42
43check_tc_version()
44{
45	tc -j &> /dev/null
46	if [[ $? -ne 0 ]]; then
47		echo "SKIP: iproute2 too old; tc is missing JSON support"
48		exit $ksft_skip
49	fi
50}
51
52# Old versions of tc don't understand "mpls_uc"
53check_tc_mpls_support()
54{
55	local dev=$1; shift
56
57	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
58		matchall action pipe &> /dev/null
59	if [[ $? -ne 0 ]]; then
60		echo "SKIP: iproute2 too old; tc is missing MPLS support"
61		return $ksft_skip
62	fi
63	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
64		matchall
65}
66
67# Old versions of tc produce invalid json output for mpls lse statistics
68check_tc_mpls_lse_stats()
69{
70	local dev=$1; shift
71	local ret;
72
73	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
74		flower mpls lse depth 2                                 \
75		action continue &> /dev/null
76
77	if [[ $? -ne 0 ]]; then
78		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
79		return $ksft_skip
80	fi
81
82	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
83	ret=$?
84	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
85		flower
86
87	if [[ $ret -ne 0 ]]; then
88		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
89		return $ksft_skip
90	fi
91}
92
93check_tc_shblock_support()
94{
95	tc filter help 2>&1 | grep block &> /dev/null
96	if [[ $? -ne 0 ]]; then
97		echo "SKIP: iproute2 too old; tc is missing shared block support"
98		exit $ksft_skip
99	fi
100}
101
102check_tc_chain_support()
103{
104	tc help 2>&1|grep chain &> /dev/null
105	if [[ $? -ne 0 ]]; then
106		echo "SKIP: iproute2 too old; tc is missing chain support"
107		exit $ksft_skip
108	fi
109}
110
111check_tc_action_hw_stats_support()
112{
113	tc actions help 2>&1 | grep -q hw_stats
114	if [[ $? -ne 0 ]]; then
115		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
116		exit $ksft_skip
117	fi
118}
119
120check_ethtool_lanes_support()
121{
122	ethtool --help 2>&1| grep lanes &> /dev/null
123	if [[ $? -ne 0 ]]; then
124		echo "SKIP: ethtool too old; it is missing lanes support"
125		exit $ksft_skip
126	fi
127}
128
129if [[ "$(id -u)" -ne 0 ]]; then
130	echo "SKIP: need root privileges"
131	exit $ksft_skip
132fi
133
134if [[ "$CHECK_TC" = "yes" ]]; then
135	check_tc_version
136fi
137
138require_command()
139{
140	local cmd=$1; shift
141
142	if [[ ! -x "$(command -v "$cmd")" ]]; then
143		echo "SKIP: $cmd not installed"
144		exit $ksft_skip
145	fi
146}
147
148if [[ "$REQUIRE_JQ" = "yes" ]]; then
149	require_command jq
150fi
151if [[ "$REQUIRE_MZ" = "yes" ]]; then
152	require_command $MZ
153fi
154
155if [[ ! -v NUM_NETIFS ]]; then
156	echo "SKIP: importer does not define \"NUM_NETIFS\""
157	exit $ksft_skip
158fi
159
160##############################################################################
161# Command line options handling
162
163count=0
164
165while [[ $# -gt 0 ]]; do
166	if [[ "$count" -eq "0" ]]; then
167		unset NETIFS
168		declare -A NETIFS
169	fi
170	count=$((count + 1))
171	NETIFS[p$count]="$1"
172	shift
173done
174
175##############################################################################
176# Network interfaces configuration
177
178create_netif_veth()
179{
180	local i
181
182	for ((i = 1; i <= NUM_NETIFS; ++i)); do
183		local j=$((i+1))
184
185		ip link show dev ${NETIFS[p$i]} &> /dev/null
186		if [[ $? -ne 0 ]]; then
187			ip link add ${NETIFS[p$i]} type veth \
188				peer name ${NETIFS[p$j]}
189			if [[ $? -ne 0 ]]; then
190				echo "Failed to create netif"
191				exit 1
192			fi
193		fi
194		i=$j
195	done
196}
197
198create_netif()
199{
200	case "$NETIF_TYPE" in
201	veth) create_netif_veth
202	      ;;
203	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
204	   exit 1
205	   ;;
206	esac
207}
208
209if [[ "$NETIF_CREATE" = "yes" ]]; then
210	create_netif
211fi
212
213for ((i = 1; i <= NUM_NETIFS; ++i)); do
214	ip link show dev ${NETIFS[p$i]} &> /dev/null
215	if [[ $? -ne 0 ]]; then
216		echo "SKIP: could not find all required interfaces"
217		exit $ksft_skip
218	fi
219done
220
221##############################################################################
222# Helpers
223
224# Exit status to return at the end. Set in case one of the tests fails.
225EXIT_STATUS=0
226# Per-test return value. Clear at the beginning of each test.
227RET=0
228
229check_err()
230{
231	local err=$1
232	local msg=$2
233
234	if [[ $RET -eq 0 && $err -ne 0 ]]; then
235		RET=$err
236		retmsg=$msg
237	fi
238}
239
240check_fail()
241{
242	local err=$1
243	local msg=$2
244
245	if [[ $RET -eq 0 && $err -eq 0 ]]; then
246		RET=1
247		retmsg=$msg
248	fi
249}
250
251check_err_fail()
252{
253	local should_fail=$1; shift
254	local err=$1; shift
255	local what=$1; shift
256
257	if ((should_fail)); then
258		check_fail $err "$what succeeded, but should have failed"
259	else
260		check_err $err "$what failed"
261	fi
262}
263
264log_test()
265{
266	local test_name=$1
267	local opt_str=$2
268
269	if [[ $# -eq 2 ]]; then
270		opt_str="($opt_str)"
271	fi
272
273	if [[ $RET -ne 0 ]]; then
274		EXIT_STATUS=1
275		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
276		if [[ ! -z "$retmsg" ]]; then
277			printf "\t%s\n" "$retmsg"
278		fi
279		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
280			echo "Hit enter to continue, 'q' to quit"
281			read a
282			[ "$a" = "q" ] && exit 1
283		fi
284		return 1
285	fi
286
287	printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
288	return 0
289}
290
291log_test_skip()
292{
293	local test_name=$1
294	local opt_str=$2
295
296	printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
297	return 0
298}
299
300log_info()
301{
302	local msg=$1
303
304	echo "INFO: $msg"
305}
306
307busywait()
308{
309	local timeout=$1; shift
310
311	local start_time="$(date -u +%s%3N)"
312	while true
313	do
314		local out
315		out=$("$@")
316		local ret=$?
317		if ((!ret)); then
318			echo -n "$out"
319			return 0
320		fi
321
322		local current_time="$(date -u +%s%3N)"
323		if ((current_time - start_time > timeout)); then
324			echo -n "$out"
325			return 1
326		fi
327	done
328}
329
330not()
331{
332	"$@"
333	[[ $? != 0 ]]
334}
335
336get_max()
337{
338	local arr=("$@")
339
340	max=${arr[0]}
341	for cur in ${arr[@]}; do
342		if [[ $cur -gt $max ]]; then
343			max=$cur
344		fi
345	done
346
347	echo $max
348}
349
350grep_bridge_fdb()
351{
352	local addr=$1; shift
353	local word
354	local flag
355
356	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
357		word=$1; shift
358		if [ "$1" == "-v" ]; then
359			flag=$1; shift
360		fi
361	fi
362
363	$@ | grep $addr | grep $flag "$word"
364}
365
366wait_for_port_up()
367{
368	"$@" | grep -q "Link detected: yes"
369}
370
371wait_for_offload()
372{
373	"$@" | grep -q offload
374}
375
376wait_for_trap()
377{
378	"$@" | grep -q trap
379}
380
381until_counter_is()
382{
383	local expr=$1; shift
384	local current=$("$@")
385
386	echo $((current))
387	((current $expr))
388}
389
390busywait_for_counter()
391{
392	local timeout=$1; shift
393	local delta=$1; shift
394
395	local base=$("$@")
396	busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
397}
398
399setup_wait_dev()
400{
401	local dev=$1; shift
402	local wait_time=${1:-$WAIT_TIME}; shift
403
404	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
405
406	if (($?)); then
407		check_err 1
408		log_test setup_wait_dev ": Interface $dev does not come up."
409		exit 1
410	fi
411}
412
413setup_wait_dev_with_timeout()
414{
415	local dev=$1; shift
416	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
417	local wait_time=${1:-$WAIT_TIME}; shift
418	local i
419
420	for ((i = 1; i <= $max_iterations; ++i)); do
421		ip link show dev $dev up \
422			| grep 'state UP' &> /dev/null
423		if [[ $? -ne 0 ]]; then
424			sleep 1
425		else
426			sleep $wait_time
427			return 0
428		fi
429	done
430
431	return 1
432}
433
434setup_wait()
435{
436	local num_netifs=${1:-$NUM_NETIFS}
437	local i
438
439	for ((i = 1; i <= num_netifs; ++i)); do
440		setup_wait_dev ${NETIFS[p$i]} 0
441	done
442
443	# Make sure links are ready.
444	sleep $WAIT_TIME
445}
446
447cmd_jq()
448{
449	local cmd=$1
450	local jq_exp=$2
451	local jq_opts=$3
452	local ret
453	local output
454
455	output="$($cmd)"
456	# it the command fails, return error right away
457	ret=$?
458	if [[ $ret -ne 0 ]]; then
459		return $ret
460	fi
461	output=$(echo $output | jq -r $jq_opts "$jq_exp")
462	ret=$?
463	if [[ $ret -ne 0 ]]; then
464		return $ret
465	fi
466	echo $output
467	# return success only in case of non-empty output
468	[ ! -z "$output" ]
469}
470
471lldpad_app_wait_set()
472{
473	local dev=$1; shift
474
475	while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do
476		echo "$dev: waiting for lldpad to push pending APP updates"
477		sleep 5
478	done
479}
480
481lldpad_app_wait_del()
482{
483	# Give lldpad a chance to push down the changes. If the device is downed
484	# too soon, the updates will be left pending. However, they will have
485	# been struck off the lldpad's DB already, so we won't be able to tell
486	# they are pending. Then on next test iteration this would cause
487	# weirdness as newly-added APP rules conflict with the old ones,
488	# sometimes getting stuck in an "unknown" state.
489	sleep 5
490}
491
492pre_cleanup()
493{
494	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
495		echo "Pausing before cleanup, hit any key to continue"
496		read
497	fi
498}
499
500vrf_prepare()
501{
502	ip -4 rule add pref 32765 table local
503	ip -4 rule del pref 0
504	ip -6 rule add pref 32765 table local
505	ip -6 rule del pref 0
506}
507
508vrf_cleanup()
509{
510	ip -6 rule add pref 0 table local
511	ip -6 rule del pref 32765
512	ip -4 rule add pref 0 table local
513	ip -4 rule del pref 32765
514}
515
516__last_tb_id=0
517declare -A __TB_IDS
518
519__vrf_td_id_assign()
520{
521	local vrf_name=$1
522
523	__last_tb_id=$((__last_tb_id + 1))
524	__TB_IDS[$vrf_name]=$__last_tb_id
525	return $__last_tb_id
526}
527
528__vrf_td_id_lookup()
529{
530	local vrf_name=$1
531
532	return ${__TB_IDS[$vrf_name]}
533}
534
535vrf_create()
536{
537	local vrf_name=$1
538	local tb_id
539
540	__vrf_td_id_assign $vrf_name
541	tb_id=$?
542
543	ip link add dev $vrf_name type vrf table $tb_id
544	ip -4 route add table $tb_id unreachable default metric 4278198272
545	ip -6 route add table $tb_id unreachable default metric 4278198272
546}
547
548vrf_destroy()
549{
550	local vrf_name=$1
551	local tb_id
552
553	__vrf_td_id_lookup $vrf_name
554	tb_id=$?
555
556	ip -6 route del table $tb_id unreachable default metric 4278198272
557	ip -4 route del table $tb_id unreachable default metric 4278198272
558	ip link del dev $vrf_name
559}
560
561__addr_add_del()
562{
563	local if_name=$1
564	local add_del=$2
565	local array
566
567	shift
568	shift
569	array=("${@}")
570
571	for addrstr in "${array[@]}"; do
572		ip address $add_del $addrstr dev $if_name
573	done
574}
575
576__simple_if_init()
577{
578	local if_name=$1; shift
579	local vrf_name=$1; shift
580	local addrs=("${@}")
581
582	ip link set dev $if_name master $vrf_name
583	ip link set dev $if_name up
584
585	__addr_add_del $if_name add "${addrs[@]}"
586}
587
588__simple_if_fini()
589{
590	local if_name=$1; shift
591	local addrs=("${@}")
592
593	__addr_add_del $if_name del "${addrs[@]}"
594
595	ip link set dev $if_name down
596	ip link set dev $if_name nomaster
597}
598
599simple_if_init()
600{
601	local if_name=$1
602	local vrf_name
603	local array
604
605	shift
606	vrf_name=v$if_name
607	array=("${@}")
608
609	vrf_create $vrf_name
610	ip link set dev $vrf_name up
611	__simple_if_init $if_name $vrf_name "${array[@]}"
612}
613
614simple_if_fini()
615{
616	local if_name=$1
617	local vrf_name
618	local array
619
620	shift
621	vrf_name=v$if_name
622	array=("${@}")
623
624	__simple_if_fini $if_name "${array[@]}"
625	vrf_destroy $vrf_name
626}
627
628tunnel_create()
629{
630	local name=$1; shift
631	local type=$1; shift
632	local local=$1; shift
633	local remote=$1; shift
634
635	ip link add name $name type $type \
636	   local $local remote $remote "$@"
637	ip link set dev $name up
638}
639
640tunnel_destroy()
641{
642	local name=$1; shift
643
644	ip link del dev $name
645}
646
647vlan_create()
648{
649	local if_name=$1; shift
650	local vid=$1; shift
651	local vrf=$1; shift
652	local ips=("${@}")
653	local name=$if_name.$vid
654
655	ip link add name $name link $if_name type vlan id $vid
656	if [ "$vrf" != "" ]; then
657		ip link set dev $name master $vrf
658	fi
659	ip link set dev $name up
660	__addr_add_del $name add "${ips[@]}"
661}
662
663vlan_destroy()
664{
665	local if_name=$1; shift
666	local vid=$1; shift
667	local name=$if_name.$vid
668
669	ip link del dev $name
670}
671
672team_create()
673{
674	local if_name=$1; shift
675	local mode=$1; shift
676
677	require_command $TEAMD
678	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
679	for slave in "$@"; do
680		ip link set dev $slave down
681		ip link set dev $slave master $if_name
682		ip link set dev $slave up
683	done
684	ip link set dev $if_name up
685}
686
687team_destroy()
688{
689	local if_name=$1; shift
690
691	$TEAMD -t $if_name -k
692}
693
694master_name_get()
695{
696	local if_name=$1
697
698	ip -j link show dev $if_name | jq -r '.[]["master"]'
699}
700
701link_stats_get()
702{
703	local if_name=$1; shift
704	local dir=$1; shift
705	local stat=$1; shift
706
707	ip -j -s link show dev $if_name \
708		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
709}
710
711link_stats_tx_packets_get()
712{
713	link_stats_get $1 tx packets
714}
715
716link_stats_rx_errors_get()
717{
718	link_stats_get $1 rx errors
719}
720
721tc_rule_stats_get()
722{
723	local dev=$1; shift
724	local pref=$1; shift
725	local dir=$1; shift
726	local selector=${1:-.packets}; shift
727
728	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
729	    | jq ".[1].options.actions[].stats$selector"
730}
731
732tc_rule_handle_stats_get()
733{
734	local id=$1; shift
735	local handle=$1; shift
736	local selector=${1:-.packets}; shift
737
738	tc -j -s filter show $id \
739	    | jq ".[] | select(.options.handle == $handle) | \
740		  .options.actions[0].stats$selector"
741}
742
743ethtool_stats_get()
744{
745	local dev=$1; shift
746	local stat=$1; shift
747
748	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
749}
750
751qdisc_stats_get()
752{
753	local dev=$1; shift
754	local handle=$1; shift
755	local selector=$1; shift
756
757	tc -j -s qdisc show dev "$dev" \
758	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
759}
760
761qdisc_parent_stats_get()
762{
763	local dev=$1; shift
764	local parent=$1; shift
765	local selector=$1; shift
766
767	tc -j -s qdisc show dev "$dev" invisible \
768	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
769}
770
771ipv6_stats_get()
772{
773	local dev=$1; shift
774	local stat=$1; shift
775
776	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
777}
778
779humanize()
780{
781	local speed=$1; shift
782
783	for unit in bps Kbps Mbps Gbps; do
784		if (($(echo "$speed < 1024" | bc))); then
785			break
786		fi
787
788		speed=$(echo "scale=1; $speed / 1024" | bc)
789	done
790
791	echo "$speed${unit}"
792}
793
794rate()
795{
796	local t0=$1; shift
797	local t1=$1; shift
798	local interval=$1; shift
799
800	echo $((8 * (t1 - t0) / interval))
801}
802
803packets_rate()
804{
805	local t0=$1; shift
806	local t1=$1; shift
807	local interval=$1; shift
808
809	echo $(((t1 - t0) / interval))
810}
811
812mac_get()
813{
814	local if_name=$1
815
816	ip -j link show dev $if_name | jq -r '.[]["address"]'
817}
818
819bridge_ageing_time_get()
820{
821	local bridge=$1
822	local ageing_time
823
824	# Need to divide by 100 to convert to seconds.
825	ageing_time=$(ip -j -d link show dev $bridge \
826		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
827	echo $((ageing_time / 100))
828}
829
830declare -A SYSCTL_ORIG
831sysctl_set()
832{
833	local key=$1; shift
834	local value=$1; shift
835
836	SYSCTL_ORIG[$key]=$(sysctl -n $key)
837	sysctl -qw $key=$value
838}
839
840sysctl_restore()
841{
842	local key=$1; shift
843
844	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
845}
846
847forwarding_enable()
848{
849	sysctl_set net.ipv4.conf.all.forwarding 1
850	sysctl_set net.ipv6.conf.all.forwarding 1
851}
852
853forwarding_restore()
854{
855	sysctl_restore net.ipv6.conf.all.forwarding
856	sysctl_restore net.ipv4.conf.all.forwarding
857}
858
859declare -A MTU_ORIG
860mtu_set()
861{
862	local dev=$1; shift
863	local mtu=$1; shift
864
865	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
866	ip link set dev $dev mtu $mtu
867}
868
869mtu_restore()
870{
871	local dev=$1; shift
872
873	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
874}
875
876tc_offload_check()
877{
878	local num_netifs=${1:-$NUM_NETIFS}
879
880	for ((i = 1; i <= num_netifs; ++i)); do
881		ethtool -k ${NETIFS[p$i]} \
882			| grep "hw-tc-offload: on" &> /dev/null
883		if [[ $? -ne 0 ]]; then
884			return 1
885		fi
886	done
887
888	return 0
889}
890
891trap_install()
892{
893	local dev=$1; shift
894	local direction=$1; shift
895
896	# Some devices may not support or need in-hardware trapping of traffic
897	# (e.g. the veth pairs that this library creates for non-existent
898	# loopbacks). Use continue instead, so that there is a filter in there
899	# (some tests check counters), and so that other filters are still
900	# processed.
901	tc filter add dev $dev $direction pref 1 \
902		flower skip_sw action trap 2>/dev/null \
903	    || tc filter add dev $dev $direction pref 1 \
904		       flower action continue
905}
906
907trap_uninstall()
908{
909	local dev=$1; shift
910	local direction=$1; shift
911
912	tc filter del dev $dev $direction pref 1 flower
913}
914
915slow_path_trap_install()
916{
917	# For slow-path testing, we need to install a trap to get to
918	# slow path the packets that would otherwise be switched in HW.
919	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
920		trap_install "$@"
921	fi
922}
923
924slow_path_trap_uninstall()
925{
926	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
927		trap_uninstall "$@"
928	fi
929}
930
931__icmp_capture_add_del()
932{
933	local add_del=$1; shift
934	local pref=$1; shift
935	local vsuf=$1; shift
936	local tundev=$1; shift
937	local filter=$1; shift
938
939	tc filter $add_del dev "$tundev" ingress \
940	   proto ip$vsuf pref $pref \
941	   flower ip_proto icmp$vsuf $filter \
942	   action pass
943}
944
945icmp_capture_install()
946{
947	__icmp_capture_add_del add 100 "" "$@"
948}
949
950icmp_capture_uninstall()
951{
952	__icmp_capture_add_del del 100 "" "$@"
953}
954
955icmp6_capture_install()
956{
957	__icmp_capture_add_del add 100 v6 "$@"
958}
959
960icmp6_capture_uninstall()
961{
962	__icmp_capture_add_del del 100 v6 "$@"
963}
964
965__vlan_capture_add_del()
966{
967	local add_del=$1; shift
968	local pref=$1; shift
969	local dev=$1; shift
970	local filter=$1; shift
971
972	tc filter $add_del dev "$dev" ingress \
973	   proto 802.1q pref $pref \
974	   flower $filter \
975	   action pass
976}
977
978vlan_capture_install()
979{
980	__vlan_capture_add_del add 100 "$@"
981}
982
983vlan_capture_uninstall()
984{
985	__vlan_capture_add_del del 100 "$@"
986}
987
988__dscp_capture_add_del()
989{
990	local add_del=$1; shift
991	local dev=$1; shift
992	local base=$1; shift
993	local dscp;
994
995	for prio in {0..7}; do
996		dscp=$((base + prio))
997		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
998				       "skip_hw ip_tos $((dscp << 2))"
999	done
1000}
1001
1002dscp_capture_install()
1003{
1004	local dev=$1; shift
1005	local base=$1; shift
1006
1007	__dscp_capture_add_del add $dev $base
1008}
1009
1010dscp_capture_uninstall()
1011{
1012	local dev=$1; shift
1013	local base=$1; shift
1014
1015	__dscp_capture_add_del del $dev $base
1016}
1017
1018dscp_fetch_stats()
1019{
1020	local dev=$1; shift
1021	local base=$1; shift
1022
1023	for prio in {0..7}; do
1024		local dscp=$((base + prio))
1025		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1026		echo "[$dscp]=$t "
1027	done
1028}
1029
1030matchall_sink_create()
1031{
1032	local dev=$1; shift
1033
1034	tc qdisc add dev $dev clsact
1035	tc filter add dev $dev ingress \
1036	   pref 10000 \
1037	   matchall \
1038	   action drop
1039}
1040
1041tests_run()
1042{
1043	local current_test
1044
1045	for current_test in ${TESTS:-$ALL_TESTS}; do
1046		$current_test
1047	done
1048}
1049
1050multipath_eval()
1051{
1052	local desc="$1"
1053	local weight_rp12=$2
1054	local weight_rp13=$3
1055	local packets_rp12=$4
1056	local packets_rp13=$5
1057	local weights_ratio packets_ratio diff
1058
1059	RET=0
1060
1061	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1062		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1063				| bc -l)
1064	else
1065		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1066				| bc -l)
1067	fi
1068
1069	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1070	       check_err 1 "Packet difference is 0"
1071	       log_test "Multipath"
1072	       log_info "Expected ratio $weights_ratio"
1073	       return
1074	fi
1075
1076	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1077		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1078				| bc -l)
1079	else
1080		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1081				| bc -l)
1082	fi
1083
1084	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1085	diff=${diff#-}
1086
1087	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1088	check_err $? "Too large discrepancy between expected and measured ratios"
1089	log_test "$desc"
1090	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1091}
1092
1093in_ns()
1094{
1095	local name=$1; shift
1096
1097	ip netns exec $name bash <<-EOF
1098		NUM_NETIFS=0
1099		source lib.sh
1100		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1101	EOF
1102}
1103
1104##############################################################################
1105# Tests
1106
1107ping_do()
1108{
1109	local if_name=$1
1110	local dip=$2
1111	local args=$3
1112	local vrf_name
1113
1114	vrf_name=$(master_name_get $if_name)
1115	ip vrf exec $vrf_name \
1116		$PING $args $dip -c $PING_COUNT -i 0.1 \
1117		-w $PING_TIMEOUT &> /dev/null
1118}
1119
1120ping_test()
1121{
1122	RET=0
1123
1124	ping_do $1 $2
1125	check_err $?
1126	log_test "ping$3"
1127}
1128
1129ping6_do()
1130{
1131	local if_name=$1
1132	local dip=$2
1133	local args=$3
1134	local vrf_name
1135
1136	vrf_name=$(master_name_get $if_name)
1137	ip vrf exec $vrf_name \
1138		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1139		-w $PING_TIMEOUT &> /dev/null
1140}
1141
1142ping6_test()
1143{
1144	RET=0
1145
1146	ping6_do $1 $2
1147	check_err $?
1148	log_test "ping6$3"
1149}
1150
1151learning_test()
1152{
1153	local bridge=$1
1154	local br_port1=$2	# Connected to `host1_if`.
1155	local host1_if=$3
1156	local host2_if=$4
1157	local mac=de:ad:be:ef:13:37
1158	local ageing_time
1159
1160	RET=0
1161
1162	bridge -j fdb show br $bridge brport $br_port1 \
1163		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1164	check_fail $? "Found FDB record when should not"
1165
1166	# Disable unknown unicast flooding on `br_port1` to make sure
1167	# packets are only forwarded through the port after a matching
1168	# FDB entry was installed.
1169	bridge link set dev $br_port1 flood off
1170
1171	tc qdisc add dev $host1_if ingress
1172	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1173		flower dst_mac $mac action drop
1174
1175	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1176	sleep 1
1177
1178	tc -j -s filter show dev $host1_if ingress \
1179		| jq -e ".[] | select(.options.handle == 101) \
1180		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1181	check_fail $? "Packet reached second host when should not"
1182
1183	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1184	sleep 1
1185
1186	bridge -j fdb show br $bridge brport $br_port1 \
1187		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1188	check_err $? "Did not find FDB record when should"
1189
1190	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1191	sleep 1
1192
1193	tc -j -s filter show dev $host1_if ingress \
1194		| jq -e ".[] | select(.options.handle == 101) \
1195		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1196	check_err $? "Packet did not reach second host when should"
1197
1198	# Wait for 10 seconds after the ageing time to make sure FDB
1199	# record was aged-out.
1200	ageing_time=$(bridge_ageing_time_get $bridge)
1201	sleep $((ageing_time + 10))
1202
1203	bridge -j fdb show br $bridge brport $br_port1 \
1204		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1205	check_fail $? "Found FDB record when should not"
1206
1207	bridge link set dev $br_port1 learning off
1208
1209	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1210	sleep 1
1211
1212	bridge -j fdb show br $bridge brport $br_port1 \
1213		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1214	check_fail $? "Found FDB record when should not"
1215
1216	bridge link set dev $br_port1 learning on
1217
1218	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1219	tc qdisc del dev $host1_if ingress
1220
1221	bridge link set dev $br_port1 flood on
1222
1223	log_test "FDB learning"
1224}
1225
1226flood_test_do()
1227{
1228	local should_flood=$1
1229	local mac=$2
1230	local ip=$3
1231	local host1_if=$4
1232	local host2_if=$5
1233	local err=0
1234
1235	# Add an ACL on `host2_if` which will tell us whether the packet
1236	# was flooded to it or not.
1237	tc qdisc add dev $host2_if ingress
1238	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1239		flower dst_mac $mac action drop
1240
1241	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1242	sleep 1
1243
1244	tc -j -s filter show dev $host2_if ingress \
1245		| jq -e ".[] | select(.options.handle == 101) \
1246		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1247	if [[ $? -ne 0 && $should_flood == "true" || \
1248	      $? -eq 0 && $should_flood == "false" ]]; then
1249		err=1
1250	fi
1251
1252	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1253	tc qdisc del dev $host2_if ingress
1254
1255	return $err
1256}
1257
1258flood_unicast_test()
1259{
1260	local br_port=$1
1261	local host1_if=$2
1262	local host2_if=$3
1263	local mac=de:ad:be:ef:13:37
1264	local ip=192.0.2.100
1265
1266	RET=0
1267
1268	bridge link set dev $br_port flood off
1269
1270	flood_test_do false $mac $ip $host1_if $host2_if
1271	check_err $? "Packet flooded when should not"
1272
1273	bridge link set dev $br_port flood on
1274
1275	flood_test_do true $mac $ip $host1_if $host2_if
1276	check_err $? "Packet was not flooded when should"
1277
1278	log_test "Unknown unicast flood"
1279}
1280
1281flood_multicast_test()
1282{
1283	local br_port=$1
1284	local host1_if=$2
1285	local host2_if=$3
1286	local mac=01:00:5e:00:00:01
1287	local ip=239.0.0.1
1288
1289	RET=0
1290
1291	bridge link set dev $br_port mcast_flood off
1292
1293	flood_test_do false $mac $ip $host1_if $host2_if
1294	check_err $? "Packet flooded when should not"
1295
1296	bridge link set dev $br_port mcast_flood on
1297
1298	flood_test_do true $mac $ip $host1_if $host2_if
1299	check_err $? "Packet was not flooded when should"
1300
1301	log_test "Unregistered multicast flood"
1302}
1303
1304flood_test()
1305{
1306	# `br_port` is connected to `host2_if`
1307	local br_port=$1
1308	local host1_if=$2
1309	local host2_if=$3
1310
1311	flood_unicast_test $br_port $host1_if $host2_if
1312	flood_multicast_test $br_port $host1_if $host2_if
1313}
1314
1315__start_traffic()
1316{
1317	local proto=$1; shift
1318	local h_in=$1; shift    # Where the traffic egresses the host
1319	local sip=$1; shift
1320	local dip=$1; shift
1321	local dmac=$1; shift
1322
1323	$MZ $h_in -p 8000 -A $sip -B $dip -c 0 \
1324		-a own -b $dmac -t "$proto" -q "$@" &
1325	sleep 1
1326}
1327
1328start_traffic()
1329{
1330	__start_traffic udp "$@"
1331}
1332
1333start_tcp_traffic()
1334{
1335	__start_traffic tcp "$@"
1336}
1337
1338stop_traffic()
1339{
1340	# Suppress noise from killing mausezahn.
1341	{ kill %% && wait %%; } 2>/dev/null
1342}
1343
1344tcpdump_start()
1345{
1346	local if_name=$1; shift
1347	local ns=$1; shift
1348
1349	capfile=$(mktemp)
1350	capout=$(mktemp)
1351
1352	if [ -z $ns ]; then
1353		ns_cmd=""
1354	else
1355		ns_cmd="ip netns exec ${ns}"
1356	fi
1357
1358	if [ -z $SUDO_USER ] ; then
1359		capuser=""
1360	else
1361		capuser="-Z $SUDO_USER"
1362	fi
1363
1364	$ns_cmd tcpdump -e -n -Q in -i $if_name \
1365		-s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 &
1366	cappid=$!
1367
1368	sleep 1
1369}
1370
1371tcpdump_stop()
1372{
1373	$ns_cmd kill $cappid
1374	sleep 1
1375}
1376
1377tcpdump_cleanup()
1378{
1379	rm $capfile $capout
1380}
1381
1382tcpdump_show()
1383{
1384	tcpdump -e -n -r $capfile 2>&1
1385}
1386
1387# return 0 if the packet wasn't seen on host2_if or 1 if it was
1388mcast_packet_test()
1389{
1390	local mac=$1
1391	local src_ip=$2
1392	local ip=$3
1393	local host1_if=$4
1394	local host2_if=$5
1395	local seen=0
1396	local tc_proto="ip"
1397	local mz_v6arg=""
1398
1399	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1400	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1401		tc_proto="ipv6"
1402		mz_v6arg="-6"
1403	fi
1404
1405	# Add an ACL on `host2_if` which will tell us whether the packet
1406	# was received by it or not.
1407	tc qdisc add dev $host2_if ingress
1408	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1409		flower ip_proto udp dst_mac $mac action drop
1410
1411	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1412	sleep 1
1413
1414	tc -j -s filter show dev $host2_if ingress \
1415		| jq -e ".[] | select(.options.handle == 101) \
1416		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1417	if [[ $? -eq 0 ]]; then
1418		seen=1
1419	fi
1420
1421	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1422	tc qdisc del dev $host2_if ingress
1423
1424	return $seen
1425}
1426
1427brmcast_check_sg_entries()
1428{
1429	local report=$1; shift
1430	local slist=("$@")
1431	local sarg=""
1432
1433	for src in "${slist[@]}"; do
1434		sarg="${sarg} and .source_list[].address == \"$src\""
1435	done
1436	bridge -j -d -s mdb show dev br0 \
1437		| jq -e ".[].mdb[] | \
1438			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1439	check_err $? "Wrong *,G entry source list after $report report"
1440
1441	for sgent in "${slist[@]}"; do
1442		bridge -j -d -s mdb show dev br0 \
1443			| jq -e ".[].mdb[] | \
1444				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1445		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1446	done
1447}
1448
1449brmcast_check_sg_fwding()
1450{
1451	local should_fwd=$1; shift
1452	local sources=("$@")
1453
1454	for src in "${sources[@]}"; do
1455		local retval=0
1456
1457		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1458		retval=$?
1459		if [ $should_fwd -eq 1 ]; then
1460			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1461		else
1462			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1463		fi
1464	done
1465}
1466
1467brmcast_check_sg_state()
1468{
1469	local is_blocked=$1; shift
1470	local sources=("$@")
1471	local should_fail=1
1472
1473	if [ $is_blocked -eq 1 ]; then
1474		should_fail=0
1475	fi
1476
1477	for src in "${sources[@]}"; do
1478		bridge -j -d -s mdb show dev br0 \
1479			| jq -e ".[].mdb[] | \
1480				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1481				 .source_list[] |
1482				 select(.address == \"$src\") |
1483				 select(.timer == \"0.00\")" &>/dev/null
1484		check_err_fail $should_fail $? "Entry $src has zero timer"
1485
1486		bridge -j -d -s mdb show dev br0 \
1487			| jq -e ".[].mdb[] | \
1488				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1489				 .flags[] == \"blocked\")" &>/dev/null
1490		check_err_fail $should_fail $? "Entry $src has blocked flag"
1491	done
1492}
1493