Skip to content

Address review findings on relay failover (PR #688) - #692

Closed
kasnder wants to merge 2 commits into
masterfrom
claude/pr-688-fixes-a4945r
Closed

Address review findings on relay failover (PR #688)#692
kasnder wants to merge 2 commits into
masterfrom
claude/pr-688-fixes-a4945r

Conversation

@kasnder

@kasnder kasnder commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Builds on #688 ("Fail over to a different Mullvad/IVPN relay when the current one stays dead") with a follow-up commit addressing review findings on that change:

  • Bound the relay-list fetch with a real timeout (FAILOVER_TIMEOUT_MS): previously forceRestartPending blocked every other recovery path while the unbounded network call ran, so a hang could stall recovery longer than not having failover at all.
  • Made failoverInFlight an AtomicBoolean to close a check-then-set race between the monitor and rebind threads.
  • Only trigger failover from monitor-observed breaks, not from rebind failures after a network change (those aren't evidence the relay is dead).
  • Reset the restart backoff after a successful switch so a fresh relay doesn't inherit the old one's penalty.
  • Persist the IVPN session WgRelayFailover generates/reuses; it was minting sessions that never got saved, orphaning them against the account's device limit and desyncing stored session state from the live config.
  • Scoped the config write to the profile ID resolved before the network call (WgProfileManager#updateProfileConfigIfActive), so a profile switch mid-fetch can't overwrite a different profile's stored config.
  • Excluded the relay that just failed from the next pick when another candidate exists, and reject a pick that silently widens the exit country, for both Mullvad and IVPN.
  • Distinguished ApiRejectedException/CaptchaRequiredException from other failures in the failover log.
  • Added WgRelayFailoverTest and extended WgProfileManagerTest for the new guard clauses.

Test plan

No Android SDK is available in this sandbox, so the change could not be compiled/tested locally; please run CI.

  • ./gradlew :app:compileGithubDebugJavaWithJavac
  • ./gradlew :app:testGithubDebugUnitTest
  • Manually verify: with a Mullvad/IVPN WireGuard profile active, block the current relay's IP and confirm TC switches to a different relay (excluding the dead one, without widening country) after a few restart cycles, and that the switch doesn't stall recovery if the relay-list fetch hangs.

Generated by Claude Code

claude added 2 commits July 28, 2026 07:24
…s dead

WgEgress already retries a broken tunnel with backoff, but every retry
re-resolved and restarted against the same fixed [Peer] endpoint baked
into the profile. If the relay server itself is down (not just a local
network blip), that loop never recovers.

After a few consecutive full-restart cycles keep failing to produce any
traffic, WgEgress now asks a new provider-aware hook (WgRelayFailover) to
move the active Mullvad/IVPN profile to a different relay, reusing the
existing key material, before continuing the restart loop. Self-hosted or
manually imported profiles are left untouched, since there is only one
server to fail over to.

Fixes #679
- Bound the relay-list fetch with a real timeout (FAILOVER_TIMEOUT_MS):
  previously forceRestartPending blocked every other recovery path while
  the unbounded network call ran, so a hang could stall recovery longer
  than not having failover at all.
- Make failoverInFlight an AtomicBoolean to close a check-then-set race
  between the monitor and rebind threads.
- Only trigger failover from monitor-observed breaks, not from rebind
  failures after a network change (those aren't evidence the relay is
  dead).
- Reset the restart backoff after a successful switch so a fresh relay
  doesn't inherit the old one's penalty.
- Persist the IVPN session WgRelayFailover generates/reuses; it was
  minting sessions that never got saved, orphaning them against the
  account's device limit and desyncing stored session state from the
  live config.
- Scope the config write to the profile ID resolved before the network
  call (WgProfileManager#updateProfileConfigIfActive), so a profile
  switch mid-fetch can't overwrite a different profile's stored config.
- Exclude the relay that just failed from the next pick when another
  candidate exists, and reject a pick that silently widens the exit
  country, for both Mullvad and IVPN.
- Distinguish ApiRejectedException/CaptchaRequiredException from other
  failures in the failover log.
- Add WgRelayFailoverTest and extend WgProfileManagerTest for the new
  guard clauses.

kasnder commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Closing — this was opened as a standalone follow-up before realizing the fix commit should just land on #688's own branch (claude/github-issue-679-2zbcql) instead. Pushed there directly; see #688.


Generated by Claude Code

@kasnder kasnder closed this Jul 28, 2026
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.

2 participants