|
| 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 |
0 commit comments