-
Notifications
You must be signed in to change notification settings - Fork 121
portfwd: drain the buffer before leaving on EOF #1228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+345
−2
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,332 @@ | ||
| #!/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; } | ||
|
|
||
| # A WOLFSSH_TEST_BLOCK build fails writes at random, which stalls the | ||
| # echoserver regardless of what the peer does. The other echoserver scripts | ||
| # skip it for the same reason. | ||
| WOLFSSH_OPTIONS=`./apps/wolfssh-options` || { | ||
| echo "fail: could not run ./apps/wolfssh-options" | ||
| exit 1 | ||
| } | ||
| echo "$WOLFSSH_OPTIONS" | grep -qx "TEST_BLOCK" \ | ||
| && { echo "macro WOLFSSH_TEST_BLOCK was used, skipping"; 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.