summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim@hulthe.net>2025-03-24 19:44:07 +0100
committerJoakim Hulthe <joakim.hulthe@mullvad.net>2025-04-23 15:35:48 +0200
commit4657cc02c930beccd8cd654271830701a8268b41 (patch)
tree184d7b67a7674e5e80aa7ca67c1112a413357737
parent17270fe5bd77dbcd57e102e95ba4c79ea2cb87c8 (diff)
downloadmullvadvpn-4657cc02c930beccd8cd654271830701a8268b41.tar.xz
mullvadvpn-4657cc02c930beccd8cd654271830701a8268b41.zip
Ensure that default route still exist on route events
-rw-r--r--talpid-routing/src/unix/macos/mod.rs89
1 files changed, 72 insertions, 17 deletions
diff --git a/talpid-routing/src/unix/macos/mod.rs b/talpid-routing/src/unix/macos/mod.rs
index df89767e38..7e819b3f0b 100644
--- a/talpid-routing/src/unix/macos/mod.rs
+++ b/talpid-routing/src/unix/macos/mod.rs
@@ -338,7 +338,7 @@ impl RouteManagerImpl {
self.add_route_with_record(message).await?;
}
- self.apply_tunnel_default_route().await?;
+ self.apply_tunnel_default_routes().await?;
// Add routes that use the default interface
if let Err(error) = self.apply_non_tunnel_routes().await {
@@ -423,23 +423,25 @@ impl RouteManagerImpl {
async fn refresh_routes(&mut self) -> Result<()> {
talpid_types::detect_flood!();
+ // These may set `self.unhandled_default_route_changes`
self.update_best_default_route(interface::Family::V4)?;
self.update_best_default_route(interface::Family::V6)?;
self.debug_offline();
if !self.unhandled_default_route_changes {
+ self.ensure_default_tunnel_routes_exists().await?;
return Ok(());
}
- // Remove any existing ifscope route that we've added
+ // Remove any existing ifscoped default route that we've added
self.remove_applied_routes(|route| {
route.is_ifscope() && route.is_default().unwrap_or(false)
})
.await;
// Substitute route with a tunnel route
- self.apply_tunnel_default_route().await?;
+ self.apply_tunnel_default_routes().await?;
// Update routes using default interface
self.apply_non_tunnel_routes().await?;
@@ -503,7 +505,7 @@ impl RouteManagerImpl {
/// Replace the default routes with an ifscope route, and
/// add a new default tunnel route.
- async fn apply_tunnel_default_route(&mut self) -> Result<()> {
+ async fn apply_tunnel_default_routes(&mut self) -> Result<()> {
// As long as the relay route has a way of reaching the internet, we'll want to add a tunnel
// route for both IPv4 and IPv6.
// NOTE: This is incorrect. We're assuming that any "default destination" is used for
@@ -539,22 +541,25 @@ impl RouteManagerImpl {
self.replace_with_scoped_route(family).await?;
// Make sure there is really no other unscoped default route
- let mut msg = RouteMessage::new_route(family.default_network().into());
- msg = msg.set_gateway_route(true);
- let old_route = self.routing_table.get_route(&msg).await;
- if let Ok(Some(old_route)) = old_route {
+
+ let actual_default_route = self.get_actual_default_route(family).await.unwrap_or(None);
+
+ if let Some(actual_default_route) = actual_default_route {
let tun_gateway_link_addr =
tunnel_route.gateway().and_then(|addr| addr.as_link_addr());
- let current_link_addr = old_route.gateway().and_then(|addr| addr.as_link_addr());
- if current_link_addr
- .map(|addr| Some(addr) != tun_gateway_link_addr)
- .unwrap_or(true)
- {
+ let actual_link_addr = actual_default_route
+ .gateway()
+ .and_then(|addr| addr.as_link_addr());
+
+ if actual_link_addr.is_none() || actual_link_addr != tun_gateway_link_addr {
log::trace!("Removing existing unscoped default route");
- let _ = self.routing_table.delete_route(&msg).await;
- } else if !old_route.is_ifscope() {
- // NOTE: Skipping route
- continue;
+
+ let _ = self
+ .routing_table
+ .delete_route(&default_route_msg(family))
+ .await;
+ } else if !actual_default_route.is_ifscope() {
+ continue; // Skipping route
}
}
@@ -734,6 +739,56 @@ impl RouteManagerImpl {
false
}
+
+ async fn ensure_default_tunnel_routes_exists(&mut self) -> Result<()> {
+ // TODO: ignore ipv6 if disabled?
+ for family in [interface::Family::V4, interface::Family::V6] {
+ if !self.default_route_is_tunnel_route(family).await? {
+ return self.apply_tunnel_default_routes().await;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Check if the `0.0.0.0/0`/`::/0`-route goes to our tunnel interface.
+ async fn default_route_is_tunnel_route(&mut self, family: interface::Family) -> Result<bool> {
+ let actual_default_route = self.get_actual_default_route(family).await?;
+
+ let Some(actual_default_route) = actual_default_route else {
+ return Ok(false);
+ };
+
+ let tunnel_route = match family {
+ interface::Family::V4 => self.v4_tunnel_default_route.as_ref(),
+ interface::Family::V6 => self.v6_tunnel_default_route.as_ref(),
+ };
+
+ let Some(tunnel_route) = tunnel_route else {
+ return Ok(false);
+ };
+
+ Ok(route_matches_interface(&actual_default_route, tunnel_route))
+ }
+
+ /// Get the route which goes to `0.0.0.0/0`/`::/0`, if any.
+ async fn get_actual_default_route(
+ &mut self,
+ family: interface::Family,
+ ) -> Result<Option<data::RouteMessage>> {
+ self.routing_table
+ .get_route(&default_route_msg(family))
+ .await
+ .map_err(Error::RoutingTable)
+ }
+}
+
+/// Construct a [RouteMessage] that refers to the `0.0.0.0/0` or `::/0` route with the
+/// RTF_GATEAWAY-flag set. Used to reference the default route created by macOS.
+fn default_route_msg(family: interface::Family) -> RouteMessage {
+ let mut msg = RouteMessage::new_route(family.default_network().into());
+ msg = msg.set_gateway_route(true);
+ msg
}
fn route_matches_interface(default_route: &RouteMessage, interface_route: &RouteMessage) -> bool {