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