Skip to content

Commit 7b1ec65

Browse files
ssh, internal: flush the worker's queued output on every call
- 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 or WS_CHAN_RXD. A failed flush reaches the return only when the receive reported nothing; ssh->error keeps the receive's code when the receive reported anything but WS_WANT_READ and the flush left bytes queued to retry. Drops the second DoReceive(), its WS_WINDOW_FULL case, and the WOLFSSH_TEST_BLOCK fork. - wolfSSH_SendPacket() records its code in ssh->error on every transport failure path, and the comments on _ChannelRead(), _ChannelReadExt() and test_ChannelReadExtHardFailureReported() no longer describe the former contract. - The echoserver shell loop and the Windows wolfsshd shell loop treat a WS_WANT_WRITE from wolfSSH_worker() as non-fatal. - 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 and an out-of-bounds send, and that queued output stays unsent on the pass that receives a disconnect.
1 parent bfe6fe0 commit 7b1ec65

6 files changed

Lines changed: 553 additions & 44 deletions

File tree

apps/wolfsshd/wolfsshd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
13311331
else if (rc == WS_CHANNEL_CLOSED) {
13321332
continue;
13331333
}
1334-
else if (rc != WS_WANT_READ) {
1334+
else if (rc != WS_WANT_READ && rc != WS_WANT_WRITE) {
13351335
break;
13361336
}
13371337
}

examples/echoserver/echoserver.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,7 @@ static int ssh_worker(thread_ctx_t* threadCtx)
11171117
#endif
11181118
continue;
11191119
}
1120-
else if (rc != WS_WANT_READ) {
1120+
else if (rc != WS_WANT_READ && rc != WS_WANT_WRITE) {
11211121
#ifdef SHELL_DEBUG
11221122
printf("Break:read sshFd returns %d: errno =%x\n",
11231123
cnt_r, errno);

src/internal.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4290,13 +4290,15 @@ static int GetInputLine(WOLFSSH* ssh, byte** pEol)
42904290
}
42914291

42924292

4293-
/* returns WS_SUCCESS on success */
4293+
/* returns WS_SUCCESS on success. Transport failures record their code in
4294+
* ssh->error */
42944295
int wolfSSH_SendPacket(WOLFSSH* ssh)
42954296
{
42964297
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendPacket()");
42974298

42984299
if (ssh->ctx->ioSendCb == NULL) {
42994300
WLOG(WS_LOG_DEBUG, "Your IO Send callback is null, please set");
4301+
ssh->error = WS_SOCKET_ERROR_E;
43004302
return WS_SOCKET_ERROR_E;
43014303
}
43024304

@@ -4307,6 +4309,7 @@ int wolfSSH_SendPacket(WOLFSSH* ssh)
43074309
if (ssh->outputBuffer.length > ssh->outputBuffer.bufferSz ||
43084310
ssh->outputBuffer.length < ssh->outputBuffer.idx) {
43094311
WLOG(WS_LOG_ERROR, "Bad buffer state");
4312+
ssh->error = WS_BUFFER_E;
43104313
return WS_BUFFER_E;
43114314
}
43124315

@@ -4332,11 +4335,13 @@ int wolfSSH_SendPacket(WOLFSSH* ssh)
43324335
case WS_CBIO_ERR_GENERAL:
43334336
ShrinkBuffer(&ssh->outputBuffer, 1);
43344337
}
4338+
ssh->error = WS_SOCKET_ERROR_E;
43354339
return WS_SOCKET_ERROR_E;
43364340
}
43374341

43384342
if ((word32)sent > ssh->outputBuffer.length) {
43394343
WLOG(WS_LOG_DEBUG, "wolfSSH_SendPacket() out of bounds read");
4344+
ssh->error = WS_SEND_OOB_READ_E;
43404345
return WS_SEND_OOB_READ_E;
43414346
}
43424347

src/ssh.c

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3614,46 +3614,30 @@ int wolfSSH_worker(WOLFSSH* ssh, word32* channelId)
36143614
return WS_FATAL_ERROR;
36153615
}
36163616

3617-
#ifdef WOLFSSH_TEST_BLOCK
3618-
/* In forced non-blocking test mode, keep legacy ordering (send before
3619-
* receive) to match the harness expectations and avoid synthetic spins. */
3620-
if (ret == WS_SUCCESS) {
3621-
if (ssh->outputBuffer.length != 0)
3622-
ret = wolfSSH_SendPacket(ssh);
3623-
}
3624-
if (ret == WS_SUCCESS)
3625-
ret = DoReceive(ssh);
3626-
#else
36273617
/* Always service inbound data first so window updates can unblock sends. */
36283618
if (ret == WS_SUCCESS) {
36293619
ret = DoReceive(ssh);
36303620
}
36313621

