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
1218ping6_do()
1219{
1220	local if_name=$1
1221	local dip=$2
1222	local args=$3
1223	local vrf_name
1224
1225	vrf_name=$(master_name_get $if_name)
1226	ip vrf exec $vrf_name \
1227		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1228		-w $PING_TIMEOUT &> /dev/null
1229}
1230
1231ping6_test()
1232{
1233	RET=0
1234
1235	ping6_do $1 $2
1236	check_err $?
1237	log_test "ping6$3"
1238}
1239
1240learning_test()
1241{
1242	local bridge=$1
1243	local br_port1=$2	# Connected to `host1_if`.
1244	local host1_if=$3
1245	local host2_if=$4
1246	local mac=de:ad:be:ef:13:37
1247	local ageing_time
1248
1249	RET=0
1250
1251	bridge -j fdb show br $bridge brport $br_port1 \
1252		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1253	check_fail $? "Found FDB record when should not"
1254
1255	# Disable unknown unicast flooding on `br_port1` to make sure
1256	# packets are only forwarded through the port after a matching
1257	# FDB entry was installed.
1258	bridge link set dev $br_port1 flood off
1259
1260	ip link set $host1_if promisc on
1261	tc qdisc add dev $host1_if ingress
1262	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1263		flower dst_mac $mac action drop
1264
1265	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1266	sleep 1
1267
1268	tc -j -s filter show dev $host1_if ingress \
1269		| jq -e ".[] | select(.options.handle == 101) \
1270		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1271	check_fail $? "Packet reached first host when should not"
1272
1273	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1274	sleep 1
1275
1276	bridge -j fdb show br $bridge brport $br_port1 \
1277		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1278	check_err $? "Did not find FDB record when should"
1279
1280	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1281	sleep 1
1282
1283	tc -j -s filter show dev $host1_if ingress \
1284		| jq -e ".[] | select(.options.handle == 101) \
1285		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1286	check_err $? "Packet did not reach second host when should"
1287
1288	# Wait for 10 seconds after the ageing time to make sure FDB
1289	# record was aged-out.
1290	ageing_time=$(bridge_ageing_time_get $bridge)
1291	sleep $((ageing_time + 10))
1292
1293	bridge -j fdb show br $bridge brport $br_port1 \
1294		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1295	check_fail $? "Found FDB record when should not"
1296
1297	bridge link set dev $br_port1 learning off
1298
1299	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1300	sleep 1
1301
1302	bridge -j fdb show br $bridge brport $br_port1 \
1303		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1304	check_fail $? "Found FDB record when should not"
1305
1306	bridge link set dev $br_port1 learning on
1307
1308	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1309	tc qdisc del dev $host1_if ingress
1310	ip link set $host1_if promisc off
1311
1312	bridge link set dev $br_port1 flood on
1313
1314	log_test "FDB learning"
1315}
1316
1317flood_test_do()
1318{
1319	local should_flood=$1
1320	local mac=$2
1321	local ip=$3
1322	local host1_if=$4
1323	local host2_if=$5
1324	local err=0
1325
1326	# Add an ACL on `host2_if` which will tell us whether the packet
1327	# was flooded to it or not.
1328	ip link set $host2_if promisc on
1329	tc qdisc add dev $host2_if ingress
1330	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1331		flower dst_mac $mac action drop
1332
1333	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1334	sleep 1
1335
1336	tc -j -s filter show dev $host2_if ingress \
1337		| jq -e ".[] | select(.options.handle == 101) \
1338		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1339	if [[ $? -ne 0 && $should_flood == "true" || \
1340	      $? -eq 0 && $should_flood == "false" ]]; then
1341		err=1
1342	fi
1343
1344	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1345	tc qdisc del dev $host2_if ingress
1346	ip link set $host2_if promisc off
1347
1348	return $err
1349}
1350
1351flood_unicast_test()
1352{
1353	local br_port=$1
1354	local host1_if=$2
1355	local host2_if=$3
1356	local mac=de:ad:be:ef:13:37
1357	local ip=192.0.2.100
1358
1359	RET=0
1360
1361	bridge link set dev $br_port flood off
1362
1363	flood_test_do false $mac $ip $host1_if $host2_if
1364	check_err $? "Packet flooded when should not"
1365
1366	bridge link set dev $br_port flood on
1367
1368	flood_test_do true $mac $ip $host1_if $host2_if
1369	check_err $? "Packet was not flooded when should"
1370
1371	log_test "Unknown unicast flood"
1372}
1373
1374flood_multicast_test()
1375{
1376	local br_port=$1
1377	local host1_if=$2
1378	local host2_if=$3
1379	local mac=01:00:5e:00:00:01
1380	local ip=239.0.0.1
1381
1382	RET=0
1383
1384	bridge link set dev $br_port mcast_flood off
1385
1386	flood_test_do false $mac $ip $host1_if $host2_if
1387	check_err $? "Packet flooded when should not"
1388
1389	bridge link set dev $br_port mcast_flood on
1390
1391	flood_test_do true $mac $ip $host1_if $host2_if
1392	check_err $? "Packet was not flooded when should"
1393
1394	log_test "Unregistered multicast flood"
1395}
1396
1397flood_test()
1398{
1399	# `br_port` is connected to `host2_if`
1400	local br_port=$1
1401	local host1_if=$2
1402	local host2_if=$3
1403
1404	flood_unicast_test $br_port $host1_if $host2_if
1405	flood_multicast_test $br_port $host1_if $host2_if
1406}
1407
1408__start_traffic()
1409{
1410	local pktsize=$1; shift
1411	local proto=$1; shift
1412	local h_in=$1; shift    # Where the traffic egresses the host
1413	local sip=$1; shift
1414	local dip=$1; shift
1415	local dmac=$1; shift
1416
1417	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1418		-a own -b $dmac -t "$proto" -q "$@" &
1419	sleep 1
1420}
1421
1422start_traffic_pktsize()
1423{
1424	local pktsize=$1; shift
1425
1426	__start_traffic $pktsize udp "$@"
1427}
1428
1429start_tcp_traffic_pktsize()
1430{
1431	local pktsize=$1; shift
1432
1433	__start_traffic $pktsize tcp "$@"
1434}
1435
1436start_traffic()
1437{
1438	start_traffic_pktsize 8000 "$@"
1439}
1440
1441start_tcp_traffic()
1442{
1443	start_tcp_traffic_pktsize 8000 "$@"
1444}
1445
1446stop_traffic()
1447{
1448	# Suppress noise from killing mausezahn.
1449	{ kill %% && wait %%; } 2>/dev/null
1450}
1451
1452declare -A cappid
1453declare -A capfile
1454declare -A capout
1455
1456tcpdump_start()
1457{
1458	local if_name=$1; shift
1459	local ns=$1; shift
1460
1461	capfile[$if_name]=$(mktemp)
1462	capout[$if_name]=$(mktemp)
1463
1464	if [ -z $ns ]; then
1465		ns_cmd=""
1466	else
1467		ns_cmd="ip netns exec ${ns}"
1468	fi
1469
1470	if [ -z $SUDO_USER ] ; then
1471		capuser=""
1472	else
1473		capuser="-Z $SUDO_USER"
1474	fi
1475
1476	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1477		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1478		> "${capout[$if_name]}" 2>&1 &
1479	cappid[$if_name]=$!
1480
1481	sleep 1
1482}
1483
1484tcpdump_stop()
1485{
1486	local if_name=$1
1487	local pid=${cappid[$if_name]}
1488
1489	$ns_cmd kill "$pid" && wait "$pid"
1490	sleep 1
1491}
1492
1493tcpdump_cleanup()
1494{
1495	local if_name=$1
1496
1497	rm ${capfile[$if_name]} ${capout[$if_name]}
1498}
1499
1500tcpdump_show()
1501{
1502	local if_name=$1
1503
1504	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1505}
1506
1507# return 0 if the packet wasn't seen on host2_if or 1 if it was
1508mcast_packet_test()
1509{
1510	local mac=$1
1511	local src_ip=$2
1512	local ip=$3
1513	local host1_if=$4
1514	local host2_if=$5
1515	local seen=0
1516	local tc_proto="ip"
1517	local mz_v6arg=""
1518
1519	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1520	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1521		tc_proto="ipv6"
1522		mz_v6arg="-6"
1523	fi
1524
1525	# Add an ACL on `host2_if` which will tell us whether the packet
1526	# was received by it or not.
1527	tc qdisc add dev $host2_if ingress
1528	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1529		flower ip_proto udp dst_mac $mac action drop
1530
1531	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1532	sleep 1
1533
1534	tc -j -s filter show dev $host2_if ingress \
1535		| jq -e ".[] | select(.options.handle == 101) \
1536		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1537	if [[ $? -eq 0 ]]; then
1538		seen=1
1539	fi
1540
1541	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1542	tc qdisc del dev $host2_if ingress
1543
1544	return $seen
1545}
1546
1547brmcast_check_sg_entries()
1548{
1549	local report=$1; shift
1550	local slist=("$@")
1551	local sarg=""
1552
1553	for src in "${slist[@]}"; do
1554		sarg="${sarg} and .source_list[].address == \"$src\""
1555	done
1556	bridge -j -d -s mdb show dev br0 \
1557		| jq -e ".[].mdb[] | \
1558			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1559	check_err $? "Wrong *,G entry source list after $report report"
1560
1561	for sgent in "${slist[@]}"; do
1562		bridge -j -d -s mdb show dev br0 \
1563			| jq -e ".[].mdb[] | \
1564				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1565		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1566	done
1567}
1568
1569brmcast_check_sg_fwding()
1570{
1571	local should_fwd=$1; shift
1572	local sources=("$@")
1573
1574	for src in "${sources[@]}"; do
1575		local retval=0
1576
1577		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1578		retval=$?
1579		if [ $should_fwd -eq 1 ]; then
1580			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1581		else
1582			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1583		fi
1584	done
1585}
1586
1587brmcast_check_sg_state()
1588{
1589	local is_blocked=$1; shift
1590	local sources=("$@")
1591	local should_fail=1
1592
1593	if [ $is_blocked -eq 1 ]; then
1594		should_fail=0
1595	fi
1596
1597	for src in "${sources[@]}"; do
1598		bridge -j -d -s mdb show dev br0 \
1599			| jq -e ".[].mdb[] | \
1600				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1601				 .source_list[] |
1602				 select(.address == \"$src\") |
1603				 select(.timer == \"0.00\")" &>/dev/null
1604		check_err_fail $should_fail $? "Entry $src has zero timer"
1605
1606		bridge -j -d -s mdb show dev br0 \
1607			| jq -e ".[].mdb[] | \
1608				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1609				 .flags[] == \"blocked\")" &>/dev/null
1610		check_err_fail $should_fail $? "Entry $src has blocked flag"
1611	done
1612}
1613
1614mc_join()
1615{
1616	local if_name=$1
1617	local group=$2
1618	local vrf_name=$(master_name_get $if_name)
1619
1620	# We don't care about actual reception, just about joining the
1621	# IP multicast group and adding the L2 address to the device's
1622	# MAC filtering table
1623	ip vrf exec $vrf_name \
1624		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1625	mreceive_pid=$!
1626
1627	sleep 1
1628}
1629
1630mc_leave()
1631{
1632	kill "$mreceive_pid" && wait "$mreceive_pid"
1633}
1634
1635mc_send()
1636{
1637	local if_name=$1
1638	local groups=$2
1639	local vrf_name=$(master_name_get $if_name)
1640
1641	ip vrf exec $vrf_name \
1642		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1643}
1644
1645start_ip_monitor()
1646{
1647	local mtype=$1; shift
1648	local ip=${1-ip}; shift
1649
1650	# start the monitor in the background
1651	tmpfile=`mktemp /var/run/nexthoptestXXX`
1652	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1653	sleep 0.2
1654	echo "$mpid $tmpfile"
1655}
1656
1657stop_ip_monitor()
1658{
1659	local mpid=$1; shift
1660	local tmpfile=$1; shift
1661	local el=$1; shift
1662	local what=$1; shift
1663
1664	sleep 0.2
1665	kill $mpid
1666	local lines=`grep '^\w' $tmpfile | wc -l`
1667	test $lines -eq $el
1668	check_err $? "$what: $lines lines of events, expected $el"
1669	rm -rf $tmpfile
1670}
1671
1672hw_stats_monitor_test()
1673{
1674	local dev=$1; shift
1675	local type=$1; shift
1676	local make_suitable=$1; shift
1677	local make_unsuitable=$1; shift
1678	local ip=${1-ip}; shift
1679
1680	RET=0
1681
1682	# Expect a notification about enablement.
1683	local ipmout=$(start_ip_monitor stats "$ip")
1684	$ip stats set dev $dev ${type}_stats on
1685	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1686
1687	# Expect a notification about offload.
1688	local ipmout=$(start_ip_monitor stats "$ip")
1689	$make_suitable
1690	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1691
1692	# Expect a notification about loss of offload.
1693	local ipmout=$(start_ip_monitor stats "$ip")
1694	$make_unsuitable
1695	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1696
1697	# Expect a notification about disablement
1698	local ipmout=$(start_ip_monitor stats "$ip")
1699	$ip stats set dev $dev ${type}_stats off
1700	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1701
1702	log_test "${type}_stats notifications"
1703}
1704
1705ipv4_to_bytes()
1706{
1707	local IP=$1; shift
1708
1709	printf '%02x:' ${IP//./ } |
1710	    sed 's/:$//'
1711}
1712
1713# Convert a given IPv6 address, `IP' such that the :: token, if present, is
1714# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1715# digits. An optional `BYTESEP' parameter can be given to further separate
1716# individual bytes of each 16-bit group.
1717expand_ipv6()
1718{
1719	local IP=$1; shift
1720	local bytesep=$1; shift
1721
1722	local cvt_ip=${IP/::/_}
1723	local colons=${cvt_ip//[^:]/}
1724	local allcol=:::::::
1725	# IP where :: -> the appropriate number of colons:
1726	local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1727
1728	echo $allcol_ip | tr : '\n' |
1729	    sed s/^/0000/ |
1730	    sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1731	    tr '\n' : |
1732	    sed 's/:$//'
1733}
1734
1735ipv6_to_bytes()
1736{
1737	local IP=$1; shift
1738
1739	expand_ipv6 "$IP" :
1740}
1741
1742u16_to_bytes()
1743{
1744	local u16=$1; shift
1745
1746	printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1747}
1748
1749# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1750# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1751# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1752# stands for 00:00.
1753payload_template_calc_checksum()
1754{
1755	local payload=$1; shift
1756
1757	(
1758	    # Set input radix.
1759	    echo "16i"
1760	    # Push zero for the initial checksum.
1761	    echo 0
1762
1763	    # Pad the payload with a terminating 00: in case we get an odd
1764	    # number of bytes.
1765	    echo "${payload%:}:00:" |
1766		sed 's/CHECKSUM/00:00/g' |
1767		tr '[:lower:]' '[:upper:]' |
1768		# Add the word to the checksum.
1769		sed 's/\(..\):\(..\):/\1\2+\n/g' |
1770		# Strip the extra odd byte we pushed if left unconverted.
1771		sed 's/\(..\):$//'
1772
1773	    echo "10000 ~ +"	# Calculate and add carry.
1774	    echo "FFFF r - p"	# Bit-flip and print.
1775	) |
1776	    dc |
1777	    tr '[:upper:]' '[:lower:]'
1778}
1779
1780payload_template_expand_checksum()
1781{
1782	local payload=$1; shift
1783	local checksum=$1; shift
1784
1785	local ckbytes=$(u16_to_bytes $checksum)
1786
1787	echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1788}
1789
1790payload_template_nbytes()
1791{
1792	local payload=$1; shift
1793
1794	payload_template_expand_checksum "${payload%:}" 0 |
1795		sed 's/:/\n/g' | wc -l
1796}
1797
1798igmpv3_is_in_get()
1799{
1800	local GRP=$1; shift
1801	local sources=("$@")
1802
1803	local igmpv3
1804	local nsources=$(u16_to_bytes ${#sources[@]})
1805
1806	# IS_IN ( $sources )
1807	igmpv3=$(:
1808		)"22:"$(			: Type - Membership Report
1809		)"00:"$(			: Reserved
1810		)"CHECKSUM:"$(			: Checksum
1811		)"00:00:"$(			: Reserved
1812		)"00:01:"$(			: Number of Group Records
1813		)"01:"$(			: Record Type - IS_IN
1814		)"00:"$(			: Aux Data Len
1815		)"${nsources}:"$(		: Number of Sources
1816		)"$(ipv4_to_bytes $GRP):"$(	: Multicast Address
1817		)"$(for src in "${sources[@]}"; do
1818			ipv4_to_bytes $src
1819			echo -n :
1820		    done)"$(			: Source Addresses
1821		)
1822	local checksum=$(payload_template_calc_checksum "$igmpv3")
1823
1824	payload_template_expand_checksum "$igmpv3" $checksum
1825}
1826
1827igmpv2_leave_get()
1828{
1829	local GRP=$1; shift
1830
1831	local payload=$(:
1832		)"17:"$(			: Type - Leave Group
1833		)"00:"$(			: Max Resp Time - not meaningful
1834		)"CHECKSUM:"$(			: Checksum
1835		)"$(ipv4_to_bytes $GRP)"$(	: Group Address
1836		)
1837	local checksum=$(payload_template_calc_checksum "$payload")
1838
1839	payload_template_expand_checksum "$payload" $checksum
1840}
1841
1842mldv2_is_in_get()
1843{
1844	local SIP=$1; shift
1845	local GRP=$1; shift
1846	local sources=("$@")
1847
1848	local hbh
1849	local icmpv6
1850	local nsources=$(u16_to_bytes ${#sources[@]})
1851
1852	hbh=$(:
1853		)"3a:"$(			: Next Header - ICMPv6
1854		)"00:"$(			: Hdr Ext Len
1855		)"00:00:00:00:00:00:"$(		: Options and Padding
1856		)
1857
1858	icmpv6=$(:
1859		)"8f:"$(			: Type - MLDv2 Report
1860		)"00:"$(			: Code
1861		)"CHECKSUM:"$(			: Checksum
1862		)"00:00:"$(			: Reserved
1863		)"00:01:"$(			: Number of Group Records
1864		)"01:"$(			: Record Type - IS_IN
1865		)"00:"$(			: Aux Data Len
1866		)"${nsources}:"$(		: Number of Sources
1867		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1868		)"$(for src in "${sources[@]}"; do
1869			ipv6_to_bytes $src
1870			echo -n :
1871		    done)"$(			: Source Addresses
1872		)
1873
1874	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1875	local sudohdr=$(:
1876		)"$(ipv6_to_bytes $SIP):"$(	: SIP
1877		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
1878	        )"${len}:"$(			: Upper-layer length
1879	        )"00:3a:"$(			: Zero and next-header
1880	        )
1881	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1882
1883	payload_template_expand_checksum "$hbh$icmpv6" $checksum
1884}
1885
1886mldv1_done_get()
1887{
1888	local SIP=$1; shift
1889	local GRP=$1; shift
1890
1891	local hbh
1892	local icmpv6
1893
1894	hbh=$(:
1895		)"3a:"$(			: Next Header - ICMPv6
1896		)"00:"$(			: Hdr Ext Len
1897		)"00:00:00:00:00:00:"$(		: Options and Padding
1898		)
1899
1900	icmpv6=$(:
1901		)"84:"$(			: Type - MLDv1 Done
1902		)"00:"$(			: Code
1903		)"CHECKSUM:"$(			: Checksum
1904		)"00:00:"$(			: Max Resp Delay - not meaningful
1905		)"00:00:"$(			: Reserved
1906		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1907		)
1908
1909	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1910	local sudohdr=$(:
1911		)"$(ipv6_to_bytes $SIP):"$(	: SIP
1912		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
1913	        )"${len}:"$(			: Upper-layer length
1914	        )"00:3a:"$(			: Zero and next-header
1915	        )
1916	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1917
1918	payload_template_expand_checksum "$hbh$icmpv6" $checksum
1919}
1920
1921bail_on_lldpad()
1922{
1923	local reason1="$1"; shift
1924	local reason2="$1"; shift
1925
1926	if systemctl is-active --quiet lldpad; then
1927
1928		cat >/dev/stderr <<-EOF
1929		WARNING: lldpad is running
1930
1931			lldpad will likely $reason1, and this test will
1932			$reason2. Both are not supported at the same time,
1933			one of them is arbitrarily going to overwrite the
1934			other. That will cause spurious failures (or, unlikely,
1935			passes) of this test.
1936		EOF
1937
1938		if [[ -z $ALLOW_LLDPAD ]]; then
1939			cat >/dev/stderr <<-EOF
1940
1941				If you want to run the test anyway, please set
1942				an environment variable ALLOW_LLDPAD to a
1943				non-empty string.
1944			EOF
1945			exit 1
1946		else
1947			return
1948		fi
1949	fi
1950}
1951