diff options
| author | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-03-07 14:18:03 +0100 |
|---|---|---|
| committer | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-03-07 15:46:55 +0100 |
| commit | 928a918b841d076a63720a67f51cb91e704744c1 (patch) | |
| tree | c429dc046a18ffc12a8aa8e0563092163624b757 | |
| parent | 9558232677cf157c1897602a2d8bc235e0d76b64 (diff) | |
| download | mullvadvpn-928a918b841d076a63720a67f51cb91e704744c1.tar.xz mullvadvpn-928a918b841d076a63720a67f51cb91e704744c1.zip | |
Add tunnel protocol migration to new v10
| -rw-r--r-- | mullvad-daemon/src/migrations/mod.rs | 3 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v10.rs | 554 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 4 |
3 files changed, 560 insertions, 1 deletions
diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs index eaf3350e0c..95301a8df6 100644 --- a/mullvad-daemon/src/migrations/mod.rs +++ b/mullvad-daemon/src/migrations/mod.rs @@ -46,6 +46,7 @@ use tokio::{ mod account_history; mod device; mod v1; +mod v10; mod v2; mod v3; mod v4; @@ -207,6 +208,8 @@ async fn migrate_settings( }), )?; + v10::migrate(settings)?; + Ok(migration_data) } diff --git a/mullvad-daemon/src/migrations/v10.rs b/mullvad-daemon/src/migrations/v10.rs new file mode 100644 index 0000000000..f38965dfab --- /dev/null +++ b/mullvad-daemon/src/migrations/v10.rs @@ -0,0 +1,554 @@ +use super::{Error, Result}; +use mullvad_types::settings::SettingsVersion; +use talpid_types::net::TunnelType; + +/// Automatic tunnel protocol has been removed. If the tunnel protocol is set to `any`, it will be +/// migrated to `wireguard`, unless the location is an openvpn relay, in which case it will be +/// migrated to `openvpn`. +pub fn migrate(settings: &mut serde_json::Value) -> Result<()> { + if !(version(settings) == Some(SettingsVersion::V10)) { + return Ok(()); + } + + log::info!("Migrating settings format to v11"); + + migrate_tunnel_type(settings)?; + + settings["settings_version"] = serde_json::json!(SettingsVersion::V11); + + Ok(()) +} + +fn version(settings: &serde_json::Value) -> Option<SettingsVersion> { + settings + .get("settings_version") + .and_then(|version| serde_json::from_value(version.clone()).ok()) +} + +fn relay_settings(settings: &mut serde_json::Value) -> Option<&mut serde_json::Value> { + settings.get_mut("relay_settings")?.get_mut("normal") +} + +fn migrate_tunnel_type(settings: &mut serde_json::Value) -> Result<()> { + let Some(ref mut normal) = relay_settings(settings) else { + return Ok(()); + }; + match normal.get_mut("tunnel_protocol") { + // Migrate tunnel protocol "any" + Some(serde_json::Value::String(s)) if s == "any" => { + // If openvpn is selected, migrate to openvpn tunnel type + // Otherwise, select wireguard + let hostname = normal + .get_mut("location") + .and_then(|location| location.get_mut("only")) + .and_then(|only| only.get_mut("location")) + .and_then(|only| only.get_mut("hostname").cloned()); + + let protocol = if let Some(serde_json::Value::String(s)) = hostname { + if s.split('-').any(|token| token == "ovpn") { + TunnelType::OpenVpn + } else { + TunnelType::Wireguard + } + } else { + TunnelType::Wireguard + }; + + normal["tunnel_protocol"] = serde_json::json!(protocol); + } + // Migrate '"only": { "tunnel_protocol": $tunnel_protocol }' + // to '"tunnel_protocol": $tunnel_protocol' + Some(serde_json::Value::Object(ref mut constraint)) => { + if let Some(tunnel_type) = constraint.get("only") { + let tunnel_type: TunnelType = serde_json::from_value(tunnel_type.clone()) + .map_err(|_| Error::InvalidSettingsContent)?; + normal["tunnel_protocol"] = serde_json::json!(tunnel_type); + } else { + return Err(Error::InvalidSettingsContent); + } + } + Some(_) => { + return Err(Error::InvalidSettingsContent); + } + // Unexpected result. Do nothing. + None => (), + } + Ok(()) +} +#[cfg(test)] +mod test { + use mullvad_types::settings::SettingsVersion; + + use super::{migrate, version}; + + /// Tunnel protocol "any" is migrated to wireguard + #[test] + fn test_v10_to_v11_migration() { + // TODO: Also test the case where the location is not an openvpn relay and the tunnel type + // is any + let mut old_settings = serde_json::from_str(V10_SETTINGS).unwrap(); + + assert_eq!(version(&old_settings), Some(SettingsVersion::V10)); + migrate(&mut old_settings).unwrap(); + let new_settings: serde_json::Value = serde_json::from_str(V11_SETTINGS).unwrap(); + assert_eq!(version(&new_settings), Some(SettingsVersion::V11)); + + eprintln!( + "old_settings: {}", + serde_json::to_string_pretty(&old_settings).unwrap() + ); + eprintln!( + "new_settings: {}", + serde_json::to_string_pretty(&new_settings).unwrap() + ); + assert_eq!(&old_settings, &new_settings); + } + + /// Tunnel protocol "any" is migrated to openvpn, since the location is an openvpn relay + #[test] + fn test_v10_to_v11_migration_openvpn_location() { + // TODO: Also test the case where the location is not an openvpn relay and the tunnel type + // is any + let mut old_settings = serde_json::from_str(V10_SETTINGS_OPENVPN_LOCATION).unwrap(); + + assert_eq!(version(&old_settings), Some(SettingsVersion::V10)); + migrate(&mut old_settings).unwrap(); + let new_settings: serde_json::Value = + serde_json::from_str(V11_SETTINGS_OPENVPN_LOCATION).unwrap(); + assert_eq!(version(&new_settings), Some(SettingsVersion::V11)); + + eprintln!( + "old_settings: {}", + serde_json::to_string_pretty(&old_settings).unwrap() + ); + eprintln!( + "new_settings: {}", + serde_json::to_string_pretty(&new_settings).unwrap() + ); + assert_eq!(&old_settings, &new_settings); + } + + /// Tunnel protocol is set to any, but the location is not an openvpn relay + pub const V10_SETTINGS: &str = r#" + { + "relay_settings": { + "normal": { + "location": { + "only": { + "location": { + "country": "se" + } + } + }, + "providers": "any", + "ownership": "any", + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any", + "ip_version": "any", + "use_multihop": false, + "entry_location": { + "only": { + "location": { + "country": "se" + } + } + } + }, + "openvpn_constraints": { + "port": "any" + } + } + }, + "bridge_settings": { + "bridge_type": "normal", + "normal": { + "location": "any", + "providers": "any", + "ownership": "any" + }, + "custom": null + }, + "obfuscation_settings": { + "selected_obfuscation": "auto", + "udp2tcp": { + "port": "any" + } + }, + "bridge_state": "auto", + "custom_lists": { + "custom_lists": [] + }, + "api_access_methods": { + "direct": { + "id": "d81121bf-c942-4ca4-971f-8ea6581bc915", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "mullvad_bridges": { + "id": "92135711-534d-4950-963d-93e446a792e4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [] + }, + "allow_lan": false, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "quantum_resistant": "auto", + "rotation_interval": null + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "state": "default", + "default_options": { + "block_ads": false, + "block_trackers": false, + "block_malware": false, + "block_adult_content": false, + "block_gambling": false, + "block_social_media": false + }, + "custom_options": { + "addresses": [] + } + } + }, + "relay_overrides": [], + "show_beta_releases": true, + "settings_version": 10 + } + "#; + + /// Tunnel protocol is migrated to wireguard + pub const V11_SETTINGS: &str = r#" + { + "relay_settings": { + "normal": { + "location": { + "only": { + "location": { + "country": "se" + } + } + }, + "providers": "any", + "ownership": "any", + "tunnel_protocol": "wireguard", + "wireguard_constraints": { + "port": "any", + "ip_version": "any", + "use_multihop": false, + "entry_location": { + "only": { + "location": { + "country": "se" + } + } + } + }, + "openvpn_constraints": { + "port": "any" + } + } + }, + "bridge_settings": { + "bridge_type": "normal", + "normal": { + "location": "any", + "providers": "any", + "ownership": "any" + }, + "custom": null + }, + "obfuscation_settings": { + "selected_obfuscation": "auto", + "udp2tcp": { + "port": "any" + } + }, + "bridge_state": "auto", + "custom_lists": { + "custom_lists": [] + }, + "api_access_methods": { + "direct": { + "id": "d81121bf-c942-4ca4-971f-8ea6581bc915", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "mullvad_bridges": { + "id": "92135711-534d-4950-963d-93e446a792e4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [] + }, + "allow_lan": false, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "quantum_resistant": "auto", + "rotation_interval": null + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "state": "default", + "default_options": { + "block_ads": false, + "block_trackers": false, + "block_malware": false, + "block_adult_content": false, + "block_gambling": false, + "block_social_media": false + }, + "custom_options": { + "addresses": [] + } + } + }, + "relay_overrides": [], + "show_beta_releases": true, + "settings_version": 11 + } + "#; + + /// This settings blob contains no constraint for tunnel type + pub const V10_SETTINGS_OPENVPN_LOCATION: &str = r#" +{ + "relay_settings": { + "normal": { + "location": { + "only": { + "location": { + "hostname": "at-vie-ovpn-001" + } + } + }, + "providers": "any", + "ownership": "any", + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any", + "ip_version": "any", + "use_multihop": false, + "entry_location": { + "only": { + "location": { + "country": "se" + } + } + } + }, + "openvpn_constraints": { + "port": "any" + } + } + }, + "bridge_settings": { + "bridge_type": "normal", + "normal": { + "location": "any", + "providers": "any", + "ownership": "any" + }, + "custom": null + }, + "obfuscation_settings": { + "selected_obfuscation": "auto", + "udp2tcp": { + "port": "any" + } + }, + "bridge_state": "auto", + "custom_lists": { + "custom_lists": [] + }, + "api_access_methods": { + "direct": { + "id": "d81121bf-c942-4ca4-971f-8ea6581bc915", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "mullvad_bridges": { + "id": "92135711-534d-4950-963d-93e446a792e4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [] + }, + "allow_lan": false, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "quantum_resistant": "auto", + "rotation_interval": null + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "state": "default", + "default_options": { + "block_ads": false, + "block_trackers": false, + "block_malware": false, + "block_adult_content": false, + "block_gambling": false, + "block_social_media": false + }, + "custom_options": { + "addresses": [] + } + } + }, + "relay_overrides": [], + "show_beta_releases": true, + "settings_version": 10 +} +"#; + + /// This settings blob does not contain an "any" tunnel type + pub const V11_SETTINGS_OPENVPN_LOCATION: &str = r#" +{ + "relay_settings": { + "normal": { + "location": { + "only": { + "location": { + "hostname": "at-vie-ovpn-001" + } + } + }, + "providers": "any", + "ownership": "any", + "tunnel_protocol": "openvpn", + "wireguard_constraints": { + "port": "any", + "ip_version": "any", + "use_multihop": false, + "entry_location": { + "only": { + "location": { + "country": "se" + } + } + } + }, + "openvpn_constraints": { + "port": "any" + } + } + }, + "bridge_settings": { + "bridge_type": "normal", + "normal": { + "location": "any", + "providers": "any", + "ownership": "any" + }, + "custom": null + }, + "obfuscation_settings": { + "selected_obfuscation": "auto", + "udp2tcp": { + "port": "any" + } + }, + "bridge_state": "auto", + "custom_lists": { + "custom_lists": [] + }, + "api_access_methods": { + "direct": { + "id": "d81121bf-c942-4ca4-971f-8ea6581bc915", + "name": "Direct", + "enabled": true, + "access_method": { + "built_in": "direct" + } + }, + "mullvad_bridges": { + "id": "92135711-534d-4950-963d-93e446a792e4", + "name": "Mullvad Bridges", + "enabled": true, + "access_method": { + "built_in": "bridge" + } + }, + "custom": [] + }, + "allow_lan": false, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "quantum_resistant": "auto", + "rotation_interval": null + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "state": "default", + "default_options": { + "block_ads": false, + "block_trackers": false, + "block_malware": false, + "block_adult_content": false, + "block_gambling": false, + "block_social_media": false + }, + "custom_options": { + "addresses": [] + } + } + }, + "relay_overrides": [], + "show_beta_releases": true, + "settings_version": 11 +} +"#; +} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index e65c21c148..432e95391b 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -20,7 +20,7 @@ mod dns; /// latest version that exists in `SettingsVersion`. /// This should be bumped when a new version is introduced along with a migration /// being added to `mullvad-daemon`. -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V10; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V11; #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)] #[repr(u32)] @@ -34,6 +34,7 @@ pub enum SettingsVersion { V8 = 8, V9 = 9, V10 = 10, + V11 = 11, } impl<'de> Deserialize<'de> for SettingsVersion { @@ -51,6 +52,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { v if v == SettingsVersion::V8 as u32 => Ok(SettingsVersion::V8), v if v == SettingsVersion::V9 as u32 => Ok(SettingsVersion::V9), v if v == SettingsVersion::V10 as u32 => Ok(SettingsVersion::V10), + v if v == SettingsVersion::V11 as u32 => Ok(SettingsVersion::V11), v => Err(serde::de::Error::custom(format!( "{v} is not a valid SettingsVersion" ))), |
