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