1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4. "$(dirname "${0}")/mptcp_lib.sh"
5
6sec=$(date +%s)
7rndh=$(printf %x $sec)-$(mktemp -u XXXXXX)
8ns="ns1-$rndh"
9ksft_skip=4
10test_cnt=1
11timeout_poll=100
12timeout_test=$((timeout_poll * 2 + 1))
13ret=0
14
15flush_pids()
16{
17	# mptcp_connect in join mode will sleep a bit before completing,
18	# give it some time
19	sleep 1.1
20
21	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGUSR1 &>/dev/null
22
23	for _ in $(seq 10); do
24		[ -z "$(ip netns pids "${ns}")" ] && break
25		sleep 0.1
26	done
27}
28
29cleanup()
30{
31	ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGKILL &>/dev/null
32
33	ip netns del $ns
34}
35
36mptcp_lib_check_mptcp
37
38ip -Version > /dev/null 2>&1
39if [ $? -ne 0 ];then
40	echo "SKIP: Could not run test without ip tool"
41	exit $ksft_skip
42fi
43ss -h | grep -q MPTCP
44if [ $? -ne 0 ];then
45	echo "SKIP: ss tool does not support MPTCP"
46	exit $ksft_skip
47fi
48
49get_msk_inuse()
50{
51	ip netns exec $ns cat /proc/net/protocols | awk '$1~/^MPTCP$/{print $3}'
52}
53
54__chk_nr()
55{
56	local command="$1"
57	local expected=$2
58	local msg="$3"
59	local skip="${4-SKIP}"
60	local nr
61
62	nr=$(eval $command)
63
64	printf "%-50s" "$msg"
65	if [ "$nr" != "$expected" ]; then
66		if [ "$nr" = "$skip" ] && ! mptcp_lib_expect_all_features; then
67			echo "[ skip ] Feature probably not supported"
68			mptcp_lib_result_skip "${msg}"
69		else
70			echo "[ fail ] expected $expected found $nr"
71			mptcp_lib_result_fail "${msg}"
72			ret=${KSFT_FAIL}
73		fi
74	else
75		echo "[  ok  ]"
76		mptcp_lib_result_pass "${msg}"
77	fi
78	test_cnt=$((test_cnt+1))
79}
80
81__chk_msk_nr()
82{
83	local condition=$1
84	shift 1
85
86	__chk_nr "ss -inmHMN $ns | $condition" "$@"
87}
88
89chk_msk_nr()
90{
91	__chk_msk_nr "grep -c token:" "$@"
92}
93
94wait_msk_nr()
95{
96	local condition="grep -c token:"
97	local expected=$1
98	local timeout=20
99	local msg nr
100	local max=0
101	local i=0
102
103	shift 1
104	msg=$*
105
106	while [ $i -lt $timeout ]; do
107		nr=$(ss -inmHMN $ns | $condition)
108		[ $nr == $expected ] && break;
109		[ $nr -gt $max ] && max=$nr
110		i=$((i + 1))
111		sleep 1
112	done
113
114	printf "%-50s" "$msg"
115	if [ $i -ge $timeout ]; then
116		echo "[ fail ] timeout while expecting $expected max $max last $nr"
117		mptcp_lib_result_fail "${msg} # timeout"
118		ret=${KSFT_FAIL}
119	elif [ $nr != $expected ]; then
120		echo "[ fail ] expected $expected found $nr"
121		mptcp_lib_result_fail "${msg} # unexpected result"
122		ret=${KSFT_FAIL}
123	else
124		echo "[  ok  ]"
125		mptcp_lib_result_pass "${msg}"
126	fi
127	test_cnt=$((test_cnt+1))
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: ns, $2: port
190wait_local_port_listen()
191{
192	local listener_ns="${1}"
193	local port="${2}"
194
195	local port_hex i
196
197	port_hex="$(printf "%04X" "${port}")"
198	for i in $(seq 10); do
199		ip netns exec "${listener_ns}" cat /proc/net/tcp | \
200			awk "BEGIN {rc=1} {if (\$2 ~ /:${port_hex}\$/ && \$4 ~ /0A/) {rc=0; exit}} END {exit rc}" &&
201			break
202		sleep 0.1
203	done
204}
205
206# $1: cestab nr
207chk_msk_cestab()
208{
209	local expected=$1
210	local msg="....chk ${2:-${expected}} cestab"
211
212	if [ "${expected}" -eq 0 ]; then
213		msg+=" after flush"
214	fi
215
216	__chk_nr "mptcp_lib_get_counter ${ns} MPTcpExtMPCurrEstab" \
217		 "${expected}" "${msg}" ""
218}
219
220wait_connected()
221{
222	local listener_ns="${1}"
223	local port="${2}"
224
225	local port_hex i
226
227	port_hex="$(printf "%04X" "${port}")"
228	for i in $(seq 10); do
229		ip netns exec ${listener_ns} grep -q " 0100007F:${port_hex} " /proc/net/tcp && break
230		sleep 0.1
231	done
232}
233
234trap cleanup EXIT
235ip netns add $ns
236ip -n $ns link set dev lo up
237
238echo "a" | \
239	timeout ${timeout_test} \
240		ip netns exec $ns \
241			./mptcp_connect -p 10000 -l -t ${timeout_poll} -w 20 \
242				0.0.0.0 >/dev/null &
243wait_local_port_listen $ns 10000
244chk_msk_nr 0 "no msk on netns creation"
245chk_msk_listen 10000
246
247echo "b" | \
248	timeout ${timeout_test} \
249		ip netns exec $ns \
250			./mptcp_connect -p 10000 -r 0 -t ${timeout_poll} -w 20 \
251				127.0.0.1 >/dev/null &
252wait_connected $ns 10000
253chk_msk_nr 2 "after MPC handshake "
254chk_msk_remote_key_nr 2 "....chk remote_key"
255chk_msk_fallback_nr 0 "....chk no fallback"
256chk_msk_inuse 2
257chk_msk_cestab 2
258flush_pids
259
260chk_msk_inuse 0 "2->0"
261chk_msk_cestab 0 "2->0"
262
263echo "a" | \
264	timeout ${timeout_test} \
265		ip netns exec $ns \
266			./mptcp_connect -p 10001 -l -s TCP -t ${timeout_poll} -w 20 \
267				0.0.0.0 >/dev/null &
268wait_local_port_listen $ns 10001
269echo "b" | \
270	timeout ${timeout_test} \
271		ip netns exec $ns \
272			./mptcp_connect -p 10001 -r 0 -t ${timeout_poll} -w 20 \
273				127.0.0.1 >/dev/null &
274wait_connected $ns 10001
275chk_msk_fallback_nr 1 "check fallback"
276chk_msk_inuse 1
277chk_msk_cestab 1
278flush_pids
279
280chk_msk_inuse 0 "1->0"
281chk_msk_cestab 0 "1->0"
282
283NR_CLIENTS=100
284for I in `seq 1 $NR_CLIENTS`; do
285	echo "a" | \
286		timeout ${timeout_test} \
287			ip netns exec $ns \
288				./mptcp_connect -p $((I+10001)) -l -w 20 \
289					-t ${timeout_poll} 0.0.0.0 >/dev/null &
290done
291wait_local_port_listen $ns $((NR_CLIENTS + 10001))
292
293for I in `seq 1 $NR_CLIENTS`; do
294	echo "b" | \
295		timeout ${timeout_test} \
296			ip netns exec $ns \
297				./mptcp_connect -p $((I+10001)) -w 20 \
298					-t ${timeout_poll} 127.0.0.1 >/dev/null &
299done
300
301wait_msk_nr $((NR_CLIENTS*2)) "many msk socket present"
302chk_msk_inuse $((NR_CLIENTS*2)) "many"
303chk_msk_cestab $((NR_CLIENTS*2)) "many"
304flush_pids
305
306chk_msk_inuse 0 "many->0"
307chk_msk_cestab 0 "many->0"
308
309mptcp_lib_result_print_all_tap
310exit $ret
311