wolfssh/scripts/fwd-bulk.test

331 lines
11 KiB
Bash
Executable File

#!/bin/sh
# Bulk data through a local direct-tcpip forward.
#
# [nc client] --plain--> :$entry_port [portfwd]
# |
# SSH
# |
# [echoserver] --plain--> :$target_port [nc server]
#
# scripts/fwd.test moves a few hundred bytes, so nothing in the suite
# exercises a forward past its first window. Phase 1 pushes a payload several
# windows long and compares the bytes that come out. Phase 2 ends a transfer
# one window plus a short tail in, the point where the tail is still sitting
# in portfwd's buffer waiting on window credit when the local socket reports
# end-of-input.
#
# The listening nc takes its stdin from a long sleep on purpose. Reading
# end-of-input on stdin makes nc close the connection, which truncates the
# transfer at whatever point the close lands and looks exactly like a
# forwarding stall.
no_pid=-1
hold_pid=$no_pid
nc_server_pid=$no_pid
server_pid=$no_pid
portfwd_pid=$no_pid
nc_client_pid=$no_pid
tail_hold_pid=$no_pid
tail_nc_server_pid=$no_pid
tail_server_pid=$no_pid
tail_portfwd_pid=$no_pid
tail_nc_client_pid=$no_pid
work_dir="`pwd`/wolfssh_fwd_bulk$$"
ready_file="$work_dir/ready"
fwd_ready_file="$work_dir/fwd_ready"
payload="$work_dir/payload"
hold_fifo="$work_dir/hold"
received="$work_dir/received"
server_log="$work_dir/server.log"
portfwd_log="$work_dir/portfwd.log"
tail_payload="$work_dir/tail_payload"
tail_received="$work_dir/tail_received"
tail_server_log="$work_dir/tail_server.log"
tail_portfwd_log="$work_dir/tail_portfwd.log"
# Several times the 128K default window, so the transfer cannot finish
# without the window being credited back at least once. The size the
# receiver is held to is read back from the file dd made.
payload_blocks=2000
payload_size=0
entry_port=0
target_port=0
port=0
counter=0
# Phase 2. One default window plus a tail short enough to be read in a single
# pass, so the tail is what portfwd is holding when end-of-input arrives.
# Whether the credit for the window beats the tail is a race, won here about
# half the time and never on some hosts, so the transfer is repeated. A build
# that overrides DEFAULT_WINDOW_SZ just moves bytes and proves nothing.
tail_window_size=131072
tail_size=2000
tail_attempts=6
tail_payload_size=0
tail_entry_port=0
tail_target_port=0
tail_got=0
# Seconds to wait for the payload. Generous: the point is that a stalled
# forward fails this test instead of hanging make check.
transfer_limit=90
# Consecutive seconds with no new bytes before calling it stalled.
stall_limit=15
[ ! -x "`command -v nc`" ] && echo "nc doesn't exist, skipping" && exit 77
[ ! -x ./examples/echoserver/echoserver ] \
&& echo "echoserver doesn't exist, skipping" && exit 77
./examples/echoserver/echoserver '-?' 2>&1 | grep -q "^echoserver " \
|| { echo "echoserver doesn't run, skipping"; exit 77; }
[ ! -x ./examples/portfwd/portfwd ] \
&& echo "portfwd doesn't exist, skipping" && exit 77
./examples/portfwd/portfwd '-?' 2>&1 | grep -q "does not exist" \
&& { echo "forwarding not compiled in, skipping"; exit 77; }
WOLFSSH_OPTIONS=`./apps/wolfssh-options` || {
echo "fail: could not run ./apps/wolfssh-options"
exit 1
}
echo "$WOLFSSH_OPTIONS" | grep -qx "TEST_BLOCK" \
&& { echo "portfwd does not support non-blocking mode, skipping test"
exit 77; }
do_cleanup() {
for pid in $nc_client_pid $portfwd_pid $server_pid $nc_server_pid \
$hold_pid $tail_nc_client_pid $tail_portfwd_pid \
$tail_server_pid $tail_nc_server_pid $tail_hold_pid
do
if [ "$pid" != "$no_pid" ]
then
kill -9 "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
fi
done
rm -rf "$work_dir"
}
# The failure here is usually just a byte count; the logs name the cause.
do_dump_logs() {
for log in "$server_log" "$portfwd_log" "$tail_server_log" \
"$tail_portfwd_log"
do
[ -f "$log" ] || continue
echo "--- `basename "$log"` ---"
cat "$log" 2>/dev/null
done
}
do_fail() {
echo "$1"
do_dump_logs
do_cleanup
exit 1
}
do_trap() {
echo "got trap"
do_cleanup
exit 1
}
trap do_trap INT TERM
do_reap_tail() {
for pid in $tail_nc_client_pid $tail_portfwd_pid $tail_server_pid \
$tail_nc_server_pid $tail_hold_pid
do
if [ "$pid" != "$no_pid" ]
then
kill -9 "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
fi
done
tail_nc_client_pid=$no_pid
tail_portfwd_pid=$no_pid
tail_server_pid=$no_pid
tail_nc_server_pid=$no_pid
tail_hold_pid=$no_pid
}
# One window-plus-tail transfer through a forward of its own, leaving the
# bytes that arrived in tail_got. Each attempt takes its own ports: the
# previous one's are in TIME_WAIT.
do_tail_attempt() {
tail_entry_port=`expr 16000 + $1 \* 1000 + \( $$ % 1000 \)`
tail_target_port=`expr 22000 + $1 \* 1000 + \( $$ % 1000 \)`
tail_fifo="$work_dir/tail_hold.$1"
tail_ready_file="$work_dir/tail_ready.$1"
tail_fwd_ready_file="$work_dir/tail_fwd_ready.$1"
tail_got=0
rm -f "$tail_received"
mkfifo "$tail_fifo" || do_fail "couldn't make the fifo"
sleep 300 > "$tail_fifo" 2>/dev/null &
tail_hold_pid=$!
nc -l $tail_target_port < "$tail_fifo" > "$tail_received" 2>/dev/null &
tail_nc_server_pid=$!
sleep 0.2
kill -0 "$tail_nc_server_pid" 2>/dev/null \
|| do_fail "couldn't listen on port $tail_target_port, is it in use?"
./examples/echoserver/echoserver -1 -f -R "$tail_ready_file" \
> "$tail_server_log" 2>&1 &
tail_server_pid=$!
counter=0
while [ ! -s "$tail_ready_file" ] && [ "$counter" -lt 20 ]; do
sleep 0.1
counter=`expr $counter + 1`
done
[ -s "$tail_ready_file" ] \
|| do_fail "no ready file, echoserver didn't start"
./examples/portfwd/portfwd -u jill -P upthehill \
-p `cat "$tail_ready_file"` \
-f $tail_entry_port -t $tail_target_port -R "$tail_fwd_ready_file" \
> "$tail_portfwd_log" 2>&1 &
tail_portfwd_pid=$!
counter=0
while [ ! -s "$tail_fwd_ready_file" ] && [ "$counter" -lt 20 ]; do
sleep 0.1
counter=`expr $counter + 1`
done
[ -s "$tail_fwd_ready_file" ] \
|| do_fail "no ready file, portfwd didn't start"
nc 127.0.0.1 $tail_entry_port < "$tail_payload" > /dev/null 2>&1 &
tail_nc_client_pid=$!
# A dropped tail leaves the count one tail short of the payload, so stop
# as soon as it stops moving instead of waiting the whole limit out.
counter=0
last=-1
while [ "$tail_got" -lt "$tail_payload_size" ] && [ "$counter" -lt 24 ]
do
sleep 0.5
counter=`expr $counter + 1`
tail_got=`wc -c < "$tail_received" 2>/dev/null | tr -d ' '`
[ -z "$tail_got" ] && tail_got=0
[ "$tail_got" = "$last" ] && [ "$counter" -gt 4 ] && break
last=$tail_got
done
do_reap_tail
}
mkdir -p "$work_dir" || { echo "couldn't make the work directory"; exit 1; }
# portfwd and nc bind what they are told, so the plaintext ports have to be
# picked here. Stamped with the pid so concurrent runs of the suite do not
# collide, kept below the ephemeral range so an outgoing connection cannot
# take one first, and clear of the ports fwd.test.expect hardcodes.
entry_port=`expr 14000 + \( $$ % 1000 \)`
target_port=`expr 15000 + \( $$ % 1000 \)`
dd if=/dev/urandom of="$payload" bs=1000 count=$payload_blocks 2>/dev/null \
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
payload_size=`wc -c < "$payload" | tr -d ' '`
[ "$payload_size" -gt 0 ] \
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
# nc closes the connection when it reads end-of-input on stdin, which
# truncates the transfer. Hold stdin open with a sleep on the far side of a
# fifo, so both ends have a pid this script can reap.
mkfifo "$hold_fifo" || { echo "couldn't make the fifo"; do_cleanup; exit 1; }
sleep 300 > "$hold_fifo" 2>/dev/null &
hold_pid=$!
nc -l $target_port < "$hold_fifo" > "$received" 2>/dev/null &
nc_server_pid=$!
# A port already in use makes nc exit at once, and the echoserver's connect
# would then be refused. Catch that here, or it reports as a stall.
sleep 0.2
if ! kill -0 "$nc_server_pid" 2>/dev/null
then
nc_server_pid=$no_pid
echo "couldn't listen on port $target_port, is it in use?"
do_cleanup
exit 1
fi
./examples/echoserver/echoserver -1 -f -R "$ready_file" \
> "$server_log" 2>&1 &
server_pid=$!
counter=0
while [ ! -s "$ready_file" ] && [ "$counter" -lt 20 ]; do
sleep 0.1
counter=`expr $counter + 1`
done
[ -s "$ready_file" ] || do_fail "no ready file, echoserver didn't start"
port=`cat "$ready_file"`
./examples/portfwd/portfwd -u jill -P upthehill -p "$port" \
-f $entry_port -t $target_port -R "$fwd_ready_file" \
> "$portfwd_log" 2>&1 &
portfwd_pid=$!
counter=0
while [ ! -s "$fwd_ready_file" ] && [ "$counter" -lt 20 ]; do
sleep 0.1
counter=`expr $counter + 1`
done
[ -s "$fwd_ready_file" ] || do_fail "no ready file, portfwd didn't start"
nc 127.0.0.1 $entry_port < "$payload" > /dev/null 2>&1 &
nc_client_pid=$!
counter=0
got=0
last=0
stalled=0
while [ "$got" -lt "$payload_size" ] && [ "$counter" -lt "$transfer_limit" ]
do
sleep 1
counter=`expr $counter + 1`
got=`wc -c < "$received" 2>/dev/null | tr -d ' '`
[ -z "$got" ] && got=0
# Give up early once the byte count stops moving, so a stalled forward
# reports in seconds instead of burning the whole limit.
if [ "$got" -eq "$last" ]
then
stalled=`expr $stalled + 1`
[ "$stalled" -ge "$stall_limit" ] && break
else
stalled=0
last=$got
fi
done
[ "$got" -eq "$payload_size" ] \
|| do_fail "forward stalled: sent $payload_size bytes, received $got"
cmp -s "$payload" "$received" \
|| do_fail "forwarded data does not match what was sent"
echo "moved $payload_size bytes through a local forward"
# --- Phase 2: end the transfer one window in --------------------------------
dd if=/dev/urandom of="$tail_payload" bs=4096 count=32 2>/dev/null \
&& dd if=/dev/urandom bs=$tail_size count=1 2>/dev/null \
>> "$tail_payload" \
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
tail_payload_size=`wc -c < "$tail_payload" | tr -d ' '`
[ "$tail_payload_size" -eq `expr $tail_window_size + $tail_size` ] \
|| { echo "couldn't make the payload"; do_cleanup; exit 1; }
attempt=0
while [ "$attempt" -lt "$tail_attempts" ]
do
do_tail_attempt $attempt
[ "$tail_got" -eq "$tail_payload_size" ] \
|| do_fail "dropped the tail: sent $tail_payload_size, got $tail_got"
cmp -s "$tail_payload" "$tail_received" \
|| do_fail "the tail transfer does not match what was sent"
attempt=`expr $attempt + 1`
done
echo "ended $tail_attempts transfers on a window boundary"
do_cleanup
exit 0