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
754humanize()
755{
756	local speed=$1; shift
757
758	for unit in bps Kbps Mbps Gbps; do
759		if (($(echo "$speed < 1024" | bc))); then
760			break
761		fi
762
763		speed=$(echo "scale=1; $speed / 1024" | bc)
764	done
765
766	echo "$speed${unit}"
767}
768
769rate()
770{
771	local t0=$1; shift
772	local t1=$1; shift
773	local interval=$1; shift
774
775	echo $((8 * (t1 - t0) / interval))
776}
777
778packets_rate()
779{
780	local t0=$1; shift
781	local t1=$1; shift
782	local interval=$1; shift
783
784	echo $(((t1 - t0) / interval))
785}
786
787mac_get()
788{
789	local if_name=$1
790
791	ip -j link show dev $if_name | jq -r '.[]["address"]'
792}
793
794bridge_ageing_time_get()
795{
796	local bridge=$1
797	local ageing_time
798
799	# Need to divide by 100 to convert to seconds.
800	ageing_time=$(ip -j -d link show dev $bridge \
801		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
802	echo $((ageing_time / 100))
803}
804
805declare -A SYSCTL_ORIG
806sysctl_set()
807{
808	local key=$1; shift
809	local value=$1; shift
810
811	SYSCTL_ORIG[$key]=$(sysctl -n $key)
812	sysctl -qw $key=$value
813}
814
815sysctl_restore()
816{
817	local key=$1; shift
818
819	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
820}
821
822forwarding_enable()
823{
824	sysctl_set net.ipv4.conf.all.forwarding 1
825	sysctl_set net.ipv6.conf.all.forwarding 1
826}
827
828forwarding_restore()
829{
830	sysctl_restore net.ipv6.conf.all.forwarding
831	sysctl_restore net.ipv4.conf.all.forwarding
832}
833
834declare -A MTU_ORIG
835mtu_set()
836{
837	local dev=$1; shift
838	local mtu=$1; shift
839
840	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
841	ip link set dev $dev mtu $mtu
842}
843
844mtu_restore()
845{
846	local dev=$1; shift
847
848	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
849}
850
851tc_offload_check()
852{
853	local num_netifs=${1:-$NUM_NETIFS}
854
855	for ((i = 1; i <= num_netifs; ++i)); do
856		ethtool -k ${NETIFS[p$i]} \
857			| grep "hw-tc-offload: on" &> /dev/null
858		if [[ $? -ne 0 ]]; then
859			return 1
860		fi
861	done
862
863	return 0
864}
865
866trap_install()
867{
868	local dev=$1; shift
869	local direction=$1; shift
870
871	# Some devices may not support or need in-hardware trapping of traffic
872	# (e.g. the veth pairs that this library creates for non-existent
873	# loopbacks). Use continue instead, so that there is a filter in there
874	# (some tests check counters), and so that other filters are still
875	# processed.
876	tc filter add dev $dev $direction pref 1 \
877		flower skip_sw action trap 2>/dev/null \
878	    || tc filter add dev $dev $direction pref 1 \
879		       flower action continue
880}
881
882trap_uninstall()
883{
884	local dev=$1; shift
885	local direction=$1; shift
886
887	tc filter del dev $dev $direction pref 1 flower
888}
889
890slow_path_trap_install()
891{
892	# For slow-path testing, we need to install a trap to get to
893	# slow path the packets that would otherwise be switched in HW.
894	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
895		trap_install "$@"
896	fi
897}
898
899slow_path_trap_uninstall()
900{
901	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
902		trap_uninstall "$@"
903	fi
904}
905
906__icmp_capture_add_del()
907{
908	local add_del=$1; shift
909	local pref=$1; shift
910	local vsuf=$1; shift
911	local tundev=$1; shift
912	local filter=$1; shift
913
914	tc filter $add_del dev "$tundev" ingress \
915	   proto ip$vsuf pref $pref \
916	   flower ip_proto icmp$vsuf $filter \
917	   action pass
918}
919
920icmp_capture_install()
921{
922	__icmp_capture_add_del add 100 "" "$@"
923}
924
925icmp_capture_uninstall()
926{
927	__icmp_capture_add_del del 100 "" "$@"
928}
929
930icmp6_capture_install()
931{
932	__icmp_capture_add_del add 100 v6 "$@"
933}
934
935icmp6_capture_uninstall()
936{
937	__icmp_capture_add_del del 100 v6 "$@"
938}
939
940__vlan_capture_add_del()
941{
942	local add_del=$1; shift
943	local pref=$1; shift
944	local dev=$1; shift
945	local filter=$1; shift
946
947	tc filter $add_del dev "$dev" ingress \
948	   proto 802.1q pref $pref \
949	   flower $filter \
950	   action pass
951}
952
953vlan_capture_install()
954{
955	__vlan_capture_add_del add 100 "$@"
956}
957
958vlan_capture_uninstall()
959{
960	__vlan_capture_add_del del 100 "$@"
961}
962
963__dscp_capture_add_del()
964{
965	local add_del=$1; shift
966	local dev=$1; shift
967	local base=$1; shift
968	local dscp;
969
970	for prio in {0..7}; do
971		dscp=$((base + prio))
972		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
973				       "skip_hw ip_tos $((dscp << 2))"
974	done
975}
976
977dscp_capture_install()
978{
979	local dev=$1; shift
980	local base=$1; shift
981
982	__dscp_capture_add_del add $dev $base
983}
984
985dscp_capture_uninstall()
986{
987	local dev=$1; shift
988	local base=$1; shift
989
990	__dscp_capture_add_del del $dev $base
991}
992
993dscp_fetch_stats()
994{
995	local dev=$1; shift
996	local base=$1; shift
997
998	for prio in {0..7}; do
999		local dscp=$((base + prio))
1000		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1001		echo "[$dscp]=$t "
1002	done
1003}
1004
1005matchall_sink_create()
1006{
1007	local dev=$1; shift
1008
1009	tc qdisc add dev $dev clsact
1010	tc filter add dev $dev ingress \
1011	   pref 10000 \
1012	   matchall \
1013	   action drop
1014}
1015
1016tests_run()
1017{
1018	local current_test
1019
1020	for current_test in ${TESTS:-$ALL_TESTS}; do
1021		$current_test
1022	done
1023}
1024
1025multipath_eval()
1026{
1027	local desc="$1"
1028	local weight_rp12=$2
1029	local weight_rp13=$3
1030	local packets_rp12=$4
1031	local packets_rp13=$5
1032	local weights_ratio packets_ratio diff
1033
1034	RET=0
1035
1036	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1037		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1038				| bc -l)
1039	else
1040		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1041				| bc -l)
1042	fi
1043
1044	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1045	       check_err 1 "Packet difference is 0"
1046	       log_test "Multipath"
1047	       log_info "Expected ratio $weights_ratio"
1048	       return
1049	fi
1050
1051	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1052		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1053				| bc -l)
1054	else
1055		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1056				| bc -l)
1057	fi
1058
1059	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1060	diff=${diff#-}
1061
1062	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1063	check_err $? "Too large discrepancy between expected and measured ratios"
1064	log_test "$desc"
1065	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1066}
1067
1068in_ns()
1069{
1070	local name=$1; shift
1071
1072	ip netns exec $name bash <<-EOF
1073		NUM_NETIFS=0
1074		source lib.sh
1075		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1076	EOF
1077}
1078
1079##############################################################################
1080# Tests
1081
1082ping_do()
1083{
1084	local if_name=$1
1085	local dip=$2
1086	local args=$3
1087	local vrf_name
1088
1089	vrf_name=$(master_name_get $if_name)
1090	ip vrf exec $vrf_name \
1091		$PING $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
1092}
1093
1094ping_test()
1095{
1096	RET=0
1097
1098	ping_do $1 $2
1099	check_err $?
1100	log_test "ping$3"
1101}
1102
1103ping6_do()
1104{
1105	local if_name=$1
1106	local dip=$2
1107	local args=$3
1108	local vrf_name
1109
1110	vrf_name=$(master_name_get $if_name)
1111	ip vrf exec $vrf_name \
1112		$PING6 $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
1113}
1114
1115ping6_test()
1116{
1117	RET=0
1118
1119	ping6_do $1 $2
1120	check_err $?
1121	log_test "ping6$3"
1122}
1123
1124learning_test()
1125{
1126	local bridge=$1
1127	local br_port1=$2	# Connected to `host1_if`.
1128	local host1_if=$3
1129	local host2_if=$4
1130	local mac=de:ad:be:ef:13:37
1131	local ageing_time
1132
1133	RET=0
1134
1135	bridge -j fdb show br $bridge brport $br_port1 \
1136		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1137	check_fail $? "Found FDB record when should not"
1138
1139	# Disable unknown unicast flooding on `br_port1` to make sure
1140	# packets are only forwarded through the port after a matching
1141	# FDB entry was installed.
1142	bridge link set dev $br_port1 flood off
1143
1144	tc qdisc add dev $host1_if ingress
1145	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1146		flower dst_mac $mac action drop
1147
1148	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1149	sleep 1
1150
1151	tc -j -s filter show dev $host1_if ingress \
1152		| jq -e ".[] | select(.options.handle == 101) \
1153		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1154	check_fail $? "Packet reached second host when should not"
1155
1156	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1157	sleep 1
1158
1159	bridge -j fdb show br $bridge brport $br_port1 \
1160		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1161	check_err $? "Did not find FDB record when should"
1162
1163	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1164	sleep 1
1165
1166	tc -j -s filter show dev $host1_if ingress \
1167		| jq -e ".[] | select(.options.handle == 101) \
1168		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1169	check_err $? "Packet did not reach second host when should"
1170
1171	# Wait for 10 seconds after the ageing time to make sure FDB
1172	# record was aged-out.
1173	ageing_time=$(bridge_ageing_time_get $bridge)
1174	sleep $((ageing_time + 10))
1175
1176	bridge -j fdb show br $bridge brport $br_port1 \
1177		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1178	check_fail $? "Found FDB record when should not"
1179
1180	bridge link set dev $br_port1 learning off
1181
1182	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1183	sleep 1
1184
1185	bridge -j fdb show br $bridge brport $br_port1 \
1186		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1187	check_fail $? "Found FDB record when should not"
1188
1189	bridge link set dev $br_port1 learning on
1190
1191	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1192	tc qdisc del dev $host1_if ingress
1193
1194	bridge link set dev $br_port1 flood on
1195
1196	log_test "FDB learning"
1197}
1198
1199flood_test_do()
1200{
1201	local should_flood=$1
1202	local mac=$2
1203	local ip=$3
1204	local host1_if=$4
1205	local host2_if=$5
1206	local err=0
1207
1208	# Add an ACL on `host2_if` which will tell us whether the packet
1209	# was flooded to it or not.
1210	tc qdisc add dev $host2_if ingress
1211	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1212		flower dst_mac $mac action drop
1213
1214	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1215	sleep 1
1216
1217	tc -j -s filter show dev $host2_if ingress \
1218		| jq -e ".[] | select(.options.handle == 101) \
1219		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1220	if [[ $? -ne 0 && $should_flood == "true" || \
1221	      $? -eq 0 && $should_flood == "false" ]]; then
1222		err=1
1223	fi
1224
1225	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1226	tc qdisc del dev $host2_if ingress
1227
1228	return $err
1229}
1230
1231flood_unicast_test()
1232{
1233	local br_port=$1
1234	local host1_if=$2
1235	local host2_if=$3
1236	local mac=de:ad:be:ef:13:37
1237	local ip=192.0.2.100
1238
1239	RET=0
1240
1241	bridge link set dev $br_port flood off
1242
1243	flood_test_do false $mac $ip $host1_if $host2_if
1244	check_err $? "Packet flooded when should not"
1245
1246	bridge link set dev $br_port flood on
1247
1248	flood_test_do true $mac $ip $host1_if $host2_if
1249	check_err $? "Packet was not flooded when should"
1250
1251	log_test "Unknown unicast flood"
1252}
1253
1254flood_multicast_test()
1255{
1256	local br_port=$1
1257	local host1_if=$2
1258	local host2_if=$3
1259	local mac=01:00:5e:00:00:01
1260	local ip=239.0.0.1
1261
1262	RET=0
1263
1264	bridge link set dev $br_port mcast_flood off
1265
1266	flood_test_do false $mac $ip $host1_if $host2_if
1267	check_err $? "Packet flooded when should not"
1268
1269	bridge link set dev $br_port mcast_flood on
1270
1271	flood_test_do true $mac $ip $host1_if $host2_if
1272	check_err $? "Packet was not flooded when should"
1273
1274	log_test "Unregistered multicast flood"
1275}
1276
1277flood_test()
1278{
1279	# `br_port` is connected to `host2_if`
1280	local br_port=$1
1281	local host1_if=$2
1282	local host2_if=$3
1283
1284	flood_unicast_test $br_port $host1_if $host2_if
1285	flood_multicast_test $br_port $host1_if $host2_if
1286}
1287
1288__start_traffic()
1289{
1290	local proto=$1; shift
1291	local h_in=$1; shift    # Where the traffic egresses the host
1292	local sip=$1; shift
1293	local dip=$1; shift
1294	local dmac=$1; shift
1295
1296	$MZ $h_in -p 8000 -A $sip -B $dip -c 0 \
1297		-a own -b $dmac -t "$proto" -q "$@" &
1298	sleep 1
1299}
1300
1301start_traffic()
1302{
1303	__start_traffic udp "$@"
1304}
1305
1306start_tcp_traffic()
1307{
1308	__start_traffic tcp "$@"
1309}
1310
1311stop_traffic()
1312{
1313	# Suppress noise from killing mausezahn.
1314	{ kill %% && wait %%; } 2>/dev/null
1315}
1316
1317tcpdump_start()
1318{
1319	local if_name=$1; shift
1320	local ns=$1; shift
1321
1322	capfile=$(mktemp)
1323	capout=$(mktemp)
1324
1325	if [ -z $ns ]; then
1326		ns_cmd=""
1327	else
1328		ns_cmd="ip netns exec ${ns}"
1329	fi
1330
1331	if [ -z $SUDO_USER ] ; then
1332		capuser=""
1333	else
1334		capuser="-Z $SUDO_USER"
1335	fi
1336
1337	$ns_cmd tcpdump -e -n -Q in -i $if_name \
1338		-s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 &
1339	cappid=$!
1340
1341	sleep 1
1342}
1343
1344tcpdump_stop()
1345{
1346	$ns_cmd kill $cappid
1347	sleep 1
1348}
1349
1350tcpdump_cleanup()
1351{
1352	rm $capfile $capout
1353}
1354
1355tcpdump_show()
1356{
1357	tcpdump -e -n -r $capfile 2>&1
1358}
1359
1360# return 0 if the packet wasn't seen on host2_if or 1 if it was
1361mcast_packet_test()
1362{
1363	local mac=$1
1364	local src_ip=$2
1365	local ip=$3
1366	local host1_if=$4
1367	local host2_if=$5
1368	local seen=0
1369	local tc_proto="ip"
1370	local mz_v6arg=""
1371
1372	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1373	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1374		tc_proto="ipv6"
1375		mz_v6arg="-6"
1376	fi
1377
1378	# Add an ACL on `host2_if` which will tell us whether the packet
1379	# was received by it or not.
1380	tc qdisc add dev $host2_if ingress
1381	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1382		flower ip_proto udp dst_mac $mac action drop
1383
1384	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1385	sleep 1
1386
1387	tc -j -s filter show dev $host2_if ingress \
1388		| jq -e ".[] | select(.options.handle == 101) \
1389		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1390	if [[ $? -eq 0 ]]; then
1391		seen=1
1392	fi
1393
1394	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1395	tc qdisc del dev $host2_if ingress
1396
1397	return $seen
1398}
1399
1400brmcast_check_sg_entries()
1401{
1402	local report=$1; shift
1403	local slist=("$@")
1404	local sarg=""
1405
1406	for src in "${slist[@]}"; do
1407		sarg="${sarg} and .source_list[].address == \"$src\""
1408	done
1409	bridge -j -d -s mdb show dev br0 \
1410		| jq -e ".[].mdb[] | \
1411			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1412	check_err $? "Wrong *,G entry source list after $report report"
1413
1414	for sgent in "${slist[@]}"; do
1415		bridge -j -d -s mdb show dev br0 \
1416			| jq -e ".[].mdb[] | \
1417				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1418		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1419	done
1420}
1421
1422brmcast_check_sg_fwding()
1423{
1424	local should_fwd=$1; shift
1425	local sources=("$@")
1426
1427	for src in "${sources[@]}"; do
1428		local retval=0
1429
1430		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1431		retval=$?
1432		if [ $should_fwd -eq 1 ]; then
1433			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1434		else
1435			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1436		fi
1437	done
1438}
1439
1440brmcast_check_sg_state()
1441{
1442	local is_blocked=$1; shift
1443	local sources=("$@")
1444	local should_fail=1
1445
1446	if [ $is_blocked -eq 1 ]; then
1447		should_fail=0
1448	fi
1449
1450	for src in "${sources[@]}"; do
1451		bridge -j -d -s mdb show dev br0 \
1452			| jq -e ".[].mdb[] | \
1453				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1454				 .source_list[] |
1455				 select(.address == \"$src\") |
1456				 select(.timer == \"0.00\")" &>/dev/null
1457		check_err_fail $should_fail $? "Entry $src has zero timer"
1458
1459		bridge -j -d -s mdb show dev br0 \
1460			| jq -e ".[].mdb[] | \
1461				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1462				 .flags[] == \"blocked\")" &>/dev/null
1463		check_err_fail $should_fail $? "Entry $src has blocked flag"
1464	done
1465}
1466