1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Can be overridden by the configuration file.
8PING=${PING:=ping}
9PING6=${PING6:=ping6}
10MZ=${MZ:=mausezahn}
11ARPING=${ARPING:=arping}
12TEAMD=${TEAMD:=teamd}
13WAIT_TIME=${WAIT_TIME:=5}
14PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
15PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
16NETIF_TYPE=${NETIF_TYPE:=veth}
17NETIF_CREATE=${NETIF_CREATE:=yes}
18MCD=${MCD:=smcrouted}
19MC_CLI=${MC_CLI:=smcroutectl}
20PING_COUNT=${PING_COUNT:=10}
21PING_TIMEOUT=${PING_TIMEOUT:=5}
22WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
23INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
24LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
25REQUIRE_JQ=${REQUIRE_JQ:=yes}
26REQUIRE_MZ=${REQUIRE_MZ:=yes}
27REQUIRE_MTOOLS=${REQUIRE_MTOOLS:=no}
28STABLE_MAC_ADDRS=${STABLE_MAC_ADDRS:=no}
29TCPDUMP_EXTRA_FLAGS=${TCPDUMP_EXTRA_FLAGS:=}
30TROUTE6=${TROUTE6:=traceroute6}
31
32relative_path="${BASH_SOURCE%/*}"
33if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
34	relative_path="."
35fi
36
37if [[ -f $relative_path/forwarding.config ]]; then
38	source "$relative_path/forwarding.config"
39fi
40
41# Kselftest framework requirement - SKIP code is 4.
42ksft_skip=4
43
44busywait()
45{
46	local timeout=$1; shift
47
48	local start_time="$(date -u +%s%3N)"
49	while true
50	do
51		local out
52		out=$("$@")
53		local ret=$?
54		if ((!ret)); then
55			echo -n "$out"
56			return 0
57		fi
58
59		local current_time="$(date -u +%s%3N)"
60		if ((current_time - start_time > timeout)); then
61			echo -n "$out"
62			return 1
63		fi
64	done
65}
66
67##############################################################################
68# Sanity checks
69
70check_tc_version()
71{
72	tc -j &> /dev/null
73	if [[ $? -ne 0 ]]; then
74		echo "SKIP: iproute2 too old; tc is missing JSON support"
75		exit $ksft_skip
76	fi
77}
78
79# Old versions of tc don't understand "mpls_uc"
80check_tc_mpls_support()
81{
82	local dev=$1; shift
83
84	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
85		matchall action pipe &> /dev/null
86	if [[ $? -ne 0 ]]; then
87		echo "SKIP: iproute2 too old; tc is missing MPLS support"
88		return $ksft_skip
89	fi
90	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
91		matchall
92}
93
94# Old versions of tc produce invalid json output for mpls lse statistics
95check_tc_mpls_lse_stats()
96{
97	local dev=$1; shift
98	local ret;
99
100	tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
101		flower mpls lse depth 2                                 \
102		action continue &> /dev/null
103
104	if [[ $? -ne 0 ]]; then
105		echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
106		return $ksft_skip
107	fi
108
109	tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
110	ret=$?
111	tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
112		flower
113
114	if [[ $ret -ne 0 ]]; then
115		echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
116		return $ksft_skip
117	fi
118}
119
120check_tc_shblock_support()
121{
122	tc filter help 2>&1 | grep block &> /dev/null
123	if [[ $? -ne 0 ]]; then
124		echo "SKIP: iproute2 too old; tc is missing shared block support"
125		exit $ksft_skip
126	fi
127}
128
129check_tc_chain_support()
130{
131	tc help 2>&1|grep chain &> /dev/null
132	if [[ $? -ne 0 ]]; then
133		echo "SKIP: iproute2 too old; tc is missing chain support"
134		exit $ksft_skip
135	fi
136}
137
138check_tc_action_hw_stats_support()
139{
140	tc actions help 2>&1 | grep -q hw_stats
141	if [[ $? -ne 0 ]]; then
142		echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
143		exit $ksft_skip
144	fi
145}
146
147check_tc_fp_support()
148{
149	tc qdisc add dev lo mqprio help 2>&1 | grep -q "fp "
150	if [[ $? -ne 0 ]]; then
151		echo "SKIP: iproute2 too old; tc is missing frame preemption support"
152		exit $ksft_skip
153	fi
154}
155
156check_ethtool_lanes_support()
157{
158	ethtool --help 2>&1| grep lanes &> /dev/null
159	if [[ $? -ne 0 ]]; then
160		echo "SKIP: ethtool too old; it is missing lanes support"
161		exit $ksft_skip
162	fi
163}
164
165check_ethtool_mm_support()
166{
167	ethtool --help 2>&1| grep -- '--show-mm' &> /dev/null
168	if [[ $? -ne 0 ]]; then
169		echo "SKIP: ethtool too old; it is missing MAC Merge layer support"
170		exit $ksft_skip
171	fi
172}
173
174check_locked_port_support()
175{
176	if ! bridge -d link show | grep -q " locked"; then
177		echo "SKIP: iproute2 too old; Locked port feature not supported."
178		return $ksft_skip
179	fi
180}
181
182check_port_mab_support()
183{
184	if ! bridge -d link show | grep -q "mab"; then
185		echo "SKIP: iproute2 too old; MacAuth feature not supported."
186		return $ksft_skip
187	fi
188}
189
190skip_on_veth()
191{
192	local kind=$(ip -j -d link show dev ${NETIFS[p1]} |
193		jq -r '.[].linkinfo.info_kind')
194
195	if [[ $kind == veth ]]; then
196		echo "SKIP: Test cannot be run with veth pairs"
197		exit $ksft_skip
198	fi
199}
200
201if [[ "$(id -u)" -ne 0 ]]; then
202	echo "SKIP: need root privileges"
203	exit $ksft_skip
204fi
205
206if [[ "$CHECK_TC" = "yes" ]]; then
207	check_tc_version
208fi
209
210require_command()
211{
212	local cmd=$1; shift
213
214	if [[ ! -x "$(command -v "$cmd")" ]]; then
215		echo "SKIP: $cmd not installed"
216		exit $ksft_skip
217	fi
218}
219
220if [[ "$REQUIRE_JQ" = "yes" ]]; then
221	require_command jq
222fi
223if [[ "$REQUIRE_MZ" = "yes" ]]; then
224	require_command $MZ
225fi
226if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then
227	# https://github.com/vladimiroltean/mtools/
228	# patched for IPv6 support
229	require_command msend
230	require_command mreceive
231fi
232
233if [[ ! -v NUM_NETIFS ]]; then
234	echo "SKIP: importer does not define \"NUM_NETIFS\""
235	exit $ksft_skip
236fi
237
238##############################################################################
239# Command line options handling
240
241count=0
242
243while [[ $# -gt 0 ]]; do
244	if [[ "$count" -eq "0" ]]; then
245		unset NETIFS
246		declare -A NETIFS
247	fi
248	count=$((count + 1))
249	NETIFS[p$count]="$1"
250	shift
251done
252
253##############################################################################
254# Network interfaces configuration
255
256create_netif_veth()
257{
258	local i
259
260	for ((i = 1; i <= NUM_NETIFS; ++i)); do
261		local j=$((i+1))
262
263		if [ -z ${NETIFS[p$i]} ]; then
264			echo "SKIP: Cannot create interface. Name not specified"
265			exit $ksft_skip
266		fi
267
268		ip link show dev ${NETIFS[p$i]} &> /dev/null
269		if [[ $? -ne 0 ]]; then
270			ip link add ${NETIFS[p$i]} type veth \
271				peer name ${NETIFS[p$j]}
272			if [[ $? -ne 0 ]]; then
273				echo "Failed to create netif"
274				exit 1
275			fi
276		fi
277		i=$j
278	done
279}
280
281create_netif()
282{
283	case "$NETIF_TYPE" in
284	veth) create_netif_veth
285	      ;;
286	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
287	   exit 1
288	   ;;
289	esac
290}
291
292declare -A MAC_ADDR_ORIG
293mac_addr_prepare()
294{
295	local new_addr=
296	local dev=
297
298	for ((i = 1; i <= NUM_NETIFS; ++i)); do
299		dev=${NETIFS[p$i]}
300		new_addr=$(printf "00:01:02:03:04:%02x" $i)
301
302		MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address')
303		# Strip quotes
304		MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/}
305		ip link set dev $dev address $new_addr
306	done
307}
308
309mac_addr_restore()
310{
311	local dev=
312
313	for ((i = 1; i <= NUM_NETIFS; ++i)); do
314		dev=${NETIFS[p$i]}
315		ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]}
316	done
317}
318
319if [[ "$NETIF_CREATE" = "yes" ]]; then
320	create_netif
321fi
322
323if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
324	mac_addr_prepare
325fi
326
327for ((i = 1; i <= NUM_NETIFS; ++i)); do
328	ip link show dev ${NETIFS[p$i]} &> /dev/null
329	if [[ $? -ne 0 ]]; then
330		echo "SKIP: could not find all required interfaces"
331		exit $ksft_skip
332	fi
333done
334
335##############################################################################
336# Helpers
337
338# Exit status to return at the end. Set in case one of the tests fails.
339EXIT_STATUS=0
340# Per-test return value. Clear at the beginning of each test.
341RET=0
342
343check_err()
344{
345	local err=$1
346	local msg=$2
347
348	if [[ $RET -eq 0 && $err -ne 0 ]]; then
349		RET=$err
350		retmsg=$msg
351	fi
352}
353
354check_fail()
355{
356	local err=$1
357	local msg=$2
358
359	if [[ $RET -eq 0 && $err -eq 0 ]]; then
360		RET=1
361		retmsg=$msg
362	fi
363}
364
365check_err_fail()
366{
367	local should_fail=$1; shift
368	local err=$1; shift
369	local what=$1; shift
370
371	if ((should_fail)); then
372		check_fail $err "$what succeeded, but should have failed"
373	else
374		check_err $err "$what failed"
375	fi
376}
377
378log_test()
379{
380	local test_name=$1
381	local opt_str=$2
382
383	if [[ $# -eq 2 ]]; then
384		opt_str="($opt_str)"
385	fi
386
387	if [[ $RET -ne 0 ]]; then
388		EXIT_STATUS=1
389		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
390		if [[ ! -z "$retmsg" ]]; then
391			printf "\t%s\n" "$retmsg"
392		fi
393		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
394			echo "Hit enter to continue, 'q' to quit"
395			read a
396			[ "$a" = "q" ] && exit 1
397		fi
398		return 1
399	fi
400
401	printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
402	return 0
403}
404
405log_test_skip()
406{
407	local test_name=$1
408	local opt_str=$2
409
410	printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
411	return 0
412}
413
414log_info()
415{
416	local msg=$1
417
418	echo "INFO: $msg"
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