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