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