summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
Diffstat (limited to 'talpid-core/src')
-rw-r--r--talpid-core/src/lib.rs2
-rw-r--r--talpid-core/src/offline/dummy.rs12
-rw-r--r--talpid-core/src/offline/macos.rs83
-rw-r--r--talpid-core/src/offline/mod.rs9
-rw-r--r--talpid-core/src/security/macos/dns.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/blocked_state.rs16
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs15
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs18
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs20
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs17
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.