Skip to content

Forward channel EOF to the connected host - #1852

Draft
Discolai wants to merge 1 commit into
sshnet:developfrom
Discolai:fix/forwarded-tcpip-channel-eof
Draft

Discolai wants to merge 1 commit into
sshnet:developfrom
Discolai:fix/forwarded-tcpip-channel-eof

Conversation

@Discolai

Copy link
Copy Markdown

Fixes #1754

ChannelForwardedTcpip ignored SSH_MSG_CHANNEL_EOF, so the connected host never received
end-of-stream when the client stopped sending. Bind stayed blocked in ReadContinuous until the
host closed the connection itself, which a host waiting for the client to speak first only does on
its own timeout.

We hit this with a TLS server behind ForwardedPortRemote. Clients that connect and disconnect
without sending anything, such as TCP health checks, left the server waiting for a
ClientHello and a thread blocked in Bind until the server's read timeout expired.

This overrides OnEof to call ShutdownSocket(SocketShutdown.Send), as ChannelDirectTcpip.OnEof
already does. The socket is not closed. Only the send direction is shut down, so the host can still
send data back over the channel until it closes its own side, and the existing close path then runs
as before. This should address the concern raised in #1754 about data still flowing in the other
direction.

ChannelForwardedTcpipTest_ChannelEofReceived covers:

  • the host receives end-of-stream after SSH_MSG_CHANNEL_EOF
  • data the host sends afterwards is still forwarded as SSH_MSG_CHANNEL_DATA
  • Bind returns without an exception once the host shuts down its side

All three fail without the change.

AI usage

I used an AI assistant (Claude) throughout. It traced the root cause by decompiling SSH.NET to find where the thread was blocked, compared ChannelForwardedTcpip with ChannelDirectTcpip, and wrote the fix and the test. I directed the
investigation, reviewed the code and the test, chose the wording of the commit, and ran the tests locally.

ChannelForwardedTcpip ignored SSH_MSG_CHANNEL_EOF, so the connected host
never received end-of-stream when the client stopped sending. Bind stayed
blocked in ReadContinuous until the host closed the connection itself,
which a host waiting for the client to speak first only does on its own
timeout.

Shut down the send direction of the socket on EOF, matching
ChannelDirectTcpip. The host can still send data back over the channel.
@Discolai
Discolai marked this pull request as draft October 23, 2026 09:24
@Discolai

Copy link
Copy Markdown
Author

I will reopen the PR after i have fixed the flaky tests

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.

Is it a better practice to actively close the socket when the forwarded-tcpip channel receives an EOF?

1 participant