3632-
/* If receive only wanted read or delivered channel data, still try to
3633-
* flush any pending outbound packets. */
3634-
if (ret == WS_SUCCESS || ret == WS_WANT_READ || ret == WS_CHAN_RXD) {
3635-
int sendRet = WS_SUCCESS;
3636-
3637-
if (ssh->outputBuffer.length != 0)
3638-
sendRet = wolfSSH_SendPacket(ssh);
3622+
/* Flush queued output whatever DoReceive() made of the socket, since an
3623+
* idle receive reports WS_FATAL_ERROR. !ssh->disconnected gates it. */
3624+
if (ssh != NULL && !ssh->disconnected && ssh->outputBuffer.length != 0) {
3625+
int rxErr = ssh->error;
3626+
int sendRet;
36393627

3640-
/* If send is back-pressured, immediately try another receive to pick
3641-
* up potential window-adjusts and then return the send status. */
3642-
if (sendRet == WS_WANT_WRITE || sendRet == WS_WINDOW_FULL) {
3643-
int recv2 = DoReceive(ssh);
3644-
if (recv2 == WS_SUCCESS || recv2 == WS_WANT_READ || recv2 == WS_CHAN_RXD)
3645-
ret = sendRet;
3646-
else
3647-
ret = recv2;
3648-
}
3649-
else {
3650-
/* Preserve meaningful receive status when send succeeded. */
3651-
if (sendRet != WS_SUCCESS)
3628+
sendRet = wolfSSH_SendPacket(ssh);
3629+
if (sendRet != WS_SUCCESS) {
3630+
if (ret == WS_SUCCESS) {
36523631
ret = sendRet;
3653-
/* else leave ret as prior receive result (SUCCESS/WANT_READ/CHAN_RXD). */
3632+
}
3633+
else if (rxErr != WS_WANT_READ
3634+
&& ssh->outputBuffer.length != 0) {
3635+
/* ret still reports the receive, so put the receive's status
3636+
* back */
3637+
ssh->error = rxErr;
3638+
}
36543639
}
36553640
}
3656-
#endif /* WOLFSSH_TEST_BLOCK */
36573641

36583642
/* WS_EXTDATA reports the channel too, so a multi-channel caller can route
36593643
* the drain to wolfSSH_ChannelIdReadExt(). */
@@ -4074,8 +4058,8 @@ static int _ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
40744058
}
40754059
}
40764060
else {
4077-
/* SendPacket() records only WS_WANT_WRITE, so a hard failure has to
4078-
* be recorded here; rewriting WS_WANT_WRITE is deliberate. */
4061+
/* The window adjust failed. ssh->error carries the code, and the
4062+
* log skips a WS_WANT_WRITE, which only asks for a retry. */
40794063
ssh->error = updateResult;
40804064
if (updateResult != WS_WANT_WRITE) {
40814065
WLOG(WS_LOG_ERROR,
@@ -4135,8 +4119,8 @@ static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
41354119
ssh->error = savedError;
41364120
}
41374121
else {
4138-
/* SendPacket() sets ssh->error only for WS_WANT_WRITE, so hard
4139-
* failures must be recorded here or they stay hidden. */
4122+
/* The window adjust failed. ssh->error carries the code, and
4123+
* the log skips a WS_WANT_WRITE, which only asks for a retry. */
41404124
ssh->error = adjustResult;
41414125
if (adjustResult != WS_WANT_WRITE) {
41424126
WLOG(WS_LOG_ERROR,

tests/regress.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4499,11 +4499,19 @@ static void TestWorkerReportsDisconnect(void)
44994499
wolfSSH_SetIOReadCtx(ssh, &io);
45004500
wolfSSH_SetIOWriteCtx(ssh, &io);
45014501

4502+
/* Queued output, so the flush on this pass has something to push. */
4503+
ssh->outputBuffer.length = 1;
4504+
ssh->outputBuffer.idx = 0;
4505+
ssh->outputBuffer.buffer[0] = 0;
4506+
45024507
AssertIntEQ(wolfSSH_worker(ssh, NULL), WS_FATAL_ERROR);
45034508
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
45044509
AssertTrue(ssh->disconnected);
45054510
AssertTrue(ssh->isKeying != 0);
4506-
io.outSz = 0;
4511+
4512+
/* The queued byte stays put: the session ended on this very pass. */
4513+
AssertIntEQ(io.outSz, 0);
4514+
AssertTrue(wolfSSH_OutputPending(ssh));
45074515

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

0 commit comments

Comments
 (0)