diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2018-11-09 12:59:01 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2018-11-09 12:59:01 +0100 |
| commit | 900833dedc44c99aa235a11d6daf62feeddd7c0f (patch) | |
| tree | b71cdfc62135f203123ab38512c959f1d5444b80 /talpid-core/src | |
| parent | 6470f1c4651b3041144f43728af12b95d29d2750 (diff) | |
| parent | b81d8e748f1b31bc9b156d28e42e5383faad4314 (diff) | |
| download | mullvadvpn-900833dedc44c99aa235a11d6daf62feeddd7c0f.tar.xz mullvadvpn-900833dedc44c99aa235a11d6daf62feeddd7c0f.zip | |
Merge branch 'add-macos-offline-state'
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/offline/dummy.rs | 12 | ||||
| -rw-r--r-- | talpid-core/src/offline/macos.rs | 83 | ||||
| -rw-r--r-- | talpid-core/src/offline/mod.rs | 9 | ||||
| -rw-r--r-- | talpid-core/src/security/macos/dns.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/blocked_state.rs | 16 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 15 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 18 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 20 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 17 |
11 files changed, 193 insertions, 7 deletions
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index f2bfb7718f..20c9302c8a 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -43,6 +43,8 @@ extern crate talpid_types; #[cfg(windows)] mod winnet; +mod offline; + /// Working with processes. pub mod process; diff --git a/talpid-core/src/offline/dummy.rs b/talpid-core/src/offline/dummy.rs new file mode 100644 index 0000000000..c654b846b8 --- /dev/null +++ b/talpid-core/src/offline/dummy.rs @@ -0,0 +1,12 @@ +use futures::sync::mpsc::UnboundedSender; +use tunnel_state_machine::TunnelCommand; + +error_chain!{} + +pub fn spawn_monitor(_sender: UnboundedSender<TunnelCommand>) -> Result<()> { + Ok(()) +} + +pub fn is_offline() -> bool { + false +} diff --git a/talpid-core/src/offline/macos.rs b/talpid-core/src/offline/macos.rs new file mode 100644 index 0000000000..12362eb16d --- /dev/null +++ b/talpid-core/src/offline/macos.rs @@ -0,0 +1,83 @@ +extern crate system_configuration; + +use self::system_configuration::{ + core_foundation::{ + array::CFArray, + runloop::{kCFRunLoopCommonModes, CFRunLoop}, + string::CFString, + }, + dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext}, +}; +use futures::sync::mpsc::UnboundedSender; +use log::{debug, trace}; +use std::{sync::mpsc, thread}; +use tunnel_state_machine::TunnelCommand; + +const PRIMARY_INTERFACE_KEY: &str = "State:/Network/Global/IPv4"; + +error_chain! { + errors { + DynamicStoreInitError { description("Failed to initialize dynamic store") } + } +} + +pub fn spawn_monitor(sender: UnboundedSender<TunnelCommand>) -> Result<()> { + let (result_tx, result_rx) = mpsc::channel(); + thread::spawn(move || match create_dynamic_store(sender) { + Ok(store) => { + result_tx.send(Ok(())).unwrap(); + run_dynamic_store_runloop(store); + log::error!("Core Foundation main loop exited! It should run forever"); + } + Err(e) => result_tx.send(Err(e)).unwrap(), + }); + result_rx.recv().unwrap() +} + +pub fn is_offline() -> bool { + let store = SCDynamicStoreBuilder::new("talpid-primary-interface").build(); + let is_offline = store.get(CFString::new(PRIMARY_INTERFACE_KEY)).is_none(); + is_offline +} + +fn create_dynamic_store(sender: UnboundedSender<TunnelCommand>) -> Result<SCDynamicStore> { + let callback_context = SCDynamicStoreCallBackContext { + callout: primary_interface_change_callback, + info: sender, + }; + + let store = SCDynamicStoreBuilder::new("talpid-primary-interface") + .callback_context(callback_context) + .build(); + + let watch_keys = CFArray::from_CFTypes(&[CFString::new(PRIMARY_INTERFACE_KEY)]); + let watch_patterns: CFArray<CFString> = CFArray::from_CFTypes(&[]); + + if store.set_notification_keys(&watch_keys, &watch_patterns) { + trace!("Registered for dynamic store notifications"); + Ok(store) + } else { + bail!(ErrorKind::DynamicStoreInitError) + } +} + +fn run_dynamic_store_runloop(store: SCDynamicStore) { + let run_loop_source = store.create_run_loop_source(); + CFRunLoop::get_current().add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes }); + + trace!("Entering primary interface CFRunLoop"); + CFRunLoop::run_current(); +} + +fn primary_interface_change_callback( + store: SCDynamicStore, + _changed_keys: CFArray<CFString>, + state: &mut UnboundedSender<TunnelCommand>, +) { + let is_offline = store.get(CFString::new(PRIMARY_INTERFACE_KEY)).is_none(); + debug!( + "Computer went {}", + if is_offline { "offline" } else { "online" } + ); + let _ = state.unbounded_send(TunnelCommand::IsOffline(is_offline)); +} diff --git a/talpid-core/src/offline/mod.rs b/talpid-core/src/offline/mod.rs new file mode 100644 index 0000000000..696e7225b3 --- /dev/null +++ b/talpid-core/src/offline/mod.rs @@ -0,0 +1,9 @@ +#[cfg(target_os = "macos")] +#[path = "macos.rs"] +mod imp; + +#[cfg(not(target_os = "macos"))] +#[path = "dummy.rs"] +mod imp; + +pub use self::imp::{is_offline, spawn_monitor}; diff --git a/talpid-core/src/security/macos/dns.rs b/talpid-core/src/security/macos/dns.rs index 391dbe7887..3ae909f10c 100644 --- a/talpid-core/src/security/macos/dns.rs +++ b/talpid-core/src/security/macos/dns.rs @@ -218,7 +218,7 @@ fn create_dynamic_store(state: Arc<Mutex<Option<State>>>) -> Result<SCDynamicSto info: state, }; - let store = SCDynamicStoreBuilder::new("mullvad-dns-monitor") + let store = SCDynamicStoreBuilder::new("talpid-dns-monitor") .callback_context(callback_context) .build(); @@ -240,7 +240,7 @@ fn run_dynamic_store_runloop(store: SCDynamicStore) { let run_loop_source = store.create_run_loop_source(); CFRunLoop::get_current().add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes }); - trace!("Entering CFRunLoop"); + trace!("Entering DNS CFRunLoop"); CFRunLoop::run_current(); } diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs index 432dea3999..3e63551110 100644 --- a/talpid-core/src/tunnel_state_machine/blocked_state.rs +++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs @@ -10,7 +10,9 @@ use super::{ use crate::security::SecurityPolicy; /// No tunnel is running and all network connections are blocked. -pub struct BlockedState; +pub struct BlockedState { + block_reason: BlockReason, +} impl BlockedState { fn set_security_policy(shared_values: &mut SharedTunnelStateValues) { @@ -36,7 +38,9 @@ impl TunnelState for BlockedState { ) -> (TunnelStateWrapper, TunnelStateTransition) { Self::set_security_policy(shared_values); ( - TunnelStateWrapper::from(BlockedState), + TunnelStateWrapper::from(BlockedState { + block_reason: block_reason.clone(), + }), TunnelStateTransition::Blocked(block_reason), ) } @@ -54,6 +58,14 @@ impl TunnelState for BlockedState { Self::set_security_policy(shared_values); SameState(self) } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + if !is_offline && self.block_reason == BlockReason::IsOffline { + NewState(ConnectingState::enter(shared_values, 0)) + } else { + SameState(self) + } + } Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)), Ok(TunnelCommand::Disconnect) | Err(_) => { NewState(DisconnectedState::enter(shared_values, ())) diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 66f9b78bc1..577f435260 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -94,6 +94,21 @@ impl ConnectedState { } } } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + if is_offline { + NewState(DisconnectingState::enter( + shared_values, + ( + self.close_handle, + self.tunnel_close_event, + AfterDisconnect::Block(BlockReason::IsOffline), + ), + )) + } else { + SameState(self) + } + } Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter( shared_values, ( diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index f34c0e6667..df0a00744e 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -211,6 +211,21 @@ impl ConnectingState { } } } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + if is_offline { + NewState(DisconnectingState::enter( + shared_values, + ( + self.close_handle, + self.tunnel_close_event, + AfterDisconnect::Block(BlockReason::IsOffline), + ), + )) + } else { + SameState(self) + } + } Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter( shared_values, ( @@ -300,6 +315,9 @@ impl TunnelState for ConnectingState { shared_values: &mut SharedTunnelStateValues, retry_attempt: u32, ) -> (TunnelStateWrapper, TunnelStateTransition) { + if shared_values.is_offline { + return BlockedState::enter(shared_values, BlockReason::IsOffline); + } match shared_values .tunnel_parameters_generator .generate(retry_attempt) diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index b1819af3f0..60444e9cc7 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -45,6 +45,10 @@ impl TunnelState for DisconnectedState { shared_values.allow_lan = allow_lan; SameState(self) } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + SameState(self) + } Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)), Ok(TunnelCommand::Block(reason)) => { NewState(BlockedState::enter(shared_values, reason)) diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 339c6e3aa3..7ebb637d2d 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -33,6 +33,10 @@ impl DisconnectingState { shared_values.allow_lan = allow_lan; AfterDisconnect::Nothing } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + AfterDisconnect::Nothing + } Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0), Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason), _ => AfterDisconnect::Nothing, @@ -42,6 +46,14 @@ impl DisconnectingState { shared_values.allow_lan = allow_lan; AfterDisconnect::Block(reason) } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + if !is_offline && reason == BlockReason::IsOffline { + AfterDisconnect::Reconnect(0) + } else { + AfterDisconnect::Block(reason) + } + } Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0), Ok(TunnelCommand::Disconnect) => AfterDisconnect::Nothing, Ok(TunnelCommand::Block(new_reason)) => AfterDisconnect::Block(new_reason), @@ -52,6 +64,14 @@ impl DisconnectingState { shared_values.allow_lan = allow_lan; AfterDisconnect::Reconnect(retry_attempt) } + Ok(TunnelCommand::IsOffline(is_offline)) => { + shared_values.is_offline = is_offline; + if is_offline { + AfterDisconnect::Block(BlockReason::IsOffline) + } else { + AfterDisconnect::Reconnect(retry_attempt) + } + } Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(retry_attempt), Ok(TunnelCommand::Disconnect) | Err(_) => AfterDisconnect::Nothing, Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason), diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 6582eb0fbc..e44ec91234 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -24,8 +24,7 @@ use self::connected_state::{ConnectedState, ConnectedStateBootstrap}; use self::connecting_state::ConnectingState; use self::disconnected_state::DisconnectedState; use self::disconnecting_state::{AfterDisconnect, DisconnectingState}; -use super::mpsc::IntoSender; -use super::security::NetworkSecurity; +use crate::{mpsc::IntoSender, offline, security::NetworkSecurity}; error_chain! { errors { @@ -55,11 +54,15 @@ where T: From<TunnelStateTransition> + Send + 'static, { let (command_tx, command_rx) = mpsc::unbounded(); - let (startup_result_tx, startup_result_rx) = sync_mpsc::channel(); + offline::spawn_monitor(command_tx.clone()) + .chain_err(|| "Unable to spawn offline state monitor")?; + let is_offline = offline::is_offline(); + let (startup_result_tx, startup_result_rx) = sync_mpsc::channel(); thread::spawn(move || { match create_event_loop( allow_lan, + is_offline, tunnel_parameters_generator, log_dir, resource_dir, @@ -94,6 +97,7 @@ where fn create_event_loop<T>( allow_lan: bool, + is_offline: bool, tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, @@ -107,6 +111,7 @@ where let reactor = Core::new().chain_err(|| ErrorKind::ReactorError)?; let state_machine = TunnelStateMachine::new( allow_lan, + is_offline, tunnel_parameters_generator, log_dir, resource_dir, @@ -127,6 +132,8 @@ where pub enum TunnelCommand { /// Enable or disable LAN access in the firewall. AllowLan(bool), + /// Notify the state machine of the connectivity of the device. + IsOffline(bool), /// Open tunnel connection. Connect, /// Close tunnel connection. @@ -161,6 +168,7 @@ struct TunnelStateMachine { impl TunnelStateMachine { fn new( allow_lan: bool, + is_offline: bool, tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, @@ -172,6 +180,7 @@ impl TunnelStateMachine { let mut shared_values = SharedTunnelStateValues { security, allow_lan, + is_offline, tunnel_parameters_generator: Box::new(tunnel_parameters_generator), log_dir, resource_dir, @@ -249,6 +258,8 @@ struct SharedTunnelStateValues { security: NetworkSecurity, /// Should LAN access be allowed outside the tunnel. allow_lan: bool, + /// True when the computer is known to be offline. + is_offline: bool, /// The generator of new `TunnelParameter`s tunnel_parameters_generator: Box<dyn TunnelParametersGenerator>, /// Directory to store tunnel log file. |
