diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-01-04 17:33:21 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-01-10 10:42:49 +0100 |
| commit | e162d0b94c6e013e964ef9dff4bde80ccae4d58f (patch) | |
| tree | 820758d4b8dd1a1d1209e75b6fe08999b041f6e3 | |
| parent | edbd1f52c44ba6ee9a290146f714453fc87e689d (diff) | |
| download | mullvadvpn-e162d0b94c6e013e964ef9dff4bde80ccae4d58f.tar.xz mullvadvpn-e162d0b94c6e013e964ef9dff4bde80ccae4d58f.zip | |
Add patch export to the management interface
| -rw-r--r-- | mullvad-cli/src/cmds/mod.rs | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/patch.rs (renamed from mullvad-cli/src/cmds/import_settings.rs) | 6 | ||||
| -rw-r--r-- | mullvad-cli/src/main.rs | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 10 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 8 | ||||
| -rw-r--r-- | mullvad-daemon/src/settings/patch.rs | 41 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 2 | ||||
| -rw-r--r-- | mullvad-management-interface/src/client.rs | 5 |
8 files changed, 69 insertions, 7 deletions
diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 29e0508d80..5a9cede691 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -9,10 +9,10 @@ pub mod bridge; pub mod custom_list; pub mod debug; pub mod dns; -pub mod import_settings; pub mod lan; pub mod lockdown; pub mod obfuscation; +pub mod patch; pub mod proxies; pub mod relay; pub mod relay_constraints; diff --git a/mullvad-cli/src/cmds/import_settings.rs b/mullvad-cli/src/cmds/patch.rs index a89c6814c3..d5a79aaef9 100644 --- a/mullvad-cli/src/cmds/import_settings.rs +++ b/mullvad-cli/src/cmds/patch.rs @@ -9,9 +9,9 @@ use std::{ /// Maximum size of a settings patch. Bigger files/streams cause the read to fail. const MAX_PATCH_BYTES: usize = 10 * 1024; -/// If source is specified, read from the provided file and send it as a settings patch to the -/// daemon. Otherwise, read the patch from standard input. -pub async fn handle(source: String) -> Result<()> { +/// If source is specified, read from the provided file and send it as a settings patch to the daemon. +/// Otherwise, read the patch from standard input. +pub async fn import(source: String) -> Result<()> { let json_blob = tokio::task::spawn_blocking(|| get_blob(source)) .await .unwrap()?; diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 270d3c293e..a5887b39d5 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -169,7 +169,7 @@ async fn main() -> Result<()> { Cli::SplitTunnel(cmd) => cmd.handle().await, Cli::Status { cmd, args } => status::handle(cmd, args).await, Cli::CustomList(cmd) => cmd.handle().await, - Cli::ImportSettings { file } => import_settings::handle(file).await, + Cli::ImportSettings { file } => patch::import(file).await, #[cfg(all(unix, not(target_os = "android")))] Cli::ShellCompletions { shell, dir } => { diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 0d02a068c4..06e289d22d 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -353,8 +353,10 @@ pub enum DaemonCommand { /// Verify that a google play payment was successful through the API. #[cfg(target_os = "android")] VerifyPlayPurchase(ResponseTx<(), Error>, PlayPurchase), - /// Patch the settings using a blob of JSON settings + /// Patch the settings using a JSON patch ApplyJsonSettings(ResponseTx<(), settings::patch::Error>, String), + /// Return a JSON blob containing all overridable settings, if there are any + ExportJsonSettings(ResponseTx<String, settings::patch::Error>), } /// All events that can happen in the daemon. Sent from various threads and exposed interfaces. @@ -1275,6 +1277,7 @@ where self.on_verify_play_purchase(tx, play_purchase) } ApplyJsonSettings(tx, blob) => self.on_apply_json_settings(tx, blob).await, + ExportJsonSettings(tx) => self.on_export_json_settings(tx), } } @@ -2626,6 +2629,11 @@ where Self::oneshot_send(tx, result, "apply_json_settings response"); } + fn on_export_json_settings(&mut self, tx: ResponseTx<String, settings::patch::Error>) { + let result = settings::patch::export_settings(&self.settings); + Self::oneshot_send(tx, result, "export_json_settings response"); + } + /// Set the target state of the client. If it changed trigger the operations needed to /// progress towards that state. /// Returns a bool representing whether or not a state change was initiated. diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 8b8d840cfb..261aa52853 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -892,6 +892,14 @@ impl ManagementService for ManagementServiceImpl { self.wait_for_result(rx).await??; Ok(Response::new(())) } + + async fn export_json_settings(&self, _: Request<()>) -> ServiceResult<String> { + log::debug!("export_json_settings"); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::ExportJsonSettings(tx))?; + let blob = self.wait_for_result(rx).await??; + Ok(Response::new(blob)) + } } impl ManagementServiceImpl { diff --git a/mullvad-daemon/src/settings/patch.rs b/mullvad-daemon/src/settings/patch.rs index 50c0da6304..253d8cb129 100644 --- a/mullvad-daemon/src/settings/patch.rs +++ b/mullvad-daemon/src/settings/patch.rs @@ -33,6 +33,9 @@ pub enum Error { /// Failed to serialize settings #[error(display = "Failed to serialize current settings")] SerializeSettings(#[error(source)] serde_json::Error), + /// Failed to serialize field + #[error(display = "Failed to serialize value")] + SerializeValue(#[error(source)] serde_json::Error), /// Recursion limit reached #[error(display = "Maximum JSON object depth reached")] RecursionLimit, @@ -54,7 +57,9 @@ impl From<Error> for mullvad_management_interface::Status { | Error::DeserializePatched(_) | Error::RecursionLimit => Status::invalid_argument(error.to_string()), Error::Settings(error) => Status::from(error), - Error::SerializeSettings(error) => Status::internal(error.to_string()), + Error::SerializeSettings(error) | Error::SerializeValue(error) => { + Status::internal(error.to_string()) + } } } } @@ -125,6 +130,40 @@ const PERMITTED_SUBKEYS: &PermittedKey = &PermittedKey::object(&[( /// tail-call optimization can be enforced? const RECURSE_LIMIT: usize = 15; +/// Export a patch containing all currently supported settings. +pub fn export_settings(settings: &Settings) -> Result<String, Error> { + let patch = export_settings_inner(settings)?; + serde_json::to_string_pretty(&patch).map_err(Error::SerializeValue) +} + +fn export_settings_inner(settings: &Settings) -> Result<serde_json::Value, Error> { + let mut out = serde_json::Map::new(); + let mut overrides = vec![]; + + for relay_override in &settings.relay_overrides { + let mut relay_override = + serde_json::to_value(relay_override).map_err(Error::SerializeValue)?; + if let Some(relay_overrides) = relay_override.as_object_mut() { + // prune empty override entries + relay_overrides.retain(|_k, v| !v.is_null()); + let has_overrides = relay_overrides.iter().any(|(key, _)| key != "hostname"); + if !has_overrides { + continue; + } + } + overrides.push(relay_override); + } + + if !overrides.is_empty() { + out.insert( + "relay_overrides".to_owned(), + serde_json::Value::Array(overrides), + ); + } + + Ok(serde_json::Value::Object(out)) +} + /// Update the settings with the supplied patch. Only settings specified in `PERMITTED_SUBKEYS` can /// be updated. All other changes are rejected pub async fn merge_validate_patch( diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index e4ef20cbcc..5bf0c69f00 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -102,6 +102,8 @@ service ManagementService { // Apply a JSON blob to the settings // See ../../docs/settings-patch-format.md for a description of the format rpc ApplyJsonSettings(google.protobuf.StringValue) returns (google.protobuf.Empty) {} + // Return a JSON blob containing all overridable settings, if there are any + rpc ExportJsonSettings(google.protobuf.Empty) returns (google.protobuf.StringValue) {} } message UUID { string value = 1; } diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 2c0ca2fecf..7ff26e0371 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -678,6 +678,11 @@ impl MullvadProxyClient { self.0.apply_json_settings(blob).await.map_err(Error::Rpc)?; Ok(()) } + + pub async fn export_json_settings(&mut self) -> Result<String> { + let blob = self.0.export_json_settings(()).await.map_err(Error::Rpc)?; + Ok(blob.into_inner()) + } } fn map_device_error(status: Status) -> Error { |
