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