summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-03-16 13:30:26 +0100
committerDavid Lönnhager <david.l@mullvad.net>2020-03-16 13:30:26 +0100
commite4985cf5e337c6be16f3af550fa5678121800f8c (patch)
tree3f311e7ae7c181b417ca6be5cca418c9f362f738
parentf57b5a3c172355b8677fdd66a6e452fa1e730ced (diff)
parent329e52f9cceb64ec2591383b56ec7dcfba11cdac (diff)
downloadmullvadvpn-e4985cf5e337c6be16f3af550fa5678121800f8c.tar.xz
mullvadvpn-e4985cf5e337c6be16f3af550fa5678121800f8c.zip
Merge branch 'block-on-upgrade'
-rw-r--r--CHANGELOG.md2
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml1
-rwxr-xr-xbuild.sh1
-rw-r--r--dist-assets/windows/installer.nsh99
-rw-r--r--mullvad-daemon/src/lib.rs83
-rw-r--r--mullvad-daemon/src/management_interface.rs10
-rw-r--r--mullvad-ipc-client/src/lib.rs4
-rw-r--r--mullvad-setup/Cargo.toml33
-rw-r--r--mullvad-setup/build.rs18
-rw-r--r--mullvad-setup/src/main.rs80
-rwxr-xr-xversion_metadata.sh3
12 files changed, 307 insertions, 42 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90f82e056c..1cbb188f40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -63,6 +63,8 @@ Line wrap the file at 100 chars. Th
#### Windows
- Fix issue in daemon where the `block_when_disconnected` setting was sometimes not honored when
stopping the daemon. I.e. traffic could flow freely after the daemon was stopped.
+- When upgrading or reinstalling while connected, exit the daemon in a blocking state to prevent
+ unintended leaks. This only affects upgrades from this release.
## [2020.3] - 2020-02-20
diff --git a/Cargo.lock b/Cargo.lock
index 0a43069b74..22ed06c8d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1386,6 +1386,21 @@ dependencies = [
]
[[package]]
+name = "mullvad-setup"
+version = "2020.3.0"
+dependencies = [
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "err-derive 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mullvad-ipc-client 0.1.0",
+ "mullvad-paths 0.1.0",
+ "talpid-core 0.1.0",
+ "talpid-types 0.1.0",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "mullvad-tests"
version = "0.1.0"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index c25bc54279..bbaaee503a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
"mullvad-daemon",
"mullvad-cli",
+ "mullvad-setup",
"mullvad-problem-report",
"mullvad-ipc-client",
"mullvad-jni",
diff --git a/build.sh b/build.sh
index 1de226460f..83073f29fd 100755
--- a/build.sh
+++ b/build.sh
@@ -160,6 +160,7 @@ elif [[ ("$(uname -s)" == "MINGW"*) ]]; then
mullvad.exe
mullvad-problem-report.exe
talpid_openvpn_plugin.dll
+ mullvad-setup.exe
)
fi
for binary in ${binaries[*]}; do
diff --git a/dist-assets/windows/installer.nsh b/dist-assets/windows/installer.nsh
index c7adccc49e..0e5d1e6646 100644
--- a/dist-assets/windows/installer.nsh
+++ b/dist-assets/windows/installer.nsh
@@ -40,22 +40,7 @@
# electron-builder uses a GUID here rather than the application name.
!define INSTALL_REGISTRY_KEY "Software\${PRODUCT_NAME}"
-#
-# BreakInstallation
-#
-# Aborting the customization step does not undo previous steps taken
-# by the installer (copy files, create shortcut, etc)
-#
-# Therefore we have to break the installed application to
-# prevent users from running a half-installed product
-#
-!macro BreakInstallation
-
- Delete "$INSTDIR\mullvad vpn.exe"
-
-!macroend
-
-!define BreakInstallation '!insertmacro "BreakInstallation"'
+!define BLOCK_OUTBOUND_IPV4_FILTER_GUID "{a81c5411-0fd0-43a9-a9be-313f299de64f}"
#
# ExtractTapDriver
@@ -649,8 +634,7 @@
${If} $R0 != 0
MessageBox MB_OK "Fatal error during driver installation: $R0"
- ${BreakInstallation}
- Abort
+ Goto customInstall_abort_installation
${EndIf}
${ExtractWintun}
@@ -658,21 +642,54 @@
${If} $R0 != 0
MessageBox MB_OK "$R0"
- ${BreakInstallation}
- Abort
+ Goto customInstall_abort_installation
${EndIf}
${InstallService}
${If} $R0 != 0
MessageBox MB_OK "$R0"
- ${BreakInstallation}
- Abort
+ Goto customInstall_abort_installation
${EndIf}
${AddCLIToEnvironPath}
${InstallTrayIcon}
+ Goto customInstall_skip_abort
+
+ customInstall_abort_installation:
+
+ # Aborting the customization step does not undo previous steps taken
+ # by the installer (copy files, create shortcut, etc)
+ #
+ # Therefore we have to break the installed application to
+ # prevent users from running a half-installed product
+ #
+ Delete "$INSTDIR\mullvad vpn.exe"
+
+ nsExec::ExecToStack '"$SYSDIR\netsh.exe" wfp show security FILTER ${BLOCK_OUTBOUND_IPV4_FILTER_GUID}'
+ Pop $0
+ Pop $1
+
+ ${If} $0 == 0
+ MessageBox MB_ICONEXCLAMATION|MB_YESNO "Do you wish to unblock your internet access? Doing so will leave you with an unsecure connection." IDNO customInstall_abortInstallation_skip_firewall_revert
+
+ SetOutPath "$TEMP"
+ File "${BUILD_RESOURCES_DIR}\mullvad-setup.exe"
+ File "${BUILD_RESOURCES_DIR}\..\windows\winfw\bin\x64-Release\winfw.dll"
+ nsExec::ExecToStack '"$TEMP\mullvad-setup.exe" reset-firewall'
+ Pop $0
+ Pop $1
+
+ log::Log "Resetting firewall: $0 $1"
+ ${EndIf}
+
+ customInstall_abortInstallation_skip_firewall_revert:
+
+ Abort
+
+ customInstall_skip_abort:
+
Pop $R0
!macroend
@@ -694,20 +711,6 @@
Push $0
Push $1
- nsExec::ExecToStack '"$SYSDIR\sc.exe" stop mullvadvpn'
-
- # Discard return value
- Pop $0
-
- Sleep 5000
-
- nsExec::ExecToStack '"$SYSDIR\sc.exe" delete mullvadvpn'
-
- # Discard return value
- Pop $0
-
- Sleep 1000
-
# Check command line arguments
Var /GLOBAL FullUninstall
@@ -722,6 +725,30 @@
${EndIf}
Pop $FullUninstall
+ ${If} $FullUninstall != 1
+ # Save the target tunnel state if we're upgrading
+ SetOutPath "$TEMP"
+ File "${BUILD_RESOURCES_DIR}\mullvad-setup.exe"
+ File "${BUILD_RESOURCES_DIR}\..\windows\winfw\bin\x64-Release\winfw.dll"
+ nsExec::ExecToStack '"$TEMP\mullvad-setup.exe" prepare-restart'
+ Pop $0
+ Pop $1
+ ${EndIf}
+
+ nsExec::ExecToStack '"$SYSDIR\sc.exe" stop mullvadvpn'
+
+ # Discard return value
+ Pop $0
+
+ Sleep 5000
+
+ nsExec::ExecToStack '"$SYSDIR\sc.exe" delete mullvadvpn'
+
+ # Discard return value
+ Pop $0
+
+ Sleep 1000
+
log::Log "Running uninstaller for ${PRODUCT_NAME} ${VERSION}"
${RemoveCLIFromEnvironPath}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 74383b43a4..bca668bc28 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -45,6 +45,7 @@ use settings::Settings;
#[cfg(not(target_os = "android"))]
use std::path::Path;
use std::{
+ fs::{self, File},
io,
marker::PhantomData,
mem,
@@ -68,6 +69,8 @@ use talpid_types::{
#[path = "wireguard.rs"]
mod wireguard;
+const TARGET_START_STATE_FILE: &str = "target-start-state.json";
+
/// FIXME(linus): This is here just because the futures crate has deprecated it and jsonrpc_core
/// did not introduce their own yet (https://github.com/paritytech/jsonrpc/pull/196).
/// Remove this and use the one in jsonrpc_core when that is released.
@@ -125,6 +128,12 @@ pub enum Error {
#[cfg(target_os = "windows")]
#[error(display = "Failed to read dir entries")]
ReadDirError(#[error(source)] io::Error),
+
+ #[error(display = "Failed to read cached target tunnel state")]
+ ReadCachedTargetState(#[error(source)] serde_json::Error),
+
+ #[error(display = "Failed to open cached target tunnel state")]
+ OpenCachedTargetState(#[error(source)] io::Error),
}
/// Enum representing commands that can be sent to the daemon.
@@ -206,6 +215,9 @@ pub enum DaemonCommand {
FactoryReset(oneshot::Sender<()>),
/// Makes the daemon exit the main loop and quit.
Shutdown,
+ /// Saves the target tunnel state and enters a blocking state. The state is restored
+ /// upon restart.
+ PrepareRestart,
}
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -438,6 +450,7 @@ pub struct Daemon<L: EventListener> {
shutdown_callbacks: Vec<Box<dyn FnOnce()>>,
/// oneshot channel that completes once the tunnel state machine has been shut down
tunnel_state_machine_shutdown_signal: oneshot::Receiver<()>,
+ cache_dir: PathBuf,
}
impl<L> Daemon<L>
@@ -506,6 +519,26 @@ where
)
.map_err(Error::LoadAccountHistory)?;
+ // Restore the tunnel to a previous state
+ let target_cache = cache_dir.join(TARGET_START_STATE_FILE);
+ let cached_target_state: Option<TargetState> = match File::open(&target_cache) {
+ Ok(handle) => serde_json::from_reader(io::BufReader::new(handle))
+ .map(Some)
+ .map_err(Error::ReadCachedTargetState),
+ Err(e) => {
+ if e.kind() == io::ErrorKind::NotFound {
+ Ok(None)
+ } else {
+ Err(Error::OpenCachedTargetState(e))
+ }
+ }
+ }?;
+ if cached_target_state.is_some() {
+ let _ = fs::remove_file(target_cache).map_err(|e| {
+ error!("Cannot delete target tunnel state cache: {}", e);
+ });
+ }
+
let tunnel_parameters_generator = MullvadTunnelParametersGenerator {
tx: internal_event_tx.clone(),
};
@@ -515,7 +548,7 @@ where
tunnel_parameters_generator,
log_dir,
resource_dir,
- cache_dir,
+ cache_dir.clone(),
internal_event_tx.to_specialized_sender(),
tunnel_state_machine_shutdown_tx,
#[cfg(target_os = "android")]
@@ -532,10 +565,23 @@ where
// Attempt to download a fresh relay list
relay_selector.update();
+ let initial_target_state = if settings.get_account_token().is_some() {
+ if settings.get_auto_connect() {
+ // Note: Auto-connect overrides the cached target state
+ info!("Automatically connecting since auto-connect is turned on");
+ TargetState::Secured
+ } else {
+ info!("Restoring cached target state");
+ cached_target_state.unwrap_or(TargetState::Unsecured)
+ }
+ } else {
+ TargetState::Unsecured
+ };
+
let mut daemon = Daemon {
tunnel_command_tx,
tunnel_state: TunnelState::Disconnected,
- target_state: TargetState::Unsecured,
+ target_state: initial_target_state,
state: DaemonExecutionState::Running,
rx: internal_event_rx.wait(),
tx: internal_event_tx,
@@ -554,6 +600,7 @@ where
app_version_info,
shutdown_callbacks: vec![],
tunnel_state_machine_shutdown_signal,
+ cache_dir,
};
daemon.ensure_wireguard_keys_for_current_account();
@@ -578,9 +625,8 @@ where
/// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a
/// shutdown event is received.
pub fn run(mut self) -> Result<(), Error> {
- if self.settings.get_auto_connect() && self.settings.get_account_token().is_some() {
- info!("Automatically connecting since auto-connect is turned on");
- self.set_target_state(TargetState::Secured);
+ if self.target_state == TargetState::Secured {
+ self.connect_tunnel();
}
while let Some(Ok(event)) = self.rx.next() {
self.handle_event(event);
@@ -939,6 +985,7 @@ where
#[cfg(not(target_os = "android"))]
FactoryReset(tx) => self.on_factory_reset(tx),
Shutdown => self.trigger_shutdown_event(),
+ PrepareRestart => self.on_prepare_restart(),
}
}
@@ -1658,6 +1705,31 @@ where
self.disconnect_tunnel();
}
+ fn on_prepare_restart(&mut self) {
+ // TODO: See if this can be made to also shut down the daemon
+ // without causing the service to be restarted.
+
+ // Cache the current target state
+ let cache_file = self.cache_dir.join(TARGET_START_STATE_FILE);
+ log::debug!("Saving tunnel target state to {}", cache_file.display());
+ match File::create(&cache_file) {
+ Ok(handle) => {
+ if let Err(e) =
+ serde_json::to_writer(io::BufWriter::new(handle), &self.target_state)
+ {
+ log::error!("Failed to serialize target start state: {}", e);
+ }
+ }
+ Err(e) => {
+ log::error!("Failed to save target start state: {}", e);
+ }
+ }
+
+ if self.target_state == TargetState::Secured {
+ self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected(true));
+ }
+ }
+
/// Set the target state of the client. If it changed trigger the operations needed to
/// progress towards that state.
/// Returns an error if trying to set secured state, but no account token is present.
@@ -1721,7 +1793,6 @@ where
#[cfg(not(target_os = "android"))]
fn clear_directory(path: &Path) -> Result<(), Error> {
- use std::fs;
#[cfg(not(target_os = "windows"))]
{
fs::remove_dir_all(path)
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index cf5d403e47..e0f7a94b4e 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -108,6 +108,11 @@ build_rpc_trait! {
#[rpc(meta, name = "shutdown")]
fn shutdown(&self, Self::Metadata) -> BoxFuture<(), Error>;
+ /// Saves the target tunnel state and enters a blocking state. The state is restored
+ /// upon restart.
+ #[rpc(meta, name = "prepare_restart")]
+ fn prepare_restart(&self, Self::Metadata) -> BoxFuture<(), Error>;
+
/// Get previously used account tokens from the account history
#[rpc(meta, name = "get_account_history")]
fn get_account_history(&self, Self::Metadata) -> BoxFuture<Vec<AccountToken>, Error>;
@@ -530,6 +535,11 @@ impl ManagementInterfaceApi for ManagementInterface {
Box::new(self.send_command_to_daemon(DaemonCommand::Shutdown))
}
+ fn prepare_restart(&self, _: Self::Metadata) -> BoxFuture<(), Error> {
+ log::debug!("prepare_restart");
+ Box::new(self.send_command_to_daemon(DaemonCommand::PrepareRestart))
+ }
+
fn get_account_history(&self, _: Self::Metadata) -> BoxFuture<Vec<AccountToken>, Error> {
log::debug!("get_account_history");
let (tx, rx) = sync::oneshot::channel();
diff --git a/mullvad-ipc-client/src/lib.rs b/mullvad-ipc-client/src/lib.rs
index 631dbd054d..511f68ae3e 100644
--- a/mullvad-ipc-client/src/lib.rs
+++ b/mullvad-ipc-client/src/lib.rs
@@ -223,6 +223,10 @@ impl DaemonRpcClient {
self.call("shutdown", &NO_ARGS)
}
+ pub fn prepare_restart(&mut self) -> Result<()> {
+ self.call("prepare_restart", &NO_ARGS)
+ }
+
pub fn factory_reset(&mut self) -> Result<()> {
self.call("factory_reset", &NO_ARGS)
}
diff --git a/mullvad-setup/Cargo.toml b/mullvad-setup/Cargo.toml
new file mode 100644
index 0000000000..b467a7b174
--- /dev/null
+++ b/mullvad-setup/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "mullvad-setup"
+version = "2020.3.0"
+authors = ["Mullvad VPN"]
+description = "Tool used to manage daemon setup"
+license = "GPL-3.0"
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "mullvad-setup"
+path = "src/main.rs"
+
+[dependencies]
+clap = "2.32"
+env_logger = "0.7"
+err-derive = "0.2.1"
+
+mullvad-ipc-client = { path = "../mullvad-ipc-client" }
+mullvad-paths = { path = "../mullvad-paths" }
+talpid-core = { path = "../talpid-core" }
+talpid-types = { path = "../talpid-types" }
+
+[target.'cfg(windows)'.build-dependencies]
+winres = "0.1"
+winapi = "0.3"
+
+[package.metadata.winres]
+ProductName = "Mullvad VPN"
+CompanyName = "Mullvad VPN AB"
+LegalCopyright = "(c) 2020 Mullvad VPN AB"
+InternalName = "mullvad-setup"
+OriginalFilename = "mullvad-setup.exe"
diff --git a/mullvad-setup/build.rs b/mullvad-setup/build.rs
new file mode 100644
index 0000000000..8cdd992c7c
--- /dev/null
+++ b/mullvad-setup/build.rs
@@ -0,0 +1,18 @@
+use std::{env, fs, path::PathBuf};
+
+fn main() {
+ let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let product_version = env!("CARGO_PKG_VERSION").replacen(".0", "", 1);
+ fs::write(out_dir.join("product-version.txt"), &product_version).unwrap();
+
+ #[cfg(windows)]
+ {
+ let mut res = winres::WindowsResource::new();
+ res.set("ProductVersion", &product_version);
+ res.set_language(winapi::um::winnt::MAKELANGID(
+ winapi::um::winnt::LANG_ENGLISH,
+ winapi::um::winnt::SUBLANG_ENGLISH_US,
+ ));
+ res.compile().expect("Unable to generate windows resources");
+ }
+}
diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs
new file mode 100644
index 0000000000..f8fda169b5
--- /dev/null
+++ b/mullvad-setup/src/main.rs
@@ -0,0 +1,80 @@
+use clap::{crate_authors, crate_description, crate_name, SubCommand};
+use mullvad_ipc_client::{new_standalone_ipc_client, DaemonRpcClient};
+use std::{io, process};
+use talpid_core::firewall::{self, Firewall, FirewallArguments};
+use talpid_types::ErrorExt;
+
+pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt"));
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "Failed to connect to daemon")]
+ DaemonConnect(#[error(source)] io::Error),
+
+ #[error(display = "RPC call failed")]
+ DaemonRpcError(#[error(source)] mullvad_ipc_client::Error),
+
+ #[error(display = "This command cannot be run if the daemon is active")]
+ DaemonIsRunning,
+
+ #[error(display = "Firewall error")]
+ FirewallError(#[error(source)] firewall::Error),
+}
+
+fn main() {
+ env_logger::init();
+
+ let subcommands = vec![
+ SubCommand::with_name("prepare-restart")
+ .about("Move a running daemon into a blocking state and save its target state"),
+ SubCommand::with_name("reset-firewall")
+ .about("Remove any firewall rules introduced by the daemon"),
+ ];
+
+ let app = clap::App::new(crate_name!())
+ .version(PRODUCT_VERSION)
+ .author(crate_authors!())
+ .about(crate_description!())
+ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
+ .global_settings(&[
+ clap::AppSettings::DisableHelpSubcommand,
+ clap::AppSettings::VersionlessSubcommands,
+ ])
+ .subcommands(subcommands);
+
+ let matches = app.get_matches();
+ let result = match matches.subcommand_name().expect("Subcommand has no name") {
+ "prepare-restart" => prepare_restart(),
+ "reset-firewall" => reset_firewall(),
+ _ => unreachable!("No command matched"),
+ };
+
+ if let Err(e) = result {
+ eprintln!("{}", e.display_chain());
+ process::exit(1);
+ }
+}
+
+fn prepare_restart() -> Result<(), Error> {
+ let mut rpc = new_rpc_client()?;
+ rpc.prepare_restart().map_err(Error::DaemonRpcError)
+}
+
+fn reset_firewall() -> Result<(), Error> {
+ // Ensure that the daemon isn't running
+ if let Ok(_) = new_rpc_client() {
+ return Err(Error::DaemonIsRunning);
+ }
+
+ let mut firewall = Firewall::new(FirewallArguments {
+ initialize_blocked: false,
+ allow_lan: None,
+ })
+ .map_err(Error::FirewallError)?;
+
+ firewall.reset_policy().map_err(Error::FirewallError)
+}
+
+fn new_rpc_client() -> Result<DaemonRpcClient, Error> {
+ new_standalone_ipc_client(&mullvad_paths::get_rpc_socket_path()).map_err(Error::DaemonConnect)
+}
diff --git a/version_metadata.sh b/version_metadata.sh
index 55f5df631b..93344bba40 100755
--- a/version_metadata.sh
+++ b/version_metadata.sh
@@ -39,6 +39,7 @@ case "$1" in
mullvad-daemon/Cargo.toml \
mullvad-cli/Cargo.toml \
mullvad-problem-report/Cargo.toml \
+ mullvad-setup/Cargo.toml \
talpid-openvpn-plugin/Cargo.toml
# Windows C++
@@ -67,6 +68,7 @@ EOF
mv mullvad-daemon/Cargo.toml.bak mullvad-daemon/Cargo.toml || true
mv mullvad-cli/Cargo.toml.bak mullvad-cli/Cargo.toml || true
mv mullvad-problem-report/Cargo.toml.bak mullvad-problem-report/Cargo.toml || true
+ mv mullvad-setup/Cargo.toml.bak mullvad-setup/Cargo.toml || true
mv talpid-openvpn-plugin/Cargo.toml.bak talpid-openvpn-plugin/Cargo.toml || true
# Windows C++
mv dist-assets/windows/version.h.bak dist-assets/windows/version.h || true
@@ -83,6 +85,7 @@ EOF
rm mullvad-daemon/Cargo.toml.bak || true
rm mullvad-cli/Cargo.toml.bak || true
rm mullvad-problem-report/Cargo.toml.bak || true
+ rm mullvad-setup/Cargo.toml.bak || true
rm talpid-openvpn-plugin/Cargo.toml.bak || true
# Windows C++
rm dist-assets/windows/version.h.bak || true