1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Check xfrm policy resolution.  Topology:
5#
6# 1.2   1.1   3.1  3.10    2.1   2.2
7# eth1  eth1 veth0 veth0 eth1   eth1
8# ns1 ---- ns3 ----- ns4 ---- ns2
9#
10# ns3 and ns4 are connected via ipsec tunnel.
11# pings from ns1 to ns2 (and vice versa) are supposed to work like this:
12# ns1: ping 10.0.2.2: passes via ipsec tunnel.
13# ns2: ping 10.0.1.2: passes via ipsec tunnel.
14
15# ns1: ping 10.0.1.253: passes via ipsec tunnel (direct policy)
16# ns2: ping 10.0.2.253: passes via ipsec tunnel (direct policy)
17#
18# ns1: ping 10.0.2.254: does NOT pass via ipsec tunnel (exception)
19# ns2: ping 10.0.1.254: does NOT pass via ipsec tunnel (exception)
20
21source lib.sh
22ret=0
23policy_checks_ok=1
24
25KEY_SHA=0xdeadbeef1234567890abcdefabcdefabcdefabcd
26KEY_AES=0x0123456789abcdef0123456789012345
27SPI1=0x1
28SPI2=0x2
29
30do_esp_policy() {
31    local ns=$1
32    local me=$2
33    local remote=$3
34    local lnet=$4
35    local rnet=$5
36
37    # to encrypt packets as they go out (includes forwarded packets that need encapsulation)
38    ip -net $ns xfrm policy add src $lnet dst $rnet dir out tmpl src $me dst $remote proto esp mode tunnel priority 100 action allow
39    # to fwd decrypted packets after esp processing:
40    ip -net $ns xfrm policy add src $rnet dst $lnet dir fwd tmpl src $remote dst $me proto esp mode tunnel priority 100 action allow
41}
42
43do_esp() {
44    local ns=$1
45    local me=$2
46    local remote=$3
47    local lnet=$4
48    local rnet=$5
49    local spi_out=$6
50    local spi_in=$7
51
52    ip -net $ns xfrm state add src $remote dst $me proto esp spi $spi_in  enc aes $KEY_AES  auth sha1 $KEY_SHA  mode tunnel sel src $rnet dst $lnet
53    ip -net $ns xfrm state add src $me  dst $remote proto esp spi $spi_out enc aes $KEY_AES auth sha1 $KEY_SHA mode tunnel sel src $lnet dst $rnet
54
55    do_esp_policy $ns $me $remote $lnet $rnet
56}
57
58# add policies with different netmasks, to make sure kernel carries
59# the policies contained within new netmask over when search tree is
60# re-built.
61# peer netns that are supposed to be encapsulated via esp have addresses
62# in the 10.0.1.0/24 and 10.0.2.0/24 subnets, respectively.
63#
64# Adding a policy for '10.0.1.0/23' will make it necessary to
65# alter the prefix of 10.0.1.0 subnet.
66# In case new prefix overlaps with existing node, the node and all
67# policies it carries need to be merged with the existing one(s).
68#
69# Do that here.
70do_overlap()
71{
72    local ns=$1
73
74    # adds new nodes to tree (neither network exists yet in policy database).
75    ip -net $ns xfrm policy add src 10.1.0.0/24 dst 10.0.0.0/24 dir fwd priority 200 action block
76
77    # adds a new node in the 10.0.0.0/24 tree (dst node exists).
78    ip -net $ns xfrm policy add src 10.2.0.0/24 dst 10.0.0.0/24 dir fwd priority 200 action block
79
80    # adds a 10.2.0.0/23 node, but for different dst.
81    ip -net $ns xfrm policy add src 10.2.0.0/23 dst 10.0.1.0/24 dir fwd priority 200 action block
82
83    # dst now overlaps with the 10.0.1.0/24 ESP policy in fwd.
84    # kernel must 'promote' existing one (10.0.0.0/24) to 10.0.0.0/23.
85    # But 10.0.0.0/23 also includes existing 10.0.1.0/24, so that node
86    # also has to be merged too, including source-sorted subtrees.
87    # old:
88    # 10.0.0.0/24 (node 1 in dst tree of the bin)
89    #    10.1.0.0/24 (node in src tree of dst node 1)
90    #    10.2.0.0/24 (node in src tree of dst node 1)
91    # 10.0.1.0/24 (node 2 in dst tree of the bin)
92    #    10.0.2.0/24 (node in src tree of dst node 2)
93    #    10.2.0.0/24 (node in src tree of dst node 2)
94    #
95    # The next 'policy add' adds dst '10.0.0.0/23', which means
96    # that dst node 1 and dst node 2 have to be merged including
97    # the sub-tree.  As no duplicates are allowed, policies in
98    # the two '10.0.2.0/24' are also merged.
99    #
100    # after the 'add', internal search tree should look like this:
101    # 10.0.0.0/23 (node in dst tree of bin)
102    #     10.0.2.0/24 (node in src tree of dst node)
103    #     10.1.0.0/24 (node in src tree of dst node)
104    #     10.2.0.0/24 (node in src tree of dst node)
105    #
106    # 10.0.0.0/24 and 10.0.1.0/24 nodes have been merged as 10.0.0.0/23.
107    ip -net $ns xfrm policy add src 10.1.0.0/24 dst 10.0.0.0/23 dir fwd priority 200 action block
108
109    # similar to above: add policies (with partially random address), with shrinking prefixes.
110    for p in 29 28 27;do
111      for k in $(seq 1 32); do
112       ip -net $ns xfrm policy add src 10.253.1.$((RANDOM%255))/$p dst 10.254.1.$((RANDOM%255))/$p dir fwd priority $((200+k)) action block 2>/dev/null
113      done
114    done
115}
116
117do_esp_policy_get_check() {
118    local ns=$1
119    local lnet=$2
120    local rnet=$3
121
122    ip -net $ns xfrm policy get src $lnet dst $rnet dir out > /dev/null
123    if [ $? -ne 0 ] && [ $policy_checks_ok -eq 1 ] ;then
124        policy_checks_ok=0
125        echo "FAIL: ip -net $ns xfrm policy get src $lnet dst $rnet dir out"
126        ret=1
127    fi
128
129    ip -net $ns xfrm policy get src $rnet dst $lnet dir fwd > /dev/null
130    if [ $? -ne 0 ] && [ $policy_checks_ok -eq 1 ] ;then
131        policy_checks_ok=0
132        echo "FAIL: ip -net $ns xfrm policy get src $rnet dst $lnet dir fwd"
133        ret=1
134    fi
135}
136
137do_exception() {
138    local ns=$1
139    local me=$2
140    local remote=$3
141    local encryptip=$4
142    local plain=$5
143
144    # network $plain passes without tunnel
145    ip -net $ns xfrm policy add dst $plain dir out priority 10 action allow
146
147    # direct policy for $encryptip, use tunnel, higher prio takes precedence
148    ip -net $ns xfrm policy add dst $encryptip dir out tmpl src $me dst $remote proto esp mode tunnel priority 1 action allow
149}
150
151# policies that are not supposed to match any packets generated in this test.
152do_dummies4() {
153    local ns=$1
154
155    for i in $(seq 10 16);do
156      # dummy policy with wildcard src/dst.
157      echo netns exec $ns ip xfrm policy add src 0.0.0.0/0 dst 10.$i.99.0/30 dir out action block
158      echo netns exec $ns ip xfrm policy add src 10.$i.99.0/30 dst 0.0.0.0/0 dir out action block
159      for j in $(seq 32 64);do
160        echo netns exec $ns ip xfrm policy add src 10.$i.1.0/30 dst 10.$i.$j.0/30 dir out action block
161        # silly, as it encompasses the one above too, but its allowed:
162        echo netns exec $ns ip xfrm policy add src 10.$i.1.0/29 dst 10.$i.$j.0/29 dir out action block
163        # and yet again, even more broad one.
164        echo netns exec $ns ip xfrm policy add src 10.$i.1.0/24 dst 10.$i.$j.0/24 dir out action block
165        echo netns exec $ns ip xfrm policy add src 10.$i.$j.0/24 dst 10.$i.1.0/24 dir fwd action block
166      done
167    done | ip -batch /dev/stdin
168}
169
170do_dummies6() {
171    local ns=$1
172
173    for i in $(seq 10 16);do
174      for j in $(seq 32 64);do
175       echo netns exec $ns ip xfrm policy add src dead:$i::/64 dst dead:$i:$j::/64 dir out action block
176       echo netns exec $ns ip xfrm policy add src dead:$i:$j::/64 dst dead:$i::/24 dir fwd action block
177      done
178    done | ip -batch /dev/stdin
179}
180
181check_ipt_policy_count()
182{
183	ns=$1
184
185	ip netns exec $ns iptables-save -c |grep policy | ( read c rest
186		ip netns exec $ns iptables -Z
187		if [ x"$c" = x'[0:0]' ]; then
188			exit 0
189		elif [ x"$c" = x ]; then
190			echo "ERROR: No counters"
191			ret=1
192			exit 111
193		else
194			exit 1
195		fi
196	)
197}
198
199check_xfrm() {
200	# 0: iptables -m policy rule count == 0
201	# 1: iptables -m policy rule count != 0
202	rval=$1
203	ip=$2
204	local lret=0
205
206	ip netns exec ${ns[1]} ping -q -c 1 10.0.2.$ip > /dev/null
207
208	check_ipt_policy_count ${ns[3]}
209	if [ $? -ne $rval ] ; then
210		lret=1
211	fi
212	check_ipt_policy_count ${ns[4]}
213	if [ $? -ne $rval ] ; then
214		lret=1
215	fi
216
217	ip netns exec ${ns[2]} ping -q -c 1 10.0.1.$ip > /dev/null
218
219	check_ipt_policy_count ${ns[3]}
220	if [ $? -ne $rval ] ; then
221		lret=1
222	fi
223	check_ipt_policy_count ${ns[4]}
224	if [ $? -ne $rval ] ; then
225		lret=1
226	fi
227
228	return $lret
229}
230
231check_exceptions()
232{
233	logpostfix="$1"
234	local lret=0
235
236	# ping to .254 should be excluded from the tunnel (exception is in place).
237	check_xfrm 0 254
238	if [ $? -ne 0 ]; then
239		echo "FAIL: expected ping to .254 to fail ($logpostfix)"
240		lret=1
241	else
242		echo "PASS: ping to .254 bypassed ipsec tunnel ($logpostfix)"
243	fi
244
245	# ping to .253 should use use ipsec due to direct policy exception.
246	check_xfrm 1 253
247	if [ $? -ne 0 ]; then
248		echo "FAIL: expected ping to .253 to use ipsec tunnel ($logpostfix)"
249		lret=1
250	else
251		echo "PASS: direct policy matches ($logpostfix)"
252	fi
253
254	# ping to .2 should use ipsec.
255	check_xfrm 1 2
256	if [ $? -ne 0 ]; then
257		echo "FAIL: expected ping to .2 to use ipsec tunnel ($logpostfix)"
258		lret=1
259	else
260		echo "PASS: policy matches ($logpostfix)"
261	fi
262
263	return $lret
264}
265
266check_hthresh_repeat()
267{
268	local log=$1
269	i=0
270
271	for i in $(seq 1 10);do
272		ip -net ${ns[1]} xfrm policy update src e000:0001::0000 dst ff01::0014:0000:0001 dir in tmpl src :: dst :: proto esp mode tunnel priority 100 action allow || break
273		ip -net ${ns[1]} xfrm policy set hthresh6 0 28 || break
274
275		ip -net ${ns[1]} xfrm policy update src e000:0001::0000 dst ff01::01 dir in tmpl src :: dst :: proto esp mode tunnel priority 100 action allow || break
276		ip -net ${ns[1]} xfrm policy set hthresh6 0 28 || break
277	done
278
279	if [ $i -ne 10 ] ;then
280		echo "FAIL: $log" 1>&2
281		ret=1
282		return 1
283	fi
284
285	echo "PASS: $log"
286	return 0
287}
288
289# insert non-overlapping policies in a random order and check that
290# all of them can be fetched using the traffic selectors.
291check_random_order()
292{
293	local ns=$1
294	local log=$2
295
296	for i in $(seq 100); do
297		ip -net $ns xfrm policy flush
298		for j in $(seq 0 16 255 | sort -R); do
299			ip -net $ns xfrm policy add dst $j.0.0.0/24 dir out priority 10 action allow
300		done
301		for j in $(seq 0 16 255); do
302			if ! ip -net $ns xfrm policy get dst $j.0.0.0/24 dir out > /dev/null; then
303				echo "FAIL: $log" 1>&2
304				return 1
305			fi
306		done
307	done
308
309	for i in $(seq 100); do
310		ip -net $ns xfrm policy flush
311		for j in $(seq 0 16 255 | sort -R); do
312			local addr=$(printf "e000:0000:%02x00::/56" $j)
313			ip -net $ns xfrm policy add dst $addr dir out priority 10 action allow
314		done
315		for j in $(seq 0 16 255); do
316			local addr=$(printf "e000:0000:%02x00::/56" $j)
317			if ! ip -net $ns xfrm policy get dst $addr dir out > /dev/null; then
318				echo "FAIL: $log" 1>&2
319				return 1
320			fi
321		done
322	done
323
324	ip -net $ns xfrm policy flush
325
326	echo "PASS: $log"
327	return 0
328}
329
330#check for needed privileges
331if [ "$(id -u)" -ne 0 ];then
332	echo "SKIP: Need root privileges"
333	exit $ksft_skip
334fi
335
336ip -Version 2>/dev/null >/dev/null
337if [ $? -ne 0 ];then
338	echo "SKIP: Could not run test without the ip tool"
339	exit $ksft_skip
340fi
341
342# needed to check if policy lookup got valid ipsec result
343iptables --version 2>/dev/null >/dev/null
344if [ $? -ne 0 ];then
345	echo "SKIP: Could not run test without iptables tool"
346	exit $ksft_skip
347fi
348
349setup_ns ns1 ns2 ns3 ns4
350ns[1]=$ns1
351ns[2]=$ns2
352ns[3]=$ns3
353ns[4]=$ns4
354
355DEV=veth0
356ip link add $DEV netns ${ns[1]} type veth peer name eth1 netns ${ns[3]}
357ip link add $DEV netns ${ns[2]} type veth peer name eth1 netns ${ns[4]}
358
359ip link add $DEV netns ${ns[3]} type veth peer name veth0 netns ${ns[4]}
360
361DEV=veth0
362for i in 1 2; do
363    ip -net ${ns[$i]} link set $DEV up
364    ip -net ${ns[$i]} addr add 10.0.$i.2/24 dev $DEV
365    ip -net ${ns[$i]} addr add dead:$i::2/64 dev $DEV
366
367    ip -net ${ns[$i]} addr add 10.0.$i.253 dev $DEV
368    ip -net ${ns[$i]} addr add 10.0.$i.254 dev $DEV
369    ip -net ${ns[$i]} addr add dead:$i::fd dev $DEV
370    ip -net ${ns[$i]} addr add dead:$i::fe dev $DEV
371done
372
373for i in 3 4; do
374    ip -net ${ns[$i]} link set eth1 up
375    ip -net ${ns[$i]} link set veth0 up
376done
377
378ip -net ${ns[1]} route add default via 10.0.1.1
379ip -net ${ns[2]} route add default via 10.0.2.1
380
381ip -net ${ns[3]} addr add 10.0.1.1/24 dev eth1
382ip -net ${ns[3]} addr add 10.0.3.1/24 dev veth0
383ip -net ${ns[3]} addr add 2001:1::1/64 dev eth1
384ip -net ${ns[3]} addr add 2001:3::1/64 dev veth0
385
386ip -net ${ns[3]} route add default via 10.0.3.10
387
388ip -net ${ns[4]} addr add 10.0.2.1/24 dev eth1
389ip -net ${ns[4]} addr add 10.0.3.10/24 dev veth0
390ip -net ${ns[4]} addr add 2001:2::1/64 dev eth1
391ip -net ${ns[4]} addr add 2001:3::10/64 dev veth0
392ip -net ${ns[4]} route add default via 10.0.3.1
393
394for j in 4 6; do
395	for i in 3 4;do
396		ip netns exec ${ns[$i]} sysctl net.ipv$j.conf.eth1.forwarding=1 > /dev/null
397		ip netns exec ${ns[$i]} sysctl net.ipv$j.conf.veth0.forwarding=1 > /dev/null
398	done
399done
400
401# abuse iptables rule counter to check if ping matches a policy
402ip netns exec ${ns[3]} iptables -p icmp -A FORWARD -m policy --dir out --pol ipsec
403ip netns exec ${ns[4]} iptables -p icmp -A FORWARD -m policy --dir out --pol ipsec
404if [ $? -ne 0 ];then
405	echo "SKIP: Could not insert iptables rule"
406	cleanup_ns $ns1 $ns2 $ns3 $ns4
407	exit $ksft_skip
408fi
409
410#          localip  remoteip  localnet    remotenet
411do_esp ${ns[3]} 10.0.3.1 10.0.3.10 10.0.1.0/24 10.0.2.0/24 $SPI1 $SPI2
412do_esp ${ns[3]} dead:3::1 dead:3::10 dead:1::/64 dead:2::/64 $SPI1 $SPI2
413do_esp ${ns[4]} 10.0.3.10 10.0.3.1 10.0.2.0/24 10.0.1.0/24 $SPI2 $SPI1
414do_esp ${ns[4]} dead:3::10 dead:3::1 dead:2::/64 dead:1::/64 $SPI2 $SPI1
415
416do_dummies4 ${ns[3]}
417do_dummies6 ${ns[4]}
418
419do_esp_policy_get_check ${ns[3]} 10.0.1.0/24 10.0.2.0/24
420do_esp_policy_get_check ${ns[4]} 10.0.2.0/24 10.0.1.0/24
421do_esp_policy_get_check ${ns[3]} dead:1::/64 dead:2::/64
422do_esp_policy_get_check ${ns[4]} dead:2::/64 dead:1::/64
423
424# ping to .254 should use ipsec, exception is not installed.
425check_xfrm 1 254
426if [ $? -ne 0 ]; then
427	echo "FAIL: expected ping to .254 to use ipsec tunnel"
428	ret=1
429else
430	echo "PASS: policy before exception matches"
431fi
432
433# installs exceptions
434#                localip  remoteip   encryptdst  plaindst
435do_exception ${ns[3]} 10.0.3.1 10.0.3.10 10.0.2.253 10.0.2.240/28
436do_exception ${ns[4]} 10.0.3.10 10.0.3.1 10.0.1.253 10.0.1.240/28
437
438do_exception ${ns[3]} dead:3::1 dead:3::10 dead:2::fd  dead:2:f0::/96
439do_exception ${ns[4]} dead:3::10 dead:3::1 dead:1::fd  dead:1:f0::/96
440
441check_exceptions "exceptions"
442if [ $? -ne 0 ]; then
443	ret=1
444fi
445
446# insert block policies with adjacent/overlapping netmasks
447do_overlap ${ns[3]}
448
449check_exceptions "exceptions and block policies"
450if [ $? -ne 0 ]; then
451	ret=1
452fi
453
454for n in ${ns[3]} ${ns[4]};do
455	ip -net $n xfrm policy set hthresh4 28 24 hthresh6 126 125
456	sleep $((RANDOM%5))
457done
458
459check_exceptions "exceptions and block policies after hresh changes"
460
461# full flush of policy db, check everything gets freed incl. internal meta data
462ip -net ${ns[3]} xfrm policy flush
463
464do_esp_policy ${ns[3]} 10.0.3.1 10.0.3.10 10.0.1.0/24 10.0.2.0/24
465do_exception ${ns[3]} 10.0.3.1 10.0.3.10 10.0.2.253 10.0.2.240/28
466
467# move inexact policies to hash table
468ip -net ${ns[3]} xfrm policy set hthresh4 16 16
469
470sleep $((RANDOM%5))
471check_exceptions "exceptions and block policies after hthresh change in ns3"
472
473# restore original hthresh settings -- move policies back to tables
474for n in ${ns[3]} ${ns[4]};do
475	ip -net $n xfrm policy set hthresh4 32 32 hthresh6 128 128
476	sleep $((RANDOM%5))
477done
478check_exceptions "exceptions and block policies after htresh change to normal"
479
480check_hthresh_repeat "policies with repeated htresh change"
481
482check_random_order ${ns[3]} "policies inserted in random order"
483
484cleanup_ns $ns1 $ns2 $ns3 $ns4
485
486exit $ret
487