Skip to content

Session channels are not deregistered on close: a second session channel fails (null on MINA, "Remote channel N is already registered" + connection teardown on OpenSSH) #238

Description

@GlassOnTin

Opening a second session channel on the same connection fails once the first has been closed. Found while promoting sshlib 0.4.0 from SFTP-only to a whole-connection engine in Haven (follow-on from #231 — thanks again for that fix, 0.4.0 is solid on rekey).

It surfaces differently depending on the server, which is why I'm reporting both:

Against Apache MINA sshd 2.19.0 — the second openSession() simply returns null:

val client = SshClient(SshClientConfig {
    host = "127.0.0.1"; port = server.port
    hostKeyVerifier = trustAll
})
client.connect()                     // Success
client.authenticatePassword(...)     // Success

repeat(12) { attempt ->
    val session = client.openSession()
        ?: error("openSession returned null on attempt $attempt")   // fires at attempt 1
    session.requestExec("true")
    session.close()
}

Against a real OpenSSH server (OpenSSH 9.x on Ubuntu, from an Android client) it throws instead — and takes the whole connection down with it:

INFO  - Opening session channel (local=1)
INFO  - Channel open confirmed
INFO  - Channel opened: local=1, remote=1, remoteWindow=0
INFO  - Opening session channel (local=2)
INFO  - Channel open confirmed
java.lang.IllegalStateException: Remote channel 1 is already registered
ERROR - Failed to open session channel

…after which the transport drops and the client reconnects. So it isn't only the new channel that fails — the existing session dies too, which is what makes it hard to work around.

Reading of it (happy to be corrected): the second channel is confirmed by the server with the same remote channel number (remote=1) as the first, because the server freed that number when we closed channel 1. The client still has remote=1 in its registry, so it rejects its own channel. That would mean a channel is registered under the remote number but not deregistered on close. The MINA null and the OpenSSH exception look like the same root cause reached by two paths (MINA appears to hand out a fresh remote number more slowly, so it fails a little differently).

Impact: any long-lived connection that opens more than one session channel — which for us is the normal case: an interactive shell, then exec for a command, then another exec later. A single exec per connection is fine; the second is where it bites.

Repro is a plain JUnit test against MINA if that's useful — happy to push it somewhere or turn it into a PR against the test suite if you'd like it as a regression test.

For now Haven keeps the sshlib whole-connection engine behind an explicit opt-in flag, with this as a known limitation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions