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