Skip to content

examples, ide: classify the worker's status where ssh->error is its own - #1251

Merged
ejohnstown merged 2 commits into
wolfSSL:masterfrom
yosuke-wolfssl:fix/echoserver
Sep 17, 2026
Merged

ejohnstown merged 2 commits into
wolfSSL:masterfrom
yosuke-wolfssl:fix/echoserver

Conversation

@yosuke-wolfssl

@yosuke-wolfssl yosuke-wolfssl commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Problem

wolfSSH_worker() reports a blocked read or write two ways: as its return, or
as WS_FATAL_ERROR with the code left in ssh->error. ssh_worker()
dispatched on the return alone, so the second shape arrived as a bare failure
with its meaning stranded in ssh->error. That one gap has two consequences:

  • Live sessions dropped. The code compensated by re-reading
    wolfSSH_get_error() down in the terminal arm, ~200 lines later. The
    !eofAnswered && echoOnly drain runs in between and latches its own code, so
    a WS_WINDOW_FULL from the drain read as fatal and tore down a healthy
    session.
  • Both ends deadlock. echoserver: answer session requests in callbacks #1234's wantWrite retry is keyed on
    rc == WS_WANT_WRITE, which the second shape never produces. wantWrite
    stayed 0, so select() waited on readability alone while the peer waited on
    output the server was still holding.

A third case carries no want-status at all: an owed write riding a
WS_CHAN_RXD, WS_EOF or WS_CHANNEL_CLOSED return. In wolfSSH_worker()'s
flush block, ret == WS_CHAN_RXD with sendRet == WS_WANT_WRITE fires none of
the three branches, so the event return is handed back with output still
queued. wolfssh/ssh.h says to ask wolfSSH_OutputPending() rather than read
a status, which is what the POSIX wolfsshd loop already does.

Fix (examples/echoserver/echoserver.c)

Carry the worker's meaning forward in rc, at the call site, while
ssh->error still belongs to the worker:

rc = cnt_r;
if (rc == WS_FATAL_ERROR) {
    int err = wolfSSH_get_error(ssh);

    if (err == WS_WANT_READ || err == WS_WANT_WRITE)
        rc = err;
}
  • echoserver: answer session requests in callbacks #1234's wantWrite = 1; continue; arm is kept. The remap feeds it the
    second shape, arming the write set where nothing did before, so this extends
    echoserver: answer session requests in callbacks #1234 rather than competing with it.
  • wantWrite is additionally set from wolfSSH_OutputPending() before
    writeFds is built, which is the only form that covers the event-return
    case.
  • The terminal arm stops re-reading wolfSSH_get_error() and tests rc. The
    two echoservers deliberately differ here:
    rc != WS_WANT_READ in this
    file, whose WS_WANT_WRITE arm consumes that case, and
    rc != WS_WANT_READ && rc != WS_WANT_WRITE in the ESP-IDF copy, which has no
    write set and whose empty WS_WANT_WRITE arm is removed. Please do not
    "align" them.
  • The chain is guarded by if (cnt_r < 0), so a WS_SUCCESS pass never
    reaches the terminal arm.

Includes tests: run app-driven sftp in a block build (John Safranek), which
un-skips the case that exercises this.

scripts/sshclient.test and scripts/fwd-bulk.test name the client's missing
non-blocking mode in their skip messages, matching scripts/scp.test. Their
old comments said the other echoserver scripts skip a block build for the same
reason, which stopped being true when sftp.test un-skipped above. Message and
comment only; no logic changed.

Verification

  • Preflight: lint plus six gcc-13 -Werror configs, all clean.
  • 166 unit + regress + testsuite pass; sshclient, scp, get-put,
    sftp and fwd pass run serially.
  • Contention (--enable-sftp -DWOLFSSH_TEST_BLOCK -DWOLFSSH_NO_FPKI), with the
    app-driven case now running rather than skipped: 21s / 39s / 88s at
    WOLFSSH_BLOCK_PROB 30 / 50 / 70.
  • Mutations at prob 70:
Change Result
Remove the WS_FATAL_ERROR remap fails, -1009, 77s
Remove #1234's wantWrite arm hangs, killed at 200s
Remove the wolfSSH_OutputPending() lines still passes, 88s

The third row is deliberate disclosure: no test distinguishes the
wolfSSH_OutputPending() arming.
wolfSSH_worker() attempts a flush on
every call, so an owed write is retried for as long as the loop keeps calling
the worker; reaching the hole needs the peer to fall silent immediately after
sending channel data while the socket refuses writes. That line is contract
conformance, not a measured fix.

The echo/shell path also stays uncovered: scripts/sshclient.test cannot run
under WOLFSSH_TEST_BLOCK because apps/wolfssh has no non-blocking mode, and
neither does portfwd for scripts/fwd-bulk.test. That is what their skip
messages now say.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Sep 15, 2026
Copilot AI lite review requested due to automatic review settings September 15, 2026 00:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved blocking issues were identified.

Pull request overview

This pull request fixes worker-status classification in both echo servers so transient write back-pressure does not terminate live sessions.

Changes:

  • Preserves and classifies wolfSSH_worker() status immediately.
  • Simplifies terminal-status handling in both implementations.
File summaries
File Description
ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c Applies the worker status-handling fix.
examples/echoserver/echoserver.c Preserves worker status across echo draining.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1251

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.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1251

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.

yosuke-wolfssl and others added 2 commits September 16, 2026 13:16
- ssh_worker() in both echoservers maps a WS_FATAL_ERROR return from
  wolfSSH_worker() to WS_WANT_READ or WS_WANT_WRITE at the call site,
  off a new local err, and keeps the result in rc.
- The terminal arm tests rc in place of re-reading wolfSSH_get_error():
  rc != WS_WANT_READ in examples/echoserver, and rc != WS_WANT_READ &&
  rc != WS_WANT_WRITE in the Espressif copy, whose empty WS_WANT_WRITE
  arm is removed.
- ssh_worker() in examples/echoserver sets wantWrite from
  wolfSSH_OutputPending() before it builds writeFds.
- The comment above rc = cnt_r in both echoservers names cnt_r and
  ssh->error as the values that are reused.
- scripts/sshclient.test and scripts/fwd-bulk.test name the client's
  missing non-blocking mode in the skip message where they echoed the
  macro name, and the comments above both skips are removed.
The non-blocking app-driven case runs under WOLFSSH_TEST_BLOCK again,
reverting ba89960. It deadlocks there until ssh_worker() waits on the
write side for a send wolfSSH_worker() reported through ssh->error, so
this wants PR 1251 landed with the wantWrite arm kept.
@ejohnstown
ejohnstown merged commit 2b58559 into wolfSSL:master Sep 17, 2026
185 checks passed
@yosuke-wolfssl
yosuke-wolfssl deleted the fix/echoserver branch September 17, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants