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		else
69			echo "[ fail ] expected $expected found $nr"
70			ret=$test_cnt
71		fi
72	else
73		echo "[  ok  ]"
74	fi
75	test_cnt=$((test_cnt+1))
76}
77
78__chk_msk_nr()
79{
80	local condition=$1
81	shift 1
82
83	__chk_nr "ss -inmHMN $ns | $condition" "$@"
84}
85
86chk_msk_nr()
87{
88	__chk_msk_nr "grep -c token:" "$@"
89}
90
91wait_msk_nr()
92{
93	local condition="grep -c token:"
94	local expected=$1
95	local timeout=20
96	local msg nr
97	local max=0
98	local i=0
99
100	shift 1
101	msg=$*
102
103	while [ $i -lt $timeout ]; do
104		nr=$(ss -inmHMN $ns | $condition)
105		[ $nr == $expected ] && break;
106		[ $nr -gt $max ] && max=$nr
107		i=$((i + 1))
108		sleep 1
109	done
110
111	printf "%-50s" "$msg"
112	if [ $i -ge $timeout ]; then
113		echo "[ fail ] timeout while expecting $expected max $max last $nr"
114		ret=$test_cnt
115	elif [ $nr != $expected ]; then
116		echo "[ fail ] expected $expected found $nr"
117		ret=$test_cnt
118	else
119		echo "[  ok  ]"
120	fi
121	test_cnt=$((test_cnt+1))
122}
123
124chk_msk_fallback_nr()
125{
126	__chk_msk_nr "grep -c fallback" "$@"
127}
128
129chk_msk_remote_key_nr()
130{
131	__chk_msk_nr "grep -c remote_key" "$@"
132}
133
134__chk_listen()
135{
136	local filter="$1"
137	local expected=$2
138	local msg="$3"
139
140	__chk_nr "ss -N $ns -Ml '$filter' | grep -c LISTEN" "$expected" "$msg" 0
141}
142
143chk_msk_listen()
144{
145	lport=$1
146
147	# destination port search should always return empty list
148	__chk_listen "dport $lport" 0 "listen match for dport $lport"
149
150	# should return 'our' mptcp listen socket
151	__chk_listen "sport $lport" 1 "listen match for sport $lport"
152
153	__chk_listen "src inet:0.0.0.0:$lport" 1 "listen match for saddr and sport"
154
155	__chk_listen "" 1 "all listen sockets"
156
157	nr=$(ss -Ml $filter | wc -l)
158}
159
160chk_msk_inuse()
161{
162	local expected=$1
163	local msg="$2"
164	local listen_nr
165
166	listen_nr=$(ss -N "${ns}" -Ml | grep -c LISTEN)
167	expected=$((expected + listen_nr))
168
169	for _ in $(seq 10); do
170		if [ $(get_msk_inuse) -eq $expected ];then
171			break
172		fi
173		sleep 0.1
174	done
175
176	__chk_nr get_msk_inuse $expected "$msg" 0
177}
178
179# $1: ns, $2: port
180wait_local_port_listen()
181{
182	local listener_ns="${1}"
183	local port="${2}"
184
185	local port_hex i
186
187	port_hex="$(printf "%04X" "${port}")"
188	for i in $(seq 10); do
189		ip netns exec "${listener_ns}" cat /proc/net/tcp | \
190			awk "BEGIN {rc=1} {if (\$2 ~ /:${port_hex}\$/ && \$4 ~ /0A/) {rc=0; exit}} END {exit rc}" &&
191			break
192		sleep 0.1
193	done
194}
195
196wait_connected()
197{
198	local listener_ns="${1}"
199	local port="${2}"
200
201	local port_hex i
202
203	port_hex="$(printf "%04X" "${port}")"
204	for i in $(seq 10); do
205		ip netns exec ${listener_ns} grep -q " 0100007F:${port_hex} " /proc/net/tcp && break
206		sleep 0.1
207	done
208}
209
210trap cleanup EXIT
211ip netns add $ns
212ip -n $ns link set dev lo up
213
214echo "a" | \
215	timeout ${timeout_test} \
216		ip netns exec $ns \
217			./mptcp_connect -p 10000 -l -t ${timeout_poll} -w 20 \
218				0.0.0.0 >/dev/null &
219wait_local_port_listen $ns 10000
220chk_msk_nr 0 "no msk on netns creation"
221chk_msk_listen 10000
222
223echo "b" | \
224	timeout ${timeout_test} \
225		ip netns exec $ns \
226			./mptcp_connect -p 10000 -r 0 -t ${timeout_poll} -w 20 \
227				127.0.0.1 >/dev/null &
228wait_connected $ns 10000
229chk_msk_nr 2 "after MPC handshake "
230chk_msk_remote_key_nr 2 "....chk remote_key"
231chk_msk_fallback_nr 0 "....chk no fallback"
232chk_msk_inuse 2 "....chk 2 msk in use"
233flush_pids
234
235chk_msk_inuse 0 "....chk 0 msk in use after flush"
236
237echo "a" | \
238	timeout ${timeout_test} \
239		ip netns exec $ns \
240			./mptcp_connect -p 10001 -l -s TCP -t ${timeout_poll} -w 20 \
241				0.0.0.0 >/dev/null &
242wait_local_port_listen $ns 10001
243echo "b" | \
244	timeout ${timeout_test} \
245		ip netns exec $ns \
246			./mptcp_connect -p 10001 -r 0 -t ${timeout_poll} -w 20 \
247				127.0.0.1 >/dev/null &
248wait_connected $ns 10001
249chk_msk_fallback_nr 1 "check fallback"
250chk_msk_inuse 1 "....chk 1 msk in use"
251flush_pids
252
253chk_msk_inuse 0 "....chk 0 msk in use after flush"
254
255NR_CLIENTS=100
256for I in `seq 1 $NR_CLIENTS`; do
257	echo "a" | \
258		timeout ${timeout_test} \
259			ip netns exec $ns \
260				./mptcp_connect -p $((I+10001)) -l -w 20 \
261					-t ${timeout_poll} 0.0.0.0 >/dev/null &
262done
263wait_local_port_listen $ns $((NR_CLIENTS + 10001))
264
265for I in `seq 1 $NR_CLIENTS`; do
266	echo "b" | \
267		timeout ${timeout_test} \
268			ip netns exec $ns \
269				./mptcp_connect -p $((I+10001)) -w 20 \
270					-t ${timeout_poll} 127.0.0.1 >/dev/null &
271done
272
273wait_msk_nr $((NR_CLIENTS*2)) "many msk socket present"
274chk_msk_inuse $((NR_CLIENTS*2)) "....chk many msk in use"
275flush_pids
276
277chk_msk_inuse 0 "....chk 0 msk in use after flush"
278
279exit $ret
280