xref: /linux/tools/testing/selftests/net/mptcp/diag.sh (revision 0fc8f6200d2313278fbf4539bbab74677c685531)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# Double quotes to prevent globbing and word splitting is recommended in new
5# code but we accept it, especially because there were too many before having
6# address all other issues detected by shellcheck.
7#shellcheck disable=SC2086
8
9. "$(dirname "${0}")/mptcp_lib.sh"
10
11ns=""
12timeout_poll=30
13timeout_test=$((timeout_poll * 2 + 1))
14ret=0
15
16flush_pids()
17{
18	# mptcp_connect in join mode will sleep a bit before completing,
19	# give it some time
20	sleep 1.1
21
22	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGUSR1 &>/dev/null
23
24	for _ in $(seq $((timeout_poll * 10))); do
25		[ -z "$(ip netns pids "${ns}")" ] && break
26		sleep 0.1
27	done
28}
29
30# This function is used in the cleanup trap
31#shellcheck disable=SC2317,SC2329
32cleanup()
33{
34	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGKILL &>/dev/null
35
36	mptcp_lib_ns_exit "${ns}"
37}
38
39mptcp_lib_check_mptcp
40mptcp_lib_check_tools ip ss
41
42get_msk_inuse()
43{
44	ip netns exec $ns cat /proc/net/protocols | awk '$1~/^MPTCP$/{print $3}'
45}
46
47__chk_nr()
48{
49	local command="$1"
50	local expected=$2
51	local msg="$3"
52	local skip="${4-SKIP}"
53	local nr
54
55	nr=$(eval $command)
56
57	mptcp_lib_print_title "$msg"
58	if [ "$nr" != "$expected" ]; then
59		if [ "$nr" = "$skip" ] && ! mptcp_lib_expect_all_features; then
60			mptcp_lib_pr_skip "Feature probably not supported"
61			mptcp_lib_result_skip "${msg}"
62		else
63			mptcp_lib_pr_fail "expected $expected found $nr"
64			mptcp_lib_result_fail "${msg}"
65			ret=${KSFT_FAIL}
66		fi
67	else
68		mptcp_lib_pr_ok
69		mptcp_lib_result_pass "${msg}"
70	fi
71}
72
73__chk_msk_nr()
74{
75	local condition=$1
76	shift 1
77
78	__chk_nr "ss -inmHMN $ns | $condition" "$@"
79}
80
81chk_msk_nr()
82{
83	__chk_msk_nr "grep -c token:" "$@"
84}
85
86chk_listener_nr()
87{
88	local expected=$1
89	local msg="$2"
90
91	__chk_nr "ss -nlHMON $ns | wc -l" "$expected" "$msg - mptcp" 0
92	__chk_nr "ss -nlHtON $ns | wc -l" "$expected" "$msg - subflows"
93}
94
95wait_msk_nr()
96{
97	local condition="grep -c token:"
98	local expected=$1
99	local timeout=20
100	local msg nr
101	local max=0
102	local i=0
103
104	shift 1
105	msg=$*
106
107	while [ $i -lt $timeout ]; do
108		nr=$(ss -inmHMN $ns | $condition)
109		[ $nr == $expected ] && break;
110		[ $nr -gt $max ] && max=$nr
111		i=$((i + 1))
112		sleep 1
113	done
114
115	mptcp_lib_print_title "$msg"
116	if [ $i -ge $timeout ]; then
117		mptcp_lib_pr_fail "timeout while expecting $expected max $max last $nr"
118		mptcp_lib_result_fail "${msg} # timeout"
119		ret=${KSFT_FAIL}
120	elif [ $nr != $expected ]; then
121		mptcp_lib_pr_fail "expected $expected found $nr"
122		mptcp_lib_result_fail "${msg} # unexpected result"
123		ret=${KSFT_FAIL}
124	else
125		mptcp_lib_pr_ok
126		mptcp_lib_result_pass "${msg}"
127	fi
128}
129
130chk_msk_fallback_nr()
131{
132	__chk_msk_nr "grep -c fallback" "$@"
133}
134
135chk_msk_remote_key_nr()
136{
137	__chk_msk_nr "grep -c remote_key" "$@"
138}
139
140__chk_listen()
141{
142	local filter="$1"
143	local expected=$2
144	local msg="$3"
145
146	__chk_nr "ss -N $ns -Ml '$filter' | grep -c LISTEN" "$expected" "$msg" 0
147}
148
149chk_msk_listen()
150{
151	lport=$1
152
153	# destination port search should always return empty list
154	__chk_listen "dport $lport" 0 "listen match for dport $lport"
155
156	# should return 'our' mptcp listen socket
157	__chk_listen "sport $lport" 1 "listen match for sport $lport"
158
159	__chk_listen "src inet:0.0.0.0:$lport" 1 "listen match for saddr and sport"
160
161	__chk_listen "" 1 "all listen sockets"
162
163	nr=$(ss -Ml $filter | wc -l)
164}
165
166chk_msk_inuse()
167{
168	local expected=$1
169	local msg="....chk ${2:-${expected}} msk in use"
170	local listen_nr
171
172	if [ "${expected}" -eq 0 ]; then
173		msg+=" after flush"
174	fi
175
176	listen_nr=$(ss -N "${ns}" -Ml | grep -c LISTEN)
177	expected=$((expected + listen_nr))
178
179	for _ in $(seq 10); do
180		if [ "$(get_msk_inuse)" -eq $expected ]; then
181			break
182		fi
183		sleep 0.1
184	done
185
186	__chk_nr get_msk_inuse $expected "${msg}" 0
187}
188
189# $1: cestab nr
190chk_msk_cestab()
191{
192	local expected=$1
193	local msg="....chk ${2:-${expected}} cestab"
194
195	if [ "${expected}" -eq 0 ]; then
196		msg+=" after flush"
197	fi
198
199	__chk_nr "mptcp_lib_get_counter ${ns} MPTcpExtMPCurrEstab" \
200		 "${expected}" "${msg}" ""
201}
202
203chk_dump_one()
204{
205	local ss_token
206	local token
207	local msg
208
209	ss_token="$(ss -inmHMN $ns |
210		    mptcp_lib_get_info_value "token" "token")"
211
212	token="$(ip netns exec $ns ./mptcp_diag -t $ss_token |\
213		 awk -F':[ \t]+' '/^token/ {print $2}')"
214
215	msg="....chk dump_one"
216
217	mptcp_lib_print_title "$msg"
218	if [ -n "$ss_token" ] && [ "$ss_token" = "$token" ]; then
219		mptcp_lib_pr_ok
220		mptcp_lib_result_pass "${msg}"
221	else
222		mptcp_lib_pr_fail "expected $ss_token found $token"
223		mptcp_lib_result_fail "${msg}"
224		ret=${KSFT_FAIL}
225	fi
226}
227
228chk_dump_subflow()
229{
230	local inet_diag_token
231	local subflow_line
232	local ss_output
233	local ss_token
234	local msg
235
236	ss_output=$(ss -tniN $ns)
237
238	subflow_line=$(echo "$ss_output" | \
239		       grep -m1 -Eo '[0-9.]+:[0-9].+ +[0-9.]+:[0-9.]+')
240
241	ss_token=$(echo "$ss_output" | grep -m1 -Eo 'token:[^ ]+')
242
243	inet_diag_token=$(ip netns exec $ns ./mptcp_diag -s "$subflow_line" | \
244			  grep -Eo 'token:[^ ]+')
245
246	msg="....chk dump_subflow"
247
248	mptcp_lib_print_title "$msg"
249	if [ -n "$ss_token" ] && [ "$ss_token" = "$inet_diag_token" ]; then
250		mptcp_lib_pr_ok
251		mptcp_lib_result_pass "${msg}"
252	else
253		mptcp_lib_pr_fail "expected $ss_token found $inet_diag_token"
254		mptcp_lib_result_fail "${msg}"
255		ret=${KSFT_FAIL}
256	fi
257}
258
259msk_info_get_value()
260{
261	local port="${1}"
262	local info="${2}"
263
264	ss -N "${ns}" -inHM dport "${port}" | \
265		mptcp_lib_get_info_value "${info}" "${info}"
266}
267
268chk_msk_info()
269{
270	local port="${1}"
271	local info="${2}"
272	local cnt="${3}"
273	local msg="....chk ${info}"
274	local delta_ms=250  # half what we waited before, just to be sure
275	local now
276
277	now=$(msk_info_get_value "${port}" "${info}")
278
279	mptcp_lib_print_title "${msg}"
280	if { [ -z "${cnt}" ] || [ -z "${now}" ]; } &&
281	   ! mptcp_lib_expect_all_features; then
282		mptcp_lib_pr_skip "Feature probably not supported"
283		mptcp_lib_result_skip "${msg}"
284	elif [ "$((cnt + delta_ms))" -lt "${now}" ]; then
285		mptcp_lib_pr_ok
286		mptcp_lib_result_pass "${msg}"
287	else
288		mptcp_lib_pr_fail "value of ${info} changed by $((now - cnt))ms," \
289				  "expected at least ${delta_ms}ms"
290		mptcp_lib_result_fail "${msg}"
291		ret=${KSFT_FAIL}
292	fi
293}
294
295chk_last_time_info()
296{
297	local port="${1}"
298	local data_sent data_recv ack_recv
299
300	data_sent=$(msk_info_get_value "${port}" "last_data_sent")
301	data_recv=$(msk_info_get_value "${port}" "last_data_recv")
302	ack_recv=$(msk_info_get_value "${port}" "last_ack_recv")
303
304	sleep 0.5  # wait to check after if the timestamps difference
305
306	chk_msk_info "${port}" "last_data_sent" "${data_sent}"
307	chk_msk_info "${port}" "last_data_recv" "${data_recv}"
308	chk_msk_info "${port}" "last_ack_recv" "${ack_recv}"
309}
310
311wait_connected()
312{
313	local listener_ns="${1}"
314	local port="${2}"
315
316	local port_hex i
317
318	port_hex="$(printf "%04X" "${port}")"
319	for i in $(seq 10); do
320		ip netns exec ${listener_ns} grep -q " 0100007F:${port_hex} " /proc/net/tcp && break
321		sleep 0.1
322	done
323}
324
325chk_sndbuf()
326{
327	local server_sndbuf client_sndbuf msg
328	local port=${1}
329
330	msg="....chk sndbuf server/client"
331	server_sndbuf=$(ss -N "${ns}" -inmHM "sport" "${port}" | \
332			sed -n 's/.*tb\([0-9]\+\).*/\1/p')
333	client_sndbuf=$(ss -N "${ns}" -inmHM "dport" "${port}" | \
334			sed -n 's/.*tb\([0-9]\+\).*/\1/p')
335
336	mptcp_lib_print_title "${msg}"
337	if [ -z "${server_sndbuf}" ] || [ -z "${client_sndbuf}" ]; then
338		mptcp_lib_pr_fail "sndbuf S=${server_sndbuf} C=${client_sndbuf}"
339		mptcp_lib_result_fail "${msg}"
340		ret=${KSFT_FAIL}
341	elif [ "${server_sndbuf}" != "${client_sndbuf}" ]; then
342		mptcp_lib_pr_fail "sndbuf S=${server_sndbuf} != C=${client_sndbuf}"
343		mptcp_lib_result_fail "${msg}"
344		ret=${KSFT_FAIL}
345	else
346		mptcp_lib_pr_ok
347		mptcp_lib_result_pass "${msg}"
348	fi
349}
350
351
352trap cleanup EXIT
353mptcp_lib_ns_init ns
354
355echo "a" | \
356	timeout ${timeout_test} \
357		ip netns exec $ns \
358			./mptcp_connect -p 10000 -l -t ${timeout_poll} -w 20 \
359				0.0.0.0 >/dev/null &
360mptcp_lib_wait_local_port_listen $ns 10000
361chk_msk_nr 0 "no msk on netns creation"
362chk_msk_listen 10000
363
364echo "b" | \
365	timeout ${timeout_test} \
366		ip netns exec $ns \
367			./mptcp_connect -p 10000 -r 0 -t ${timeout_poll} -w 20 \
368				127.0.0.1 >/dev/null &
369wait_connected $ns 10000
370chk_msk_nr 2 "after MPC handshake"
371chk_sndbuf 10000
372chk_last_time_info 10000
373chk_msk_remote_key_nr 2 "....chk remote_key"
374chk_msk_fallback_nr 0 "....chk no fallback"
375chk_msk_inuse 2
376chk_msk_cestab 2
377chk_dump_one
378chk_dump_subflow
379flush_pids
380
381chk_msk_inuse 0 "2->0"
382chk_msk_cestab 0 "2->0"
383
384echo "a" | \
385	timeout ${timeout_test} \
386		ip netns exec $ns \
387			./mptcp_connect -p 10001 -l -s TCP -t ${timeout_poll} -w 20 \
388				0.0.0.0 >/dev/null &
389mptcp_lib_wait_local_port_listen $ns 10001
390echo "b" | \
391	timeout ${timeout_test} \
392		ip netns exec $ns \
393			./mptcp_connect -p 10001 -r 0 -t ${timeout_poll} -w 20 \
394				127.0.0.1 >/dev/null &
395wait_connected $ns 10001
396chk_msk_fallback_nr 1 "check fallback"
397chk_msk_inuse 1
398chk_msk_cestab 1
399flush_pids
400
401chk_msk_inuse 0 "1->0"
402chk_msk_cestab 0 "1->0"
403
404NR_CLIENTS=100
405for I in $(seq 1 $NR_CLIENTS); do
406	echo "a" | \
407		timeout ${timeout_test} \
408			ip netns exec $ns \
409				./mptcp_connect -p $((I+10001)) -l -w 20 \
410					-t ${timeout_poll} 0.0.0.0 >/dev/null &
411done
412mptcp_lib_wait_local_port_listen $ns $((NR_CLIENTS + 10001))
413
414for I in $(seq 1 $NR_CLIENTS); do
415	echo "b" | \
416		timeout ${timeout_test} \
417			ip netns exec $ns \
418				./mptcp_connect -p $((I+10001)) -w 20 \
419					-t ${timeout_poll} 127.0.0.1 >/dev/null &
420done
421
422wait_msk_nr $((NR_CLIENTS*2)) "many msk socket present"
423chk_msk_inuse $((NR_CLIENTS*2)) "many"
424chk_msk_cestab $((NR_CLIENTS*2)) "many"
425flush_pids
426
427chk_msk_inuse 0 "many->0"
428chk_msk_cestab 0 "many->0"
429
430chk_listener_nr 0 "no listener sockets"
431NR_SERVERS=100
432for I in $(seq 1 $NR_SERVERS); do
433	ip netns exec $ns ./mptcp_connect -p $((I + 20001)) \
434		-t ${timeout_poll} -l 0.0.0.0 >/dev/null 2>&1 &
435done
436mptcp_lib_wait_local_port_listen $ns $((NR_SERVERS + 20001))
437
438chk_listener_nr $NR_SERVERS "many listener sockets"
439
440# graceful termination
441for I in $(seq 1 $NR_SERVERS); do
442	echo a | ip netns exec $ns ./mptcp_connect -p $((I + 20001)) 127.0.0.1 >/dev/null 2>&1 &
443done
444flush_pids
445
446mptcp_lib_result_print_all_tap
447exit $ret
448