diff options
| author | Emīls <emils@mullvad.net> | 2021-12-14 15:05:47 +0000 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2022-01-14 14:42:53 +0000 |
| commit | f46e257b5a0de0b530016672773ecda5d08de9df (patch) | |
| tree | a49c6d32ac3bf56cd1332a34f74388607d8dce8e /talpid-core/src | |
| parent | c0396210ad8bd9e6e51c4b36975fa7a9de3270ff (diff) | |
| download | mullvadvpn-f46e257b5a0de0b530016672773ecda5d08de9df.tar.xz mullvadvpn-f46e257b5a0de0b530016672773ecda5d08de9df.zip | |
Simplify custom resolver to not leak any traffic
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/dns/macos.rs | 29 | ||||
| -rw-r--r-- | talpid-core/src/dns/mod.rs | 11 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 92 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 10 | ||||
| -rw-r--r-- | talpid-core/src/resolver.rs | 340 | ||||
| -rw-r--r-- | talpid-core/src/resolver/mod.rs | 822 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 12 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 19 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 155 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 41 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 200 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 51 |
12 files changed, 416 insertions, 1366 deletions
diff --git a/talpid-core/src/dns/macos.rs b/talpid-core/src/dns/macos.rs index b3d2a5280f..a33e1d0a4a 100644 --- a/talpid-core/src/dns/macos.rs +++ b/talpid-core/src/dns/macos.rs @@ -1,5 +1,3 @@ -use crate::tunnel_state_machine::TunnelCommand; -use futures::channel::mpsc; use parking_lot::Mutex; use std::{ collections::HashMap, @@ -62,25 +60,6 @@ struct State { dns_settings: DnsSettings, /// The backup of all DNS settings. These are being applied back on reset. backup: HashMap<ServicePath, Option<DnsSettings>>, - /// Tunnel command sender for reporting updates to the system DNS config - tunnel_tx: std::sync::Weak<mpsc::UnboundedSender<crate::tunnel_state_machine::TunnelCommand>>, -} - -impl State { - fn send_new_config(&self) { - if let Some(tunnel_tx) = self.tunnel_tx.upgrade() { - match parse_sc_config(&self.backup) { - Ok(config) => { - // TODO: do better filtering to get the best resolver - let _ = tunnel_tx - .unbounded_send(TunnelCommand::HostDnsConfig(config.into_iter().next())); - } - Err(err) => { - log::error!("Failed to parse host's DNS config: {}", err); - } - }; - } - } } /// Holds the configuration for one service. @@ -223,8 +202,6 @@ pub struct DnsMonitor { /// When it's `Some(state)` we are actively making sure `state.dns_settings` is configured /// on all network interfaces. state: Arc<Mutex<Option<State>>>, - - tunnel_tx: std::sync::Weak<mpsc::UnboundedSender<crate::tunnel_state_machine::TunnelCommand>>, } /// SAFETY: The `SCDynamicStore` can be sent to other threads since it doesn't share mutable state @@ -238,13 +215,12 @@ impl super::DnsMonitorT for DnsMonitor { /// DNS settings for all network interfaces. If any changes occur it will instantly reset /// the DNS settings for that interface back to the last server list set to this instance /// with `set_dns`. - fn new(tunnel_tx: std::sync::Weak<mpsc::UnboundedSender<TunnelCommand>>) -> Result<Self> { + fn new() -> Result<Self> { let state = Arc::new(Mutex::new(None)); Self::spawn(state.clone())?; Ok(DnsMonitor { store: SCDynamicStoreBuilder::new("mullvad-dns").build(), state, - tunnel_tx, }) } @@ -262,7 +238,6 @@ impl super::DnsMonitorT for DnsMonitor { State { dns_settings: settings, backup, - tunnel_tx: self.tunnel_tx.clone(), } } Some(state) => { @@ -273,7 +248,6 @@ impl super::DnsMonitorT for DnsMonitor { State { dns_settings: settings, backup: state.backup, - tunnel_tx: self.tunnel_tx.clone(), } } else { log::debug!("No change, new DNS same as the one already set"); @@ -405,7 +379,6 @@ 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 => { diff --git a/talpid-core/src/dns/mod.rs b/talpid-core/src/dns/mod.rs index 5a7b5c0dec..a878e01ae8 100644 --- a/talpid-core/src/dns/mod.rs +++ b/talpid-core/src/dns/mod.rs @@ -33,9 +33,6 @@ impl DnsMonitor { pub fn new( #[cfg(target_os = "linux")] handle: tokio::runtime::Handle, #[cfg(target_os = "linux")] route_manager: RouteManagerHandle, - #[cfg(target_os = "macos")] command_tx: std::sync::Weak< - futures::channel::mpsc::UnboundedSender<crate::tunnel_state_machine::TunnelCommand>, - >, ) -> Result<Self, Error> { Ok(DnsMonitor { inner: imp::DnsMonitor::new( @@ -43,8 +40,6 @@ impl DnsMonitor { handle, #[cfg(target_os = "linux")] route_manager, - #[cfg(target_os = "macos")] - command_tx, )?, }) } @@ -87,11 +82,7 @@ trait DnsMonitorT: Sized { ) -> Result<Self, Self::Error>; #[cfg(not(target_os = "linux"))] - fn new( - #[cfg(target_os = "macos")] command_tx: std::sync::Weak< - futures::channel::mpsc::UnboundedSender<crate::tunnel_state_machine::TunnelCommand>, - >, - ) -> Result<Self, Self::Error>; + fn new() -> Result<Self, Self::Error>; fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Self::Error>; diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index b8672a1f92..fc81e47db4 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -2,7 +2,6 @@ use super::{FirewallArguments, FirewallPolicy, FirewallT}; use ipnetwork::IpNetwork; use pfctl::{DropAction, FilterRuleAction, Uid}; use std::{ - collections::BTreeSet, env, net::{IpAddr, Ipv4Addr}, }; @@ -21,7 +20,8 @@ pub struct Firewall { pf: pfctl::PfCtl, pf_was_enabled: Option<bool>, rule_logging: RuleLogging, - exclusion_gid: u32, + /// An exclusion group ID may be used in the future to help split tunneling in the future. + _exclusion_gid: u32, } impl FirewallT for Firewall { @@ -43,7 +43,7 @@ impl FirewallT for Firewall { pf: pfctl::PfCtl::new()?, pf_was_enabled: None, rule_logging, - exclusion_gid: args.exclusion_gid, + _exclusion_gid: args.exclusion_gid, }) } @@ -70,7 +70,7 @@ impl Firewall { new_filter_rules.append(&mut self.get_allow_loopback_rules()?); new_filter_rules.append(&mut self.get_allow_dhcp_client_rules()?); new_filter_rules.append(&mut self.get_allow_ndp_rules()?); - new_filter_rules.append(&mut self.get_policy_specific_rules(policy)?); + new_filter_rules.append(&mut self.get_policy_specific_rules(&policy)?); let return_out_rule = self .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) @@ -87,12 +87,34 @@ impl Firewall { let mut anchor_change = pfctl::AnchorChange::new(); anchor_change.set_filter_rules(new_filter_rules); + anchor_change.set_redirect_rules(self.get_dns_redirect_rules(&policy)?); Ok(self.pf.set_rules(ANCHOR_NAME, anchor_change)?) } + fn get_dns_redirect_rules( + &mut self, + policy: &FirewallPolicy, + ) -> Result<Vec<pfctl::RedirectRule>> { + let redirect_rules = match policy { + FirewallPolicy::Blocked { + dns_redirect_port, .. + } => { + vec![pfctl::RedirectRuleBuilder::default() + .action(pfctl::RedirectRuleAction::Redirect) + .interface("lo0") + .proto(pfctl::Proto::Udp) + .to(pfctl::Port::from(53)) + .redirect_to(pfctl::Port::from(*dns_redirect_port)) + .build()?] + } + _ => vec![], + }; + Ok(redirect_rules) + } + fn get_policy_specific_rules( &mut self, - policy: FirewallPolicy, + policy: &FirewallPolicy, ) -> Result<Vec<pfctl::FilterRule>> { match policy { FirewallPolicy::Connecting { @@ -101,7 +123,7 @@ impl Firewall { allow_lan, allowed_endpoint, } => { - let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?]; + let mut rules = vec![self.get_allow_relay_rule(*peer_endpoint)?]; rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); // Important to block DNS after allow relay rule (so the relay can operate @@ -112,7 +134,7 @@ impl Firewall { rules.push(self.get_allow_tunnel_rule(&tunnel.interface)?); } - if allow_lan { + if *allow_lan { rules.append(&mut self.get_allow_lan_rules()?); } Ok(rules) @@ -125,11 +147,11 @@ impl Firewall { } => { let mut rules = vec![]; - for server in &dns_servers { + for server in dns_servers.iter() { rules.append(&mut self.get_allow_dns_rules_when_connected(&tunnel, *server)?); } - rules.push(self.get_allow_relay_rule(peer_endpoint)?); + rules.push(self.get_allow_relay_rule(*peer_endpoint)?); // Important to block DNS *before* we allow the tunnel and allow LAN. So DNS // can't leak to the wrong IPs in the tunnel or on the LAN. @@ -137,7 +159,7 @@ impl Firewall { rules.push(self.get_allow_tunnel_rule(tunnel.interface.as_str())?); - if allow_lan { + if *allow_lan { rules.append(&mut self.get_allow_lan_rules()?); } @@ -146,17 +168,12 @@ impl Firewall { FirewallPolicy::Blocked { allow_lan, allowed_endpoint, - allowed_ips, - allow_gid_exclusion_traffic, + .. } => { let mut rules = Vec::new(); rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); - if allow_gid_exclusion_traffic { - rules.extend(self.get_allow_excluded_dns_rules()?); - rules.extend(self.get_exclusion_rules(&allowed_ips)?); - } - if allow_lan { + if *allow_lan { // Important to block DNS before allow LAN (so DNS does not leak to the LAN) rules.append(&mut self.get_block_dns_rules()?); rules.append(&mut self.get_allow_lan_rules()?); @@ -167,26 +184,6 @@ impl Firewall { } } - /// Constructs rules that allow DNS traffic coming from processes that belong to the excluded - /// group ID to leak. - fn get_allow_excluded_dns_rules(&self) -> Result<[pfctl::FilterRule; 2]> { - let mut builder = self.create_rule_builder(FilterRuleAction::Pass); - - builder.direction(pfctl::Direction::Out); - builder.quick(true); - builder.keep_state(pfctl::StatePolicy::Keep); - builder.to(pfctl::Port::from(53)); - builder.group(self.exclusion_gid); - - Ok([ - builder.proto(pfctl::Proto::Udp).build()?, - builder - .proto(pfctl::Proto::Tcp) - .tcp_flags(Self::get_tcp_flags()) - .build()?, - ]) - } - fn get_allow_dns_rules_when_connected( &self, tunnel: &crate::tunnel::TunnelMetadata, @@ -344,27 +341,6 @@ impl Firewall { Ok(vec![lo0_rule]) } - /// Constructs firewall rules that allow traffic to a set of allowed IP addresses coming from - /// UID 0 processes to leak. - fn get_exclusion_rules( - &self, - allowed_ips: &BTreeSet<IpAddr>, - ) -> Result<Vec<pfctl::FilterRule>> { - let mut vec = Vec::with_capacity(allowed_ips.len()); - for ip in allowed_ips.iter() { - vec.push( - self.create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .to(*ip) - .quick(true) - .user(Uid::from(super::ROOT_UID)) - .keep_state(pfctl::StatePolicy::Keep) - .build()?, - ); - } - Ok(vec) - } - fn get_allow_lan_rules(&self) -> Result<Vec<pfctl::FilterRule>> { let mut rules = vec![]; for net in &*super::ALLOWED_LAN_NETS { diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 761691e216..7231f8f20c 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -2,8 +2,6 @@ use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; #[cfg(unix)] use lazy_static::lazy_static; -#[cfg(target_os = "macos")] -use std::collections::BTreeSet; use std::fmt; #[cfg(not(target_os = "android"))] use std::net::IpAddr; @@ -140,12 +138,10 @@ pub enum FirewallPolicy { allow_lan: bool, /// Host that should be reachable while in the blocked state. allowed_endpoint: AllowedEndpoint, - /// A list of IPs that can be reached outside the tunnel. - #[cfg(target_os = "macos")] - allowed_ips: BTreeSet<IpAddr>, - /// Enables specific GID exclusion traffic + /// Desination port for DNS traffic redirection. Traffic destined to `127.0.0.1:53` will be + /// redirected to `127.0.0.1:$dns_redirect_port`. #[cfg(target_os = "macos")] - allow_gid_exclusion_traffic: bool, + dns_redirect_port: u16, }, } diff --git a/talpid-core/src/resolver.rs b/talpid-core/src/resolver.rs new file mode 100644 index 0000000000..8338fe2ed6 --- /dev/null +++ b/talpid-core/src/resolver.rs @@ -0,0 +1,340 @@ +use std::{ + io, + net::{Ipv4Addr, SocketAddr}, + str::FromStr, + sync::{Arc, Weak}, +}; + +use std::time::{Duration, Instant}; + +use futures::{ + channel::{mpsc, oneshot}, + SinkExt, StreamExt, +}; + +use trust_dns_server::{ + authority::{ + EmptyLookup, LookupObject, MessageRequest, MessageResponse, MessageResponseBuilder, + }, + client::{ + op::LowerQuery, + rr::{LowerName, RecordType}, + }, + proto::{ + op::{header::MessageType, op_code::OpCode, Header}, + rr::{domain::Name, record_data::RData, Record}, + }, + resolver::lookup::Lookup, + server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, + ServerFuture, +}; + +const ALLOWED_RECORD_TYPES: &[RecordType] = &[RecordType::A, RecordType::AAAA, RecordType::CNAME]; +const CAPTIVE_PORTAL_DOMAIN: &str = "captive.apple.com"; +const TTL_SECONDS: u32 = 3; +/// An IP address to be used in the DNS response to the captive domain query. The address itself +/// belongs to the documentation range so should never be reachable. +const RESOLVED_ADDR: Ipv4Addr = Ipv4Addr::new(198, 51, 100, 1); + +/// Starts a resolver. Returns a cloneable handle, which can activate, deactivate and shut down the +/// resolver. When all instances of a handle are dropped, the server will stop. +pub(crate) async fn start_resolver() -> Result<ResolverHandle, Error> { + let (resolver, resolver_handle) = FilteringResolver::new().await?; + tokio::spawn(resolver.run()); + Ok(resolver_handle) +} + +/// Resolver errors +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + /// Failed to bind UDP socket + #[error(display = "Failed to bind UDP socket")] + UdpBindError(#[error(source)] io::Error), + + /// Failed to get local address of a bound UDP socket + #[error(display = "Failed to get local address of a bound UDP socket")] + GetSocketAddrError(#[error(source)] io::Error), +} + +/// A filtering resolver. Listens on a specified port for DNS queries and responds queries for +/// `catpive.apple.com`. Can be toggled to unbind, be bound but not respond or bound and responding +/// to some queries. +struct FilteringResolver { + rx: mpsc::Receiver<ResolverMessage>, + dns_server: Option<(tokio::task::JoinHandle<()>, oneshot::Receiver<()>)>, +} + +/// The `FilteringResolver` is an actor responding to DNS queries. +type ResolverMessage = (LowerQuery, oneshot::Sender<Box<dyn LookupObject>>); + +/// A handle to control a filtering resolver. When all resolver handles are dropped, custom +/// resolver will stop. +#[derive(Clone)] +pub(crate) struct ResolverHandle { + _tx: Arc<mpsc::Sender<ResolverMessage>>, + listening_port: u16, +} + +impl ResolverHandle { + fn new(tx: Arc<mpsc::Sender<ResolverMessage>>, listening_port: u16) -> Self { + Self { + _tx: tx, + listening_port, + } + } + + /// Get listening port for resolver handle + pub fn listening_port(&self) -> u16 { + self.listening_port + } +} + +impl FilteringResolver { + /// Constructs a new filtering resolver and it's handle. + async fn new() -> Result<(Self, ResolverHandle), Error> { + let (tx, rx) = mpsc::channel(0); + let command_tx = Arc::new(tx); + + let mut server = ServerFuture::new(ResolverImpl { + tx: Arc::downgrade(&command_tx), + }); + + let server_listening_socket = + tokio::net::UdpSocket::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0)) + .await + .map_err(Error::UdpBindError)?; + let port = server_listening_socket + .local_addr() + .map_err(Error::GetSocketAddrError)? + .port(); + server.register_socket(server_listening_socket); + + let (server_done_tx, server_done_rx) = oneshot::channel(); + let server_handle = tokio::spawn(async move { + if let Err(err) = server.block_until_done().await { + log::error!("DNS server stopped: {}", err); + } + + let _ = server_done_tx.send(()); + }); + let resolver = Self { + rx, + dns_server: Some((server_handle, server_done_rx)), + }; + + Ok((resolver, ResolverHandle::new(command_tx, port))) + } + + /// Runs the filtering resolver as an actor, listening for new queries instances. When all + /// related [ResolverHandle] instances are dropped, this function will return, closing the DNS + /// server. + async fn run(mut self) { + while let Some((query, tx)) = self.rx.next().await { + self.resolve(query, tx); + } + + if let Some((server_handle, done_rx)) = self.dns_server.take() { + server_handle.abort(); + let _ = done_rx.await; + } + } + + /// Resolvers a query to nothing or a documentation address + fn resolve(&mut self, query: LowerQuery, tx: oneshot::Sender<Box<dyn LookupObject>>) { + if !self.allow_query(&query) { + let _ = tx.send(Box::new(EmptyLookup) as Box<dyn LookupObject>); + return; + } + + let return_query = query.original().clone(); + let mut return_record = Record::with( + return_query.name().clone(), + return_query.query_type(), + TTL_SECONDS, + ); + return_record.set_rdata(RData::A(RESOLVED_ADDR)); + + let lookup = Lookup::new_with_deadline( + return_query, + Arc::new([return_record]), + Instant::now() + Duration::from_secs(3), + ); + let _ = tx.send(Box::new(ForwardLookup(lookup))); + } + + /// Determines whether a DNS query is allowable. Currently, this implies that the query is + /// either a `A`, `AAAA` or a `CNAME` query for `captive.apple.com`. + fn allow_query(&self, query: &LowerQuery) -> bool { + let captive_apple_com: LowerName = + LowerName::from(Name::from_str(CAPTIVE_PORTAL_DOMAIN).unwrap()); + ALLOWED_RECORD_TYPES.contains(&query.query_type()) && query.name() == &captive_apple_com + } +} + +/// An implementation of [trust_dns_server::server::RequestHandler] that forwards queries to +/// `FilteringResolver`. +struct ResolverImpl { + tx: Weak<mpsc::Sender<ResolverMessage>>, +} + +impl ResolverImpl { + fn build_response<'a>( + message: &'a MessageRequest, + lookup: &'a mut Box<dyn LookupObject>, + ) -> MessageResponse<'a, 'a> { + let mut response_header = Header::new(); + response_header.set_id(message.id()); + response_header.set_op_code(OpCode::Query); + response_header.set_message_type(MessageType::Response); + response_header.set_authoritative(false); + + MessageResponseBuilder::from_message_request(message).build( + response_header, + lookup.iter(), + // forwarder responses only contain query answers, no ns,soa or additionals + Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, + Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, + Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, + ) + } + + async fn lookup<R: ResponseHandler>(&self, message: &Request, mut response_handler: R) { + if let Some(tx_ref) = self.tx.upgrade() { + let mut tx = (&*tx_ref).clone(); + let query = message.query(); + let (lookup_tx, lookup_rx) = oneshot::channel(); + let _ = tx.send((query.clone(), lookup_tx)).await; + let mut lookup_result: Box<dyn LookupObject> = lookup_rx + .await + .unwrap_or_else(|_| Box::new(EmptyLookup) as Box<dyn LookupObject>); + let response = Self::build_response(&message, &mut lookup_result); + + if let Err(err) = response_handler.send_response(response).await { + log::error!("Failed to send response: {}", err); + } + } + } +} + +#[async_trait::async_trait] +impl RequestHandler for ResolverImpl { + async fn handle_request<R: ResponseHandler>( + &self, + request: &Request, + response_handle: R, + ) -> ResponseInfo { + if !request.src().ip().is_loopback() { + log::error!("Dropping a stray request from outside: {}", request.src()); + return Header::new().into(); + } + if let MessageType::Query = request.message_type() { + match request.op_code() { + OpCode::Query => { + self.lookup(request, response_handle).await; + } + _ => { + log::trace!("Dropping non-query request: {:?}", request); + } + }; + } + + return Header::new().into(); + } +} + +struct ForwardLookup(Lookup); + +/// This trait has to be reimplemented for the Lookup so that it can be sent back to the +/// RequestHandler implementation. +impl LookupObject for ForwardLookup { + fn is_empty(&self) -> bool { + self.0.is_empty() + } + + fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> { + Box::new(self.0.record_iter()) + } + + fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> { + None + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::{mem, net::UdpSocket, thread, time::Duration}; + use trust_dns_server::{ + proto, + resolver::{ + config::{NameServerConfigGroup, ResolverConfig, ResolverOpts}, + AsyncResolver, + }, + }; + + async fn start_resolver() -> ResolverHandle { + super::start_resolver().await.unwrap() + } + + async fn get_test_resolver(port: u16) -> trust_dns_server::resolver::TokioAsyncResolver { + let resolver_config = ResolverConfig::from_parts( + None, + vec![], + NameServerConfigGroup::from_ips_clear(&[Ipv4Addr::LOCALHOST.into()], port, true), + ); + AsyncResolver::new( + resolver_config, + ResolverOpts::default(), + proto::TokioRuntime, + ) + .unwrap() + } + + #[test] + fn test_successful_lookup() { + let rt = tokio::runtime::Runtime::new().unwrap(); + let handle = rt.block_on(start_resolver()); + let test_resolver = rt.block_on(get_test_resolver(handle.listening_port())); + + let captive_portal_domain = LowerName::from(Name::from_str(CAPTIVE_PORTAL_DOMAIN).unwrap()); + let resolver_result = rt.block_on(async move { + let dns_request = + test_resolver.lookup(captive_portal_domain, RecordType::A, Default::default()); + + dns_request.await + }); + resolver_result.expect("Failed to resolve test domain"); + } + + #[test] + fn test_failed_lookup() { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let handle = rt.block_on(start_resolver()); + let test_resolver = rt.block_on(get_test_resolver(handle.listening_port())); + + let captive_portal_domain = LowerName::from(Name::from_str("apple.com").unwrap()); + let resolver_result = rt.block_on(async move { + test_resolver + .lookup(captive_portal_domain, RecordType::A, Default::default()) + .await + }); + assert!( + resolver_result.is_err(), + "Non-whitelisted DNS request should fail" + ) + } + + #[test] + fn test_shutdown() { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let handle = rt.block_on(start_resolver()); + let port = handle.listening_port(); + mem::drop(handle); + thread::sleep(Duration::from_millis(300)); + UdpSocket::bind((Ipv4Addr::LOCALHOST, port)) + .expect("Failed to bind to a port that should have been removed"); + } +} diff --git a/talpid-core/src/resolver/mod.rs b/talpid-core/src/resolver/mod.rs deleted file mode 100644 index 8e2731346b..0000000000 --- a/talpid-core/src/resolver/mod.rs +++ /dev/null @@ -1,822 +0,0 @@ -use socket2::{Domain, Socket, Type}; - -use std::{ - collections::BTreeSet, - ffi::CString, - future::Future, - io, - net::{IpAddr, Ipv4Addr, SocketAddr}, - pin::Pin, - str::FromStr, - sync::{Arc, Mutex, Weak}, -}; - -#[cfg(target_os = "macos")] -use std::{ - net, - num::NonZeroU32, - os::unix::io::{FromRawFd, IntoRawFd, RawFd}, -}; - -use futures::{ - channel::{mpsc, oneshot}, - future::Either, - SinkExt, StreamExt, -}; - -use crate::tunnel_state_machine::TunnelCommand; -use trust_dns_server::{ - authority::{ - EmptyLookup, LookupObject, MessageRequest, MessageResponse, MessageResponseBuilder, - }, - client::{ - op::LowerQuery, - rr::{LowerName, RecordType}, - }, - proto::{ - self, - iocompat::AsyncIoTokioAsStd, - op::{header::MessageType, op_code::OpCode, Header}, - rr::{domain::Name, record_data::RData, Record}, - TokioTime, - }, - resolver::{ - config::{NameServerConfigGroup, ResolverConfig, ResolverOpts}, - error::ResolveError, - lookup::Lookup, - name_server::{GenericConnection, GenericConnectionProvider}, - AsyncResolver, - }, - server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, - ServerFuture, -}; - -const ALLOWED_RECORD_TYPES: &[RecordType] = &[RecordType::A, RecordType::AAAA, RecordType::CNAME]; -const CAPTIVE_PORTAL_DOMAIN: &str = "captive.apple.com"; - -type TunnelCommandSender = Weak<mpsc::UnboundedSender<TunnelCommand>>; - -/// Starts a resolver. Returns a cloneable handle, which can activate, deactivate and shut down the -/// resolver. When all instances of a handle are dropped, the server will stop. -pub(crate) async fn start_resolver(sender: TunnelCommandSender) -> Result<ResolverHandle, Error> { - start_resolver_inner(sender, 53).await -} - -async fn start_resolver_inner( - tunnel_tx: TunnelCommandSender, - port: u16, -) -> Result<ResolverHandle, Error> { - let (resolver, resolver_handle) = FilteringResolver::new(tunnel_tx, port).await?; - tokio::spawn(resolver.run()); - Ok(resolver_handle) -} - -/// Resolver errors -#[derive(err_derive::Error, Debug)] -#[error(no_from)] -pub enum Error { - /// Failed to launch resolver - #[error(display = "Failed to launch resolver")] - LaunchResolver(#[error(source)] ResolveError), - - /// Failed to bind TCP socket - #[error(display = "Failed to bind TCP socket")] - TcpBindError(#[error(source)] io::Error), - - /// Failed to bind UDP socket - #[error(display = "Failed to bind UDP socket")] - UdpBindError(#[error(source)] io::Error), - - /// Launcher thread panicked - #[error(display = "Panic in the launcher thread")] - LauncherThreadPanic, - - /// The resolver has already shut down - #[error(display = "Resolver is already shut down")] - ResolverShutdown, - - /// 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) - } -} - -/// A filtering resolver. Listens on a specified port for DNS queries and responds queries for -/// `catpive.apple.com`. Can be toggled to unbind, be bound but not respond or bound and responding -/// to some queries. -struct FilteringResolver { - excluded_resolver: ExcludedUpstreamResolver, - rx: mpsc::Receiver<ResolverMessage>, - resolver_state: ResolverState, - tunnel_tx: TunnelCommandSender, - dns_server: Option<(tokio::task::JoinHandle<()>, oneshot::Receiver<()>)>, - command_sender: Weak<mpsc::Sender<ResolverMessage>>, - runtime_provider: RuntimeProvider, - port: u16, -} - -type OurConnectionProvider = GenericConnectionProvider<RuntimeProvider>; -type ExcludedUpstreamResolver = AsyncResolver<GenericConnection, OurConnectionProvider>; - -/// Resolver state -#[derive(Debug, PartialEq, Clone)] -enum ResolverState { - /// When in an active state, the resolver needs a set of upstream resolvers and the name of the - /// interface it should bind to. - Active(Option<(String, Vec<IpAddr>)>), - /// In the inactive state, the resolver is still listening for DNS queries but it won't be - /// responding to any of them - Inactive, - /// In the shutdown state the resolver is unbound and not listening to queries. - Shutdown, -} - -impl ResolverState { - fn is_running(&self) -> bool { - match self { - Self::Active(_) => true, - _ => false, - } - } -} - -/// The `FilteringResolver` is an actor responding to 2 types of messages: either it's a new DNS -/// query or it's a message to toggle it's state. -enum ResolverMessage { - /// A new DNS query coming in from listener. - Request(LowerQuery, oneshot::Sender<Box<dyn LookupObject>>), - /// Set the resolver's state. - SetResolverState(ResolverState, oneshot::Sender<Result<(), Error>>), -} - -/// A handle to control a filtering resolver -#[derive(Clone)] -pub(crate) struct ResolverHandle { - tx: Arc<mpsc::Sender<ResolverMessage>>, -} - -impl ResolverHandle { - fn new(tx: Arc<mpsc::Sender<ResolverMessage>>) -> Self { - Self { tx } - } - - /// Activate the resolver to have it respond to allowed DNS queries. - pub async fn set_active(&self, config: Option<(String, Vec<IpAddr>)>) -> Result<(), Error> { - self.set_state(ResolverState::Active(config)).await - } - - /// De-activate the resolver to have it ignore all DNS queries. - pub async fn set_inactive(&self) -> Result<(), Error> { - self.set_state(ResolverState::Inactive).await - } - - /// Shut down the resolver so that it no longer listens on port 53. - pub async fn shutdown(&self) -> Result<(), Error> { - self.set_state(ResolverState::Shutdown).await - } - - 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(); - tx.send(ResolverMessage::SetResolverState(state, done_tx)) - .await - .map_err(|_| Error::ResolverShutdown)?; - - done_rx.await.map_err(|_| Error::ResolverShutdown)? - } -} - -impl FilteringResolver { - /// Constructs a new filtering resolver and it's handle. - async fn new( - tunnel_tx: TunnelCommandSender, - port: u16, - ) -> Result<(Self, ResolverHandle), Error> { - let (tx, rx) = mpsc::channel(0); - let command_tx = Arc::new(tx); - - let runtime_provider = RuntimeProvider::new(); - - let resolver_config = ResolverConfig::from_parts( - None, - vec![], - NameServerConfigGroup::from_ips_clear(&[], 53, false), - ); - let resolver = ExcludedUpstreamResolver::new( - resolver_config.clone(), - ResolverOpts::default(), - runtime_provider.clone(), - ) - .map_err(Error::LaunchResolver)?; - - let resolver = Self { - excluded_resolver: resolver, - resolver_state: ResolverState::Shutdown, - rx, - tunnel_tx, - command_sender: Arc::downgrade(&command_tx), - dns_server: None, - runtime_provider, - port, - }; - - Ok((resolver, ResolverHandle::new(command_tx))) - } - - /// Runs the filtering resolver as an actor, listening for new [ResolverMessage] instances. - /// When all related [ResolverHandle] instances are dropped, this function will return. - async fn run(mut self) { - use ResolverMessage::*; - while let Some(message) = self.rx.next().await { - match message { - Request(query, tx) => { - if self.resolver_state.is_running() { - tokio::spawn(self.resolve(query, tx)); - } - } - SetResolverState(resolver_state, tx) => { - match resolver_state { - ResolverState::Shutdown => { - self.stop_server().await; - self.resolver_state = ResolverState::Shutdown; - } - running_state => { - if self.dns_server.is_none() { - if let Err(err) = self.spawn_new_server().await { - let _ = tx.send(Err(err)); - let _ = self.reset_resolver().await; - continue; - } - } - self.resolver_state = running_state; - } - } - match self.reset_resolver().await { - Ok(_) => { - let _ = tx.send(Ok(())); - } - Err(err) => { - let _ = tx.send(Err(err)); - } - } - } - } - } - - std::mem::drop(self); - } - - /// Spawns a new [trust_dns_server::server::ServerFuture], used whenever moving away from the - /// [ResolverState::Shutdown] state. - async fn spawn_new_server(&mut self) -> Result<(), Error> { - self.stop_server().await; - if let Some(tx) = self.command_sender.upgrade() { - let resolver_handle = ResolverImpl { tx }; - let mut server = ServerFuture::new(resolver_handle); - let listening_addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), self.port); - let udp_sock = tokio::net::UdpSocket::bind(listening_addr) - .await - .map_err(Error::UdpBindError)?; - let tcp_sock = tokio::net::TcpListener::bind(listening_addr) - .await - .map_err(Error::TcpBindError)?; - server.register_socket(udp_sock); - server.register_listener(tcp_sock, std::time::Duration::from_secs(1)); - - let (server_done_tx, server_done_rx) = oneshot::channel(); - let server_handle = tokio::spawn(async move { - if let Err(err) = server.block_until_done().await { - log::error!("DNS server stopped: {}", err); - } - let _ = server_done_tx.send(()); - }); - - self.dns_server = Some((server_handle, server_done_rx)); - } - Ok(()) - } - - /// Tries to stop the server future as best as it can. - async fn stop_server(&mut self) { - if let Some((old_server, done_rx)) = self.dns_server.take() { - old_server.abort(); - if done_rx.await.is_err() { - log::error!("Server future was already stopped"); - } - } - } - - /// Resets the current upstream resolver to clear it's config. - async fn reset_resolver(&mut self) -> Result<(), Error> { - log::trace!("Resetting filtering resolver"); - let (best_interface, resolver_addresses) = self.get_resolver_config(); - self.runtime_provider.update_best_interface(best_interface); - let resolver_config = ResolverConfig::from_parts( - None, - vec![], - NameServerConfigGroup::from_ips_clear(resolver_addresses, 53, false), - ); - let mut resolver_options = ResolverOpts::default(); - resolver_options.preserve_intermediates = true; - let resolver = AsyncResolver::new( - resolver_config.clone(), - resolver_options, - self.runtime_provider.clone(), - ) - .map_err(Error::LaunchResolver)?; - self.excluded_resolver = resolver; - Ok(()) - } - - /// Gets the best interface to use and a list of upstream resolver addresses to use when - /// resolving domains. Returns an empty config if the current resolver state isn't - /// [ResolverState::Active]. - fn get_resolver_config(&self) -> (&str, &[IpAddr]) { - match &self.resolver_state { - ResolverState::Active(ref resolvers) => resolvers - .as_ref() - .filter(|(_, addresses)| !addresses.iter().any(|ip| ip.is_loopback())) - .map(|(interface_name, addresses)| (interface_name.as_str(), addresses.as_slice())) - .unwrap_or(("", &[])), - _ => ("", &[]), - } - } - - /// Constructs a lookup future for a given DNS query. - fn resolve( - &mut self, - query: LowerQuery, - tx: oneshot::Sender<Box<dyn LookupObject>>, - ) -> impl Future<Output = ()> { - let empty_response = Box::new(EmptyLookup) as Box<dyn LookupObject>; - if !self.should_service_request(&query) { - let _ = tx.send(empty_response); - return Either::Left(async {}); - } - - log::trace!("Looking up {}", query.name()); - - let unblock_tx = self.tunnel_tx.clone(); - let lookup: Box<dyn Future<Output = Result<Lookup, ResolveError>> + Unpin + Send> = - Box::new(self.excluded_resolver.lookup( - query.name().clone(), - query.query_type(), - Default::default(), - )); - let resolver_state = self.resolver_state.clone(); - Either::Right(async move { - match lookup.await { - Ok(result) => { - let lookup = ForwardLookup(result); - let ip_records = lookup - .iter() - .filter_map(|record| match record.rdata() { - RData::A(ipv4) => Some(IpAddr::from(*ipv4)), - RData::AAAA(ipv6) => Some(IpAddr::from(*ipv6)), - _ => None, - }) - .collect::<BTreeSet<_>>(); - - if !ip_records.is_empty() { - if resolver_state.is_running() { - Self::unblock_ips(unblock_tx, ip_records).await; - } - } - if tx.send(Box::new(lookup)).is_err() { - log::error!("Failed to send response to resolver"); - } - } - Err(err) => { - log::trace!("Failed to resolve {}: {}", query, err); - let _ = tx.send(empty_response); - } - } - }) - } - - /// Unblocks a set of addresses in the firewall by sending a message to the tunnel state - /// machine and waiting for completion. Be careful not to call this from the context of - /// [FilteringResolver::run] and instead call it in a different task, as otherwise a deadlock - /// will occur. - async fn unblock_ips(maybe_tx: TunnelCommandSender, addresses: BTreeSet<IpAddr>) { - let (done_tx, done_rx) = oneshot::channel(); - if maybe_tx - .upgrade() - .and_then(|tx| { - tx.unbounded_send(TunnelCommand::AddAllowedIps(addresses, done_tx)) - .ok() - }) - .is_some() - { - let _ = done_rx.await; - } else { - log::error!("Failed to send IPs to unblocker"); - } - } - - /// Determines whether a query should be responded to given the current state of the resolver - /// and if the query is valid. - fn should_service_request(&self, query: &LowerQuery) -> bool { - self.resolver_state.is_running() && self.allow_query(query) - } - - /// Determines whether a DNS query is allowable. Currently, this implies that the query is - /// either a `A`, `AAAA` or a `CNAME` query for `captive.apple.com`. - fn allow_query(&self, query: &LowerQuery) -> bool { - let captive_apple_com: LowerName = - LowerName::from(Name::from_str(CAPTIVE_PORTAL_DOMAIN).unwrap()); - ALLOWED_RECORD_TYPES.contains(&query.query_type()) && query.name() == &captive_apple_com - } -} - -/// An implementation of [trust_dns_server::server::RequestHandler] that forwards queries to -/// `FilteringResolver` as `ResolverMessage::Request` messages. -struct ResolverImpl { - tx: Arc<mpsc::Sender<ResolverMessage>>, -} - -impl ResolverImpl { - fn build_response<'a>( - message: &'a MessageRequest, - lookup: &'a mut Box<dyn LookupObject>, - ) -> MessageResponse<'a, 'a> { - let mut response_header = Header::new(); - response_header.set_id(message.id()); - response_header.set_op_code(OpCode::Query); - response_header.set_message_type(MessageType::Response); - response_header.set_authoritative(false); - - MessageResponseBuilder::from_message_request(message).build( - response_header, - lookup.iter(), - // forwarder responses only contain query answers, no ns,soa or additionals - Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, - Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, - Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _> + Send>, - ) - } - - async fn lookup<R: ResponseHandler>(&self, message: &Request, mut response_handler: R) { - let tx_ref: &mpsc::Sender<ResolverMessage> = &*self.tx; - let mut tx = tx_ref.clone(); - - let query = message.query(); - let (lookup_tx, lookup_rx) = oneshot::channel(); - let _ = tx - .send(ResolverMessage::Request(query.clone(), lookup_tx)) - .await; - let mut lookup_result: Box<dyn LookupObject> = lookup_rx - .await - .unwrap_or_else(|_| Box::new(EmptyLookup) as Box<dyn LookupObject>); - let response = Self::build_response(&message, &mut lookup_result); - - if let Err(err) = response_handler.send_response(response).await { - log::error!("Failed to send response: {}", err); - } - } -} - -#[async_trait::async_trait] -impl RequestHandler for ResolverImpl { - async fn handle_request<R: ResponseHandler>( - &self, - request: &Request, - response_handle: R, - ) -> ResponseInfo { - if !request.src().ip().is_loopback() { - log::error!("Dropping a stray request from outside: {}", request.src()); - return Header::new().into(); - } - if let MessageType::Query = request.message_type() { - match request.op_code() { - OpCode::Query => { - self.lookup(request, response_handle).await; - } - _ => { - log::trace!("Dropping non-query request: {:?}", request); - } - }; - } - - return Header::new().into(); - } -} - -/// RuntimeProvider is used to construct sockets to reach the upstream resolver. -#[derive(Clone)] -struct RuntimeProvider { - best_interface: Arc<Mutex<Option<NonZeroU32>>>, -} - -impl RuntimeProvider { - fn new() -> Self { - Self { - best_interface: Arc::new(Mutex::new(None)), - } - } - - fn update_best_interface(&self, best_interface: &str) { - let ifname = match CString::new(best_interface) { - Ok(name) => name, - Err(err) => { - log::error!("Failed to construct an interface name CString: {}", err); - return; - } - }; - if let Some(index) = NonZeroU32::new(unsafe { libc::if_nametoindex(ifname.as_ptr()) }) { - *self.best_interface.lock().unwrap() = Some(index); - } - } -} - -impl proto::runtime_provider::RuntimeProvider for RuntimeProvider { - type UdpSocket = tokio::net::UdpSocket; - type TcpConnection = AsyncIoTokioAsStd<tokio::net::TcpStream>; - type Time = TokioTime; - - fn connect_tcp( - &self, - addr: SocketAddr, - ) -> Pin<Box<dyn Future<Output = io::Result<Self::TcpConnection>> + Send>> { - let best_interface = self.best_interface.clone(); - - Box::pin(async move { - let raw_fd = open_socket(addr, Type::STREAM, socket2::Protocol::TCP, best_interface)?; - - let socket = unsafe { tokio::net::TcpSocket::from_raw_fd(raw_fd) }; - socket.connect(addr).await.map(AsyncIoTokioAsStd) - }) - } - - fn bind_udp( - &self, - addr: SocketAddr, - ) -> Pin<Box<dyn Future<Output = io::Result<Self::UdpSocket>> + Send>> { - let best_interface = self.best_interface.clone(); - Box::pin(async move { - let raw_fd = open_socket( - addr, - socket2::Type::DGRAM, - socket2::Protocol::UDP, - best_interface.clone(), - )?; - - let std_socket = unsafe { net::UdpSocket::from_raw_fd(raw_fd) }; - tokio::net::UdpSocket::from_std(std_socket) - }) - } - - fn spawn_bg<F>(&self, f: F) - where - F: Future<Output = Result<(), trust_dns_server::proto::error::ProtoError>> + Send + 'static, - { - tokio::spawn(f); - } -} - -fn open_socket( - addr: SocketAddr, - sock_type: Type, - protocol: socket2::Protocol, - best_interface: Arc<Mutex<Option<NonZeroU32>>>, -) -> io::Result<RawFd> { - let socket = Socket::new(Domain::for_address(addr), sock_type, Some(protocol))?; - - socket.set_nonblocking(true)?; - - match best_interface - .lock() - .expect("best interface lock poisoned") - .as_ref() - { - Some(iface_index) => { - if let Err(err) = socket.bind_device_by_index(Some(*iface_index)) { - log::error!("Failed to bind by index: {}", err); - return Err(err); - } - } - None => { - log::error!("Failed to get best interface index"); - } - }; - Ok(socket.into_raw_fd()) -} - -struct ForwardLookup(Lookup); - -/// This trait has to be reimplemented for the Lookup so that it can be sent back to the -/// RequestHandler implementation. -impl LookupObject for ForwardLookup { - fn is_empty(&self) -> bool { - self.0.is_empty() - } - - fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> { - Box::new(self.0.record_iter()) - } - - fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> { - None - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::{fs, net::UdpSocket, process::Command}; - use subslice::SubsliceExt; - - fn random_port() -> u16 { - let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); - socket.local_addr().unwrap().port() - } - - const NAMESERVER: &[u8] = b"nameserver"; - - fn read_resolvconf() -> Option<(String, Vec<IpAddr>)> { - let contents = fs::read("/etc/resolv.conf").unwrap(); - let nameserver_index = contents - .find(NAMESERVER) - .expect("Failed to read /etc/resolv.conf"); - let end = contents[nameserver_index..] - .find(b"\n") - .expect("no \n after nameserver") - + nameserver_index; - let ip_addr_subslice = &contents[nameserver_index + NAMESERVER.len()..end]; - - let resolver_ip = - IpAddr::from_str(std::str::from_utf8(ip_addr_subslice).unwrap().trim()).unwrap(); - let route_output = String::from_utf8( - Command::new("route") - .arg("get") - .arg(resolver_ip.to_string()) - .output() - .expect("Failed to run 'route get'") - .stdout, - ) - .unwrap(); - - let mut output_parts = route_output.split_whitespace(); - while let Some(part) = output_parts.next() { - if part.trim() == "interface:" { - return Some((output_parts.next().unwrap().to_string(), vec![resolver_ip])); - } - } - panic!("Couldn't deduce interface") - } - - async fn start_resolver() -> ( - ResolverHandle, - u16, - mpsc::UnboundedReceiver<TunnelCommand>, - Arc<mpsc::UnboundedSender<TunnelCommand>>, - ) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let tx = Arc::new(tx); - let port = random_port(); - - let resolver_handle = super::start_resolver_inner(Arc::downgrade(&tx), port) - .await - .unwrap(); - (resolver_handle, port, rx, tx) - } - - async fn get_test_resolver(port: u16) -> trust_dns_server::resolver::TokioAsyncResolver { - let resolver_config = ResolverConfig::from_parts( - None, - vec![], - NameServerConfigGroup::from_ips_clear(&[Ipv4Addr::LOCALHOST.into()], port, true), - ); - AsyncResolver::new( - resolver_config, - ResolverOpts::default(), - proto::TokioRuntime, - ) - .unwrap() - } - - #[test] - fn test_successful_lookup() { - let rt = tokio::runtime::Runtime::new().unwrap(); - 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 { 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 { - let dns_request = - test_resolver.lookup(captive_portal_domain, RecordType::A, Default::default()); - let unblock_request = cmd_rx.next(); - - use futures::future::Either; - match futures::future::select(dns_request, unblock_request).await { - Either::Left((_resolution_result, _unblock_request_future)) => { - panic!("DNS response recieved before unblocking request") - } - Either::Right((unblock_request, resolution)) => { - std::mem::drop(unblock_request); - resolution.await - } - } - }); - resolver_result.expect("Failed to resolve test domain"); - } - - #[test] - fn test_failed_lookup_when_active() { - let rt = tokio::runtime::Runtime::new().unwrap(); - - let (handle, port, mut cmd_rx, _tx) = rt.block_on(start_resolver()); - let test_resolver = rt.block_on(get_test_resolver(port)); - - let resolver_config = read_resolvconf(); - 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 { - let dns_request = - test_resolver.lookup(captive_portal_domain, RecordType::A, Default::default()); - let unblock_request = cmd_rx.next(); - - use futures::future::Either; - match futures::future::select(dns_request, unblock_request).await { - Either::Left((dns_response, _unblock_request_future)) => dns_response, - Either::Right((_unblock_request, _resolution)) => { - panic!( - "There should be no unblocking for a request that shouldn't be serviced" - ); - } - } - }); - assert!( - resolver_result.is_err(), - "Non-whitelisted DNS request should fail" - ) - } - - #[test] - fn test_failed_lookup_when_inactive() { - let rt = tokio::runtime::Runtime::new().unwrap(); - - 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 { 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 { - let dns_request = - test_resolver.lookup(captive_portal_domain, RecordType::A, Default::default()); - let unblock_request = cmd_rx.next(); - - use futures::future::Either; - match futures::future::select(dns_request, unblock_request).await { - Either::Left((dns_response, _unblock_request_future)) => { - dns_response - } - Either::Right((_unblock_request, _resolution)) => { - panic!("There should be no unblocking for for a request when the resolver is inactive"); - } - } - - }); - assert!( - resolver_result.is_err(), - "Non-whitelisted DNS request should fail" - ) - } - - #[test] - fn test_unbinding() { - let rt = tokio::runtime::Runtime::new().unwrap(); - - let (handle, port, mut _cmd_rx, _tx) = rt.block_on(start_resolver()); - let server_sockaddr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port); - - let _ = UdpSocket::bind(server_sockaddr) - .expect("Failed to bind to resolver socket addr when it should be unbound"); - - 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 { handle.shutdown().await }) - .expect("failed to make resovler active"); - - // macOS takes it sweet time reaping the socket - std::thread::sleep(std::time::Duration::from_millis(300)); - 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 e8777b5d14..546f9e92ab 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -185,18 +185,6 @@ 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::AllowMacosNetworkCheck(enable, done_tx)) => { - let _ = done_tx.send(shared_values.deactivate_filtering_resolver(enable)); - SameState(self.into()) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(_new_config)) => SameState(self.into()), Some(TunnelCommand::AllowLan(allow_lan)) => { if let Err(error_cause) = shared_values.set_allow_lan(allow_lan) { self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 74b6d116b5..2ae1924988 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -268,18 +268,6 @@ 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::AllowMacosNetworkCheck(enable, done_tx)) => { - let _ = done_tx.send(shared_values.deactivate_filtering_resolver(enable)); - SameState(self.into()) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(_new_config)) => SameState(self.into()), Some(TunnelCommand::AllowLan(allow_lan)) => { if let Err(error_cause) = shared_values.set_allow_lan(allow_lan) { self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) @@ -493,13 +481,6 @@ 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_filtering_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 58e5aa23ac..3682accc0b 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -2,28 +2,21 @@ use super::{ ConnectingState, ErrorState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelCommandReceiver, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::firewall::FirewallPolicy; #[cfg(target_os = "macos")] -use crate::{dns, resolver}; +use crate::dns; +use crate::firewall::FirewallPolicy; use futures::StreamExt; #[cfg(target_os = "macos")] -use std::{ - collections::BTreeSet, - net::{IpAddr, Ipv4Addr}, -}; +use std::net::Ipv4Addr; #[cfg(target_os = "macos")] use talpid_types::tunnel::ErrorStateCause; use talpid_types::ErrorExt; /// No tunnel is running. -pub struct DisconnectedState { - #[cfg(target_os = "macos")] - allowed_ips: BTreeSet<IpAddr>, -} +pub struct DisconnectedState; impl DisconnectedState { fn set_firewall_policy( - &mut self, shared_values: &mut SharedTunnelStateValues, should_reset_firewall: bool, ) { @@ -32,9 +25,7 @@ impl DisconnectedState { 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")] - allow_gid_exclusion_traffic: shared_values.enable_filtering_resolver, + dns_redirect_port: shared_values.filtering_resolver.listening_port(), }; let firewall_result = shared_values.firewall.apply_policy(policy).map_err(|e| { @@ -88,27 +79,14 @@ impl DisconnectedState { } } - /// Starts the filtering resolver and configures host to use it. + /// Configures host to use a localhost resolver #[cfg(target_os = "macos")] - fn start_filtering_resolver( - &mut self, + fn setup_local_dns_config( 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)?; - - shared_values - .runtime - .block_on(shared_values.filtering_resolver.set_active(system_config)) - .map_err(Either::Left)?; + ) -> Result<(), dns::Error> { shared_values .dns_monitor .set("lo", &[Ipv4Addr::LOCALHOST.into()]) - .map_err(resolver::Error::SystemDnsError) - .map_err(Either::Left) } } @@ -119,21 +97,16 @@ impl TunnelState for DisconnectedState { shared_values: &mut SharedTunnelStateValues, should_reset_firewall: Self::Bootstrap, ) -> (TunnelStateWrapper, TunnelStateTransition) { - let mut disconnected_state = DisconnectedState { - #[cfg(target_os = "macos")] - allowed_ips: BTreeSet::new(), - }; - #[cfg(target_os = "macos")] - if shared_values.enable_filtering_resolver && shared_values.block_when_disconnected { - if let Err(err) = disconnected_state.start_filtering_resolver(shared_values) { + if shared_values.block_when_disconnected { + if let Err(err) = Self::setup_local_dns_config(shared_values) { log::error!( "{}", err.display_chain_with_msg("Failed to start filtering resolver:") ); } } else { - if let Err(error) = shared_values.disable_filtering_resolver() { + if let Err(error) = shared_values.dns_monitor.reset() { log::error!( "{}", error.display_chain_with_msg("Unable to disable filtering resolver") @@ -143,20 +116,20 @@ impl TunnelState for DisconnectedState { #[cfg(windows)] Self::register_split_tunnel_addresses(shared_values, should_reset_firewall); - disconnected_state.set_firewall_policy(shared_values, should_reset_firewall); + Self::set_firewall_policy(shared_values, should_reset_firewall); #[cfg(target_os = "linux")] shared_values.reset_connectivity_check(); #[cfg(target_os = "android")] shared_values.tun_provider.close_tun(); ( - TunnelStateWrapper::from(disconnected_state), + TunnelStateWrapper::from(DisconnectedState), TunnelStateTransition::Disconnected, ) } fn handle_event( - mut self, + self, runtime: &tokio::runtime::Handle, commands: &mut TunnelCommandReceiver, shared_values: &mut SharedTunnelStateValues, @@ -172,13 +145,13 @@ impl TunnelState for DisconnectedState { .set_allow_lan(allow_lan) .expect("Failed to set allow LAN parameter"); - self.set_firewall_policy(shared_values, true); + Self::set_firewall_policy(shared_values, true); } SameState(self.into()) } Some(TunnelCommand::AllowEndpoint(endpoint, tx)) => { if shared_values.set_allowed_endpoint(endpoint) { - self.set_firewall_policy(shared_values, true); + Self::set_firewall_policy(shared_values, true); } if let Err(_) = tx.send(()) { log::error!("The AllowEndpoint receiver was dropped"); @@ -196,14 +169,20 @@ impl TunnelState for DisconnectedState { Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { if shared_values.block_when_disconnected != block_when_disconnected { shared_values.block_when_disconnected = block_when_disconnected; - self.set_firewall_policy(shared_values, true); + Self::set_firewall_policy(shared_values, true); #[cfg(windows)] Self::register_split_tunnel_addresses(shared_values, true); #[cfg(target_os = "macos")] - if block_when_disconnected && shared_values.enable_filtering_resolver { - if let Err(err) = self.start_filtering_resolver(shared_values) { - let block_reason = map_filtering_resolver_start(&err); - return NewState(ErrorState::enter(shared_values, block_reason)); + if block_when_disconnected { + if let Err(err) = Self::setup_local_dns_config(shared_values) { + log::error!( + "{}", + err.display_chain_with_msg("Failed to configure host DNS") + ); + return NewState(ErrorState::enter( + shared_values, + ErrorStateCause::SetDnsError, + )); } } else { Self::reset_dns(shared_values); @@ -230,71 +209,6 @@ impl TunnelState for DisconnectedState { shared_values.split_tunnel.set_paths(&paths, result_tx); SameState(self.into()) } - #[cfg(target_os = "macos")] - Some(TunnelCommand::AllowMacosNetworkCheck(enable, done_tx)) => { - if !enable { - if let Err(err) = shared_values.dns_monitor.reset() { - log::error!( - "{}", - err.display_chain_with_msg("Failed to reset DNS config") - ); - } - if let Err(err) = shared_values.deactivate_filtering_resolver(enable) { - let _ = done_tx.send(Err(err)); - if shared_values.enable_filtering_resolver { - self.set_firewall_policy(shared_values, false); - } - return SameState(self.into()); - }; - } - shared_values.enable_filtering_resolver = enable; - self.set_firewall_policy(shared_values, false); - if shared_values.block_when_disconnected && enable { - if let Err(err) = self.start_filtering_resolver(shared_values) { - log::error!( - "{}", - err.display_chain_with_msg("Failed to start filtering resolver:") - ); - - let error_cause = map_filtering_resolver_start(&err); - let _ = done_tx.send(Err(err.left_or_else(resolver::Error::from))); - return NewState(ErrorState::enter(shared_values, error_cause)); - } - } - let _ = done_tx.send(Ok(())); - SameState(self.into()) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(host_config)) => { - if shared_values.block_when_disconnected && shared_values.enable_filtering_resolver - { - if let Err(err) = shared_values - .runtime - .block_on(shared_values.filtering_resolver.set_active(host_config)) - { - log::error!( - "{}", - err.display_chain_with_msg("Failed to activate filtering resolver") - ); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::FilteringResolverError, - )); - } - } - SameState(self.into()) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::AddAllowedIps(allowed_ips, done_tx)) => { - let new_addresses = allowed_ips.iter().any(|ip| self.allowed_ips.insert(*ip)); - if new_addresses { - let _ = self.set_firewall_policy(shared_values, false); - } - let _ = done_tx.send(()); - - SameState(self.into()) - } - None => { Self::reset_dns(shared_values); Finished @@ -303,18 +217,3 @@ impl TunnelState for DisconnectedState { } } } - -/// Maps a DNS or a resovler error to an [ErrorStateCause] to be used when failing to start a -/// filtering resolver. -#[cfg(target_os = "macos")] -fn map_filtering_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::FilteringResolverError, - } -} diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index e1fab84ffc..8f6f6ae68b 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -28,18 +28,6 @@ impl DisconnectingState { self.after_disconnect = match after_disconnect { AfterDisconnect::Nothing => match command { - #[cfg(target_os = "macos")] - Some(TunnelCommand::AddAllowedIps(_, done_tx)) => { - let _ = done_tx.send(()); - AfterDisconnect::Nothing - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::AllowMacosNetworkCheck(enable, done_tx)) => { - let _ = done_tx.send(shared_values.deactivate_filtering_resolver(enable)); - AfterDisconnect::Nothing - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(_new_config)) => AfterDisconnect::Nothing, Some(TunnelCommand::AllowLan(allow_lan)) => { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Nothing @@ -78,20 +66,6 @@ impl DisconnectingState { } }, AfterDisconnect::Block(reason) => match command { - #[cfg(target_os = "macos")] - Some(TunnelCommand::AddAllowedIps(_, done_tx)) => { - let _ = done_tx.send(()); - AfterDisconnect::Block(reason) - } - - #[cfg(target_os = "macos")] - Some(TunnelCommand::AllowMacosNetworkCheck(enable, done_tx)) => { - let _ = done_tx.send(shared_values.deactivate_filtering_resolver(enable)); - AfterDisconnect::Block(reason) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(_new_config)) => AfterDisconnect::Block(reason), - Some(TunnelCommand::AllowLan(allow_lan)) => { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Block(reason) @@ -139,21 +113,6 @@ impl DisconnectingState { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Reconnect(retry_attempt) } - #[cfg(target_os = "macos")] - Some(TunnelCommand::AllowMacosNetworkCheck(enable, done_tx)) => { - let _ = done_tx.send(shared_values.deactivate_filtering_resolver(enable)); - AfterDisconnect::Reconnect(retry_attempt) - } - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(_new_config)) => { - AfterDisconnect::Reconnect(retry_attempt) - } - - #[cfg(target_os = "macos")] - Some(TunnelCommand::AddAllowedIps(_allowed_ips, done_tx)) => { - let _ = done_tx.send(()); - AfterDisconnect::Reconnect(retry_attempt) - } Some(TunnelCommand::AllowEndpoint(endpoint, tx)) => { let _ = shared_values.set_allowed_endpoint(endpoint); if let Err(_) = tx.send(()) { diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 6a32ec157c..a501b21f92 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -3,14 +3,9 @@ use super::{ TunnelCommandReceiver, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; 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}, -}; +use std::net::Ipv4Addr; use talpid_types::{ tunnel::{self as talpid_tunnel, ErrorStateCause, FirewallPolicyError}, ErrorExt, @@ -18,37 +13,18 @@ use talpid_types::{ /// No tunnel is running and all network connections are blocked. pub struct ErrorState { - #[cfg(target_os = "macos")] - allowed_ips: BTreeSet<IpAddr>, block_reason: ErrorStateCause, } impl ErrorState { - fn set_firewall( - &self, - shared_values: &mut SharedTunnelStateValues, - ) -> Result<(), FirewallPolicyError> { - Self::set_firewall_policy( - shared_values, - #[cfg(target_os = "macos")] - self.allowed_ips.clone(), - #[cfg(target_os = "macos")] - shared_values.enable_filtering_resolver, - ) - } - fn set_firewall_policy( shared_values: &mut SharedTunnelStateValues, - #[cfg(target_os = "macos")] allowed_ips: BTreeSet<IpAddr>, - #[cfg(target_os = "macos")] allow_gid_exclusion_traffic: bool, ) -> Result<(), FirewallPolicyError> { let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, allowed_endpoint: shared_values.allowed_endpoint.clone(), #[cfg(target_os = "macos")] - allowed_ips, - #[cfg(target_os = "macos")] - allow_gid_exclusion_traffic, + dns_redirect_port: shared_values.filtering_resolver.listening_port(), }; #[cfg(target_os = "linux")] @@ -114,9 +90,7 @@ impl TunnelState for ErrorState { } #[cfg(target_os = "macos")] - let host_config = if shared_values.enable_filtering_resolver - && !block_reason.prevents_filtering_resolver() - { + if !block_reason.prevents_filtering_resolver() { if let Err(err) = shared_values .dns_monitor .set("lo", &[Ipv4Addr::LOCALHOST.into()]) @@ -129,52 +103,10 @@ impl TunnelState for ErrorState { ); return Self::enter(shared_values, ErrorStateCause::SetDnsError); } - match shared_values.dns_monitor.get_system_config() { - Ok(host_config) => host_config, - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg("Failed to start filtering resolver") - ); - if let Err(err) = shared_values.dns_monitor.reset() { - log::error!( - "{}", - err.display_chain_with_msg( - "Faield to reset DNS after failing to obtain host config" - ) - ); - } - return Self::enter(shared_values, ErrorStateCause::FilteringResolverError); - } - } - } else { - None }; #[cfg(not(target_os = "android"))] - let block_failure = Self::set_firewall_policy( - shared_values, - #[cfg(target_os = "macos")] - BTreeSet::new(), - #[cfg(target_os = "macos")] - shared_values.enable_filtering_resolver, - ) - .err(); - - #[cfg(target_os = "macos")] - if let Some(dns_config) = host_config { - if let Err(err) = shared_values.runtime.block_on( - shared_values - .filtering_resolver - .set_active(Some(dns_config)), - ) { - log::error!( - "{}", - err.display_chain_with_msg("Failed to activate filtering resolver") - ); - return Self::enter(shared_values, ErrorStateCause::FilteringResolverError); - } - } + let block_failure = Self::set_firewall_policy(shared_values).err(); #[cfg(target_os = "android")] let block_failure = if !Self::create_blocking_tun(shared_values) { @@ -185,8 +117,6 @@ impl TunnelState for ErrorState { ( TunnelStateWrapper::from(ErrorState { block_reason: block_reason.clone(), - #[cfg(target_os = "macos")] - allowed_ips: BTreeSet::new(), }), TunnelStateTransition::Error(talpid_tunnel::ErrorState::new( block_reason, @@ -197,7 +127,7 @@ impl TunnelState for ErrorState { #[cfg_attr(not(target_os = "macos"), allow(unused_mut))] fn handle_event( - mut self, + self, runtime: &tokio::runtime::Handle, commands: &mut TunnelCommandReceiver, shared_values: &mut SharedTunnelStateValues, @@ -205,127 +135,17 @@ impl TunnelState for ErrorState { use self::EventConsequence::*; match runtime.block_on(commands.next()) { - #[cfg(target_os = "macos")] - Some(TunnelCommand::AddAllowedIps(allowed_ips, done_tx)) => { - let new_addresses = allowed_ips.iter().any(|ip| self.allowed_ips.insert(*ip)); - if new_addresses { - if let Err(err) = self.set_firewall(shared_values) { - return NewState(Self::enter( - shared_values, - ErrorStateCause::SetFirewallPolicyError(err), - )); - } - } - let _ = done_tx.send(()); - SameState(self.into()) - } - - #[cfg(target_os = "macos")] - Some(TunnelCommand::AllowMacosNetworkCheck(enable, done_tx)) => { - let result = if enable { - shared_values.enable_filtering_resolver = enable; - if let Err(err) = self.set_firewall(shared_values) { - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::SetFirewallPolicyError(err), - )); - } - - match shared_values.dns_monitor.get_system_config() { - Ok(current_system_config) => { - match shared_values.runtime.block_on( - shared_values - .filtering_resolver - .set_active(current_system_config), - ) { - Ok(_) => { - 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 filtering resolver" - ) - ); - let _ = - done_tx.send(Err(resolver::Error::SystemDnsError(err))); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::SetDnsError, - )); - } - Ok(()) - } - - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg( - "Failed to start filtering resolver" - ) - ); - Err(err) - } - } - } - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg("Failed to obtain system DNS config") - ); - - let _ = done_tx.send(Err(resolver::Error::SystemDnsError(err))); - return NewState(ErrorState::enter( - shared_values, - ErrorStateCause::ReadSystemDnsConfig, - )); - } - } - } else { - if let Err(err) = shared_values.dns_monitor.reset() { - log::error!( - "{}", - err.display_chain_with_msg("Failed to reset DNS config") - ); - } - shared_values.deactivate_filtering_resolver(enable) - }; - let _ = done_tx.send(result); - SameState(self.into()) - } - - #[cfg(target_os = "macos")] - Some(TunnelCommand::HostDnsConfig(host_config)) => { - if shared_values.enable_filtering_resolver { - if let Err(err) = shared_values - .runtime - .block_on(shared_values.filtering_resolver.set_active(host_config)) - { - log::error!( - "Failed to set apply new DNS config to filtering resolver: {}", - err - ); - return NewState(Self::enter( - shared_values, - ErrorStateCause::FilteringResolverError, - )); - } - } - SameState(self.into()) - } Some(TunnelCommand::AllowLan(allow_lan)) => { if let Err(error_state_cause) = shared_values.set_allow_lan(allow_lan) { NewState(Self::enter(shared_values, error_state_cause)) } else { - let _ = self.set_firewall(shared_values); + let _ = Self::set_firewall_policy(shared_values); SameState(self.into()) } } Some(TunnelCommand::AllowEndpoint(endpoint, tx)) => { if shared_values.set_allowed_endpoint(endpoint) { - let _ = self.set_firewall(shared_values); + let _ = Self::set_firewall_policy(shared_values); #[cfg(target_os = "android")] if !Self::create_blocking_tun(shared_values) { @@ -368,12 +188,6 @@ impl TunnelState for ErrorState { 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_filtering_resolver() { - log::error!("Failed to disable filtering resolver: {}", err); - } - } Self::reset_dns(shared_values); NewState(DisconnectedState::enter(shared_values, true)) } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index dd6ee01e52..298f8fc7a6 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -28,8 +28,6 @@ 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}; @@ -106,7 +104,6 @@ pub async fn spawn( offline_state_listener: mpsc::UnboundedSender<bool>, shutdown_tx: oneshot::Sender<()>, #[cfg(target_os = "macos")] exclusion_gid: u32, - #[cfg(target_os = "macos")] enable_resolver: bool, #[cfg(target_os = "android")] android_context: AndroidContext, ) -> Result<Arc<mpsc::UnboundedSender<TunnelCommand>>, Error> { let (command_tx, command_rx) = mpsc::unbounded(); @@ -135,8 +132,6 @@ pub async fn spawn( command_rx, #[cfg(target_os = "macos")] exclusion_gid, - #[cfg(target_os = "macos")] - enable_resolver, #[cfg(target_os = "android")] android_context, ) @@ -180,15 +175,6 @@ pub enum TunnelCommand { oneshot::Sender<Result<(), split_tunnel::Error>>, Vec<OsString>, ), - /// Sets IP addresses which should be allowed to pass through the firewall. - #[cfg(target_os = "macos")] - AddAllowedIps(BTreeSet<IpAddr>, oneshot::Sender<()>), - /// Toggles filtering resolver - #[cfg(target_os = "macos")] - AllowMacosNetworkCheck(bool, oneshot::Sender<Result<(), crate::resolver::Error>>), - /// Receive up-to-date system DNS config. It should never contain our changes to the DNS. - #[cfg(target_os = "macos")] - HostDnsConfig(Option<(String, Vec<IpAddr>)>), } type TunnelCommandReceiver = stream::Fuse<mpsc::UnboundedReceiver<TunnelCommand>>; @@ -222,11 +208,13 @@ impl TunnelStateMachine { resource_dir: PathBuf, commands_rx: mpsc::UnboundedReceiver<TunnelCommand>, #[cfg(target_os = "macos")] exclusion_gid: u32, - #[cfg(target_os = "macos")] enable_resolver: bool, #[cfg(target_os = "android")] android_context: AndroidContext, ) -> Result<Self, Error> { let runtime = tokio::runtime::Handle::current(); + #[cfg(target_os = "macos")] + let filtering_resolver = crate::resolver::start_resolver().await?; + #[cfg(windows)] let split_tunnel = split_tunnel::SplitTunnel::new(runtime.clone(), command_tx.clone()) .map_err(Error::InitSplitTunneling)?; @@ -253,14 +241,9 @@ impl TunnelStateMachine { route_manager .handle() .map_err(Error::InitRouteManagerError)?, - #[cfg(target_os = "macos")] - command_tx.clone(), ) .map_err(Error::InitDnsMonitorError)?; - #[cfg(target_os = "macos")] - let filtering_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(); tokio::spawn(async move { @@ -313,8 +296,6 @@ impl TunnelStateMachine { connectivity_check_was_enabled: None, #[cfg(target_os = "macos")] filtering_resolver, - #[cfg(target_os = "macos")] - enable_filtering_resolver: enable_resolver, }; tokio::task::spawn_blocking(move || { @@ -410,9 +391,6 @@ struct SharedTunnelStateValues { /// Filtering resolver handle #[cfg(target_os = "macos")] filtering_resolver: crate::resolver::ResolverHandle, - /// Whether filtering resolver should be enabled - #[cfg(target_os = "macos")] - enable_filtering_resolver: bool, } impl SharedTunnelStateValues { @@ -438,29 +416,6 @@ impl SharedTunnelStateValues { Ok(()) } - /// Sets the filtering resolver setting and toggles it's state to either inactive or shutdown - /// state. - #[cfg(target_os = "macos")] - pub fn deactivate_filtering_resolver( - &mut self, - enable_resolver: bool, - ) -> Result<(), crate::resolver::Error> { - self.enable_filtering_resolver = enable_resolver; - self.disable_filtering_resolver() - } - - /// Toggles filtering resolver state to either inactive or shutdown. - #[cfg(target_os = "macos")] - pub fn disable_filtering_resolver(&mut self) -> Result<(), crate::resolver::Error> { - if self.enable_filtering_resolver { - self.runtime - .block_on(self.filtering_resolver.set_inactive())?; - } else { - self.runtime.block_on(self.filtering_resolver.shutdown())?; - } - Ok(()) - } - pub fn set_allowed_endpoint(&mut self, endpoint: AllowedEndpoint) -> bool { if self.allowed_endpoint != endpoint { #[cfg(target_os = "android")] |
