summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-06-18 11:35:41 +0200
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-06-20 13:04:40 +0200
commit751026be2029636e491b6c8d22b979a33fa7cec1 (patch)
tree19cccc1f2add8354813da929f63eaa46444240f4
parent397d094c516802e39a34282bf58a1121b85bcace (diff)
downloadmullvadvpn-751026be2029636e491b6c8d22b979a33fa7cec1.tar.xz
mullvadvpn-751026be2029636e491b6c8d22b979a33fa7cec1.zip
Add `PrepareRestartv2` gRPC call
- Add option to automatically shutdown daemon on after running through the same safety routine as `PrepareRestart`. This is exposed via a new gRPC call called `PrepareRestartV2`. - Add help text for enabling full disk access to the CLI
-rw-r--r--gui/src/main/daemon-rpc.ts4
-rw-r--r--gui/src/main/index.ts6
-rw-r--r--gui/src/main/tunnel-state.ts13
-rw-r--r--mullvad-cli/src/format.rs15
-rw-r--r--mullvad-daemon/src/lib.rs17
-rw-r--r--mullvad-daemon/src/management_interface.rs13
-rw-r--r--mullvad-management-interface/proto/management_interface.proto4
-rw-r--r--mullvad-management-interface/src/client.rs14
8 files changed, 77 insertions, 9 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index e8f8d96e18..3461a85ac5 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -568,6 +568,10 @@ export class DaemonRpc {
await this.callEmpty(this.client.updateDevice);
}
+ public async prepareRestart(quit: boolean) {
+ await this.callBool(this.client.prepareRestartV2, quit);
+ }
+
public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise<void> {
const grpcDaitaSettings = new grpcTypes.DaitaSettings();
grpcDaitaSettings.setEnabled(daitaSettings.enabled);
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index ded7ea6ede..b96bc07f12 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -321,7 +321,11 @@ class ApplicationMain
}
};
- private onBeforeQuit = (event: Electron.Event) => {
+ private onBeforeQuit = async (event: Electron.Event) => {
+ if (this.tunnelState.hasReceivedFullDiskAccessError) {
+ await this.daemonRpc.prepareRestart(true);
+ }
+
log.info('before-quit received');
if (this.quitInitiated) {
event.preventDefault();
diff --git a/gui/src/main/tunnel-state.ts b/gui/src/main/tunnel-state.ts
index 6829118818..297a7e481e 100644
--- a/gui/src/main/tunnel-state.ts
+++ b/gui/src/main/tunnel-state.ts
@@ -1,5 +1,5 @@
import { connectEnabled, disconnectEnabled, reconnectEnabled } from '../shared/connect-helper';
-import { ILocation, TunnelState } from '../shared/daemon-rpc-types';
+import { ErrorStateCause, ILocation, TunnelState } from '../shared/daemon-rpc-types';
import { Scheduler } from '../shared/scheduler';
export interface TunnelStateProvider {
@@ -20,10 +20,15 @@ export default class TunnelStateHandler {
// Scheduler for discarding the assumed next state.
private tunnelStateFallbackScheduler = new Scheduler();
+ private receivedFullDiskAccessError = false;
+
private lastKnownDisconnectedLocation: Partial<ILocation> | undefined;
public constructor(private delegate: TunnelStateHandlerDelegate) {}
+ public get hasReceivedFullDiskAccessError() {
+ return this.receivedFullDiskAccessError;
+ }
public get tunnelState() {
return this.tunnelStateValue;
}
@@ -53,6 +58,12 @@ export default class TunnelStateHandler {
}
public handleNewTunnelState(newState: TunnelState) {
+ if (newState.state === 'error' && newState.details) {
+ if (newState.details.cause === ErrorStateCause.needFullDiskPermissions) {
+ this.receivedFullDiskAccessError = true;
+ }
+ }
+
// If there's a fallback state set then the app is in an assumed next state and need to check
// if it's now reached or if the current state should be ignored and set as the fallback state.
if (this.tunnelStateFallback) {
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs
index 0d6ea0b0c0..6b092e8939 100644
--- a/mullvad-cli/src/format.rs
+++ b/mullvad-cli/src/format.rs
@@ -225,6 +225,21 @@ fn print_error_state(error_state: &ErrorState) {
println!("Blocked: {cause}");
println!("Your kernel might be terribly out of date or missing nftables");
}
+ #[cfg(target_os = "macos")]
+ cause @ talpid_types::tunnel::ErrorStateCause::NeedFullDiskPermissions => {
+ println!("Blocked: {cause}");
+ println!();
+ println!(
+ r#"Enable "Full Disk Access" for "Mullvad VPN" in the macOS system settings:"#
+ );
+ println!(
+ r#"open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles""#
+ );
+ println!();
+ println!("Restart the Mullvad daemon for the change to take effect:");
+ println!("launchctl unload -w /Library/LaunchDaemons/net.mullvad.daemon.plist");
+ println!("launchctl load -w /Library/LaunchDaemons/net.mullvad.daemon.plist");
+ }
talpid_types::tunnel::ErrorStateCause::AuthFailed(Some(auth_failed)) => {
println!(
"Blocked: Authentication with remote server failed: {}",
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 3fadfcd814..3affb7915d 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -76,9 +76,9 @@ use std::{
sync::{Arc, Weak},
time::Duration,
};
-use talpid_core::split_tunnel;
use talpid_core::{
mpsc::Sender,
+ split_tunnel,
tunnel_state_machine::{self, TunnelCommand, TunnelStateMachineHandle},
};
#[cfg(target_os = "android")]
@@ -350,7 +350,7 @@ pub enum DaemonCommand {
SetObfuscationSettings(ResponseTx<(), settings::Error>, ObfuscationSettings),
/// Saves the target tunnel state and enters a blocking state. The state is restored
/// upon restart.
- PrepareRestart,
+ PrepareRestart(bool),
/// Causes a socket to bypass the tunnel. This has no effect when connected. It is only used
/// to bypass the tunnel in blocking states.
#[cfg(target_os = "android")]
@@ -1281,7 +1281,7 @@ where
SetObfuscationSettings(tx, settings) => {
self.on_set_obfuscation_settings(tx, settings).await
}
- PrepareRestart => self.on_prepare_restart(),
+ PrepareRestart(shutdown) => self.on_prepare_restart(shutdown),
#[cfg(target_os = "android")]
BypassSocket(fd, tx) => self.on_bypass_socket(fd, tx),
#[cfg(target_os = "android")]
@@ -2688,7 +2688,11 @@ where
self.disconnect_tunnel();
}
- fn on_prepare_restart(&mut self) {
+ /// Prepare the daemon for a restart by setting the target state to [`TargetState::Secured`].
+ ///
+ /// - `shutdown`: If the daemon should shut down itself when after setting the secured target
+ /// state. set to `false` if the intention is to close the daemon process manually.
+ fn on_prepare_restart(&mut self, shutdown: bool) {
// TODO: See if this can be made to also shut down the daemon
// without causing the service to be restarted.
@@ -2697,6 +2701,11 @@ where
self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected(true, tx));
}
self.target_state.lock();
+
+ if shutdown {
+ self.state.shutdown(&self.tunnel_state);
+ self.disconnect_tunnel();
+ }
}
#[cfg(target_os = "android")]
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 269432be30..b5f29a386e 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -8,14 +8,13 @@ use mullvad_management_interface::{
types::{self, daemon_event, management_service_server::ManagementService},
Code, Request, Response, Status,
};
-use mullvad_types::settings::DnsOptions;
use mullvad_types::{
account::AccountToken,
relay_constraints::{
BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
},
relay_list::RelayList,
- settings::Settings,
+ settings::{DnsOptions, Settings},
states::{TargetState, TunnelState},
version,
wireguard::{RotationInterval, RotationIntervalError},
@@ -104,7 +103,15 @@ impl ManagementService for ManagementServiceImpl {
async fn prepare_restart(&self, _: Request<()>) -> ServiceResult<()> {
log::debug!("prepare_restart");
- self.send_command_to_daemon(DaemonCommand::PrepareRestart)?;
+ // Note: The old `PrepareRestart` behavior never shutdown the daemon.
+ let shutdown = false;
+ self.send_command_to_daemon(DaemonCommand::PrepareRestart(shutdown))?;
+ Ok(Response::new(()))
+ }
+
+ async fn prepare_restart_v2(&self, shutdown: Request<bool>) -> ServiceResult<()> {
+ log::debug!("prepare_restart_v2");
+ self.send_command_to_daemon(DaemonCommand::PrepareRestart(shutdown.into_inner()))?;
Ok(Response::new(()))
}
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 3588c69612..aa279070f0 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -17,7 +17,11 @@ service ManagementService {
// Control the daemon and receive events
rpc EventsListen(google.protobuf.Empty) returns (stream DaemonEvent) {}
+ // DEPRECATED: Prefer PrepareRestartV2.
rpc PrepareRestart(google.protobuf.Empty) returns (google.protobuf.Empty) {}
+ // Takes a a boolean argument which says whether the daemon should stop after
+ // it is done preparing for a restart.
+ rpc PrepareRestartV2(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc FactoryReset(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 6a3b794b39..3d217ace32 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -149,11 +149,25 @@ impl MullvadProxyClient {
}))
}
+ /// DEPRECATED: Prefer to use `prepare_restart_v2`.
pub async fn prepare_restart(&mut self) -> Result<()> {
self.0.prepare_restart(()).await.map_err(Error::Rpc)?;
Ok(())
}
+ /// Tell the daemon to get ready for a restart by securing a user, i.e. putting firewall rules
+ /// in place.
+ ///
+ /// - `shutdown`: Whether the daemon should shutdown immediately after its prepare-for-restart
+ /// routine.
+ pub async fn prepare_restart_v2(&mut self, shutdown: bool) -> Result<()> {
+ self.0
+ .prepare_restart_v2(shutdown)
+ .await
+ .map_err(Error::Rpc)?;
+ Ok(())
+ }
+
pub async fn factory_reset(&mut self) -> Result<()> {
self.0.factory_reset(()).await.map_err(Error::Rpc)?;
Ok(())