diff options
| -rw-r--r-- | Cargo.lock | 3 | ||||
| -rw-r--r-- | mullvad-cli/src/format.rs | 2 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 1 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types.rs | 4 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/src/dns/macos.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/macos.rs | 15 | ||||
| -rw-r--r-- | talpid-core/src/resolver/mod.rs | 141 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 10 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 196 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 168 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 69 | ||||
| -rw-r--r-- | talpid-types/src/tunnel.rs | 15 |
15 files changed, 336 insertions, 302 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2318a4aff5..f61e4a57b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1021,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" dependencies = [ "socket2 0.3.19", - "widestring", + "widestring 0.4.3", "winapi 0.3.9", "winreg 0.6.2", ] @@ -2620,6 +2620,7 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "duct", + "either", "err-derive", "futures", "hex", diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index b4b311b49d..ac5087ba3c 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -164,6 +164,8 @@ fn error_state_to_string(error_state: &ErrorState) -> String { SplitTunnelError => "The split tunneling module reported an error", #[cfg(target_os = "macos")] CustomResolverError => "Failed to start custom resolver", + #[cfg(target_os = "macos")] + ReadSystemDnsConfig => "Failed to read system DNS config", #[cfg(not(target_os = "android"))] _ => unreachable!("unknown error cause"), }; diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 61c22388d2..ef28bc4640 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -112,6 +112,7 @@ message ErrorState { VPN_PERMISSION_DENIED = 7; SPLIT_TUNNEL_ERROR = 8; CUSTOM_RESOLVER_ERROR = 9; + READ_SYSTEM_DNS_CONFIG = 10; } enum GenerationError { diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs index dbcd7076e8..0edf324a4c 100644 --- a/mullvad-management-interface/src/types.rs +++ b/mullvad-management-interface/src/types.rs @@ -153,6 +153,10 @@ impl From<mullvad_types::states::TunnelState> for TunnelState { talpid_tunnel::ErrorStateCause::CustomResolverError => { i32::from(Cause::CustomResolverError) } + #[cfg(target_os = "macos")] + talpid_tunnel::ErrorStateCause::ReadSystemDnsConfig => { + i32::from(Cause::ReadSystemDnsConfig) + } }, blocking_error: error_state.block_failure().map(map_firewall_error), auth_fail_reason: if let talpid_tunnel::ErrorStateCause::AuthFailed( diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 86c0204ca2..42186f07b1 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -69,6 +69,7 @@ internet-checksum = "0.2" [target.'cfg(target_os = "macos")'.dependencies] +either = "1" pfctl = "0.4.4" system-configuration = "0.4" tun = "0.5.1" diff --git a/talpid-core/src/dns/macos.rs b/talpid-core/src/dns/macos.rs index fe7501d905..393de23dd9 100644 --- a/talpid-core/src/dns/macos.rs +++ b/talpid-core/src/dns/macos.rs @@ -406,6 +406,7 @@ fn dns_change_callback_internal( changed_keys: CFArray<CFString>, state: &mut State, ) { + state.send_new_config(); for path in &changed_keys { let should_set_dns = match DnsSettings::load(&store, path.clone()).ok() { None => { @@ -424,7 +425,6 @@ fn dns_change_callback_internal( } } }; - state.send_new_config(); if should_set_dns { if let Err(e) = state.dns_settings.save(&store, path.clone()) { log::error!("Failed changing DNS for {}: {}", *path, e); diff --git a/talpid-core/src/macos.rs b/talpid-core/src/macos.rs index 0b102e4dd7..79de8570e1 100644 --- a/talpid-core/src/macos.rs +++ b/talpid-core/src/macos.rs @@ -1,15 +1,26 @@ use std::{ffi::CStr, io}; -/// Returns the GID of `mullvad-exclusion` group if it exists. +/// Returns the GID of the specified group name pub fn get_group_id(group_name: &CStr) -> Option<u32> { + // SAFETY: group_name is a valid CString let group = unsafe { libc::getgrnam(group_name.as_ptr() as *const _) }; if group.is_null() { return None; } + // SAFETY: group is not null let gid = unsafe { (*group).gr_gid }; Some(gid) } +/// Sets group ID for the current process +pub fn set_gid(gid: u32) -> io::Result<()> { + let result = unsafe { libc::setgid(gid) }; + if result == 0 { + Ok(()) + } else { + Err(io::Error::from_raw_os_error(result)) + } +} const INCREASED_FILEHANDLE_LIMIT: u64 = 1024; /// Bump filehandle limit @@ -18,6 +29,7 @@ pub fn bump_filehandle_limit() { rlim_cur: 0, rlim_max: 0, }; + // SAFETY: `&mut limits` is a valid pointer parameter for the getrlimit syscall let status = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits as *mut _) }; if status != 0 { log::error!( @@ -34,6 +46,7 @@ pub fn bump_filehandle_limit() { } limits.rlim_cur = INCREASED_FILEHANDLE_LIMIT; + // SAFETY: `&limits` is a valid pointer parameter for the getrlimit syscall let status = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &limits as *const _) }; if status != 0 { log::error!( diff --git a/talpid-core/src/resolver/mod.rs b/talpid-core/src/resolver/mod.rs index fc3509f513..fa026a48f3 100644 --- a/talpid-core/src/resolver/mod.rs +++ b/talpid-core/src/resolver/mod.rs @@ -56,45 +56,29 @@ const CAPTIVE_PORTAL_DOMAIN: &str = "captive.apple.com"; type TunnelCommandSender = Weak<mpsc::UnboundedSender<TunnelCommand>>; -pub(crate) async fn start_resolver( - sender: TunnelCommandSender, - exclusion_gid: Option<u32>, -) -> Result<ResolverHandle, Error> { - start_resolver_inner(sender, exclusion_gid, 53).await +pub(crate) async fn start_resolver(sender: TunnelCommandSender) -> Result<ResolverHandle, Error> { + start_resolver_inner(sender, 53).await } async fn start_resolver_inner( sender: TunnelCommandSender, - exclusion_gid: Option<u32>, port: u16, ) -> Result<ResolverHandle, Error> { let (tx, rx) = oneshot::channel(); - std::thread::spawn(move || run_resolver(sender, tx, exclusion_gid, port)); + std::thread::spawn(move || run_resolver(sender, tx, port)); rx.await.map_err(|_| Error::LauncherThreadPanic)? } -fn run_resolver( +async fn run_resolver( tunnel_tx: TunnelCommandSender, done_tx: oneshot::Sender<Result<ResolverHandle, Error>>, - exclusion_gid: Option<u32>, port: u16, ) { let mut builder = tokio::runtime::Builder::new_multi_thread(); builder.enable_all(); builder.worker_threads(2); builder.max_blocking_threads(1); - builder.on_thread_start(move || { - #[cfg(target_os = "macos")] - if let Some(gid) = exclusion_gid.clone() { - let ret = unsafe { libc::setgid(gid) }; - if ret != 0 { - log::error!("Failed to set group ID"); - return; - } - } else { - return; - } - }); + let rt = builder.build().expect("failed to initialize tokio runtime"); match rt.block_on(FilteringResolver::new(tunnel_tx, port)) { Ok((resolver, resolver_handle)) => { @@ -130,9 +114,15 @@ pub enum Error { #[error(display = "Resolver is already shut down")] ResolverShutdown, - /// Failed to obtain system resolvers - #[error(display = "Failed to obtain system resolvers")] - NoSystemResolvers, + /// System DNS error + #[error(display = "System DNS error")] + SystemDnsError(crate::dns::Error), +} + +impl From<crate::dns::Error> for Error { + fn from(err: crate::dns::Error) -> Self { + Error::SystemDnsError(err) + } } struct FilteringResolver { @@ -167,32 +157,7 @@ impl ResolverState { pub(crate) enum ResolverMessage { Request(LowerQuery, oneshot::Sender<Box<dyn LookupObject>>), - SetResolverState( - ResolverState, - oneshot::Sender<Result<ResolverStateToggleResult, Error>>, - ), -} - -pub(crate) struct ResolverStateToggleResult { - pub currently_used_resolvers: BTreeSet<IpAddr>, - unblock_tx: oneshot::Sender<()>, -} - -impl ResolverStateToggleResult { - fn new(resolvers: &[IpAddr]) -> (Self, oneshot::Receiver<()>) { - let (unblock_tx, rx) = oneshot::channel(); - ( - Self { - currently_used_resolvers: resolvers.iter().cloned().collect(), - unblock_tx, - }, - rx, - ) - } - - pub fn unblock(self) { - let _ = self.unblock_tx.send(()); - } + SetResolverState(ResolverState, oneshot::Sender<Result<(), Error>>), } #[derive(Clone)] @@ -206,22 +171,19 @@ impl ResolverHandle { } /// Enable the resolver - pub async fn set_active( - &self, - config: Option<(String, Vec<IpAddr>)>, - ) -> Result<ResolverStateToggleResult, Error> { + pub async fn set_active(&self, config: Option<(String, Vec<IpAddr>)>) -> Result<(), Error> { self.set_state(ResolverState::Active(config)).await } - pub async fn set_inactive(&self) -> Result<ResolverStateToggleResult, Error> { + pub async fn set_inactive(&self) -> Result<(), Error> { self.set_state(ResolverState::Inactive).await } - pub async fn shutdown(&self) -> Result<ResolverStateToggleResult, Error> { + pub async fn shutdown(&self) -> Result<(), Error> { self.set_state(ResolverState::Shutdown).await } - async fn set_state(&self, state: ResolverState) -> Result<ResolverStateToggleResult, Error> { + async fn set_state(&self, state: ResolverState) -> Result<(), Error> { let (done_tx, done_rx) = oneshot::channel(); let tx: &mpsc::Sender<ResolverMessage> = &*self.tx; let mut tx = tx.clone(); @@ -295,11 +257,8 @@ impl FilteringResolver { } } match self.reset_resolver().await { - Ok(new_resolvers) => { - let (result, unblock_rx) = - ResolverStateToggleResult::new(&new_resolvers); - let _ = tx.send(Ok(result)); - let _ = unblock_rx.await; + Ok(_) => { + let _ = tx.send(Ok(())); } Err(err) => { let _ = tx.send(Err(err)); @@ -349,7 +308,7 @@ impl FilteringResolver { } } - async fn reset_resolver(&mut self) -> Result<Vec<IpAddr>, Error> { + async fn reset_resolver(&mut self) -> Result<(), Error> { log::trace!("Resetting custom resolver"); let (best_interface, resolver_addresses) = self.get_resolver_config(); self.runtime_provider.update_best_interface(best_interface); @@ -366,9 +325,8 @@ impl FilteringResolver { self.runtime_provider.clone(), ) .map_err(Error::LaunchResolver)?; - let resolver_addresses = resolver_addresses.to_vec(); self.excluded_resolver = resolver; - Ok(resolver_addresses) + Ok(()) } fn get_resolver_config(&self) -> (&str, &[IpAddr]) { @@ -377,10 +335,10 @@ impl FilteringResolver { // TODO: actually pick the best resolver resolvers .as_ref() - .filter(|(_, addresses)| { - !addresses.iter().any(|ip| ip.is_loopback()) + .filter(|(_, addresses)| !addresses.iter().any(|ip| ip.is_loopback())) + .map(|(interface_name, addresses)| { + (interface_name.as_str(), addresses.as_slice()) }) - .map(|(interface_name, addresses)| (interface_name.as_str(), addresses.as_slice())) .unwrap_or(("", &[])) } _ => ("", &[]), @@ -708,7 +666,7 @@ mod test { let tx = Arc::new(tx); let port = random_port(); - let resolver_handle = super::start_resolver_inner(Arc::downgrade(&tx), None, port) + let resolver_handle = super::start_resolver_inner(Arc::downgrade(&tx), port) .await .unwrap(); (resolver_handle, port, rx, tx) @@ -734,13 +692,8 @@ mod test { let (handle, port, mut cmd_rx, _txx) = rt.block_on(start_resolver()); let test_resolver = rt.block_on(get_test_resolver(port)); let resolver_config = read_resolvconf(); - rt.block_on(async { - let unblocker = handle - .set_active(resolver_config) - .await - .expect("failed to make resovler active"); - unblocker.unblock(); - }); + rt.block_on(async { handle.set_active(resolver_config).await }) + .expect("failed to make resovler active"); let captive_portal_domain = LowerName::from(Name::from_str(CAPTIVE_PORTAL_DOMAIN).unwrap()); let resolver_result = rt.block_on(async move { @@ -770,13 +723,8 @@ mod test { let test_resolver = rt.block_on(get_test_resolver(port)); let resolver_config = read_resolvconf(); - rt.block_on(async { - let unblocker = handle - .set_active(resolver_config) - .await - .expect("failed to make resovler active"); - unblocker.unblock(); - }); + rt.block_on(async { handle.set_active(resolver_config).await }) + .expect("failed to make resovler active"); let captive_portal_domain = LowerName::from(Name::from_str("apple.com").unwrap()); let resolver_result = rt.block_on(async move { @@ -807,13 +755,8 @@ mod test { let (handle, port, mut cmd_rx, _tx) = rt.block_on(start_resolver()); let test_resolver = rt.block_on(get_test_resolver(port)); - rt.block_on(async { - let unblocker = handle - .set_inactive() - .await - .expect("failed to make resovler active"); - unblocker.unblock(); - }); + rt.block_on(async { handle.set_inactive().await }) + .expect("failed to make resovler active"); let captive_portal_domain = LowerName::from(Name::from_str("apple.com").unwrap()); let resolver_result = rt.block_on(async move { @@ -848,23 +791,13 @@ mod test { let _ = UdpSocket::bind(server_sockaddr) .expect("Failed to bind to resolver socket addr when it should be unbound"); - rt.block_on(async { - let unblocker = handle - .set_inactive() - .await - .expect("failed to make resovler active"); - unblocker.unblock(); - }); + rt.block_on(async { handle.set_inactive().await }) + .expect("failed to make resovler active"); assert!(UdpSocket::bind(server_sockaddr).is_err()); - rt.block_on(async { - let unblocker = handle - .shutdown() - .await - .expect("failed to make resovler active"); - unblocker.unblock(); - }); + rt.block_on(async { handle.shutdown().await }) + .expect("failed to make resovler active"); UdpSocket::bind(server_sockaddr) .expect("Failed to bind to resolver socket addr when it should be unbound"); diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 18c47ff213..3586b88e48 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -185,13 +185,14 @@ impl ConnectedState { use self::EventConsequence::*; match command { + #[cfg(target_os = "macos")] Some(TunnelCommand::AddAllowedIps(_allowed_ips, done_tx)) => { let _ = done_tx.send(()); SameState(self.into()) } #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - let _ = done_tx.send(shared_values.toggle_custom_resolver(enable)); + let _ = done_tx.send(shared_values.deactivate_custom_resolver(enable)); SameState(self.into()) } #[cfg(target_os = "macos")] diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index b0fb0fc176..3d8b34a725 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -268,13 +268,14 @@ impl ConnectingState { use self::EventConsequence::*; match command { + #[cfg(target_os = "macos")] Some(TunnelCommand::AddAllowedIps(_, done_tx)) => { let _ = done_tx.send(()); SameState(self.into()) } #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - let _ = done_tx.send(shared_values.toggle_custom_resolver(enable)); + let _ = done_tx.send(shared_values.deactivate_custom_resolver(enable)); SameState(self.into()) } #[cfg(target_os = "macos")] @@ -492,6 +493,13 @@ impl TunnelState for ConnectingState { if shared_values.is_offline { return ErrorState::enter(shared_values, ErrorStateCause::IsOffline); } + #[cfg(target_os = "macos")] + if let Err(err) = shared_values.disable_custom_resolver() { + log::error!( + "{}", + err.display_chain_with_msg("Failed to disable custom resolver") + ); + } match shared_values .tunnel_parameters_generator .generate(retry_attempt) diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 59da83bde6..a920697176 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -2,13 +2,18 @@ use super::{ ConnectingState, ErrorState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelCommandReceiver, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::{firewall::FirewallPolicy, resolver}; +use crate::firewall::FirewallPolicy; +#[cfg(target_os = "macos")] +use crate::{dns, resolver}; use futures::StreamExt; +#[cfg(target_os = "macos")] use std::{ collections::BTreeSet, net::{IpAddr, Ipv4Addr}, }; -use talpid_types::{tunnel::ErrorStateCause, ErrorExt}; +#[cfg(target_os = "macos")] +use talpid_types::tunnel::ErrorStateCause; +use talpid_types::ErrorExt; /// No tunnel is running. pub struct DisconnectedState { @@ -19,20 +24,32 @@ pub struct DisconnectedState { } impl DisconnectedState { + #[cfg(target_os = "macos")] + fn reset_allowed_resolvers( + &mut self, + resolver_config: &Option<(String, Vec<IpAddr>)>, + shared_values: &mut SharedTunnelStateValues, + ) { + if let Some((_interface, resolver_ips)) = &resolver_config { + self.allowed_resolvers = resolver_ips.iter().cloned().collect(); + } else { + self.allowed_resolvers = BTreeSet::new(); + } + self.set_firewall_policy(shared_values, false); + } + fn set_firewall_policy( &mut self, shared_values: &mut SharedTunnelStateValues, should_reset_firewall: bool, ) { let result = if shared_values.block_when_disconnected { - #[cfg(target_os = "macos")] - let (resolver_unblocker, allowed_resolvers) = shared_values.start_custom_resolver(); - self.allowed_resolvers = allowed_resolvers; - let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, allowed_endpoint: shared_values.allowed_endpoint.clone(), + #[cfg(target_os = "macos")] allowed_ips: self.allowed_ips.clone(), + #[cfg(target_os = "macos")] allowed_resolvers: self.allowed_resolvers.clone(), }; @@ -42,10 +59,6 @@ impl DisconnectedState { ) }); - #[cfg(target_os = "macos")] - if let Some(resolver) = resolver_unblocker { - resolver.unblock() - }; firewall_result } else if should_reset_firewall { shared_values @@ -85,19 +98,34 @@ impl DisconnectedState { } } - fn set_dns(shared_values: &mut SharedTunnelStateValues) { - if let Some(ref dns_servers) = shared_values.dns_servers { - if let Err(err) = shared_values.dns_monitor.set("lo0", &dns_servers) { - log::error!("failed to set custom DNS servers: {}", err); - } - } - } - fn reset_dns(shared_values: &mut SharedTunnelStateValues) { if let Err(error) = shared_values.dns_monitor.reset() { log::error!("{}", error.display_chain_with_msg("Unable to reset DNS")); } } + + #[cfg(target_os = "macos")] + fn start_custom_resolver( + &mut self, + shared_values: &mut SharedTunnelStateValues, + ) -> Result<(), either::Either<resolver::Error, dns::Error>> { + use either::Either; + let system_config = shared_values + .dns_monitor + .get_system_config() + .map_err(Either::Right)?; + self.reset_allowed_resolvers(&system_config, shared_values); + + shared_values + .runtime + .block_on(shared_values.custom_resolver.set_active(system_config)) + .map_err(Either::Left)?; + shared_values + .dns_monitor + .set("lo", &[Ipv4Addr::LOCALHOST.into()]) + .map_err(resolver::Error::SystemDnsError) + .map_err(Either::Left) + } } impl TunnelState for DisconnectedState { @@ -114,6 +142,26 @@ impl TunnelState for DisconnectedState { allowed_resolvers: BTreeSet::new(), }; + #[cfg(target_os = "macos")] + if shared_values.enable_custom_resolver { + if let Err(err) = shared_values + .dns_monitor + .set("lo", &[Ipv4Addr::LOCALHOST.into()]) + { + log::error!( + "{}", + err.display_chain_with_msg("Failed to configure system to use custom resolver") + ); + } + } else { + if let Err(error) = shared_values.disable_custom_resolver() { + log::error!( + "{}", + error.display_chain_with_msg("Unable to disable custom resolver") + ); + } + } + #[cfg(windows)] Self::register_split_tunnel_addresses(shared_values, should_reset_firewall); disconnected_state.set_firewall_policy(shared_values, should_reset_firewall); @@ -163,7 +211,6 @@ impl TunnelState for DisconnectedState { shared_values .set_dns_servers(servers) .expect("Failed to reconnect after changing custom DNS servers"); - Self::set_dns(shared_values); SameState(self.into()) } @@ -172,12 +219,20 @@ impl TunnelState for DisconnectedState { shared_values.block_when_disconnected = block_when_disconnected; #[cfg(windows)] Self::register_split_tunnel_addresses(shared_values, true); - if block_when_disconnected { - Self::set_dns(shared_values); + #[cfg(target_os = "macos")] + if !block_when_disconnected { + // TODO setup custom resolver + // + // + if let Err(err) = self.start_custom_resolver(shared_values) { + let block_reason = map_custom_resolver_start(&err); + return NewState(ErrorState::enter(shared_values, block_reason)); + } + return SameState(self.into()); } else { Self::reset_dns(shared_values); + self.set_firewall_policy(shared_values, true); } - self.set_firewall_policy(shared_values, true); } SameState(self.into()) } @@ -202,95 +257,49 @@ impl TunnelState for DisconnectedState { } #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - if let Err(err) = shared_values.toggle_custom_resolver(enable) { + if let Err(err) = shared_values.deactivate_custom_resolver(enable) { let _ = done_tx.send(Err(err)); return SameState(self.into()); }; if shared_values.block_when_disconnected && enable { - match shared_values.dns_monitor.get_system_config() { - Ok(system_resolvers) => { - match shared_values.runtime.block_on( - shared_values.custom_resolver.set_active(system_resolvers), - ) { - Ok(result) => { - self.allowed_resolvers = - result.currently_used_resolvers.clone(); - self.set_firewall_policy(shared_values, false); - result.unblock(); - if let Err(err) = shared_values - .dns_monitor - .set("lo", &[Ipv4Addr::LOCALHOST.into()]) - { - log::error!( - "{}", - err.display_chain_with_msg( - "Failed to configure system to use custom resolver" - ) - ); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::SetDnsError, - )); - } - } - Err(err) => { - let _ = done_tx.send(Err(err)); - } - } + match self.start_custom_resolver(shared_values) { + Ok(_) => { + let _ = done_tx.send(Ok(())); + SameState(self.into()) } Err(err) => { log::error!( "{}", - err.display_chain_with_msg("Failed to obtain system DNS config") + err.display_chain_with_msg("Failed to start custom resolver:") ); - let _ = done_tx.send(Err(resolver::Error::NoSystemResolvers)); + let error_cause = map_custom_resolver_start(&err); + let _ = done_tx.send(Err(err.left_or_else(resolver::Error::from))); + NewState(ErrorState::enter(shared_values, error_cause)) } } } else { let _ = done_tx.send(Ok(())); + SameState(self.into()) } - SameState(self.into()) } #[cfg(target_os = "macos")] Some(TunnelCommand::HostDnsConfig(host_config)) => { if shared_values.block_when_disconnected && shared_values.enable_custom_resolver { - // TODO: reconfigure custom resolver - match shared_values + self.reset_allowed_resolvers(&host_config, shared_values); + if let Err(err) = shared_values .runtime .block_on(shared_values.custom_resolver.set_active(host_config)) { - Ok(result) => { - self.allowed_resolvers = result.currently_used_resolvers.clone(); - self.set_firewall_policy(shared_values, false); - result.unblock(); - if let Err(err) = shared_values - .dns_monitor - .set("lo", &[Ipv4Addr::LOCALHOST.into()]) - { - log::error!( - "{}", - err.display_chain_with_msg( - "Failed to configure system to use custom resolver" - ) - ); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::SetDnsError, - )); - } - } - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg("Failed to activate custom resolver") - ); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::CustomResolverError, - )); - } + log::error!( + "{}", + err.display_chain_with_msg("Failed to activate custom resolver") + ); + return NewState(ErrorState::enter( + shared_values, + ErrorStateCause::CustomResolverError, + )); } } SameState(self.into()) @@ -314,3 +323,14 @@ impl TunnelState for DisconnectedState { } } } + +#[cfg(target_os = "macos")] +fn map_custom_resolver_start(err: &either::Either<resolver::Error, dns::Error>) -> ErrorStateCause { + match err { + either::Either::Right(_dns_err) => ErrorStateCause::SetDnsError, + either::Either::Left(resolver::Error::SystemDnsError(_)) => { + ErrorStateCause::ReadSystemDnsConfig + } + either::Either::Left(_other_err) => ErrorStateCause::CustomResolverError, + } +} diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 61ab038bfb..27f33bf04f 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -35,7 +35,7 @@ impl DisconnectingState { } #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - let _ = done_tx.send(shared_values.toggle_custom_resolver(enable)); + let _ = done_tx.send(shared_values.deactivate_custom_resolver(enable)); AfterDisconnect::Nothing } #[cfg(target_os = "macos")] @@ -86,7 +86,7 @@ impl DisconnectingState { #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - let _ = done_tx.send(shared_values.toggle_custom_resolver(enable)); + let _ = done_tx.send(shared_values.deactivate_custom_resolver(enable)); AfterDisconnect::Block(reason) } #[cfg(target_os = "macos")] @@ -139,8 +139,9 @@ impl DisconnectingState { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Reconnect(retry_attempt) } + #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - let _ = done_tx.send(shared_values.toggle_custom_resolver(enable)); + let _ = done_tx.send(shared_values.deactivate_custom_resolver(enable)); AfterDisconnect::Reconnect(retry_attempt) } #[cfg(target_os = "macos")] @@ -148,6 +149,7 @@ impl DisconnectingState { AfterDisconnect::Reconnect(retry_attempt) } + #[cfg(target_os = "macos")] Some(TunnelCommand::AddAllowedIps(_allowed_ips, done_tx)) => { let _ = done_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 7c747d2a60..350514aaa7 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -2,8 +2,11 @@ use super::{ ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelCommandReceiver, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::{firewall::FirewallPolicy, resolver}; +use crate::firewall::FirewallPolicy; +#[cfg(target_os = "macos")] +use crate::resolver; use futures::StreamExt; +#[cfg(target_os = "macos")] use std::{ collections::BTreeSet, net::{IpAddr, Ipv4Addr}, @@ -36,10 +39,24 @@ impl ErrorState { ) } + #[cfg(target_os = "macos")] + fn reset_allowed_resolvers( + &mut self, + resolver_config: &Option<(String, Vec<IpAddr>)>, + shared_values: &mut SharedTunnelStateValues, + ) -> Result<(), FirewallPolicyError> { + if let Some((_interface, resolver_ips)) = &resolver_config { + self.allowed_resolvers = resolver_ips.iter().cloned().collect(); + } else { + self.allowed_resolvers = BTreeSet::new(); + } + self.set_firewall(shared_values) + } + fn set_firewall_policy( shared_values: &mut SharedTunnelStateValues, - allowed_ips: BTreeSet<IpAddr>, - allowed_resolvers: BTreeSet<IpAddr>, + #[cfg(target_os = "macos")] allowed_ips: BTreeSet<IpAddr>, + #[cfg(target_os = "macos")] allowed_resolvers: BTreeSet<IpAddr>, ) -> Result<(), FirewallPolicyError> { let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, @@ -113,7 +130,37 @@ impl TunnelState for ErrorState { } #[cfg(target_os = "macos")] - let (resolver_unblocker, allowed_resolvers) = shared_values.start_custom_resolver(); + let host_config = + if shared_values.enable_custom_resolver && !block_reason.prevents_custom_resolver() { + if let Err(err) = shared_values + .dns_monitor + .set("lo", &[Ipv4Addr::LOCALHOST.into()]) + { + log::error!( + "{}", + err.display_chain_with_msg("Failed to configure custom resolver") + ); + return Self::enter(shared_values, ErrorStateCause::SetDnsError); + } + match shared_values.get_custom_resolver_config() { + Ok(host_config) => host_config, + Err(err) => { + log::error!( + "{}", + err.display_chain_with_msg("Failed to start custom resolver") + ); + return Self::enter(shared_values, ErrorStateCause::CustomResolverError); + } + } + } else { + None + }; + + #[cfg(target_os = "macos")] + let allowed_resolvers = host_config + .as_ref() + .map(|(_interface, resolvers)| resolvers.iter().cloned().collect()) + .unwrap_or(BTreeSet::new()); #[cfg(not(target_os = "android"))] let block_failure = Self::set_firewall_policy( @@ -124,10 +171,21 @@ impl TunnelState for ErrorState { allowed_resolvers.clone(), ) .err(); + #[cfg(target_os = "macos")] - if let Some(resolver_result) = resolver_unblocker { - resolver_result.unblock(); + if let Some(dns_config) = host_config { + if let Err(err) = shared_values + .runtime + .block_on(shared_values.custom_resolver.set_active(Some(dns_config))) + { + log::error!( + "{}", + err.display_chain_with_msg("Failed to activate custom resolver") + ); + return Self::enter(shared_values, ErrorStateCause::CustomResolverError); + } } + #[cfg(target_os = "android")] let block_failure = if !Self::create_blocking_tun(shared_values) { Some(FirewallPolicyError::Generic) @@ -149,6 +207,7 @@ impl TunnelState for ErrorState { ) } + #[cfg_attr(not(target_os = "macos"), allow(unused_mut))] fn handle_event( mut self, runtime: &tokio::runtime::Handle, @@ -175,28 +234,25 @@ impl TunnelState for ErrorState { #[cfg(target_os = "macos")] Some(TunnelCommand::SetCustomResolver(enable, done_tx)) => { - if let Err(err) = shared_values.toggle_custom_resolver(enable) { - let _ = done_tx.send(Err(err)); - return SameState(self.into()); - }; - if enable { - // TODO: enable custom resolver + if enable && !shared_values.enable_custom_resolver { + shared_values.enable_custom_resolver = enable; + match shared_values.dns_monitor.get_system_config() { - Ok(system_resolvers) => { + Ok(current_system_config) => { + if let Err(err) = + self.reset_allowed_resolvers(¤t_system_config, shared_values) + { + return NewState(ErrorState::enter( + shared_values, + ErrorStateCause::SetFirewallPolicyError(err), + )); + } match shared_values.runtime.block_on( - shared_values.custom_resolver.set_active(system_resolvers), + shared_values + .custom_resolver + .set_active(current_system_config), ) { - Ok(result) => { - self.allowed_resolvers = - result.currently_used_resolvers.clone(); - let _ = self.set_firewall(shared_values); - result.unblock(); - if let Err(err) = shared_values - .dns_monitor - .set("lo0", &[Ipv4Addr::LOCALHOST.into()]) - { - log::error!("failed to set custom DNS servers: {}", err); - } + Ok(_) => { if let Err(err) = shared_values .dns_monitor .set("lo", &[Ipv4Addr::LOCALHOST.into()]) @@ -207,13 +263,24 @@ impl TunnelState for ErrorState { "Failed to configure system to use custom resolver" ) ); + let _ = + done_tx.send(Err(resolver::Error::SystemDnsError(err))); return NewState(ErrorState::enter( shared_values, ErrorStateCause::SetDnsError, )); } + + let _ = done_tx.send(Ok(())); } + Err(err) => { + log::error!( + "{}", + err.display_chain_with_msg( + "Failed to start custom resolver" + ) + ); let _ = done_tx.send(Err(err)); } } @@ -224,35 +291,41 @@ impl TunnelState for ErrorState { err.display_chain_with_msg("Failed to obtain system DNS config") ); - let _ = done_tx.send(Err(resolver::Error::NoSystemResolvers)); + let _ = done_tx.send(Err(resolver::Error::SystemDnsError(err))); + return NewState(ErrorState::enter( + shared_values, + ErrorStateCause::ReadSystemDnsConfig, + )); } } + } else { + if let Err(err) = shared_values.deactivate_custom_resolver(enable) { + let _ = done_tx.send(Err(err)); + }; } SameState(self.into()) } #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(config)) => { + Some(TunnelCommand::HostDnsConfig(host_config)) => { if shared_values.enable_custom_resolver { - match shared_values + if let Err(err) = self.reset_allowed_resolvers(&host_config, shared_values) { + return NewState(ErrorState::enter( + shared_values, + ErrorStateCause::SetFirewallPolicyError(err), + )); + } + if let Err(err) = shared_values .runtime - .block_on(shared_values.custom_resolver.set_active(config)) + .block_on(shared_values.custom_resolver.set_active(host_config)) { - Ok(toggle_result) => { - self.allowed_resolvers = toggle_result.currently_used_resolvers.clone(); - let _ = self.set_firewall(shared_values); - toggle_result.unblock(); - } - - Err(err) => { - log::error!( - "failed to set apply new DNS config to custom resolver: {}", - err - ); - return NewState(Self::enter( - shared_values, - ErrorStateCause::CustomResolverError, - )); - } + log::error!( + "Failed to set apply new DNS config to custom resolver: {}", + err + ); + return NewState(Self::enter( + shared_values, + ErrorStateCause::CustomResolverError, + )); } } SameState(self.into()) @@ -303,15 +376,14 @@ impl TunnelState for ErrorState { } } Some(TunnelCommand::Connect) => { - if let Err(err) = shared_values.disable_custom_resolver() { - log::error!("Failed to disable custom resolver: {}", err); - } Self::reset_dns(shared_values); + NewState(ConnectingState::enter(shared_values, 0)) } Some(TunnelCommand::Disconnect) | None => { #[cfg(target_os = "linux")] shared_values.reset_connectivity_check(); + #[cfg(target_os = "macos")] if !shared_values.block_when_disconnected { if let Err(err) = shared_values.disable_custom_resolver() { log::error!("Failed to disable custom resolver: {}", err); diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index a6696117fc..8b44f39dad 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -28,6 +28,8 @@ use futures::{ channel::{mpsc, oneshot}, stream, StreamExt, }; +#[cfg(target_os = "macos")] +use std::collections::BTreeSet; #[cfg(target_os = "android")] use std::os::unix::io::RawFd; use std::{collections::HashSet, io, net::IpAddr, path::PathBuf, sync::Arc}; @@ -36,7 +38,6 @@ use talpid_types::{android::AndroidContext, ErrorExt}; use talpid_types::{ net::{AllowedEndpoint, TunnelParameters}, tunnel::{ErrorStateCause, ParameterGenerationError, TunnelStateTransition}, - ErrorExt, }; /// Errors that can happen when setting up or using the state machine. @@ -145,7 +146,6 @@ pub async fn spawn( ) .await?; - tokio::task::spawn_blocking(move || { state_machine.run(state_change_listener); if shutdown_tx.send(()).is_err() { @@ -262,8 +262,8 @@ impl TunnelStateMachine { ) .map_err(Error::InitDnsMonitorError)?; - let custom_resolver = - crate::resolver::start_resolver(command_tx.clone(), exclusion_gid).await?; + #[cfg(target_os = "macos")] + let custom_resolver = crate::resolver::start_resolver(command_tx.clone()).await?; let (offline_tx, mut offline_rx) = mpsc::unbounded(); let initial_offline_state_tx = offline_state_tx.clone(); @@ -296,7 +296,6 @@ impl TunnelStateMachine { .set_paths_sync(&settings.exclude_paths) .map_err(Error::InitSplitTunneling)?; - let mut shared_values = SharedTunnelStateValues { #[cfg(windows)] split_tunnel, @@ -439,26 +438,25 @@ impl SharedTunnelStateValues { Ok(()) } - pub fn toggle_custom_resolver( + #[cfg(target_os = "macos")] + pub fn deactivate_custom_resolver( &mut self, enable_resolver: bool, ) -> Result<(), crate::resolver::Error> { - if enable_resolver { - self.runtime.block_on(self.custom_resolver.set_inactive())?; - } else { - self.runtime.block_on(self.custom_resolver.shutdown())?; - } self.enable_custom_resolver = enable_resolver; - Ok(()) + self.disable_custom_resolver() } + #[cfg(target_os = "macos")] pub fn disable_custom_resolver(&mut self) -> Result<(), crate::resolver::Error> { if self.enable_custom_resolver { self.runtime.block_on(self.custom_resolver.set_inactive())?; } else { self.runtime.block_on(self.custom_resolver.shutdown())?; } - Ok(()) + self.dns_monitor + .reset() + .map_err(crate::resolver::Error::SystemDnsError) } pub fn set_allowed_endpoint(&mut self, endpoint: AllowedEndpoint) -> bool { @@ -536,50 +534,13 @@ impl SharedTunnelStateValues { } #[cfg(target_os = "macos")] - pub fn start_custom_resolver( + pub fn get_custom_resolver_config( &mut self, - ) -> ( - Option<crate::resolver::ResolverStateToggleResult>, - BTreeSet<IpAddr>, - ) { + ) -> Result<Option<(String, Vec<IpAddr>)>, crate::dns::Error> { if self.enable_custom_resolver { - // TODO: enable custom resolver - match self.dns_monitor.get_system_config() { - Ok(system_resolvers) => { - match self - .runtime - .block_on(self.custom_resolver.set_active(system_resolvers)) - { - Ok(result) => { - if let Err(err) = - self.dns_monitor.set("lo", &[Ipv4Addr::LOCALHOST.into()]) - { - log::error!( - "{}", - err.display_chain_with_msg( - "Failed to configure system to use custom resolver" - ) - ); - } - let allowed_resolvers = result.currently_used_resolvers.clone(); - (Some(result), allowed_resolvers) - } - Err(err) => { - log::error!("Failed to get DNS {}", err); - (None, BTreeSet::new()) - } - } - } - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg("Failed to obtain system DNS config") - ); - (None, BTreeSet::new()) - } - } + self.dns_monitor.get_system_config() } else { - (None, BTreeSet::new()) + Ok(None) } } } diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs index 4a0badb2b7..8b63295693 100644 --- a/talpid-types/src/tunnel.rs +++ b/talpid-types/src/tunnel.rs @@ -109,6 +109,19 @@ pub enum ErrorStateCause { /// Failed to set set custom resolver #[cfg(target_os = "macos")] CustomResolverError, + /// Failed read system DNS config + #[cfg(target_os = "macos")] + ReadSystemDnsConfig, +} + +impl ErrorStateCause { + #[cfg(target_os = "macos")] + pub fn prevents_custom_resolver(&self) -> bool { + match self { + Self::CustomResolverError | Self::ReadSystemDnsConfig | Self::SetDnsError => true, + _ => false, + } + } } /// Errors that can occur when generating tunnel parameters. @@ -203,6 +216,8 @@ impl fmt::Display for ErrorStateCause { SplitTunnelError => "The split tunneling module reported an error", #[cfg(target_os = "macos")] CustomResolverError => "Failed to set up custom resolver", + #[cfg(target_os = "macos")] + ReadSystemDnsConfig => "Failed to read system DNS config", }; write!(f, "{}", description) |
