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