diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-10-07 13:56:27 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-10-07 13:56:27 +0200 |
| commit | 24fea3591794cadaef4f3ffad948acc08adb0e30 (patch) | |
| tree | 905876a81e2cddedcfd59581ebcd67b9a334e0e9 | |
| parent | e31b0d39fdbeff9421cc69a1dd0c6a8c807f6732 (diff) | |
| parent | 70e7fdc141476ed9f5b086e928238576d58ec4b6 (diff) | |
| download | mullvadvpn-24fea3591794cadaef4f3ffad948acc08adb0e30.tar.xz mullvadvpn-24fea3591794cadaef4f3ffad948acc08adb0e30.zip | |
Merge branch 'win-st-improve-reqs'
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 248 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/windows/driver.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/windows/mod.rs | 70 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 6 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 4 |
9 files changed, 213 insertions, 137 deletions
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 15733d2f29..46d44c18d9 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -317,6 +317,15 @@ pub(crate) enum InternalDaemonEvent { NewAccountEvent(AccountToken, oneshot::Sender<Result<String, Error>>), /// The background job fetching new `AppVersionInfo`s got a new info object. NewAppVersionInfo(AppVersionInfo), + /// The split tunnel paths or state were updated. + #[cfg(target_os = "windows")] + ExcludedPathsEvent(ExcludedPathsUpdate, oneshot::Sender<Result<(), Error>>), +} + +#[cfg(target_os = "windows")] +pub(crate) enum ExcludedPathsUpdate { + SetState(bool), + SetPaths(HashSet<PathBuf>), } impl From<TunnelStateTransition> for InternalDaemonEvent { @@ -909,6 +918,8 @@ where NewAppVersionInfo(app_version_info) => { self.handle_new_app_version_info(app_version_info) } + #[cfg(windows)] + ExcludedPathsEvent(update, tx) => self.handle_new_excluded_paths(update, tx).await, } } @@ -1351,6 +1362,56 @@ where self.event_listener.notify_app_version(app_version_info); } + #[cfg(windows)] + async fn handle_new_excluded_paths( + &mut self, + update: ExcludedPathsUpdate, + tx: ResponseTx<(), Error>, + ) { + match update { + ExcludedPathsUpdate::SetState(state) => { + let save_result = self + .settings + .set_split_tunnel_state(state) + .await + .map_err(Error::SettingsError); + match save_result { + Ok(true) => { + let _ = tx.send(Ok(())); + self.event_listener + .notify_settings(self.settings.to_settings()); + } + Ok(false) => { + let _ = tx.send(Ok(())); + } + Err(error) => { + let _ = tx.send(Err(error)); + } + } + } + ExcludedPathsUpdate::SetPaths(paths) => { + let save_result = self + .settings + .set_split_tunnel_apps(paths) + .await + .map_err(Error::SettingsError); + match save_result { + Ok(true) => { + let _ = tx.send(Ok(())); + self.event_listener + .notify_settings(self.settings.to_settings()); + } + Ok(false) => { + let _ = tx.send(Ok(())); + } + Err(error) => { + let _ = tx.send(Err(error)); + } + } + } + } + } + async fn on_set_target_state( &mut self, tx: oneshot::Sender<bool>, @@ -1799,54 +1860,63 @@ where tx: ResponseTx<(), Error>, response_msg: &'static str, settings: Settings, - new_list: HashSet<PathBuf>, + update: ExcludedPathsUpdate, ) { - if new_list == settings.split_tunnel.apps { - Self::oneshot_send(tx, Ok(()), response_msg); - return; - } - - if settings.split_tunnel.enable_exclusions { - let (result_tx, result_rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::SetExcludedApps( - result_tx, - new_list.iter().map(|s| OsString::from(s)).collect(), - )); - match result_rx.await { - Ok(Ok(_)) => (), - Ok(Err(error)) => { - log::error!( - "{}", - error.display_chain_with_msg("Failed to set excluded apps list") - ); - Self::oneshot_send(tx, Err(Error::SplitTunnelError(error)), response_msg); + let new_list = match update { + ExcludedPathsUpdate::SetPaths(ref paths) => { + if *paths == settings.split_tunnel.apps { + Self::oneshot_send(tx, Ok(()), response_msg); return; } - Err(_) => { - log::error!("The tunnel failed to return a result"); + paths.iter() + } + ExcludedPathsUpdate::SetState(_) => settings.split_tunnel.apps.iter(), + }; + let new_state = match update { + ExcludedPathsUpdate::SetPaths(_) => settings.split_tunnel.enable_exclusions, + ExcludedPathsUpdate::SetState(state) => { + if state == settings.split_tunnel.enable_exclusions { + Self::oneshot_send(tx, Ok(()), response_msg); return; } + state } - } + }; - let save_result = self - .settings - .set_split_tunnel_apps(new_list) - .await - .map_err(Error::SettingsError); - match save_result { - Ok(true) => { - Self::oneshot_send(tx, Ok(()), response_msg); - self.event_listener - .notify_settings(self.settings.to_settings()); - } - Err(error) => { - Self::oneshot_send(tx, Err(error), response_msg); - } - Ok(false) => { - // unreachable!("new_list != settings.split_tunnel_apps") - error!("BUG: new_list != settings.split_tunnel_apps"); - } + if new_state || new_state != settings.split_tunnel.enable_exclusions { + let tunnel_list = if new_state { + new_list.map(|s| OsString::from(s)).collect() + } else { + vec![] + }; + + let (result_tx, result_rx) = oneshot::channel(); + self.send_tunnel_command(TunnelCommand::SetExcludedApps(result_tx, tunnel_list)); + let daemon_tx = self.tx.clone(); + + tokio::spawn(async move { + match result_rx.await { + Ok(Ok(_)) => (), + Ok(Err(error)) => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to set excluded apps list") + ); + Self::oneshot_send(tx, Err(Error::SplitTunnelError(error)), response_msg); + return; + } + Err(_) => { + log::error!("The tunnel failed to return a result"); + return; + } + } + + let _ = daemon_tx.send(InternalDaemonEvent::ExcludedPathsEvent(update, tx)); + }); + } else { + let _ = self + .tx + .send(InternalDaemonEvent::ExcludedPathsEvent(update, tx)); } } @@ -1857,8 +1927,13 @@ where let mut new_list = settings.split_tunnel.apps.clone(); new_list.insert(path); - self.set_split_tunnel_paths(tx, "add_split_tunnel_app response", settings, new_list) - .await; + self.set_split_tunnel_paths( + tx, + "add_split_tunnel_app response", + settings, + ExcludedPathsUpdate::SetPaths(new_list), + ) + .await; } #[cfg(windows)] @@ -1868,81 +1943,38 @@ where let mut new_list = settings.split_tunnel.apps.clone(); new_list.remove(&path); - self.set_split_tunnel_paths(tx, "remove_split_tunnel_app response", settings, new_list) - .await; + self.set_split_tunnel_paths( + tx, + "remove_split_tunnel_app response", + settings, + ExcludedPathsUpdate::SetPaths(new_list), + ) + .await; } #[cfg(windows)] async fn on_clear_split_tunnel_apps(&mut self, tx: ResponseTx<(), Error>) { let settings = self.settings.to_settings(); let new_list = HashSet::new(); - self.set_split_tunnel_paths(tx, "clear_split_tunnel_apps response", settings, new_list) - .await; + self.set_split_tunnel_paths( + tx, + "clear_split_tunnel_apps response", + settings, + ExcludedPathsUpdate::SetPaths(new_list), + ) + .await; } #[cfg(windows)] - async fn on_set_split_tunnel_state(&mut self, tx: ResponseTx<(), Error>, enabled: bool) { + async fn on_set_split_tunnel_state(&mut self, tx: ResponseTx<(), Error>, state: bool) { let settings = self.settings.to_settings(); - - if enabled != settings.split_tunnel.enable_exclusions { - let new_list = if enabled { - settings.split_tunnel.apps.clone() - } else { - HashSet::new() - }; - if !settings.split_tunnel.apps.is_empty() { - let (result_tx, result_rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::SetExcludedApps( - result_tx, - new_list.iter().map(|app| OsString::from(app)).collect(), - )); - match result_rx.await { - Ok(Ok(_)) => (), - Ok(Err(error)) => { - log::error!( - "{}", - error.display_chain_with_msg("Failed to set excluded apps list") - ); - Self::oneshot_send( - tx, - Err(Error::SplitTunnelError(error)), - "set_split_tunnel_state response", - ); - return; - } - Err(_) => { - log::error!("The tunnel failed to return a result"); - return; - } - } - } - - let save_result = self - .settings - .set_split_tunnel_state(enabled) - .await - .map_err(Error::SettingsError); - match save_result { - Ok(true) => { - Self::oneshot_send(tx, Ok(()), "set_split_tunnel_state response"); - self.event_listener - .notify_settings(self.settings.to_settings()); - } - Err(error) => { - error!( - "{}", - error.display_chain_with_msg("Unable to save settings") - ); - Self::oneshot_send(tx, Err(error), "set_split_tunnel_state response"); - } - Ok(false) => { - // unreachable!("enabled != settings.split_tunnel"), - error!("BUG: enabled != settings.split_tunnel"); - } - } - } else { - Self::oneshot_send(tx, Ok(()), "set_split_tunnel_state response"); - } + self.set_split_tunnel_paths( + tx, + "set_split_tunnel_state response", + settings, + ExcludedPathsUpdate::SetState(state), + ) + .await; } #[cfg(windows)] diff --git a/talpid-core/src/split_tunnel/windows/driver.rs b/talpid-core/src/split_tunnel/windows/driver.rs index 7f097f9e54..5a9f27b7e3 100644 --- a/talpid-core/src/split_tunnel/windows/driver.rs +++ b/talpid-core/src/split_tunnel/windows/driver.rs @@ -42,8 +42,6 @@ use winapi::{ const DRIVER_SYMBOLIC_NAME: &str = "\\\\.\\MULLVADSPLITTUNNEL"; const ST_DEVICE_TYPE: u32 = 0x8000; -const DRIVER_IO_TIMEOUT: Duration = Duration::from_secs(3); - const fn ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> u32 { device_type << 16 | access << 14 | function << 2 | method } @@ -170,7 +168,7 @@ impl DeviceHandle { DriverIoctlCode::Initialize as u32, None, 0, - Some(DRIVER_IO_TIMEOUT), + None, )?; Ok(()) } @@ -182,7 +180,7 @@ impl DeviceHandle { DriverIoctlCode::RegisterProcesses as u32, Some(&process_tree_buffer), 0, - Some(DRIVER_IO_TIMEOUT), + None, )?; Ok(()) } @@ -244,7 +242,7 @@ impl DeviceHandle { DriverIoctlCode::RegisterIpAddresses as u32, Some(buffer), 0, - Some(DRIVER_IO_TIMEOUT), + None, )?; Ok(()) @@ -256,7 +254,7 @@ impl DeviceHandle { DriverIoctlCode::GetState as u32, None, size_of::<u64>() as u32, - Some(DRIVER_IO_TIMEOUT), + None, )? .unwrap(); @@ -292,7 +290,7 @@ impl DeviceHandle { DriverIoctlCode::SetConfiguration as u32, Some(&config), 0, - Some(DRIVER_IO_TIMEOUT), + None, )?; Ok(()) @@ -304,7 +302,7 @@ impl DeviceHandle { DriverIoctlCode::ClearConfiguration as u32, None, 0, - Some(DRIVER_IO_TIMEOUT), + None, )?; Ok(()) diff --git a/talpid-core/src/split_tunnel/windows/mod.rs b/talpid-core/src/split_tunnel/windows/mod.rs index 2fd19da420..e826eda8d3 100644 --- a/talpid-core/src/split_tunnel/windows/mod.rs +++ b/talpid-core/src/split_tunnel/windows/mod.rs @@ -9,7 +9,7 @@ use crate::{ self, get_best_default_route, interface_luid_to_ip, WinNetAddrFamily, WinNetCallbackHandle, }, }; -use futures::channel::mpsc; +use futures::channel::{mpsc, oneshot}; use std::{ convert::TryFrom, ffi::{OsStr, OsString}, @@ -17,7 +17,10 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, os::windows::io::{AsRawHandle, RawHandle}, ptr, - sync::{mpsc as sync_mpsc, Arc, Mutex, Weak}, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc as sync_mpsc, Arc, Mutex, Weak, + }, time::Duration, }; use talpid_types::{tunnel::ErrorStateCause, ErrorExt}; @@ -86,15 +89,21 @@ pub enum Error { /// Failed to start the NTFS reparse point monitor #[error(display = "Failed to start path monitor")] StartPathMonitor(#[error(source)] io::Error), + + /// A previous path update has not yet completed + #[error(display = "A previous update is not yet complete")] + AlreadySettingPaths, } /// Manages applications whose traffic to exclude from the tunnel. pub struct SplitTunnel { + runtime: tokio::runtime::Handle, request_tx: RequestTx, event_thread: Option<std::thread::JoinHandle<()>>, quit_event: RawHandle, _route_change_callback: Option<WinNetCallbackHandle>, daemon_tx: Weak<mpsc::UnboundedSender<TunnelCommand>>, + async_path_update_in_progress: Arc<AtomicBool>, } enum Request { @@ -107,7 +116,7 @@ enum Request { ), } type RequestResponseTx = sync_mpsc::Sender<Result<(), Error>>; -type RequestTx = sync_mpsc::SyncSender<(Request, RequestResponseTx)>; +type RequestTx = sync_mpsc::Sender<(Request, RequestResponseTx)>; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -120,7 +129,10 @@ unsafe impl Send for EventThreadContext {} impl SplitTunnel { /// Initialize the driver. - pub fn new(daemon_tx: Weak<mpsc::UnboundedSender<TunnelCommand>>) -> Result<Self, Error> { + pub fn new( + runtime: tokio::runtime::Handle, + daemon_tx: Weak<mpsc::UnboundedSender<TunnelCommand>>, + ) -> Result<Self, Error> { let (request_tx, handle) = Self::spawn_request_thread()?; let mut event_overlapped: OVERLAPPED = unsafe { mem::zeroed() }; @@ -277,16 +289,18 @@ impl SplitTunnel { }); Ok(SplitTunnel { + runtime, request_tx, event_thread: Some(event_thread), quit_event, _route_change_callback: None, daemon_tx, + async_path_update_in_progress: Arc::new(AtomicBool::new(false)), }) } fn spawn_request_thread() -> Result<(RequestTx, Arc<driver::DeviceHandle>), Error> { - let (tx, rx): (RequestTx, _) = sync_mpsc::sync_channel(3); + let (tx, rx): (RequestTx, _) = sync_mpsc::channel(); let (init_tx, init_rx) = sync_mpsc::channel(); let (path_monitor, path_change_rx) = @@ -398,11 +412,8 @@ impl SplitTunnel { let (response_tx, response_rx) = sync_mpsc::channel(); request_tx - .try_send((request, response_tx)) - .map_err(|error| match error { - sync_mpsc::TrySendError::Disconnected(_) => Error::RequestThreadDown, - sync_mpsc::TrySendError::Full(_) => Error::RequestThreadStuck, - })?; + .send((request, response_tx)) + .map_err(|_| Error::RequestThreadDown)?; response_rx .recv_timeout(REQUEST_TIMEOUT) @@ -410,7 +421,7 @@ impl SplitTunnel { } /// Set a list of applications to exclude from the tunnel. - pub fn set_paths<T: AsRef<OsStr>>(&self, paths: &[T]) -> Result<(), Error> { + pub fn set_paths_sync<T: AsRef<OsStr>>(&self, paths: &[T]) -> Result<(), Error> { self.send_request(Request::SetPaths( paths .iter() @@ -419,6 +430,41 @@ impl SplitTunnel { )) } + /// Set a list of applications to exclude from the tunnel. + pub fn set_paths<T: AsRef<OsStr>>( + &self, + paths: &[T], + result_tx: oneshot::Sender<Result<(), Error>>, + ) { + let busy = self + .async_path_update_in_progress + .swap(true, Ordering::SeqCst); + if busy { + let _ = result_tx.send(Err(Error::AlreadySettingPaths)); + return; + } + let (response_tx, response_rx) = sync_mpsc::channel(); + let request = Request::SetPaths( + paths + .iter() + .map(|path| path.as_ref().to_os_string()) + .collect(), + ); + let request_tx = self.request_tx.clone(); + + let wait_task = move || { + request_tx + .send((request, response_tx)) + .map_err(|_| Error::RequestThreadDown)?; + response_rx.recv().map_err(|_| Error::RequestThreadDown)? + }; + let in_progress = self.async_path_update_in_progress.clone(); + self.runtime.spawn_blocking(move || { + let _ = result_tx.send(wait_task()); + in_progress.store(false, Ordering::SeqCst); + }); + } + /// Instructs the driver to redirect traffic from sockets bound to 0.0.0.0, ::, or the /// tunnel addresses (if any) to the default route. pub fn set_tunnel_addresses(&mut self, metadata: Option<&TunnelMetadata>) -> Result<(), Error> { @@ -482,7 +528,7 @@ impl Drop for SplitTunnel { } let paths: [&OsStr; 0] = []; - if let Err(error) = self.set_paths(&paths) { + if let Err(error) = self.set_paths_sync(&paths) { log::error!("{}", error.display_chain()); } } diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 49365d379e..400df2695d 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -262,7 +262,7 @@ impl ConnectedState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); SameState(self.into()) } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 066163fd73..3e16c7a23d 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -326,7 +326,7 @@ impl ConnectingState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); SameState(self.into()) } } diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 9b5d51bd51..c644dc4c31 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -146,7 +146,7 @@ impl TunnelState for DisconnectedState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); SameState(self.into()) } Some(_) => SameState(self.into()), diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 71fbd6ae95..8f6f6ae68b 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -61,7 +61,7 @@ impl DisconnectingState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); AfterDisconnect::Nothing } }, @@ -103,7 +103,7 @@ impl DisconnectingState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); AfterDisconnect::Block(reason) } None => AfterDisconnect::Block(reason), @@ -146,7 +146,7 @@ impl DisconnectingState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); AfterDisconnect::Reconnect(retry_attempt) } }, diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 2f1f461dfd..7b9be4c479 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -168,7 +168,7 @@ impl TunnelState for ErrorState { } #[cfg(windows)] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.split_tunnel.set_paths(&paths)); + shared_values.split_tunnel.set_paths(&paths, result_tx); SameState(self.into()) } } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index c4e5e5ef0c..c871998a71 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -228,7 +228,7 @@ impl TunnelStateMachine { #[cfg(target_os = "android")] android_context: AndroidContext, ) -> Result<Self, Error> { #[cfg(windows)] - let split_tunnel = split_tunnel::SplitTunnel::new(command_tx.clone()) + let split_tunnel = split_tunnel::SplitTunnel::new(runtime.clone(), command_tx.clone()) .map_err(Error::InitSplitTunneling)?; let args = FirewallArguments { @@ -279,7 +279,7 @@ impl TunnelStateMachine { #[cfg(windows)] split_tunnel - .set_paths(&settings.exclude_paths) + .set_paths_sync(&settings.exclude_paths) .map_err(Error::InitSplitTunneling)?; let mut shared_values = SharedTunnelStateValues { |
