Skip to content

fix(session): hold --lock-on-disconnect while a reconnect is handshaking - #190

Merged
clintcan merged 1 commit into
mainfrom
fix/lock-mid-handshake
Sep 29, 2026
Merged

clintcan merged 1 commit into
mainfrom
fix/lock-mid-handshake

Conversation

@clintcan

Copy link
Copy Markdown
Owner

Follow-up to #181, from the final review there.

Problem

--lock-on-disconnect treats a client as back only once SessionTracker counts a fully connected session, which takes ~10 s over ZeroTier. A client that reconnects 12–22 s after leaving is locked underneath its own handshake. With --auto-unlock it self-corrects about 3 s later; with the lock alone, a remote user lands on a lock screen they can't get past.

Fix

src/lock_activity.rs wraps the connection handler to timestamp accepted connections and successful CredSSP authentications. The wrapper is installed only with --lock-on-disconnect, so the default connection path is unchanged, and it forwards every hook to the auth guard unchanged. When the lock comes due, it holds while there has been activity since the disconnect:

  • an authenticated reconnect within the last 30 s (only someone who knows the password can produce one), or
  • a merely accepted connection within the last 10 s (the timer can end mid-CredSSP).

Every hold is capped at 30 s past the normal firing time, so an unauthenticated peer opening connections can only delay the lock, never prevent it. Activity from before the disconnect (the departing session's own authentication) is ignored. A later disconnect supersedes an earlier pending lock (LOCK_GENERATION), so leave → return → leave locks on the last disconnect's timer rather than the first.

This is timestamp-based rather than a per-connection counter on purpose: during a live session the preemption race calls on_accept for candidates that may never reach on_disconnected, so a counter would drift upward.

Verification

  • 8 new unit tests: the policy, the cap, ignoring pre-disconnect activity, and the wrapper preserving the inner handler's verdict. 247 tests pass; clippy -D warnings and stable + nightly fmt are clean.
  • Live, on a MacBook (--shield-primary --lock-on-disconnect --auto-unlock, timer shortened to ~5.5 s so an ordinary reconnect lands mid-handshake), from a Windows 11 mstsc client:
    • Reconnect straight away: the lock came due 2 s after the reconnect's first accept and held (a client is reconnecting — holding the lock). mstsc's spare pre-auth connection kept the hold alive until the real one authenticated about 6 s later. The session came up and the lock was skipped. The old code would have locked about 6 s before the client was back.
    • Negative control (nobody comes back): the lock fired on schedule 5.6 s after the disconnect, with no hold, and auto-unlock succeeded on the later reconnect.

Residual

The 10 s pre-auth hold covers one slow-to-authenticate connection. In the live run, mstsc's spare connection used about 7.7 s of it before the real connection arrived; each new accept restarts the window, within the 30 s cap.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MtHRbjs42rW2fLZD1jqgwG

A session only counts as live once FULLY connected (~10 s over ZeroTier),
so a client that reconnected 12-22 s after leaving was locked underneath
its own handshake (#181 follow-up; invisible with --auto-unlock, strands a
remote user without it).

src/lock_activity.rs wraps the connection handler — only with
--lock-on-disconnect, so the default path is unchanged — to timestamp
accepted connections and successful CredSSP authentications. At expiry
the pending lock holds while there is activity since the disconnect: an
authenticated reconnect within 30 s, or an accepted connection within
10 s (the timer can end mid-CredSSP). Each hold is capped at 30 s past
the normal firing time, so an unauthenticated peer can only delay the
lock, never prevent it. Activity from before the disconnect is ignored,
and a later disconnect supersedes an earlier pending lock
(LOCK_GENERATION).

8 new unit tests (the policy, the cap, pre-disconnect activity, the
wrapper preserving the inner handler's verdict).
@clintcan
clintcan merged commit b61262b into main Sep 29, 2026
3 checks passed
@clintcan
clintcan deleted the fix/lock-mid-handshake branch September 29, 2026 05:16
clintcan added a commit that referenced this pull request Sep 30, 2026
The client's microphone presents as "macrdp Microphone", a real macOS
input device (#191). Opt-in and EXPERIMENTAL
(--enable-microphone-redirection, or the GUI controller); the Core Audio
driver ships in macrdp.app and installs from the controller. 16-bit PCM
44.1 kHz only. Protocol layer is vendored divergence (25); upstream
ironrdp-rdpeai replaces it at the next pin bump.

Also #190: --lock-on-disconnect holds while a reconnect is handshaking.

Pre-tag gates: fmt clean (stable + nightly), clippy -D warnings clean,
259 tests passing. Docs: release-history, README status, CLAUDE.md status.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant