Repository navigation
Keep the tunnel state honest: reconcile, retry, detect drops - #14
Merged
Merged
Conversation
The switcher now records which profile the routes ask for and reconciles it with the tunnel that is actually up. Every trigger (app switch, retry, tunnel drop, refreshed config, end of a connection test) just signals one reconciler, and the UI only shows a profile as active once it is up. - Mullvad refresh kept the relay's bare hostname as Endpoint, which does not resolve. Refresh now keeps the existing endpoint, generated configs use <host>.relays.mullvad.net, and broken configs are repaired when the service starts. Refreshed configs are applied to a running tunnel. - A failed start no longer marks the profile active. The status chip and overlay show "not connected" and the start is retried after 5/10/20/40/60 s. - A tunnel taken down externally (another VPN app took the slot) is detected. UniVPN shows the warning and reconnects on the next app switch instead of fighting the other app for the slot. - Restart and wake-up look back 3 days for the foreground app instead of 10 s, and the last requested profile survives a process restart. - "Default (keep current tunnel)" now keeps the current tunnel. - App switches during a connection test wait for it instead of being dropped after 2 s. - Test connection requires bytes from the peer (a completed handshake) before trusting the external-IP check. - Refresh updates the profile instead of INSERT OR REPLACE, so a profile deleted mid-refresh stays deleted and its metadata isn't cascaded away. Fixes #1, fixes #2, fixes #3, fixes #4, fixes #5, fixes #7, fixes #10, fixes #11 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Fixes the "shows protected but isn't" problems from the community review.
What changed
VpnSwitcherServicenow records which profile the routes ask for and reconciles it with the tunnel that is actually up. App switches, retries, tunnel drops, refreshed configs and the end of a connection test all signal one reconciler. The UI only shows a profile as active once its tunnel is up.<host>.relays.mullvad.net; configs broken by earlier refreshes are repaired on service start; refreshed configs are applied to a running tunnelonStateChange(DOWN)without our own stop is reported to the switcher. If another VPN app took the slot, UniVPN shows the warning and reconnects on the next app switch instead of fighting it@Updatein a transaction instead ofINSERT OR REPLACETesting
Unit tests (new, JVM):
MullvadEndpointTest,WgConfigTest,RouteDecisionTest— 14 tests.Instrumented (Android TV emulator, API 30): all 13 pass, including the new
ProfileRefreshDaoTest(update keeps metadata, deleted profile stays deleted) and the updated "no route keeps the tunnel" test.Manual, on the emulator with seeded profiles:
DNS_RESOLUTION_FAILURE, chip shows ⚠ in red, retries at 5 s, 10 s, 20 s; switching to a working profile cancels the retriesal-tia-wg-001endpoint → repaired on service start, resolves and comes upNot tested: the success path of Test Connection and a real provider refresh (no WireGuard server or provider account in the test setup).
🤖 Generated with Claude Code