summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-09-24 15:11:24 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-10-04 09:54:13 +0200
commitb71ec360998a29f08c1220de627d078fac575b7c (patch)
tree63425d1432f7feaa9adbcb725fea9fc25f77d0a8
parentb6bb6b5ba7a1c6172b2f7cf5a98841a19c5cd092 (diff)
downloadmullvadvpn-b71ec360998a29f08c1220de627d078fac575b7c.tar.xz
mullvadvpn-b71ec360998a29f08c1220de627d078fac575b7c.zip
Route unexpected primary interface traffic via VPN utun
-rw-r--r--Cargo.lock4
-rw-r--r--gui/locales/messages.pot16
-rw-r--r--talpid-core/Cargo.toml2
-rw-r--r--talpid-core/src/firewall/macos.rs124
-rw-r--r--talpid-core/src/firewall/mod.rs12
5 files changed, 117 insertions, 41 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c34a60d2fb..79cdfc518f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3015,9 +3015,9 @@ dependencies = [
[[package]]
name = "pfctl"
-version = "0.6.0"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10824597de43012ff83ed8b044fd3aeeb3b93107e80303a86649c86742e27c5a"
+checksum = "a44e65c0d3523afa79a600a3964c3ac0fabdabe2d7c68da624b2bb0b441b9d61"
dependencies = [
"derive_builder",
"ioctl-sys 0.8.0",
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index 72e70ee4f1..b60044134e 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -1527,26 +1527,10 @@ msgid "App version"
msgstr ""
msgctxt "settings-view"
-msgid "Apple services bypass"
-msgstr ""
-
-msgctxt "settings-view"
-msgid "Attention: this traffic will go outside of the VPN tunnel. Any application that tries to can bypass the VPN tunnel and send traffic to these Apple networks."
-msgstr ""
-
-msgctxt "settings-view"
-msgid "Enabling this setting allows traffic to specific Apple-owned networks to go outside of the VPN tunnel, allowing services like iMessage and FaceTime to work whilst using Mullvad."
-msgstr ""
-
-msgctxt "settings-view"
msgid "Multihop"
msgstr ""
msgctxt "settings-view"
-msgid "Some Apple services have an issue where the network settings set by Mullvad get ignored, this in turn blocks certain apps."
-msgstr ""
-
-msgctxt "settings-view"
msgid "Support"
msgstr ""
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index a0ddb45825..3207d485cb 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -47,7 +47,7 @@ duct = "0.13"
[target.'cfg(target_os = "macos")'.dependencies]
async-trait = "0.1"
duct = "0.13"
-pfctl = "0.6.0"
+pfctl = "0.6.1"
subslice = "0.2"
system-configuration = "0.5.1"
hickory-proto = "0.24.1"
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs
index 2dddc2381e..2a4c9c9a8b 100644
--- a/talpid-core/src/firewall/macos.rs
+++ b/talpid-core/src/firewall/macos.rs
@@ -1,7 +1,7 @@
use super::{FirewallArguments, FirewallPolicy};
use ipnetwork::IpNetwork;
use libc::{c_int, sysctlbyname};
-use pfctl::{DropAction, Endpoint, FilterRuleAction, Uid};
+use pfctl::{DropAction, FilterRuleAction, Ip, Uid};
use std::{
env, io,
net::{IpAddr, Ipv4Addr},
@@ -109,6 +109,16 @@ impl Firewall {
return Ok(false);
}
+ if policy.allow_lan() {
+ let net_is_lan = ALLOWED_LAN_NETS
+ .iter()
+ .chain(ALLOWED_LAN_MULTICAST_NETS.iter())
+ .any(|net| net.contains(remote_address.ip()));
+ if net_is_lan {
+ return Ok(false);
+ }
+ }
+
let Some(peer) = policy.peer_endpoint().map(|endpoint| endpoint.endpoint) else {
// If there's no peer, there's also no tunnel. We have no states to preserve
return Ok(true);
@@ -169,7 +179,10 @@ impl Firewall {
anchor_change.set_scrub_rules(Self::get_scrub_rules()?);
anchor_change.set_filter_rules(new_filter_rules);
anchor_change.set_redirect_rules(self.get_dns_redirect_rules(policy)?);
- self.pf.set_rules(ANCHOR_NAME, anchor_change)
+ anchor_change.set_nat_rules(self.get_nat_rules(policy)?);
+ self.pf.set_rules(ANCHOR_NAME, anchor_change)?;
+
+ Ok(())
}
fn get_scrub_rules() -> Result<Vec<pfctl::ScrubRule>> {
@@ -207,6 +220,74 @@ impl Firewall {
Ok(redirect_rules)
}
+ fn get_nat_rules(&mut self, policy: &FirewallPolicy) -> Result<Vec<pfctl::NatRule>> {
+ let (FirewallPolicy::Connected {
+ peer_endpoint,
+ tunnel,
+ ..
+ }
+ | FirewallPolicy::Connecting {
+ peer_endpoint,
+ tunnel: Some(tunnel),
+ ..
+ }) = policy
+ else {
+ return Ok(vec![]);
+ };
+
+ let mut rules = vec![];
+
+ // no nat from/to localhost
+ let no_nat_localhost = pfctl::NatRuleBuilder::default()
+ .interface("lo0")
+ .action(pfctl::NatRuleAction::NoNat)
+ .build()?;
+ rules.push(no_nat_localhost);
+
+ // no nat to LAN nets
+ for net in ALLOWED_LAN_NETS
+ .iter()
+ .chain(ALLOWED_LAN_MULTICAST_NETS.iter())
+ {
+ let rule = pfctl::NatRuleBuilder::default()
+ .action(pfctl::NatRuleAction::NoNat)
+ .to(pfctl::Ip::from(*net))
+ .build()?;
+ rules.push(rule);
+ }
+
+ // no nat to [vpn ip]
+ let no_nat_to_vpn_server = pfctl::NatRuleBuilder::default()
+ .action(pfctl::NatRuleAction::NoNat)
+ .to(peer_endpoint.endpoint.address.ip())
+ .build()?;
+ rules.push(no_nat_to_vpn_server);
+
+ // no nat on [tun interface]
+ let no_nat_on_tun = pfctl::NatRuleBuilder::default()
+ .action(pfctl::NatRuleAction::NoNat)
+ .interface(&tunnel.interface)
+ .build()?;
+ rules.push(no_nat_on_tun);
+
+ // Masquerade other traffic via VPN utun
+ for ip in &tunnel.ips {
+ // nat from {inet,inet6} any to any -> [tun ip]
+ let nat_primary_to_tun = pfctl::NatRuleBuilder::default()
+ .action(pfctl::NatRuleAction::Nat {
+ nat_to: pfctl::NatEndpoint::from(pfctl::Ip::from(*ip)),
+ })
+ .from(Ip::Net(match ip {
+ IpAddr::V4(_) => "0.0.0.0/0".parse().unwrap(),
+ IpAddr::V6(_) => "::/0".parse().unwrap(),
+ }))
+ .build()?;
+ rules.push(nat_primary_to_tun);
+ }
+
+ Ok(rules)
+ }
+
fn get_policy_specific_rules(
&mut self,
policy: &FirewallPolicy,
@@ -295,10 +376,6 @@ impl Firewall {
rules.append(&mut self.get_allow_lan_rules()?);
}
- if *apple_services_bypass {
- rules.append(&mut self.get_apple_services_bypass_rules()?);
- }
-
if let Some(redirect_interface) = redirect_interface {
enable_forwarding();
@@ -306,6 +383,7 @@ impl Firewall {
&mut self.get_split_tunnel_rules(&tunnel.interface, redirect_interface)?,
);
} else {
+ rules.push(self.route_everything_to(&tunnel.interface)?);
rules.extend(self.get_allow_tunnel_rules(
tunnel.interface.as_str(),
&AllowedTunnelTraffic::All,
@@ -340,6 +418,19 @@ impl Firewall {
}
}
+ /// Route outbound traffic to the selected interface
+ fn route_everything_to(&self, interface: &str) -> Result<pfctl::FilterRule> {
+ self.create_rule_builder(FilterRuleAction::Pass)
+ .quick(true)
+ .direction(pfctl::Direction::Out)
+ .route(pfctl::Route::RouteTo(pfctl::PoolAddr::from(
+ pfctl::Interface::from(&interface),
+ )))
+ .keep_state(pfctl::StatePolicy::Keep)
+ .tcp_flags(Self::get_tcp_flags())
+ .build()
+ }
+
fn get_allow_local_dns_rules_when_connected(
&self,
tunnel: &crate::tunnel::TunnelMetadata,
@@ -554,6 +645,7 @@ impl Firewall {
let allow_out = rule_builder
.direction(pfctl::Direction::Out)
.from(pfctl::Ip::Any)
+ .keep_state(pfctl::StatePolicy::Keep)
.to(pfctl::Ip::from(*net))
.build()?;
let allow_in = rule_builder
@@ -658,17 +750,7 @@ impl Firewall {
.keep_state(pfctl::StatePolicy::Keep)
.interface(to_interface)
.build()?;
- let redir_rule = self
- .create_rule_builder(FilterRuleAction::Pass)
- .quick(true)
- .direction(pfctl::Direction::Out)
- .route(pfctl::Route::RouteTo(pfctl::PoolAddr::from(
- pfctl::Interface::from(to_interface),
- )))
- .keep_state(pfctl::StatePolicy::Keep)
- .tcp_flags(Self::get_tcp_flags())
- .interface(from_interface)
- .build()?;
+ let redir_rule = self.route_everything_to(to_interface)?;
Ok(vec![tunnel_rule, allow_rule, redir_rule])
}
@@ -861,11 +943,13 @@ impl Firewall {
fn add_anchor(&mut self) -> Result<()> {
self.pf
+ .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Scrub)?;
+ self.pf
+ .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Nat)?;
+ self.pf
.try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?;
self.pf
.try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)?;
- self.pf
- .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Scrub)?;
Ok(())
}
@@ -873,6 +957,8 @@ impl Firewall {
self.pf
.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Scrub)?;
self.pf
+ .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Nat)?;
+ self.pf
.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)?;
self.pf
.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?;
diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs
index af3db8dcb3..ee5ea3aaa0 100644
--- a/talpid-core/src/firewall/mod.rs
+++ b/talpid-core/src/firewall/mod.rs
@@ -94,7 +94,6 @@ pub enum FirewallPolicy {
/// Interface to redirect (VPN tunnel) traffic to
#[cfg(target_os = "macos")]
redirect_interface: Option<String>,
-
/// Flag setting if we should leak traffic to apple services.
#[cfg(target_os = "macos")]
apple_services_bypass: bool,
@@ -118,7 +117,6 @@ pub enum FirewallPolicy {
/// Interface to redirect (VPN tunnel) traffic to
#[cfg(target_os = "macos")]
redirect_interface: Option<String>,
-
/// Flag setting if we should leak traffic to apple services.
#[cfg(target_os = "macos")]
apple_services_bypass: bool,
@@ -138,7 +136,6 @@ pub enum FirewallPolicy {
/// be redirected to `127.0.0.1:$dns_redirect_port`.
#[cfg(target_os = "macos")]
dns_redirect_port: u16,
-
/// Flag setting if we should leak traffic to apple services.
#[cfg(target_os = "macos")]
apple_services_bypass: bool,
@@ -178,6 +175,15 @@ impl FirewallPolicy {
_ => &AllowedTunnelTraffic::None,
}
}
+
+ /// Return whether LAN traffic is allowed
+ pub fn allow_lan(&self) -> bool {
+ match self {
+ FirewallPolicy::Connecting { allow_lan, .. }
+ | FirewallPolicy::Connected { allow_lan, .. }
+ | FirewallPolicy::Blocked { allow_lan, .. } => *allow_lan,
+ }
+ }
}
impl fmt::Display for FirewallPolicy {