diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-09-23 10:06:12 +0200 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-09-25 11:44:14 +0200 |
| commit | d0b2b24a97e55239ee73e0dc96754bda55f88e63 (patch) | |
| tree | 1558405f31ea02445c2565b12fb1728c537b5c2b /talpid-core/src | |
| parent | 78c7edb64a94dadff4782e57380ec4a69c7c7e34 (diff) | |
| download | mullvadvpn-d0b2b24a97e55239ee73e0dc96754bda55f88e63.tar.xz mullvadvpn-d0b2b24a97e55239ee73e0dc96754bda55f88e63.zip | |
Add setting to leak traffic to apple networks
Co-authored-by: David Lönnhager <david.l@mullvad.net>
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 57 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 12 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 21 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 13 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 10 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 18 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 21 |
8 files changed, 165 insertions, 1 deletions
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 78f72ab647..ef26ebb97f 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, FilterRuleAction, Uid}; +use pfctl::{DropAction, Endpoint, FilterRuleAction, Uid}; use std::{ env, io, net::{IpAddr, Ipv4Addr}, @@ -138,6 +138,7 @@ impl Firewall { new_filter_rules.append(&mut self.get_allow_loopback_rules()?); new_filter_rules.append(&mut self.get_allow_dhcp_client_rules()?); new_filter_rules.append(&mut self.get_allow_ndp_rules()?); + new_filter_rules.append(&mut self.get_policy_specific_rules(policy)?); let return_out_rule = self @@ -202,6 +203,7 @@ impl Firewall { allowed_endpoint, allowed_tunnel_traffic, redirect_interface, + apple_services_bypass, } => { let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?]; rules.push(self.get_allowed_endpoint_rule(allowed_endpoint)?); @@ -238,6 +240,10 @@ impl Firewall { rules.append(&mut self.get_allow_lan_rules()?); } + if *apple_services_bypass { + rules.append(&mut self.get_apple_services_bypass_rules()?); + } + Ok(rules) } FirewallPolicy::Connected { @@ -246,6 +252,7 @@ impl Firewall { allow_lan, dns_config, redirect_interface, + apple_services_bypass, } => { let mut rules = vec![]; @@ -270,6 +277,10 @@ 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(); @@ -288,6 +299,7 @@ impl Firewall { FirewallPolicy::Blocked { allow_lan, allowed_endpoint, + apple_services_bypass, .. } => { let mut rules = Vec::new(); @@ -301,6 +313,10 @@ impl Firewall { rules.append(&mut self.get_allow_lan_rules()?); } + if *apple_services_bypass { + rules.append(&mut self.get_apple_services_bypass_rules()?); + } + Ok(rules) } } @@ -566,6 +582,45 @@ impl Firewall { Ok(rules) } + /// Generate rules that allow traffic to the networks required for Apple push notification + /// services to work. This is a hack to get around the fact that apple services in MacOS 15 has + /// a bug where they don't respect the routing table. + /// + /// All allowed networks are part of apple-owned IP subnets. + fn get_apple_services_bypass_rules(&self) -> Result<Vec<pfctl::FilterRule>> { + // https://support.apple.com/en-us/102266 + let apple_networks: &[IpNetwork] = &[ + "17.249.0.0/16".parse().unwrap(), + "17.252.0.0/16".parse().unwrap(), + "17.57.144.0/22".parse().unwrap(), + "17.188.128.0/18".parse().unwrap(), + "17.188.20.0/23".parse().unwrap(), + "2620:149:a44::/48".parse().unwrap(), + "2403:300:a42::/48".parse().unwrap(), + "2403:300:a51::/48".parse().unwrap(), + "2a01:b740:a42::/48".parse().unwrap(), + ]; + + let apple_ports: &[u16] = &[443, 2197, 5223]; + + let mut rules = vec![]; + for &net in apple_networks { + for &port in apple_ports { + let mut rule_builder = self.create_rule_builder(FilterRuleAction::Pass); + rule_builder.quick(true); + let allow_out = rule_builder + .quick(true) + .direction(pfctl::Direction::Out) + .from(pfctl::Ip::Any) + .to(Endpoint::new(pfctl::Ip::from(net), port)) + .keep_state(pfctl::StatePolicy::Keep) + .build()?; + rules.push(allow_out); + } + } + Ok(rules) + } + fn get_split_tunnel_rules( &self, from_interface: &str, diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 8f36e905ff..2596f330e3 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -94,6 +94,10 @@ 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, }, /// Allow traffic only to server and over tunnel interface @@ -110,6 +114,10 @@ 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, }, /// Block all network traffic in and out from the computer. @@ -122,6 +130,10 @@ 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, }, } diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index a5ca3395a4..abb97d282d 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -142,6 +142,8 @@ impl ConnectedState { dns_config: Self::resolve_dns(&self.metadata, shared_values), #[cfg(target_os = "macos")] redirect_interface, + #[cfg(target_os = "macos")] + apple_services_bypass: shared_values.apple_services_bypass, } } @@ -371,6 +373,25 @@ impl ConnectedState { } SameState(self) } + + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let consequence = if shared_values.set_apple_services_bypass(apple_services_bypass) + { + match self.set_firewall_policy(shared_values) { + Ok(()) => SameState(self), + Err(error) => self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), + ), + } + } else { + SameState(self) + }; + + let _ = complete_tx.send(()); + consequence + } } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 09ba4f18e1..e5d9dcb6cb 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -172,6 +172,8 @@ impl ConnectingState { allowed_tunnel_traffic, #[cfg(target_os = "macos")] redirect_interface, + #[cfg(target_os = "macos")] + apple_services_bypass: shared_values.apple_services_bypass, }; shared_values .firewall @@ -544,6 +546,17 @@ impl ConnectingState { } SameState(self) } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let consequence = if shared_values.set_apple_services_bypass(apple_services_bypass) + { + self.reset_firewall(shared_values) + } else { + SameState(self) + }; + let _ = complete_tx.send(()); + consequence + } } } diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 9da6520f61..f7bc5e712b 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -78,6 +78,8 @@ impl DisconnectedState { allowed_endpoint: Some(shared_values.allowed_endpoint.clone()), #[cfg(target_os = "macos")] dns_redirect_port: shared_values.filtering_resolver.listening_port(), + #[cfg(target_os = "macos")] + apple_services_bypass: shared_values.apple_services_bypass, }; shared_values.firewall.apply_policy(policy).map_err(|e| { @@ -230,6 +232,14 @@ impl TunnelState for DisconnectedState { let _ = result_tx.send(shared_values.set_exclude_paths(paths).map(|_| ())); SameState(self) } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + if shared_values.set_apple_services_bypass(apple_services_bypass) { + Self::set_firewall_policy(shared_values, false); + } + let _ = complete_tx.send(()); + SameState(self) + } None => { Self::reset_dns(shared_values); Finished diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 4a108788e1..16bed626e1 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -92,6 +92,12 @@ impl DisconnectingState { let _ = result_tx.send(shared_values.set_exclude_paths(paths).map(|_| ())); AfterDisconnect::Nothing } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let _ = shared_values.set_apple_services_bypass(apple_services_bypass); + let _ = complete_tx.send(()); + AfterDisconnect::Nothing + } }, AfterDisconnect::Block(reason) => match command { Some(TunnelCommand::AllowLan(allow_lan, complete_tx)) => { @@ -149,6 +155,12 @@ impl DisconnectingState { let _ = result_tx.send(shared_values.set_exclude_paths(paths).map(|_| ())); AfterDisconnect::Block(reason) } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let _ = shared_values.set_apple_services_bypass(apple_services_bypass); + let _ = complete_tx.send(()); + AfterDisconnect::Block(reason) + } None => AfterDisconnect::Block(reason), }, AfterDisconnect::Reconnect(retry_attempt) => match command { @@ -207,6 +219,12 @@ impl DisconnectingState { let _ = result_tx.send(shared_values.set_exclude_paths(paths).map(|_| ())); AfterDisconnect::Reconnect(retry_attempt) } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let _ = shared_values.set_apple_services_bypass(apple_services_bypass); + let _ = complete_tx.send(()); + AfterDisconnect::Reconnect(retry_attempt) + } }, }; diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index eeaf48956b..14885b0a60 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -78,6 +78,8 @@ impl ErrorState { allowed_endpoint: Some(shared_values.allowed_endpoint.clone()), #[cfg(target_os = "macos")] dns_redirect_port: shared_values.filtering_resolver.listening_port(), + #[cfg(target_os = "macos")] + apple_services_bypass: shared_values.apple_services_bypass, }; #[cfg(target_os = "linux")] @@ -235,6 +237,18 @@ impl TunnelState for ErrorState { let _ = result_tx.send(shared_values.set_exclude_paths(paths).map(|_| ())); SameState(self) } + #[cfg(target_os = "macos")] + Some(TunnelCommand::AppleServicesBypass(complete_tx, apple_services_bypass)) => { + let consequence = if shared_values.set_apple_services_bypass(apple_services_bypass) + { + let _ = Self::set_firewall_policy(shared_values); + SameState(self) + } else { + SameState(self) + }; + let _ = complete_tx.send(()); + consequence + } } } } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index e0ef07850d..052dd74a49 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -104,6 +104,9 @@ pub struct InitialTunnelState { /// Apps to exclude from the tunnel. #[cfg(target_os = "android")] pub exclude_paths: Vec<String>, + /// Whether we should leak traffic to Apple services. + #[cfg(target_os = "macos")] + pub apple_services_bypass: bool, } /// Identifiers for various network resources that should be unique to a given instance of a tunnel @@ -214,6 +217,9 @@ pub enum TunnelCommand { oneshot::Sender<Result<(), split_tunnel::Error>>, Vec<String>, ), + /// Set if we should leak traffic to Apple services. + #[cfg(target_os = "macos")] + AppleServicesBypass(oneshot::Sender<()>, bool), } type TunnelCommandReceiver = stream::Fuse<mpsc::UnboundedReceiver<TunnelCommand>>; @@ -383,6 +389,8 @@ impl TunnelStateMachine { connectivity_check_was_enabled: None, #[cfg(target_os = "macos")] filtering_resolver, + #[cfg(target_os = "macos")] + apple_services_bypass: args.settings.apple_services_bypass, }; tokio::task::spawn_blocking(move || { @@ -486,6 +494,9 @@ struct SharedTunnelStateValues { /// Filtering resolver handle #[cfg(target_os = "macos")] filtering_resolver: crate::resolver::ResolverHandle, + + #[cfg(target_os = "macos")] + apple_services_bypass: bool, } impl SharedTunnelStateValues { @@ -652,6 +663,16 @@ impl SharedTunnelStateValues { } } } + + #[cfg(target_os = "macos")] + pub fn set_apple_services_bypass(&mut self, apple_services_bypass: bool) -> bool { + if self.apple_services_bypass != apple_services_bypass { + self.apple_services_bypass = apple_services_bypass; + true + } else { + false + } + } } /// Asynchronous result of an attempt to progress a state. |
