diff options
| author | Odd Stranne <odd@mullvad.net> | 2022-03-15 14:26:54 +0100 |
|---|---|---|
| committer | Odd Stranne <odd@mullvad.net> | 2022-03-24 10:36:16 +0100 |
| commit | cf4011b9063da4bbe29c735a444154d462d47288 (patch) | |
| tree | 70d1ae4910ec3e60a2c72b8d6544c5486253b42e | |
| parent | f308d4bd6be1dda4112e33b829b8d406c6a7eb86 (diff) | |
| download | mullvadvpn-cf4011b9063da4bbe29c735a444154d462d47288.tar.xz mullvadvpn-cf4011b9063da4bbe29c735a444154d462d47288.zip | |
Add migration logic for WireGuard port constraint
| -rw-r--r-- | mullvad-daemon/src/migrations/mod.rs | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v5.rs | 163 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 4 |
3 files changed, 128 insertions, 40 deletions
diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs index 98f3781061..5392f9828c 100644 --- a/mullvad-daemon/src/migrations/mod.rs +++ b/mullvad-daemon/src/migrations/mod.rs @@ -43,7 +43,6 @@ mod v1; mod v2; mod v3; mod v4; -// Not yet done. Add to this instead of creating v6 for now. mod v5; const SETTINGS_FILE: &str = "settings.json"; diff --git a/mullvad-daemon/src/migrations/v5.rs b/mullvad-daemon/src/migrations/v5.rs index 4231e52a23..ba12c5ae91 100644 --- a/mullvad-daemon/src/migrations/v5.rs +++ b/mullvad-daemon/src/migrations/v5.rs @@ -1,5 +1,5 @@ use super::{Error, Result}; -use mullvad_types::settings::SettingsVersion; +use mullvad_types::{relay_constraints::Constraint, settings::SettingsVersion}; // ====================================================== // Section for vendoring types and values that @@ -7,6 +7,45 @@ use mullvad_types::settings::SettingsVersion; pub type AccountToken = String; +/// Representation of a transport protocol, either UDP or TCP. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum TransportProtocol { + /// Represents the UDP transport protocol. + Udp, + /// Represents the TCP transport protocol. + Tcp, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct TransportPort { + pub protocol: TransportProtocol, + pub port: Constraint<u16>, +} + +/// Contains obfuscation settings +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct ObfuscationSettings { + pub selected_obfuscation: SelectedObfuscation, + pub udp2tcp: Udp2TcpObfuscationSettings, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct Udp2TcpObfuscationSettings { + pub port: Constraint<u16>, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SelectedObfuscation { + Auto, + Off, + Udp2Tcp, +} + // ====================================================== pub(crate) struct MigrationData { @@ -14,13 +53,6 @@ pub(crate) struct MigrationData { pub wg_data: Option<serde_json::Value>, } -/// This is an open ended migration. There is no v6 yet! -/// The migrations performed by this function are still backwards compatible. -/// The JSON coming out of this migration can be read by any v5 compatible daemon. -/// -/// When further migrations are needed, add them here and if they are not backwards -/// compatible then create v6 and "close" this migration for further modification. -/// /// # Changes to the format /// /// The ability to disable WireGuard multihop while preserving the entry location was added. @@ -32,61 +64,105 @@ pub(crate) struct MigrationData { /// is null in order to make it default back to the default location. /// /// This also removes the account token and WireGuard key from the settings. +/// +/// Additionally, the WireGuard protocol constraint, if set to be using TCP, is migrated into +/// having an active Udp2Tcp obfuscator. The protocol constraint is then removed from WireGuard +/// settings since all WireGuard traffic is UDP. pub(crate) async fn migrate(settings: &mut serde_json::Value) -> Result<Option<MigrationData>> { if !version_matches(settings) { return Ok(None); } - let wireguard_constraints = || -> Option<&serde_json::Value> { - settings - .get("relay_settings")? - .get("normal")? - .get("wireguard_constraints") - }(); - if let Some(constraints) = wireguard_constraints { - if let Some(location) = constraints.get("entry_location") { - if constraints.get("use_multihop").is_none() { + if let Some(wireguard_constraints) = get_wireguard_constraints(settings) { + if let Some(location) = wireguard_constraints.get("entry_location") { + if wireguard_constraints.get("use_multihop").is_none() { if location.is_null() { // "Null" is no longer valid. It is not an option. - settings["relay_settings"]["normal"]["wireguard_constraints"] + wireguard_constraints .as_object_mut() .ok_or(Error::NoMatchingVersion)? .remove("entry_location"); } else { - settings["relay_settings"]["normal"]["wireguard_constraints"]["use_multihop"] = - serde_json::json!(true); + wireguard_constraints["use_multihop"] = serde_json::json!(true); + } + } + } + // The field `pub port: Constraint<TransportPort>` is now `pub port: Constraint<u16>`. + // Data is migrated as follows: + // If the existing field has `protocol == Tcp` configured, then we need to create a + // corresponding setting to enable the Udp2Tcp obfuscator. In this case, the port + // constraint is moved as well. Otherwise the existing port constraint is moved into + // the new field. + // + if let Some(port) = wireguard_constraints.get("port") { + let port_constraint: Constraint<TransportPort> = + serde_json::from_value(port.clone()).map_err(Error::ParseError)?; + if let Some(transport_port) = port_constraint.option() { + let (port, obfuscation_settings) = match transport_port.protocol { + TransportProtocol::Udp => (serde_json::json!(transport_port.port), None), + TransportProtocol::Tcp => ( + serde_json::json!(Constraint::<u16>::Any), + Some(serde_json::json!(create_migrated_obfuscation_settings( + transport_port.port + ))), + ), + }; + wireguard_constraints["port"] = port; + if let Some(obfuscation_settings) = obfuscation_settings { + settings["obfuscation_settings"] = obfuscation_settings; } } } } - if let Some(token) = settings.get("account_token").filter(|t| !t.is_null()) { + let migration_data = if let Some(token) = settings.get("account_token").filter(|t| !t.is_null()) + { let token: AccountToken = serde_json::from_value(token.clone()).map_err(Error::ParseError)?; let migration_data = if let Some(wg_data) = settings.get("wireguard").filter(|wg| !wg.is_null()) { - Ok(Some(MigrationData { + Some(MigrationData { token, wg_data: Some(wg_data.clone()), - })) + }) } else { - Ok(Some(MigrationData { + Some(MigrationData { token, wg_data: None, - })) + }) }; let settings_map = settings.as_object_mut().ok_or(Error::NoMatchingVersion)?; settings_map.remove("account_token"); settings_map.remove("wireguard"); - return migration_data; - } + migration_data + } else { + None + }; - // Note: Not incrementing the version number yet, since this migration is still open - // for future modification. - // settings["settings_version"] = serde_json::json!(SettingsVersion::V6); + settings["settings_version"] = serde_json::json!(SettingsVersion::V6); + + Ok(migration_data) +} - Ok(None) +fn get_wireguard_constraints(settings: &mut serde_json::Value) -> Option<&mut serde_json::Value> { + if let Some(relay_settings) = settings.get_mut("relay_settings") { + if let Some(normal) = relay_settings.get_mut("normal") { + return normal.get_mut("wireguard_constraints"); + } + } + None +} + +// Create an ObfuscationSettings struct that replaces the `protocol == TCP` setting +// that was previously used on the wireguard constraints. +// If a port is specified, this is the remote port to be used for Udp2Tcp. +// +fn create_migrated_obfuscation_settings(port: Constraint<u16>) -> ObfuscationSettings { + ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Udp2Tcp, + udp2tcp: Udp2TcpObfuscationSettings { port }, + } } fn version_matches(settings: &mut serde_json::Value) -> bool { @@ -101,7 +177,7 @@ mod test { use super::{migrate, version_matches}; use serde_json; - pub const V5_SETTINGS_V1: &str = r#" + pub const V5_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { @@ -113,7 +189,12 @@ mod test { }, "tunnel_protocol": "any", "wireguard_constraints": { - "port": "any", + "port": { + "only": { + "protocol": "tcp", + "port": "any" + } + }, "ip_version": "any", "entry_location": "any" }, @@ -170,7 +251,7 @@ mod test { } "#; - pub const V5_SETTINGS_V2: &str = r#" + pub const V6_SETTINGS: &str = r#" { "relay_settings": { "normal": { @@ -203,6 +284,12 @@ mod test { "location": "any" } }, + "obfuscation_settings": { + "selected_obfuscation": "udp2_tcp", + "udp2tcp": { + "port": "any" + } + }, "bridge_state": "auto", "allow_lan": true, "block_when_disconnected": false, @@ -235,17 +322,17 @@ mod test { } } }, - "settings_version": 5 + "settings_version": 6 } "#; #[tokio::test] - async fn test_v5_v1_migration() { - let mut old_settings = serde_json::from_str(V5_SETTINGS_V1).unwrap(); + async fn test_v5_to_v6_migration() { + let mut old_settings = serde_json::from_str(V5_SETTINGS).unwrap(); assert!(version_matches(&mut old_settings)); migrate(&mut old_settings).await.unwrap(); - let new_settings: serde_json::Value = serde_json::from_str(V5_SETTINGS_V2).unwrap(); + let new_settings: serde_json::Value = serde_json::from_str(V6_SETTINGS).unwrap(); assert_eq!(&old_settings, &new_settings); } diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index c2f0a17781..638a0d01e4 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -18,7 +18,7 @@ use talpid_types::net::{self, openvpn, GenericTunnelOptions}; /// 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::V5; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V6; #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] #[repr(u32)] @@ -27,6 +27,7 @@ pub enum SettingsVersion { V3 = 3, V4 = 4, V5 = 5, + V6 = 6, } impl<'de> Deserialize<'de> for SettingsVersion { @@ -39,6 +40,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), v if v == SettingsVersion::V4 as u32 => Ok(SettingsVersion::V4), v if v == SettingsVersion::V5 as u32 => Ok(SettingsVersion::V5), + v if v == SettingsVersion::V6 as u32 => Ok(SettingsVersion::V6), v => Err(serde::de::Error::custom(format!( "{} is not a valid SettingsVersion", v |
