summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2020-06-09 15:23:04 +0100
committerEmīls <emils@mullvad.net>2020-06-09 15:23:04 +0100
commit68d2730eaa9f3fce7d914aab91132f30ea34297e (patch)
tree9496d4ddfa0622f631320eb9f0ce85692aab55f6
parentdeaf23daa2e3aa2a3ece934481d105e0e46c99bc (diff)
parent1645577dc1e76adb2d87734ab1381b8cc6a5a84c (diff)
downloadmullvadvpn-68d2730eaa9f3fce7d914aab91132f30ea34297e.tar.xz
mullvadvpn-68d2730eaa9f3fce7d914aab91132f30ea34297e.zip
Merge branch 'macos-use-network-reachability'
-rw-r--r--CHANGELOG.md3
-rw-r--r--Cargo.lock33
-rw-r--r--talpid-core/Cargo.toml4
-rw-r--r--talpid-core/src/offline/macos.rs195
4 files changed, 175 insertions, 60 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a86a29045a..6845ea5c59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,9 @@ Line wrap the file at 100 chars. Th
- Send an ICMP reject message or TCP reset packet when blocking outgoing packets to prevent
timeouts.
+#### macOS
+- Use `SCNetworkReachability` to help determine connectivity of host.
+
#### Android
- Show the remaining account time in the Settings screen in days if it's less than 3 months.
- Prevent commands to connect or disconnect to be sent when the device is locked.
diff --git a/Cargo.lock b/Cargo.lock
index df894cd109..6de2bee9a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -307,15 +307,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "core-foundation"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -325,11 +316,6 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "core-foundation-sys"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2780,19 +2766,20 @@ dependencies = [
[[package]]
name = "system-configuration"
-version = "0.3.0"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "system-configuration-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "system-configuration-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "system-configuration-sys"
-version = "0.3.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -2838,7 +2825,7 @@ dependencies = [
"rtnetlink 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"shell-escape 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "system-configuration 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "system-configuration 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"talpid-ipc 0.1.0",
"talpid-types 0.1.0",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3912,9 +3899,7 @@ dependencies = [
"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03"
"checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
-"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
-"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71"
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
@@ -4158,8 +4143,8 @@ dependencies = [
"checksum syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04c48f32867b6114449155b2a82114b86d4b09e1bddb21c47ff104ab9172b646"
"checksum syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd49988e52451813c61fecbe9abb5cfd4e1b7bb6cdbb980a6fbcbab859171a6"
"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d"
-"checksum system-configuration 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df3dc3e701a89dd6764083d19f048b57ec01c26d0904ff8108a507059a6462e6"
-"checksum system-configuration-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bfd906a2882d54084bfdf517bf03892ac06820f1c0a3d37e48609f334798ad99"
+"checksum system-configuration 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4bc0637a2b8c0b1a5145cca3e21b707865edc7e32285771536af1ade129468"
+"checksum system-configuration-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "269e271436d8e4bb2621c535a11fe03d5d012f74b19af72f80288f3a72f6180a"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 163eb641c7..f18c867cfe 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -19,7 +19,7 @@ ipnetwork = "0.16"
jsonrpc-core = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" }
jsonrpc-macros = { git = "https://github.com/mullvad/jsonrpc", branch = "mullvad-fork" }
lazy_static = "1.0"
-libc = "0.2.20"
+libc = "0.2"
log = "0.4"
openvpn-plugin = { git = "https://github.com/mullvad/openvpn-plugin-rs", branch = "auth-failed-event", features = ["serde"] }
os_pipe = "0.8"
@@ -69,7 +69,7 @@ tun = "0.4.3"
[target.'cfg(target_os = "macos")'.dependencies]
pfctl = "0.4"
-system-configuration = "0.3"
+system-configuration = "0.4"
tun = "0.4.3"
diff --git a/talpid-core/src/offline/macos.rs b/talpid-core/src/offline/macos.rs
index fa0cd3c906..602da6cda9 100644
--- a/talpid-core/src/offline/macos.rs
+++ b/talpid-core/src/offline/macos.rs
@@ -1,58 +1,196 @@
use crate::tunnel_state_machine::TunnelCommand;
use futures01::sync::mpsc::UnboundedSender;
-use log::{debug, trace};
use std::{
- sync::{mpsc, Weak},
+ net::{Ipv4Addr, SocketAddr},
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ mpsc, Arc, Weak,
+ },
thread,
};
use system_configuration::{
core_foundation::{
array::CFArray,
+ base::{CFType, TCFType, ToVoid},
+ boolean::CFBoolean,
+ dictionary::CFDictionary,
runloop::{kCFRunLoopCommonModes, CFRunLoop},
string::CFString,
},
dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext},
+ network_configuration::{self, SCNetworkInterface, SCNetworkInterfaceType},
+ network_reachability::{
+ ReachabilityFlags, SCNetworkReachability, SchedulingError, SetCallbackError,
+ },
};
const PRIMARY_INTERFACE_KEY: &str = "State:/Network/Global/IPv4";
-
#[derive(err_derive::Error, Debug)]
pub enum Error {
#[error(display = "Failed to initialize dynamic store")]
DynamicStoreInitError,
+ #[error(display = "Failed to schedule reachability callback")]
+ ScheduleReachabilityCallbackError(#[error(source)] SchedulingError),
+ #[error(display = "Failed to set reachability callback")]
+ SetCallbackError(#[error(source)] SetCallbackError),
+ #[error(display = "Panic during initialization")]
+ InitializationError,
}
pub struct MonitorHandle;
+impl MonitorHandle {
+ /// Host is considered to be offline if the IPv4 internet is considered to be unreachable by the
+ /// given reachability flags *or* there are no active physical interfaces.
+ pub fn is_offline(&self) -> bool {
+ let reachability = SCNetworkReachability::from(ipv4_internet());
+ let store = SCDynamicStoreBuilder::new("talpid-offline-check").build();
+ reachability
+ .reachability()
+ .map(|flags| check_offline_state(&store, flags))
+ .unwrap_or(false)
+ }
+}
+
pub fn spawn_monitor(sender: Weak<UnboundedSender<TunnelCommand>>) -> Result<MonitorHandle, Error> {
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");
+ thread::spawn(move || {
+ let mut reachability_ref = SCNetworkReachability::from(ipv4_internet());
+ let store = SCDynamicStoreBuilder::new("talpid-offline-watcher").build();
+
+ let is_currently_offline = match reachability_ref.reachability() {
+ Ok(flags) => check_offline_state(&store, flags),
+ Err(_) => {
+ log::error!("Failed to obtain current connectivity, assuming machine is online");
+ false
+ }
+ };
+
+ let context = OfflineStateContext {
+ sender,
+ is_offline: Arc::new(AtomicBool::new(is_currently_offline)),
+ };
+
+
+ let result = || -> Result<SCDynamicStore, Error> {
+ let dynamic_store = create_dynamic_store(context.clone())?;
+ CFRunLoop::get_current().add_source(&dynamic_store.create_run_loop_source(), unsafe {
+ kCFRunLoopCommonModes
+ });
+
+ reachability_ref.set_callback(move |flags| {
+ let store = SCDynamicStoreBuilder::new("talpid-offline-watcher").build();
+ context.new_state(check_offline_state(&store, flags));
+ })?;
+
+ reachability_ref.schedule_with_runloop(&CFRunLoop::get_current(), unsafe {
+ kCFRunLoopCommonModes
+ })?;
+
+
+ Ok(dynamic_store)
+ };
+
+
+ match result() {
+ Ok(_dynamic_store) => {
+ let _ = result_tx.send(Ok(()));
+ CFRunLoop::run_current()
+ }
+ Err(err) => {
+ let _ = result_tx.send(Err(err));
+ }
}
- Err(e) => result_tx.send(Err(e)).unwrap(),
});
- result_rx.recv().unwrap().map(|_| MonitorHandle)
+
+ let _ = result_rx.recv().map_err(|_| Error::InitializationError)??;
+ Ok(MonitorHandle {})
}
-impl MonitorHandle {
- pub fn is_offline(&self) -> bool {
- let store = SCDynamicStoreBuilder::new("talpid-primary-interface").build();
- let is_offline = store.get(CFString::new(PRIMARY_INTERFACE_KEY)).is_none();
- is_offline
+fn ipv4_internet() -> SocketAddr {
+ SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0)
+}
+
+fn check_offline_state(store: &SCDynamicStore, flags: ReachabilityFlags) -> bool {
+ let is_offline =
+ !flags.contains(ReachabilityFlags::REACHABLE) || !exists_active_physical_iface(store);
+ is_offline
+}
+
+fn exists_active_physical_iface(store: &SCDynamicStore) -> bool {
+ network_configuration::get_interfaces().iter().any(|iface| {
+ let is_physical = iface_is_physical(&*iface);
+ let is_active = iface_is_active(&*iface, store);
+ let is_valid = is_physical && is_active;
+ if is_valid {
+ log::trace!(
+ "Considering interface {:?} {:?} to be active and physical",
+ iface.bsd_name(),
+ iface.display_name()
+ );
+ }
+ is_valid
+ })
+}
+
+fn iface_is_active(iface: &SCNetworkInterface, store: &SCDynamicStore) -> bool {
+ || -> Option<bool> {
+ let path = format!("State:/Network/Interface/{}/Link", iface.bsd_name()?);
+ let link_properties = store
+ .get(CFString::from(path.as_ref()))?
+ .downcast::<CFDictionary>()?;
+
+ let active_ptr = link_properties.find(CFString::from("Active").to_void())?;
+ if active_ptr.is_null() {
+ return None;
+ }
+
+ unsafe { CFType::wrap_under_get_rule(*active_ptr) }
+ .downcast::<CFBoolean>()
+ .map(Into::into)
+ }()
+ .unwrap_or(false)
+}
+
+fn iface_is_physical(iface: &SCNetworkInterface) -> bool {
+ use SCNetworkInterfaceType::*;
+ match iface.interface_type() {
+ Some(iface_type) => match iface_type {
+ Bluetooth | Modem | Serial | IrDA | Ethernet | FireWire | WWAN | IEEE80211 => true,
+ _ => false,
+ },
+ // if interface type is unknown, have to assume it provides internet
+ None => true,
}
}
-fn create_dynamic_store(
+#[derive(Clone)]
+struct OfflineStateContext {
sender: Weak<UnboundedSender<TunnelCommand>>,
-) -> Result<SCDynamicStore, Error> {
+ is_offline: Arc<AtomicBool>,
+}
+
+impl OfflineStateContext {
+ fn no_primary_interface(&self) {
+ self.new_state(true);
+ }
+
+ fn new_state(&self, is_offline: bool) {
+ if self.is_offline.swap(is_offline, Ordering::SeqCst) != is_offline {
+ if let Some(sender) = self.sender.upgrade() {
+ let cmd = TunnelCommand::IsOffline(is_offline);
+ let _ = sender.unbounded_send(cmd);
+ }
+ }
+ }
+}
+
+fn create_dynamic_store(context: OfflineStateContext) -> Result<SCDynamicStore, Error> {
let callback_context = SCDynamicStoreCallBackContext {
callout: primary_interface_change_callback,
- info: sender,
+ info: context,
};
let store = SCDynamicStoreBuilder::new("talpid-primary-interface")
@@ -63,32 +201,21 @@ fn create_dynamic_store(
let watch_patterns: CFArray<CFString> = CFArray::from_CFTypes(&[]);
if store.set_notification_keys(&watch_keys, &watch_patterns) {
- trace!("Registered for dynamic store notifications");
+ log::trace!("Registered for dynamic store notifications");
Ok(store)
} else {
Err(Error::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 Weak<UnboundedSender<TunnelCommand>>,
+ state: &mut OfflineStateContext,
) {
let is_offline = store.get(CFString::new(PRIMARY_INTERFACE_KEY)).is_none();
- debug!(
- "Computer went {}",
- if is_offline { "offline" } else { "online" }
- );
- if let Some(state) = state.upgrade() {
- let _ = state.unbounded_send(TunnelCommand::IsOffline(is_offline));
+ if is_offline {
+ log::debug!("No primary interface, considering host to be offline");
+ state.no_primary_interface();
}
}