Skip to content

fix: add VPN wake rebind hook#26739

Merged
EhabY merged 4 commits into
mainfrom
fix/vpn-wake-restun
Jul 13, 2026
Merged

fix: add VPN wake rebind hook#26739
EhabY merged 4 commits into
mainfrom
fix/vpn-wake-restun

Conversation

@EhabY

@EhabY EhabY commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

After macOS sleep/wake, stale Coder Connect paths can take minutes to recover: magicsock's periodic re-STUN timer stops once the session is idle (and sleep is idle by definition), and a short same-network wake often produces no link-change event. New SSH dials then time out until something else forces path re-discovery (#26736).

This adds a CoderVPN WakeRequest RPC so Coder Desktop can trigger the existing link-change recovery path (Rebind + ReSTUN) immediately on OS wake, instead of waiting for a timer that isn't running. No polling or health checks are added. Coder Desktop still needs to send the RPC from its wake notification handler: coder/coder-desktop-macos#260.

Debouncing

macOS can emit several wake-adjacent notifications in a burst, and each rebind resets peer path trust, briefly forcing traffic onto both the direct path and DERP. The tunnel therefore runs at most one wake-triggered rebind per 5s; debounced wakes still return success.

This only bounds the wake RPC. Rebinds triggered inside magicsock (netmon link changes, periodic re-STUN) are unaffected and continue to serve as the fallback recovery path.

Closes #26736

Generated by Coder Agents.

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change LGTM but deferring approval to Spike or Ethan.

Comment thread vpn/tunnel_internal_test.go Outdated

@ethanndickson ethanndickson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good, and the problem being fixed makes sense to me, though I'm very much surprised I've not experienced nor heard anyone internally complain about the issue being described.

How do we know this is a real issue on macOS? There's that tailscale/tailscale issue, but they've said they won't be fixing it, and also didn't mention whether it was an issue in practice...

Regardless, I've left a comment on coder/coder-desktop-macos#260 for how I think we should handle this on mac.

May be good to hear from Spike on this too.

Comment thread vpn/vpn.proto Outdated
@ethanndickson

ethanndickson commented Jun 29, 2026

Copy link
Copy Markdown
Member

In an unscientific experiment, I slept my MacBook for 3 minutes, 6 minutes and 15 minutes. I'll do an hour+ sleep now, but for each of these, the logs for the Coder Desktop daemon included:

LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=en0 ifs={en0:[192.168.1.247/24] utun4:[fd60:627a:a42b:45c0:b418:849f:c8f:400e/128]} v4=true v6=false}

and

monitor: time jumped (probably wake from sleep); synthesizing major change event

EDIT: Saw these logs after a 5+ hour sleep. Device was connected to the same wifi the entire day.

@EhabY

EhabY commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@ethanndickson If the client synthesizes a major change event on wake then I guess this PR is not needed, I do wonder though if this happens immediately or within a poll (not immediate so might cause some strange UX or VS Code might think it's dead)

@ethanndickson

ethanndickson commented Jul 6, 2026

Copy link
Copy Markdown
Member

Yeah I'm honestly not sure. Maybe it's better to have an explicit rebind event? Right now coder/tailscale is doing a bit more guess work to synthesize a rebind than the solution proposed here. Maybe this event just wasn't firing for the reporting user for whatever reason and so we need to build this?

Could be good to check if there's any downsides to spurious rebinds - check if they need debouncing etc

@EhabY

EhabY commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

I looked into what spurious rebinds cost. They're safe but not free, so I think we should add a debounce.

  • ReSTUN already coalesces overlapping calls, no concerns there.
  • Rebind is serialized and mostly idempotent. The main cost is resetting trustBestAddrUntil for all peers, so traffic dual-sends UDP+DERP until disco pings restore trust (~1 RTT), plus a brief packet-loss window during the socket rebind.
  • netmon's synthesized wake event fires up to ~15s after wake (wall-clock poll), so with this PR the common case is a double-fire: RPC immediately, netmon seconds later.

What do you think of adding a ~5s debounce in updater.rebind so it's one rebind per wake regardless of how the two interleave.

@ethanndickson

Copy link
Copy Markdown
Member

What do you think of adding a ~5s debounce in updater.rebind so it's one rebind per wake regardless of how the two interleave.

That sounds good to me!

@ethanndickson

ethanndickson commented Jul 7, 2026

Copy link
Copy Markdown
Member

https://linear.app/codercom/issue/DEVEX-534/tray-reports-coder-connect-as-healthy-while-tunneldns-is-broken-stale has been sent my way. I haven't spent any time on it, but it's entirely possible these major link changes aren't happening on Windows, and so this mechanism might be more beneficial there? We have logs somewhere for that issue which would let us know.

@EhabY

EhabY commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

What do you think of adding a ~5s debounce in updater.rebind so it's one rebind per wake regardless of how the two interleave.

I think we can only debounce client events, the synthesized events get propagated from a different code path and thus it would still run anyway 🤔

Also I'm not so sure that this would fix DEVEX-534 since it seems to be in a different path entirely that a reSTUN/reBIND would not fix

EhabY and others added 4 commits July 7, 2026 11:07
Wake events can arrive in quick succession, e.g. the OS wake notification
racing netmon's synthesized link-change event, and each rebind resets peer
path trust in magicsock, forcing dual-path sends until paths re-verify.
Collapse wakes within 5s into a single rebind so each wake recovers with
exactly one rebind.

Also drop the unused error_message field from WakeResponse.
@EhabY EhabY force-pushed the fix/vpn-wake-restun branch from a22a29c to c92f4e5 Compare July 7, 2026 08:07

@ethanndickson ethanndickson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with merging this if we think we'll pursue the OS-specific paths in the near future. I think having an explicit / deterministic kick for rebind is good, and it looks like we don't need to be worried about spurious rebinds.

@EhabY EhabY merged commit e11147e into main Jul 13, 2026
32 of 33 checks passed
@EhabY EhabY deleted the fix/vpn-wake-restun branch July 13, 2026 12:14
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 13, 2026
@coder coder unlocked this conversation Jul 13, 2026
Comment thread vpn/vpn.proto
message WakeRequest {}

message WakeResponse {
bool success = 1;

@ethanndickson ethanndickson Jul 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks unused, always true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed this is not needed #27202

@ethanndickson ethanndickson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to flag this, sorry 😓

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.

Coder Connect tunnel not re-STUN'd on system wake; dead for minutes after macOS sleep

3 participants