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
795	tc -j -s filter show $id \
796	    | jq ".[] | select(.options.handle == $handle) | \
797		  .options.actions[0].stats$selector"
798}
799
800ethtool_stats_get()
801{
802	local dev=$1; shift
803	local stat=$1; shift
804
805	ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
806}
807
808ethtool_std_stats_get()
809{
810	local dev=$1; shift
811	local grp=$1; shift
812	local name=$1; shift
813	local src=$1; shift
814
815	ethtool --json -S $dev --groups $grp -- --src $src | \
816		jq '.[]."'"$grp"'"."'$name'"'
817}
818
819qdisc_stats_get()
820{
821	local dev=$1; shift
822	local handle=$1; shift
823	local selector=$1; shift
824
825	tc -j -s qdisc show dev "$dev" \
826	    | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
827}
828
829qdisc_parent_stats_get()
830{
831	local dev=$1; shift
832	local parent=$1; shift
833	local selector=$1; shift
834
835	tc -j -s qdisc show dev "$dev" invisible \
836	    | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
837}
838
839ipv6_stats_get()
840{
841	local dev=$1; shift
842	local stat=$1; shift
843
844	cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
845}
846
847hw_stats_get()
848{
849	local suite=$1; shift
850	local if_name=$1; shift
851	local dir=$1; shift
852	local stat=$1; shift
853
854	ip -j stats show dev $if_name group offload subgroup $suite |
855		jq ".[0].stats64.$dir.$stat"
856}
857
858humanize()
859{
860	local speed=$1; shift
861
862	for unit in bps Kbps Mbps Gbps; do
863		if (($(echo "$speed < 1024" | bc))); then
864			break
865		fi
866
867		speed=$(echo "scale=1; $speed / 1024" | bc)
868	done
869
870	echo "$speed${unit}"
871}
872
873rate()
874{
875	local t0=$1; shift
876	local t1=$1; shift
877	local interval=$1; shift
878
879	echo $((8 * (t1 - t0) / interval))
880}
881
882packets_rate()
883{
884	local t0=$1; shift
885	local t1=$1; shift
886	local interval=$1; shift
887
888	echo $(((t1 - t0) / interval))
889}
890
891mac_get()
892{
893	local if_name=$1
894
895	ip -j link show dev $if_name | jq -r '.[]["address"]'
896}
897
898ipv6_lladdr_get()
899{
900	local if_name=$1
901
902	ip -j addr show dev $if_name | \
903		jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
904		head -1
905}
906
907bridge_ageing_time_get()
908{
909	local bridge=$1
910	local ageing_time
911
912	# Need to divide by 100 to convert to seconds.
913	ageing_time=$(ip -j -d link show dev $bridge \
914		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
915	echo $((ageing_time / 100))
916}
917
918declare -A SYSCTL_ORIG
919sysctl_set()
920{
921	local key=$1; shift
922	local value=$1; shift
923
924	SYSCTL_ORIG[$key]=$(sysctl -n $key)
925	sysctl -qw $key="$value"
926}
927
928sysctl_restore()
929{
930	local key=$1; shift
931
932	sysctl -qw $key="${SYSCTL_ORIG[$key]}"
933}
934
935forwarding_enable()
936{
937	sysctl_set net.ipv4.conf.all.forwarding 1
938	sysctl_set net.ipv6.conf.all.forwarding 1
939}
940
941forwarding_restore()
942{
943	sysctl_restore net.ipv6.conf.all.forwarding
944	sysctl_restore net.ipv4.conf.all.forwarding
945}
946
947declare -A MTU_ORIG
948mtu_set()
949{
950	local dev=$1; shift
951	local mtu=$1; shift
952
953	MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
954	ip link set dev $dev mtu $mtu
955}
956
957mtu_restore()
958{
959	local dev=$1; shift
960
961	ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
962}
963
964tc_offload_check()
965{
966	local num_netifs=${1:-$NUM_NETIFS}
967
968	for ((i = 1; i <= num_netifs; ++i)); do
969		ethtool -k ${NETIFS[p$i]} \
970			| grep "hw-tc-offload: on" &> /dev/null
971		if [[ $? -ne 0 ]]; then
972			return 1
973		fi
974	done
975
976	return 0
977}
978
979trap_install()
980{
981	local dev=$1; shift
982	local direction=$1; shift
983
984	# Some devices may not support or need in-hardware trapping of traffic
985	# (e.g. the veth pairs that this library creates for non-existent
986	# loopbacks). Use continue instead, so that there is a filter in there
987	# (some tests check counters), and so that other filters are still
988	# processed.
989	tc filter add dev $dev $direction pref 1 \
990		flower skip_sw action trap 2>/dev/null \
991	    || tc filter add dev $dev $direction pref 1 \
992		       flower action continue
993}
994
995trap_uninstall()
996{
997	local dev=$1; shift
998	local direction=$1; shift
999
1000	tc filter del dev $dev $direction pref 1 flower
1001}
1002
1003slow_path_trap_install()
1004{
1005	# For slow-path testing, we need to install a trap to get to
1006	# slow path the packets that would otherwise be switched in HW.
1007	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1008		trap_install "$@"
1009	fi
1010}
1011
1012slow_path_trap_uninstall()
1013{
1014	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1015		trap_uninstall "$@"
1016	fi
1017}
1018
1019__icmp_capture_add_del()
1020{
1021	local add_del=$1; shift
1022	local pref=$1; shift
1023	local vsuf=$1; shift
1024	local tundev=$1; shift
1025	local filter=$1; shift
1026
1027	tc filter $add_del dev "$tundev" ingress \
1028	   proto ip$vsuf pref $pref \
1029	   flower ip_proto icmp$vsuf $filter \
1030	   action pass
1031}
1032
1033icmp_capture_install()
1034{
1035	__icmp_capture_add_del add 100 "" "$@"
1036}
1037
1038icmp_capture_uninstall()
1039{
1040	__icmp_capture_add_del del 100 "" "$@"
1041}
1042
1043icmp6_capture_install()
1044{
1045	__icmp_capture_add_del add 100 v6 "$@"
1046}
1047
1048icmp6_capture_uninstall()
1049{
1050	__icmp_capture_add_del del 100 v6 "$@"
1051}
1052
1053__vlan_capture_add_del()
1054{
1055	local add_del=$1; shift
1056	local pref=$1; shift
1057	local dev=$1; shift
1058	local filter=$1; shift
1059
1060	tc filter $add_del dev "$dev" ingress \
1061	   proto 802.1q pref $pref \
1062	   flower $filter \
1063	   action pass
1064}
1065
1066vlan_capture_install()
1067{
1068	__vlan_capture_add_del add 100 "$@"
1069}
1070
1071vlan_capture_uninstall()
1072{
1073	__vlan_capture_add_del del 100 "$@"
1074}
1075
1076__dscp_capture_add_del()
1077{
1078	local add_del=$1; shift
1079	local dev=$1; shift
1080	local base=$1; shift
1081	local dscp;
1082
1083	for prio in {0..7}; do
1084		dscp=$((base + prio))
1085		__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1086				       "skip_hw ip_tos $((dscp << 2))"
1087	done
1088}
1089
1090dscp_capture_install()
1091{
1092	local dev=$1; shift
1093	local base=$1; shift
1094
1095	__dscp_capture_add_del add $dev $base
1096}
1097
1098dscp_capture_uninstall()
1099{
1100	local dev=$1; shift
1101	local base=$1; shift
1102
1103	__dscp_capture_add_del del $dev $base
1104}
1105
1106dscp_fetch_stats()
1107{
1108	local dev=$1; shift
1109	local base=$1; shift
1110
1111	for prio in {0..7}; do
1112		local dscp=$((base + prio))
1113		local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1114		echo "[$dscp]=$t "
1115	done
1116}
1117
1118matchall_sink_create()
1119{
1120	local dev=$1; shift
1121
1122	tc qdisc add dev $dev clsact
1123	tc filter add dev $dev ingress \
1124	   pref 10000 \
1125	   matchall \
1126	   action drop
1127}
1128
1129tests_run()
1130{
1131	local current_test
1132
1133	for current_test in ${TESTS:-$ALL_TESTS}; do
1134		$current_test
1135	done
1136}
1137
1138multipath_eval()
1139{
1140	local desc="$1"
1141	local weight_rp12=$2
1142	local weight_rp13=$3
1143	local packets_rp12=$4
1144	local packets_rp13=$5
1145	local weights_ratio packets_ratio diff
1146
1147	RET=0
1148
1149	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1150		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1151				| bc -l)
1152	else
1153		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1154				| bc -l)
1155	fi
1156
1157	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1158	       check_err 1 "Packet difference is 0"
1159	       log_test "Multipath"
1160	       log_info "Expected ratio $weights_ratio"
1161	       return
1162	fi
1163
1164	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1165		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1166				| bc -l)
1167	else
1168		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1169				| bc -l)
1170	fi
1171
1172	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1173	diff=${diff#-}
1174
1175	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1176	check_err $? "Too large discrepancy between expected and measured ratios"
1177	log_test "$desc"
1178	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1179}
1180
1181in_ns()
1182{
1183	local name=$1; shift
1184
1185	ip netns exec $name bash <<-EOF
1186		NUM_NETIFS=0
1187		source lib.sh
1188		$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1189	EOF
1190}
1191
1192##############################################################################
1193# Tests
1194
1195ping_do()
1196{
1197	local if_name=$1
1198	local dip=$2
1199	local args=$3
1200	local vrf_name
1201
1202	vrf_name=$(master_name_get $if_name)
1203	ip vrf exec $vrf_name \
1204		$PING $args $dip -c $PING_COUNT -i 0.1 \
1205		-w $PING_TIMEOUT &> /dev/null
1206}
1207
1208ping_test()
1209{
1210	RET=0
1211
1212	ping_do $1 $2
1213	check_err $?
1214	log_test "ping$3"
1215}
1216
1217ping6_do()
1218{
1219	local if_name=$1
1220	local dip=$2
1221	local args=$3
1222	local vrf_name
1223
1224	vrf_name=$(master_name_get $if_name)
1225	ip vrf exec $vrf_name \
1226		$PING6 $args $dip -c $PING_COUNT -i 0.1 \
1227		-w $PING_TIMEOUT &> /dev/null
1228}
1229
1230ping6_test()
1231{
1232	RET=0
1233
1234	ping6_do $1 $2
1235	check_err $?
1236	log_test "ping6$3"
1237}
1238
1239learning_test()
1240{
1241	local bridge=$1
1242	local br_port1=$2	# Connected to `host1_if`.
1243	local host1_if=$3
1244	local host2_if=$4
1245	local mac=de:ad:be:ef:13:37
1246	local ageing_time
1247
1248	RET=0
1249
1250	bridge -j fdb show br $bridge brport $br_port1 \
1251		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1252	check_fail $? "Found FDB record when should not"
1253
1254	# Disable unknown unicast flooding on `br_port1` to make sure
1255	# packets are only forwarded through the port after a matching
1256	# FDB entry was installed.
1257	bridge link set dev $br_port1 flood off
1258
1259	ip link set $host1_if promisc on
1260	tc qdisc add dev $host1_if ingress
1261	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1262		flower dst_mac $mac action drop
1263
1264	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1265	sleep 1
1266
1267	tc -j -s filter show dev $host1_if ingress \
1268		| jq -e ".[] | select(.options.handle == 101) \
1269		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1270	check_fail $? "Packet reached first host when should not"
1271
1272	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1273	sleep 1
1274
1275	bridge -j fdb show br $bridge brport $br_port1 \
1276		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1277	check_err $? "Did not find FDB record when should"
1278
1279	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1280	sleep 1
1281
1282	tc -j -s filter show dev $host1_if ingress \
1283		| jq -e ".[] | select(.options.handle == 101) \
1284		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1285	check_err $? "Packet did not reach second host when should"
1286
1287	# Wait for 10 seconds after the ageing time to make sure FDB
1288	# record was aged-out.
1289	ageing_time=$(bridge_ageing_time_get $bridge)
1290	sleep $((ageing_time + 10))
1291
1292	bridge -j fdb show br $bridge brport $br_port1 \
1293		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1294	check_fail $? "Found FDB record when should not"
1295
1296	bridge link set dev $br_port1 learning off
1297
1298	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1299	sleep 1
1300
1301	bridge -j fdb show br $bridge brport $br_port1 \
1302		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1303	check_fail $? "Found FDB record when should not"
1304
1305	bridge link set dev $br_port1 learning on
1306
1307	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1308	tc qdisc del dev $host1_if ingress
1309	ip link set $host1_if promisc off
1310
1311	bridge link set dev $br_port1 flood on
1312
1313	log_test "FDB learning"
1314}
1315
1316flood_test_do()
1317{
1318	local should_flood=$1
1319	local mac=$2
1320	local ip=$3
1321	local host1_if=$4
1322	local host2_if=$5
1323	local err=0
1324
1325	# Add an ACL on `host2_if` which will tell us whether the packet
1326	# was flooded to it or not.
1327	ip link set $host2_if promisc on
1328	tc qdisc add dev $host2_if ingress
1329	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1330		flower dst_mac $mac action drop
1331
1332	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1333	sleep 1
1334
1335	tc -j -s filter show dev $host2_if ingress \
1336		| jq -e ".[] | select(.options.handle == 101) \
1337		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1338	if [[ $? -ne 0 && $should_flood == "true" || \
1339	      $? -eq 0 && $should_flood == "false" ]]; then
1340		err=1
1341	fi
1342
1343	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1344	tc qdisc del dev $host2_if ingress
1345	ip link set $host2_if promisc off
1346
1347	return $err
1348}
1349
1350flood_unicast_test()
1351{
1352	local br_port=$1
1353	local host1_if=$2
1354	local host2_if=$3
1355	local mac=de:ad:be:ef:13:37
1356	local ip=192.0.2.100
1357
1358	RET=0
1359
1360	bridge link set dev $br_port flood off
1361
1362	flood_test_do false $mac $ip $host1_if $host2_if
1363	check_err $? "Packet flooded when should not"
1364
1365	bridge link set dev $br_port flood on
1366
1367	flood_test_do true $mac $ip $host1_if $host2_if
1368	check_err $? "Packet was not flooded when should"
1369
1370	log_test "Unknown unicast flood"
1371}
1372
1373flood_multicast_test()
1374{
1375	local br_port=$1
1376	local host1_if=$2
1377	local host2_if=$3
1378	local mac=01:00:5e:00:00:01
1379	local ip=239.0.0.1
1380
1381	RET=0
1382
1383	bridge link set dev $br_port mcast_flood off
1384
1385	flood_test_do false $mac $ip $host1_if $host2_if
1386	check_err $? "Packet flooded when should not"
1387
1388	bridge link set dev $br_port mcast_flood on
1389
1390	flood_test_do true $mac $ip $host1_if $host2_if
1391	check_err $? "Packet was not flooded when should"
1392
1393	log_test "Unregistered multicast flood"
1394}
1395
1396flood_test()
1397{
1398	# `br_port` is connected to `host2_if`
1399	local br_port=$1
1400	local host1_if=$2
1401	local host2_if=$3
1402
1403	flood_unicast_test $br_port $host1_if $host2_if
1404	flood_multicast_test $br_port $host1_if $host2_if
1405}
1406
1407__start_traffic()
1408{
1409	local pktsize=$1; shift
1410	local proto=$1; shift
1411	local h_in=$1; shift    # Where the traffic egresses the host
1412	local sip=$1; shift
1413	local dip=$1; shift
1414	local dmac=$1; shift
1415
1416	$MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1417		-a own -b $dmac -t "$proto" -q "$@" &
1418	sleep 1
1419}
1420
1421start_traffic_pktsize()
1422{
1423	local pktsize=$1; shift
1424
1425	__start_traffic $pktsize udp "$@"
1426}
1427
1428start_tcp_traffic_pktsize()
1429{
1430	local pktsize=$1; shift
1431
1432	__start_traffic $pktsize tcp "$@"
1433}
1434
1435start_traffic()
1436{
1437	start_traffic_pktsize 8000 "$@"
1438}
1439
1440start_tcp_traffic()
1441{
1442	start_tcp_traffic_pktsize 8000 "$@"
1443}
1444
1445stop_traffic()
1446{
1447	# Suppress noise from killing mausezahn.
1448	{ kill %% && wait %%; } 2>/dev/null
1449}
1450
1451declare -A cappid
1452declare -A capfile
1453declare -A capout
1454
1455tcpdump_start()
1456{
1457	local if_name=$1; shift
1458	local ns=$1; shift
1459
1460	capfile[$if_name]=$(mktemp)
1461	capout[$if_name]=$(mktemp)
1462
1463	if [ -z $ns ]; then
1464		ns_cmd=""
1465	else
1466		ns_cmd="ip netns exec ${ns}"
1467	fi
1468
1469	if [ -z $SUDO_USER ] ; then
1470		capuser=""
1471	else
1472		capuser="-Z $SUDO_USER"
1473	fi
1474
1475	$ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1476		-s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1477		> "${capout[$if_name]}" 2>&1 &
1478	cappid[$if_name]=$!
1479
1480	sleep 1
1481}
1482
1483tcpdump_stop()
1484{
1485	local if_name=$1
1486	local pid=${cappid[$if_name]}
1487
1488	$ns_cmd kill "$pid" && wait "$pid"
1489	sleep 1
1490}
1491
1492tcpdump_cleanup()
1493{
1494	local if_name=$1
1495
1496	rm ${capfile[$if_name]} ${capout[$if_name]}
1497}
1498
1499tcpdump_show()
1500{
1501	local if_name=$1
1502
1503	tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1504}
1505
1506# return 0 if the packet wasn't seen on host2_if or 1 if it was
1507mcast_packet_test()
1508{
1509	local mac=$1
1510	local src_ip=$2
1511	local ip=$3
1512	local host1_if=$4
1513	local host2_if=$5
1514	local seen=0
1515	local tc_proto="ip"
1516	local mz_v6arg=""
1517
1518	# basic check to see if we were passed an IPv4 address, if not assume IPv6
1519	if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1520		tc_proto="ipv6"
1521		mz_v6arg="-6"
1522	fi
1523
1524	# Add an ACL on `host2_if` which will tell us whether the packet
1525	# was received by it or not.
1526	tc qdisc add dev $host2_if ingress
1527	tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1528		flower ip_proto udp dst_mac $mac action drop
1529
1530	$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1531	sleep 1
1532
1533	tc -j -s filter show dev $host2_if ingress \
1534		| jq -e ".[] | select(.options.handle == 101) \
1535		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
1536	if [[ $? -eq 0 ]]; then
1537		seen=1
1538	fi
1539
1540	tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1541	tc qdisc del dev $host2_if ingress
1542
1543	return $seen
1544}
1545
1546brmcast_check_sg_entries()
1547{
1548	local report=$1; shift
1549	local slist=("$@")
1550	local sarg=""
1551
1552	for src in "${slist[@]}"; do
1553		sarg="${sarg} and .source_list[].address == \"$src\""
1554	done
1555	bridge -j -d -s mdb show dev br0 \
1556		| jq -e ".[].mdb[] | \
1557			 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1558	check_err $? "Wrong *,G entry source list after $report report"
1559
1560	for sgent in "${slist[@]}"; do
1561		bridge -j -d -s mdb show dev br0 \
1562			| jq -e ".[].mdb[] | \
1563				 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1564		check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1565	done
1566}
1567
1568brmcast_check_sg_fwding()
1569{
1570	local should_fwd=$1; shift
1571	local sources=("$@")
1572
1573	for src in "${sources[@]}"; do
1574		local retval=0
1575
1576		mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1577		retval=$?
1578		if [ $should_fwd -eq 1 ]; then
1579			check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1580		else
1581			check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1582		fi
1583	done
1584}
1585
1586brmcast_check_sg_state()
1587{
1588	local is_blocked=$1; shift
1589	local sources=("$@")
1590	local should_fail=1
1591
1592	if [ $is_blocked -eq 1 ]; then
1593		should_fail=0
1594	fi
1595
1596	for src in "${sources[@]}"; do
1597		bridge -j -d -s mdb show dev br0 \
1598			| jq -e ".[].mdb[] | \
1599				 select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1600				 .source_list[] |
1601				 select(.address == \"$src\") |
1602				 select(.timer == \"0.00\")" &>/dev/null
1603		check_err_fail $should_fail $? "Entry $src has zero timer"
1604
1605		bridge -j -d -s mdb show dev br0 \
1606			| jq -e ".[].mdb[] | \
1607				 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1608				 .flags[] == \"blocked\")" &>/dev/null
1609		check_err_fail $should_fail $? "Entry $src has blocked flag"
1610	done
1611}
1612
1613mc_join()
1614{
1615	local if_name=$1
1616	local group=$2
1617	local vrf_name=$(master_name_get $if_name)
1618
1619	# We don't care about actual reception, just about joining the
1620	# IP multicast group and adding the L2 address to the device's
1621	# MAC filtering table
1622	ip vrf exec $vrf_name \
1623		mreceive -g $group -I $if_name > /dev/null 2>&1 &
1624	mreceive_pid=$!
1625
1626	sleep 1
1627}
1628
1629mc_leave()
1630{
1631	kill "$mreceive_pid" && wait "$mreceive_pid"
1632}
1633
1634mc_send()
1635{
1636	local if_name=$1
1637	local groups=$2
1638	local vrf_name=$(master_name_get $if_name)
1639
1640	ip vrf exec $vrf_name \
1641		msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1642}
1643
1644start_ip_monitor()
1645{
1646	local mtype=$1; shift
1647	local ip=${1-ip}; shift
1648
1649	# start the monitor in the background
1650	tmpfile=`mktemp /var/run/nexthoptestXXX`
1651	mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1652	sleep 0.2
1653	echo "$mpid $tmpfile"
1654}
1655
1656stop_ip_monitor()
1657{
1658	local mpid=$1; shift
1659	local tmpfile=$1; shift
1660	local el=$1; shift
1661	local what=$1; shift
1662
1663	sleep 0.2
1664	kill $mpid
1665	local lines=`grep '^\w' $tmpfile | wc -l`
1666	test $lines -eq $el
1667	check_err $? "$what: $lines lines of events, expected $el"
1668	rm -rf $tmpfile
1669}
1670
1671hw_stats_monitor_test()
1672{
1673	local dev=$1; shift
1674	local type=$1; shift
1675	local make_suitable=$1; shift
1676	local make_unsuitable=$1; shift
1677	local ip=${1-ip}; shift
1678
1679	RET=0
1680
1681	# Expect a notification about enablement.
1682	local ipmout=$(start_ip_monitor stats "$ip")
1683	$ip stats set dev $dev ${type}_stats on
1684	stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1685
1686	# Expect a notification about offload.
1687	local ipmout=$(start_ip_monitor stats "$ip")
1688	$make_suitable
1689	stop_ip_monitor $ipmout 1 "${type}_stats installation"
1690
1691	# Expect a notification about loss of offload.
1692	local ipmout=$(start_ip_monitor stats "$ip")
1693	$make_unsuitable
1694	stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1695
1696	# Expect a notification about disablement
1697	local ipmout=$(start_ip_monitor stats "$ip")
1698	$ip stats set dev $dev ${type}_stats off
1699	stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1700
1701	log_test "${type}_stats notifications"
1702}
1703
1704ipv4_to_bytes()
1705{
1706	local IP=$1; shift
1707
1708	printf '%02x:' ${IP//./ } |
1709	    sed 's/:$//'
1710}
1711
1712# Convert a given IPv6 address, `IP' such that the :: token, if present, is
1713# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1714# digits. An optional `BYTESEP' parameter can be given to further separate
1715# individual bytes of each 16-bit group.
1716expand_ipv6()
1717{
1718	local IP=$1; shift
1719	local bytesep=$1; shift
1720
1721	local cvt_ip=${IP/::/_}
1722	local colons=${cvt_ip//[^:]/}
1723	local allcol=:::::::
1724	# IP where :: -> the appropriate number of colons:
1725	local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1726
1727	echo $allcol_ip | tr : '\n' |
1728	    sed s/^/0000/ |
1729	    sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1730	    tr '\n' : |
1731	    sed 's/:$//'
1732}
1733
1734ipv6_to_bytes()
1735{
1736	local IP=$1; shift
1737
1738	expand_ipv6 "$IP" :
1739}
1740
1741u16_to_bytes()
1742{
1743	local u16=$1; shift
1744
1745	printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1746}
1747
1748# Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1749# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1750# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1751# stands for 00:00.
1752payload_template_calc_checksum()
1753{
1754	local payload=$1; shift
1755
1756	(
1757	    # Set input radix.
1758	    echo "16i"
1759	    # Push zero for the initial checksum.
1760	    echo 0
1761
1762	    # Pad the payload with a terminating 00: in case we get an odd
1763	    # number of bytes.
1764	    echo "${payload%:}:00:" |
1765		sed 's/CHECKSUM/00:00/g' |
1766		tr '[:lower:]' '[:upper:]' |
1767		# Add the word to the checksum.
1768		sed 's/\(..\):\(..\):/\1\2+\n/g' |
1769		# Strip the extra odd byte we pushed if left unconverted.
1770		sed 's/\(..\):$//'
1771
1772	    echo "10000 ~ +"	# Calculate and add carry.
1773	    echo "FFFF r - p"	# Bit-flip and print.
1774	) |
1775	    dc |
1776	    tr '[:upper:]' '[:lower:]'
1777}
1778
1779payload_template_expand_checksum()
1780{
1781	local payload=$1; shift
1782	local checksum=$1; shift
1783
1784	local ckbytes=$(u16_to_bytes $checksum)
1785
1786	echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1787}
1788
1789payload_template_nbytes()
1790{
1791	local payload=$1; shift
1792
1793	payload_template_expand_checksum "${payload%:}" 0 |
1794		sed 's/:/\n/g' | wc -l
1795}
1796
1797igmpv3_is_in_get()
1798{
1799	local GRP=$1; shift
1800	local sources=("$@")
1801
1802	local igmpv3
1803	local nsources=$(u16_to_bytes ${#sources[@]})
1804
1805	# IS_IN ( $sources )
1806	igmpv3=$(:
1807		)"22:"$(			: Type - Membership Report
1808		)"00:"$(			: Reserved
1809		)"CHECKSUM:"$(			: Checksum
1810		)"00:00:"$(			: Reserved
1811		)"00:01:"$(			: Number of Group Records
1812		)"01:"$(			: Record Type - IS_IN
1813		)"00:"$(			: Aux Data Len
1814		)"${nsources}:"$(		: Number of Sources
1815		)"$(ipv4_to_bytes $GRP):"$(	: Multicast Address
1816		)"$(for src in "${sources[@]}"; do
1817			ipv4_to_bytes $src
1818			echo -n :
1819		    done)"$(			: Source Addresses
1820		)
1821	local checksum=$(payload_template_calc_checksum "$igmpv3")
1822
1823	payload_template_expand_checksum "$igmpv3" $checksum
1824}
1825
1826igmpv2_leave_get()
1827{
1828	local GRP=$1; shift
1829
1830	local payload=$(:
1831		)"17:"$(			: Type - Leave Group
1832		)"00:"$(			: Max Resp Time - not meaningful
1833		)"CHECKSUM:"$(			: Checksum
1834		)"$(ipv4_to_bytes $GRP)"$(	: Group Address
1835		)
1836	local checksum=$(payload_template_calc_checksum "$payload")
1837
1838	payload_template_expand_checksum "$payload" $checksum
1839}
1840
1841mldv2_is_in_get()
1842{
1843	local SIP=$1; shift
1844	local GRP=$1; shift
1845	local sources=("$@")
1846
1847	local hbh
1848	local icmpv6
1849	local nsources=$(u16_to_bytes ${#sources[@]})
1850
1851	hbh=$(:
1852		)"3a:"$(			: Next Header - ICMPv6
1853		)"00:"$(			: Hdr Ext Len
1854		)"00:00:00:00:00:00:"$(		: Options and Padding
1855		)
1856
1857	icmpv6=$(:
1858		)"8f:"$(			: Type - MLDv2 Report
1859		)"00:"$(			: Code
1860		)"CHECKSUM:"$(			: Checksum
1861		)"00:00:"$(			: Reserved
1862		)"00:01:"$(			: Number of Group Records
1863		)"01:"$(			: Record Type - IS_IN
1864		)"00:"$(			: Aux Data Len
1865		)"${nsources}:"$(		: Number of Sources
1866		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1867		)"$(for src in "${sources[@]}"; do
1868			ipv6_to_bytes $src
1869			echo -n :
1870		    done)"$(			: Source Addresses
1871		)
1872
1873	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1874	local sudohdr=$(:
1875		)"$(ipv6_to_bytes $SIP):"$(	: SIP
1876		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
1877	        )"${len}:"$(			: Upper-layer length
1878	        )"00:3a:"$(			: Zero and next-header
1879	        )
1880	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1881
1882	payload_template_expand_checksum "$hbh$icmpv6" $checksum
1883}
1884
1885mldv1_done_get()
1886{
1887	local SIP=$1; shift
1888	local GRP=$1; shift
1889
1890	local hbh
1891	local icmpv6
1892
1893	hbh=$(:
1894		)"3a:"$(			: Next Header - ICMPv6
1895		)"00:"$(			: Hdr Ext Len
1896		)"00:00:00:00:00:00:"$(		: Options and Padding
1897		)
1898
1899	icmpv6=$(:
1900		)"84:"$(			: Type - MLDv1 Done
1901		)"00:"$(			: Code
1902		)"CHECKSUM:"$(			: Checksum
1903		)"00:00:"$(			: Max Resp Delay - not meaningful
1904		)"00:00:"$(			: Reserved
1905		)"$(ipv6_to_bytes $GRP):"$(	: Multicast address
1906		)
1907
1908	local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1909	local sudohdr=$(:
1910		)"$(ipv6_to_bytes $SIP):"$(	: SIP
1911		)"$(ipv6_to_bytes $GRP):"$(	: DIP is multicast address
1912	        )"${len}:"$(			: Upper-layer length
1913	        )"00:3a:"$(			: Zero and next-header
1914	        )
1915	local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1916
1917	payload_template_expand_checksum "$hbh$icmpv6" $checksum
1918}
1919
1920bail_on_lldpad()
1921{
1922	local reason1="$1"; shift
1923	local reason2="$1"; shift
1924
1925	if systemctl is-active --quiet lldpad; then
1926
1927		cat >/dev/stderr <<-EOF
1928		WARNING: lldpad is running
1929
1930			lldpad will likely $reason1, and this test will
1931			$reason2. Both are not supported at the same time,
1932			one of them is arbitrarily going to overwrite the
1933			other. That will cause spurious failures (or, unlikely,
1934			passes) of this test.
1935		EOF
1936
1937		if [[ -z $ALLOW_LLDPAD ]]; then
1938			cat >/dev/stderr <<-EOF
1939
1940				If you want to run the test anyway, please set
1941				an environment variable ALLOW_LLDPAD to a
1942				non-empty string.
1943			EOF
1944			exit 1
1945		else
1946			return
1947		fi
1948	fi
1949}
1950