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