1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Setup/topology:
5#
6#    NS1             NS2             NS3
7#   veth1 <---> veth2   veth3 <---> veth4 (the top route)
8#   veth5 <---> veth6   veth7 <---> veth8 (the bottom route)
9#
10#   each vethN gets IPv[4|6]_N address
11#
12#   IPv*_SRC = IPv*_1
13#   IPv*_DST = IPv*_4
14#
15#   all tests test pings from IPv*_SRC to IPv*_DST
16#
17#   by default, routes are configured to allow packets to go
18#   IP*_1 <=> IP*_2 <=> IP*_3 <=> IP*_4 (the top route)
19#
20#   a GRE device is installed in NS3 with IPv*_GRE, and
21#   NS1/NS2 are configured to route packets to IPv*_GRE via IP*_8
22#   (the bottom route)
23#
24# Tests:
25#
26#   1. routes NS2->IPv*_DST are brought down, so the only way a ping
27#      from IP*_SRC to IP*_DST can work is via IPv*_GRE
28#
29#   2a. in an egress test, a bpf LWT_XMIT program is installed on veth1
30#       that encaps the packets with an IP/GRE header to route to IPv*_GRE
31#
32#       ping: SRC->[encap at veth1:egress]->GRE:decap->DST
33#       ping replies go DST->SRC directly
34#
35#   2b. in an ingress test, a bpf LWT_IN program is installed on veth2
36#       that encaps the packets with an IP/GRE header to route to IPv*_GRE
37#
38#       ping: SRC->[encap at veth2:ingress]->GRE:decap->DST
39#       ping replies go DST->SRC directly
40
41if [[ $EUID -ne 0 ]]; then
42	echo "This script must be run as root"
43	echo "FAIL"
44	exit 1
45fi
46
47readonly NS1="ns1-$(mktemp -u XXXXXX)"
48readonly NS2="ns2-$(mktemp -u XXXXXX)"
49readonly NS3="ns3-$(mktemp -u XXXXXX)"
50
51readonly IPv4_1="172.16.1.100"
52readonly IPv4_2="172.16.2.100"
53readonly IPv4_3="172.16.3.100"
54readonly IPv4_4="172.16.4.100"
55readonly IPv4_5="172.16.5.100"
56readonly IPv4_6="172.16.6.100"
57readonly IPv4_7="172.16.7.100"
58readonly IPv4_8="172.16.8.100"
59readonly IPv4_GRE="172.16.16.100"
60
61readonly IPv4_SRC=$IPv4_1
62readonly IPv4_DST=$IPv4_4
63
64readonly IPv6_1="fb01::1"
65readonly IPv6_2="fb02::1"
66readonly IPv6_3="fb03::1"
67readonly IPv6_4="fb04::1"
68readonly IPv6_5="fb05::1"
69readonly IPv6_6="fb06::1"
70readonly IPv6_7="fb07::1"
71readonly IPv6_8="fb08::1"
72readonly IPv6_GRE="fb10::1"
73
74readonly IPv6_SRC=$IPv6_1
75readonly IPv6_DST=$IPv6_4
76
77TEST_STATUS=0
78TESTS_SUCCEEDED=0
79TESTS_FAILED=0
80
81TMPFILE=""
82
83process_test_results()
84{
85	if [[ "${TEST_STATUS}" -eq 0 ]] ; then
86		echo "PASS"
87		TESTS_SUCCEEDED=$((TESTS_SUCCEEDED+1))
88	else
89		echo "FAIL"
90		TESTS_FAILED=$((TESTS_FAILED+1))
91	fi
92}
93
94print_test_summary_and_exit()
95{
96	echo "passed tests: ${TESTS_SUCCEEDED}"
97	echo "failed tests: ${TESTS_FAILED}"
98	if [ "${TESTS_FAILED}" -eq "0" ] ; then
99		exit 0
100	else
101		exit 1
102	fi
103}
104
105setup()
106{
107	set -e  # exit on error
108	TEST_STATUS=0
109
110	# create devices and namespaces
111	ip netns add "${NS1}"
112	ip netns add "${NS2}"
113	ip netns add "${NS3}"
114
115	ip link add veth1 type veth peer name veth2
116	ip link add veth3 type veth peer name veth4
117	ip link add veth5 type veth peer name veth6
118	ip link add veth7 type veth peer name veth8
119
120	ip netns exec ${NS2} sysctl -wq net.ipv4.ip_forward=1
121	ip netns exec ${NS2} sysctl -wq net.ipv6.conf.all.forwarding=1
122
123	ip link set veth1 netns ${NS1}
124	ip link set veth2 netns ${NS2}
125	ip link set veth3 netns ${NS2}
126	ip link set veth4 netns ${NS3}
127	ip link set veth5 netns ${NS1}
128	ip link set veth6 netns ${NS2}
129	ip link set veth7 netns ${NS2}
130	ip link set veth8 netns ${NS3}
131
132	# configure addesses: the top route (1-2-3-4)
133	ip -netns ${NS1}    addr add ${IPv4_1}/24  dev veth1
134	ip -netns ${NS2}    addr add ${IPv4_2}/24  dev veth2
135	ip -netns ${NS2}    addr add ${IPv4_3}/24  dev veth3
136	ip -netns ${NS3}    addr add ${IPv4_4}/24  dev veth4
137	ip -netns ${NS1} -6 addr add ${IPv6_1}/128 nodad dev veth1
138	ip -netns ${NS2} -6 addr add ${IPv6_2}/128 nodad dev veth2
139	ip -netns ${NS2} -6 addr add ${IPv6_3}/128 nodad dev veth3
140	ip -netns ${NS3} -6 addr add ${IPv6_4}/128 nodad dev veth4
141
142	# configure addresses: the bottom route (5-6-7-8)
143	ip -netns ${NS1}    addr add ${IPv4_5}/24  dev veth5
144	ip -netns ${NS2}    addr add ${IPv4_6}/24  dev veth6
145	ip -netns ${NS2}    addr add ${IPv4_7}/24  dev veth7
146	ip -netns ${NS3}    addr add ${IPv4_8}/24  dev veth8
147	ip -netns ${NS1} -6 addr add ${IPv6_5}/128 nodad dev veth5
148	ip -netns ${NS2} -6 addr add ${IPv6_6}/128 nodad dev veth6
149	ip -netns ${NS2} -6 addr add ${IPv6_7}/128 nodad dev veth7
150	ip -netns ${NS3} -6 addr add ${IPv6_8}/128 nodad dev veth8
151
152	ip -netns ${NS1} link set dev veth1 up
153	ip -netns ${NS2} link set dev veth2 up
154	ip -netns ${NS2} link set dev veth3 up
155	ip -netns ${NS3} link set dev veth4 up
156	ip -netns ${NS1} link set dev veth5 up
157	ip -netns ${NS2} link set dev veth6 up
158	ip -netns ${NS2} link set dev veth7 up
159	ip -netns ${NS3} link set dev veth8 up
160
161	# configure routes: IP*_SRC -> veth1/IP*_2 (= top route) default;
162	# the bottom route to specific bottom addresses
163
164	# NS1
165	# top route
166	ip -netns ${NS1}    route add ${IPv4_2}/32  dev veth1
167	ip -netns ${NS1}    route add default dev veth1 via ${IPv4_2}  # go top by default
168	ip -netns ${NS1} -6 route add ${IPv6_2}/128 dev veth1
169	ip -netns ${NS1} -6 route add default dev veth1 via ${IPv6_2}  # go top by default
170	# bottom route
171	ip -netns ${NS1}    route add ${IPv4_6}/32  dev veth5
172	ip -netns ${NS1}    route add ${IPv4_7}/32  dev veth5 via ${IPv4_6}
173	ip -netns ${NS1}    route add ${IPv4_8}/32  dev veth5 via ${IPv4_6}
174	ip -netns ${NS1} -6 route add ${IPv6_6}/128 dev veth5
175	ip -netns ${NS1} -6 route add ${IPv6_7}/128 dev veth5 via ${IPv6_6}
176	ip -netns ${NS1} -6 route add ${IPv6_8}/128 dev veth5 via ${IPv6_6}
177
178	# NS2
179	# top route
180	ip -netns ${NS2}    route add ${IPv4_1}/32  dev veth2
181	ip -netns ${NS2}    route add ${IPv4_4}/32  dev veth3
182	ip -netns ${NS2} -6 route add ${IPv6_1}/128 dev veth2
183	ip -netns ${NS2} -6 route add ${IPv6_4}/128 dev veth3
184	# bottom route
185	ip -netns ${NS2}    route add ${IPv4_5}/32  dev veth6
186	ip -netns ${NS2}    route add ${IPv4_8}/32  dev veth7
187	ip -netns ${NS2} -6 route add ${IPv6_5}/128 dev veth6
188	ip -netns ${NS2} -6 route add ${IPv6_8}/128 dev veth7
189
190	# NS3
191	# top route
192	ip -netns ${NS3}    route add ${IPv4_3}/32  dev veth4
193	ip -netns ${NS3}    route add ${IPv4_1}/32  dev veth4 via ${IPv4_3}
194	ip -netns ${NS3}    route add ${IPv4_2}/32  dev veth4 via ${IPv4_3}
195	ip -netns ${NS3} -6 route add ${IPv6_3}/128 dev veth4
196	ip -netns ${NS3} -6 route add ${IPv6_1}/128 dev veth4 via ${IPv6_3}
197	ip -netns ${NS3} -6 route add ${IPv6_2}/128 dev veth4 via ${IPv6_3}
198	# bottom route
199	ip -netns ${NS3}    route add ${IPv4_7}/32  dev veth8
200	ip -netns ${NS3}    route add ${IPv4_5}/32  dev veth8 via ${IPv4_7}
201	ip -netns ${NS3}    route add ${IPv4_6}/32  dev veth8 via ${IPv4_7}
202	ip -netns ${NS3} -6 route add ${IPv6_7}/128 dev veth8
203	ip -netns ${NS3} -6 route add ${IPv6_5}/128 dev veth8 via ${IPv6_7}
204	ip -netns ${NS3} -6 route add ${IPv6_6}/128 dev veth8 via ${IPv6_7}
205
206	# configure IPv4 GRE device in NS3, and a route to it via the "bottom" route
207	ip -netns ${NS3} tunnel add gre_dev mode gre remote ${IPv4_1} local ${IPv4_GRE} ttl 255
208	ip -netns ${NS3} link set gre_dev up
209	ip -netns ${NS3} addr add ${IPv4_GRE} dev gre_dev
210	ip -netns ${NS1} route add ${IPv4_GRE}/32 dev veth5 via ${IPv4_6}
211	ip -netns ${NS2} route add ${IPv4_GRE}/32 dev veth7 via ${IPv4_8}
212
213
214	# configure IPv6 GRE device in NS3, and a route to it via the "bottom" route
215	ip -netns ${NS3} -6 tunnel add name gre6_dev mode ip6gre remote ${IPv6_1} local ${IPv6_GRE} ttl 255
216	ip -netns ${NS3} link set gre6_dev up
217	ip -netns ${NS3} -6 addr add ${IPv6_GRE} nodad dev gre6_dev
218	ip -netns ${NS1} -6 route add ${IPv6_GRE}/128 dev veth5 via ${IPv6_6}
219	ip -netns ${NS2} -6 route add ${IPv6_GRE}/128 dev veth7 via ${IPv6_8}
220
221	# rp_filter gets confused by what these tests are doing, so disable it
222	ip netns exec ${NS1} sysctl -wq net.ipv4.conf.all.rp_filter=0
223	ip netns exec ${NS2} sysctl -wq net.ipv4.conf.all.rp_filter=0
224	ip netns exec ${NS3} sysctl -wq net.ipv4.conf.all.rp_filter=0
225
226	TMPFILE=$(mktemp /tmp/test_lwt_ip_encap.XXXXXX)
227
228	sleep 1  # reduce flakiness
229	set +e
230}
231
232cleanup()
233{
234	if [ -f ${TMPFILE} ] ; then
235		rm ${TMPFILE}
236	fi
237
238	ip netns del ${NS1} 2> /dev/null
239	ip netns del ${NS2} 2> /dev/null
240	ip netns del ${NS3} 2> /dev/null
241}
242
243trap cleanup EXIT
244
245remove_routes_to_gredev()
246{
247	ip -netns ${NS1} route del ${IPv4_GRE} dev veth5
248	ip -netns ${NS2} route del ${IPv4_GRE} dev veth7
249	ip -netns ${NS1} -6 route del ${IPv6_GRE}/128 dev veth5
250	ip -netns ${NS2} -6 route del ${IPv6_GRE}/128 dev veth7
251}
252
253add_unreachable_routes_to_gredev()
254{
255	ip -netns ${NS1} route add unreachable ${IPv4_GRE}/32
256	ip -netns ${NS2} route add unreachable ${IPv4_GRE}/32
257	ip -netns ${NS1} -6 route add unreachable ${IPv6_GRE}/128
258	ip -netns ${NS2} -6 route add unreachable ${IPv6_GRE}/128
259}
260
261test_ping()
262{
263	local readonly PROTO=$1
264	local readonly EXPECTED=$2
265	local RET=0
266
267	if [ "${PROTO}" == "IPv4" ] ; then
268		ip netns exec ${NS1} ping  -c 1 -W 1 -I ${IPv4_SRC} ${IPv4_DST} 2>&1 > /dev/null
269		RET=$?
270	elif [ "${PROTO}" == "IPv6" ] ; then
271		ip netns exec ${NS1} ping6 -c 1 -W 6 -I ${IPv6_SRC} ${IPv6_DST} 2>&1 > /dev/null
272		RET=$?
273	else
274		echo "    test_ping: unknown PROTO: ${PROTO}"
275		TEST_STATUS=1
276	fi
277
278	if [ "0" != "${RET}" ]; then
279		RET=1
280	fi
281
282	if [ "${EXPECTED}" != "${RET}" ] ; then
283		echo "    test_ping failed: expected: ${EXPECTED}; got ${RET}"
284		TEST_STATUS=1
285	fi
286}
287
288test_gso()
289{
290	local readonly PROTO=$1
291	local readonly PKT_SZ=5000
292	local IP_DST=""
293	: > ${TMPFILE}  # trim the capture file
294
295	# check that nc is present
296	command -v nc >/dev/null 2>&1 || \
297		{ echo >&2 "nc is not available: skipping TSO tests"; return; }
298
299	# listen on IPv*_DST, capture TCP into $TMPFILE
300	if [ "${PROTO}" == "IPv4" ] ; then
301		IP_DST=${IPv4_DST}
302		ip netns exec ${NS3} bash -c \
303			"nc -4 -l -s ${IPv4_DST} -p 9000 > ${TMPFILE} &"
304	elif [ "${PROTO}" == "IPv6" ] ; then
305		IP_DST=${IPv6_DST}
306		ip netns exec ${NS3} bash -c \
307			"nc -6 -l -s ${IPv6_DST} -p 9000 > ${TMPFILE} &"
308		RET=$?
309	else
310		echo "    test_gso: unknown PROTO: ${PROTO}"
311		TEST_STATUS=1
312	fi
313	sleep 1  # let nc start listening
314
315	# send a packet larger than MTU
316	ip netns exec ${NS1} bash -c \
317		"dd if=/dev/zero bs=$PKT_SZ count=1 > /dev/tcp/${IP_DST}/9000 2>/dev/null"
318	sleep 2 # let the packet get delivered
319
320	# verify we received all expected bytes
321	SZ=$(stat -c %s ${TMPFILE})
322	if [ "$SZ" != "$PKT_SZ" ] ; then
323		echo "    test_gso failed: ${PROTO}"
324		TEST_STATUS=1
325	fi
326}
327
328test_egress()
329{
330	local readonly ENCAP=$1
331	echo "starting egress ${ENCAP} encap test"
332	setup
333
334	# by default, pings work
335	test_ping IPv4 0
336	test_ping IPv6 0
337
338	# remove NS2->DST routes, ping fails
339	ip -netns ${NS2}    route del ${IPv4_DST}/32  dev veth3
340	ip -netns ${NS2} -6 route del ${IPv6_DST}/128 dev veth3
341	test_ping IPv4 1
342	test_ping IPv6 1
343
344	# install replacement routes (LWT/eBPF), pings succeed
345	if [ "${ENCAP}" == "IPv4" ] ; then
346		ip -netns ${NS1} route add ${IPv4_DST} encap bpf xmit obj test_lwt_ip_encap.o sec encap_gre dev veth1
347		ip -netns ${NS1} -6 route add ${IPv6_DST} encap bpf xmit obj test_lwt_ip_encap.o sec encap_gre dev veth1
348	elif [ "${ENCAP}" == "IPv6" ] ; then
349		ip -netns ${NS1} route add ${IPv4_DST} encap bpf xmit obj test_lwt_ip_encap.o sec encap_gre6 dev veth1
350		ip -netns ${NS1} -6 route add ${IPv6_DST} encap bpf xmit obj test_lwt_ip_encap.o sec encap_gre6 dev veth1
351	else
352		echo "    unknown encap ${ENCAP}"
353		TEST_STATUS=1
354	fi
355	test_ping IPv4 0
356	test_ping IPv6 0
357	test_gso IPv4
358	test_gso IPv6
359
360	# a negative test: remove routes to GRE devices: ping fails
361	remove_routes_to_gredev
362	test_ping IPv4 1
363	test_ping IPv6 1
364
365	# another negative test
366	add_unreachable_routes_to_gredev
367	test_ping IPv4 1
368	test_ping IPv6 1
369
370	cleanup
371	process_test_results
372}
373
374test_ingress()
375{
376	local readonly ENCAP=$1
377	echo "starting ingress ${ENCAP} encap test"
378	setup
379
380	# need to wait a bit for IPv6 to autoconf, otherwise
381	# ping6 sometimes fails with "unable to bind to address"
382
383	# by default, pings work
384	test_ping IPv4 0
385	test_ping IPv6 0
386
387	# remove NS2->DST routes, pings fail
388	ip -netns ${NS2}    route del ${IPv4_DST}/32  dev veth3
389	ip -netns ${NS2} -6 route del ${IPv6_DST}/128 dev veth3
390	test_ping IPv4 1
391	test_ping IPv6 1
392
393	# install replacement routes (LWT/eBPF), pings succeed
394	if [ "${ENCAP}" == "IPv4" ] ; then
395		ip -netns ${NS2} route add ${IPv4_DST} encap bpf in obj test_lwt_ip_encap.o sec encap_gre dev veth2
396		ip -netns ${NS2} -6 route add ${IPv6_DST} encap bpf in obj test_lwt_ip_encap.o sec encap_gre dev veth2
397	elif [ "${ENCAP}" == "IPv6" ] ; then
398		ip -netns ${NS2} route add ${IPv4_DST} encap bpf in obj test_lwt_ip_encap.o sec encap_gre6 dev veth2
399		ip -netns ${NS2} -6 route add ${IPv6_DST} encap bpf in obj test_lwt_ip_encap.o sec encap_gre6 dev veth2
400	else
401		echo "FAIL: unknown encap ${ENCAP}"
402		TEST_STATUS=1
403	fi
404	test_ping IPv4 0
405	test_ping IPv6 0
406
407	# a negative test: remove routes to GRE devices: ping fails
408	remove_routes_to_gredev
409	test_ping IPv4 1
410	test_ping IPv6 1
411
412	# another negative test
413	add_unreachable_routes_to_gredev
414	test_ping IPv4 1
415	test_ping IPv6 1
416
417	cleanup
418	process_test_results
419}
420
421test_egress IPv4
422test_egress IPv6
423test_ingress IPv4
424test_ingress IPv6
425
426print_test_summary_and_exit
427