summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-01-04 17:33:21 +0100
committerDavid Lönnhager <david.l@mullvad.net>2024-01-10 10:42:49 +0100
commite162d0b94c6e013e964ef9dff4bde80ccae4d58f (patch)
tree820758d4b8dd1a1d1209e75b6fe08999b041f6e3
parentedbd1f52c44ba6ee9a290146f714453fc87e689d (diff)
downloadmullvadvpn-e162d0b94c6e013e964ef9dff4bde80ccae4d58f.tar.xz
mullvadvpn-e162d0b94c6e013e964ef9dff4bde80ccae4d58f.zip
Add patch export to the management interface
-rw-r--r--mullvad-cli/src/cmds/mod.rs2
-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.rs2
-rw-r--r--mullvad-daemon/src/lib.rs10
-rw-r--r--mullvad-daemon/src/management_interface.rs8
-rw-r--r--mullvad-daemon/src/settings/patch.rs41
-rw-r--r--mullvad-management-interface/proto/management_interface.proto2
-rw-r--r--mullvad-management-interface/src/client.rs5
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 {