diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-05-27 17:11:40 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-05-29 12:55:01 +0200 |
| commit | b919ffdcb0f3e2d67d684e2a6304b18e91fcc4d9 (patch) | |
| tree | c00daefeee80b96d384bf71eb3ecca896cb43916 /talpid-core/src | |
| parent | 331a90e1b5895097db65c08f5670519d03e3b2d6 (diff) | |
| download | mullvadvpn-b919ffdcb0f3e2d67d684e2a6304b18e91fcc4d9.tar.xz mullvadvpn-b919ffdcb0f3e2d67d684e2a6304b18e91fcc4d9.zip | |
Do not get stuck in failed state when default route is missing
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/split_tunnel/macos/mod.rs | 111 |
1 files changed, 82 insertions, 29 deletions
diff --git a/talpid-core/src/split_tunnel/macos/mod.rs b/talpid-core/src/split_tunnel/macos/mod.rs index 335944123c..000ee87dd0 100644 --- a/talpid-core/src/split_tunnel/macos/mod.rs +++ b/talpid-core/src/split_tunnel/macos/mod.rs @@ -379,20 +379,14 @@ impl State { /// Set paths to exclude. For a non-empty path, this will initialize split tunneling if a tunnel /// device is also set. async fn set_exclude_paths(&mut self, paths: HashSet<PathBuf>) -> Result<(), Error> { - let state = self.fail(None); - match state.set_exclude_paths_inner(paths).await { - Ok(new_state) => { - *self = new_state; - Ok(()) - } - Err(error) => { - self.fail(Some(error.clone())); - Err(error) - } - } + self.transition(move |self_| self_.set_exclude_paths_inner(paths)) + .await } - async fn set_exclude_paths_inner(mut self, paths: HashSet<PathBuf>) -> Result<Self, Error> { + async fn set_exclude_paths_inner( + mut self, + paths: HashSet<PathBuf>, + ) -> Result<Self, ErrorWithTransition> { match self { // If there are currently no paths and no process monitor, initialize it State::NoExclusions { @@ -437,40 +431,44 @@ impl State { }) } // Otherwise, remain in the failed state - State::Failed { cause, .. } => Err(cause.unwrap_or(Error::unavailable())), + State::Failed { cause, .. } => Err(cause.unwrap_or(Error::unavailable()).into()), } } /// Update VPN tunnel interface that non-excluded packets are sent on async fn set_tunnel(&mut self, new_vpn_interface: Option<VpnInterface>) -> Result<(), Error> { - let state = self.fail(None); - match state.set_tunnel_inner(new_vpn_interface).await { - Ok(new_state) => { - *self = new_state; - Ok(()) - } - Err(error) => { - self.fail(Some(error.clone())); - Err(error) - } - } + self.transition(move |self_| self_.set_tunnel_inner(new_vpn_interface)) + .await } async fn set_tunnel_inner( mut self, new_vpn_interface: Option<VpnInterface>, - ) -> Result<Self, Error> { + ) -> Result<Self, ErrorWithTransition> { match self { // If split tunneling is already initialized, just update the interfaces State::Initialized { route_manager, mut process, tun_handle, - vpn_interface: _, + vpn_interface, } => { // Try to update the default interface first // If this fails, remain in the current state and just fail - let default_interface = default::get_default_interface(&route_manager).await?; + let default_interface = match default::get_default_interface(&route_manager).await { + Ok(default_interface) => default_interface, + Err(error) => { + return Err(ErrorWithTransition { + error: error.into(), + next_state: Some(State::Initialized { + route_manager, + process, + tun_handle, + vpn_interface, + }), + }); + } + }; log::debug!("Updating split tunnel device"); @@ -497,7 +495,18 @@ impl State { } if new_vpn_interface.is_some() => { // Try to update the default interface first // If this fails, remain in the current state and just fail - let default_interface = default::get_default_interface(&route_manager).await?; + let default_interface = match default::get_default_interface(&route_manager).await { + Ok(default_interface) => default_interface, + Err(error) => { + return Err(ErrorWithTransition { + error: error.into(), + next_state: Some(State::ProcessMonitorOnly { + route_manager, + process, + }), + }); + } + }; log::debug!("Initializing split tunnel device"); @@ -548,7 +557,7 @@ impl State { .. } if new_vpn_interface.is_some() => { *vpn_interface = new_vpn_interface; - Err(cause.clone().unwrap_or(Error::unavailable())) + Err(cause.unwrap_or(Error::unavailable()).into()) } // Remain in the failed state without failing otherwise State::Failed { @@ -560,4 +569,48 @@ impl State { } } } + + /// Helper function that tries to perform a state transition using `transition`. + /// On error, transition to `next_state` specified alongside the error. If not specified, + /// transition to or remain in `State::Failed`. + async fn transition<F: std::future::Future<Output = Result<Self, ErrorWithTransition>>>( + &mut self, + transition: impl FnOnce(Self) -> F, + ) -> Result<(), Error> { + let state = self.fail(None); + match (transition)(state).await { + Ok(new_state) => { + *self = new_state; + Ok(()) + } + Err(ErrorWithTransition { + error, + next_state: Some(next_state), + }) => { + *self = next_state; + Err(error) + } + Err(ErrorWithTransition { + error, + next_state: None, + }) => { + self.fail(Some(error.clone())); + Err(error) + } + } + } +} + +struct ErrorWithTransition { + error: Error, + next_state: Option<State>, +} + +impl<T: Into<Error>> From<T> for ErrorWithTransition { + fn from(error: T) -> Self { + Self { + error: error.into(), + next_state: None, + } + } } |
