1#!/bin/sh
2# SPDX-License-Identifier: GPL-2.0
3
4# Author: Matthias May <matthias.may@westermo.com>
5#
6# This script evaluates ip tunnels that are capable of carrying L2 traffic
7# if they inherit or set the inheritable fields.
8# Namely these tunnels are: 'gretap', 'vxlan' and 'geneve'.
9# Checked inheritable fields are: TOS and TTL.
10# The outer tunnel protocol of 'IPv4' or 'IPv6' is verified-
11# As payload frames of type 'IPv4', 'IPv6' and 'other'(ARP) are verified.
12# In addition this script also checks if forcing a specific field in the
13# outer header is working.
14
15if [ "$(id -u)" != "0" ]; then
16	echo "Please run as root."
17	exit 0
18fi
19if ! which tcpdump > /dev/null 2>&1; then
20	echo "No tcpdump found. Required for this test."
21	exit 0
22fi
23
24expected_tos="0x00"
25expected_ttl="0"
26failed=false
27
28get_random_tos() {
29	# Get a random hex tos value between 0x00 and 0xfc, a multiple of 4
30	echo "0x$(tr -dc '0-9a-f' < /dev/urandom | head -c 1)\
31$(tr -dc '048c' < /dev/urandom | head -c 1)"
32}
33get_random_ttl() {
34	# Get a random dec value between 0 and 255
35	printf "%d" "0x$(tr -dc '0-9a-f' < /dev/urandom | head -c 2)"
36}
37get_field() {
38	# Expects to get the 'head -n 1' of a captured frame by tcpdump.
39	# Parses this first line and returns the specified field.
40	local field="$1"
41	local input="$2"
42	local found=false
43	input="$(echo "$input" | tr -d '(),')"
44	for input_field in $input; do
45		if $found; then
46			echo "$input_field"
47			return
48		fi
49		# The next field that we iterate over is the looked for value
50		if [ "$input_field" = "$field" ]; then
51			found=true
52		fi
53	done
54	echo "0"
55}
56setup() {
57	local type="$1"
58	local outer="$2"
59	local inner="$3"
60	local tos_ttl="$4"
61	local vlan="$5"
62	local test_tos="0x00"
63	local test_ttl="0"
64	local ns="ip netns exec testing"
65
66	# We don't want a test-tos of 0x00,
67	# because this is the value that we get when no tos is set.
68	expected_tos="$(get_random_tos)"
69	while [ "$expected_tos" = "0x00" ]; do
70		expected_tos="$(get_random_tos)"
71	done
72	if [ "$tos_ttl" = "random" ]; then
73		test_tos="$expected_tos"
74		tos="fixed $test_tos"
75	elif [ "$tos_ttl" = "inherit" ]; then
76		test_tos="$tos_ttl"
77		tos="inherit $expected_tos"
78	fi
79
80	# We don't want a test-ttl of 64 or 0,
81	# because 64 is when no ttl is set and 0 is not a valid ttl.
82	expected_ttl="$(get_random_ttl)"
83	while [ "$expected_ttl" = "64" ] || [ "$expected_ttl" = "0" ]; do
84		expected_ttl="$(get_random_ttl)"
85	done
86
87	if [ "$tos_ttl" = "random" ]; then
88		test_ttl="$expected_ttl"
89		ttl="fixed $test_ttl"
90	elif [ "$tos_ttl" = "inherit" ]; then
91		test_ttl="$tos_ttl"
92		ttl="inherit $expected_ttl"
93	fi
94	printf "│%7s │%6s │%6s │%13s │%13s │%6s │" \
95	"$type" "$outer" "$inner" "$tos" "$ttl" "$vlan"
96
97	# Create 'testing' netns, veth pair and connect main ns with testing ns
98	ip netns add testing
99	ip link add type veth
100	ip link set veth1 netns testing
101	ip link set veth0 up
102	$ns ip link set veth1 up
103	ip addr flush dev veth0
104	$ns ip addr flush dev veth1
105
106	local local_addr1=""
107	local local_addr2=""
108	if [ "$type" = "gre" ] || [ "$type" = "vxlan" ]; then
109		if [ "$outer" = "4" ]; then
110			local_addr1="local 198.18.0.1"
111			local_addr2="local 198.18.0.2"
112		elif [ "$outer" = "6" ]; then
113			local_addr1="local fdd1:ced0:5d88:3fce::1"
114			local_addr2="local fdd1:ced0:5d88:3fce::2"
115		fi
116	fi
117	local vxlan=""
118	if [ "$type" = "vxlan" ]; then
119		vxlan="vni 100 dstport 4789"
120	fi
121	local geneve=""
122	if [ "$type" = "geneve" ]; then
123		geneve="vni 100"
124	fi
125	# Create tunnel and assign outer IPv4/IPv6 addresses
126	if [ "$outer" = "4" ]; then
127		if [ "$type" = "gre" ]; then
128			type="gretap"
129		fi
130		ip addr add 198.18.0.1/24 dev veth0
131		$ns ip addr add 198.18.0.2/24 dev veth1
132		ip link add name tep0 type $type $local_addr1 remote \
133		198.18.0.2 tos $test_tos ttl $test_ttl $vxlan $geneve
134		$ns ip link add name tep1 type $type $local_addr2 remote \
135		198.18.0.1 tos $test_tos ttl $test_ttl $vxlan $geneve
136	elif [ "$outer" = "6" ]; then
137		if [ "$type" = "gre" ]; then
138			type="ip6gretap"
139		fi
140		ip addr add fdd1:ced0:5d88:3fce::1/64 dev veth0
141		$ns ip addr add fdd1:ced0:5d88:3fce::2/64 dev veth1
142		ip link add name tep0 type $type $local_addr1 \
143		remote fdd1:ced0:5d88:3fce::2 tos $test_tos ttl $test_ttl \
144		$vxlan $geneve
145		$ns ip link add name tep1 type $type $local_addr2 \
146		remote fdd1:ced0:5d88:3fce::1 tos $test_tos ttl $test_ttl \
147		$vxlan $geneve
148	fi
149
150	# Bring L2-tunnel link up and create VLAN on top
151	ip link set tep0 up
152	$ns ip link set tep1 up
153	ip addr flush dev tep0
154	$ns ip addr flush dev tep1
155	local parent
156	if $vlan; then
157		parent="vlan99-"
158		ip link add link tep0 name ${parent}0 type vlan id 99
159		$ns ip link add link tep1 name ${parent}1 type vlan id 99
160		ip link set ${parent}0 up
161		$ns ip link set ${parent}1 up
162		ip addr flush dev ${parent}0
163		$ns ip addr flush dev ${parent}1
164	else
165		parent="tep"
166	fi
167
168	# Assign inner IPv4/IPv6 addresses
169	if [ "$inner" = "4" ] || [ "$inner" = "other" ]; then
170		ip addr add 198.19.0.1/24 brd + dev ${parent}0
171		$ns ip addr add 198.19.0.2/24 brd + dev ${parent}1
172	elif [ "$inner" = "6" ]; then
173		ip addr add fdd4:96cf:4eae:443b::1/64 dev ${parent}0
174		$ns ip addr add fdd4:96cf:4eae:443b::2/64 dev ${parent}1
175	fi
176}
177
178verify() {
179	local outer="$1"
180	local inner="$2"
181	local tos_ttl="$3"
182	local vlan="$4"
183
184	local ping_pid out captured_tos captured_ttl result
185
186	local ping_dst
187	if [ "$inner" = "4" ]; then
188		ping_dst="198.19.0.2"
189	elif [ "$inner" = "6" ]; then
190		ping_dst="fdd4:96cf:4eae:443b::2"
191	elif [ "$inner" = "other" ]; then
192		ping_dst="198.19.0.3" # Generates ARPs which are not IPv4/IPv6
193	fi
194	if [ "$tos_ttl" = "inherit" ]; then
195		ping -i 0.1 $ping_dst -Q "$expected_tos" -t "$expected_ttl" \
196		2>/dev/null 1>&2 & ping_pid="$!"
197	else
198		ping -i 0.1 $ping_dst 2>/dev/null 1>&2 & ping_pid="$!"
199	fi
200	local tunnel_type_offset tunnel_type_proto req_proto_offset req_offset
201	if [ "$type" = "gre" ]; then
202		tunnel_type_proto="0x2f"
203	elif [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
204		tunnel_type_proto="0x11"
205	fi
206	if [ "$outer" = "4" ]; then
207		tunnel_type_offset="9"
208		if [ "$inner" = "4" ]; then
209			req_proto_offset="47"
210			req_offset="58"
211			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
212				req_proto_offset="$((req_proto_offset + 12))"
213				req_offset="$((req_offset + 12))"
214			fi
215			if $vlan; then
216				req_proto_offset="$((req_proto_offset + 4))"
217				req_offset="$((req_offset + 4))"
218			fi
219			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
220			ip[$tunnel_type_offset] = $tunnel_type_proto and \
221			ip[$req_proto_offset] = 0x01 and \
222			ip[$req_offset] = 0x08 2>/dev/null | head -n 1)"
223		elif [ "$inner" = "6" ]; then
224			req_proto_offset="44"
225			req_offset="78"
226			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
227				req_proto_offset="$((req_proto_offset + 12))"
228				req_offset="$((req_offset + 12))"
229			fi
230			if $vlan; then
231				req_proto_offset="$((req_proto_offset + 4))"
232				req_offset="$((req_offset + 4))"
233			fi
234			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
235			ip[$tunnel_type_offset] = $tunnel_type_proto and \
236			ip[$req_proto_offset] = 0x3a and \
237			ip[$req_offset] = 0x80 2>/dev/null | head -n 1)"
238		elif [ "$inner" = "other" ]; then
239			req_proto_offset="36"
240			req_offset="45"
241			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
242				req_proto_offset="$((req_proto_offset + 12))"
243				req_offset="$((req_offset + 12))"
244			fi
245			if $vlan; then
246				req_proto_offset="$((req_proto_offset + 4))"
247				req_offset="$((req_offset + 4))"
248			fi
249			if [ "$tos_ttl" = "inherit" ]; then
250				expected_tos="0x00"
251				expected_ttl="64"
252			fi
253			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
254			ip[$tunnel_type_offset] = $tunnel_type_proto and \
255			ip[$req_proto_offset] = 0x08 and \
256			ip[$((req_proto_offset + 1))] = 0x06 and \
257			ip[$req_offset] = 0x01 2>/dev/null | head -n 1)"
258		fi
259	elif [ "$outer" = "6" ]; then
260		if [ "$type" = "gre" ]; then
261			tunnel_type_offset="40"
262		elif [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
263			tunnel_type_offset="6"
264		fi
265		if [ "$inner" = "4" ]; then
266			local req_proto_offset="75"
267			local req_offset="86"
268			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
269				req_proto_offset="$((req_proto_offset + 4))"
270				req_offset="$((req_offset + 4))"
271			fi
272			if $vlan; then
273				req_proto_offset="$((req_proto_offset + 4))"
274				req_offset="$((req_offset + 4))"
275			fi
276			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
277			ip6[$tunnel_type_offset] = $tunnel_type_proto and \
278			ip6[$req_proto_offset] = 0x01 and \
279			ip6[$req_offset] = 0x08 2>/dev/null | head -n 1)"
280		elif [ "$inner" = "6" ]; then
281			local req_proto_offset="72"
282			local req_offset="106"
283			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
284				req_proto_offset="$((req_proto_offset + 4))"
285				req_offset="$((req_offset + 4))"
286			fi
287			if $vlan; then
288				req_proto_offset="$((req_proto_offset + 4))"
289				req_offset="$((req_offset + 4))"
290			fi
291			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
292			ip6[$tunnel_type_offset] = $tunnel_type_proto and \
293			ip6[$req_proto_offset] = 0x3a and \
294			ip6[$req_offset] = 0x80 2>/dev/null | head -n 1)"
295		elif [ "$inner" = "other" ]; then
296			local req_proto_offset="64"
297			local req_offset="73"
298			if [ "$type" = "vxlan" ] || [ "$type" = "geneve" ]; then
299				req_proto_offset="$((req_proto_offset + 4))"
300				req_offset="$((req_offset + 4))"
301			fi
302			if $vlan; then
303				req_proto_offset="$((req_proto_offset + 4))"
304				req_offset="$((req_offset + 4))"
305			fi
306			if [ "$tos_ttl" = "inherit" ]; then
307				expected_tos="0x00"
308				expected_ttl="64"
309			fi
310			out="$(tcpdump --immediate-mode -p -c 1 -v -i veth0 -n \
311			ip6[$tunnel_type_offset] = $tunnel_type_proto and \
312			ip6[$req_proto_offset] = 0x08 and \
313			ip6[$((req_proto_offset + 1))] = 0x06 and \
314			ip6[$req_offset] = 0x01 2>/dev/null | head -n 1)"
315		fi
316	fi
317	kill -9 $ping_pid
318	wait $ping_pid 2>/dev/null
319	result="FAIL"
320	if [ "$outer" = "4" ]; then
321		captured_ttl="$(get_field "ttl" "$out")"
322		captured_tos="$(printf "0x%02x" "$(get_field "tos" "$out")")"
323		if [ "$captured_tos" = "$expected_tos" ] &&
324		   [ "$captured_ttl" = "$expected_ttl" ]; then
325			result="OK"
326		fi
327	elif [ "$outer" = "6" ]; then
328		captured_ttl="$(get_field "hlim" "$out")"
329		captured_tos="$(printf "0x%02x" "$(get_field "class" "$out")")"
330		if [ "$captured_tos" = "$expected_tos" ] &&
331		   [ "$captured_ttl" = "$expected_ttl" ]; then
332			result="OK"
333		fi
334	fi
335
336	printf "%7s │\n" "$result"
337	if [ "$result" = "FAIL" ]; then
338		failed=true
339		if [ "$captured_tos" != "$expected_tos" ]; then
340			printf "│%43s%27s │\n" \
341			"Expected TOS value: $expected_tos" \
342			"Captured TOS value: $captured_tos"
343		fi
344		if [ "$captured_ttl" != "$expected_ttl" ]; then
345			printf "│%43s%27s │\n" \
346			"Expected TTL value: $expected_ttl" \
347			"Captured TTL value: $captured_ttl"
348		fi
349		printf "│%71s│\n" " "
350	fi
351}
352
353cleanup() {
354	ip link del veth0 2>/dev/null
355	ip netns del testing 2>/dev/null
356	ip link del tep0 2>/dev/null
357}
358
359printf "┌────────┬───────┬───────┬──────────────┬"
360printf "──────────────┬───────┬────────┐\n"
361for type in gre vxlan geneve; do
362	if ! $(modprobe "$type" 2>/dev/null); then
363		continue
364	fi
365	for outer in 4 6; do
366		printf "├────────┼───────┼───────┼──────────────┼"
367		printf "──────────────┼───────┼────────┤\n"
368		printf "│  Type  │ outer | inner │     tos      │"
369		printf "      ttl     │  vlan │ result │\n"
370		for inner in 4 6 other; do
371			printf "├────────┼───────┼───────┼──────────────┼"
372			printf "──────────────┼───────┼────────┤\n"
373			for tos_ttl in inherit random; do
374				for vlan in false true; do
375					setup "$type" "$outer" "$inner" \
376					"$tos_ttl" "$vlan"
377					verify "$outer" "$inner" "$tos_ttl" \
378					"$vlan"
379					cleanup
380				done
381			done
382		done
383	done
384done
385printf "└────────┴───────┴───────┴──────────────┴"
386printf "──────────────┴───────┴────────┘\n"
387
388if $failed; then
389	exit 1
390fi
391