Skip to content

Commit f8b72ac

Browse files
ejohnstownphilljj
authored andcommitted
tests: move bulk data through a forward
scripts/fwd.test sends eight short lines, so nothing in the suite drives a forwarding channel past its first window, and a forward that stalled once the window needed crediting would go unnoticed. - push a payload several windows long through a local direct-tcpip forward and compare the bytes that arrive - end a second transfer one window plus a short tail in, where the tail is what portfwd still holds at end-of-input, and repeat it since the window credit beating that tail is a race - give the listening nc its stdin from a fifo a sleep holds open: reading end-of-input makes nc close the connection, which truncates the transfer and looks exactly like a stall - bail out early once the byte count stops moving, so a real stall reports in seconds - dump the logs on failure, check the listening nc came up, and take ports clear of the ones fwd.test.expect hardcodes, so a squatted or shared port is not reported as a stall
1 parent 31f831b commit f8b72ac

2 files changed

Lines changed: 333 additions & 0 deletions

File tree

scripts/fwd-bulk.test

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
#!/bin/sh
2+
3+
# Bulk data through a local direct-tcpip forward.
4+
#
5+
# [nc client] --plain--> :$entry_port [portfwd]
6+
# |
7+
# SSH
8+
# |
9+
# [echoserver] --plain--> :$target_port [nc server]
10+
#
11+
# scripts/fwd.test moves a few hundred bytes, so nothing in the suite
12+
# exercises a forward past its first window. Phase 1 pushes a payload several
13+
# windows long and compares the bytes that come out. Phase 2 ends a transfer
14+
# one window plus a short tail in, the point where the tail is still sitting
15+
# in portfwd's buffer waiting on window credit when the local socket reports
16+
# end-of-input.
17+
#
18+
# The listening nc takes its stdin from a long sleep on purpose. Reading
19+
# end-of-input on stdin makes nc close the connection, which truncates the
20+
# transfer at whatever point the close lands and looks exactly like a
21+
# forwarding stall.
22+
23+
no_pid=-1
24+
hold_pid=$no_pid
25+
nc_server_pid=$no_pid
26+
server_pid=$no_pid
27+
portfwd_pid=$no_pid
28+
nc_client_pid=$no_pid
29+
tail_hold_pid=$no_pid
30+
tail_nc_server_pid=$no_pid
31+
tail_server_pid=$no_pid
32+
tail_portfwd_pid=$no_pid
33+
tail_nc_client_pid=$no_pid
34+
work_dir="`pwd`/wolfssh_fwd_bulk$$"
35+
ready_file="$work_dir/ready"
36+
fwd_ready_file="$work_dir/fwd_ready"
37+
payload="$work_dir/payload"
38+
hold_fifo="$work_dir/hold"
39+
received="$work_dir/received"
40+
server_log="$work_dir/server.log"
41+
portfwd_log="$work_dir/portfwd.log"
42+
tail_payload="$work_dir/tail_payload"
43+
tail_received="$work_dir/tail_received"
44+
tail_server_log="$work_dir/tail_server.log"
45+
tail_portfwd_log="$work_dir/tail_portfwd.log"
46+
# Several times the 128K default window, so the transfer cannot finish
47+
# without the window being credited back at least once. The size the
48+
# receiver is held to is read back from the file dd made.
49+
payload_blocks=2000
50+
payload_size=0
51+
entry_port=0
52+
target_port=0
53+
port=0
54+
counter=0
55+
# Phase 2. One default window plus a tail short enough to be read in a single
56+
# pass, so the tail is what portfwd is holding when end-of-input arrives.
57+
# Whether the credit for the window beats the tail is a race, won here about
58+
# half the time and never on some hosts, so the transfer is repeated. A build
59+
# that overrides DEFAULT_WINDOW_SZ just moves bytes and proves nothing.
60+
tail_window_size=131072
61+
tail_size=2000
62+
tail_attempts=6
63+
tail_payload_size=0
64+
tail_entry_port=0
65+
tail_target_port=0
66+
tail_got=0
67+
# Seconds to wait for the payload. Generous: the point is that a stalled
68+
# forward fails this test instead of hanging make check.
69+
transfer_limit=90
70+
# Consecutive seconds with no new bytes before calling it stalled.
71+
stall_limit=15
72+
73+
[ ! -x "`command -v nc`" ] && echo "nc doesn't exist, skipping" && exit 77
74+
[ ! -x ./examples/echoserver/echoserver ] \
75+
&& echo "echoserver doesn't exist, skipping" && exit 77
76+
./examples/echoserver/echoserver '-?' 2>&1 | grep -q "^echoserver " \
77+
|| { echo "echoserver doesn't run, skipping"; exit 77; }
78+
[ ! -x ./examples/portfwd/portfwd ] \
79+
&& echo "portfwd doesn't exist, skipping" && exit 77
80+
./examples/portfwd/portfwd '-?' 2>&1 | grep -q "does not exist" \
81+
&& { echo "forwarding not compiled in, skipping"; exit 77; }
82+
83+
# A WOLFSSH_TEST_BLOCK build fails writes at random, which stalls the
84+
# echoserver regardless of what the peer does. The other echoserver scripts
85+
# skip it for the same reason.
86+
WOLFSSH_OPTIONS=`./apps/wolfssh-options` || {
87+
echo "fail: could not run ./apps/wolfssh-options"
88+
exit 1
89+
}
90+
echo "$WOLFSSH_OPTIONS" | grep -qx "TEST_BLOCK" \
91+
&& { echo "macro WOLFSSH_TEST_BLOCK was used, skipping"; exit 77; }
92+
93+
do_cleanup() {
94+
for pid in $nc_client_pid $portfwd_pid $server_pid $nc_server_pid \
95+
$hold_pid $tail_nc_client_pid $tail_portfwd_pid \
96+
$tail_server_pid $tail_nc_server_pid $tail_hold_pid
97+
do
98+
if [ "$pid" != "$no_pid" ]
99+
then
100+
kill -9 "$pid" 2>/dev/null
101+
wait "$pid" 2>/dev/null
102+
fi
103+
done
104+
rm -rf "$work_dir"
105+
}
106+
107+
# The failure here is usually just a byte count; the logs name the cause.
108+
do_dump_logs() {
109+
for log in "$server_log" "$portfwd_log" "$tail_server_log" \
110+
"$tail_portfwd_log"
111+
do
112+
[ -f "$log" ] || continue
113+
echo "--- `basename "$log"` ---"
114+
cat "$log" 2>/dev/null
115+
done
116+
}
117+
118+
do_fail() {
119+
echo "$1"
120+
do_dump_logs
121+
do_cleanup
122+
exit 1
123+
}
124+
125+
do_trap() {
126+
echo "got trap"
127+
do_cleanup
128+
exit 1
129+
}
130+
131+
trap do_trap INT TERM
132+
133+
do_reap_tail() {
134+
for pid in $tail_nc_client_pid $tail_portfwd_pid $tail_server_pid \
135+
$tail_nc_server_pid $tail_hold_pid
136+
do
137+
if [ "$pid" != "$no_pid" ]
138+
then
139+
kill -9 "$pid" 2>/dev/null
140+
wait "$pid" 2>/dev/null
141+
fi
142+
done
143+
tail_nc_client_pid=$no_pid
144+
tail_portfwd_pid=$no_pid
145+
tail_server_pid=$no_pid
146+
tail_nc_server_pid=$no_pid
147+
tail_hold_pid=$no_pid
148+
}
149+
150+
# One window-plus-tail transfer through a forward of its own, leaving the
151+
# bytes that arrived in tail_got. Each attempt takes its own ports: the
152+
# previous one's are in TIME_WAIT.
153+
do_tail_attempt() {
154+
tail_entry_port=`expr 16000 + $1 \* 1000 + \( $$ % 1000 \)`
155+
tail_target_port=`expr 22000 + $1 \* 1000 + \( $$ % 1000 \)`
156+
tail_fifo="$work_dir/tail_hold.$1"
157+
tail_ready_file="$work_dir/tail_ready.$1"
158+
tail_fwd_ready_file="$work_dir/tail_fwd_ready.$1"
159+
tail_got=0
160+
rm -f "$tail_received"
161+
162+
mkfifo "$tail_fifo" || do_fail "couldn't make the fifo"
163+
sleep 300 > "$tail_fifo" 2>/dev/null &
164+
tail_hold_pid=$!
165+
nc -l $tail_target_port < "$tail_fifo" > "$tail_received" 2>/dev/null &
166+
tail_nc_server_pid=$!
167+
168+
sleep 0.2
169+
kill -0 "$tail_nc_server_pid" 2>/dev/null \
170+
|| do_fail "couldn't listen on port $tail_target_port, is it in use?"
171+
172+
./examples/echoserver/echoserver -1 -f -R "$tail_ready_file" \
173+
> "$tail_server_log" 2>&1 &
174+
tail_server_pid=$!
175+
176+
counter=0
177+
while [ ! -s "$tail_ready_file" ] && [ "$counter" -lt 20 ]; do
178+
sleep 0.1
179+
counter=`expr $counter + 1`
180+
done
181+
[ -s "$tail_ready_file" ] \
182+
|| do_fail "no ready file, echoserver didn't start"
183+
184+
./examples/portfwd/portfwd -u jill -P upthehill \
185+
-p `cat "$tail_ready_file"` \
186+
-f $tail_entry_port -t $tail_target_port -R "$tail_fwd_ready_file" \
187+
> "$tail_portfwd_log" 2>&1 &
188+
tail_portfwd_pid=$!
189+
190+
counter=0
191+
while [ ! -s "$tail_fwd_ready_file" ] && [ "$counter" -lt 20 ]; do
192+
sleep 0.1
193+
counter=`expr $counter + 1`
194+
done
195+
[ -s "$tail_fwd_ready_file" ] \
196+
|| do_fail "no ready file, portfwd didn't start"
197+
198+
nc 127.0.0.1 $tail_entry_port < "$tail_payload" > /dev/null 2>&1 &
199+
tail_nc_client_pid=$!
200+
201+
# A dropped tail leaves the count one tail short of the payload, so stop
202+
# as soon as it stops moving instead of waiting the whole limit out.
203+
counter=0
204+
last=-1
205+
while [ "$tail_got" -lt "$tail_payload_size" ] && [ "$counter" -lt 24 ]
206+
do
207+
sleep 0.5
208+
counter=`expr $counter + 1`
209+
tail_got=`wc -c < "$tail_received" 2>/dev/null | tr -d ' '`
210+
[ -z "$tail_got" ] && tail_got=0
211+
[ "$tail_got" = "$last" ] && [ "$counter" -gt 4 ] && break
212+
last=$tail_got
213+
done
214+
215+
do_reap_tail
216+
}
217+
218+
mkdir -p "$work_dir" || { echo "couldn't make the work directory"; exit 1; }
219+
220+
# portfwd and nc bind what they are told, so the plaintext ports have to be
221+
# picked here. Stamped with the pid so concurrent runs of the suite do not
222+
# collide, kept below the ephemeral range so an outgoing connection cannot
223+
# take one first, and clear of the ports fwd.test.expect hardcodes.
224+
entry_port=`expr 14000 + \( $$ % 1000 \)`
225+
target_port=`expr 15000 + \( $$ % 1000 \)`
226+
227+
dd if=/dev/urandom of="$payload" bs=1000 count=$payload_blocks 2>/dev/null \
228+
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
229+
payload_size=`wc -c < "$payload" | tr -d ' '`
230+
[ "$payload_size" -gt 0 ] \
231+
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
232+
233+
# nc closes the connection when it reads end-of-input on stdin, which
234+
# truncates the transfer. Hold stdin open with a sleep on the far side of a
235+
# fifo, so both ends have a pid this script can reap.
236+
mkfifo "$hold_fifo" || { echo "couldn't make the fifo"; do_cleanup; exit 1; }
237+
sleep 300 > "$hold_fifo" 2>/dev/null &
238+
hold_pid=$!
239+
nc -l $target_port < "$hold_fifo" > "$received" 2>/dev/null &
240+
nc_server_pid=$!
241+
242+
# A port already in use makes nc exit at once, and the echoserver's connect
243+
# would then be refused. Catch that here, or it reports as a stall.
244+
sleep 0.2
245+
if ! kill -0 "$nc_server_pid" 2>/dev/null
246+
then
247+
nc_server_pid=$no_pid
248+
echo "couldn't listen on port $target_port, is it in use?"
249+
do_cleanup
250+
exit 1
251+
fi
252+
253+
./examples/echoserver/echoserver -1 -f -R "$ready_file" \
254+
> "$server_log" 2>&1 &
255+
server_pid=$!
256+
257+
counter=0
258+
while [ ! -s "$ready_file" ] && [ "$counter" -lt 20 ]; do
259+
sleep 0.1
260+
counter=`expr $counter + 1`
261+
done
262+
[ -s "$ready_file" ] || do_fail "no ready file, echoserver didn't start"
263+
port=`cat "$ready_file"`
264+
265+
./examples/portfwd/portfwd -u jill -P upthehill -p "$port" \
266+
-f $entry_port -t $target_port -R "$fwd_ready_file" \
267+
> "$portfwd_log" 2>&1 &
268+
portfwd_pid=$!
269+
270+
counter=0
271+
while [ ! -s "$fwd_ready_file" ] && [ "$counter" -lt 20 ]; do
272+
sleep 0.1
273+
counter=`expr $counter + 1`
274+
done
275+
[ -s "$fwd_ready_file" ] || do_fail "no ready file, portfwd didn't start"
276+
277+
nc 127.0.0.1 $entry_port < "$payload" > /dev/null 2>&1 &
278+
nc_client_pid=$!
279+
280+
counter=0
281+
got=0
282+
last=0
283+
stalled=0
284+
while [ "$got" -lt "$payload_size" ] && [ "$counter" -lt "$transfer_limit" ]
285+
do
286+
sleep 1
287+
counter=`expr $counter + 1`
288+
got=`wc -c < "$received" 2>/dev/null | tr -d ' '`
289+
[ -z "$got" ] && got=0
290+
# Give up early once the byte count stops moving, so a stalled forward
291+
# reports in seconds instead of burning the whole limit.
292+
if [ "$got" -eq "$last" ]
293+
then
294+
stalled=`expr $stalled + 1`
295+
[ "$stalled" -ge "$stall_limit" ] && break
296+
else
297+
stalled=0
298+
last=$got
299+
fi
300+
done
301+
302+
[ "$got" -eq "$payload_size" ] \
303+
|| do_fail "forward stalled: sent $payload_size bytes, received $got"
304+
305+
cmp -s "$payload" "$received" \
306+
|| do_fail "forwarded data does not match what was sent"
307+
308+
echo "moved $payload_size bytes through a local forward"
309+
310+
# --- Phase 2: end the transfer one window in --------------------------------
311+
dd if=/dev/urandom of="$tail_payload" bs=4096 count=32 2>/dev/null \
312+
&& dd if=/dev/urandom bs=$tail_size count=1 2>/dev/null \
313+
>> "$tail_payload" \
314+
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
315+
tail_payload_size=`wc -c < "$tail_payload" | tr -d ' '`
316+
[ "$tail_payload_size" -eq `expr $tail_window_size + $tail_size` ] \
317+
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
318+
319+
attempt=0
320+
while [ "$attempt" -lt "$tail_attempts" ]
321+
do
322+
do_tail_attempt $attempt
323+
[ "$tail_got" -eq "$tail_payload_size" ] \
324+
|| do_fail "dropped the tail: sent $tail_payload_size, got $tail_got"
325+
cmp -s "$tail_payload" "$tail_received" \
326+
|| do_fail "the tail transfer does not match what was sent"
327+
attempt=`expr $attempt + 1`
328+
done
329+
330+
echo "ended $tail_attempts transfers on a window boundary"
331+
do_cleanup
332+
exit 0

scripts/include.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ dist_noinst_SCRIPTS+= scripts/sshclient.test
1717

1818
dist_noinst_SCRIPTS+= scripts/fwd.test
1919
EXTRA_DIST += scripts/fwd.test.expect
20+
dist_noinst_SCRIPTS+= scripts/fwd-bulk.test

0 commit comments

Comments
 (0)