Conversation
Add same-server, in-memory IRCv3 session resume for secure connections: an eligible client that loses its transport is held for a grace window instead of quitting, and may reattach keeping its nick, account, modes, and channels. - draft/resume-0.5 capability with bearer token (128-bit id + 256-bit secret, URL-safe base64), rotated on every resume; constant-time secret compare. - BRB for voluntary suspend; auto-detach on unexpected transport loss (EOF, reset, TLS error, abnormal WebSocket close); optional detach on ping timeout (RESUME_DETACH_PINGOUT) for silent losses a proxy never propagated. - Account-based auto-reattach (RESUME_AUTO_ACCOUNT): an authenticated client (SASL, login-on-connect, or a services login after connecting) that returns with the same nick+account reattaches with no token and no client support; the nick collision is deferred through registration. Per-account opt-out via the 0x080 account flag. - Preserves user modes/oper privileges/snomask/class limits across the connection swap; restores pre-detach away. - Eligibility requires TLS; RESUME_REQUIRE_WEBSOCKET (default TRUE) further restricts to secure WebSockets. Detached sessions capped by RESUME_MAX_DETACHED; window by RESUME_TIMEOUT (10-300s). - WHOIS shows a detached session to opers/self; detach/resume/expiry notices via RESUME_SERVER_NOTICES; tokens never logged or shown. - Off by default (RESUME). Docs in doc/readme.resume and doc/readme.features; tests under tests/pr_resume/.
…efer iauth nick - Re-send RPL_LOGGEDIN (900) during replay for an authenticated session that resumed via token. A token-path resumer never SASLs on the new connection, so it never received 900 and could not tell it was still logged in -- it would retry SASL on the next CAP NEW, which fails when services are down (the very case where a server-local token still lets it back in). Gated on the resumer not having authenticated on this connection, so account-path resumers are not sent a duplicate. - Flush the replay burst with send_queued() before restoring the SendQ ceiling, so a large rebuild cannot leave the queue backed up above the class limit and trip "Max SendQ exceeded" on the client's next message. Document that the ceiling is enforced at queue time, not at dispatch. - Withhold the deferred resume nick from iauth until it is actually committed, and forward it only once the client keeps it, so iauth/dronescan never evaluate a nick the client may not end up holding.
Carry state that must survive detach->resume across the connection swap and clean up the transient's registration state, so a resumed session no longer leaks or corrupts server-side bookkeeping: - Drop the transient's in-flight SASL cookie and timer before the swap frees it, so the SASL session table / timer can't dangle at a freed client (H0). - Carry the accumulated nick-change penalty (con_nextnick), closing a BRB-reconnect nick-flood bypass (M10). - Move the session's conf attachments (incl. any Operator block) onto the live connection so oper-class link accounting stays balanced (L20). - Carry the sockhost and byte counters so the local-count bucket balances and session traffic isn't lost to is_ni (L21). - Tell iauth the detached fd is gone, so it doesn't leak a record on a reusable fd (L19).
…etes A nick deferred for account-based reattach was withheld from iauth. iauth (login-on-connect) needs the client's nick to finish its registration handshake and send its verdict. The account is derived from the username, but it isn't applied to the client until iauth replies -- and iauth won't reply without the nick. So the account was never set, the reattach (which needs it) never fired, and the reconnecting client hung. Forward the nick to iauth even while deferred; it still stays out of the client nick-hash.
|
Feedback from running this against a real deployment: a bouncer holding a persistent WebSocket uplink (client → Cloudflare → nginx → ircu, Resume works well in the normal case. But we hit a reproducible What happens It occurs when the transport dies with a hard reset rather than a clean EOF, and the client reconnects quickly. Our side saw Root cause The token check in s = resume_find(id);
if (!s || s->state != RESUME_STATE_DETACHED
|| resume_ct_memcmp(s->secret, secret, RESUME_SECRET_BYTES) != 0
|| (RESUME_REQUIRE_SAME_IP && irc_in_addr_cmp(&cli_ip(sptr), &cli_ip(s->client)))) {
→ FAIL RESUME INVALID_TOKENBut a session only transitions to In other words the resume races the server's own detection of the dead socket, and loses whenever the client notices the drop first (which, for a proxied RST, is the common case). Suggested fix When a Happy to help test a patch against the same proxied setup. |
|
Follow-up with concrete timing from the same deployment, because it shows the race window is much wider than it first appears. A drop at 20:10:33 ended with the session expiring at 20:14:09 — about 3m36s later, not the 60s
Why detection took ~2.5 minutes. There are two detach triggers. The socket-error path in Consequence for resume. The session is only resumable during that final So the race isn't a millisecond edge; it is as wide as the ping-timeout, minutes long, and lost on essentially every proxied drop where the client detects loss first. That makes the force-detach-on-valid-secret behaviour I suggested above close to required for resume to work behind a proxy: a |
Summary
Adds same-server, in-memory IRCv3 session resume for secure connections. An eligible client that loses its transport is held in a limbo state for a grace window instead of quitting, and can reattach to the same session — keeping its nick, account, user modes, oper privileges, and channels — with no visible quit/rejoin to the rest of the network. Primarily designed to survive edge reployments or other short-lived interruptions of WebSocket connections.
Off by default (
RESUMEfeature flag); no behavior change unless explicitly enabled.What it does
draft/resume-0.5capability with a bearer token (128-bit id + 256-bit secret, URL-safe base64), rotated on every resume, constant-time secret comparison. Tokens are never logged or shown.BRBcommand for voluntary suspend; automatic detach on unexpected transport loss (EOF, reset, TLS error, abnormal WebSocket close); optional detach on ping timeout (RESUME_DETACH_PINGOUT) for silent losses a proxy never propagates.RESUME_AUTO_ACCOUNT): an authenticated client (SASL, login-on-connect, or a services login after connecting) returning with the same nick+account reattaches with no token and no client support — the nick collision is deferred through registration. Per-account opt-out via the0x080account flag.RPL_LOGGEDINis re-sent for authenticated sessions so a token-path resumer re-learns its account.Eligibility & limits
RESUME_REQUIRE_WEBSOCKET(default on) further restricts to secure WebSockets.RESUME_MAX_DETACHED; grace window byRESUME_TIMEOUT(10–300s).WHOISshows a detached session to opers/self; detach/resume/expiry operator notices viaRESUME_SERVER_NOTICES.Testing
tests/pr_resume/covering detach, resume, account auto-reattach, auto-detach, and the secure-WebSocket requirement.Docs
doc/readme.resume(feature guide),doc/readme.features, anddoc/example.conf.