Skip to content

Commit e2839fa

Browse files
ssh, internal: always flush the worker's output
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever ssh->outputBuffer holds bytes and the session is not disconnected, in place of doing so only for WS_SUCCESS, WS_WANT_READ, WS_CHAN_RXD or WS_EOF. A failed flush reaches the return only when the receive reported nothing. ssh->error keeps the receive's code when the receive failed, and the close's when a WS_CHANNEL_CLOSED pass hard-failed its flush. Drops the second DoReceive(), its WS_WINDOW_FULL case, the WOLFSSH_TEST_BLOCK fork, and the separate WS_CHANNEL_CLOSED flush. - SendPacketFlush() records its code in ssh->error on every transport failure path, and wolfSSH_SendPacket() says so. The comments on _ChannelRead(), _ChannelReadExt() and test_ChannelReadExtHardFailureReported() drop the former contract. - The echoserver and Espressif shell loops and the Windows wolfsshd shell loop treat a WS_WANT_WRITE from wolfSSH_worker() as non-fatal, and sftp_worker()'s handshake flush retry waits on tcp_select() between passes. - wolfSSH_stream_read()'s note no longer says a queued adjust needs a wolfSSH_worker() whose receive succeeded. - tests cover the flush on an idle receive, the owed flush across calls, what ssh->error holds after a receive failure, a hard send failure, channel data alongside a failed send, a discarded buffer, an out-of-bounds send and a close whose flush hard-failed, and queued output staying unsent on the disconnect pass.
1 parent 5151732 commit e2839fa

8 files changed

Lines changed: 622 additions & 67 deletions

File tree

apps/wolfsshd/wolfsshd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
13611361
* peer. Both want fixing where they can be tested. */
13621362
continue;
13631363
}
1364-
else if (rc != WS_WANT_READ) {
1364+
else if (rc != WS_WANT_READ && rc != WS_WANT_WRITE) {
13651365
break;
13661366
}
13671367
}

examples/echoserver/echoserver.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ static int ssh_worker(thread_ctx_t* threadCtx)
12051205
* above, which has already run this pass. */
12061206
continue;
12071207
}
1208-
else if (rc != WS_WANT_READ) {
1208+
else if (rc != WS_WANT_READ && rc != WS_WANT_WRITE) {
12091209
#ifdef SHELL_DEBUG
12101210
printf("Break:read sshFd returns %d: errno =%x\n",
12111211
cnt_r, errno);
@@ -1495,8 +1495,11 @@ static int sftp_worker(thread_ctx_t* threadCtx)
14951495
ret = error = wolfSSH_get_error(ssh);
14961496

14971497
/* there is an edge case where the last SFTP handshake message sent got a
1498-
* WANT_WRITE case, keep trying to send it here. */
1498+
* WANT_WRITE case, keep trying to send it here. tcp_select() watches the
1499+
* read side only, so this paces the retry, it does not wait to write. */
14991500
while (error == WS_WANT_WRITE) {
1501+
if (tcp_select(s, TEST_SFTP_TIMEOUT_SHORT) == WS_SELECT_ERROR_READY)
1502+
break;
15001503
ret = wolfSSH_worker(ssh, NULL);
15011504
error = wolfSSH_get_error(ssh);
15021505
}

ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1170,7 +1170,7 @@ static int ssh_worker(thread_ctx_t* threadCtx)
11701170
* above, which has already run this pass. */
11711171
continue;
11721172
}
1173-
else if (rc != WS_WANT_READ) {
1173+
else if (rc != WS_WANT_READ && rc != WS_WANT_WRITE) {
11741174
#ifdef SHELL_DEBUG
11751175
printf("Break:read sshFd returns %d: errno =%x\n",
11761176
cnt_r, errno);

src/internal.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4955,6 +4955,7 @@ static int SendPacketFlush(WOLFSSH* ssh)
49554955

49564956
if (ssh->ctx->ioSendCb == NULL) {
49574957
WLOG(WS_LOG_DEBUG, "Your IO Send callback is null, please set");
4958+
ssh->error = WS_SOCKET_ERROR_E;
49584959
return WS_SOCKET_ERROR_E;
49594960
}
49604961

@@ -4965,6 +4966,7 @@ static int SendPacketFlush(WOLFSSH* ssh)
49654966
if (ssh->outputBuffer.length > ssh->outputBuffer.bufferSz ||
49664967
ssh->outputBuffer.length < ssh->outputBuffer.idx) {
49674968
WLOG(WS_LOG_ERROR, "Bad buffer state");
4969+
ssh->error = WS_BUFFER_E;
49684970
return WS_BUFFER_E;
49694971
}
49704972

@@ -5003,11 +5005,13 @@ static int SendPacketFlush(WOLFSSH* ssh)
50035005
ssh->outputBuffer.plainSz = 0;
50045006
ShrinkBuffer(&ssh->outputBuffer, 1);
50055007
}
5008+
ssh->error = WS_SOCKET_ERROR_E;
50065009
return WS_SOCKET_ERROR_E;
50075010
}
50085011

50095012
if ((word32)sent > ssh->outputBuffer.length) {
50105013
WLOG(WS_LOG_DEBUG, "wolfSSH_SendPacket() out of bounds read");
5014+
ssh->error = WS_SEND_OOB_READ_E;
50115015
return WS_SEND_OOB_READ_E;
50125016
}
50135017

@@ -5032,7 +5036,8 @@ static int SendPacketFlush(WOLFSSH* ssh)
50325036
}
50335037

