Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VPN-6408 and VPN-6678: Turn daemon server switches into round robin of servers, and fix DNS bug #9980

Merged
merged 11 commits into from
Oct 29, 2024

Conversation

mcleinman
Copy link
Collaborator

@mcleinman mcleinman commented Oct 22, 2024

Description

There are a few things in this PR, apologies. If you'd like them broken out into separate PRs, please let me know and I'll happily restructure.

  1. (no ticket) Adding a developer debug option for daemon silent switching on Android. This exists on iOS already, and this was extended for Android as well. (This was necessary to test other work in this PR.)
  2. (VPN-6408) Currently daemon silent server switches (which exist only on mobile, as on desktop the client is always running) will attempt a single switch to a new server. Once they've used their backup server, they never again try to switch until the VPN is again activated from the client. This updates it to use up to 4 servers, and rotate between them.
  3. (VPN-6678) In looking at the code and testing my work, I discovered a bug - Special DNS (custom or privacy DNS) isn't maintained when moving to the backup server - it falls back to using the main Mullvad DNS. (This currently exists in prod, though it doesn't look like there was a ticket for it before I filed one today.) I've fixed this bug by looking at whether the main DNS is used on the main server, and setting DNS for the fallback servers appropriately.

Reference

VPN-6408 and VPN-6678

Checklist

  • My code follows the style guidelines for this project
  • I have not added any packages that contain high risk or unknown licenses (GPL, LGPL, MPL, etc. consult with DevOps if in question)
  • I have performed a self review of my own code
  • I have commented my code PARTICULARLY in hard to understand areas
  • I have added thorough tests where needed

@mcleinman mcleinman requested review from strseb, lesleyjanenorton and oskirby and removed request for lesleyjanenorton October 22, 2024 21:12
Base automatically changed from vpn-6407-15-minutes-between-switches to main October 24, 2024 22:01
@mcleinman
Copy link
Collaborator Author

With #9972 merged into main, this is now ready for review.

@@ -588,6 +589,16 @@ class VPNService : android.net.VpnService() {
return confBuilder.build()
}

private fun getServerConfig(obj: JSONObject, useFallbackServer: Boolean): JSONObject {
Copy link
Collaborator

@oskirby oskirby Oct 28, 2024

Choose a reason for hiding this comment

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

Just a minor nitpick, do we really need the useFallbackServer parameter anymore? That seems like it should be covered by the state of the currentServerConfig variable.

Also, this function is not idempotent, it actually does the rotation between servers, so a better name might be getNextServerConfig() to imply that it's iterating.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated to getNextServerConfig

useFallbackServer now just controls whether we use the first server in the config (when false) or the next server in line (when true). I think using the client-selected main server on initial connection seems helpful - and we thus would need to keep useFallBackServer in order to reset currentServerConfig. (If the daemon is maintained between VPN activations - I'm not sure if it is - we'd otherwise never reset currentServerConfig back to 0, and potentially take a non-first server on initial connection to a new geo, for instance.)

@@ -295,12 +295,18 @@ void ServerData::setExitServerPublicKey(const QString& publicKey) {
m_exitServerPublicKey = publicKey;
}

const Server ServerData::backupServer(const QString& currentPublicKey) const {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ideally, the backup servers should be selected according to the weighting as well.... but that is hard to do so I accept that this should be fine for fallback and recovery purposes. (See the mess of an algorithm in Server::weightChooser() for how it's done for the primary server) but a comment would be nice for our future selves.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just philosophizing: It would be a cleaner approach if we combined the concepts of primary and backup servers and instead we just provided a list of servers down to the controller to handle. This would require changing the controllers APIs a bunch though, and that's also scary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Philosophizing - I almost went that way, but this idea of additional backups isn't relevant to desktop. (We pass just the main server to the platform-specific controller, and then the mobile controller requests additional backup servers. And for desktop controllers, they get the main server and that's all they ever need - any server switches will come from the client.)

@@ -161,27 +161,30 @@ public class IOSControllerImpl: NSObject {
}
}

@objc func connect(serverData: VPNServerData, fallbackServer: VPNServerData?, excludeLocalNetworks: Bool, allowedIPAddressRanges: [VPNIPAddressRange], reason: Int, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) {
@objc func connect(serverData: [VPNServerData], excludeLocalNetworks: Bool, allowedIPAddressRanges: [VPNIPAddressRange], reason: Int, gleanDebugTag: String, isSuperDooperFeatureActive: Bool, installationId: String, disconnectOnErrorCallback: @escaping () -> Void, onboardingCompletedCallback: @escaping () -> Void, vpnConfigPermissionResponseCallback: @escaping (Bool) -> Void) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Does all of this really have to be on one line? It's kind of hard to read.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It doesn't have to be. I had just learned to live with it, but you're right, it's ugly and unclear. I've updated this to a few lines (both here and in configureTunnel) - thanks for saying something.

Copy link
Collaborator

@oskirby oskirby left a comment

Choose a reason for hiding this comment

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

Looks good - the only comments I've got are nitpicks and random philosophical journeys.

@mcleinman mcleinman merged commit 7fa5bb4 into main Oct 29, 2024
117 checks passed
@mcleinman mcleinman deleted the vpn-6408-adjust-daemon-switches branch October 29, 2024 14:56
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