#![allow(clippy::undocumented_unsafe_blocks)] // Remove me if you dare. mod driver; mod path_monitor; mod service; mod volume_monitor; mod windows; use crate::tunnel_state_machine::TunnelCommand; use futures::channel::{mpsc, oneshot}; use std::{ collections::HashMap, ffi::{OsStr, OsString}, io, net::{IpAddr, Ipv4Addr, Ipv6Addr}, os::windows::io::AsRawHandle, path::{Path, PathBuf}, sync::{ Arc, Mutex, MutexGuard, RwLock, Weak, atomic::{AtomicBool, Ordering}, mpsc as sync_mpsc, }, time::Duration, }; use talpid_routing::{CallbackHandle, EventType, RouteManagerHandle, get_best_default_route}; use talpid_tunnel::TunnelMetadata; use talpid_types::{ErrorExt, split_tunnel::ExcludedProcess, tunnel::ErrorStateCause}; use talpid_windows::{ io::Overlapped, net::{AddressFamily, get_ip_address_for_interface}, sync::Event, }; use windows_sys::Win32::Foundation::ERROR_OPERATION_ABORTED; const DRIVER_EVENT_BUFFER_SIZE: usize = 2048; const RESERVED_IP_V4: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 123); /// Errors that may occur in [`SplitTunnel`]. #[derive(thiserror::Error, Debug)] pub enum Error { /// Failed to install or start driver service #[error("Failed to start driver service")] ServiceError(#[source] service::Error), /// Failed to initialize the driver #[error("Failed to initialize driver")] InitializationError(#[source] driver::DeviceHandleError), /// Failed to reset the driver #[error("Failed to reset driver")] ResetError(#[source] io::Error), /// Failed to set paths to excluded applications #[error("Failed to set list of excluded applications")] SetConfiguration(#[source] io::Error), /// Failed to obtain the current driver state #[error("Failed to obtain the driver state")] GetState(#[source] io::Error), /// Failed to register interface IP addresses #[error("Failed to register IP addresses for exclusions")] RegisterIps(#[source] io::Error), /// Failed to clear interface IP addresses #[error("Failed to clear registered IP addresses")] ClearIps(#[source] io::Error), /// Failed to set up the driver event loop #[error("Failed to set up the driver event loop")] EventThreadError(#[source] io::Error), /// Failed to obtain default route #[error("Failed to obtain the default route")] ObtainDefaultRoute(#[source] talpid_routing::Error), /// Failed to obtain an IP address given a network interface LUID #[error("Failed to obtain IP address for interface LUID")] LuidToIp(#[source] talpid_windows::net::Error), /// Failed to set up callback for monitoring default route changes #[error("Failed to register default route change callback")] RegisterRouteChangeCallback, /// Unexpected IP parsing error #[error("Failed to parse IP address")] IpParseError, /// The request handling thread is stuck #[error("The ST request thread is stuck")] RequestThreadStuck, /// The request handling thread is down #[error("The split tunnel monitor is down")] SplitTunnelDown, /// Failed to start the NTFS reparse point monitor #[error("Failed to start path monitor")] StartPathMonitor(#[source] io::Error), /// A previous path update has not yet completed #[error("A previous update is not yet complete")] AlreadySettingPaths, /// Resetting in the engaged state risks leaking into the tunnel #[error("Failed to reset driver because it is engaged")] CannotResetEngaged, /// Split tunneling is unavailable #[error("Split tunneling is unavailable. Review logs for details.")] Unavailable, } /// Manages applications whose traffic to exclude from the tunnel. pub struct SplitTunnel { state: SplitTunnelState, } enum SplitTunnelState { Initialized(InitializedSplitTunnelState), Failed(FailedSplitTunnelState), } impl SplitTunnel { /// Initialize the split tunnel device. /// /// If initialization fails, split tunneling will be disabled/unavailable. pub fn new>( runtime: tokio::runtime::Handle, resource_dir: PathBuf, daemon_tx: Weak>, volume_update_rx: mpsc::UnboundedReceiver<()>, route_manager: RouteManagerHandle, initial_paths: &[T], ) -> Self { let state = InitializedSplitTunnelState::new( runtime, resource_dir, daemon_tx, volume_update_rx, route_manager, ) .map(SplitTunnelState::Initialized) .unwrap_or_else(|err| { log::error!( "{}", err.display_chain_with_msg("Failed to initialize split tunneling") ); SplitTunnelState::Failed(FailedSplitTunnelState::new()) }); let mut split_tunnel = Self { state }; // If we fail to exclude anything and later connect, we may end up with a working // tunnel but no split tunneling, meaning apps may appear excluded despite not being so. // Rather than silently risking that, fail loudly instead. if let Err(error) = split_tunnel.set_paths_sync(initial_paths) { log::error!( "{}", error.display_chain_with_msg("Failed to set initial split tunnel paths") ); // This will deinitialize split tunneling // TODO: We may want to make it possible to retry initialization instead of // failing permanently. split_tunnel.state = SplitTunnelState::Failed(FailedSplitTunnelState::new()); split_tunnel .set_paths_sync(initial_paths) .expect("failed tunnel cannot fail without VPN addrs set"); } split_tunnel } /// Set a list of applications to exclude from the tunnel. pub fn set_paths>( &mut self, paths: &[T], result_tx: oneshot::Sender>, ) { match &mut self.state { SplitTunnelState::Initialized(state) => state.set_paths(paths, result_tx), SplitTunnelState::Failed(state) => state.set_paths(paths, result_tx), } } /// Set a list of applications to exclude from the tunnel. fn set_paths_sync>(&mut self, paths: &[T]) -> Result<(), Error> { match &mut self.state { SplitTunnelState::Initialized(state) => state.set_paths_sync(paths), SplitTunnelState::Failed(state) => state.set_paths_sync(paths), } } /// Instructs the driver to redirect traffic from sockets bound to 0.0.0.0, ::, or the /// tunnel addresses (if any) to the default route. pub fn set_tunnel_addresses(&mut self, metadata: Option<&TunnelMetadata>) -> Result<(), Error> { match &mut self.state { SplitTunnelState::Initialized(state) => state.set_tunnel_addresses(metadata), SplitTunnelState::Failed(state) => state.set_tunnel_addresses(metadata), } } /// Instructs the driver to stop redirecting tunnel traffic and INADDR_ANY. pub fn clear_tunnel_addresses(&mut self) -> Result<(), Error> { match &mut self.state { SplitTunnelState::Initialized(state) => state.clear_tunnel_addresses(), SplitTunnelState::Failed(state) => state.clear_tunnel_addresses(), } } /// Returns a handle used for interacting with the split tunnel module. pub fn handle(&self) -> SplitTunnelHandle { match &self.state { SplitTunnelState::Initialized(state) => state.handle(), SplitTunnelState::Failed(state) => state.handle(), } } } /// Dummy implementation of split tunneling that fails in the "engaged" state. /// /// The fake implementation can be considered _engaged_ when two conditions are met: /// 1. There is a tunnel (i.e., there are tunnel IP addresses registered). /// 2. There are paths to exclude. /// /// Otherwise, it is _non-engaged_. /// /// This is used to fail when split tunneling should be engaged due to user settings, /// but work when we would not have been in the engaged state anyway (e.g. /// when the user is not excluding any apps, has no tunnel, or has disabled split tunneling). struct FailedSplitTunnelState { paths: Vec, tunnel_addresses: Vec, } #[derive(PartialEq, Eq)] enum InactiveSplitTunnelStateState { NonEngaged, Engaged, } impl FailedSplitTunnelState { pub fn new() -> Self { Self { paths: vec![], tunnel_addresses: vec![], } } pub fn set_paths>( &mut self, paths: &[T], result_tx: oneshot::Sender>, ) { let _ = result_tx.send(self.set_paths_sync(paths)); } fn set_paths_sync>(&mut self, paths: &[T]) -> Result<(), Error> { self.paths = paths.iter().map(|p| p.as_ref().to_owned()).collect(); self.get_action_result() } pub fn set_tunnel_addresses(&mut self, metadata: Option<&TunnelMetadata>) -> Result<(), Error> { self.tunnel_addresses = metadata.map(|m| m.ips.clone()).unwrap_or_default(); self.get_action_result() } pub fn clear_tunnel_addresses(&mut self) -> Result<(), Error> { self.tunnel_addresses.clear(); self.get_action_result() } pub fn handle(&self) -> SplitTunnelHandle { SplitTunnelHandle { loaded: false, excluded_processes: None, } } /// Current inactive split tunnel state state. /// /// # Non-engaged state /// /// If there is no tunnel or no paths to exclude, then split tunneling is supposed to be /// inactive. /// /// In this case, no action should fail (unless it causes a transition to 'engaged'). /// /// # Engaged state /// /// If there is a tunnel as well as paths to exclude, then split tunneling is supposed to be /// active. /// /// In this case, any action should fail (unless it causes a transition to 'non-engaged'). fn current_state(&self) -> InactiveSplitTunnelStateState { if self.tunnel_addresses.is_empty() || self.paths.is_empty() { InactiveSplitTunnelStateState::NonEngaged } else { InactiveSplitTunnelStateState::Engaged } } /// Result of an action based on the current state. fn get_action_result(&self) -> Result<(), Error> { if self.current_state() == InactiveSplitTunnelStateState::NonEngaged { Ok(()) } else { Err(Error::Unavailable) } } } /// Manages applications whose traffic to exclude from the tunnel. struct InitializedSplitTunnelState { runtime: tokio::runtime::Handle, request_tx: RequestTx, event_thread: Option>, quit_event: Arc, excluded_processes: Arc>>, _route_change_callback: Option, daemon_tx: Weak>, async_path_update_in_progress: Arc, route_manager: RouteManagerHandle, } enum Request { SetPaths(Vec), RegisterIps(InterfaceAddresses), Stop, } type RequestResponseTx = sync_mpsc::Sender>; type RequestTx = sync_mpsc::Sender<(Request, RequestResponseTx)>; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); #[derive(Default, PartialEq, Clone)] struct InterfaceAddresses { tunnel_ipv4: Option, tunnel_ipv6: Option, internet_ipv4: Option, internet_ipv6: Option, } /// Cloneable handle for interacting with the split tunnel module. #[derive(Debug, Clone)] pub struct SplitTunnelHandle { loaded: bool, excluded_processes: Option>>>, } impl SplitTunnelHandle { /// Return processes that are currently being excluded, including /// their pids, paths, and reason for being excluded. pub fn get_processes(&self) -> Result, Error> { let Some(excluded_procs) = &self.excluded_processes else { return Ok(vec![]); }; let processes = excluded_procs.upgrade().ok_or(Error::SplitTunnelDown)?; let processes = processes.read().unwrap(); Ok(processes.values().cloned().collect()) } /// Return whether split tunneling was properly initialized. /// This can be `false` if the driver failed to load. pub fn is_loaded(&self) -> bool { self.loaded } } enum EventResult { /// Result containing the next event. Event(driver::EventId, driver::EventBody), /// Quit event was signaled. Quit, } impl InitializedSplitTunnelState { /// Initialize the split tunnel device. pub fn new( runtime: tokio::runtime::Handle, resource_dir: PathBuf, daemon_tx: Weak>, volume_update_rx: mpsc::UnboundedReceiver<()>, route_manager: RouteManagerHandle, ) -> Result { let excluded_processes = Arc::new(RwLock::new(HashMap::new())); let (request_tx, handle) = Self::spawn_request_thread(resource_dir, volume_update_rx, excluded_processes.clone())?; let (event_thread, quit_event) = Self::spawn_event_listener(handle, excluded_processes.clone())?; Ok(InitializedSplitTunnelState { runtime, request_tx, event_thread: Some(event_thread), quit_event, _route_change_callback: None, daemon_tx, async_path_update_in_progress: Arc::new(AtomicBool::new(false)), excluded_processes, route_manager, }) } /// Spawns an event loop thread that processes events from the driver service. fn spawn_event_listener( handle: Arc, excluded_processes: Arc>>, ) -> Result<(std::thread::JoinHandle<()>, Arc), Error> { let mut event_overlapped = Overlapped::new(Some( Event::new(true, false).map_err(Error::EventThreadError)?, )) .map_err(Error::EventThreadError)?; let quit_event = Arc::new(Event::new(true, false).map_err(Error::EventThreadError)?); let quit_event_copy = quit_event.clone(); let event_thread = std::thread::spawn(move || { log::debug!("Starting split tunnel event thread"); let mut data_buffer = vec![]; loop { // Wait until either the next event is received or the quit event is signaled. let (event_id, event_body) = match Self::fetch_next_event( &handle, &quit_event, &mut event_overlapped, &mut data_buffer, ) { Ok(EventResult::Event(event_id, event_body)) => (event_id, event_body), Ok(EventResult::Quit) => break, Err(error) => { if error.raw_os_error() == Some(ERROR_OPERATION_ABORTED as i32) { // The driver will normally abort the request if the driver state // is reset. Give the driver service some time to recover before // retrying. std::thread::sleep(Duration::from_millis(500)); } continue; } }; Self::handle_event(event_id, event_body, &excluded_processes); } log::debug!("Stopping split tunnel event thread"); }); Ok((event_thread, quit_event_copy)) } fn fetch_next_event( device: &Arc, quit_event: &Event, overlapped: &mut Overlapped, data_buffer: &mut Vec, ) -> io::Result { if unsafe { driver::wait_for_single_object(quit_event, Some(Duration::ZERO)) }.is_ok() { return Ok(EventResult::Quit); } data_buffer.resize(DRIVER_EVENT_BUFFER_SIZE, 0u8); unsafe { driver::device_io_control_buffer_async( device, driver::DriverIoctlCode::DequeEvent as u32, None, data_buffer.as_mut_ptr(), u32::try_from(data_buffer.len()).expect("buffer must be smaller than u32"), overlapped.as_mut_ptr(), ) } .inspect_err(|error| { log::error!( "{}", error.display_chain_with_msg("DeviceIoControl failed to deque event") ); })?; let event_objects = [ overlapped.get_event().unwrap().as_raw_handle(), quit_event.as_raw_handle(), ]; let signaled_object = unsafe { driver::wait_for_multiple_objects(&event_objects[..], false) }.inspect_err( |error| { log::error!( "{}", error.display_chain_with_msg("wait_for_multiple_objects failed") ); }, )?; if signaled_object == quit_event.as_raw_handle() { // Quit event was signaled return Ok(EventResult::Quit); } let returned_bytes = driver::get_overlapped_result(device, overlapped).inspect_err(|error| { if error.raw_os_error() != Some(ERROR_OPERATION_ABORTED as i32) { log::error!( "{}", error.display_chain_with_msg( "get_overlapped_result failed for dequeued event" ), ); } })?; data_buffer .truncate(usize::try_from(returned_bytes).expect("usize must be no smaller than u32")); driver::parse_event_buffer(data_buffer) .map(|(id, body)| EventResult::Event(id, body)) .map_err(|error| { log::error!( "{}", error.display_chain_with_msg("Failed to parse ST event buffer") ); io::Error::other("Failed to parse ST event buffer") }) } fn handle_event( event_id: driver::EventId, event_body: driver::EventBody, excluded_processes: &Arc>>, ) { use driver::{EventBody, EventId}; let event_str = match &event_id { EventId::StartSplittingProcess | EventId::ErrorStartSplittingProcess => { "Start splitting process" } EventId::StopSplittingProcess | EventId::ErrorStopSplittingProcess => { "Stop splitting process" } EventId::ErrorMessage => "ErrorMessage", }; match event_body { EventBody::SplittingEvent { process_id, reason, image, } => { let mut pids = excluded_processes.write().unwrap(); match event_id { EventId::StartSplittingProcess => { if let Some(prev_entry) = pids.get(&process_id) { log::error!( "PID collision: {process_id} is already in the list of excluded processes. New image: {:?}. Current image: {:?}", image, prev_entry ); } pids.insert( process_id, ExcludedProcess { pid: u32::try_from(process_id) .expect("PID should be containable in a DWORD"), image: Path::new(&image).to_path_buf(), inherited: reason .contains(driver::SplittingChangeReason::BY_INHERITANCE), }, ); } EventId::StopSplittingProcess => { if pids.remove(&process_id).is_none() { log::error!("Inconsistent process tree: {process_id} was not found"); } } _ => (), } log::trace!( "{}:\n\tpid: {}\n\treason: {:?}\n\timage: {:?}", event_str, process_id, reason, image, ); } EventBody::SplittingError { process_id, image } => { log::error!( "FAILED: {}:\n\tpid: {}\n\timage: {:?}", event_str, process_id, image, ); } EventBody::ErrorMessage { status, message } => { log::error!("NTSTATUS {:#x}: {}", status, message.to_string_lossy()) } } } fn spawn_request_thread( resource_dir: PathBuf, volume_update_rx: mpsc::UnboundedReceiver<()>, excluded_processes: Arc>>, ) -> Result<(RequestTx, Arc), Error> { let (tx, rx): (RequestTx, _) = sync_mpsc::channel(); let (init_tx, init_rx) = sync_mpsc::channel(); let monitored_paths = Arc::new(Mutex::new(vec![])); let monitored_paths_copy = monitored_paths.clone(); let (monitor_tx, monitor_rx) = sync_mpsc::channel(); let path_monitor = path_monitor::PathMonitor::spawn(monitor_tx.clone()) .map_err(Error::StartPathMonitor)?; let volume_monitor = volume_monitor::VolumeMonitor::spawn( path_monitor.clone(), monitor_tx, monitored_paths.clone(), volume_update_rx, ); std::thread::spawn(move || { let init_fn = || { service::install_driver_if_required(&resource_dir).map_err(Error::ServiceError)?; driver::DeviceHandle::new() .map(Arc::new) .map_err(Error::InitializationError) }; let handle = match init_fn() { Ok(handle) => { let _ = init_tx.send(Ok(handle.clone())); handle } Err(error) => { let _ = init_tx.send(Err(error)); return; } }; let mut previous_addresses = InterfaceAddresses::default(); while let Ok((request, response_tx)) = rx.recv() { let response = match request { Request::SetPaths(paths) => { let mut monitored_paths_guard = monitored_paths.lock().unwrap(); let result = if !paths.is_empty() { handle.set_config(&paths).map_err(Error::SetConfiguration) } else { handle.clear_config().map_err(Error::SetConfiguration) }; if result.is_ok() { if let Err(error) = path_monitor.set_paths(&paths) { log::error!( "{}", error.display_chain_with_msg("Failed to update path monitor") ); } *monitored_paths_guard = paths; } result } Request::RegisterIps(mut ips) => { if ips.internet_ipv4.is_none() && ips.internet_ipv6.is_none() { ips.tunnel_ipv4 = None; ips.tunnel_ipv6 = None; } if previous_addresses == ips { Ok(()) } else { let result = handle .register_ips( ips.tunnel_ipv4, ips.tunnel_ipv6, ips.internet_ipv4, ips.internet_ipv6, ) .map_err(Error::RegisterIps); if result.is_ok() { previous_addresses = ips; } result } } // INVARIANT: This arm will always the terminate the request thread. Request::Stop => { // Start by attempting to reset the driver state. Do this first, since // we'd like to prevent the process monitor from updating `excluded_processes`. // If reset fails, the driver ends up in a "zombie" state. If that happens, // the best we can do is try to clean up as much as possible. let reset_result = handle.reset().map_err(Error::ResetError); monitored_paths.lock().unwrap().clear(); excluded_processes.write().unwrap().clear(); drop(volume_monitor); if let Err(error) = path_monitor.shutdown() { log::error!( "{}", error.display_chain_with_msg("Failed to shut down path monitor") ); } // Device handles must be dropped before unloading the driver. // Otherwise, it will fail and time out. drop(handle); // If we failed to reset, make sure to NEVER unload the driver. // See the safety comment on `stop_driver_service`. // Unloading without a reset can trigger a BSOD! let unload_driver = reset_result.is_ok(); if unload_driver { log::debug!("Stopping ST service"); // SAFETY: We have reset the driver before calling this. if let Err(error) = unsafe { service::stop_driver_service() } { log::error!( "{}", error.display_chain_with_msg("Failed to stop ST service") ); } } let _ = response_tx.send(reset_result); break; } }; if response_tx.send(response).is_err() { log::error!("A response could not be sent for a completed request"); } } log::info!("Stopping ST request thread"); }); let handle = init_rx .recv_timeout(REQUEST_TIMEOUT) .map_err(|_| Error::RequestThreadStuck)??; let handle_copy = handle.clone(); std::thread::spawn(move || { while let Ok(()) = monitor_rx.recv() { let paths = monitored_paths_copy.lock().unwrap(); let result = if !paths.is_empty() { log::debug!("Re-resolving excluded paths"); handle_copy.set_config(&paths) } else { continue; }; if let Err(error) = result { log::error!( "{}", error.display_chain_with_msg("Failed to update excluded paths") ); } } }); Ok((tx, handle)) } fn send_request(&self, request: Request) -> Result<(), Error> { Self::send_request_inner(&self.request_tx, request) } fn send_request_inner(request_tx: &RequestTx, request: Request) -> Result<(), Error> { let (response_tx, response_rx) = sync_mpsc::channel(); request_tx .send((request, response_tx)) .map_err(|_| Error::SplitTunnelDown)?; response_rx .recv_timeout(REQUEST_TIMEOUT) .map_err(|_| Error::RequestThreadStuck)? } /// Set a list of applications to exclude from the tunnel. fn set_paths_sync>(&mut self, paths: &[T]) -> Result<(), Error> { self.send_request(Request::SetPaths( paths .iter() .map(|path| path.as_ref().to_os_string()) .collect(), )) } /// Set a list of applications to exclude from the tunnel. pub fn set_paths>( &mut self, paths: &[T], result_tx: oneshot::Sender>, ) { let busy = self .async_path_update_in_progress .swap(true, Ordering::SeqCst); if busy { let _ = result_tx.send(Err(Error::AlreadySettingPaths)); return; } let (response_tx, response_rx) = sync_mpsc::channel(); let request = Request::SetPaths( paths .iter() .map(|path| path.as_ref().to_os_string()) .collect(), ); let request_tx = self.request_tx.clone(); let wait_task = move || { request_tx .send((request, response_tx)) .map_err(|_| Error::SplitTunnelDown)?; response_rx.recv().map_err(|_| Error::SplitTunnelDown)? }; let in_progress = self.async_path_update_in_progress.clone(); self.runtime.spawn_blocking(move || { let _ = result_tx.send(wait_task()); in_progress.store(false, Ordering::SeqCst); }); } /// Instructs the driver to redirect traffic from sockets bound to 0.0.0.0, ::, or the /// tunnel addresses (if any) to the default route. pub fn set_tunnel_addresses(&mut self, metadata: Option<&TunnelMetadata>) -> Result<(), Error> { let mut tunnel_ipv4 = None; let mut tunnel_ipv6 = None; if let Some(metadata) = metadata { for ip in &metadata.ips { match ip { IpAddr::V4(address) => tunnel_ipv4 = Some(*address), IpAddr::V6(address) => tunnel_ipv6 = Some(*address), } } } let tunnel_ipv4 = Some(tunnel_ipv4.unwrap_or(RESERVED_IP_V4)); let context_mutex = Arc::new(Mutex::new( SplitTunnelDefaultRouteChangeHandlerContext::new( self.request_tx.clone(), self.daemon_tx.clone(), tunnel_ipv4, tunnel_ipv6, ), )); self._route_change_callback = None; let moved_context_mutex = context_mutex.clone(); let context = context_mutex.lock().unwrap(); let callback = self .runtime .block_on( self.route_manager .add_default_route_change_callback(Box::new(move |event, addr_family| { split_tunnel_default_route_change_handler( event, addr_family, &moved_context_mutex, ) })), ) .map(Some) // NOTE: This cannot fail if a callback is created. If that assumption is wrong, this // could deadlock if the dropped callback is invoked (see `init_context`). .map_err(|_| Error::RegisterRouteChangeCallback)?; Self::init_context(context)?; self._route_change_callback = callback; Ok(()) } fn init_context( mut context: MutexGuard<'_, SplitTunnelDefaultRouteChangeHandlerContext>, ) -> Result<(), Error> { // NOTE: This should remain a separate function. Dropping the context after `callback` // causes a deadlock if `split_tunnel_default_route_change_handler` is called at the same // time (i.e. if a route change has occurred), since it waits on the context and // `CallbackHandle::drop` also waits for `split_tunnel_default_route_change_handler` // to complete. context.initialize_internet_addresses()?; context.register_ips() } /// Instructs the driver to stop redirecting tunnel traffic and INADDR_ANY. pub fn clear_tunnel_addresses(&mut self) -> Result<(), Error> { self._route_change_callback = None; self.send_request(Request::RegisterIps(InterfaceAddresses::default())) } /// Returns a handle used for interacting with the split tunnel module. pub fn handle(&self) -> SplitTunnelHandle { SplitTunnelHandle { loaded: true, excluded_processes: Some(Arc::downgrade(&self.excluded_processes)), } } } impl Drop for InitializedSplitTunnelState { fn drop(&mut self) { if let Some(_event_thread) = self.event_thread.take() && let Err(error) = self.quit_event.set() { log::error!( "{}", error.display_chain_with_msg("Failed to close ST event thread") ); // Not joining `event_thread`: It may be unresponsive. } if let Err(error) = self.send_request(Request::Stop) { log::error!( "{}", error.display_chain_with_msg("Failed to stop ST driver service") ); } } } struct SplitTunnelDefaultRouteChangeHandlerContext { request_tx: RequestTx, pub daemon_tx: Weak>, pub addresses: InterfaceAddresses, } impl SplitTunnelDefaultRouteChangeHandlerContext { pub fn new( request_tx: RequestTx, daemon_tx: Weak>, tunnel_ipv4: Option, tunnel_ipv6: Option, ) -> Self { SplitTunnelDefaultRouteChangeHandlerContext { request_tx, daemon_tx, addresses: InterfaceAddresses { tunnel_ipv4, tunnel_ipv6, internet_ipv4: None, internet_ipv6: None, }, } } pub fn register_ips(&self) -> Result<(), Error> { InitializedSplitTunnelState::send_request_inner( &self.request_tx, Request::RegisterIps(self.addresses.clone()), ) } pub fn initialize_internet_addresses(&mut self) -> Result<(), Error> { // Identify IP address that gives us Internet access let internet_ipv4 = get_best_default_route(AddressFamily::Ipv4) .map_err(Error::ObtainDefaultRoute)? .map(|route| { get_ip_address_for_interface(AddressFamily::Ipv4, route.iface).map(|ip| match ip { Some(IpAddr::V4(addr)) => Some(addr), Some(_) => unreachable!("wrong address family (expected IPv4)"), None => { log::warn!("No IPv4 address was found for the default route interface"); None } }) }) .transpose() .map_err(Error::LuidToIp)? .flatten(); let internet_ipv6 = get_best_default_route(AddressFamily::Ipv6) .map_err(Error::ObtainDefaultRoute)? .map(|route| { get_ip_address_for_interface(AddressFamily::Ipv6, route.iface).map(|ip| match ip { Some(IpAddr::V6(addr)) => Some(addr), Some(_) => unreachable!("wrong address family (expected IPv6)"), None => { log::warn!("No IPv6 address was found for the default route interface"); None } }) }) .transpose() .map_err(Error::LuidToIp)? .flatten(); self.addresses.internet_ipv4 = internet_ipv4; self.addresses.internet_ipv6 = internet_ipv6; Ok(()) } } fn split_tunnel_default_route_change_handler( event_type: EventType<'_>, address_family: AddressFamily, ctx_mutex: &Arc>, ) { use talpid_routing::EventType::*; // Update the "internet interface" IP when best default route changes let mut ctx = ctx_mutex.lock().expect("ST route handler mutex poisoned"); let daemon_tx = ctx.daemon_tx.upgrade(); let maybe_send = move |content| { if let Some(tx) = daemon_tx { let _ = tx.unbounded_send(content); } }; let result = match event_type { Updated(default_route) | UpdatedDetails(default_route) => { match get_ip_address_for_interface(address_family, default_route.iface) { Ok(Some(ip)) => match ip { IpAddr::V4(addr) => ctx.addresses.internet_ipv4 = Some(addr), IpAddr::V6(addr) => ctx.addresses.internet_ipv6 = Some(addr), }, Ok(None) => { log::warn!("Failed to obtain default route interface address"); match address_family { AddressFamily::Ipv4 => { ctx.addresses.internet_ipv4 = None; } AddressFamily::Ipv6 => { ctx.addresses.internet_ipv6 = None; } } } Err(error) => { log::error!( "{}", error.display_chain_with_msg( "Failed to obtain default route interface address" ) ); maybe_send(TunnelCommand::Block(ErrorStateCause::SplitTunnelError)); return; } }; ctx.register_ips() } // no default route Removed => { match address_family { AddressFamily::Ipv4 => { ctx.addresses.internet_ipv4 = None; } AddressFamily::Ipv6 => { ctx.addresses.internet_ipv6 = None; } } ctx.register_ips() } }; if let Err(error) = result { log::error!( "{}", error.display_chain_with_msg("Failed to register new addresses in split tunnel driver") ); maybe_send(TunnelCommand::Block(ErrorStateCause::SplitTunnelError)); } }