diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-09-24 15:11:24 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-10-04 09:54:13 +0200 |
| commit | b71ec360998a29f08c1220de627d078fac575b7c (patch) | |
| tree | 63425d1432f7feaa9adbcb725fea9fc25f77d0a8 | |
| parent | b6bb6b5ba7a1c6172b2f7cf5a98841a19c5cd092 (diff) | |
| download | mullvadvpn-b71ec360998a29f08c1220de627d078fac575b7c.tar.xz mullvadvpn-b71ec360998a29f08c1220de627d078fac575b7c.zip | |
Route unexpected primary interface traffic via VPN utun
| -rw-r--r-- | Cargo.lock | 4 | ||||
| -rw-r--r-- | gui/locales/messages.pot | 16 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 124 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 12 |
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 { |
