summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2023-11-14 12:01:31 +0100
committerDavid Lönnhager <david.l@mullvad.net>2023-11-17 11:09:25 +0100
commit1dd07884754b1f07ce9ffd3f83987993458e072a (patch)
tree01c7e75959eca62a7da3e39b3e1cb8c2f3fe4361
parentf6ab6637456de4f30cd59b33de0e3e455ea3fb5a (diff)
downloadmullvadvpn-1dd07884754b1f07ce9ffd3f83987993458e072a.tar.xz
mullvadvpn-1dd07884754b1f07ce9ffd3f83987993458e072a.zip
Add support for applying JSON patches to management interface
-rw-r--r--mullvad-daemon/src/lib.rs15
-rw-r--r--mullvad-daemon/src/management_interface.rs128
-rw-r--r--mullvad-daemon/src/settings/mod.rs17
-rw-r--r--mullvad-daemon/src/settings/patch.rs18
-rw-r--r--mullvad-management-interface/proto/management_interface.proto3
-rw-r--r--mullvad-management-interface/src/client.rs5
6 files changed, 102 insertions, 84 deletions
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index d4964a8b80..890cdfb13e 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -349,6 +349,8 @@ 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
+ ApplyJsonSettings(ResponseTx<(), settings::patch::Error>, String),
}
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -1171,6 +1173,7 @@ where
VerifyPlayPurchase(tx, play_purchase) => {
self.on_verify_play_purchase(tx, play_purchase)
}
+ ApplyJsonSettings(tx, blob) => self.on_apply_json_settings(tx, blob).await,
}
}
@@ -2439,6 +2442,18 @@ where
});
}
+ async fn on_apply_json_settings(
+ &mut self,
+ tx: ResponseTx<(), settings::patch::Error>,
+ blob: String,
+ ) {
+ let result = settings::patch::merge_validate_patch(&mut self.settings, &blob).await;
+ if result.is_ok() {
+ self.reconnect_tunnel();
+ }
+ Self::oneshot_send(tx, result, "apply_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 e67a02117c..f042a923e5 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -1,4 +1,4 @@
-use crate::{account_history, device, settings, DaemonCommand, DaemonCommandSender, EventListener};
+use crate::{account_history, device, DaemonCommand, DaemonCommandSender, EventListener};
use futures::{
channel::{mpsc, oneshot},
StreamExt,
@@ -177,10 +177,8 @@ impl ManagementService for ManagementServiceImpl {
let message = DaemonCommand::SetRelaySettings(tx, constraints_update);
self.send_command_to_daemon(message)?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn get_relay_locations(&self, _: Request<()>) -> ServiceResult<types::RelayList> {
@@ -215,10 +213,8 @@ impl ManagementService for ManagementServiceImpl {
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, settings))?;
- let settings_result = self.wait_for_result(rx).await?;
- settings_result
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_obfuscation_settings(
@@ -230,10 +226,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_obfuscation_settings({:?})", settings);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetObfuscationSettings(tx, settings))?;
- let settings_result = self.wait_for_result(rx).await?;
- settings_result
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_bridge_state(&self, request: Request<types::BridgeState>) -> ServiceResult<()> {
@@ -243,10 +237,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_bridge_state({:?})", bridge_state);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetBridgeState(tx, bridge_state))?;
- let settings_result = self.wait_for_result(rx).await?;
- settings_result
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
// Settings
@@ -266,10 +258,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_allow_lan({})", allow_lan);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetAllowLan(tx, allow_lan))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_show_beta_releases(&self, request: Request<bool>) -> ServiceResult<()> {
@@ -277,10 +267,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_show_beta_releases({})", enabled);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetShowBetaReleases(tx, enabled))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_block_when_disconnected(&self, request: Request<bool>) -> ServiceResult<()> {
@@ -291,10 +279,8 @@ impl ManagementService for ManagementServiceImpl {
tx,
block_when_disconnected,
))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_auto_connect(&self, request: Request<bool>) -> ServiceResult<()> {
@@ -302,10 +288,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_auto_connect({})", auto_connect);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetAutoConnect(tx, auto_connect))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_openvpn_mssfix(&self, request: Request<u32>) -> ServiceResult<()> {
@@ -318,10 +302,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_openvpn_mssfix({:?})", mssfix);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetOpenVpnMssfix(tx, mssfix))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_wireguard_mtu(&self, request: Request<u32>) -> ServiceResult<()> {
@@ -330,10 +312,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_wireguard_mtu({:?})", mtu);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetWireguardMtu(tx, mtu))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_enable_ipv6(&self, request: Request<bool>) -> ServiceResult<()> {
@@ -341,10 +321,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_enable_ipv6({})", enable_ipv6);
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetEnableIpv6(tx, enable_ipv6))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn set_quantum_resistant_tunnel(
@@ -357,10 +335,8 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_quantum_resistant_tunnel({state:?})");
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetQuantumResistantTunnel(tx, state))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
#[cfg(not(target_os = "android"))]
@@ -370,10 +346,8 @@ impl ManagementService for ManagementServiceImpl {
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetDnsOptions(tx, options))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
#[cfg(target_os = "android")]
@@ -390,20 +364,16 @@ impl ManagementService for ManagementServiceImpl {
log::debug!("set_relay_override");
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetRelayOverride(tx, relay_override))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn clear_all_relay_overrides(&self, _: Request<()>) -> ServiceResult<()> {
log::debug!("clear_all_relay_overrides");
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::ClearAllRelayOverrides(tx))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
// Account management
@@ -571,20 +541,16 @@ impl ManagementService for ManagementServiceImpl {
tx,
Some(interval),
))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn reset_wireguard_rotation_interval(&self, _: Request<()>) -> ServiceResult<()> {
log::debug!("reset_wireguard_rotation_interval");
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetWireguardRotationInterval(tx, None))?;
- self.wait_for_result(rx)
- .await?
- .map(Response::new)
- .map_err(map_settings_error)
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
}
async fn rotate_wireguard_key(&self, _: Request<()>) -> ServiceResult<()> {
@@ -929,6 +895,14 @@ impl ManagementService for ManagementServiceImpl {
async fn check_volumes(&self, _: Request<()>) -> ServiceResult<()> {
Ok(Response::new(()))
}
+
+ async fn apply_json_settings(&self, blob: Request<String>) -> ServiceResult<()> {
+ log::debug!("apply_json_settings");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::ApplyJsonSettings(tx, blob.into_inner()))?;
+ self.wait_for_result(rx).await??;
+ Ok(Response::new(()))
+ }
}
impl ManagementServiceImpl {
@@ -1061,7 +1035,7 @@ fn map_daemon_error(error: crate::Error) -> Status {
match error {
DaemonError::RestError(error) => map_rest_error(&error),
- DaemonError::SettingsError(error) => map_settings_error(error),
+ DaemonError::SettingsError(error) => Status::from(error),
DaemonError::AlreadyLoggedIn => Status::already_exists(error.to_string()),
DaemonError::LoginError(error) => map_device_error(&error),
DaemonError::LogoutError(error) => map_device_error(&error),
@@ -1121,20 +1095,6 @@ fn map_rest_error(error: &RestError) -> Status {
}
}
-/// Converts an instance of [`mullvad_daemon::settings::Error`] into a tonic status.
-fn map_settings_error(error: settings::Error) -> Status {
- match error {
- settings::Error::DeleteError(..)
- | settings::Error::WriteError(..)
- | settings::Error::ReadError(..) => {
- Status::new(Code::FailedPrecondition, error.to_string())
- }
- settings::Error::SerializeError(..) | settings::Error::ParseError(..) => {
- Status::new(Code::Internal, error.to_string())
- }
- }
-}
-
/// Converts an instance of [`mullvad_daemon::device::Error`] into a tonic status.
fn map_device_error(error: &device::Error) -> Status {
match error {
diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs
index 671ed87346..99f637c023 100644
--- a/mullvad-daemon/src/settings/mod.rs
+++ b/mullvad-daemon/src/settings/mod.rs
@@ -40,6 +40,23 @@ pub enum Error {
WriteError(String, #[error(source)] io::Error),
}
+/// Converts an [Error] to a management interface status
+#[cfg(not(target_os = "android"))]
+impl From<Error> for mullvad_management_interface::Status {
+ fn from(error: Error) -> mullvad_management_interface::Status {
+ use mullvad_management_interface::{Code, Status};
+
+ match error {
+ Error::DeleteError(..) | Error::WriteError(..) | Error::ReadError(..) => {
+ Status::new(Code::FailedPrecondition, error.to_string())
+ }
+ Error::SerializeError(..) | Error::ParseError(..) => {
+ Status::new(Code::Internal, error.to_string())
+ }
+ }
+ }
+}
+
pub struct SettingsPersister {
settings: Settings,
path: PathBuf,
diff --git a/mullvad-daemon/src/settings/patch.rs b/mullvad-daemon/src/settings/patch.rs
index 63152b740d..f006b21056 100644
--- a/mullvad-daemon/src/settings/patch.rs
+++ b/mullvad-daemon/src/settings/patch.rs
@@ -41,6 +41,24 @@ pub enum Error {
Settings(#[error(source)] super::Error),
}
+/// Converts an [Error] to a management interface status
+#[cfg(not(target_os = "android"))]
+impl From<Error> for mullvad_management_interface::Status {
+ fn from(error: Error) -> mullvad_management_interface::Status {
+ use mullvad_management_interface::Status;
+
+ match error {
+ Error::InvalidOrMissingValue(_)
+ | Error::UnknownOrProhibitedKey(_)
+ | Error::ParsePatch(_)
+ | Error::DeserializePatched(_)
+ | Error::RecursionLimit => Status::invalid_argument(error.to_string()),
+ Error::Settings(error) => Status::from(error),
+ Error::SerializeSettings(error) => Status::internal(error.to_string()),
+ }
+ }
+}
+
enum MergeStrategy {
/// Replace or append keys to objects, and replace everything else
Replace,
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 64d87d37ef..a27698f317 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -99,6 +99,9 @@ service ManagementService {
// Notify the split tunnel monitor that a volume was mounted or dismounted
// (Windows).
rpc CheckVolumes(google.protobuf.Empty) returns (google.protobuf.Empty) {}
+
+ // Apply a JSON blob to the settings
+ rpc ApplyJsonSettings(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
}
message UUID { string value = 1; }
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 140eddc08a..c1b27fea65 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -677,6 +677,11 @@ impl MullvadProxyClient {
}
// check_volumes
+
+ pub async fn apply_json_settings(&mut self, blob: String) -> Result<()> {
+ self.0.apply_json_settings(blob).await.map_err(Error::Rpc)?;
+ Ok(())
+ }
}
fn map_device_error(status: Status) -> Error {