50345038

5035-
/* returns WS_SUCCESS on success */
5039+
/* returns WS_SUCCESS on success. Transport failures record their code in
5040+
* ssh->error */
50365041
int wolfSSH_SendPacket(WOLFSSH* ssh)
50375042
{
50385043
int ret;

src/ssh.c

Lines changed: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,59 +3720,31 @@ int wolfSSH_worker(WOLFSSH* ssh, word32* channelId)
37203720
return WS_FATAL_ERROR;
37213721
}
37223722

3723-
#ifdef WOLFSSH_TEST_BLOCK
3724-
/* In forced non-blocking test mode, keep legacy ordering (send before
3725-
* receive) to match the harness expectations and avoid synthetic spins. */
3726-
if (ret == WS_SUCCESS) {
3727-
if (ssh->outputBuffer.length != 0)
3728-
ret = wolfSSH_SendPacket(ssh);
3729-
}
3730-
if (ret == WS_SUCCESS)
3731-
ret = DoReceive(ssh);
3732-
#else
37333723
/* Always service inbound data first so window updates can unblock sends. */
37343724
if (ret == WS_SUCCESS) {
37353725
ret = DoReceive(ssh);
37363726
}
37373727

3738-
/* If receive only wanted read or delivered channel data, still try to
3739-
* flush any pending outbound packets. */
3740-
if (ret == WS_SUCCESS || ret == WS_WANT_READ || ret == WS_CHAN_RXD
3741-
|| ret == WS_EOF) {
3742-
int sendRet = WS_SUCCESS;
3743-
3744-
if (ssh->outputBuffer.length != 0)
3745-
sendRet = wolfSSH_SendPacket(ssh);
3746-
3747-
/* If send is back-pressured, immediately try another receive to pick
3748-
* up potential window-adjusts and then return the send status. The
3749-
* send status wins; a peer EOF stays latched on the channel. */
3750-
if (sendRet == WS_WANT_WRITE || sendRet == WS_WINDOW_FULL) {
3751-
int recv2 = DoReceive(ssh);
3752-
if (recv2 == WS_SUCCESS || recv2 == WS_WANT_READ || recv2 == WS_CHAN_RXD
3753-
|| recv2 == WS_EOF)
3754-
ret = sendRet;
3755-
else
3756-
ret = recv2;
3757-
}
3758-
else {
3759-
/* Preserve meaningful receive status when send succeeded. */
3760-
if (sendRet != WS_SUCCESS)
3728+
/* Flush queued output whatever DoReceive() made of the socket, since an
3729+
* idle receive reports WS_FATAL_ERROR. !ssh->disconnected gates it. */
3730+
if (ssh != NULL && !ssh->disconnected && ssh->outputBuffer.length != 0) {
3731+
int rxErr = ssh->error;
3732+
int sendRet;
3733+
3734+
sendRet = wolfSSH_SendPacket(ssh);
3735+
if (sendRet != WS_SUCCESS) {
3736+
if (ret == WS_SUCCESS) {
37613737
ret = sendRet;
3762-
/* else leave ret as prior receive result (SUCCESS/WANT_READ/CHAN_RXD). */
3738+
}
3739+
else if ((ret == WS_CHANNEL_CLOSED && sendRet != WS_WANT_WRITE)
3740+
|| (ret == WS_FATAL_ERROR && rxErr != WS_WANT_READ)) {
3741+
/* A failed receive outranks the flush, and so does a close
3742+
* whose flush hard-failed: callers route teardown on it.
3743+
* Every other status keeps the code the send set. */
3744+
ssh->error = rxErr;
3745+
}
37633746
}
37643747
}
3765-
#endif /* WOLFSSH_TEST_BLOCK */
3766-
3767-
/* DoChannelClose() bundles the reply inside DoReceive(), and callers
3768-
* treat the close as terminal, so flush it here. The close stays the
3769-
* return value; a short flush leaves WS_WANT_WRITE latched. */
3770-
if (ret == WS_CHANNEL_CLOSED && ssh->outputBuffer.length != 0) {
3771-
int closeErr = ssh->error;
3772-
3773-
if (wolfSSH_SendPacket(ssh) == WS_SUCCESS)
3774-
ssh->error = closeErr;
3775-
}
37763748

37773749
/* WS_EXTDATA and WS_EOF report the channel too, so a multi-channel caller
37783750
* can route the drain, or see which channel half-closed. */
@@ -4225,8 +4197,8 @@ static int _ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
42254197
}
42264198
}
42274199
else {
4228-
/* SendPacket() records only WS_WANT_WRITE, so a hard failure has to
4229-
* be recorded here; rewriting WS_WANT_WRITE is deliberate. */
4200+
/* The window adjust failed. ssh->error carries the code, and the
4201+
* log skips a WS_WANT_WRITE, which only asks for a retry. */
42304202
ssh->error = updateResult;
42314203
if (updateResult != WS_WANT_WRITE) {
42324204
WLOG(WS_LOG_ERROR,
@@ -4286,8 +4258,8 @@ static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
42864258
ssh->error = savedError;
42874259
}
42884260
else {
4289-
/* SendPacket() sets ssh->error only for WS_WANT_WRITE, so hard
4290-
* failures must be recorded here or they stay hidden. */
4261+
/* The window adjust failed. ssh->error carries the code, and
4262+
* the log skips a WS_WANT_WRITE, which only asks for a retry. */
42914263
ssh->error = adjustResult;
42924264
if (adjustResult != WS_WANT_WRITE) {
42934265
WLOG(WS_LOG_ERROR,

tests/regress.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6770,11 +6770,19 @@ static void TestWorkerReportsDisconnect(void)
67706770
wolfSSH_SetIOReadCtx(ssh, &io);
67716771
wolfSSH_SetIOWriteCtx(ssh, &io);
67726772

6773+
/* Queued output, so the flush on this pass has something to push. */
6774+
ssh->outputBuffer.length = 1;
6775+
ssh->outputBuffer.idx = 0;
6776+
ssh->outputBuffer.buffer[0] = 0;
6777+
67736778
AssertIntEQ(wolfSSH_worker(ssh, NULL), WS_FATAL_ERROR);
67746779
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
67756780
AssertTrue(ssh->disconnected);
67766781
AssertTrue(ssh->isKeying != 0);
6777-
io.outSz = 0;
6782+
6783+
/* The queued byte stays put: the session ended on this very pass. */
6784+
AssertIntEQ(io.outSz, 0);
6785+
AssertTrue(wolfSSH_OutputPending(ssh));
67786786

67796787
/* The message behind it is still queued, and every further pass reports
67806788
* the disconnect rather than the WS_SUCCESS of a skipped dispatch or the

0 commit comments

Comments
 (0)