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