fix(ssh): replace connection pool poller with lazy acquire and bounded backoff - #2316
Merged
Merged
Conversation
…iss the reconnect modal
…l audit vulnerabilities
…the install check
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Switching VPN killed the connection to a node and the next login was rejected instantly at TCP level rather than timing out. The cause was two independent client-side connection storms that tripped fail2ban's stock defaults (
maxretry=5,bantime=10m, ban action REJECT), which apply because Stereum installs fail2ban inconfigure-firewallwithout ajail.local. sshd'sMaxStartupscontributes to the immediacy of the refusal.Storm 1: the 100 ms pool poller.
checkConnectionPoolgrew the pool whenever it held fewer than 6 connections. During a blackhole nothing reachedready, so the pool never grew, the condition never cleared, and it dialled roughly 10 times per second. Measured: 29 attempts in 3 s, about 190 half-open handshakes per 20 sreadyTimeoutwindow.Storm 2:
checkConnection. Polled every 2 s byHeaderScreen.vue, it opened a throwaway SSH connection each time, entirely outside the pool. About 9 overlapping 18 s handshakes during an outage, roughly 300 failed attempts over 10 minutes.Changes
3 commits, 10 files (+445/−62) plus lockfile.
a2366a7e(fix)Backend:
acquireConnection(), single-flight, so a burst of execs opens one connection.dropConnection()wired toerror/end/closeso dead sockets leave the pool andconnectedgoes false.[2s, 5s, 15s, 30s, 60s]withAbortController, one attempt in flight.checkConnectionbecame a plain read.Frontend:
require2FAchannel pattern (no preload change).BaseLayoutclearsstereumStatus/refreshon reconnect so the modal dismisses itself.Three latent bugs fixed, since the feature did not work without them:
headerStore.reconnectingwas never declared in the store;ReconnectModaldestructured the store and lost reactivity; andservices.jssetrefresh = falseon loss with nothing ever setting it back, so the UI latched until reload.Two races closed:
disconnect()no longer emits state (logout was flashing the modal), and an epoch counter discards a handshake that lands after logout. The pool was verified to be re-populating post-logout, and becauselocation.reload()only restarts the renderer, the main process was holding an SSH session to a node you had left.10411812(countdown)Modal shows
Attempt 2 of 5andNext try in 12s, switching toConnecting...during a dial. The backoff emits{attempt, total, phase, waitMs};BaseLayoutconverts this to an absolute deadline so a missed tick cannot drift it. Three keys added toen.json(additive; Crowdin picks up the rest).7987e47a(version + deps)2.5.2 to 2.5.3. 3 high-severity vulnerabilities down to 0 (
@xmldom/xmldom,fast-uri,js-yaml), all resolved inside existing semver ranges, sopackage.jsonchanged only its version line. Plus 14 semver-compatible updates.