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