summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOdd Stranne <odd@mullvad.net>2022-03-15 14:26:54 +0100
committerOdd Stranne <odd@mullvad.net>2022-03-24 10:36:16 +0100
commitcf4011b9063da4bbe29c735a444154d462d47288 (patch)
tree70d1ae4910ec3e60a2c72b8d6544c5486253b42e
parentf308d4bd6be1dda4112e33b829b8d406c6a7eb86 (diff)
downloadmullvadvpn-cf4011b9063da4bbe29c735a444154d462d47288.tar.xz
mullvadvpn-cf4011b9063da4bbe29c735a444154d462d47288.zip
Add migration logic for WireGuard port constraint
-rw-r--r--mullvad-daemon/src/migrations/mod.rs1
-rw-r--r--mullvad-daemon/src/migrations/v5.rs163
-rw-r--r--mullvad-types/src/settings/mod.rs4
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