diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index 9cd153ab..e66c0be3 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -332,14 +332,20 @@ static int FlushQueuedSend(WOLFSSH* ssh, wolfSSL_Mutex* lock) if (lock != NULL) { wc_UnLockMutex(lock); } - } while (ret == WS_WANT_WRITE && WTIME(NULL) < deadline); - /* The queue is out. Whatever the worker made of the peer's end of the - * conversation is for the reader to sort out. A rekey started on the way - * through is the reader's as well, the send itself went out. */ - if (ret == WS_WANT_READ || ret == WS_CHAN_RXD || ret == WS_EXTDATA - || ret == WS_REKEYING || ret == WS_EOF) { - ret = WS_SUCCESS; + /* None of these is a failure for the flush. */ + if (ret == WS_WANT_READ || ret == WS_CHAN_RXD || ret == WS_EXTDATA + || ret == WS_REKEYING || ret == WS_EOF + || ret == WS_WANT_WRITE) { + ret = WS_SUCCESS; + } + } while (ret == WS_SUCCESS + && wolfSSH_get_error(ssh) == WS_WANT_WRITE + && WTIME(NULL) < deadline); + + /* The deadline can run out with the packet still queued. */ + if (ret == WS_SUCCESS && wolfSSH_get_error(ssh) == WS_WANT_WRITE) { + ret = WS_WANT_WRITE; } return ret; diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index 3e6d9eda..eb857e18 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -2056,6 +2056,8 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, /* read in case a window-change packet might be queued */ { int rc; + int selected = WS_SELECT_SEND_READY; + WS_SOCKET_T fd = wolfSSH_get_fd(ssh); word32 lastChannel = 0; do { @@ -2063,7 +2065,20 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, if (rc < 0) { rc = wolfSSH_get_error(ssh); } - } while (rc == WS_WANT_WRITE); + if (rc == WS_WANT_WRITE) { + selected = tcp_select_write(fd, 1); + } + } while (rc == WS_WANT_WRITE + && selected == WS_SELECT_SEND_READY); + + /* A dead socket ends the session */ + if (ret == WS_SUCCESS + && (selected == WS_SELECT_ERROR_READY + || selected == WS_SELECT_FAIL + || rc == WS_SOCKET_ERROR_E + || rc == WS_DISCONNECT)) { + ret = WS_FATAL_ERROR; + } } } } @@ -2263,6 +2278,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, cnt_r = wolfSSH_worker(ssh, &lastChannel); if (cnt_r < 0) { rc = wolfSSH_get_error(ssh); + if (cnt_r == WS_CHAN_RXD || cnt_r == WS_CHANNEL_CLOSED + || cnt_r == WS_EOF) { + rc = cnt_r; + } if (rc == WS_CHAN_RXD) { if (lastChannel == shellChannelId) { cnt_r = wolfSSH_ChannelIdRead(ssh, @@ -2962,6 +2981,14 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, cnt_r = wolfSSH_worker(ssh, NULL); if (cnt_r < 0) { rc = wolfSSH_get_error(ssh); + /* Take the owed write before the event overwrites rc. */ + if (rc == WS_WANT_WRITE) { + wantWrite = 1; + } + if (cnt_r == WS_CHAN_RXD || cnt_r == WS_CHANNEL_CLOSED + || cnt_r == WS_EOF) { + rc = cnt_r; + } if (rc == WS_CHAN_RXD) { /* Arrival only; the drain below owns the read. */ } @@ -2984,8 +3011,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, /* Half-close, handled below. */ } else if (rc == WS_WANT_WRITE) { - wantWrite = 1; - continue; + /* Recorded above; the channel drain below still runs. */ } else if (rc == WS_REKEYING) { wantWrite = 1; @@ -3019,6 +3045,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, if (cnt_r <= 0) break; + /* The credit that read issued may still be queued. */ + if (wolfSSH_get_error(ssh) == WS_WANT_WRITE) + wantWrite = 1; + childInIdx = 0; childInSz = cnt_r; diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 5a08e393..e45ee806 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -1035,9 +1035,11 @@ static int ssh_worker(thread_ctx_t* threadCtx) channel. The additional channel is only used with the agent. */ cnt_r = wolfSSH_worker(ssh, &lastChannel); - /* Take the worker's status before the drain below: its - * reads and sends latch their own into ssh->error. */ rc = wolfSSH_get_error(ssh); + if (cnt_r == WS_CHAN_RXD || cnt_r == WS_REKEYING + || cnt_r == WS_CHANNEL_CLOSED || cnt_r == WS_EOF) { + rc = cnt_r; + } /* The peer is done sending: hand back the backlog and answer * its EOF, since the library no longer answers for us. Off @@ -1530,8 +1532,12 @@ static int sftp_worker(thread_ctx_t* threadCtx) ret = error = wolfSSH_get_error(ssh); /* there is an edge case where the last SFTP handshake message sent got a - * WANT_WRITE case, keep trying to send it here. */ + * WANT_WRITE case, keep trying to send it here. Waits for the socket to + * take bytes again rather than retrying into a full one. */ while (error == WS_WANT_WRITE) { + selected = tcp_select_write(s, TEST_SFTP_TIMEOUT); + if (selected != WS_SELECT_SEND_READY) + break; ret = wolfSSH_worker(ssh, NULL); error = wolfSSH_get_error(ssh); } diff --git a/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c b/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c index f55b0212..0e8db096 100644 --- a/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c +++ b/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c @@ -995,9 +995,11 @@ static int ssh_worker(thread_ctx_t* threadCtx) channel. The additional channel is only used with the agent. */ cnt_r = wolfSSH_worker(ssh, &lastChannel); - /* Take the worker's status before the drain below: its - * reads and sends latch their own into ssh->error. */ rc = wolfSSH_get_error(ssh); + if (cnt_r == WS_CHAN_RXD || cnt_r == WS_REKEYING + || cnt_r == WS_CHANNEL_CLOSED || cnt_r == WS_EOF) { + rc = cnt_r; + } /* The peer is done sending: hand back the backlog and answer * its EOF, since the library no longer answers for us. Off diff --git a/src/wolfscp.c b/src/wolfscp.c index 1fbd2baf..f75da559 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -1788,6 +1788,10 @@ int ReceiveScpMessage(WOLFSSH* ssh) int rc; rc = wolfSSH_get_error(ssh); + if (err == WS_CHAN_RXD || err == WS_EXTDATA + || err == WS_CHANNEL_CLOSED) { + rc = err; + } switch (rc) { case WS_CHAN_RXD: sz = wolfSSH_ChannelIdRead(ssh, lastChannel, diff --git a/wolfssh/test.h b/wolfssh/test.h index 7da65731..8ed3d24d 100644 --- a/wolfssh/test.h +++ b/wolfssh/test.h @@ -714,7 +714,8 @@ enum { WS_SELECT_FAIL, WS_SELECT_TIMEOUT, WS_SELECT_RECV_READY, - WS_SELECT_ERROR_READY + WS_SELECT_ERROR_READY, + WS_SELECT_SEND_READY }; #if (defined(WOLFSSH_TEST_SERVER) || defined(WOLFSSH_TEST_CLIENT)) && !defined(FREESCALE_MQX) @@ -795,6 +796,35 @@ static INLINE int tcp_select(SOCKET_T socketfd, int to_sec) return WS_SELECT_FAIL; } + +/* tcp_select() waits on the read side. This is the write side, for a caller + * holding output the socket would not take. */ +static INLINE int tcp_select_write(SOCKET_T socketfd, int to_sec) +{ + WFD_SET_TYPE sendfds, errfds; + int nfds = (int)socketfd + 1; + struct timeval timeout = {(to_sec > 0) ? to_sec : 0, 100}; + int result; + + WFD_ZERO(&sendfds); + WFD_SET(socketfd, &sendfds); + WFD_ZERO(&errfds); + WFD_SET(socketfd, &errfds); + + result = wSelect(nfds, NULL, &sendfds, &errfds, &timeout); + + if (result == 0) + return WS_SELECT_TIMEOUT; + else if (result > 0) { + if (WFD_ISSET(socketfd, &sendfds)) + return WS_SELECT_SEND_READY; + else if (WFD_ISSET(socketfd, &errfds)) + return WS_SELECT_ERROR_READY; + } + + return WS_SELECT_FAIL; +} + #endif /* WOLFSSH_TEST_SERVER || WOLFSSH_TEST_CLIENT */