Skip to content

Reply with CHANNEL_CLOSE in server handler per RFC 4254 - #675

Merged
Eugeny merged 1 commit into
Eugeny:mainfrom
coreyleavitt:fix-server-close-reply
Jul 2, 2026
Merged

Reply with CHANNEL_CLOSE in server handler per RFC 4254#675
Eugeny merged 1 commit into
Eugeny:mainfrom
coreyleavitt:fix-server-close-reply

Conversation

@coreyleavitt

Copy link
Copy Markdown
Contributor

Problem

When the server receives CHANNEL_CLOSE from a client, it removes the channel from enc.channels but never sends CHANNEL_CLOSE back on the wire.

RFC 4254 Section 5.3 requires:

Upon receiving this message, a party MUST send back an SSH_MSG_CHANNEL_CLOSE unless it has already sent this message for the channel.

The client-side CHANNEL_CLOSE handler already does this correctly via enc.close(). The server-side handler was calling enc.channels.remove() directly, skipping the reply. This also matches OpenSSH's behavior, where chan_rcvd_oclose() always ensures CHANNEL_CLOSE is sent back before freeing the channel.

Changes

One-line change in server/encrypted.rs: replace enc.channels.remove(&channel_num) with enc.close(channel_num)?, matching the client-side handler. enc.close() sends CHANNEL_CLOSE on the wire (or defers it if there is pending data) and removes the channel.

The server-side CHANNEL_CLOSE handler was removing the channel
without sending CHANNEL_CLOSE back. RFC 4254 Section 5.3 requires
that upon receiving CHANNEL_CLOSE, a party MUST reply with
CHANNEL_CLOSE unless already sent. Use enc.close() to match the
client-side handler.
@coreyleavitt
coreyleavitt marked this pull request as ready for review March 19, 2026 07:29
}
self.channels.remove(&channel_num);
debug!("handler.channel_close {channel_num:?}");
handler.channel_close(channel_num, self).await

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.

This calls into user code, the RFC states:

The channel is considered closed for a party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and the party may then reuse the channel number.

This means currently the channel is still open when handler.channel_close is called, but with this change the channel is going to be closed at that point.

In current russh it's the handler's responsibility to send SSH_MSG_CHANNEL_CLOSE.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, you're right. enc.close() removes the channel before the handler runs, which would break handlers that do final work in channel_close. I also realize enc.close() has pending-data logic meant for locally-initiated closes that doesn't apply here.

I'm thinking the fix is to move the CLOSE reply after the handler call and send the byte directly rather than going through enc.close(). Does that track with how you'd expect it to work?

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.

3 participants