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