1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4sec=$(date +%s)
5rndh=$(printf %x $sec)-$(mktemp -u XXXXXX)
6ns="ns1-$rndh"
7ksft_skip=4
8test_cnt=1
9timeout_poll=100
10timeout_test=$((timeout_poll * 2 + 1))
11ret=0
12
13flush_pids()
14{
15	# mptcp_connect in join mode will sleep a bit before completing,
16	# give it some time
17	sleep 1.1
18
19	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGUSR1 &>/dev/null
20
21	for _ in $(seq 10); do
22		[ -z "$(ip netns pids "${ns}")" ] && break
23		sleep 0.1
24	done
25}
26
27cleanup()
28{
29	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGKILL &>/dev/null
30
31	ip netns del $ns
32}
33
34ip -Version > /dev/null 2>&1
35if [ $? -ne 0 ];then
36	echo "SKIP: Could not run test without ip tool"
37	exit $ksft_skip
38fi
39ss -h | grep -q MPTCP
40if [ $? -ne 0 ];then
41	echo "SKIP: ss tool does not support MPTCP"
42	exit $ksft_skip
43fi
44
45get_msk_inuse()
46{
47	ip netns exec $ns cat /proc/net/protocols | awk '$1~/^MPTCP$/{print $3}'
48}
49
50__chk_nr()
51{
52	local command="$1"
53	local expected=$2
54	local msg nr
55
56	shift 2
57	msg=$*
58	nr=$(eval $command)
59
60	printf "%-50s" "$msg"
61	if [ $nr != $expected ]; then
62		echo "[ fail ] expected $expected found $nr"
63		ret=$test_cnt
64	else
65		echo "[  ok  ]"
66	fi
67	test_cnt=$((test_cnt+1))
68}
69
70__chk_msk_nr()
71{
72	local condition=$1
73	shift 1
74
75	__chk_nr "ss -inmHMN $ns | $condition" $*
76}
77
78chk_msk_nr()
79{
80	__chk_msk_nr "grep -c token:" $*
81}
82
83wait_msk_nr()
84{
85	local condition="grep -c token:"
86	local expected=$1
87	local timeout=20
88	local msg nr
89	local max=0
90	local i=0
91
92	shift 1
93	msg=$*
94
95	while [ $i -lt $timeout ]; do
96		nr=$(ss -inmHMN $ns | $condition)
97		[ $nr == $expected ] && break;
98		[ $nr -gt $max ] && max=$nr
99		i=$((i + 1))
100		sleep 1
101	done
102
103	printf "%-50s" "$msg"
104	if [ $i -ge $timeout ]; then
105		echo "[ fail ] timeout while expecting $expected max $max last $nr"
106		ret=$test_cnt
107	elif [ $nr != $expected ]; then
108		echo "[ fail ] expected $expected found $nr"
109		ret=$test_cnt
110	else
111		echo "[  ok  ]"
112	fi
113	test_cnt=$((test_cnt+1))
114}
115
116chk_msk_fallback_nr()
117{
118		__chk_msk_nr "grep -c fallback" $*
119}
120
121chk_msk_remote_key_nr()
122{
123		__chk_msk_nr "grep -c remote_key" $*
124}
125
126__chk_listen()
127{
128	local filter="$1"
129	local expected=$2
130
131	shift 2
132	msg=$*
133
134	nr=$(ss -N $ns -Ml "$filter" | grep -c LISTEN)
135	printf "%-50s" "$msg"
136
137	if [ $nr != $expected ]; then
138		echo "[ fail ] expected $expected found $nr"
139		ret=$test_cnt
140	else
141		echo "[  ok  ]"
142	fi
143}
144
145chk_msk_listen()
146{
147	lport=$1
148	local msg="check for listen socket"
149
150	# destination port search should always return empty list
151	__chk_listen "dport $lport" 0 "listen match for dport $lport"
152
153	# should return 'our' mptcp listen socket
154	__chk_listen "sport $lport" 1 "listen match for sport $lport"
155
156	__chk_listen "src inet:0.0.0.0:$lport" 1 "listen match for saddr and sport"
157
158	__chk_listen "" 1 "all listen sockets"
159
160	nr=$(ss -Ml $filter | wc -l)
161}
162
163chk_msk_inuse()
164{
165	local expected=$1
166	local listen_nr
167
168	shift 1
169
170	listen_nr=$(ss -N "${ns}" -Ml | grep -c LISTEN)
171	expected=$((expected + listen_nr))
172
173	for _ in $(seq 10); do
174		if [ $(get_msk_inuse) -eq $expected ];then
175			break
176		fi
177		sleep 0.1
178	done
179
180	__chk_nr get_msk_inuse $expected $*
181}
182
183# $1: ns, $2: port
184wait_local_port_listen()
185{
186	local listener_ns="${1}"
187	local port="${2}"
188
189	local port_hex i
190
191	port_hex="$(printf "%04X" "${port}")"
192	for i in $(seq 10); do
193		ip netns exec "${listener_ns}" cat /proc/net/tcp | \
194			awk "BEGIN {rc=1} {if (\$2 ~ /:${port_hex}\$/ && \$4 ~ /0A/) {rc=0; exit}} END {exit rc}" &&
195			break
196		sleep 0.1
197	done
198}
199
200wait_connected()
201{
202	local listener_ns="${1}"
203	local port="${2}"
204
205	local port_hex i
206
207	port_hex="$(printf "%04X" "${port}")"
208	for i in $(seq 10); do
209		ip netns exec ${listener_ns} grep -q " 0100007F:${port_hex} " /proc/net/tcp && break
210		sleep 0.1
211	done
212}
213
214trap cleanup EXIT
215ip netns add $ns
216ip -n $ns link set dev lo up
217
218echo "a" | \
219	timeout ${timeout_test} \
220		ip netns exec $ns \
221			./mptcp_connect -p 10000 -l -t ${timeout_poll} -w 20 \
222				0.0.0.0 >/dev/null &
223wait_local_port_listen $ns 10000
224chk_msk_nr 0 "no msk on netns creation"
225chk_msk_listen 10000
226
227echo "b" | \
228	timeout ${timeout_test} \
229		ip netns exec $ns \
230			./mptcp_connect -p 10000 -r 0 -t ${timeout_poll} -w 20 \
231				127.0.0.1 >/dev/null &
232wait_connected $ns 10000
233chk_msk_nr 2 "after MPC handshake "
234chk_msk_remote_key_nr 2 "....chk remote_key"
235chk_msk_fallback_nr 0 "....chk no fallback"
236chk_msk_inuse 2 "....chk 2 msk in use"
237flush_pids
238
239chk_msk_inuse 0 "....chk 0 msk in use after flush"
240
241echo "a" | \
242	timeout ${timeout_test} \
243		ip netns exec $ns \
244			./mptcp_connect -p 10001 -l -s TCP -t ${timeout_poll} -w 20 \
245				0.0.0.0 >/dev/null &
246wait_local_port_listen $ns 10001
247echo "b" | \
248	timeout ${timeout_test} \
249		ip netns exec $ns \
250			./mptcp_connect -p 10001 -r 0 -t ${timeout_poll} -w 20 \
251				127.0.0.1 >/dev/null &
252wait_connected $ns 10001
253chk_msk_fallback_nr 1 "check fallback"
254chk_msk_inuse 1 "....chk 1 msk in use"
255flush_pids
256
257chk_msk_inuse 0 "....chk 0 msk in use after flush"
258
259NR_CLIENTS=100
260for I in `seq 1 $NR_CLIENTS`; do
261	echo "a" | \
262		timeout ${timeout_test} \
263			ip netns exec $ns \
264				./mptcp_connect -p $((I+10001)) -l -w 20 \
265					-t ${timeout_poll} 0.0.0.0 >/dev/null &
266done
267wait_local_port_listen $ns $((NR_CLIENTS + 10001))
268
269for I in `seq 1 $NR_CLIENTS`; do
270	echo "b" | \
271		timeout ${timeout_test} \
272			ip netns exec $ns \
273				./mptcp_connect -p $((I+10001)) -w 20 \
274					-t ${timeout_poll} 127.0.0.1 >/dev/null &
275done
276
277wait_msk_nr $((NR_CLIENTS*2)) "many msk socket present"
278chk_msk_inuse $((NR_CLIENTS*2)) "....chk many msk in use"
279flush_pids
280
281chk_msk_inuse 0 "....chk 0 msk in use after flush"
282
283exit $ret
284