Skip to content

Fix Mono socket read timeouts and retain explicit algorithm selection - #1814

Open
oznetmaster wants to merge 3 commits into
sshnet:developfrom
oznetmaster:copilot/mono-crestron-fixes
Open

oznetmaster wants to merge 3 commits into
sshnet:developfrom
oznetmaster:copilot/mono-crestron-fixes

Conversation

@oznetmaster

@oznetmaster oznetmaster commented Jul 30, 2026 •

Copy link
Copy Markdown

Enforce finite socket-read deadlines on Mono and preserve unlimited timeouts

On Linux Mono 6.8, a silent peer produces SocketException.WouldBlock when a synchronous
receive timeout expires. SSH.NET treats that code as resumable and keeps retrying, so a
150 ms library read remains blocked until an independent watchdog closes the peer.
Using a polling deadline makes this read throw SshOperationTimeoutException instead.

This refresh merges current develop, retaining upstream's deletion of unused ReadPartial
and ClearReadBuffer. It preserves zero and -1 as unlimited waits and existing AsTimeout
validation. Three added regression cases fail on the previous PR and pass after correction.

The explicit client-preference algorithm loops remain. They preserve the inspected
LINQ query's ordinal string equality and preference order; the earlier explanation that
standard string == was culture-sensitive was incorrect. A processor-specific LINQ issue
was the original motivation, but this refresh does not claim to reproduce that issue.

Validation: all five library target frameworks build with no warnings/errors. Focused
Windows unit checks pass 102/102 each on net462, net8.0 and net10.0. Actual Linux Mono
loopback checks reproduce the retry problem and the correction, with external bounds.
No processor DLLs were replaced.

Review limits: the deadline bounds the complete read instead of resetting for each
Receive. A zero-length read differs on Mono, and the DateTime deadline remains subject
to wall-clock adjustment. These deserve explicit review before merge.

The full suite is not green: the PR run before the final input-guard correction had
2397 passed, 2 failed and 26 skipped; unchanged develop had 2267 passed, 132 failed and
26 skipped. Both PR failures also occurred in the baseline. Many baseline failures were
address-in-use errors; this is evidence for comparison, not a claim that all are harmless.
No Docker integration or fresh Crestron processor validation was performed.

AI disclosure: OpenAI Codex performed the source review, local code/test changes and automated validation. Neil Colvin supplied the processor context and authorized this update. This does not claim independent manual code review or processor validation by Neil.

Upstream CI on 9a75dc1: Linux, Windows, Windows .NET integration and Windows .NET Framework integration all passed. Package publishing jobs were skipped for the pull request. CI run.

… comp

atibility- KeyExchange: replace culture-sensitive LINQ equality (==) algorithm negotiation with an explicit ordinal string comparison (SelectAlgorithm helper), since default string equality can behave inconsistently across runtimes/cultures.
- SocketAbstraction: Socket.ReceiveTimeout (SO_RCVTIMEO) is not reliably enforced on some Mono-derived runtimes when the TCP handshake completes but the remote peer subsequently stops responding without sending RST/FIN, causing blocking reads to hang indefinitely instead of timing out. Add a Socket.Poll-based deadline wait (PollWithDeadline) which maps directly onto the underlying select()/poll() syscall and reliably unblocks on timeout regardless of platform.

Discovered and validated while running this library embedded in a Crestron Home processor driver (Mono-based .NET runtime) for SSH-based device management.
The Socket.Poll-based read timeout implementation changes the exception reported when the client socket is closed while a blocking read is in progress: Socket.Poll consistently throws SocketError.Interrupted (regardless of target framework), and on .NET Framework the socket can also be disposed between the Poll and the subsequent Receive call, resulting in an ObjectDisposedException. Update HandleSocks to swallow both of these expected shutdown-related exceptions instead of raising them via the Exception event, restoring the previous behavior where closing a forwarded port with an active SOCKS handshake in progress does not fire spurious exceptions.
Retain upstream removal of unused socket helpers, preserve zero and infinite
read timeouts, and keep AsTimeout input validation before polling. Add bounded
regressions and describe the reproduced Mono WouldBlock retry behavior.

Prepared with OpenAI Codex; Neil Colvin supplied context and authorized the update.
@oznetmaster oznetmaster changed the title Fix Mono runtime compatibility: ordinal algorithm comparison and Socket.Poll-based read timeouts Fix Mono socket read timeouts and retain explicit algorithm selection Oct 17, 2026

This branch has not been deployed

No deployments
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.

1 participant