fix(dialer): evict dead SSHD sessions before reuse to prevent channel-open failure (#382)

When an SSH session behind an SSHD dialer dies silently (NAT timeout,
server disconnect, idle TCP drop), IsClosed() may still return false
because the health-check goroutine hasn't detected the failure yet.
Returning this dead cached session to the connector causes:

  ssh: unexpected packet in response to channel open:

Add a Ping(req)-based liveness check before returning a cached session.
If the ping fails, evict the session so the next Dial rebuilds a fresh
connection. This matches the dead-session eviction pattern already used
by the SSH, KCP, QUIC, and ICMP dialers.

Also export the ping-as-health-check as Session.Ping(timeout) so it can
be used by external health probes. Retain the existing ping() helper
unmodified.
This commit is contained in:
ginuerzh
2026-06-21 13:02:10 +08:00
parent 9fab332772
commit c45d27113e
2 changed files with 31 additions and 0 deletions
+19
View File
@@ -114,6 +114,25 @@ func (s *Session) Keepalive(interval, timeout time.Duration, retries int) {
}
}
// Ping checks if the SSH session is alive by sending a keepalive request.
// It blocks until a response is received or the timeout expires.
// A nil error indicates the session is healthy.
func (s *Session) Ping(timeout time.Duration) error {
if timeout <= 0 {
timeout = defaultKeepaliveTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
select {
case err := <-s.ping():
return err
case <-ctx.Done():
return ctx.Err()
}
}
func (s *Session) ping() <-chan error {
ch := make(chan error, 1)
go func() {