xref: /linux/tools/testing/selftests/net/forwarding/lib.sh (revision ae22a94997b8a03dcb3c922857c203246711f9d4)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Can be overridden by the configuration file.
8PING=${PING:=ping}
9PING6=${PING6:=ping6}
10MZ=${MZ:=mausezahn}
11MZ_DELAY=${MZ_DELAY:=0}
12ARPING=${ARPING:=arping}
13TEAMD=${TEAMD:=teamd}
14WAIT_TIME=${WAIT_TIME:=5}
15PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
16PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
17NETIF_TYPE=${NETIF_TYPE:=veth}
18NETIF_CREATE=${NETIF_CREATE:=yes}
19MCD=${MCD:=smcrouted}
20MC_CLI=${MC_CLI:=smcroutectl}
21PING_COUNT=${PING_COUNT:=10}
22PING_TIMEOUT=${PING_TIMEOUT:=5}
23WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
24INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
25LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
26REQUIRE_JQ=${REQUIRE_JQ:=yes}
27REQUIRE_MZ=${REQUIRE_MZ:=yes}
28REQUIRE_MTOOLS=${REQUIRE_MTOOLS:=no}
29STABLE_MAC_ADDRS=${STABLE_MAC_ADDRS:=no}
30TCPDUMP_EXTRA_FLAGS=${TCPDUMP_EXTRA_FLAGS:=}
31TROUTE6=${TROUTE6:=traceroute6}
32
33net_forwarding_dir=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")
34
35if [[ -f $net_forwarding_dir/forwarding.config ]]; then
36	source "$net_forwarding_dir/forwarding.config"
37fi
38
39source "$net_forwarding_dir/../lib.sh"
40
41# timeout in seconds
42slowwait()
43{
44	local timeout=$1; shift
45
46	local start_time="$(date -u +%s)"
47	while true
48	do
49		local out
50		out=$("$@")
51		local ret=$?
52		if ((!ret)); then
53			echo -n "$out"
54			return 0
55		fi
56
57		local current_time="$(date -u +%s)"
58		if ((current_time - start_time > timeout)); then
59			echo -n "$out"
60			return 1
61		fi
62
63		sleep 0.1
64	done
65}
66
67##############################################################################
68# Sanity checks
69
70check_tc_version()
71{
72	tc -j &> /dev/null
73	if [[ $? -ne 0 ]]; then
74		echo "SKIP: iproute2 too old; tc is missing JSON support"
75		exit $ksft_skip
76	fi
77}
78
79# Old versions of tc don't understand "mpls_uc"
80check_tc_mpls_support()
81{
82	local dev=$1; shift
83
84	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
85		matchall action pipe &> /dev/null
86	if [[ $? -ne 0 ]]; then
87		echo "SKIP: iproute2 too old; tc is missing MPLS support"
88		return $ksft_skip
89	fi
90	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
91		matchall
92}
93
94# Old versions of tc produce invalid json output for mpls lse statistics
95check_tc_mpls_lse_stats()
96{
97	local dev=$1; shift
98	local ret;
99
100	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
101		flower mpls lse depth 2                                 \
102		action continue &> /dev/null
103
104	if [[ $? -ne 0 ]]; then
105		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
106		return $ksft_skip
107	fi
108
109	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
110	ret=$?
111	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
112		flower
113
114	if [[ $ret -ne 0 ]]; then
115		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
116		return $ksft_skip
117	fi
118}
119
120check_tc_shblock_support()
121{
122	tc filter help 2>&1 | grep block &> /dev/null
123	if [[ $? -ne 0 ]]; then
124		echo "SKIP: iproute2 too old; tc is missing shared block support"
125		exit $ksft_skip
126	fi
127}
128
129check_tc_chain_support()
130{
131	tc help 2>&1|grep chain &> /dev/null
132	if [[ $? -ne 0 ]]; then
133		echo "SKIP: iproute2 too old; tc is missing chain support"
134		exit $ksft_skip
135	fi
136}
137
138check_tc_action_hw_stats_support()
139{
140	tc actions help 2>&1 | grep -q hw_stats
141	if [[ $? -ne 0 ]]; then
142		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
143		exit $ksft_skip
144	fi
145}
146
147check_tc_fp_support()
148{
149	tc qdisc add dev lo mqprio help 2>&1 | grep -q "fp "
150	if [[ $? -ne 0 ]]; then
151		echo "SKIP: iproute2 too old; tc is missing frame preemption support"
152		exit $ksft_skip
153	fi
154}
155
156check_ethtool_lanes_support()
157{
158	ethtool --help 2>&1| grep lanes &> /dev/null
159	if [[ $? -ne 0 ]]; then
160		echo "SKIP: ethtool too old; it is missing lanes support"
161		exit $ksft_skip
162	fi
163}
164
165check_ethtool_mm_support()
166{
167	ethtool --help 2>&1| grep -- '--show-mm' &> /dev/null
168	if [[ $? -ne 0 ]]; then
169		echo "SKIP: ethtool too old; it is missing MAC Merge layer support"
170		exit $ksft_skip
171	fi
172}
173
174check_ethtool_counter_group_support()
175{
176	ethtool --help 2>&1| grep -- '--all-groups' &> /dev/null
177	if [[ $? -ne 0 ]]; then
178		echo "SKIP: ethtool too old; it is missing standard counter group support"
179		exit $ksft_skip
180	fi
181}
182
183check_ethtool_pmac_std_stats_support()
184{
185	local dev=$1; shift
186	local grp=$1; shift
187
188	[ 0 -ne $(ethtool --json -S $dev --all-groups --src pmac 2>/dev/null \
189		| jq ".[].\"$grp\" | length") ]
190}
191
192check_locked_port_support()
193{
194	if ! bridge -d link show | grep -q " locked"; then
195		echo "SKIP: iproute2 too old; Locked port feature not supported."
196		return $ksft_skip
197	fi
198}
199
200check_port_mab_support()
201{
202	if ! bridge -d link show | grep -q "mab"; then
203		echo "SKIP: iproute2 too old; MacAuth feature not supported."
204		return $ksft_skip
205	fi
206}
207
208skip_on_veth()
209{
210	local kind=$(ip -j -d link show dev ${NETIFS[p1]} |
211		jq -r '.[].linkinfo.info_kind')
212
213	if [[ $kind == veth ]]; then
214		echo "SKIP: Test cannot be run with veth pairs"
215		exit $ksft_skip
216	fi
217}
218
219if [[ "$(id -u)" -ne 0 ]]; then
220	echo "SKIP: need root privileges"
221	exit $ksft_skip
222fi
223
224if [[ "$CHECK_TC" = "yes" ]]; then
225	check_tc_version
226fi
227
228require_command()
229{
230	local cmd=$1; shift
231
232	if [[ ! -x "$(command -v "$cmd")" ]]; then
233		echo "SKIP: $cmd not installed"
234		exit $ksft_skip
235	fi
236}
237
238if [[ "$REQUIRE_JQ" = "yes" ]]; then
239	require_command jq
240fi
241if [[ "$REQUIRE_MZ" = "yes" ]]; then
242	require_command $MZ
243fi
244if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then
245	# https://github.com/vladimiroltean/mtools/
246	# patched for IPv6 support
247	require_command msend
248	require_command mreceive
249fi
250
251if [[ ! -v NUM_NETIFS ]]; then
252	echo "SKIP: importer does not define \"NUM_NETIFS\""
253	exit $ksft_skip
254fi
255
256##############################################################################
257# Command line options handling
258
259count=0
260
261while [[ $# -gt 0 ]]; do
262	if [[ "$count" -eq "0" ]]; then
263		unset NETIFS
264		declare -A NETIFS
265	fi
266	count=$((count + 1))
267	NETIFS[p$count]="$1"
268	shift
269done
270
271##############################################################################
272# Network interfaces configuration
273
274create_netif_veth()
275{
276	local i
277
278	for ((i = 1; i <= NUM_NETIFS; ++i)); do
279		local j=$((i+1))
280
281		if [ -z ${NETIFS[p$i]} ]; then
282			echo "SKIP: Cannot create interface. Name not specified"
283			exit $ksft_skip
284		fi
285
286		ip link show dev ${NETIFS[p$i]} &> /dev/null
287		if [[ $? -ne 0 ]]; then
288			ip link add ${NETIFS[p$i]} type veth \
289				peer name ${NETIFS[p$j]}
290			if [[ $? -ne 0 ]]; then
291				echo "Failed to create netif"
292				exit 1
293			fi
294		fi
295		i=$j
296	done
297}
298
299create_netif()
300{
301	case "$NETIF_TYPE" in
302	veth) create_netif_veth
303	      ;;
304	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
305	   exit 1
306	   ;;
307	esac
308}
309
310declare -A MAC_ADDR_ORIG
311mac_addr_prepare()
312{
313	local new_addr=
314	local dev=
315
316	for ((i = 1; i <= NUM_NETIFS; ++i)); do
317		dev=${NETIFS[p$i]}
318		new_addr=$(printf "00:01:02:03:04:%02x" $i)
319
320		MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address')
321		# Strip quotes
322		MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/}
323		ip link set dev $dev address $new_addr
324	done
325}
326
327mac_addr_restore()
328{
329	local dev=
330
331	for ((i = 1; i <= NUM_NETIFS; ++i)); do
332		dev=${NETIFS[p$i]}
333		ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]}
334	done
335}
336
337if [[ "$NETIF_CREATE" = "yes" ]]; then
338	create_netif
339fi
340
341if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
342	mac_addr_prepare
343fi
344
345for ((i = 1; i <= NUM_NETIFS; ++i)); do
346	ip link show dev ${NETIFS[p$i]} &> /dev/null
347	if [[ $? -ne 0 ]]; then
348		echo "SKIP: could not find all required interfaces"
349		exit $ksft_skip
350	fi
351done
352
353##############################################################################
354# Helpers
355
356# Exit status to return at the end. Set in case one of the tests fails.
357EXIT_STATUS=0
358# Per-test return value. Clear at the beginning of each test.
359RET=0
360
361check_err()
362{
363	local err=$1
364	local msg=$2
365
366	if [[ $RET -eq 0 && $err -ne 0 ]]; then
367		RET=$err
368		retmsg=$msg
369	fi
370}
371
372check_fail()
373{
374	local err=$1
375	local msg=$2
376
377	if [[ $RET -eq 0 && $err -eq 0 ]]; then
378		RET=1
379		retmsg=$msg
380	fi
381}
382
383check_err_fail()
384{
385	local should_fail=$1; shift
386	local err=$1; shift
387	local what=$1; shift
388
389	if ((should_fail)); then
390		check_fail $err "$what succeeded, but should have failed"
391	else
392		check_err $err "$what failed"
393	fi
394}
395
396log_test()
397{
398	local test_name=$1
399	local opt_str=$2
400
401	if [[ $# -eq 2 ]]; then
402		opt_str="($opt_str)"
403	fi
404
405	if [[ $RET -ne 0 ]]; then
406		EXIT_STATUS=1
407		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
408		if [[ ! -z "$retmsg" ]]; then
409			printf "\t%s\n" "$retmsg"
410		fi
411		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
412			echo "Hit enter to continue, 'q' to quit"
413			read a
414			[ "$a" = "q" ] && exit 1
415		fi
416		return 1
417	fi
418
419	printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
420	return 0
421}
422
423log_test_skip()
424{
425	local test_name=$1
426	local opt_str=$2
427
428	printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
429	return 0
430}
431
432log_info()
433{
434	local msg=$1
435
436	echo "INFO: $msg"
437}
438
439not()
440{
441	"$@"
442	[[ $? != 0 ]]
443}
444
445get_max()
446{
447	local arr=("$@")
448
449	max=${arr[0]}
450	for cur in ${arr[@]}; do
451		if [[ $cur -gt $max ]]; then
452			max=$cur
453		fi
454	done
455
456	echo $max
457}
458
459grep_bridge_fdb()
460{
461	local addr=$1; shift
462	local word
463	local flag
464
465	if [ "$1" == "self" ] || [ "$1" == "master" ]; then
466		word=$1; shift
467		if [ "$1" == "-v" ]; then
468			flag=$1; shift
469		fi
470	fi
471
472	$@ | grep $addr | grep $flag "$word"
473}
474
475wait_for_port_up()
476{
477	"$@" | grep -q "Link detected: yes"
478}
479
480wait_for_offload()
481{
482	"$@" | grep -q offload
483}
484
485wait_for_trap()
486{
487	"$@" | grep -q trap
488}
489
490until_counter_is()
491{
492	local expr=$1; shift
493	local current=$("$@")
494
495	echo $((current))
496	((current $expr))
497}
498
499busywait_for_counter()
500{
501	local timeout=$1; shift
502	local delta=$1; shift
503
504	local base=$("$@")
505	busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
506}
507
508slowwait_for_counter()
509{
510	local timeout=$1; shift
511	local delta=$1; shift
512
513	local base=$("$@")
514	slowwait "$timeout" until_counter_is ">= $((base + delta))" "$@"
515}
516
517setup_wait_dev()
518{
519	local dev=$1; shift
520	local wait_time=${1:-$WAIT_TIME}; shift
521
522	setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
523
524	if (($?)); then
525		check_err 1
526		log_test setup_wait_dev ": Interface $dev does not come up."
527		exit 1
528	fi
529}
530
531setup_wait_dev_with_timeout()
532{
533	local dev=$1; shift
534	local max_iterations=${1:-$WAIT_TIMEOUT}; shift
535	local wait_time=${1:-$WAIT_TIME}; shift
536	local i
537
538	for ((i = 1; i <= $max_iterations; ++i)); do
539		ip link show dev $dev up \
540			| grep 'state UP' &> /dev/null
541		if [[ $? -ne 0 ]]; then
542			sleep 1
543		else
544			sleep $wait_time
545			return 0
546		fi
547	done
548
549	return 1
550}
551
552setup_wait()
553{
554	local num_netifs=${1:-$NUM_NETIFS}
555	local i
556
557	for ((i = 1; i <= num_netifs; ++i)); do
558		setup_wait_dev ${NETIFS[p$i]} 0
559	done
560
561	# Make sure links are ready.
562	sleep $WAIT_TIME
563}
564
565cmd_jq()
566{
567	local cmd=$1
568	local jq_exp=$2
569	local jq_opts=$3
570	local ret
571	local output
572
573	output="$($cmd)"
574	# it the command fails, return error right away
575	ret=$?
576	if [[ $ret -ne 0 ]]; then
577		return $ret
578	fi
579	output=$(echo $output | jq -r $jq_opts "$jq_exp")
580	ret=$?
581	if [[ $ret -ne 0 ]]; then
582		return $ret
583	fi
584	echo $output
585	# return success only in case of non-empty output
586	[ ! -z "$output" ]
587}
588
589pre_cleanup()
590{
591	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
592		echo "Pausing before cleanup, hit any key to continue"
593		read
594	fi
595
596	if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
597		mac_addr_restore
598	fi
599}
600
601vrf_prepare()
602{
603	ip -4 rule add pref 32765 table local
604	ip -4 rule del pref 0
605	ip -6 rule add pref 32765 table local
606	ip -6 rule del pref 0
607}
608
609vrf_cleanup()
610{
611	ip -6 rule add pref 0 table local
612	ip -6 rule del pref 32765
613	ip -4 rule add pref 0 table local
614	ip -4 rule del pref 32765
615}
616
617__last_tb_id=0
618declare -A __TB_IDS
619
620__vrf_td_id_assign()
621{
622	local vrf_name=$1
623
624	__last_tb_id=$((__last_tb_id + 1))
625	__TB_IDS[$vrf_name]=$__last_tb_id
626	return $__last_tb_id
627}
628
629__vrf_td_id_lookup()
630{
631	local vrf_name=$1
632
633	return ${__TB_IDS[$vrf_name]}
634}
635
636vrf_create()
637{
638	local vrf_name=$1
639	local tb_id
640
641	__vrf_td_id_assign $vrf_name
642	tb_id=$?
643
644	ip link add dev $vrf_name type vrf table $tb_id
645	ip -4 route add table $tb_id unreachable default metric 4278198272
646	ip -6 route add table $tb_id unreachable default metric 4278198272
647}
648
649vrf_destroy()
650{
651	local vrf_name=$1
652	local tb_id
653
654	__vrf_td_id_lookup $vrf_name
655	tb_id=$?
656
657	ip -6 route del table $tb_id unreachable default metric 4278198272
658	ip -4 route del table $tb_id unreachable default metric 4278198272
659	ip link del dev $vrf_name
660}
661
662__addr_add_del()
663{
664	local if_name=$1
665	local add_del=$2
666	local array
667
668	shift
669	shift
670	array=("${@}")
671
672	for addrstr in "${array[@]}"; do
673		ip address $add_del $addrstr dev $if_name
674	done
675}
676
677__simple_if_init()
678{
679	local if_name=$1; shift
680	local vrf_name=$1; shift
681	local addrs=("${@}")
682
683	ip link set dev $if_name master $vrf_name
684	ip link set dev $if_name up
685
686	__addr_add_del $if_name add "${addrs[@]}"
687}
688
689__simple_if_fini()
690{
691	local if_name=$1; shift
692	local addrs=("${@}")
693
694	__addr_add_del $if_name del "${addrs[@]}"
695
696	ip link set dev $if_name down
697	ip link set dev $if_name nomaster
698}
699
700simple_if_init()
701{
702	local if_name=$1
703	local vrf_name
704	local array
705
706	shift
707	vrf_name=v$if_name
708	array=("${@}")
709
710	vrf_create $vrf_name
711	ip link set dev $vrf_name up
712	__simple_if_init $if_name $vrf_name "${array[@]}"
713}
714
715simple_if_fini()
716{
717	local if_name=$1
718	local vrf_name
719	local array
720
721	shift
722	vrf_name=v$if_name
723	array=("${@}")
724
725	__simple_if_fini $if_name "${array[@]}"
726	vrf_destroy $vrf_name
727}
728
729tunnel_create()
730{
731	local name=$1; shift
732	local type=$1; shift
733	local local=$1; shift
734	local remote=$1; shift
735
736	ip link add name $name type $type \
737	   local $local remote $remote "$@"
738	ip link set dev $name up
739}
740
741tunnel_destroy()
742{
743	local name=$1; shift
744
745	ip link del dev $name
746}
747
748vlan_create()
749{
750	local if_name=$1; shift
751	local vid=$1; shift
752	local vrf=$1; shift
753	local ips=("${@}")
754	local name=$if_name.$vid
755
756	ip link add name $name link $if_name type vlan id $vid
757	if [ "$vrf" != "" ]; then
758		ip link set dev $name master $vrf
759	fi
760	ip link set dev $name up
761	__addr_add_del $name add "${ips[@]}"
762}
763
764vlan_destroy()
765{
766	local if_name=$1; shift
767	local vid=$1; shift
768	local name=$if_name.$vid
769
770	ip link del dev $name
771}
772
773team_create()
774{
775	local if_name=$1; shift
776	local mode=$1; shift
777
778	require_command $TEAMD
779	$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
780	for slave in "$@"; do
781		ip link set dev $slave down
782		ip link set dev $slave master $if_name
783		ip link set dev $slave up
784	done
785	ip link set dev $if_name up
786}
787
788team_destroy()
789{
790	local if_name=$1; shift
791
792	$TEAMD -t $if_name -k
793}
794
795master_name_get()
796{
797	local if_name=$1
798
799	ip -j link show dev $if_name | jq -r '.[]["master"]'
800}
801
802link_stats_get()
803{
804	local if_name=$1; shift
805	local dir=$1; shift
806	local stat=$1; shift
807
808	ip -j -s link show dev $if_name \
809		| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
810}
811
812link_stats_tx_packets_get()
813{
814	link_stats_get $1 tx packets
815}
816
817link_stats_rx_errors_get()
818{
819	link_stats_get $1 rx errors
820}
821
822tc_rule_stats_get()
823{
824	local dev=$1; shift
825	local pref=$1; shift
826	local dir=$1; shift
827	local selector=${1:-.packets}; shift
828
829	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
830	    | jq ".[1].options.actions[].stats$selector"
831}
832
833tc_rule_handle_stats_get()
834{
835	local id=$1; shift
836	local handle=$1; shift
837	local selector=${1:-.packets}; shift
838	local netns=${1:-""}; shift
839
840	tc $netns -j -s filter show $id \
841	    | jq ".[] | select(.options.handle == $handle) | \
842		  .options.actions[0].stats$selector"
843}
844
845ethtool_stats_get()
846{
847	local dev=$1; shift
848	local stat=$1; shift
849
850	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
851}
852
853ethtool_std_stats_get()
854{
855	local dev=$1; shift
856	local grp=$1; shift
857	local name=$1; shift
858	local src=$1; shift
859
860	ethtool --json -S $dev --groups $grp -- --src $src | \
861		jq '.[]."'"$grp"'"."'$name'"'
862}
863
864qdisc_stats_get()
865{
866	local dev=$1; shift
867	local handle=$1; shift
868	local selector=$1; shift
869
870	tc -j -s qdisc show dev "$dev" \
871	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
872}
873
874qdisc_parent_stats_get()
875{
876	local dev=$1; shift
877	local parent=$1; shift
878	local selector=$1; shift
879
880	tc -j -s qdisc show dev "$dev" invisible \
881	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
882}
883
884ipv6_stats_get()
885{
886	local dev=$1; shift
887	local stat=$1; shift
888
889	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
890}
891
892hw_stats_get()
893{
894	local suite=$1; shift
895	local if_name=$1; shift
896	local dir=$1; shift
897	local stat=$1; shift
898
899	ip -j stats show dev $if_name group offload subgroup $suite |
900		jq ".[0].stats64.$dir.$stat"
901}
902
903__nh_stats_get()
904{
905	local key=$1; shift
906	local group_id=$1; shift
907	local member_id=$1; shift
908
909	ip -j -s -s nexthop show id $group_id |
910	    jq --argjson member_id "$member_id" --arg key "$key" \
911	       '.[].group_stats[] | select(.id == $member_id) | .[$key]'
912}
913
914nh_stats_get()
915{
916	local group_id=$1; shift
917	local member_id=$1; shift
918
919	__nh_stats_get packets "$group_id" "$member_id"
920}
921
922nh_stats_get_hw()
923{
924	local group_id=$1; shift
925	local member_id=$1; shift
926
927	__nh_stats_get packets_hw "$group_id" "$member_id"
928}
929
930humanize()
931{
932	local speed=$1; shift
933
934	for unit in bps Kbps Mbps Gbps; do
935		if (($(echo "$speed < 1024" | bc))); then
936			break
937		fi
938
939		speed=$(echo "scale=1; $speed / 1024" | bc)
940	done
941
942	echo "$speed${unit}"
943}
944
945rate()
946{
947	local t0=$1; shift
948	local t1=$1; shift
949	local interval=$1; shift
950
951	echo $((8 * (t1 - t0) / interval))
952}
953
954packets_rate()
955{
956	local t0=$1; shift
957	local t1=$1; shift
958	local interval=$1; shift
959
960	echo $(((t1 - t0) / interval))
961}
962
963mac_get()
964{
965	local if_name=$1
966
967	ip -j link show dev $if_name | jq -r '.[]["address"]'
968}
969
970ipv6_lladdr_get()
971{
972	local if_name=$1
973
974	ip -j addr show dev $if_name | \
975		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
976		head -1
977}
978
979bridge_ageing_time_get()
980{
981	local bridge=$1
982	local ageing_time
983
984	# Need to divide by 100 to convert to seconds.
985	ageing_time=$(ip -j -d link show dev $bridge \
986		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
987	echo $((ageing_time / 100))
988}
989
990declare -A SYSCTL_ORIG
991sysctl_set()
992{
993	local key=$1; shift
994	local value=$1; shift
995
996	SYSCTL_ORIG[$key]=$(sysctl -n $key)
997	sysctl -qw $key="$value"
998}
999
1000sysctl_restore()
1001{
1002	local key=$1; shift
1003
1004	sysctl -qw $key="${SYSCTL_ORIG[$key]}"
1005}
1006
1007forwarding_enable()
1008{
1009	sysctl_set net.ipv4.conf.all.forwarding 1
1010	sysctl_set net.ipv6.conf.all.forwarding 1
1011}
1012
1013forwarding_restore()
1014{
1015	sysctl_restore net.ipv6.conf.all.forwarding
1016	sysctl_restore net.ipv4.conf.all.forwarding
1017}
1018
1019declare -A MTU_ORIG
1020mtu_set()
1021{
1022	local dev=$1; shift
1023	local mtu=$1; shift
1024
1025	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
1026	ip link set dev $dev mtu $mtu
1027}
1028
1029mtu_restore()
1030{
1031	local dev=$1; shift
1032
1033	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
1034}
1035
1036tc_offload_check()
1037{
1038	local num_netifs=${1:-$NUM_NETIFS}
1039
1040	for ((i = 1; i <= num_netifs; ++i)); do
1041		ethtool -k ${NETIFS[p$i]} \
1042			| grep "hw-tc-offload: on" &> /dev/null
1043		if [[ $? -ne 0 ]]; then
1044			return 1
1045		fi
1046	done
1047
1048	return 0
1049}
1050
1051trap_install()
1052{
1053	local dev=$1; shift
1054	local direction=$1; shift
1055
1056	# Some devices may not support or need in-hardware trapping of traffic
1057	# (e.g. the veth pairs that this library creates for non-existent
1058	# loopbacks). Use continue instead, so that there is a filter in there
1059	# (some tests check counters), and so that other filters are still
1060	# processed.
1061	tc filter add dev $dev $direction pref 1 \
1062		flower skip_sw action trap 2>/dev/null \
1063	    || tc filter add dev $dev $direction pref 1 \
1064		       flower action continue
1065}
1066
1067trap_uninstall()
1068{
1069	local dev=$1; shift
1070	local direction=$1; shift
1071
1072	tc filter del dev $dev $direction pref 1 flower
1073}
1074
1075slow_path_trap_install()
1076{
1077	# For slow-path testing, we need to install a trap to get to
1078	# slow path the packets that would otherwise be switched in HW.
1079	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1080		trap_install "$@"
1081	fi
1082}
1083
1084slow_path_trap_uninstall()
1085{
1086	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1087		trap_uninstall "$@"
1088	fi
1089}
1090
1091__icmp_capture_add_del()
1092{
1093	local add_del=$1; shift
1094	local pref=$1; shift
1095	local vsuf=$1; shift
1096	local tundev=$1; shift
1097	local filter=$1; shift
1098
1099	tc filter $add_del dev "$tundev" ingress \
1100	   proto ip$vsuf pref $pref \
1101	   flower ip_proto icmp$vsuf $filter \
1102	   action pass
1103}
1104
1105icmp_capture_install()
1106{
1107	__icmp_capture_add_del add 100 "" "$@"
1108}
1109
1110icmp_capture_uninstall()
1111{
1112	__icmp_capture_add_del del 100 "" "$@"
1113}
1114
1115icmp6_capture_install()
1116{
1117	__icmp_capture_add_del add 100 v6 "$@"
1118}
1119
1120icmp6_capture_uninstall()
1121{
1122	__icmp_capture_add_del del 100 v6 "$@"
1123}
1124
1125__vlan_capture_add_del()
1126{
1127	local add_del=$1; shift
1128	local pref=$1; shift
1129	local dev=$1; shift
1130	local filter=$1; shift
1131
1132	tc filter $add_del dev "$dev" ingress \
1133	   proto 802.1q pref $pref \
1134	   flower $filter \
1135	   action pass
1136}
1137
1138vlan_capture_install()
1139{
1140	__vlan_capture_add_del add 100 "$@"
1141}
1142
1143vlan_capture_uninstall()
1144{
1145	__vlan_capture_add_del del 100 "$@"
1146}
1147
1148__dscp_capture_add_del()
1149{
1150	local add_del=$1; shift
1151	local dev=$1; shift
1152	local base=$1; shift
1153	local dscp;
1154
1155	for prio in {0..7}; do
1156		dscp=$((base + prio))
1157		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1158				       "skip_hw ip_tos $((dscp << 2))"
1159	done
1160}
1161
1162dscp_capture_install()
1163{
1164	local dev=$1; shift
1165	local base=$1; shift
1166
1167	__dscp_capture_add_del add $dev $base
1168}
1169
1170dscp_capture_uninstall()
1171{
1172	local dev=$1; shift
1173	local base=$1; shift
1174
1175	__dscp_capture_add_del del $dev $base
1176}
1177
1178dscp_fetch_stats()
1179{
1180	local dev=$1; shift
1181	local base=$1; shift
1182
1183	for prio in {0..7}; do
1184		local dscp=$((base + prio))
1185		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1186		echo "[$dscp]=$t "
1187	done
1188}
1189
1190matchall_sink_create()
1191{
1192	local dev=$1; shift
1193
1194	tc qdisc add dev $dev clsact
1195	tc filter add dev $dev ingress \
1196	   pref 10000 \
1197	   matchall \
1198	   action drop
1199}
1200
1201tests_run()
1202{
1203	local current_test
1204
1205	for current_test in ${TESTS:-$ALL_TESTS}; do
1206		$current_test
1207	done
1208}
1209
1210multipath_eval()
1211{
1212	local desc="$1"
1213	local weight_rp12=$2
1214	local weight_rp13=$3
1215	local packets_rp12=$4
1216	local packets_rp13=$5
1217	local weights_ratio packets_ratio diff
1218
1219	RET=0
1220
1221	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1222		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1223				| bc -l)
1224	else
1225		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1226				| bc -l)
1227	fi
1228
1229	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1230	       check_err 1 "Packet difference is 0"
1231	       log_test "Multipath"
1232	       log_info "Expected ratio $weights_ratio"
1233	       return
1234	fi
1235
1236	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1237		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1238				| bc -l)
1239	else
1240		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1241				| bc -l)
1242	fi
1243
1244	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1245	diff=${diff#-}
1246
1247	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1248	check_err $? "Too large discrepancy between expected and measured ratios"
1249	log_test "$desc"
1250	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1251}
1252
1253in_ns()
1254{
1255	local name=$1; shift
1256
1257	ip netns exec $name bash <<-EOF
1258		NUM_NETIFS=0
1259		source lib.sh
1260		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1261	EOF
1262}
1263
1264##############################################################################
1265# Tests
1266
1267ping_do()
1268{
1269	local if_name=$1
1270	local dip=$2
1271	local args=$3
1272	local vrf_name
1273
1274	vrf_name=$(master_name_get $if_name)
1275	ip vrf exec $vrf_name \
1276		$PING $args $dip -c $PING_COUNT -i 0.1 \
1277		-w $PING_TIMEOUT &> /dev/null
1278}
1279
1280ping_test()
1281{
1282	RET=0
1283
1284	ping_do $1 $2
1285	check_err $?
1286	log_test "ping$3"
1287}
1288
1289ping_test_fails()
1290{
1291	RET=0
1292
1293	ping_do $1 $2
1294	check_fail $?
1295	log_test "ping fails$3"
1296}
1297
1298ping6_do()
1299{
1300	local if_name=$1
1301	local dip=$2
1302	local args=$3
1303	local vrf_name
1304
1305	vrf_name=$(master_name_get $if_name)
1306	ip vrf exec $vrf_name \
1307		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1308		-w $PING_TIMEOUT &> /dev/null
1309}
1310
1311ping6_test()
1312{
1313	RET=0
1314
1315	ping6_do $1 $2
1316	check_err $?
1317	log_test "ping6$3"
1318}
1319
1320ping6_test_fails()
1321{
1322	RET=0
1323
1324	ping6_do $1 $2
1325	check_fail $?
1326	log_test "ping6 fails$3"
1327}
1328
1329learning_test()
1330{
1331	local bridge=$1
1332	local br_port1=$2	# Connected to `host1_if`.
1333	local host1_if=$3
1334	local host2_if=$4
1335	local mac=de:ad:be:ef:13:37
1336	local ageing_time
1337
1338	RET=0
1339
1340	bridge -j fdb show br $bridge brport $br_port1 \
1341		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1342	check_fail $? "Found FDB record when should not"
1343
1344	# Disable unknown unicast flooding on `br_port1` to make sure
1345	# packets are only forwarded through the port after a matching
1346	# FDB entry was installed.
1347	bridge link set dev $br_port1 flood off
1348
1349	ip link set $host1_if promisc on
1350	tc qdisc add dev $host1_if ingress
1351	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1352		flower dst_mac $mac action drop
1353
1354	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1355	sleep 1
1356
1357	tc -j -s filter show dev $host1_if ingress \
1358		| jq -e ".[] | select(.options.handle == 101) \
1359		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1360	check_fail $? "Packet reached first host when should not"
1361
1362	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1363	sleep 1
1364
1365	bridge -j fdb show br $bridge brport $br_port1 \
1366		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1367	check_err $? "Did not find FDB record when should"
1368
1369	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1370	sleep 1
1371
1372	tc -j -s filter show dev $host1_if ingress \
1373		| jq -e ".[] | select(.options.handle == 101) \
1374		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1375	check_err $? "Packet did not reach second host when should"
1376
1377	# Wait for 10 seconds after the ageing time to make sure FDB
1378	# record was aged-out.
1379	ageing_time=$(bridge_ageing_time_get $bridge)
1380	sleep $((ageing_time + 10))
1381
1382	bridge -j fdb show br $bridge brport $br_port1 \
1383		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1384	check_fail $? "Found FDB record when should not"
1385
1386	bridge link set dev $br_port1 learning off
1387
1388	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1389	sleep 1
1390
1391	bridge -j fdb show br $bridge brport $br_port1 \
1392		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1393	check_fail $? "Found FDB record when should not"
1394
1395	bridge link set dev $br_port1 learning on
1396
1397	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1398	tc qdisc del dev $host1_if ingress
1399	ip link set $host1_if promisc off
1400
1401	bridge link set dev $br_port1 flood on
1402
1403	log_test "FDB learning"
1404}
1405
1406flood_test_do()
1407{
1408	local should_flood=$1
1409	local mac=$2
1410	local ip=$3
1411	local host1_if=$4
1412	local host2_if=$5
1413	local err=0
1414
1415	# Add an ACL on `host2_if` which will tell us whether the packet
1416	# was flooded to it or not.
1417	ip link set $host2_if promisc on
1418	tc qdisc add dev $host2_if ingress
1419	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1420		flower dst_mac $mac action drop
1421
1422	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1423	sleep 1
1424
1425	tc -j -s filter show dev $host2_if ingress \
1426		| jq -e ".[] | select(.options.handle == 101) \
1427		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1428	if [[ $? -ne 0 && $should_flood == "true" || \
1429	      $? -eq 0 && $should_flood == "false" ]]; then
1430		err=1
1431	fi
1432
1433	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1434	tc qdisc del dev $host2_if ingress
1435	ip link set $host2_if promisc off
1436
1437	return $err
1438}
1439
1440flood_unicast_test()
1441{
1442	local br_port=$1
1443	local host1_if=$2
1444	local host2_if=$3
1445	local mac=de:ad:be:ef:13:37
1446	local ip=192.0.2.100
1447
1448	RET=0
1449
1450	bridge link set dev $br_port flood off
1451
1452	flood_test_do false $mac $ip $host1_if $host2_if
1453	check_err $? "Packet flooded when should not"
1454
1455	bridge link set dev $br_port flood on
1456
1457	flood_test_do true $mac $ip $host1_if $host2_if
1458	check_err $? "Packet was not flooded when should"
1459
1460	log_test "Unknown unicast flood"
1461}
1462
1463flood_multicast_test()
1464{
1465	local br_port=$1
1466	local host1_if=$2
1467	local host2_if=$3
1468	local mac=01:00:5e:00:00:01
1469	local ip=239.0.0.1
1470
1471	RET=0
1472
1473	bridge link set dev $br_port mcast_flood off
1474
1475	flood_test_do false $mac $ip $host1_if $host2_if
1476	check_err $? "Packet flooded when should not"
1477
1478	bridge link set dev $br_port mcast_flood on
1479
1480	flood_test_do true $mac $ip $host1_if $host2_if
1481	check_err $? "Packet was not flooded when should"
1482
1483	log_test "Unregistered multicast flood"
1484}
1485
1486flood_test()
1487{
1488	# `br_port` is connected to `host2_if`
1489	local br_port=$1
1490	local host1_if=$2
1491	local host2_if=$3
1492
1493	flood_unicast_test $br_port $host1_if $host2_if
1494	flood_multicast_test $br_port $host1_if $host2_if
1495}
1496
1497__start_traffic()
1498{
1499	local pktsize=$1; shift
1500	local proto=$1; shift
1501	local h_in=$1; shift    # Where the traffic egresses the host
1502	local sip=$1; shift
1503	local dip=$1; shift
1504	local dmac=$1; shift
1505
1506	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1507		-a own -b $dmac -t "$proto" -q "$@" &
1508	sleep 1
1509}
1510
1511start_traffic_pktsize()
1512{
1513	local pktsize=$1; shift
1514
1515	__start_traffic $pktsize udp "$@"
1516}
1517
1518start_tcp_traffic_pktsize()
1519{
1520	local pktsize=$1; shift
1521
1522	__start_traffic $pktsize tcp "$@"
1523}
1524
1525start_traffic()
1526{
1527	start_traffic_pktsize 8000 "$@"
1528}
1529
1530start_tcp_traffic()
1531{
1532	start_tcp_traffic_pktsize 8000 "$@"
1533}
1534
1535stop_traffic()
1536{
1537	# Suppress noise from killing mausezahn.
1538	{ kill %% && wait %%; } 2>/dev/null
1539}
1540
1541declare -A cappid
1542declare -A capfile
1543declare -A capout
1544
1545tcpdump_start()
1546{
1547	local if_name=$1; shift
1548	local ns=$1; shift
1549
1550	capfile[$if_name]=$(mktemp)
1551	capout[$if_name]=$(mktemp)
1552
1553	if [ -z $ns ]; then
1554		ns_cmd=""
1555	else
1556		ns_cmd="ip netns exec ${ns}"
1557	fi
1558
1559	if [ -z $SUDO_USER ] ; then
1560		capuser=""
1561	else
1562		capuser="-Z $SUDO_USER"
1563	fi
1564
1565	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1566		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1567		> "${capout[$if_name]}" 2>&1 &
1568	cappid[$if_name]=$!
1569
1570	sleep 1
1571}
1572
1573tcpdump_stop()
1574{
1575	local if_name=$1
1576	local pid=${cappid[$if_name]}
1577
1578	$ns_cmd kill "$pid" && wait "$pid"
1579	sleep 1
1580}
1581
1582tcpdump_cleanup()
1583{
1584	local if_name=$1
1585
1586	rm ${capfile[$if_name]} ${capout[$if_name]}
1587}
1588
1589tcpdump_show()
1590{
1591	local if_name=$1
1592
1593	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1594}
1595
1596# return 0 if the packet wasn't seen on host2_if or 1 if it was
1597mcast_packet_test()
1598{
1599	local mac=$1
1600	local src_ip=$2
1601	local ip=$3
1602	local host1_if=$4
1603	local host2_if=$5
1604	local seen=0
1605	local tc_proto="ip"
1606	local mz_v6arg=""
1607
1608	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1609	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1610		tc_proto="ipv6"
1611		mz_v6arg="-6"
1612	fi
1613
1614	# Add an ACL on `host2_if` which will tell us whether the packet
1615	# was received by it or not.
1616	tc qdisc add dev $host2_if ingress
1617	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1618		flower ip_proto udp dst_mac $mac action drop
1619
1620	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1621	sleep 1
1622
1623	tc -j -s filter show dev $host2_if ingress \
1624		| jq -e ".[] | select(.options.handle == 101) \
1625		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1626	if [[ $? -eq 0 ]]; then
1627		seen=1
1628	fi
1629
1630	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1631	tc qdisc del dev $host2_if ingress
1632
1633	return $seen
1634}
1635
1636brmcast_check_sg_entries()
1637{
1638	local report=$1; shift
1639	local slist=("$@")
1640	local sarg=""
1641
1642	for src in "${slist[@]}"; do
1643		sarg="${sarg} and .source_list[].address == \"$src\""
1644	done
1645	bridge -j -d -s mdb show dev br0 \
1646		| jq -e ".[].mdb[] | \
1647			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1648	check_err $? "Wrong *,G entry source list after $report report"
1649
1650	for sgent in "${slist[@]}"; do
1651		bridge -j -d -s mdb show dev br0 \
1652			| jq -e ".[].mdb[] | \
1653				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1654		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1655	done
1656}
1657
1658brmcast_check_sg_fwding()
1659{
1660	local should_fwd=$1; shift
1661	local sources=("$@")
1662
1663	for src in "${sources[@]}"; do
1664		local retval=0
1665
1666		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1667		retval=$?
1668		if [ $should_fwd -eq 1 ]; then
1669			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1670		else
1671			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1672		fi
1673	done
1674}
1675
1676brmcast_check_sg_state()
1677{
1678	local is_blocked=$1; shift
1679	local sources=("$@")
1680	local should_fail=1
1681
1682	if [ $is_blocked -eq 1 ]; then
1683		should_fail=0
1684	fi
1685
1686	for src in "${sources[@]}"; do
1687		bridge -j -d -s mdb show dev br0 \
1688			| jq -e ".[].mdb[] | \
1689				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1690				 .source_list[] |
1691				 select(.address == \"$src\") |
1692				 select(.timer == \"0.00\")" &>/dev/null
1693		check_err_fail $should_fail $? "Entry $src has zero timer"
1694
1695		bridge -j -d -s mdb show dev br0 \
1696			| jq -e ".[].mdb[] | \
1697				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1698				 .flags[] == \"blocked\")" &>/dev/null
1699		check_err_fail $should_fail $? "Entry $src has blocked flag"
1700	done
1701}
1702
1703mc_join()
1704{
1705	local if_name=$1
1706	local group=$2
1707	local vrf_name=$(master_name_get $if_name)
1708
1709	# We don't care about actual reception, just about joining the
1710	# IP multicast group and adding the L2 address to the device's
1711	# MAC filtering table
1712	ip vrf exec $vrf_name \
1713		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1714	mreceive_pid=$!
1715
1716	sleep 1
1717}
1718
1719mc_leave()
1720{
1721	kill "$mreceive_pid" && wait "$mreceive_pid"
1722}
1723
1724mc_send()
1725{
1726	local if_name=$1
1727	local groups=$2
1728	local vrf_name=$(master_name_get $if_name)
1729
1730	ip vrf exec $vrf_name \
1731		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1732}
1733
1734start_ip_monitor()
1735{
1736	local mtype=$1; shift
1737	local ip=${1-ip}; shift
1738
1739	# start the monitor in the background
1740	tmpfile=`mktemp /var/run/nexthoptestXXX`
1741	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1742	sleep 0.2
1743	echo "$mpid $tmpfile"
1744}
1745
1746stop_ip_monitor()
1747{
1748	local mpid=$1; shift
1749	local tmpfile=$1; shift
1750	local el=$1; shift
1751	local what=$1; shift
1752
1753	sleep 0.2
1754	kill $mpid
1755	local lines=`grep '^\w' $tmpfile | wc -l`
1756	test $lines -eq $el
1757	check_err $? "$what: $lines lines of events, expected $el"
1758	rm -rf $tmpfile
1759}
1760
1761hw_stats_monitor_test()
1762{
1763	local dev=$1; shift
1764	local type=$1; shift
1765	local make_suitable=$1; shift
1766	local make_unsuitable=$1; shift
1767	local ip=${1-ip}; shift
1768
1769	RET=0
1770
1771	# Expect a notification about enablement.
1772	local ipmout=$(start_ip_monitor stats "$ip")
1773	$ip stats set dev $dev ${type}_stats on
1774	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1775
1776	# Expect a notification about offload.
1777	local ipmout=$(start_ip_monitor stats "$ip")
1778	$make_suitable
1779	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1780
1781	# Expect a notification about loss of offload.
1782	local ipmout=$(start_ip_monitor stats "$ip")
1783	$make_unsuitable
1784	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1785
1786	# Expect a notification about disablement
1787	local ipmout=$(start_ip_monitor stats "$ip")
1788	$ip stats set dev $dev ${type}_stats off
1789	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1790
1791	log_test "${type}_stats notifications"
1792}
1793
1794ipv4_to_bytes()
1795{
1796	local IP=$1; shift
1797
1798	printf '%02x:' ${IP//./ } |
1799	    sed 's/:$//'
1800}
1801
1802# Convert a given IPv6 address, `IP' such that the :: token, if present, is
1803# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1804# digits. An optional `BYTESEP' parameter can be given to further separate
1805# individual bytes of each 16-bit group.
1806expand_ipv6()
1807{
1808	local IP=$1; shift
1809	local bytesep=$1; shift
1810
1811	local cvt_ip=${IP/::/_}
1812	local colons=${cvt_ip//[^:]/}
1813	local allcol=:::::::
1814	# IP where :: -> the appropriate number of colons:
1815	local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1816
1817	echo $allcol_ip | tr : '\n' |
1818	    sed s/^/0000/ |
1819	    sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1820	    tr '\n' : |
1821	    sed 's/:$//'
1822}
1823
1824ipv6_to_bytes()
1825{
1826	local IP=$1; shift
1827
1828	expand_ipv6 "$IP" :
1829}
1830
1831u16_to_bytes()
1832{
1833	local u16=$1; shift
1834
1835	printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1836}
1837
1838# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1839# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1840# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1841# stands for 00:00.
1842payload_template_calc_checksum()
1843{
1844	local payload=$1; shift
1845
1846	(
1847	    # Set input radix.
1848	    echo "16i"
1849	    # Push zero for the initial checksum.
1850	    echo 0
1851
1852	    # Pad the payload with a terminating 00: in case we get an odd
1853	    # number of bytes.
1854	    echo "${payload%:}:00:" |
1855		sed 's/CHECKSUM/00:00/g' |
1856		tr '[:lower:]' '[:upper:]' |
1857		# Add the word to the checksum.
1858		sed 's/\(..\):\(..\):/\1\2+\n/g' |
1859		# Strip the extra odd byte we pushed if left unconverted.
1860		sed 's/\(..\):$//'
1861
1862	    echo "10000 ~ +"	# Calculate and add carry.
1863	    echo "FFFF r - p"	# Bit-flip and print.
1864	) |
1865	    dc |
1866	    tr '[:upper:]' '[:lower:]'
1867}
1868
1869payload_template_expand_checksum()
1870{
1871	local payload=$1; shift
1872	local checksum=$1; shift
1873
1874	local ckbytes=$(u16_to_bytes $checksum)
1875
1876	echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1877}
1878
1879payload_template_nbytes()
1880{
1881	local payload=$1; shift
1882
1883	payload_template_expand_checksum "${payload%:}" 0 |
1884		sed 's/:/\n/g' | wc -l
1885}
1886
1887igmpv3_is_in_get()
1888{
1889	local GRP=$1; shift
1890	local sources=("$@")
1891
1892	local igmpv3
1893	local nsources=$(u16_to_bytes ${#sources[@]})
1894
1895	# IS_IN ( $sources )
1896	igmpv3=$(:
1897		)"22:"$(			: Type - Membership Report
1898		)"00:"$(			: Reserved
1899		)"CHECKSUM:"$(			: Checksum
1900		)"00:00:"$(			: Reserved
1901		)"00:01:"$(			: Number of Group Records
1902		)"01:"$(			: Record Type - IS_IN
1903		)"00:"$(			: Aux Data Len
1904		)"${nsources}:"$(		: Number of Sources
1905		)"$(ipv4_to_bytes $GRP):"$(	: Multicast Address
1906		)"$(for src in "${sources[@]}"; do
1907			ipv4_to_bytes $src
1908			echo -n :
1909		    done)"$(			: Source Addresses
1910		)
1911	local checksum=$(payload_template_calc_checksum "$igmpv3")
1912
1913	payload_template_expand_checksum "$igmpv3" $checksum
1914}
1915
1916igmpv2_leave_get()
1917{
1918	local GRP=$1; shift
1919
1920	local payload=$(:
1921		)"17:"$(			: Type - Leave Group
1922		)"00:"$(			: Max Resp Time - not meaningful
1923		)"CHECKSUM:"$(			: Checksum
1924		)"$(ipv4_to_bytes $GRP)"$(	: Group Address
1925		)
1926	local checksum=$(payload_template_calc_checksum "$payload")
1927
1928	payload_template_expand_checksum "$payload" $checksum
1929}
1930
1931mldv2_is_in_get()
1932{
1933	local SIP=$1; shift
1934	local GRP=$1; shift
1935	local sources=("$@")
1936
1937	local hbh
1938	local icmpv6
1939	local nsources=$(u16_to_bytes ${#sources[@]})
1940
1941	hbh=$(:
1942		)"3a:"$(			: Next Header - ICMPv6
1943		)"00:"$(			: Hdr Ext Len
1944		)"00:00:00:00:00:00:"$(		: Options and Padding
1945		)
1946
1947	icmpv6=$(:
1948		)"8f:"$(			: Type - MLDv2 Report
1949		)"00:"$(			: Code
1950		)"CHECKSUM:"$(			: Checksum
1951		)"00:00:"$(			: Reserved
1952		)"00:01:"$(			: Number of Group Records
1953		)"01:"$(			: Record Type - IS_IN
1954		)"00:"$(			: Aux Data Len
1955		)"${nsources}:"$(		: Number of Sources
1956		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1957		)"$(for src in "${sources[@]}"; do
1958			ipv6_to_bytes $src
1959			echo -n :
1960		    done)"$(			: Source Addresses
1961		)
1962
1963	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1964	local sudohdr=$(:
1965		)"$(ipv6_to_bytes $SIP):"$(	: SIP
1966		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
1967	        )"${len}:"$(			: Upper-layer length
1968	        )"00:3a:"$(			: Zero and next-header
1969	        )
1970	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1971
1972	payload_template_expand_checksum "$hbh$icmpv6" $checksum
1973}
1974
1975mldv1_done_get()
1976{
1977	local SIP=$1; shift
1978	local GRP=$1; shift
1979
1980	local hbh
1981	local icmpv6
1982
1983	hbh=$(:
1984		)"3a:"$(			: Next Header - ICMPv6
1985		)"00:"$(			: Hdr Ext Len
1986		)"00:00:00:00:00:00:"$(		: Options and Padding
1987		)
1988
1989	icmpv6=$(:
1990		)"84:"$(			: Type - MLDv1 Done
1991		)"00:"$(			: Code
1992		)"CHECKSUM:"$(			: Checksum
1993		)"00:00:"$(			: Max Resp Delay - not meaningful
1994		)"00:00:"$(			: Reserved
1995		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1996		)
1997
1998	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1999	local sudohdr=$(:
2000		)"$(ipv6_to_bytes $SIP):"$(	: SIP
2001		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
2002	        )"${len}:"$(			: Upper-layer length
2003	        )"00:3a:"$(			: Zero and next-header
2004	        )
2005	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
2006
2007	payload_template_expand_checksum "$hbh$icmpv6" $checksum
2008}
2009
2010bail_on_lldpad()
2011{
2012	local reason1="$1"; shift
2013	local reason2="$1"; shift
2014
2015	if systemctl is-active --quiet lldpad; then
2016
2017		cat >/dev/stderr <<-EOF
2018		WARNING: lldpad is running
2019
2020			lldpad will likely $reason1, and this test will
2021			$reason2. Both are not supported at the same time,
2022			one of them is arbitrarily going to overwrite the
2023			other. That will cause spurious failures (or, unlikely,
2024			passes) of this test.
2025		EOF
2026
2027		if [[ -z $ALLOW_LLDPAD ]]; then
2028			cat >/dev/stderr <<-EOF
2029
2030				If you want to run the test anyway, please set
2031				an environment variable ALLOW_LLDPAD to a
2032				non-empty string.
2033			EOF
2034			exit 1
2035		else
2036			return
2037		fi
2038	fi
2039}
2040
2041absval()
2042{
2043	local v=$1; shift
2044
2045	echo $((v > 0 ? v : -v))
2046}
2047