scp, test.h, apps, examples: wait to write and route on the return

- tcp_select_write() joins tcp_select(), with WS_SELECT_SEND_READY
  at the end of the enum. tcp_select() passes NULL for writefds and
  cannot wait on the write side.
- The Windows wolfsshd window-change drain and sftp_worker()'s
  handshake flush retry wait on it. The drain records a give-up in
  ret, and sftp_worker() breaks only on WS_SELECT_ERROR_READY.
- The echoserver, Espressif and both wolfsshd shell loops, and
  ReceiveScpMessage(), take the event from wolfSSH_worker()'s
  return when it carries one, in place of reading ssh->error alone.
  The two echoservers cover WS_CHAN_RXD, WS_REKEYING,
  WS_CHANNEL_CLOSED and WS_EOF; the wolfsshd loops and
  ReceiveScpMessage() cover the ones they have arms for.
- The POSIX wolfsshd loop takes an owed write into wantWrite before
  the event overwrites rc, and its WS_WANT_WRITE arm runs the
  channel drain below in place of skipping it.
- FlushQueuedSend() folds the receive's own statuses into
  WS_SUCCESS inside its loop and keeps flushing while
  wolfSSH_get_error() reports WS_WANT_WRITE within the deadline, in
  place of looping on the worker's return. It reports
  WS_WANT_WRITE when the deadline leaves the packet queued.
pull/1249/head
Yosuke Shimizu 2026-09-10 16:27:14 +09:00 committed by John Safranek
parent 586b697b18
commit 21deb724fe
6 changed files with 94 additions and 16 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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,

View File

@ -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 */