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