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