Problem
In SshTunnelManager (lib/core/security/ssh_tunnel_manager.dart, lines 393–404):
Timer? keepAliveTimer;
if (config.keepAliveIntervalSeconds > 0) {
keepAliveTimer = Timer.periodic(
Duration(seconds: config.keepAliveIntervalSeconds),
(_) {
if (!client.isClosed) {
client.ping().catchError((_) {});
}
},
);
}
- Suppressed Ping Failures:
When an SSH bastion drops (network blip, Wi-Fi reconnection, laptop sleep/wake, firewall state timeout), client.ping() fails, but .catchError((_) {}) suppresses the error without taking action.
- Zombie Local Forwarder:
serverSocket continues listening on 127.0.0.1:localPort.
When database drivers attempt to connect to 127.0.0.1:localPort, the local TCP handshake succeeds, but line 383 (client.forwardLocal(remoteHost, remotePort)) immediately fails and destroys the socket with clientSocket.destroy().
The driver observes an immediate RST/EOF.
Furthermore, until someone explicitly calls openTunnel again with the same pool key, the dead session remains in _sessions[poolKey].
Scope
- In
keepAliveTimer, track consecutive ping failures (e.g. 2–3 failed pings).
- When pings fail consecutively, proactively close
serverSocket and client, removing the session from _sessions.
- Ensure new connection requests to that pool key dial a fresh SSH tunnel rather than reusing a zombie session.
- Add unit tests simulating ping failure and verifying proactive session cleanup.
Problem
In
SshTunnelManager(lib/core/security/ssh_tunnel_manager.dart, lines 393–404):When an SSH bastion drops (network blip, Wi-Fi reconnection, laptop sleep/wake, firewall state timeout),
client.ping()fails, but.catchError((_) {})suppresses the error without taking action.serverSocketcontinues listening on127.0.0.1:localPort.When database drivers attempt to connect to
127.0.0.1:localPort, the local TCP handshake succeeds, but line 383 (client.forwardLocal(remoteHost, remotePort)) immediately fails and destroys the socket withclientSocket.destroy().The driver observes an immediate RST/EOF.
Furthermore, until someone explicitly calls
openTunnelagain with the same pool key, the dead session remains in_sessions[poolKey].Scope
keepAliveTimer, track consecutive ping failures (e.g. 2–3 failed pings).serverSocketandclient, removing the session from_sessions.