summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2018-11-09 12:59:01 +0100
committerLinus Färnstrand <linus@mullvad.net>2018-11-09 12:59:01 +0100
commit900833dedc44c99aa235a11d6daf62feeddd7c0f (patch)
treeb71cdfc62135f203123ab38512c959f1d5444b80
parent6470f1c4651b3041144f43728af12b95d29d2750 (diff)
parentb81d8e748f1b31bc9b156d28e42e5383faad4314 (diff)
downloadmullvadvpn-900833dedc44c99aa235a11d6daf62feeddd7c0f.tar.xz
mullvadvpn-900833dedc44c99aa235a11d6daf62feeddd7c0f.zip
Merge branch 'add-macos-offline-state'
-rw-r--r--CHANGELOG.md4
-rw-r--r--gui/packages/desktop/src/renderer/components/NotificationArea.js2
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js4
-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
-rw-r--r--talpid-types/src/tunnel.rs22
15 files changed, 214 insertions, 18 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f4795142d..1cd4059435 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,10 @@ Line wrap the file at 100 chars. Th
- Allow DHCPv6 in the firewall.
- CLI command `relay update` that triggers an update of the relay list in the daemon.
+# macOS
+- Detect if the computer is offline. If so, don't sit in a reconnect loop, instead block and show
+ an error message.
+
### Fixed
- Pick new random relay for each reconnect attempt instead of just retrying with the same one.
- Make the `problem-report` tool fall back to the bundled API IP if DNS resolution fails.
diff --git a/gui/packages/desktop/src/renderer/components/NotificationArea.js b/gui/packages/desktop/src/renderer/components/NotificationArea.js
index df8c63706c..936a29b5d0 100644
--- a/gui/packages/desktop/src/renderer/components/NotificationArea.js
+++ b/gui/packages/desktop/src/renderer/components/NotificationArea.js
@@ -45,6 +45,8 @@ function getBlockReasonMessage(blockReason: BlockReason): string {
return 'Failed to start tunnel connection';
case 'no_matching_relay':
return 'No relay server matches the current settings';
+ case 'is_offline':
+ return 'This device is offline, no tunnels can be established';
default:
return `Unknown error: ${(blockReason.reason: empty)}`;
}
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
index 1b2bb0523e..5417ec4f21 100644
--- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
+++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
@@ -50,7 +50,8 @@ export type BlockReason =
| 'ipv6_unavailable'
| 'set_security_policy_error'
| 'start_tunnel_error'
- | 'no_matching_relay',
+ | 'no_matching_relay'
+ | 'is_offline',
}
| { reason: 'auth_failed', details: ?string };
@@ -310,6 +311,7 @@ const TunnelStateTransitionSchema = oneOf(
'set_security_policy_error',
'start_tunnel_error',
'no_matching_relay',
+ 'is_offline',
),
}),
object({
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.
diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs
index cfe1472cdf..8614f09ad0 100644
--- a/talpid-types/src/tunnel.rs
+++ b/talpid-types/src/tunnel.rs
@@ -52,14 +52,17 @@ pub enum BlockReason {
StartTunnelError,
/// No relay server matching the current filter parameters.
NoMatchingRelay,
+ /// This device is offline, no tunnels can be established.
+ IsOffline,
}
impl fmt::Display for BlockReason {
- fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::BlockReason::*;
let description = match *self {
- BlockReason::AuthFailed(ref reason) => {
+ AuthFailed(ref reason) => {
return write!(
- formatter,
+ f,
"Authentication with remote server failed: {}",
match reason {
Some(ref reason) => reason.as_str(),
@@ -67,14 +70,13 @@ impl fmt::Display for BlockReason {
}
);
}
- BlockReason::Ipv6Unavailable => {
- "Failed to configure IPv6 because it's disabled in the platform"
- }
- BlockReason::SetSecurityPolicyError => "Failed to set security policy",
- BlockReason::StartTunnelError => "Failed to start connection to remote server",
- BlockReason::NoMatchingRelay => "No relay server matches the current settings",
+ Ipv6Unavailable => "Failed to configure IPv6 because it's disabled in the platform",
+ SetSecurityPolicyError => "Failed to set security policy",
+ StartTunnelError => "Failed to start connection to remote server",
+ NoMatchingRelay => "No relay server matches the current settings",
+ IsOffline => "This device is offline, no tunnels can be established",
};
- write!(formatter, "{}", description)
+ write!(f, "{}", description)
}
}