examples/echoserver: hold what a channel send did not take - #1261
yosuke-wolfssl wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Critical EOF flushing and forwarding teardown issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Reworks echo-server buffering to preserve unsent data during short channel and descriptor writes, with regression tests for flow control and half-close behavior.
Changes:
- Adds persistent staging and reliable drain helpers.
- Updates echo, agent, and forwarding paths.
- Adds 8,000-byte round-trip regression tests.
File summaries
| File | Summary |
|---|---|
tests/api.c |
Adds flow-control, refill, and half-close tests. |
examples/echoserver/echoserver.c |
Preserves partial transfers across worker passes. |
Review details
Suppressed comments (1)
examples/echoserver/echoserver.c:1760
- When the local forwarding socket reaches EOF, this path changes
fwdCtxtoAPP_STATE_LISTENand continues without resettingbufferIdx/bufferOff. Any staged tail left by an earlier channel send therefore survives into the next accepted connection and is sent on its channel. Reset the staging fields on this EOF/reset teardown as well (and do the same for the analogous agent-socket close paths).
if (cnt_r == 0) {
/* Read zero-returned. Socket is closed. Go back
to listening. */
WCLOSESOCKET(fwdFd);
fwdFd = -1;
threadCtx->fwdCtx.appFd = -1;
if (threadCtx->fwdCbCtx.hostName != NULL) {
WFREE(threadCtx->fwdCbCtx.hostName, NULL, 0);
threadCtx->fwdCbCtx.hostName = NULL;
}
threadCtx->fwdCtx.state = APP_STATE_LISTEN;
continue;
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
d939176 to
89337e0
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1261
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
89337e0 to
8d87a43
Compare
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1261
Scan targets checked: wolfssh-src, wolfssh-bugs
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
ea034be to
44d9b8b
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1261
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
- WS_AppCtx gains bufferIdx and bufferOff; app_staged() reports the bytes its buffer still owes the channel. - app_drain_to_channel() advances bufferOff by what wolfSSH_ChannelIdSend() took, keeps the rest on WS_WANT_WRITE, WS_WINDOW_FULL, WS_REKEYING, WS_CHANNEL_NOT_CONF and WS_CHAN_RXD, and raises wantWrite for the first of those. For the other four it puts back the error code it found on entry, so the status of a send that is only owed does not outlive the call. A send reporting more than it was given, or zero, ends the session. - The shell, agent and forward reads stage into their own WS_AppCtx buffer and drain through it; their descriptors leave the select read set while bufferOff is short of bufferIdx. - app_echo_pump() reads the shell channel into shellCtx.buffer, hands each chunk to process_bytes() and drains it back. It reports the channel dry on a zero read alone, and a negative one stops the pump without ending the session. ssh_worker() runs it on every pass rather than on a data report. - process_bytes() sees every echoed chunk, including the backlog that goes out behind the peer's EOF, and only ever clears ChildRunning. - The EOF reply takes its drained state from app_echo_pump(); eofBuffer, eofOff and eofRead are dropped. - app_write_all() writes a whole buffer to the pty, agent and forward descriptors, retrying an interrupted write, in place of the single write() and send() calls on the channel-to-descriptor paths. - The agent and forward contexts clear their staging as they enter a connection; the forward's recv() fills the buffer from the start, and fwdBufferIdx and cnt_w are dropped. Issue: F-10544
44d9b8b to
5df5245
Compare
Problem
ssh_worker()treated every positivewrite(),send()andwolfSSH_ChannelIdSend()as a complete transfer.
SendChannelData()clamps each send tomin(peerWindowSz, peerMaxPacketSz), so short sends are routine and seven sitesdiscarded the unsent suffix — the forwarding path reset its buffer index on any
positive return. Not confined to small windows: a 4 MB echo at the default window
loses 98 KiB. Closes f-10544 (High).
Fix (
examples/echoserver/echoserver.c)WS_AppCtxgainsbufferIdx/bufferOff, so each direction keeps the tail its sinkwould not take.
app_drain_to_channel()advancesbufferOffby what the send took and holds therest on
WS_WANT_WRITE,WS_WINDOW_FULL,WS_REKEYING,WS_CHANNEL_NOT_CONFandWS_CHAN_RXD— restoring the entry error code for the latter four, so an "owed"status is not read as fatal on the next pass.
app_echo_pump()reads and echoes the shell channel, replacing the EOF drain(
eofBuffer,eofOff,eofReadare gone). It runs every pass, since data alreadybuffered inside the library draws no second data report.
app_write_all()writes whole buffers to the pty, agent and forward descriptors.A descriptor leaves the read set while its buffer is non-empty, and a context clears
its staging as it enters a connection. Holds are retired by existing wakes:
wolfSSH_OutputPending()arms the write set, and inbound traffic arrives on a socketalready in the read set.
Verification
Manual, since the tests are deferred. Same client and constrained window, master vs
this branch:
make check13/13 (incl.fwd.test,fwd-bulk.test); GCC-Werrorclean on 6configurations; ASan + UBSan clean; Zephyr qemu_x86 3/3.
Not in this PR
builds, hitting a separate defect that reproduces on
master— the echo path treatsa rekey-blocked send as fatal. They land with that fix.
dump_stats()has the same flaw: a shortwolfSSH_stream_send()truncates thestatistics blob. It is left alone on purpose. A plain retry is worse than the
truncation — the second send finds the window exhausted, returns
WS_WINDOW_FULL,and the caller's
<= 0check ends the session. Doing it properly means staging theblob and resuming across passes, which is out of proportion for debug output behind
a control byte.
ssh_worker()has diverged by ~580 lines and hasno
select()write set, which this fix depends on.