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