summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2017-12-04 11:15:38 +0100
committerLinus Färnstrand <linus@mullvad.net>2017-12-04 11:15:38 +0100
commit26d9f85b9c779ca18f935df9006ca6fad037aeb7 (patch)
tree421e8a350ec907bb7dbb0c4ddf31b06a9e21d238
parent08530bb297f5f1faff0ca5f6b68d33af03b217ea (diff)
parent98ad6988a8ad239af6ce6639269801d125156088 (diff)
downloadmullvadvpn-26d9f85b9c779ca18f935df9006ca6fad037aeb7.tar.xz
mullvadvpn-26d9f85b9c779ca18f935df9006ca6fad037aeb7.zip
Merge branch 'set-dns-on-macos'
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.lock55
-rw-r--r--Cargo.toml1
-rwxr-xr-xformat.sh3
-rw-r--r--mullvad-daemon/src/main.rs4
-rw-r--r--talpid-core/Cargo.toml4
-rw-r--r--talpid-core/src/firewall/macos/dns.rs316
-rw-r--r--talpid-core/src/firewall/macos/mod.rs (renamed from talpid-core/src/firewall/macos.rs)165
-rw-r--r--talpid-core/src/firewall/mod.rs20
-rw-r--r--talpid-core/src/firewall/unix.rs3
-rw-r--r--talpid-core/src/firewall/windows.rs3
-rw-r--r--talpid-core/src/lib.rs3
12 files changed, 446 insertions, 136 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df5110dcfb..5b7d167dbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
### Changed
- Renamed daemon binary from `mullvadd` to `mullvad-daemon`.
+### Security
+- DNS leak found when using redirect firewall rules and a custom DNS forwarder. Replaced all of that
+ with strict DNS blocking firewall rules and SystemConfiguration integration where DNS settings are
+ injected to the operating system settings and constantly monitored for external changes.
+
## [2017.1-beta6] - 2017-11-09
### Added
diff --git a/Cargo.lock b/Cargo.lock
index bb9982afc7..8dd38c5b99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -191,6 +191,15 @@ dependencies = [
]
[[package]]
+name = "core-foundation"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "core-foundation-sys"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -199,6 +208,14 @@ dependencies = [
]
[[package]]
+name = "core-foundation-sys"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "crypt32-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1142,18 +1159,6 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
-name = "socket-relay"
-version = "0.1.0"
-dependencies = [
- "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
name = "stable_deref_trait"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1182,6 +1187,25 @@ dependencies = [
]
[[package]]
+name = "system-configuration"
+version = "0.1.0"
+source = "git+https://github.com/mullvad/system-configuration-rs#cfda5c348acbbc9ca9c9f621c29d2661604b4af9"
+dependencies = [
+ "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "system-configuration-sys 0.1.0 (git+https://github.com/mullvad/system-configuration-rs)",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.1.0"
+source = "git+https://github.com/mullvad/system-configuration-rs#cfda5c348acbbc9ca9c9f621c29d2661604b4af9"
+dependencies = [
+ "bindgen 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "take"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1190,6 +1214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "talpid-core"
version = "0.1.0"
dependencies = [
+ "core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"duct 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 7.1.1 (git+https://github.com/paritytech/jsonrpc?tag=v7.1.1)",
@@ -1200,7 +1225,7 @@ dependencies = [
"openvpn-plugin 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pfctl 0.1.0 (git+https://github.com/mullvad/pfctl-rs?rev=dae436f6ee4e3529fc995c5192b314f1cc8dccec)",
"shell-escape 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "socket-relay 0.1.0",
+ "system-configuration 0.1.0 (git+https://github.com/mullvad/system-configuration-rs)",
"talpid-ipc 0.1.0",
"talpid-types 0.1.0",
"tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1507,7 +1532,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180"
"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
+"checksum core-foundation 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8047f547cd6856d45b1cdd75ef8d2f21f3d0e4bf1dab0a0041b0ae9a5dda9c0e"
"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
+"checksum core-foundation-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "152195421a2e6497a8179195672e9d4ee8e45ed8c465b626f1606d27a08ebcd5"
"checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec"
"checksum ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df391ea008fca636e41e40863a0b39a850e2ab26b0cdeed0c3657fd05a66d44c"
"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
@@ -1614,6 +1641,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
+"checksum system-configuration 0.1.0 (git+https://github.com/mullvad/system-configuration-rs)" = "<none>"
+"checksum system-configuration-sys 0.1.0 (git+https://github.com/mullvad/system-configuration-rs)" = "<none>"
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
diff --git a/Cargo.toml b/Cargo.toml
index 0082f61236..1bed44a013 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,5 +7,4 @@ members = [
"talpid-openvpn-plugin",
"talpid-core",
"talpid-ipc",
- "socket-relay",
]
diff --git a/format.sh b/format.sh
index 1603a29b3f..a0765bd03e 100755
--- a/format.sh
+++ b/format.sh
@@ -5,7 +5,7 @@
set -u
-VERSION="0.2.15"
+VERSION="0.2.16"
CMD="rustfmt"
INSTALL_CMD="cargo install --vers $VERSION --force rustfmt-nightly"
@@ -14,6 +14,7 @@ function correct_rustfmt() {
echo "$CMD is not installed" >&2
return 1
fi
+ export DYLD_LIBRARY_PATH=$(rustc --print sysroot)/lib
local installed_version=$($CMD --version | cut -d'-' -f1)
if [[ "$installed_version" != "$VERSION" ]]; then
echo "Wrong version of $CMD installed. Expected $VERSION, got $installed_version" >&2
diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs
index 2adcb0a9b7..c1c7aa4980 100644
--- a/mullvad-daemon/src/main.rs
+++ b/mullvad-daemon/src/main.rs
@@ -562,8 +562,8 @@ impl Daemon {
Ok(())
}
(TargetState::Unsecured, TunnelState::NotRunning) => self.reset_security_policy(),
- (TargetState::Unsecured, TunnelState::Connecting) |
- (TargetState::Unsecured, TunnelState::Connected) => self.kill_tunnel(),
+ (TargetState::Unsecured, TunnelState::Connecting)
+ | (TargetState::Unsecured, TunnelState::Connected) => self.kill_tunnel(),
(..) => Ok(()),
}
}
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index a7dfc01aa9..32e36c9ed2 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -24,6 +24,6 @@ libc = "0.2.20"
[target.'cfg(target_os = "macos")'.dependencies]
pfctl = { git = "https://github.com/mullvad/pfctl-rs", rev = "dae436f6ee4e3529fc995c5192b314f1cc8dccec" }
-socket-relay = { path = "../socket-relay" }
+system-configuration = { git = "https://github.com/mullvad/system-configuration-rs", version = "0.1.0" }
+core-foundation = "0.4.6"
tokio-core = "0.1"
-
diff --git a/talpid-core/src/firewall/macos/dns.rs b/talpid-core/src/firewall/macos/dns.rs
new file mode 100644
index 0000000000..abf12353ab
--- /dev/null
+++ b/talpid-core/src/firewall/macos/dns.rs
@@ -0,0 +1,316 @@
+extern crate core_foundation;
+extern crate system_configuration;
+
+use self::core_foundation::array::{CFArray, CFArrayRef};
+use self::core_foundation::base::{CFType, TCFType};
+use self::core_foundation::dictionary::CFDictionary;
+use self::core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes};
+use self::core_foundation::string::{CFString, CFStringRef};
+
+use self::system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder,
+ SCDynamicStoreCallBackContext};
+
+use error_chain::ChainedError;
+
+use std::collections::HashMap;
+use std::sync::{mpsc, Arc, Mutex};
+use std::thread;
+
+error_chain! {
+ errors {
+ SettingDnsFailed { description("Error while setting DNS servers") }
+ DynamicStoreInitError { description("Failed to initialize dynamic store") }
+ }
+}
+
+const STATE_PATH_PATTERN: &str = "State:/Network/Service/.*/DNS";
+const SETUP_PATH_PATTERN: &str = "Setup:/Network/Service/.*/DNS";
+
+type ServicePath = String;
+type DnsServer = String;
+
+struct State {
+ desired_dns: Vec<DnsServer>,
+ backup: HashMap<ServicePath, Option<Vec<DnsServer>>>,
+}
+
+pub struct DnsMonitor {
+ store: SCDynamicStore,
+
+ /// The current DNS injection state. If this is `None` it means we are not injecting any DNS.
+ /// When it's `Some(state)` we are actively making sure `state.desired_dns` is configured
+ /// on all network interfaces.
+ state: Arc<Mutex<Option<State>>>,
+}
+
+impl DnsMonitor {
+ /// Creates and returns a new `DnsMonitor`. This spawns a background thread that will monitor
+ /// DNS settings for all network interfaces. If any changes occur it will instantly reset
+ /// the DNS settings for that interface back to the last server list set to this instance
+ /// with `set_dns`.
+ pub fn new() -> Result<Self> {
+ let state = Arc::new(Mutex::new(None));
+ Self::spawn(state.clone())?;
+ Ok(DnsMonitor {
+ store: SCDynamicStoreBuilder::new("mullvad-dns").build(),
+ state,
+ })
+ }
+
+ /// Spawns the background thread running the CoreFoundation main loop and monitors the system
+ /// for DNS changes.
+ fn spawn(state: Arc<Mutex<Option<State>>>) -> Result<()> {
+ let (result_tx, result_rx) = mpsc::channel();
+ thread::spawn(move || match create_dynamic_store(state) {
+ Ok(store) => {
+ result_tx.send(Ok(())).unwrap();
+ run_dynamic_store_runloop(store);
+ // TODO(linus): This is critical. Improve later by sending error signal to Daemon
+ error!("Core Foundation main loop exited! It should run forever");
+ }
+ Err(e) => result_tx.send(Err(e)).unwrap(),
+ });
+ result_rx.recv().unwrap()
+ }
+
+ pub fn set_dns(&self, servers: Vec<DnsServer>) -> Result<()> {
+ let mut state_lock = self.state.lock().unwrap();
+ *state_lock = Some(match state_lock.take() {
+ None => {
+ debug!("Setting DNS to [{}]", servers.join(", "));
+ let backup = read_all_dns(&self.store);
+ for service_path in backup.keys() {
+ set_dns(&self.store, CFString::new(service_path), &servers)?;
+ }
+ State {
+ desired_dns: servers,
+ backup,
+ }
+ }
+ Some(state) => if servers != state.desired_dns {
+ debug!("Changing DNS to [{}]", servers.join(", "));
+ for service_path in state.backup.keys() {
+ set_dns(&self.store, CFString::new(service_path), &servers)?;
+ }
+ State {
+ desired_dns: servers,
+ backup: state.backup,
+ }
+ } else {
+ debug!("No change, new DNS same as the one already set");
+ state
+ },
+ });
+ Ok(())
+ }
+
+ /// Reset all DNS settings to the latest backed up values.
+ pub fn reset(&self) -> Result<()> {
+ let mut state_lock = self.state.lock().unwrap();
+ if let Some(state) = state_lock.take() {
+ for (service_path, servers) in state.backup {
+ if let Some(servers) = servers {
+ set_dns(&self.store, CFString::new(&service_path), &servers)?;
+ } else {
+ debug!("Removing DNS for {}", service_path);
+ if !self.store.remove(CFString::new(&service_path)) {
+ bail!(ErrorKind::SettingDnsFailed);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+/// Creates a `SCDynamicStore` that watches all network interfaces for changes to the DNS settings.
+fn create_dynamic_store(state: Arc<Mutex<Option<State>>>) -> Result<SCDynamicStore> {
+ let callback_context = SCDynamicStoreCallBackContext {
+ callout: dns_change_callback,
+ info: state,
+ };
+
+ let store = SCDynamicStoreBuilder::new("mullvad-dns-monitor")
+ .callback_context(callback_context)
+ .build();
+
+ let watch_keys: CFArray<CFString> = CFArray::from_CFTypes(&[]);
+ let watch_patterns = CFArray::from_CFTypes(&[
+ CFString::new(STATE_PATH_PATTERN),
+ CFString::new(SETUP_PATH_PATTERN),
+ ]);
+
+ 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 CFRunLoop");
+ CFRunLoop::run_current();
+}
+
+/// This function is called by the Core Foundation event loop when there is a change to one or more
+/// watched dynamic store values. In our case we watch all DNS settings.
+fn dns_change_callback(
+ store: SCDynamicStore,
+ changed_keys: CFArray<CFString>,
+ state: &mut Arc<Mutex<Option<State>>>,
+) {
+ if let Err(e) = dns_change_callback_internal(store, changed_keys, state) {
+ error!("{}", e.display_chain());
+ }
+}
+
+fn dns_change_callback_internal(
+ store: SCDynamicStore,
+ changed_keys: CFArray<CFString>,
+ state: &mut Arc<Mutex<Option<State>>>,
+) -> Result<()> {
+ let mut state_lock = state.lock().unwrap();
+ match *state_lock {
+ None => {
+ trace!("Not injecting DNS at this time");
+ }
+ Some(ref mut state) => for path_ptr in changed_keys.as_untyped().iter() {
+ let path = unsafe { CFString::wrap_under_get_rule(path_ptr as CFStringRef) };
+ let should_set_dns = match read_dns(&store, path.clone()) {
+ None => {
+ debug!("Detected DNS removed for {}", path);
+ state.backup.insert(path.to_string(), None);
+ true
+ }
+ Some(servers) => if servers != state.desired_dns {
+ debug!(
+ "Detected DNS changed to [{}] for {}",
+ servers.join(", "),
+ path
+ );
+ state.backup.insert(path.to_string(), Some(servers));
+ true
+ } else {
+ false
+ },
+ };
+ if should_set_dns {
+ set_dns(&store, path.clone(), &state.desired_dns)
+ .chain_err(|| format!("Failed changing DNS for {}", path))?;
+ // If we changed a state DNS, also set the corresponding setup DNS.
+ if let Some(setup_path_str) = state_to_setup_path(&path.to_string()) {
+ let setup_path = CFString::new(&setup_path_str);
+ if !state.backup.contains_key(&setup_path_str) {
+ state
+ .backup
+ .insert(setup_path_str, read_dns(&store, setup_path.clone()));
+ }
+ set_dns(&store, setup_path.clone(), &state.desired_dns)
+ .chain_err(|| format!("Failed changing DNS for {}", setup_path))?;
+ }
+ }
+ },
+ }
+ Ok(())
+}
+
+/// Set the dynamic store entry at `path` to a dictionary with the given servers under the
+/// "ServerAddresses" key.
+fn set_dns(store: &SCDynamicStore, path: CFString, servers: &[DnsServer]) -> Result<()> {
+ debug!("Setting DNS to [{}] for {}", servers.join(", "), path);
+ let server_addresses_key = CFString::from_static_string("ServerAddresses");
+
+ let cf_string_servers: Vec<CFString> = servers.iter().map(|s| CFString::new(s)).collect();
+ let server_addresses_value = CFArray::from_CFTypes(&cf_string_servers);
+
+ let dns_dictionary =
+ CFDictionary::from_CFType_pairs(&[(server_addresses_key, server_addresses_value)]);
+
+ if store.set(path, &dns_dictionary) {
+ Ok(())
+ } else {
+ bail!(ErrorKind::SettingDnsFailed)
+ }
+}
+
+/// Read all existing DNS settings and return them.
+fn read_all_dns(store: &SCDynamicStore) -> HashMap<ServicePath, Option<Vec<DnsServer>>> {
+ let mut backup = HashMap::new();
+ // Backup all "state" DNS, and all corresponding "setup" DNS even if they don't exist
+ if let Some(paths) = store.get_keys(STATE_PATH_PATTERN) {
+ for path_ptr in paths.as_untyped().iter() {
+ let state_path = unsafe { CFString::wrap_under_get_rule(path_ptr as CFStringRef) };
+ let state_path_str = state_path.to_string();
+ let setup_path_str = state_to_setup_path(&state_path_str).unwrap();
+ let setup_path = CFString::new(&setup_path_str);
+ backup.insert(state_path_str, read_dns(store, state_path));
+ backup.insert(setup_path_str, read_dns(store, setup_path));
+ }
+ }
+ // Backup all "setup" DNS not already covered
+ if let Some(paths) = store.get_keys(SETUP_PATH_PATTERN) {
+ for path_ptr in paths.as_untyped().iter() {
+ let setup_path = unsafe { CFString::wrap_under_get_rule(path_ptr as CFStringRef) };
+ let setup_path_str = setup_path.to_string();
+ if !backup.contains_key(&setup_path_str) {
+ backup.insert(setup_path_str, read_dns(store, setup_path));
+ }
+ }
+ }
+ backup
+}
+
+fn state_to_setup_path(state_path: &str) -> Option<String> {
+ if state_path.starts_with("State:/") {
+ Some(state_path.replacen("State:/", "Setup:/", 1))
+ } else {
+ None
+ }
+}
+
+/// Get DNS settings for a given dynamic store path. Returns `None` If the path does not exist
+/// or does not contain the expected format.
+fn read_dns(store: &SCDynamicStore, path: CFString) -> Option<Vec<DnsServer>> {
+ store
+ .get(path.clone())
+ .and_then(|property_list| property_list.downcast::<_, CFDictionary>())
+ .and_then(|dictionary| {
+ dictionary
+ .find2(&CFString::from_static_string("ServerAddresses"))
+ .map(|array_ptr| unsafe {
+ CFType::wrap_under_get_rule(array_ptr)
+ })
+ })
+ .and_then(|addresses: CFType| {
+ if addresses.instance_of::<_, CFArray>() {
+ let addresses_array = unsafe {
+ CFArray::wrap_under_get_rule(addresses.as_concrete_TypeRef() as CFArrayRef)
+ };
+ parse_cf_string_array(addresses_array)
+ } else {
+ error!("DNS settings is not an array: {:?}", addresses);
+ None
+ }
+ })
+}
+
+/// Parses a CFArray into a Rust vector of Rust strings, if the array contains CFString instances,
+/// otherwise `None` is returned.
+fn parse_cf_string_array(array: CFArray) -> Option<Vec<String>> {
+ let mut strings = Vec::new();
+ for string_ptr in array.iter() {
+ let cf_type = unsafe { CFType::wrap_under_get_rule(string_ptr) };
+ if cf_type.instance_of::<_, CFString>() {
+ let address = unsafe { CFString::wrap_under_get_rule(string_ptr as CFStringRef) };
+ strings.push(address.to_string());
+ } else {
+ error!("DNS server entry is not a string: {:?}", cf_type);
+ return None;
+ };
+ }
+ Some(strings)
+}
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos/mod.rs
index fdef40ed9e..9065a607d6 100644
--- a/talpid-core/src/firewall/macos.rs
+++ b/talpid-core/src/firewall/macos/mod.rs
@@ -1,27 +1,34 @@
-extern crate socket_relay;
+extern crate pfctl;
extern crate tokio_core;
use super::{Firewall, SecurityPolicy};
-use pfctl;
-use std::net::{IpAddr, Ipv4Addr, SocketAddr};
-use std::sync::mpsc;
-use std::thread;
+use std::net::Ipv4Addr;
-use self::socket_relay::udp::{Relay, RelayCloseHandle};
use talpid_types::net;
-use tunnel::TunnelMetadata;
-// alias used to instantiate firewall implementation
+mod dns;
+
+use self::dns::DnsMonitor;
+
+error_chain! {
+ links {
+ PfCtl(self::pfctl::Error, self::pfctl::ErrorKind) #[doc = "PF error"];
+ DnsMonitor(self::dns::Error, self::dns::ErrorKind) #[doc = "DNS error"];
+ }
+}
+
+/// alias used to instantiate firewall implementation
pub type ConcreteFirewall = PacketFilter;
-pub use pfctl::{Error, ErrorKind, Result, ResultExt};
const ANCHOR_NAME: &'static str = "mullvad";
+/// The macOS firewall implementation. Acting as converter between the `Firewall` trait API
+/// and actual PF firewall rules and other protective measures to keep the `SecurityPolicy`.
pub struct PacketFilter {
pf: pfctl::PfCtl,
pf_was_enabled: Option<bool>,
- dns_proxy_close_handle: Option<RelayCloseHandle>,
+ dns_monitor: DnsMonitor,
}
impl Firewall<Error> for PacketFilter {
@@ -29,7 +36,7 @@ impl Firewall<Error> for PacketFilter {
Ok(PacketFilter {
pf: pfctl::PfCtl::new()?,
pf_was_enabled: None,
- dns_proxy_close_handle: None,
+ dns_monitor: DnsMonitor::new()?,
})
}
@@ -40,11 +47,11 @@ impl Firewall<Error> for PacketFilter {
}
fn reset_policy(&mut self) -> Result<()> {
- self.stop_dns_proxy();
vec![
self.remove_rules(),
self.remove_anchor(),
self.restore_state(),
+ self.restore_dns(),
].into_iter()
.collect::<Result<Vec<_>>>()
.map(|_| ())
@@ -54,15 +61,12 @@ impl Firewall<Error> for PacketFilter {
impl PacketFilter {
fn set_rules(&mut self, policy: SecurityPolicy) -> Result<()> {
let mut new_filter_rules = vec![];
- let mut new_redirect_rules = vec![];
new_filter_rules.append(&mut Self::get_allow_loopback_rules()?);
new_filter_rules.append(&mut Self::get_allow_dhcp_rules()?);
- let (mut policy_filter_rules, mut policy_redirect_rules) =
- self.get_policy_specific_rules(policy)?;
+ let mut policy_filter_rules = self.get_policy_specific_rules(policy)?;
new_filter_rules.append(&mut policy_filter_rules);
- new_redirect_rules.append(&mut policy_redirect_rules);
let drop_all_rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop)
@@ -72,64 +76,59 @@ impl PacketFilter {
let mut anchor_change = pfctl::AnchorChange::new();
anchor_change.set_filter_rules(new_filter_rules);
- anchor_change.set_redirect_rules(new_redirect_rules);
- self.pf.set_rules(ANCHOR_NAME, anchor_change)
+ Ok(self.pf.set_rules(ANCHOR_NAME, anchor_change)?)
}
fn get_policy_specific_rules(
&mut self,
policy: SecurityPolicy,
- ) -> Result<(Vec<pfctl::FilterRule>, Vec<pfctl::RedirectRule>)> {
+ ) -> Result<Vec<pfctl::FilterRule>> {
match policy {
SecurityPolicy::Connecting(relay_endpoint) => {
- self.stop_dns_proxy();
- Ok((vec![Self::get_allow_relay_rule(relay_endpoint)?], vec![]))
+ Ok(vec![Self::get_allow_relay_rule(relay_endpoint)?])
}
SecurityPolicy::Connected(relay_endpoint, tunnel) => {
- let dns_proxy_listen_addr = self.start_dns_proxy(&tunnel)?;
+ self.dns_monitor.set_dns(vec![tunnel.gateway.to_string()])?;
- let allow_dns_to_relay_rule = pfctl::FilterRuleBuilder::default()
+ let allow_tcp_dns_to_relay_rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::Out)
.quick(true)
.interface(&tunnel.interface)
- .proto(pfctl::Proto::Udp)
+ .proto(pfctl::Proto::Tcp)
.to(pfctl::Endpoint::new(tunnel.gateway, 53))
.build()?;
- let reroute_dns_rule = pfctl::FilterRuleBuilder::default()
+ let allow_udp_dns_to_relay_rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::Out)
.quick(true)
- .route(pfctl::Route::route_to(pfctl::Interface::from("lo0")))
+ .interface(&tunnel.interface)
.proto(pfctl::Proto::Udp)
- .to(pfctl::Port::from(53))
+ .to(pfctl::Endpoint::new(tunnel.gateway, 53))
.build()?;
- let block_all_other_dns_rule = pfctl::FilterRuleBuilder::default()
+ let block_tcp_dns_rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop)
.direction(pfctl::Direction::Out)
.quick(true)
.proto(pfctl::Proto::Tcp)
.to(pfctl::Port::from(53))
.build()?;
-
- let dns_redirect_rule = pfctl::RedirectRuleBuilder::default()
- .action(pfctl::RedirectRuleAction::Redirect)
- .interface("lo0")
+ let block_udp_dns_rule = pfctl::FilterRuleBuilder::default()
+ .action(pfctl::FilterRuleAction::Drop)
+ .direction(pfctl::Direction::Out)
+ .quick(true)
.proto(pfctl::Proto::Udp)
.to(pfctl::Port::from(53))
- .redirect_to(dns_proxy_listen_addr)
.build()?;
- Ok((
- vec![
- allow_dns_to_relay_rule,
- reroute_dns_rule,
- block_all_other_dns_rule,
- Self::get_allow_relay_rule(relay_endpoint)?,
- Self::get_allow_tunnel_rule(tunnel.interface.as_str())?,
- ],
- vec![dns_redirect_rule],
- ))
+ Ok(vec![
+ allow_tcp_dns_to_relay_rule,
+ allow_udp_dns_to_relay_rule,
+ block_tcp_dns_rule,
+ block_udp_dns_rule,
+ Self::get_allow_relay_rule(relay_endpoint)?,
+ Self::get_allow_tunnel_rule(tunnel.interface.as_str())?,
+ ])
}
}
}
@@ -137,7 +136,7 @@ impl PacketFilter {
fn get_allow_relay_rule(relay_endpoint: net::Endpoint) -> Result<pfctl::FilterRule> {
let pfctl_proto = as_pfctl_proto(relay_endpoint.protocol);
- pfctl::FilterRuleBuilder::default()
+ Ok(pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::Out)
.to(relay_endpoint.address)
@@ -145,17 +144,17 @@ impl PacketFilter {
.keep_state(pfctl::StatePolicy::Keep)
.tcp_flags(Self::get_tcp_flags())
.quick(true)
- .build()
+ .build()?)
}
fn get_allow_tunnel_rule(tunnel_interface: &str) -> Result<pfctl::FilterRule> {
- pfctl::FilterRuleBuilder::default()
+ Ok(pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.interface(tunnel_interface)
.keep_state(pfctl::StatePolicy::Keep)
.tcp_flags(Self::get_tcp_flags())
.quick(true)
- .build()
+ .build()?)
}
fn get_allow_loopback_rules() -> Result<Vec<pfctl::FilterRule>> {
@@ -200,49 +199,43 @@ impl PacketFilter {
fn remove_rules(&mut self) -> Result<()> {
// remove_anchor() does not deactivate active rules
- self.pf.flush_rules(ANCHOR_NAME, pfctl::RulesetKind::Filter)
+ Ok(self.pf
+ .flush_rules(ANCHOR_NAME, pfctl::RulesetKind::Filter)?)
}
fn enable(&mut self) -> Result<()> {
if self.pf_was_enabled.is_none() {
self.pf_was_enabled = Some(self.pf.is_enabled()?);
}
- self.pf.try_enable()
+ Ok(self.pf.try_enable()?)
}
fn restore_state(&mut self) -> Result<()> {
match self.pf_was_enabled.take() {
- Some(true) => self.pf.try_enable(),
- Some(false) => self.pf.try_disable(),
+ Some(true) => Ok(self.pf.try_enable()?),
+ Some(false) => Ok(self.pf.try_disable()?),
None => Ok(()),
}
}
+ fn restore_dns(&self) -> Result<()> {
+ Ok(self.dns_monitor.reset()?)
+ }
+
fn add_anchor(&mut self) -> Result<()> {
self.pf
.try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?;
self.pf
- .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)
+ .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)?;
+ Ok(())
}
fn remove_anchor(&mut self) -> Result<()> {
self.pf
.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?;
self.pf
- .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)
- }
-
- fn start_dns_proxy(&mut self, tunnel: &TunnelMetadata) -> Result<SocketAddr> {
- self.stop_dns_proxy();
- let (listen_addr, close_handle) = spawn_dns_proxy(tunnel.ip, tunnel.gateway)?;
- self.dns_proxy_close_handle = Some(close_handle);
- Ok(listen_addr)
- }
-
- fn stop_dns_proxy(&mut self) {
- if let Some(close_handle) = self.dns_proxy_close_handle.take() {
- close_handle.close();
- }
+ .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)?;
+ Ok(())
}
}
@@ -252,43 +245,3 @@ fn as_pfctl_proto(protocol: net::TransportProtocol) -> pfctl::Proto {
net::TransportProtocol::Tcp => pfctl::Proto::Tcp,
}
}
-
-fn spawn_dns_proxy(
- tunnel_ip: Ipv4Addr,
- tunnel_gateway: Ipv4Addr,
-) -> Result<(SocketAddr, RelayCloseHandle)> {
- let (tx, rx) = mpsc::channel();
- thread::spawn(move || {
- match spawn_dns_proxy_helper(tunnel_ip, tunnel_gateway) {
- Ok((mut core, relay)) => {
- tx.send(Ok((relay.listen_addr(), relay.close_handle())))
- .unwrap();
- match core.run(relay) {
- Err(e) => error!("DNS proxy died with an error: {}", e),
- Ok(_) => info!("DNS proxy exiting"),
- }
- }
- Err(e) => {
- tx.send(Err(e)).unwrap();
- }
- }
- });
- rx.recv().unwrap()
-}
-
-fn spawn_dns_proxy_helper(
- tunnel_ip: Ipv4Addr,
- tunnel_gateway: Ipv4Addr,
-) -> Result<(tokio_core::reactor::Core, Relay)> {
- let core = tokio_core::reactor::Core::new().chain_err(|| "Unable to init Tokio event loop")?;
-
- let relay = Relay::new(
- "127.0.0.1:0".parse().unwrap(),
- IpAddr::V4(tunnel_ip),
- SocketAddr::from((tunnel_gateway, 53)),
- core.handle(),
- ).chain_err(|| "Unable to create DNS proxy socket relay")?;
- info!("DNS proxy listening on {}", relay.listen_addr());
-
- Ok((core, relay))
-}
diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs
index 6ac63e2493..17ba65b88d 100644
--- a/talpid-core/src/firewall/mod.rs
+++ b/talpid-core/src/firewall/mod.rs
@@ -1,16 +1,24 @@
use talpid_types::net::Endpoint;
+/// macOS implementation of the firewall/security policy enforcer.
#[cfg(target_os = "macos")]
-#[path = "macos.rs"]
-mod imp;
+pub mod macos;
+#[cfg(target_os = "macos")]
+use self::macos as imp;
+/// Linux implementation of the firewall/security policy enforcer.
+#[cfg(all(unix, not(target_os = "macos")))]
+pub mod unix;
#[cfg(all(unix, not(target_os = "macos")))]
-#[path = "unix.rs"]
-mod imp;
+use self::unix as imp;
+/// Windows implementation of the firewall/security policy enforcer.
+#[cfg(windows)]
+pub mod windows;
#[cfg(windows)]
-#[path = "windows.rs"]
-mod imp;
+use self::windows as imp;
+
+
error_chain!{
errors {
diff --git a/talpid-core/src/firewall/unix.rs b/talpid-core/src/firewall/unix.rs
index 7550c3c051..b39013e3c0 100644
--- a/talpid-core/src/firewall/unix.rs
+++ b/talpid-core/src/firewall/unix.rs
@@ -1,10 +1,11 @@
use super::{Firewall, SecurityPolicy};
-// alias used to instantiate firewall implementation
+/// alias used to instantiate firewall implementation
pub type ConcreteFirewall = Netfilter;
error_chain!{}
+/// The Linux implementation for the `Firewall` trait.
pub struct Netfilter;
impl Firewall<Error> for Netfilter {
fn new() -> Result<Self> {
diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs
index 3405ba12c0..bae506fdc5 100644
--- a/talpid-core/src/firewall/windows.rs
+++ b/talpid-core/src/firewall/windows.rs
@@ -1,10 +1,11 @@
use super::{Firewall, SecurityPolicy};
-// alias used to instantiate firewall implementation
+/// alias used to instantiate firewall implementation
pub type ConcreteFirewall = WindowsFirewall;
error_chain!{}
+/// The Windows implementation for the `Firewall` trait.
pub struct WindowsFirewall;
impl Firewall<Error> for WindowsFirewall {
fn new() -> Result<Self> {
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs
index 1af5587def..fc85584267 100644
--- a/talpid-core/src/lib.rs
+++ b/talpid-core/src/lib.rs
@@ -29,9 +29,6 @@ extern crate openvpn_plugin;
extern crate talpid_ipc;
extern crate talpid_types;
-#[cfg(target_os = "macos")]
-extern crate pfctl;
-
/// Working with processes.
pub mod process;