diff options
| author | Joakim Hulthe <joakim@hulthe.net> | 2025-03-24 19:44:07 +0100 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-04-23 15:35:48 +0200 |
| commit | 4657cc02c930beccd8cd654271830701a8268b41 (patch) | |
| tree | 184d7b67a7674e5e80aa7ca67c1112a413357737 | |
| parent | 17270fe5bd77dbcd57e102e95ba4c79ea2cb87c8 (diff) | |
| download | mullvadvpn-4657cc02c930beccd8cd654271830701a8268b41.tar.xz mullvadvpn-4657cc02c930beccd8cd654271830701a8268b41.zip | |
Ensure that default route still exist on route events
| -rw-r--r-- | talpid-routing/src/unix/macos/mod.rs | 89 |
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 { |
