summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2021-12-14 15:05:47 +0000
committerEmīls <emils@mullvad.net>2022-01-14 14:42:53 +0000
commitf46e257b5a0de0b530016672773ecda5d08de9df (patch)
treea49c6d32ac3bf56cd1332a34f74388607d8dce8e /talpid-core/src
parentc0396210ad8bd9e6e51c4b36975fa7a9de3270ff (diff)
downloadmullvadvpn-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.rs29
-rw-r--r--talpid-core/src/dns/mod.rs11
-rw-r--r--talpid-core/src/firewall/macos.rs92
-rw-r--r--talpid-core/src/firewall/mod.rs10
-rw-r--r--talpid-core/src/resolver.rs340
-rw-r--r--talpid-core/src/resolver/mod.rs822
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs12
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs19
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs155
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs41
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs200
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs51
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")]