xref: /linux/tools/testing/selftests/net/double_udp_encap.sh (revision ca220141fa8ebae09765a242076b2b77338106b0)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4source lib.sh
5
6# shellcheck disable=SC2155 # prefer RO variable over return value from cmd
7readonly CLI="$(dirname "$(readlink -f "$0")")/../../../net/ynl/pyynl/cli.py"
8
9readonly SRC=1
10readonly DST=2
11
12readonly NET_V4=192.168.1.
13readonly NET_V6=2001:db8::
14readonly OL1_NET_V4=172.16.1.
15readonly OL1_NET_V6=2001:db8:1::
16readonly OL2_NET_V4=172.16.2.
17readonly OL2_NET_V6=2001:db8:2::
18
19trap cleanup_all_ns EXIT
20
21# shellcheck disable=SC2329 # can't figure out usage trough a variable
22is_ipv6() {
23	if [[ $1 =~ .*:.* ]]; then
24		return 0
25	fi
26	return 1
27}
28
29# shellcheck disable=SC2329 # can't figure out usage trough a variable
30create_gnv_endpoint() {
31	local -r netns=$1
32	local -r bm_rem_addr=$2
33	local -r gnv_dev=$3
34	local -r gnv_id=$4
35	local opts=$5
36	local gnv_json
37	local rem
38
39	if is_ipv6 "$bm_rem_addr"; then
40		rem=remote6
41	else
42		rem=remote
43	fi
44
45	# add ynl opt separator, if needed
46	[ -n "$opts" ] && opts=", $opts"
47
48	gnv_json="{ \"id\": $gnv_id, \"$rem\": \"$bm_rem_addr\"$opts }"
49	ip netns exec "$netns" "$CLI" --family rt-link --create --excl \
50	   --do newlink  --json "{\"ifname\": \"$gnv_dev\",
51	       \"linkinfo\": {\"kind\":\"geneve\",
52	       \"data\": $gnv_json } }" > /dev/null
53	ip -n "$netns" link set dev "$gnv_dev" up
54}
55
56# shellcheck disable=SC2329 # can't figure out usage trough a variable
57create_vxlan_endpoint() {
58	local -r netns=$1
59	local -r bm_rem_addr=$2
60	local -r vxlan_dev=$3
61	local -r vxlan_id=$4
62	local -r opts_str=$5
63	local oldifs
64	local -a opts
65	local opt
66
67	# convert the arguments from yaml format
68	oldifs=$IFS
69	IFS=','
70	for opt in $opts_str; do
71		local pattern='"port":'
72
73		[ -n "$opt" ] || continue
74
75		opts+=("${opt/$pattern*/dstport}" "${opt/$pattern/}")
76	done
77	IFS=$oldifs
78	[ ${#opts[@]} -gt 0 ] || opts+=("dstport" "4789")
79
80	ip -n "$netns" link add "$vxlan_dev" type vxlan id "$vxlan_id" \
81	   remote "$bm_rem_addr" "${opts[@]}"
82	ip -n "$netns" link set dev "$vxlan_dev" up
83}
84
85create_ns() {
86	local nested_opt='"port":6082'
87	local create_endpoint
88	local options="$1"
89	local feature
90	local dev
91	local id
92	local ns
93
94	RET=0
95
96	#  +-------------+    +-------------+
97	#  | NS_SRC      |    | NS_NST_DST  |
98	#  |             |    |             |
99	#  |   gnv_nst1  |    |  gnv_nst2   |
100	#  |   +         |    |         +   |
101	#  |   |         |    |         |   |
102	#  |   +         |    |         +   |
103	#  |  gnv1       |    |        gnv2 |
104	#  |   +         |    |         +   |
105	#  |   |         |    |         |   |
106	#  |   + veth1 +--------+ veth2 +   |
107	#  |             |    |             |
108	#  +-------------+    +-------------+
109
110	setup_ns NS_SRC NS_DST
111
112	# concatenate caller provided options and default one
113	[ -n "$2" ] && nested_opt="$nested_opt,$2"
114
115	ip link add name "veth$SRC" netns "$NS_SRC" type veth \
116	   peer name "veth$DST" netns "$NS_DST"
117	case "$ENCAP" in
118	vxlan)
119		create_endpoint=create_vxlan_endpoint
120		dev=vx
121		;;
122	geneve)
123		create_endpoint=create_gnv_endpoint
124		dev=gnv
125		;;
126	esac
127
128	id=1
129	for ns in "${NS_LIST[@]}"; do
130		ip -n "$ns" link set dev "veth$id" up
131
132		# ensure the sender can do large write just after 3whs
133		ip netns exec "$ns" \
134		   sysctl -qw net.ipv4.tcp_wmem="4096 4194304 4194304"
135
136		# note that 3 - $SRC == $DST and 3 - $DST == $SRC
137		if [ $FAMILY = "4" ]; then
138			ip -n "$ns" addr add dev "veth$id" "$NET_V4$id/24"
139			$create_endpoint "$ns" "$NET_V4$((3 - id))" \
140			   "$dev$id" 4 "$options"
141			ip -n "$ns" addr add dev "$dev$id" "$OL1_NET_V4$id/24"
142
143			# nested tunnel devices
144			# pmtu can't be propagated to upper layer devices;
145			# need manual adjust
146			$create_endpoint "$ns" "$OL1_NET_V4$((3 - id))" \
147			   "$dev"_nst"$id" 40 "$nested_opt"
148			ip -n "$ns" addr add dev "$dev"_nst"$id" \
149			   "$OL2_NET_V4$id/24"
150			ip -n "$ns" link set dev "$dev"_nst"$id" mtu 1392
151		else
152			ip -n "$ns" addr add dev "veth$id" "$NET_V6$id/64" \
153			   nodad
154			$create_endpoint "$ns" "$NET_V6$((3 - id))" \
155			   "$dev"6"$id" 6 "$options"
156			ip -n "$ns" addr add dev "$dev"6"$id" \
157			   "$OL1_NET_V6$id/64" nodad
158
159			$create_endpoint "$ns" "$OL1_NET_V6$((3 - id))" \
160			   "$dev"6_nst"$id" 60 "$nested_opt"
161			ip -n "$ns" addr add dev "$dev"6_nst"$id" \
162			   "$OL2_NET_V6$id/64" nodad
163			ip -n "$ns" link set dev "$dev"6_nst"$id" mtu 1352
164		fi
165		id=$((id+1))
166	done
167
168	# enable GRO heuristic on the veth peer and ensure UDP L4 over tunnel is
169	# actually segmented
170	for feature in tso tx-udp_tnl-segmentation; do
171		ip netns exec "$NS_SRC" ethtool -K "veth$SRC" \
172		   "$feature" off 2>/dev/null
173	done
174}
175
176create_ns_gso() {
177	local dev
178
179	create_ns "$@"
180	if [ "$ENCAP" = "geneve" ]; then
181		dev=gnv
182	else
183		dev=vx
184	fi
185	[ "$FAMILY" = "6" ] && dev="$dev"6
186	ip netns exec "$NS_SRC" ethtool -K "$dev$SRC" \
187	   tx-gso-partial on \
188	   tx-udp_tnl-segmentation on \
189	   tx-udp_tnl-csum-segmentation on
190}
191
192create_ns_gso_gro() {
193	create_ns_gso "$@"
194	ip netns exec "$NS_DST" ethtool -K "veth$DST" gro on
195	ip netns exec "$NS_SRC" ethtool -K "veth$SRC" tx off >/dev/null 2>&1
196}
197
198run_test() {
199	local -r dst=$NET$DST
200	local -r msg=$1
201	local -r total_size=$2
202	local -r encappkts=$3
203	local inner_proto_offset=0
204	local inner_maclen=14
205	local rx_family="-4"
206	local ipt=iptables
207	local bpf_filter
208	local -a rx_args
209	local wire_pkts
210	local rcvpkts
211	local encl=8
212	local dport
213	local pkts
214	local snd
215
216	if [ $FAMILY = "6" ]; then
217		ipt=ip6tables
218	else
219		# rx program does not support '-6' and implies ipv6 usage by
220		# default
221		rx_args=("$rx_family")
222	fi
223
224	# The received can only check fixed size packet
225	pkts=$((total_size / GSO_SIZE))
226	if [ -n "$4" ]; then
227		wire_pkts=$4
228	elif [ $((total_size % GSO_SIZE)) -eq 0 ]; then
229		wire_pkts=1
230		rx_args+=("-l" "$GSO_SIZE")
231	else
232		wire_pkts=2
233		pkts=$((pkts + 1))
234	fi
235
236	if [ "$ENCAP" = "geneve" ]; then
237		dport=6081
238	else
239		dport=4789
240	fi
241
242	# Either:
243	# - IPv4, nested tunnel carries UDP over IPv4, with dport 6082,
244	#   innermost is TCP over IPv4 on port 8000
245	# - IPv6, nested tunnel carries UDP over IPv6, with dport 6082,
246	#   innermost is TCP over IPv6 on port 8000
247	# The nested tunnel port is 6082 and the nested encap len is 8
248	# regardless of the encap type (no geneve opts).
249	# In inherit protocol mode there is no nested mac hdr and the nested
250	# l3 protocol type field belongs to the geneve hdr.
251	[ "$USE_HINT" = true ] && encl=16
252	[ "$INHERIT" = true ] && inner_maclen=0
253	[ "$INHERIT" = true ] && inner_proto_offset=-4
254	local inner=$((inner_maclen+encl))
255	local proto=$((inner_maclen+encl+inner_proto_offset))
256	bpf_filter=$(nfbpf_compile "(ip &&
257		ip[$((40+encl))] == 0x08 && ip[$((41+encl))] == 0x00 &&
258		ip[$((51+encl))] == 0x11 &&
259		ip[$((64+encl))] == 0x17 && ip[$((65+encl))] == 0xc2 &&
260		ip[$((76+proto))] == 0x08 && ip[$((77+proto))] == 0x00 &&
261		ip[$((87+inner))] == 0x6 &&
262		ip[$((100+inner))] == 0x1f && ip[$((101+inner))] == 0x40) ||
263		(ip6 &&
264		ip6[$((60+encl))] == 0x86 && ip6[$((61+encl))] == 0xdd &&
265		ip6[$((68+encl))] == 0x11 &&
266		ip6[$((104+encl))] == 0x17 && ip6[$((105+encl))] == 0xc2 &&
267		ip6[$((116+proto))] == 0x86 && ip6[$((117+proto))] == 0xdd &&
268		ip6[$((124+inner))] == 0x6 &&
269		ip6[$((160+inner))] == 0x1f && ip6[$((161+inner))] == 0x40)")
270
271	# ignore shorts packet, to avoid arp/mld induced noise
272	ip netns exec "$NS_SRC" "$ipt" -A OUTPUT -p udp --dport "$dport" \
273	   -m length --length 600:65535 -m bpf --bytecode "$bpf_filter"
274	ip netns exec "$NS_DST" "$ipt" -A INPUT -p udp --dport "$dport" \
275	   -m length --length 600:65535 -m bpf --bytecode "$bpf_filter"
276	ip netns exec "$NS_DST" ./udpgso_bench_rx -C 2000 -t -R 100 \
277	   -n "$pkts" "${rx_args[@]}" &
278	local pid=$!
279	wait_local_port_listen "$NS_DST" 8000 tcp
280	ip netns exec "$NS_SRC" ./udpgso_bench_tx -"$FAMILY" -t -M 1 \
281	   -s "$total_size" -D "$dst"
282	local ret=$?
283	check_err "$ret" "client failure exit code $ret"
284	wait "$pid"
285	ret=$?
286	check_err "$ret" "sever failure exit code $ret"
287
288	snd=$(ip netns exec "$NS_SRC" "$ipt"-save -c |
289	    grep "dport $dport" | sed -e 's/\[//' -e 's/:.*//')
290
291	[ "$snd" = "$wire_pkts" ]
292	# shellcheck disable=SC2319 # known false positive
293	check_err $? "send $snd packets on the lowest link, expected $wire_pkts"
294
295	rcvpkts=$(ip netns exec "$NS_DST" "$ipt"-save -c | \
296	   grep "dport $dport" | sed -e 's/\[//' -e 's/:.*//')
297
298	[ "$rcvpkts" = "$encappkts" ]
299	check_err $? "received $rcvpkts $ENCAP packets, expected $encappkts"
300	log_test "$msg"
301}
302
303run_tests() {
304	for FAMILY in 4 6; do
305		NET=$OL2_NET_V4
306		GSO_SIZE=1340 # 1392 - 20 - 32
307
308		if [ $FAMILY = 6 ]; then
309			NET=$OL2_NET_V6
310			GSO_SIZE=1280 # 1352 - 40 - 32
311		fi
312
313		echo "IPv$FAMILY"
314
315		unset USE_HINT
316		unset INHERIT
317
318		# "geneve" must be last encap in list, so that later
319		# test cases will run on it
320		for ENCAP in "vxlan" "geneve"; do
321			create_ns
322			run_test "No GSO - $ENCAP" $((GSO_SIZE * 4)) 4 4
323			cleanup_all_ns
324
325			create_ns_gso
326			run_test "GSO without GRO - $ENCAP" $((GSO_SIZE * 4)) \
327			   4 1
328			cleanup_all_ns
329
330			# IPv4 only test
331			[ $FAMILY = "4" ] || continue
332			create_ns_gso
333			ip netns exec "$NS_SRC" \
334			   sysctl -qw net.ipv4.ip_no_pmtu_disc=1
335			run_test "GSO disable due to no fixedid - $ENCAP" \
336			   $((GSO_SIZE * 4)) 4 4
337			cleanup_all_ns
338		done
339
340		# GRO tests imply/require geneve encap, the only one providing
341		# GRO hints
342		create_ns_gso_gro
343		run_test "double tunnel GRO, no hints" $((GSO_SIZE * 4)) 4
344		cleanup_all_ns
345
346		# hint option is expected for all the following tests in the RX
347		# path
348		USE_HINT=true
349		create_ns_gso_gro \
350		   '"gro-hint":1,"udp-zero-csum6-tx":1,"udp-zero-csum6-rx":1' \
351		   '"udp-zero-csum6-tx":1,"udp-zero-csum6-rx":1'
352		run_test "double tunnel GRO" $((GSO_SIZE * 4)) 1
353		cleanup_all_ns
354
355		create_ns_gso_gro '"gro-hint":1,"udp-csum":1' '"udp-csum":1'
356		run_test "double tunnel GRO - csum complete" $((GSO_SIZE * 4))\
357		   1
358		cleanup_all_ns
359
360		create_ns_gso_gro '"gro-hint":1' \
361		   '"udp-csum":0,"udp-zero-csum6-tx":1,"udp-zero-csum6-rx":1'
362		run_test "double tunnel GRO - no nested csum" \
363		   $((GSO_SIZE * 4)) 1
364		cleanup_all_ns
365
366		create_ns_gso_gro \
367		   '"gro-hint":1,"udp-zero-csum6-tx":1,"udp-zero-csum6-rx":1' \
368		   '"udp-csum":1'
369		run_test "double tunnel GRO - nested csum, outer 0-csum, skip"\
370		   $((GSO_SIZE * 4)) 4
371		cleanup_all_ns
372
373		INHERIT=true
374		create_ns_gso_gro '"gro-hint":1,"udp-csum":1' \
375		   '"udp-csum":1,"inner-proto-inherit":1'
376		run_test "double tunnel GRO - nested inherit proto" \
377		   $((GSO_SIZE * 4)) 1
378		cleanup_all_ns
379		unset INHERIT
380
381		create_ns_gso_gro '"gro-hint":1'
382		run_test "double tunnel GRO - short last pkt" \
383		   $((GSO_SIZE * 4 + GSO_SIZE / 2)) 2
384		cleanup_all_ns
385	done
386}
387
388require_command nfbpf_compile
389require_command jq
390
391# tcp retransmisions will break the accounting
392xfail_on_slow run_tests
393exit "$EXIT_STATUS"
394