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