1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# This test is for checking IPv4 and IPv6 FIB rules API
5
6# Kselftest framework requirement - SKIP code is 4.
7ksft_skip=4
8
9ret=0
10
11PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
12IP="ip -netns testns"
13
14RTABLE=100
15GW_IP4=192.51.100.2
16SRC_IP=192.51.100.3
17GW_IP6=2001:db8:1::2
18SRC_IP6=2001:db8:1::3
19
20DEV_ADDR=192.51.100.1
21DEV_ADDR6=2001:db8:1::1
22DEV=dummy0
23TESTS="fib_rule6 fib_rule4"
24
25log_test()
26{
27	local rc=$1
28	local expected=$2
29	local msg="$3"
30
31	if [ ${rc} -eq ${expected} ]; then
32		nsuccess=$((nsuccess+1))
33		printf "\n    TEST: %-50s  [ OK ]\n" "${msg}"
34	else
35		ret=1
36		nfail=$((nfail+1))
37		printf "\n    TEST: %-50s  [FAIL]\n" "${msg}"
38		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
39			echo
40			echo "hit enter to continue, 'q' to quit"
41			read a
42			[ "$a" = "q" ] && exit 1
43		fi
44	fi
45}
46
47log_section()
48{
49	echo
50	echo "######################################################################"
51	echo "TEST SECTION: $*"
52	echo "######################################################################"
53}
54
55setup()
56{
57	set -e
58	ip netns add testns
59	$IP link set dev lo up
60
61	$IP link add dummy0 type dummy
62	$IP link set dev dummy0 up
63	$IP address add $DEV_ADDR/24 dev dummy0
64	$IP -6 address add $DEV_ADDR6/64 dev dummy0
65
66	set +e
67}
68
69cleanup()
70{
71	$IP link del dev dummy0 &> /dev/null
72	ip netns del testns
73}
74
75fib_check_iproute_support()
76{
77	ip rule help 2>&1 | grep -q $1
78	if [ $? -ne 0 ]; then
79		echo "SKIP: iproute2 iprule too old, missing $1 match"
80		return 1
81	fi
82
83	ip route get help 2>&1 | grep -q $2
84	if [ $? -ne 0 ]; then
85		echo "SKIP: iproute2 get route too old, missing $2 match"
86		return 1
87	fi
88
89	return 0
90}
91
92fib_rule6_del()
93{
94	$IP -6 rule del $1
95	log_test $? 0 "rule6 del $1"
96}
97
98fib_rule6_del_by_pref()
99{
100	pref=$($IP -6 rule show $1 table $RTABLE | cut -d ":" -f 1)
101	$IP -6 rule del pref $pref
102}
103
104fib_rule6_test_match_n_redirect()
105{
106	local match="$1"
107	local getmatch="$2"
108	local description="$3"
109
110	$IP -6 rule add $match table $RTABLE
111	$IP -6 route get $GW_IP6 $getmatch | grep -q "table $RTABLE"
112	log_test $? 0 "rule6 check: $description"
113
114	fib_rule6_del_by_pref "$match"
115	log_test $? 0 "rule6 del by pref: $description"
116}
117
118fib_rule6_test_reject()
119{
120	local match="$1"
121	local rc
122
123	$IP -6 rule add $match table $RTABLE 2>/dev/null
124	rc=$?
125	log_test $rc 2 "rule6 check: $match"
126
127	if [ $rc -eq 0 ]; then
128		$IP -6 rule del $match table $RTABLE
129	fi
130}
131
132fib_rule6_test()
133{
134	local getmatch
135	local match
136	local cnt
137
138	# setup the fib rule redirect route
139	$IP -6 route add table $RTABLE default via $GW_IP6 dev $DEV onlink
140
141	match="oif $DEV"
142	fib_rule6_test_match_n_redirect "$match" "$match" "oif redirect to table"
143
144	match="from $SRC_IP6 iif $DEV"
145	fib_rule6_test_match_n_redirect "$match" "$match" "iif redirect to table"
146
147	# Reject dsfield (tos) options which have ECN bits set
148	for cnt in $(seq 1 3); do
149		match="dsfield $cnt"
150		fib_rule6_test_reject "$match"
151	done
152
153	# Don't take ECN bits into account when matching on dsfield
154	match="tos 0x10"
155	for cnt in "0x10" "0x11" "0x12" "0x13"; do
156		# Using option 'tos' instead of 'dsfield' as old iproute2
157		# versions don't support 'dsfield' in ip rule show.
158		getmatch="tos $cnt"
159		fib_rule6_test_match_n_redirect "$match" "$getmatch" \
160						"$getmatch redirect to table"
161	done
162
163	match="fwmark 0x64"
164	getmatch="mark 0x64"
165	fib_rule6_test_match_n_redirect "$match" "$getmatch" "fwmark redirect to table"
166
167	fib_check_iproute_support "uidrange" "uid"
168	if [ $? -eq 0 ]; then
169		match="uidrange 100-100"
170		getmatch="uid 100"
171		fib_rule6_test_match_n_redirect "$match" "$getmatch" "uid redirect to table"
172	fi
173
174	fib_check_iproute_support "sport" "sport"
175	if [ $? -eq 0 ]; then
176		match="sport 666 dport 777"
177		fib_rule6_test_match_n_redirect "$match" "$match" "sport and dport redirect to table"
178	fi
179
180	fib_check_iproute_support "ipproto" "ipproto"
181	if [ $? -eq 0 ]; then
182		match="ipproto tcp"
183		fib_rule6_test_match_n_redirect "$match" "$match" "ipproto match"
184	fi
185
186	fib_check_iproute_support "ipproto" "ipproto"
187	if [ $? -eq 0 ]; then
188		match="ipproto ipv6-icmp"
189		fib_rule6_test_match_n_redirect "$match" "$match" "ipproto ipv6-icmp match"
190	fi
191}
192
193fib_rule4_del()
194{
195	$IP rule del $1
196	log_test $? 0 "del $1"
197}
198
199fib_rule4_del_by_pref()
200{
201	pref=$($IP rule show $1 table $RTABLE | cut -d ":" -f 1)
202	$IP rule del pref $pref
203}
204
205fib_rule4_test_match_n_redirect()
206{
207	local match="$1"
208	local getmatch="$2"
209	local description="$3"
210
211	$IP rule add $match table $RTABLE
212	$IP route get $GW_IP4 $getmatch | grep -q "table $RTABLE"
213	log_test $? 0 "rule4 check: $description"
214
215	fib_rule4_del_by_pref "$match"
216	log_test $? 0 "rule4 del by pref: $description"
217}
218
219fib_rule4_test_reject()
220{
221	local match="$1"
222	local rc
223
224	$IP rule add $match table $RTABLE 2>/dev/null
225	rc=$?
226	log_test $rc 2 "rule4 check: $match"
227
228	if [ $rc -eq 0 ]; then
229		$IP rule del $match table $RTABLE
230	fi
231}
232
233fib_rule4_test()
234{
235	local getmatch
236	local match
237	local cnt
238
239	# setup the fib rule redirect route
240	$IP route add table $RTABLE default via $GW_IP4 dev $DEV onlink
241
242	match="oif $DEV"
243	fib_rule4_test_match_n_redirect "$match" "$match" "oif redirect to table"
244
245	# need enable forwarding and disable rp_filter temporarily as all the
246	# addresses are in the same subnet and egress device == ingress device.
247	ip netns exec testns sysctl -qw net.ipv4.ip_forward=1
248	ip netns exec testns sysctl -qw net.ipv4.conf.$DEV.rp_filter=0
249	match="from $SRC_IP iif $DEV"
250	fib_rule4_test_match_n_redirect "$match" "$match" "iif redirect to table"
251	ip netns exec testns sysctl -qw net.ipv4.ip_forward=0
252
253	# Reject dsfield (tos) options which have ECN bits set
254	for cnt in $(seq 1 3); do
255		match="dsfield $cnt"
256		fib_rule4_test_reject "$match"
257	done
258
259	# Don't take ECN bits into account when matching on dsfield
260	match="tos 0x10"
261	for cnt in "0x10" "0x11" "0x12" "0x13"; do
262		# Using option 'tos' instead of 'dsfield' as old iproute2
263		# versions don't support 'dsfield' in ip rule show.
264		getmatch="tos $cnt"
265		fib_rule4_test_match_n_redirect "$match" "$getmatch" \
266						"$getmatch redirect to table"
267	done
268
269	match="fwmark 0x64"
270	getmatch="mark 0x64"
271	fib_rule4_test_match_n_redirect "$match" "$getmatch" "fwmark redirect to table"
272
273	fib_check_iproute_support "uidrange" "uid"
274	if [ $? -eq 0 ]; then
275		match="uidrange 100-100"
276		getmatch="uid 100"
277		fib_rule4_test_match_n_redirect "$match" "$getmatch" "uid redirect to table"
278	fi
279
280	fib_check_iproute_support "sport" "sport"
281	if [ $? -eq 0 ]; then
282		match="sport 666 dport 777"
283		fib_rule4_test_match_n_redirect "$match" "$match" "sport and dport redirect to table"
284	fi
285
286	fib_check_iproute_support "ipproto" "ipproto"
287	if [ $? -eq 0 ]; then
288		match="ipproto tcp"
289		fib_rule4_test_match_n_redirect "$match" "$match" "ipproto tcp match"
290	fi
291
292	fib_check_iproute_support "ipproto" "ipproto"
293	if [ $? -eq 0 ]; then
294		match="ipproto icmp"
295		fib_rule4_test_match_n_redirect "$match" "$match" "ipproto icmp match"
296	fi
297}
298
299run_fibrule_tests()
300{
301	log_section "IPv4 fib rule"
302	fib_rule4_test
303	log_section "IPv6 fib rule"
304	fib_rule6_test
305}
306
307if [ "$(id -u)" -ne 0 ];then
308	echo "SKIP: Need root privileges"
309	exit $ksft_skip
310fi
311
312if [ ! -x "$(command -v ip)" ]; then
313	echo "SKIP: Could not run test without ip tool"
314	exit $ksft_skip
315fi
316
317# start clean
318cleanup &> /dev/null
319setup
320for t in $TESTS
321do
322	case $t in
323	fib_rule6_test|fib_rule6)		fib_rule6_test;;
324	fib_rule4_test|fib_rule4)		fib_rule4_test;;
325
326	help) echo "Test names: $TESTS"; exit 0;;
327
328	esac
329done
330cleanup
331
332if [ "$TESTS" != "none" ]; then
333	printf "\nTests passed: %3d\n" ${nsuccess}
334	printf "Tests failed: %3d\n"   ${nfail}
335fi
336
337exit $ret
338