use super::{Error, Result}; use mullvad_types::settings::SettingsVersion; use serde::{Deserialize, Serialize}; use std::net::IpAddr; // ====================================================== // Section for vendoring types and values that // this settings version depend on. See `mod.rs`. #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "snake_case")] pub enum DnsState { #[default] Default, Custom, } /// DNS config #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(default)] pub struct DnsOptions { pub state: DnsState, pub default_options: DefaultDnsOptions, pub custom_options: CustomDnsOptions, } /// Default DNS config #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(default)] pub struct DefaultDnsOptions { pub block_ads: bool, pub block_trackers: bool, } /// Custom DNS config #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct CustomDnsOptions { pub addresses: Vec, } // ====================================================== pub fn migrate(settings: &mut serde_json::Value) -> Result<()> { if !version_matches(settings) { return Ok(()); } log::info!("Migrating settings format to V4"); let dns_options = || -> Option<&serde_json::Value> { settings.get("tunnel_options")?.get("dns_options") }(); if let Some(options) = dns_options && options.get("state").is_none() { let new_state = if options .get("custom") .map(|custom| custom.as_bool().unwrap_or(false)) .unwrap_or(false) { DnsState::Custom } else { DnsState::Default }; let addresses = if let Some(addrs) = options.get("addresses") { serde_json::from_value(addrs.clone()).map_err(|_| Error::InvalidSettingsContent)? } else { vec![] }; settings["tunnel_options"]["dns_options"] = serde_json::json!(DnsOptions { state: new_state, default_options: DefaultDnsOptions::default(), custom_options: CustomDnsOptions { addresses }, }); } settings["settings_version"] = serde_json::json!(SettingsVersion::V4); Ok(()) } fn version_matches(settings: &serde_json::Value) -> bool { settings .get("settings_version") .map(|version| version == SettingsVersion::V3 as u64) .unwrap_or(false) } #[cfg(test)] mod test { use super::{migrate, version_matches}; pub const V3_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { "normal": { "location": { "only": { "country": "se" } }, "tunnel_protocol": "any", "wireguard_constraints": { "port": "any" }, "openvpn_constraints": { "port": { "only": 1195 }, "protocol": { "only": "udp" } } } }, "bridge_settings": { "normal": { "location": "any" } }, "bridge_state": "auto", "allow_lan": true, "block_when_disconnected": false, "auto_connect": false, "tunnel_options": { "openvpn": { "mssfix": null }, "wireguard": { "mtu": null, "rotation_interval": { "secs": 86400, "nanos": 0 } }, "generic": { "enable_ipv6": false }, "dns_options": { "custom": false, "addresses": [ "1.1.1.1", "1.2.3.4" ] } }, "settings_version": 3 } "#; pub const V4_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { "normal": { "location": { "only": { "country": "se" } }, "tunnel_protocol": "any", "wireguard_constraints": { "port": "any" }, "openvpn_constraints": { "port": { "only": 1195 }, "protocol": { "only": "udp" } } } }, "bridge_settings": { "normal": { "location": "any" } }, "bridge_state": "auto", "allow_lan": true, "block_when_disconnected": false, "auto_connect": false, "tunnel_options": { "openvpn": { "mssfix": null }, "wireguard": { "mtu": null, "rotation_interval": { "secs": 86400, "nanos": 0 } }, "generic": { "enable_ipv6": false }, "dns_options": { "state": "default", "default_options": { "block_ads": false, "block_trackers": false }, "custom_options": { "addresses": [ "1.1.1.1", "1.2.3.4" ] } } }, "settings_version": 4 } "#; #[test] fn test_v3_migration() { let mut old_settings = serde_json::from_str(V3_SETTINGS).unwrap(); assert!(version_matches(&old_settings)); migrate(&mut old_settings).unwrap(); let new_settings: serde_json::Value = serde_json::from_str(V4_SETTINGS).unwrap(); assert_eq!(&old_settings, &new_settings); } }