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