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