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