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