diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 32 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/account_history.rs | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 145 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 20 | ||||
| -rw-r--r-- | mullvad-daemon/src/system_service.rs | 63 |
7 files changed, 215 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 008daea001..113c7571b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Line wrap the file at 100 chars. Th ### Fixed - Mark CLI `bridge set state` argument as required to avoid a crash. +- The VPN service on Windows will now be restarted when it crashes. ## [2019.6] - 2019-07-15 diff --git a/Cargo.lock b/Cargo.lock index 1caa605063..79ed38fae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,7 +1186,7 @@ dependencies = [ "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-service 0.1.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=aab5b26beae364253802b6e5554c9ecdc6285454)", + "windows-service 0.2.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=a5eb1dcbbcee4ec2c6479256305c64b25640c799)", "winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2809,6 +2809,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.7" +source = "git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082#4bcf5cab87124bbeef8c1a445137494d874f8082" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)", +] + +[[package]] +name = "winapi" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2823,6 +2832,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" +source = "git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082#4bcf5cab87124bbeef8c1a445137494d874f8082" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2836,6 +2850,11 @@ dependencies = [ [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" +source = "git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082#4bcf5cab87124bbeef8c1a445137494d874f8082" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2860,13 +2879,13 @@ dependencies = [ [[package]] name = "windows-service" -version = "0.1.0" -source = "git+https://github.com/mullvad/windows-service-rs.git?rev=aab5b26beae364253802b6e5554c9ecdc6285454#aab5b26beae364253802b6e5554c9ecdc6285454" +version = "0.2.0" +source = "git+https://github.com/mullvad/windows-service-rs.git?rev=a5eb1dcbbcee4ec2c6479256305c64b25640c799#a5eb1dcbbcee4ec2c6479256305c64b25640c799" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)", ] [[package]] @@ -3185,14 +3204,17 @@ dependencies = [ "checksum widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a212922ea58fbf5044f83663aa4fc6281ff890f1fd7546c0c3f52f5290831781" "checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.7 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)" = "<none>" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)" = "<none>" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (git+https://github.com/mullvad/winapi-rs.git?rev=4bcf5cab87124bbeef8c1a445137494d874f8082)" = "<none>" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032" -"checksum windows-service 0.1.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=aab5b26beae364253802b6e5554c9ecdc6285454)" = "<none>" +"checksum windows-service 0.2.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=a5eb1dcbbcee4ec2c6479256305c64b25640c799)" = "<none>" "checksum winreg 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "daf67b95d0b1bf421c4f11048d63110ca3719977169eec86396b614c8942b6e0" "checksum winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index bbd1de43b2..511e52b060 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -52,7 +52,7 @@ simple-signal = "1.1" [target.'cfg(windows)'.dependencies] ctrlc = "3.0" -windows-service = { git = "https://github.com/mullvad/windows-service-rs.git", rev = "aab5b26beae364253802b6e5554c9ecdc6285454" } +windows-service = { git = "https://github.com/mullvad/windows-service-rs.git", rev = "a5eb1dcbbcee4ec2c6479256305c64b25640c799" } winapi = "0.3" [target.'cfg(windows)'.build-dependencies] diff --git a/mullvad-daemon/src/account_history.rs b/mullvad-daemon/src/account_history.rs index faecc5b5e7..8455c060d2 100644 --- a/mullvad-daemon/src/account_history.rs +++ b/mullvad-daemon/src/account_history.rs @@ -148,6 +148,7 @@ impl AccountHistory { } /// Remove account history + #[cfg(not(target_os = "android"))] pub fn clear(&mut self) -> Result<()> { self.accounts = VecDeque::new(); self.save_to_disk() diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 5fb1f11a89..27594c8adf 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -46,7 +46,9 @@ use mullvad_types::{ wireguard::KeygenEvent, }; use settings::Settings; -use std::{fs, io, mem, path::PathBuf, sync::mpsc, thread, time::Duration}; +#[cfg(not(target_os = "android"))] +use std::path::Path; +use std::{io, mem, path::PathBuf, sync::mpsc, thread, time::Duration}; use talpid_core::{ mpsc::IntoSender, tunnel::tun_provider::{PlatformTunProvider, TunProvider}, @@ -102,14 +104,26 @@ pub enum Error { #[error(display = "Tunnel state machine error")] TunnelError(#[error(cause)] tunnel_state_machine::Error), - #[error(display = "Failed to remove a directory")] - RemovalError(#[error(cause)] io::Error), + #[error(display = "Failed to remove directory {}", _0)] + RemoveDirError(String, #[error(cause)] io::Error), - #[error(display = "Failed to create a directory")] - CreateDirError(#[error(cause)] io::Error), + #[error(display = "Failed to create directory {}", _0)] + CreateDirError(String, #[error(cause)] io::Error), #[error(display = "Failed to get path")] PathError(#[error(cause)] mullvad_paths::Error), + + #[cfg(target_os = "windows")] + #[error(display = "Failed to get file type info")] + FileTypeError(#[error(cause)] io::Error), + + #[cfg(target_os = "windows")] + #[error(display = "Failed to get dir entry")] + FileEntryError(#[error(cause)] io::Error), + + #[cfg(target_os = "windows")] + #[error(display = "Failed to read dir entries")] + ReadDirError(#[error(cause)] io::Error), } type SyncUnboundedSender<T> = ::futures::sink::Wait<UnboundedSender<T>>; @@ -238,6 +252,7 @@ pub struct Daemon<L: EventListener = ManagementInterfaceEventBroadcaster> { last_generated_relay: Option<Relay>, last_generated_bridge_relay: Option<Relay>, version: String, + shutdown_callbacks: Vec<Box<dyn FnOnce()>>, } impl Daemon<ManagementInterfaceEventBroadcaster> { @@ -416,6 +431,7 @@ where last_generated_bridge_relay: None, version, wireguard_key_manager, + shutdown_callbacks: vec![], }; daemon.ensure_wireguard_keys_for_current_account(); @@ -442,9 +458,31 @@ where break; } } + + self.finalize(); Ok(()) } + fn finalize(self) { + let (event_listener, shutdown_callbacks) = self.shutdown(); + for cb in shutdown_callbacks { + cb(); + } + mem::drop(event_listener); + } + + /// Shuts down the daemon without shutting down the underlying management interface event + /// listener and the shutdown callbacks + fn shutdown(self) -> (L, Vec<Box<dyn FnOnce()>>) { + let Daemon { + event_listener, + shutdown_callbacks, + .. + } = self; + (event_listener, shutdown_callbacks) + } + + fn handle_event(&mut self, event: InternalDaemonEvent) -> Result<()> { use self::InternalDaemonEvent::*; match event { @@ -456,7 +494,7 @@ where ManagementInterfaceExited => { return Err(Error::ManagementInterfaceExited); } - TriggerShutdown => self.handle_trigger_shutdown_event(), + TriggerShutdown => self.trigger_shutdown_event(), WgKeyEvent(key_event) => self.handle_wireguard_key_event(key_event), } Ok(()) @@ -677,6 +715,10 @@ where fn handle_management_interface_event(&mut self, event: ManagementCommand) { use self::ManagementCommand::*; + if !self.state.is_running() { + log::trace!("Dropping management command because the daemon is shutting down",); + return; + } match event { SetTargetState(tx, state) => self.on_set_target_state(tx, state), GetState(tx) => self.on_get_state(tx), @@ -708,8 +750,9 @@ where VerifyWireguardKey(tx) => self.on_verify_wireguard_key(tx), GetVersionInfo(tx) => self.on_get_version_info(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), + #[cfg(not(target_os = "android"))] FactoryReset(tx) => self.on_factory_reset(tx), - Shutdown => self.handle_trigger_shutdown_event(), + Shutdown => self.trigger_shutdown_event(), } } @@ -942,19 +985,10 @@ where Self::oneshot_send(tx, self.version.clone(), "get_current_version response"); } + #[cfg(not(target_os = "android"))] fn on_factory_reset(&mut self, tx: oneshot::Sender<()>) { - self.set_target_state(TargetState::Unsecured); let mut failed = false; - if let Err(e) = self.clear_cache_directory() { - log::error!("Failed to clear cache directory - {}", e); - failed = true; - } - - if let Err(e) = self.clear_log_directory() { - log::error!("Failed to clear log directory - {}", e); - failed = true; - } if let Err(e) = self.settings.reset() { log::error!("Failed to reset settings - {}", e); @@ -966,9 +1000,29 @@ where failed = true; } - if !failed { - Self::oneshot_send(tx, (), "factory_reset response"); - } + // Shut the daemon down. + self.trigger_shutdown_event(); + + self.shutdown_callbacks.push(Box::new(move || { + if let Err(e) = Self::clear_cache_directory() { + log::error!( + "{}", + e.display_chain_with_msg("Failed to clear cache directory") + ); + failed = true; + } + + if let Err(e) = Self::clear_log_directory() { + log::error!( + "{}", + e.display_chain_with_msg("Failed to clear log directory") + ); + failed = true; + } + if !failed { + Self::oneshot_send(tx, (), "factory_reset response"); + } + })); } fn on_update_relay_settings(&mut self, tx: oneshot::Sender<()>, update: RelaySettingsUpdate) { @@ -1293,7 +1347,7 @@ where } } - fn handle_trigger_shutdown_event(&mut self) { + fn trigger_shutdown_event(&mut self) { self.state.shutdown(&self.tunnel_state); self.disconnect_tunnel(); } @@ -1332,18 +1386,55 @@ where .expect("Tunnel state machine has stopped"); } - fn clear_log_directory(&self) -> Result<()> { + #[cfg(not(target_os = "android"))] + fn clear_log_directory() -> Result<()> { let log_dir = mullvad_paths::get_log_dir().map_err(Error::PathError)?; - fs::remove_dir_all(&log_dir).map_err(Error::RemovalError)?; - fs::create_dir_all(&log_dir).map_err(Error::CreateDirError) + Self::clear_directory(&log_dir) } - fn clear_cache_directory(&self) -> Result<()> { + #[cfg(not(target_os = "android"))] + fn clear_cache_directory() -> Result<()> { let cache_dir = mullvad_paths::cache_dir().map_err(Error::PathError)?; - fs::remove_dir_all(&cache_dir).map_err(Error::RemovalError)?; - fs::create_dir_all(&cache_dir).map_err(Error::CreateDirError) + Self::clear_directory(&cache_dir) } + #[cfg(not(target_os = "android"))] + fn clear_directory(path: &Path) -> Result<()> { + use std::fs; + #[cfg(not(target_os = "windows"))] + { + fs::remove_dir_all(path) + .map_err(|e| Error::RemoveDirError(path.display().to_string(), e))?; + fs::create_dir_all(path) + .map_err(|e| Error::CreateDirError(path.display().to_string(), e)) + } + #[cfg(target_os = "windows")] + { + fs::read_dir(&path) + .map_err(Error::ReadDirError) + .and_then(|dir_entries| { + dir_entries + .into_iter() + .map(|entry| { + let entry = entry.map_err(Error::FileEntryError)?; + let entry_type = entry.file_type().map_err(Error::FileTypeError)?; + + + let removal = if entry_type.is_file() || entry_type.is_symlink() { + fs::remove_file(entry.path()) + } else { + fs::remove_dir_all(entry.path()) + }; + removal.map_err(|e| { + Error::RemoveDirError(entry.path().display().to_string(), e) + }) + }) + .collect::<Result<()>>() + }) + } + } + + pub fn shutdown_handle(&self) -> DaemonShutdownHandle { DaemonShutdownHandle { tx: self.tx.clone(), diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 04193ca9b5..0940780ea3 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -226,6 +226,7 @@ pub enum ManagementCommand { GetVersionInfo(OneshotSender<BoxFuture<version::AppVersionInfo, mullvad_rpc::Error>>), /// Get current version of the app GetCurrentVersion(OneshotSender<version::AppVersion>), + #[cfg(not(target_os = "android"))] /// Remove settings and clear the cache FactoryReset(OneshotSender<()>), /// Makes the daemon exit the main loop and quit. @@ -686,13 +687,20 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi } fn factory_reset(&self, _: Self::Metadata) -> BoxFuture<(), Error> { - log::debug!("factory_reset"); - let (tx, rx) = sync::oneshot::channel(); - let future = self - .send_command_to_daemon(ManagementCommand::FactoryReset(tx)) - .and_then(|_| rx.map_err(|_| Error::internal_error())); + #[cfg(not(target_os = "android"))] + { + log::debug!("factory_reset"); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::FactoryReset(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); - Box::new(future) + Box::new(future) + } + #[cfg(target_os = "android")] + { + Box::new(future::ok(())) + } } diff --git a/mullvad-daemon/src/system_service.rs b/mullvad-daemon/src/system_service.rs index 62c997022a..c7007ae666 100644 --- a/mullvad-daemon/src/system_service.rs +++ b/mullvad-daemon/src/system_service.rs @@ -4,7 +4,7 @@ use std::{ env, ffi::OsString, sync::{ - atomic::{AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, mpsc, Arc, }, thread, @@ -13,9 +13,10 @@ use std::{ use talpid_types::ErrorExt; use windows_service::{ service::{ - ServiceAccess, ServiceControl, ServiceControlAccept, ServiceDependency, - ServiceErrorControl, ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, - ServiceStatus, ServiceType, + ServiceAccess, ServiceAction, ServiceActionType, ServiceControl, ServiceControlAccept, + ServiceDependency, ServiceErrorControl, ServiceExitCode, ServiceFailureActions, + ServiceFailureResetPeriod, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, + ServiceType, }, service_control_handler::{self, ServiceControlHandlerResult, ServiceStatusHandle}, service_dispatcher, @@ -69,26 +70,42 @@ fn run_service() -> Result<(), String> { .set_pending_start(Duration::from_secs(1)) .unwrap(); + let clean_shutdown = Arc::new(AtomicBool::new(false)); + let log_dir = crate::get_log_dir(cli::get_config()).expect("Log dir should be available here"); let result = crate::create_daemon(log_dir).and_then(|daemon| { let shutdown_handle = daemon.shutdown_handle(); // Register monitor that translates `ServiceControl` to Daemon events - start_event_monitor(persistent_service_status.clone(), shutdown_handle, event_rx); + start_event_monitor( + persistent_service_status.clone(), + shutdown_handle, + event_rx, + clean_shutdown.clone(), + ); persistent_service_status.set_running().unwrap(); daemon.run().map_err(|e| e.display_chain()) }); + let exit_code = match result { - Ok(()) => ServiceExitCode::default(), + Ok(()) => { + // check if shutdown signal was sent from the system + if clean_shutdown.load(Ordering::Acquire) { + ServiceExitCode::default() + } else { + // otherwise return a non-zero code so that the daemon gets restarted + ServiceExitCode::ServiceSpecific(1) + } + } Err(_) => ServiceExitCode::ServiceSpecific(1), }; persistent_service_status.set_stopped(exit_code).unwrap(); - result + result.map(|_| ()) } /// Start event monitor thread that polls for `ServiceControl` and translates them into calls to @@ -97,6 +114,7 @@ fn start_event_monitor( mut persistent_service_status: PersistentServiceStatus, shutdown_handle: DaemonShutdownHandle, event_rx: mpsc::Receiver<ServiceControl>, + clean_shutdown: Arc<AtomicBool>, ) -> thread::JoinHandle<()> { thread::spawn(move || { for event in event_rx { @@ -106,6 +124,7 @@ fn start_event_monitor( .set_pending_stop(Duration::from_secs(10)) .unwrap(); + clean_shutdown.store(true, Ordering::Release); shutdown_handle.shutdown(); } _ => (), @@ -227,9 +246,33 @@ pub fn install_service() -> Result<(), InstallError> { let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access) .map_err(InstallError::ConnectServiceManager)?; - service_manager - .create_service(get_service_info(), ServiceAccess::empty()) - .map(|_| ()) + let service_access = ServiceAccess::QUERY_CONFIG + | ServiceAccess::CHANGE_CONFIG + | ServiceAccess::START + | ServiceAccess::DELETE; + + let service = service_manager + .create_service(get_service_info(), service_access) + .or(service_manager.open_service(SERVICE_NAME, service_access)) + .map_err(InstallError::CreateService)?; + + let recovery_actions = vec![ServiceAction { + action_type: ServiceActionType::Restart, + delay: Duration::from_secs(3), + }]; + + let failure_actions = ServiceFailureActions { + reset_period: ServiceFailureResetPeriod::After(Duration::from_secs(2)), + reboot_msg: None, + command: None, + actions: Some(recovery_actions), + }; + + service + .update_failure_actions(failure_actions) + .map_err(InstallError::CreateService)?; + service + .set_failure_actions_on_non_crash_failures(true) .map_err(InstallError::CreateService) } |
