xref: /linux/tools/testing/selftests/net/fib_rule_tests.sh (revision 9410645520e9b820069761f3450ef6661418e279)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# This test is for checking IPv4 and IPv6 FIB rules API
5
6source lib.sh
7ret=0
8PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
9
10RTABLE=100
11RTABLE_PEER=101
12RTABLE_VRF=102
13GW_IP4=192.51.100.2
14SRC_IP=192.51.100.3
15GW_IP6=2001:db8:1::2
16SRC_IP6=2001:db8:1::3
17
18DEV_ADDR=192.51.100.1
19DEV_ADDR6=2001:db8:1::1
20DEV=dummy0
21TESTS="
22	fib_rule6
23	fib_rule4
24	fib_rule6_connect
25	fib_rule4_connect
26	fib_rule6_vrf
27	fib_rule4_vrf
28"
29
30SELFTEST_PATH=""
31
32log_test()
33{
34	local rc=$1
35	local expected=$2
36	local msg="$3"
37
38	if [ ${rc} -eq ${expected} ]; then
39		nsuccess=$((nsuccess+1))
40		printf "    TEST: %-60s  [ OK ]\n" "${msg}"
41	else
42		ret=1
43		nfail=$((nfail+1))
44		printf "    TEST: %-60s  [FAIL]\n" "${msg}"
45		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
46			echo
47			echo "hit enter to continue, 'q' to quit"
48			read a
49			[ "$a" = "q" ] && exit 1
50		fi
51	fi
52}
53
54setup()
55{
56	set -e
57	setup_ns testns
58	IP="ip -netns $testns"
59
60	$IP link add dummy0 type dummy
61	$IP link set dev dummy0 up
62	$IP address add $DEV_ADDR/24 dev dummy0
63	$IP -6 address add $DEV_ADDR6/64 dev dummy0
64
65	set +e
66}
67
68cleanup()
69{
70	$IP link del dev dummy0 &> /dev/null
71	cleanup_ns $testns
72}
73
74setup_peer()
75{
76	set -e
77
78	setup_ns peerns
79	IP_PEER="ip -netns $peerns"
80	$IP_PEER link set dev lo up
81
82	ip link add name veth0 netns $testns type veth \
83		peer name veth1 netns $peerns
84	$IP link set dev veth0 up
85	$IP_PEER link set dev veth1 up
86
87	$IP address add 192.0.2.10 peer 192.0.2.11/32 dev veth0
88	$IP_PEER address add 192.0.2.11 peer 192.0.2.10/32 dev veth1
89
90	$IP address add 2001:db8::10 peer 2001:db8::11/128 dev veth0 nodad
91	$IP_PEER address add 2001:db8::11 peer 2001:db8::10/128 dev veth1 nodad
92
93	$IP_PEER address add 198.51.100.11/32 dev lo
94	$IP route add table $RTABLE_PEER 198.51.100.11/32 via 192.0.2.11
95
96	$IP_PEER address add 2001:db8::1:11/128 dev lo
97	$IP route add table $RTABLE_PEER 2001:db8::1:11/128 via 2001:db8::11
98
99	set +e
100}
101
102cleanup_peer()
103{
104	$IP link del dev veth0
105	ip netns del $peerns
106}
107
108setup_vrf()
109{
110	$IP link add name vrf0 up type vrf table $RTABLE_VRF
111	$IP link set dev $DEV master vrf0
112}
113
114cleanup_vrf()
115{
116	$IP link del dev vrf0
117}
118
119fib_check_iproute_support()
120{
121	ip rule help 2>&1 | grep -q $1
122	if [ $? -ne 0 ]; then
123		echo "SKIP: iproute2 iprule too old, missing $1 match"
124		return 1
125	fi
126
127	ip route get help 2>&1 | grep -q $2
128	if [ $? -ne 0 ]; then
129		echo "SKIP: iproute2 get route too old, missing $2 match"
130		return 1
131	fi
132
133	return 0
134}
135
136fib_rule6_del()
137{
138	$IP -6 rule del $1
139	log_test $? 0 "rule6 del $1"
140}
141
142fib_rule6_del_by_pref()
143{
144	pref=$($IP -6 rule show $1 table $RTABLE | cut -d ":" -f 1)
145	$IP -6 rule del pref $pref
146}
147
148fib_rule6_test_match_n_redirect()
149{
150	local match="$1"
151	local getmatch="$2"
152	local getnomatch="$3"
153	local description="$4"
154	local nomatch_description="$5"
155
156	$IP -6 rule add $match table $RTABLE
157	$IP -6 route get $GW_IP6 $getmatch | grep -q "table $RTABLE"
158	log_test $? 0 "rule6 check: $description"
159
160	$IP -6 route get $GW_IP6 $getnomatch 2>&1 | grep -q "table $RTABLE"
161	log_test $? 1 "rule6 check: $nomatch_description"
162
163	fib_rule6_del_by_pref "$match"
164	log_test $? 0 "rule6 del by pref: $description"
165}
166
167fib_rule6_test_reject()
168{
169	local match="$1"
170	local rc
171
172	$IP -6 rule add $match table $RTABLE 2>/dev/null
173	rc=$?
174	log_test $rc 2 "rule6 check: $match"
175
176	if [ $rc -eq 0 ]; then
177		$IP -6 rule del $match table $RTABLE
178	fi
179}
180
181fib_rule6_test()
182{
183	local ext_name=$1; shift
184	local getnomatch
185	local getmatch
186	local match
187	local cnt
188
189	echo
190	echo "IPv6 FIB rule tests $ext_name"
191
192	# setup the fib rule redirect route
193	$IP -6 route add table $RTABLE default via $GW_IP6 dev $DEV onlink
194
195	match="oif $DEV"
196	getnomatch="oif lo"
197	fib_rule6_test_match_n_redirect "$match" "$match" "$getnomatch" \
198		"oif redirect to table" "oif no redirect to table"
199
200	match="from $SRC_IP6 iif $DEV"
201	getnomatch="from $SRC_IP6 iif lo"
202	fib_rule6_test_match_n_redirect "$match" "$match" "$getnomatch" \
203		"iif redirect to table" "iif no redirect to table"
204
205	# Reject dsfield (tos) options which have ECN bits set
206	for cnt in $(seq 1 3); do
207		match="dsfield $cnt"
208		fib_rule6_test_reject "$match"
209	done
210
211	# Don't take ECN bits into account when matching on dsfield
212	match="tos 0x10"
213	for cnt in "0x10" "0x11" "0x12" "0x13"; do
214		# Using option 'tos' instead of 'dsfield' as old iproute2
215		# versions don't support 'dsfield' in ip rule show.
216		getmatch="tos $cnt"
217		getnomatch="tos 0x20"
218		fib_rule6_test_match_n_redirect "$match" "$getmatch" \
219			"$getnomatch" "$getmatch redirect to table" \
220			"$getnomatch no redirect to table"
221	done
222
223	# Re-test TOS matching, but with input routes since they are handled
224	# differently from output routes.
225	match="tos 0x10"
226	for cnt in "0x10" "0x11" "0x12" "0x13"; do
227		getmatch="tos $cnt"
228		getnomatch="tos 0x20"
229		fib_rule6_test_match_n_redirect "$match" \
230			"from $SRC_IP6 iif $DEV $getmatch" \
231			"from $SRC_IP6 iif $DEV $getnomatch" \
232			"iif $getmatch redirect to table" \
233			"iif $getnomatch no redirect to table"
234	done
235
236	match="fwmark 0x64"
237	getmatch="mark 0x64"
238	getnomatch="mark 0x63"
239	fib_rule6_test_match_n_redirect "$match" "$getmatch" "$getnomatch" \
240		"fwmark redirect to table" "fwmark no redirect to table"
241
242	fib_check_iproute_support "uidrange" "uid"
243	if [ $? -eq 0 ]; then
244		match="uidrange 100-100"
245		getmatch="uid 100"
246		getnomatch="uid 101"
247		fib_rule6_test_match_n_redirect "$match" "$getmatch" \
248			"$getnomatch" "uid redirect to table" \
249			"uid no redirect to table"
250	fi
251
252	fib_check_iproute_support "sport" "sport"
253	if [ $? -eq 0 ]; then
254		match="sport 666 dport 777"
255		getnomatch="sport 667 dport 778"
256		fib_rule6_test_match_n_redirect "$match" "$match" \
257			"$getnomatch" "sport and dport redirect to table" \
258			"sport and dport no redirect to table"
259	fi
260
261	fib_check_iproute_support "ipproto" "ipproto"
262	if [ $? -eq 0 ]; then
263		match="ipproto tcp"
264		getnomatch="ipproto udp"
265		fib_rule6_test_match_n_redirect "$match" "$match" \
266			"$getnomatch" "ipproto tcp match" "ipproto udp no match"
267	fi
268
269	fib_check_iproute_support "ipproto" "ipproto"
270	if [ $? -eq 0 ]; then
271		match="ipproto ipv6-icmp"
272		getnomatch="ipproto tcp"
273		fib_rule6_test_match_n_redirect "$match" "$match" \
274			"$getnomatch" "ipproto ipv6-icmp match" \
275			"ipproto ipv6-tcp no match"
276	fi
277
278	fib_check_iproute_support "dscp" "tos"
279	if [ $? -eq 0 ]; then
280		match="dscp 0x3f"
281		getmatch="tos 0xfc"
282		getnomatch="tos 0xf4"
283		fib_rule6_test_match_n_redirect "$match" "$getmatch" \
284			"$getnomatch" "dscp redirect to table" \
285			"dscp no redirect to table"
286
287		match="dscp 0x3f"
288		getmatch="from $SRC_IP6 iif $DEV tos 0xfc"
289		getnomatch="from $SRC_IP6 iif $DEV tos 0xf4"
290		fib_rule6_test_match_n_redirect "$match" "$getmatch" \
291			"$getnomatch" "iif dscp redirect to table" \
292			"iif dscp no redirect to table"
293	fi
294}
295
296fib_rule6_vrf_test()
297{
298	setup_vrf
299	fib_rule6_test "- with VRF"
300	cleanup_vrf
301}
302
303# Verify that the IPV6_TCLASS option of UDPv6 and TCPv6 sockets is properly
304# taken into account when connecting the socket and when sending packets.
305fib_rule6_connect_test()
306{
307	local dsfield
308
309	echo
310	echo "IPv6 FIB rule connect tests"
311
312	setup_peer
313	$IP -6 rule add dsfield 0x04 table $RTABLE_PEER
314
315	# Combine the base DS Field value (0x04) with all possible ECN values
316	# (Not-ECT: 0, ECT(1): 1, ECT(0): 2, CE: 3).
317	# The ECN bits shouldn't influence the result of the test.
318	for dsfield in 0x04 0x05 0x06 0x07; do
319		nettest -q -6 -B -t 5 -N $testns -O $peerns -U -D \
320			-Q "${dsfield}" -l 2001:db8::1:11 -r 2001:db8::1:11
321		log_test $? 0 "rule6 dsfield udp connect (dsfield ${dsfield})"
322
323		nettest -q -6 -B -t 5 -N $testns -O $peerns -Q "${dsfield}" \
324			-l 2001:db8::1:11 -r 2001:db8::1:11
325		log_test $? 0 "rule6 dsfield tcp connect (dsfield ${dsfield})"
326	done
327
328	# Check that UDP and TCP connections fail when using a DS Field that
329	# does not match the previously configured FIB rule.
330	nettest -q -6 -B -t 5 -N $testns -O $peerns -U -D \
331		-Q 0x20 -l 2001:db8::1:11 -r 2001:db8::1:11
332	log_test $? 1 "rule6 dsfield udp no connect (dsfield 0x20)"
333
334	nettest -q -6 -B -t 5 -N $testns -O $peerns -Q 0x20 \
335		-l 2001:db8::1:11 -r 2001:db8::1:11
336	log_test $? 1 "rule6 dsfield tcp no connect (dsfield 0x20)"
337
338	$IP -6 rule del dsfield 0x04 table $RTABLE_PEER
339
340	ip rule help 2>&1 | grep -q dscp
341	if [ $? -ne 0 ]; then
342		echo "SKIP: iproute2 iprule too old, missing dscp match"
343		cleanup_peer
344		return
345	fi
346
347	$IP -6 rule add dscp 0x3f table $RTABLE_PEER
348
349	nettest -q -6 -B -t 5 -N $testns -O $peerns -U -D -Q 0xfc \
350		-l 2001:db8::1:11 -r 2001:db8::1:11
351	log_test $? 0 "rule6 dscp udp connect"
352
353	nettest -q -6 -B -t 5 -N $testns -O $peerns -Q 0xfc \
354		-l 2001:db8::1:11 -r 2001:db8::1:11
355	log_test $? 0 "rule6 dscp tcp connect"
356
357	nettest -q -6 -B -t 5 -N $testns -O $peerns -U -D -Q 0xf4 \
358		-l 2001:db8::1:11 -r 2001:db8::1:11
359	log_test $? 1 "rule6 dscp udp no connect"
360
361	nettest -q -6 -B -t 5 -N $testns -O $peerns -Q 0xf4 \
362		-l 2001:db8::1:11 -r 2001:db8::1:11
363	log_test $? 1 "rule6 dscp tcp no connect"
364
365	$IP -6 rule del dscp 0x3f table $RTABLE_PEER
366
367	cleanup_peer
368}
369
370fib_rule4_del()
371{
372	$IP rule del $1
373	log_test $? 0 "del $1"
374}
375
376fib_rule4_del_by_pref()
377{
378	pref=$($IP rule show $1 table $RTABLE | cut -d ":" -f 1)
379	$IP rule del pref $pref
380}
381
382fib_rule4_test_match_n_redirect()
383{
384	local match="$1"
385	local getmatch="$2"
386	local getnomatch="$3"
387	local description="$4"
388	local nomatch_description="$5"
389
390	$IP rule add $match table $RTABLE
391	$IP route get $GW_IP4 $getmatch | grep -q "table $RTABLE"
392	log_test $? 0 "rule4 check: $description"
393
394	$IP route get $GW_IP4 $getnomatch 2>&1 | grep -q "table $RTABLE"
395	log_test $? 1 "rule4 check: $nomatch_description"
396
397	fib_rule4_del_by_pref "$match"
398	log_test $? 0 "rule4 del by pref: $description"
399}
400
401fib_rule4_test_reject()
402{
403	local match="$1"
404	local rc
405
406	$IP rule add $match table $RTABLE 2>/dev/null
407	rc=$?
408	log_test $rc 2 "rule4 check: $match"
409
410	if [ $rc -eq 0 ]; then
411		$IP rule del $match table $RTABLE
412	fi
413}
414
415fib_rule4_test()
416{
417	local ext_name=$1; shift
418	local getnomatch
419	local getmatch
420	local match
421	local cnt
422
423	echo
424	echo "IPv4 FIB rule tests $ext_name"
425
426	# setup the fib rule redirect route
427	$IP route add table $RTABLE default via $GW_IP4 dev $DEV onlink
428
429	match="oif $DEV"
430	getnomatch="oif lo"
431	fib_rule4_test_match_n_redirect "$match" "$match" "$getnomatch" \
432		"oif redirect to table" "oif no redirect to table"
433
434	# Enable forwarding and disable rp_filter as all the addresses are in
435	# the same subnet and egress device == ingress device.
436	ip netns exec $testns sysctl -qw net.ipv4.ip_forward=1
437	ip netns exec $testns sysctl -qw net.ipv4.conf.$DEV.rp_filter=0
438	match="from $SRC_IP iif $DEV"
439	getnomatch="from $SRC_IP iif lo"
440	fib_rule4_test_match_n_redirect "$match" "$match" "$getnomatch" \
441		"iif redirect to table" "iif no redirect to table"
442
443	# Reject dsfield (tos) options which have ECN bits set
444	for cnt in $(seq 1 3); do
445		match="dsfield $cnt"
446		fib_rule4_test_reject "$match"
447	done
448
449	# Don't take ECN bits into account when matching on dsfield
450	match="tos 0x10"
451	for cnt in "0x10" "0x11" "0x12" "0x13"; do
452		# Using option 'tos' instead of 'dsfield' as old iproute2
453		# versions don't support 'dsfield' in ip rule show.
454		getmatch="tos $cnt"
455		getnomatch="tos 0x20"
456		fib_rule4_test_match_n_redirect "$match" "$getmatch" \
457			"$getnomatch" "$getmatch redirect to table" \
458			"$getnomatch no redirect to table"
459	done
460
461	# Re-test TOS matching, but with input routes since they are handled
462	# differently from output routes.
463	match="tos 0x10"
464	for cnt in "0x10" "0x11" "0x12" "0x13"; do
465		getmatch="tos $cnt"
466		getnomatch="tos 0x20"
467		fib_rule4_test_match_n_redirect "$match" \
468			"from $SRC_IP iif $DEV $getmatch" \
469			"from $SRC_IP iif $DEV $getnomatch" \
470			"iif $getmatch redirect to table" \
471			"iif $getnomatch no redirect to table"
472	done
473
474	match="fwmark 0x64"
475	getmatch="mark 0x64"
476	getnomatch="mark 0x63"
477	fib_rule4_test_match_n_redirect "$match" "$getmatch" "$getnomatch" \
478		"fwmark redirect to table" "fwmark no redirect to table"
479
480	fib_check_iproute_support "uidrange" "uid"
481	if [ $? -eq 0 ]; then
482		match="uidrange 100-100"
483		getmatch="uid 100"
484		getnomatch="uid 101"
485		fib_rule4_test_match_n_redirect "$match" "$getmatch" \
486			"$getnomatch" "uid redirect to table" \
487			"uid no redirect to table"
488	fi
489
490	fib_check_iproute_support "sport" "sport"
491	if [ $? -eq 0 ]; then
492		match="sport 666 dport 777"
493		getnomatch="sport 667 dport 778"
494		fib_rule4_test_match_n_redirect "$match" "$match" \
495			"$getnomatch" "sport and dport redirect to table" \
496			"sport and dport no redirect to table"
497	fi
498
499	fib_check_iproute_support "ipproto" "ipproto"
500	if [ $? -eq 0 ]; then
501		match="ipproto tcp"
502		getnomatch="ipproto udp"
503		fib_rule4_test_match_n_redirect "$match" "$match" \
504			"$getnomatch" "ipproto tcp match" \
505			"ipproto udp no match"
506	fi
507
508	fib_check_iproute_support "ipproto" "ipproto"
509	if [ $? -eq 0 ]; then
510		match="ipproto icmp"
511		getnomatch="ipproto tcp"
512		fib_rule4_test_match_n_redirect "$match" "$match" \
513			"$getnomatch" "ipproto icmp match" \
514			"ipproto tcp no match"
515	fi
516
517	fib_check_iproute_support "dscp" "tos"
518	if [ $? -eq 0 ]; then
519		match="dscp 0x3f"
520		getmatch="tos 0xfc"
521		getnomatch="tos 0xf4"
522		fib_rule4_test_match_n_redirect "$match" "$getmatch" \
523			"$getnomatch" "dscp redirect to table" \
524			"dscp no redirect to table"
525
526		match="dscp 0x3f"
527		getmatch="from $SRC_IP iif $DEV tos 0xfc"
528		getnomatch="from $SRC_IP iif $DEV tos 0xf4"
529		fib_rule4_test_match_n_redirect "$match" "$getmatch" \
530			"$getnomatch" "iif dscp redirect to table" \
531			"iif dscp no redirect to table"
532	fi
533}
534
535fib_rule4_vrf_test()
536{
537	setup_vrf
538	fib_rule4_test "- with VRF"
539	cleanup_vrf
540}
541
542# Verify that the IP_TOS option of UDPv4 and TCPv4 sockets is properly taken
543# into account when connecting the socket and when sending packets.
544fib_rule4_connect_test()
545{
546	local dsfield
547
548	echo
549	echo "IPv4 FIB rule connect tests"
550
551	setup_peer
552	$IP -4 rule add dsfield 0x04 table $RTABLE_PEER
553
554	# Combine the base DS Field value (0x04) with all possible ECN values
555	# (Not-ECT: 0, ECT(1): 1, ECT(0): 2, CE: 3).
556	# The ECN bits shouldn't influence the result of the test.
557	for dsfield in 0x04 0x05 0x06 0x07; do
558		nettest -q -B -t 5 -N $testns -O $peerns -D -U -Q "${dsfield}" \
559			-l 198.51.100.11 -r 198.51.100.11
560		log_test $? 0 "rule4 dsfield udp connect (dsfield ${dsfield})"
561
562		nettest -q -B -t 5 -N $testns -O $peerns -Q "${dsfield}" \
563			-l 198.51.100.11 -r 198.51.100.11
564		log_test $? 0 "rule4 dsfield tcp connect (dsfield ${dsfield})"
565	done
566
567	# Check that UDP and TCP connections fail when using a DS Field that
568	# does not match the previously configured FIB rule.
569	nettest -q -B -t 5 -N $testns -O $peerns -D -U -Q 0x20 \
570		-l 198.51.100.11 -r 198.51.100.11
571	log_test $? 1 "rule4 dsfield udp no connect (dsfield 0x20)"
572
573	nettest -q -B -t 5 -N $testns -O $peerns -Q 0x20 \
574		-l 198.51.100.11 -r 198.51.100.11
575	log_test $? 1 "rule4 dsfield tcp no connect (dsfield 0x20)"
576
577	$IP -4 rule del dsfield 0x04 table $RTABLE_PEER
578
579	ip rule help 2>&1 | grep -q dscp
580	if [ $? -ne 0 ]; then
581		echo "SKIP: iproute2 iprule too old, missing dscp match"
582		cleanup_peer
583		return
584	fi
585
586	$IP -4 rule add dscp 0x3f table $RTABLE_PEER
587
588	nettest -q -B -t 5 -N $testns -O $peerns -D -U -Q 0xfc \
589		-l 198.51.100.11 -r 198.51.100.11
590	log_test $? 0 "rule4 dscp udp connect"
591
592	nettest -q -B -t 5 -N $testns -O $peerns -Q 0xfc \
593		-l 198.51.100.11 -r 198.51.100.11
594	log_test $? 0 "rule4 dscp tcp connect"
595
596	nettest -q -B -t 5 -N $testns -O $peerns -D -U -Q 0xf4 \
597		-l 198.51.100.11 -r 198.51.100.11
598	log_test $? 1 "rule4 dscp udp no connect"
599
600	nettest -q -B -t 5 -N $testns -O $peerns -Q 0xf4 \
601		-l 198.51.100.11 -r 198.51.100.11
602	log_test $? 1 "rule4 dscp tcp no connect"
603
604	$IP -4 rule del dscp 0x3f table $RTABLE_PEER
605
606	cleanup_peer
607}
608################################################################################
609# usage
610
611usage()
612{
613	cat <<EOF
614usage: ${0##*/} OPTS
615
616        -t <test>   Test(s) to run (default: all)
617                    (options: $TESTS)
618EOF
619}
620
621################################################################################
622# main
623
624while getopts ":t:h" opt; do
625	case $opt in
626		t) TESTS=$OPTARG;;
627		h) usage; exit 0;;
628		*) usage; exit 1;;
629	esac
630done
631
632if [ "$(id -u)" -ne 0 ];then
633	echo "SKIP: Need root privileges"
634	exit $ksft_skip
635fi
636
637if [ ! -x "$(command -v ip)" ]; then
638	echo "SKIP: Could not run test without ip tool"
639	exit $ksft_skip
640fi
641
642check_gen_prog "nettest"
643
644# start clean
645cleanup &> /dev/null
646setup
647for t in $TESTS
648do
649	case $t in
650	fib_rule6_test|fib_rule6)		fib_rule6_test;;
651	fib_rule4_test|fib_rule4)		fib_rule4_test;;
652	fib_rule6_connect_test|fib_rule6_connect)	fib_rule6_connect_test;;
653	fib_rule4_connect_test|fib_rule4_connect)	fib_rule4_connect_test;;
654	fib_rule6_vrf_test|fib_rule6_vrf)	fib_rule6_vrf_test;;
655	fib_rule4_vrf_test|fib_rule4_vrf)	fib_rule4_vrf_test;;
656
657	help) echo "Test names: $TESTS"; exit 0;;
658
659	esac
660done
661cleanup
662
663if [ "$TESTS" != "none" ]; then
664	printf "\nTests passed: %3d\n" ${nsuccess}
665	printf "Tests failed: %3d\n"   ${nfail}
666fi
667
668exit $ret
669