Repository navigation
Conversation
handleRelay awaited the host lookup and openSshTransport, which resolves a host given by name, before it created the WebSocket stream. A message that arrived in that gap had no listener and was dropped. The client speaks first, so the host's sshd lost the client's SSH greeting, read the KEXINIT as its first line and answered "Invalid SSH identification string.", which ssh2 on the desktop reports as "Bad packet length". Create the stream first thing, so early messages wait in its read buffer, with an error listener so a socket error during the lookup isn't rethrown with nobody listening. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
AI review | Needs changes Moves WebSocket stream creation earlier in the SSH relay handler to buffer the client's initial greeting during DNS lookup, preventing dropped packets and handshake failures. Adds an error listener to prevent uncaught exceptions during the lookup phase and includes tests for the new behavior.
Reviewed 3ff0112. AI can be wrong, a maintainer still decides. Comment |
|
The lint-and-build failure is Prettier on |
A desktop app linked to a server, with connections starting on the server, reaches hosts through
/plugin-ws/core/ssh-relay. In that setup, Tmux Monitor in the desktop app failed with "Bad packet length" ("Failed to load tmux sessions" in the All hosts list) for every host given by name, while the same hosts worked from the web app. Hosts given by IP worked everywhere.handleRelayawaits the host lookup andopenSshTransport, which resolves a host name throughresolveSshConnectConfigHost, before it callscreateWebSocketStream(ws). A message that arrives in that gap has no listener and is dropped. The client speaks first: ssh2 sends its identification line as soon as the socket opens. So for a host given by name, the host's sshd never sees the client's greeting. It reads the KEXINIT as its first line, answersInvalid SSH identification string., and ssh2 on the desktop reads that text as a packet:Bad packet length. An IP needs no real lookup, so the stream is attached before any message arrives.Through the relay on my server, hosts given by
.lannames failed 19 of 21 handshakes and hosts given by IP failed none. Holding the client's first write back 300 ms made the named hosts pass.The stream is now created first thing in
handleRelay, so early messages wait in its read buffer (ws pauses the socket when it fills) until the host side is piped in. Anerrorlistener comes with it: the stream's own error handler rethrows when nothing else listens, so without one, a socket error during the lookup would be an uncaught exception. The existingpipe.once("error", finish)is unchanged.Before this change, a protocol error from the client during the lookup (for example an unmasked frame) was already an uncaught exception, because nothing listened on the socket yet. The backend logs it and keeps running. The new tests cover that case too.
Validation:
ssh-relay.test.ts, with the transport held until the test releases it:errorlistener.ssh-relay.test.ts: 9 passed, 20 runs in a row.main(02fb265): 4635 passed, 1 skipped. 5 failed, inssh2-sk-patch,ctx-desktop-local,host-commandsandhost-modules, and they fail the same way without this change in my checkout (installed with--ignore-scripts, so the postinstall patches did not run).tsc -b --force, eslint and prettier pass, and so docheck-test-locationsandcheck-changelog.ssh-relay.json my 26.11.0 server: 30 of 30 handshakes through the relay to hosts given by name or IP, and the desktop app lists tmux sessions on the named hosts again. Clients leaving during the lookup left no open connections to the host.This targets
mainbecause there is nodev-*branch right now. Happy to retarget.🤖 Generated with Claude Code