xref: /openbmc/linux/tools/testing/selftests/net/forwarding/lib.sh (revision a0ae2562c6c4b2721d9fddba63b7286c13517d9f)
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}
11WAIT_TIME=${WAIT_TIME:=5}
12PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
13PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
14NETIF_TYPE=${NETIF_TYPE:=veth}
15NETIF_CREATE=${NETIF_CREATE:=yes}
16
17relative_path="${BASH_SOURCE%/*}"
18if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
19	relative_path="."
20fi
21
22if [[ -f $relative_path/forwarding.config ]]; then
23	source "$relative_path/forwarding.config"
24fi
25
26##############################################################################
27# Sanity checks
28
29check_tc_version()
30{
31	tc -j &> /dev/null
32	if [[ $? -ne 0 ]]; then
33		echo "SKIP: iproute2 too old; tc is missing JSON support"
34		exit 1
35	fi
36
37	tc filter help 2>&1 | grep block &> /dev/null
38	if [[ $? -ne 0 ]]; then
39		echo "SKIP: iproute2 too old; tc is missing shared block support"
40		exit 1
41	fi
42}
43
44if [[ "$(id -u)" -ne 0 ]]; then
45	echo "SKIP: need root privileges"
46	exit 0
47fi
48
49if [[ "$CHECK_TC" = "yes" ]]; then
50	check_tc_version
51fi
52
53if [[ ! -x "$(command -v jq)" ]]; then
54	echo "SKIP: jq not installed"
55	exit 1
56fi
57
58if [[ ! -x "$(command -v $MZ)" ]]; then
59	echo "SKIP: $MZ not installed"
60	exit 1
61fi
62
63if [[ ! -v NUM_NETIFS ]]; then
64	echo "SKIP: importer does not define \"NUM_NETIFS\""
65	exit 1
66fi
67
68##############################################################################
69# Command line options handling
70
71count=0
72
73while [[ $# -gt 0 ]]; do
74	if [[ "$count" -eq "0" ]]; then
75		unset NETIFS
76		declare -A NETIFS
77	fi
78	count=$((count + 1))
79	NETIFS[p$count]="$1"
80	shift
81done
82
83##############################################################################
84# Network interfaces configuration
85
86create_netif_veth()
87{
88	local i
89
90	for i in $(eval echo {1..$NUM_NETIFS}); do
91		local j=$((i+1))
92
93		ip link show dev ${NETIFS[p$i]} &> /dev/null
94		if [[ $? -ne 0 ]]; then
95			ip link add ${NETIFS[p$i]} type veth \
96				peer name ${NETIFS[p$j]}
97			if [[ $? -ne 0 ]]; then
98				echo "Failed to create netif"
99				exit 1
100			fi
101		fi
102		i=$j
103	done
104}
105
106create_netif()
107{
108	case "$NETIF_TYPE" in
109	veth) create_netif_veth
110	      ;;
111	*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
112	   exit 1
113	   ;;
114	esac
115}
116
117if [[ "$NETIF_CREATE" = "yes" ]]; then
118	create_netif
119fi
120
121for i in $(eval echo {1..$NUM_NETIFS}); do
122	ip link show dev ${NETIFS[p$i]} &> /dev/null
123	if [[ $? -ne 0 ]]; then
124		echo "SKIP: could not find all required interfaces"
125		exit 1
126	fi
127done
128
129##############################################################################
130# Helpers
131
132# Exit status to return at the end. Set in case one of the tests fails.
133EXIT_STATUS=0
134# Per-test return value. Clear at the beginning of each test.
135RET=0
136
137check_err()
138{
139	local err=$1
140	local msg=$2
141
142	if [[ $RET -eq 0 && $err -ne 0 ]]; then
143		RET=$err
144		retmsg=$msg
145	fi
146}
147
148check_fail()
149{
150	local err=$1
151	local msg=$2
152
153	if [[ $RET -eq 0 && $err -eq 0 ]]; then
154		RET=1
155		retmsg=$msg
156	fi
157}
158
159check_err_fail()
160{
161	local should_fail=$1; shift
162	local err=$1; shift
163	local what=$1; shift
164
165	if ((should_fail)); then
166		check_fail $err "$what succeeded, but should have failed"
167	else
168		check_err $err "$what failed"
169	fi
170}
171
172log_test()
173{
174	local test_name=$1
175	local opt_str=$2
176
177	if [[ $# -eq 2 ]]; then
178		opt_str="($opt_str)"
179	fi
180
181	if [[ $RET -ne 0 ]]; then
182		EXIT_STATUS=1
183		printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
184		if [[ ! -z "$retmsg" ]]; then
185			printf "\t%s\n" "$retmsg"
186		fi
187		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
188			echo "Hit enter to continue, 'q' to quit"
189			read a
190			[ "$a" = "q" ] && exit 1
191		fi
192		return 1
193	fi
194
195	printf "TEST: %-60s  [PASS]\n" "$test_name $opt_str"
196	return 0
197}
198
199log_info()
200{
201	local msg=$1
202
203	echo "INFO: $msg"
204}
205
206setup_wait_dev()
207{
208	local dev=$1; shift
209
210	while true; do
211		ip link show dev $dev up \
212			| grep 'state UP' &> /dev/null
213		if [[ $? -ne 0 ]]; then
214			sleep 1
215		else
216			break
217		fi
218	done
219}
220
221setup_wait()
222{
223	local num_netifs=${1:-$NUM_NETIFS}
224
225	for ((i = 1; i <= num_netifs; ++i)); do
226		setup_wait_dev ${NETIFS[p$i]}
227	done
228
229	# Make sure links are ready.
230	sleep $WAIT_TIME
231}
232
233pre_cleanup()
234{
235	if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
236		echo "Pausing before cleanup, hit any key to continue"
237		read
238	fi
239}
240
241vrf_prepare()
242{
243	ip -4 rule add pref 32765 table local
244	ip -4 rule del pref 0
245	ip -6 rule add pref 32765 table local
246	ip -6 rule del pref 0
247}
248
249vrf_cleanup()
250{
251	ip -6 rule add pref 0 table local
252	ip -6 rule del pref 32765
253	ip -4 rule add pref 0 table local
254	ip -4 rule del pref 32765
255}
256
257__last_tb_id=0
258declare -A __TB_IDS
259
260__vrf_td_id_assign()
261{
262	local vrf_name=$1
263
264	__last_tb_id=$((__last_tb_id + 1))
265	__TB_IDS[$vrf_name]=$__last_tb_id
266	return $__last_tb_id
267}
268
269__vrf_td_id_lookup()
270{
271	local vrf_name=$1
272
273	return ${__TB_IDS[$vrf_name]}
274}
275
276vrf_create()
277{
278	local vrf_name=$1
279	local tb_id
280
281	__vrf_td_id_assign $vrf_name
282	tb_id=$?
283
284	ip link add dev $vrf_name type vrf table $tb_id
285	ip -4 route add table $tb_id unreachable default metric 4278198272
286	ip -6 route add table $tb_id unreachable default metric 4278198272
287}
288
289vrf_destroy()
290{
291	local vrf_name=$1
292	local tb_id
293
294	__vrf_td_id_lookup $vrf_name
295	tb_id=$?
296
297	ip -6 route del table $tb_id unreachable default metric 4278198272
298	ip -4 route del table $tb_id unreachable default metric 4278198272
299	ip link del dev $vrf_name
300}
301
302__addr_add_del()
303{
304	local if_name=$1
305	local add_del=$2
306	local array
307
308	shift
309	shift
310	array=("${@}")
311
312	for addrstr in "${array[@]}"; do
313		ip address $add_del $addrstr dev $if_name
314	done
315}
316
317__simple_if_init()
318{
319	local if_name=$1; shift
320	local vrf_name=$1; shift
321	local addrs=("${@}")
322
323	ip link set dev $if_name master $vrf_name
324	ip link set dev $if_name up
325
326	__addr_add_del $if_name add "${addrs[@]}"
327}
328
329__simple_if_fini()
330{
331	local if_name=$1; shift
332	local addrs=("${@}")
333
334	__addr_add_del $if_name del "${addrs[@]}"
335
336	ip link set dev $if_name down
337	ip link set dev $if_name nomaster
338}
339
340simple_if_init()
341{
342	local if_name=$1
343	local vrf_name
344	local array
345
346	shift
347	vrf_name=v$if_name
348	array=("${@}")
349
350	vrf_create $vrf_name
351	ip link set dev $vrf_name up
352	__simple_if_init $if_name $vrf_name "${array[@]}"
353}
354
355simple_if_fini()
356{
357	local if_name=$1
358	local vrf_name
359	local array
360
361	shift
362	vrf_name=v$if_name
363	array=("${@}")
364
365	__simple_if_fini $if_name "${array[@]}"
366	vrf_destroy $vrf_name
367}
368
369tunnel_create()
370{
371	local name=$1; shift
372	local type=$1; shift
373	local local=$1; shift
374	local remote=$1; shift
375
376	ip link add name $name type $type \
377	   local $local remote $remote "$@"
378	ip link set dev $name up
379}
380
381tunnel_destroy()
382{
383	local name=$1; shift
384
385	ip link del dev $name
386}
387
388vlan_create()
389{
390	local if_name=$1; shift
391	local vid=$1; shift
392	local vrf=$1; shift
393	local ips=("${@}")
394	local name=$if_name.$vid
395
396	ip link add name $name link $if_name type vlan id $vid
397	if [ "$vrf" != "" ]; then
398		ip link set dev $name master $vrf
399	fi
400	ip link set dev $name up
401	__addr_add_del $name add "${ips[@]}"
402}
403
404vlan_destroy()
405{
406	local if_name=$1; shift
407	local vid=$1; shift
408	local name=$if_name.$vid
409
410	ip link del dev $name
411}
412
413master_name_get()
414{
415	local if_name=$1
416
417	ip -j link show dev $if_name | jq -r '.[]["master"]'
418}
419
420link_stats_tx_packets_get()
421{
422       local if_name=$1
423
424       ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]'
425}
426
427tc_rule_stats_get()
428{
429	local dev=$1; shift
430	local pref=$1; shift
431	local dir=$1; shift
432
433	tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
434	    | jq '.[1].options.actions[].stats.packets'
435}
436
437mac_get()
438{
439	local if_name=$1
440
441	ip -j link show dev $if_name | jq -r '.[]["address"]'
442}
443
444bridge_ageing_time_get()
445{
446	local bridge=$1
447	local ageing_time
448
449	# Need to divide by 100 to convert to seconds.
450	ageing_time=$(ip -j -d link show dev $bridge \
451		      | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
452	echo $((ageing_time / 100))
453}
454
455declare -A SYSCTL_ORIG
456sysctl_set()
457{
458	local key=$1; shift
459	local value=$1; shift
460
461	SYSCTL_ORIG[$key]=$(sysctl -n $key)
462	sysctl -qw $key=$value
463}
464
465sysctl_restore()
466{
467	local key=$1; shift
468
469	sysctl -qw $key=${SYSCTL_ORIG["$key"]}
470}
471
472forwarding_enable()
473{
474	sysctl_set net.ipv4.conf.all.forwarding 1
475	sysctl_set net.ipv6.conf.all.forwarding 1
476}
477
478forwarding_restore()
479{
480	sysctl_restore net.ipv6.conf.all.forwarding
481	sysctl_restore net.ipv4.conf.all.forwarding
482}
483
484tc_offload_check()
485{
486	local num_netifs=${1:-$NUM_NETIFS}
487
488	for ((i = 1; i <= num_netifs; ++i)); do
489		ethtool -k ${NETIFS[p$i]} \
490			| grep "hw-tc-offload: on" &> /dev/null
491		if [[ $? -ne 0 ]]; then
492			return 1
493		fi
494	done
495
496	return 0
497}
498
499trap_install()
500{
501	local dev=$1; shift
502	local direction=$1; shift
503
504	# Some devices may not support or need in-hardware trapping of traffic
505	# (e.g. the veth pairs that this library creates for non-existent
506	# loopbacks). Use continue instead, so that there is a filter in there
507	# (some tests check counters), and so that other filters are still
508	# processed.
509	tc filter add dev $dev $direction pref 1 \
510		flower skip_sw action trap 2>/dev/null \
511	    || tc filter add dev $dev $direction pref 1 \
512		       flower action continue
513}
514
515trap_uninstall()
516{
517	local dev=$1; shift
518	local direction=$1; shift
519
520	tc filter del dev $dev $direction pref 1 flower
521}
522
523slow_path_trap_install()
524{
525	# For slow-path testing, we need to install a trap to get to
526	# slow path the packets that would otherwise be switched in HW.
527	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
528		trap_install "$@"
529	fi
530}
531
532slow_path_trap_uninstall()
533{
534	if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
535		trap_uninstall "$@"
536	fi
537}
538
539__icmp_capture_add_del()
540{
541	local add_del=$1; shift
542	local pref=$1; shift
543	local vsuf=$1; shift
544	local tundev=$1; shift
545	local filter=$1; shift
546
547	tc filter $add_del dev "$tundev" ingress \
548	   proto ip$vsuf pref $pref \
549	   flower ip_proto icmp$vsuf $filter \
550	   action pass
551}
552
553icmp_capture_install()
554{
555	__icmp_capture_add_del add 100 "" "$@"
556}
557
558icmp_capture_uninstall()
559{
560	__icmp_capture_add_del del 100 "" "$@"
561}
562
563icmp6_capture_install()
564{
565	__icmp_capture_add_del add 100 v6 "$@"
566}
567
568icmp6_capture_uninstall()
569{
570	__icmp_capture_add_del del 100 v6 "$@"
571}
572
573__vlan_capture_add_del()
574{
575	local add_del=$1; shift
576	local pref=$1; shift
577	local dev=$1; shift
578	local filter=$1; shift
579
580	tc filter $add_del dev "$dev" ingress \
581	   proto 802.1q pref $pref \
582	   flower $filter \
583	   action pass
584}
585
586vlan_capture_install()
587{
588	__vlan_capture_add_del add 100 "$@"
589}
590
591vlan_capture_uninstall()
592{
593	__vlan_capture_add_del del 100 "$@"
594}
595
596matchall_sink_create()
597{
598	local dev=$1; shift
599
600	tc qdisc add dev $dev clsact
601	tc filter add dev $dev ingress \
602	   pref 10000 \
603	   matchall \
604	   action drop
605}
606
607tests_run()
608{
609	local current_test
610
611	for current_test in ${TESTS:-$ALL_TESTS}; do
612		$current_test
613	done
614}
615
616multipath_eval()
617{
618	local desc="$1"
619	local weight_rp12=$2
620	local weight_rp13=$3
621	local packets_rp12=$4
622	local packets_rp13=$5
623	local weights_ratio packets_ratio diff
624
625	RET=0
626
627	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
628		weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
629				| bc -l)
630	else
631		weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
632				| bc -l)
633	fi
634
635	if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
636	       check_err 1 "Packet difference is 0"
637	       log_test "Multipath"
638	       log_info "Expected ratio $weights_ratio"
639	       return
640	fi
641
642	if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
643		packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
644				| bc -l)
645	else
646		packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
647				| bc -l)
648	fi
649
650	diff=$(echo $weights_ratio - $packets_ratio | bc -l)
651	diff=${diff#-}
652
653	test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
654	check_err $? "Too large discrepancy between expected and measured ratios"
655	log_test "$desc"
656	log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
657}
658
659##############################################################################
660# Tests
661
662ping_do()
663{
664	local if_name=$1
665	local dip=$2
666	local vrf_name
667
668	vrf_name=$(master_name_get $if_name)
669	ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null
670}
671
672ping_test()
673{
674	RET=0
675
676	ping_do $1 $2
677	check_err $?
678	log_test "ping"
679}
680
681ping6_do()
682{
683	local if_name=$1
684	local dip=$2
685	local vrf_name
686
687	vrf_name=$(master_name_get $if_name)
688	ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null
689}
690
691ping6_test()
692{
693	RET=0
694
695	ping6_do $1 $2
696	check_err $?
697	log_test "ping6"
698}
699
700learning_test()
701{
702	local bridge=$1
703	local br_port1=$2	# Connected to `host1_if`.
704	local host1_if=$3
705	local host2_if=$4
706	local mac=de:ad:be:ef:13:37
707	local ageing_time
708
709	RET=0
710
711	bridge -j fdb show br $bridge brport $br_port1 \
712		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
713	check_fail $? "Found FDB record when should not"
714
715	# Disable unknown unicast flooding on `br_port1` to make sure
716	# packets are only forwarded through the port after a matching
717	# FDB entry was installed.
718	bridge link set dev $br_port1 flood off
719
720	tc qdisc add dev $host1_if ingress
721	tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
722		flower dst_mac $mac action drop
723
724	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
725	sleep 1
726
727	tc -j -s filter show dev $host1_if ingress \
728		| jq -e ".[] | select(.options.handle == 101) \
729		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
730	check_fail $? "Packet reached second host when should not"
731
732	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
733	sleep 1
734
735	bridge -j fdb show br $bridge brport $br_port1 \
736		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
737	check_err $? "Did not find FDB record when should"
738
739	$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
740	sleep 1
741
742	tc -j -s filter show dev $host1_if ingress \
743		| jq -e ".[] | select(.options.handle == 101) \
744		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
745	check_err $? "Packet did not reach second host when should"
746
747	# Wait for 10 seconds after the ageing time to make sure FDB
748	# record was aged-out.
749	ageing_time=$(bridge_ageing_time_get $bridge)
750	sleep $((ageing_time + 10))
751
752	bridge -j fdb show br $bridge brport $br_port1 \
753		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
754	check_fail $? "Found FDB record when should not"
755
756	bridge link set dev $br_port1 learning off
757
758	$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
759	sleep 1
760
761	bridge -j fdb show br $bridge brport $br_port1 \
762		| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
763	check_fail $? "Found FDB record when should not"
764
765	bridge link set dev $br_port1 learning on
766
767	tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
768	tc qdisc del dev $host1_if ingress
769
770	bridge link set dev $br_port1 flood on
771
772	log_test "FDB learning"
773}
774
775flood_test_do()
776{
777	local should_flood=$1
778	local mac=$2
779	local ip=$3
780	local host1_if=$4
781	local host2_if=$5
782	local err=0
783
784	# Add an ACL on `host2_if` which will tell us whether the packet
785	# was flooded to it or not.
786	tc qdisc add dev $host2_if ingress
787	tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
788		flower dst_mac $mac action drop
789
790	$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
791	sleep 1
792
793	tc -j -s filter show dev $host2_if ingress \
794		| jq -e ".[] | select(.options.handle == 101) \
795		| select(.options.actions[0].stats.packets == 1)" &> /dev/null
796	if [[ $? -ne 0 && $should_flood == "true" || \
797	      $? -eq 0 && $should_flood == "false" ]]; then
798		err=1
799	fi
800
801	tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
802	tc qdisc del dev $host2_if ingress
803
804	return $err
805}
806
807flood_unicast_test()
808{
809	local br_port=$1
810	local host1_if=$2
811	local host2_if=$3
812	local mac=de:ad:be:ef:13:37
813	local ip=192.0.2.100
814
815	RET=0
816
817	bridge link set dev $br_port flood off
818
819	flood_test_do false $mac $ip $host1_if $host2_if
820	check_err $? "Packet flooded when should not"
821
822	bridge link set dev $br_port flood on
823
824	flood_test_do true $mac $ip $host1_if $host2_if
825	check_err $? "Packet was not flooded when should"
826
827	log_test "Unknown unicast flood"
828}
829
830flood_multicast_test()
831{
832	local br_port=$1
833	local host1_if=$2
834	local host2_if=$3
835	local mac=01:00:5e:00:00:01
836	local ip=239.0.0.1
837
838	RET=0
839
840	bridge link set dev $br_port mcast_flood off
841
842	flood_test_do false $mac $ip $host1_if $host2_if
843	check_err $? "Packet flooded when should not"
844
845	bridge link set dev $br_port mcast_flood on
846
847	flood_test_do true $mac $ip $host1_if $host2_if
848	check_err $? "Packet was not flooded when should"
849
850	log_test "Unregistered multicast flood"
851}
852
853flood_test()
854{
855	# `br_port` is connected to `host2_if`
856	local br_port=$1
857	local host1_if=$2
858	local host2_if=$3
859
860	flood_unicast_test $br_port $host1_if $host2_if
861	flood_multicast_test $br_port $host1_if $host2_if
862}
863