summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim.hulthe@mullvad.net>2024-09-23 10:06:12 +0200
committerJoakim Hulthe <joakim.hulthe@mullvad.net>2024-09-25 11:44:14 +0200
commitd0b2b24a97e55239ee73e0dc96754bda55f88e63 (patch)
tree1558405f31ea02445c2565b12fb1728c537b5c2b /talpid-core/src
parent78c7edb64a94dadff4782e57380ec4a69c7c7e34 (diff)
downloadmullvadvpn-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.rs57
-rw-r--r--talpid-core/src/firewall/mod.rs12
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs21
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs13
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs10
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs18
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs14
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs21
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.