Skip to content

Commit d7b6e2a

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 7325678 commit d7b6e2a

6 files changed

Lines changed: 533 additions & 54 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: 1 addition & 1 deletion
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);

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: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3688,49 +3688,30 @@ int wolfSSH_worker(WOLFSSH* ssh, word32* channelId)
36883688
return WS_FATAL_ERROR;
36893689
}
36903690

3691-
#ifdef WOLFSSH_TEST_BLOCK
3692-
/* In forced non-blocking test mode, keep legacy ordering (send before
3693-
* receive) to match the harness expectations and avoid synthetic spins. */
3694-
if (ret == WS_SUCCESS) {
3695-
if (ssh->outputBuffer.length != 0)
3696-
ret = wolfSSH_SendPacket(ssh);
3697-
}
3698-
if (ret == WS_SUCCESS)
3699-
ret = DoReceive(ssh);
3700-
#else
37013691
/* Always service inbound data first so window updates can unblock sends. */
37023692
if (ret == WS_SUCCESS) {
37033693
ret = DoReceive(ssh);
37043694
}
37053695

3706-
/* If receive only wanted read or delivered channel data, still try to
3707-
* flush any pending outbound packets. */
3708-
if (ret == WS_SUCCESS || ret == WS_WANT_READ || ret == WS_CHAN_RXD
3709-
|| ret == WS_EOF) {
3710-
int sendRet = WS_SUCCESS;
3711-
3712-
if (ssh->outputBuffer.length != 0)
3713-
sendRet = wolfSSH_SendPacket(ssh);
3714-
3715-
/* If send is back-pressured, immediately try another receive to pick
3716-
* up potential window-adjusts and then return the send status. The
3717-
* send status wins; a peer EOF stays latched on the channel. */
3718-
if (sendRet == WS_WANT_WRITE || sendRet == WS_WINDOW_FULL) {
3719-
int recv2 = DoReceive(ssh);
3720-
if (recv2 == WS_SUCCESS || recv2 == WS_WANT_READ || recv2 == WS_CHAN_RXD
3721-
|| recv2 == WS_EOF)
3722-
ret = sendRet;
3723-
else
3724-
ret = recv2;
3725-
}
3726-
else {
3727-
/* Preserve meaningful receive status when send succeeded. */
3728-
if (sendRet != WS_SUCCESS)
3696+
/* Flush queued output whatever DoReceive() made of the socket, since an
3697+
* idle receive reports WS_FATAL_ERROR. !ssh->disconnected gates it. */
3698+
if (ssh != NULL && !ssh->disconnected && ssh->outputBuffer.length != 0) {
3699+
int rxErr = ssh->error;
3700+
int sendRet;
3701+
3702+
sendRet = wolfSSH_SendPacket(ssh);
3703+
if (sendRet != WS_SUCCESS) {
3704+
if (ret == WS_SUCCESS) {
37293705
ret = sendRet;
3730-
/* else leave ret as prior receive result (SUCCESS/WANT_READ/CHAN_RXD). */
3706+
}
3707+
else if (rxErr != WS_WANT_READ
3708+
&& ssh->outputBuffer.length != 0) {
3709+
/* ret still reports the receive, so put the receive's status
3710+
* back */
3711+
ssh->error = rxErr;
3712+
}
37313713
}
37323714
}
3733-
#endif /* WOLFSSH_TEST_BLOCK */
37343715

37353716
/* DoChannelClose() bundles the reply inside DoReceive(), and callers
37363717
* treat the close as terminal, so flush it here. The close stays the
@@ -4163,8 +4144,8 @@ static int _ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
41634144
}
41644145
}
41654146
else {
4166-
/* SendPacket() records only WS_WANT_WRITE, so a hard failure has to
4167-
* be recorded here; rewriting WS_WANT_WRITE is deliberate. */
4147+
/* The window adjust failed. ssh->error carries the code, and the
4148+
* log skips a WS_WANT_WRITE, which only asks for a retry. */
41684149
ssh->error = updateResult;
41694150
if (updateResult != WS_WANT_WRITE) {
41704151
WLOG(WS_LOG_ERROR,
@@ -4224,8 +4205,8 @@ static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
42244205
ssh->error = savedError;
42254206
}
42264207
else {
4227-
/* SendPacket() sets ssh->error only for WS_WANT_WRITE, so hard
4228-
* failures must be recorded here or they stay hidden. */
4208+
/* The window adjust failed. ssh->error carries the code, and
4209+
* the log skips a WS_WANT_WRITE, which only asks for a retry. */
42294210
ssh->error = adjustResult;
42304211
if (adjustResult != WS_WANT_WRITE) {
42314212
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)