use std::fmt; use std::net::IpAddr; #[cfg(target_os = "linux")] use talpid_routing::RouteManagerHandle; #[cfg(target_os = "macos")] #[path = "macos.rs"] mod imp; #[cfg(target_os = "linux")] #[path = "linux/mod.rs"] mod imp; #[cfg(target_os = "linux")] pub use imp::will_use_nm; #[cfg(windows)] #[path = "windows/mod.rs"] mod imp; #[cfg(target_os = "android")] #[path = "android.rs"] mod imp; pub use self::imp::Error; /// DNS configuration #[derive(Debug, Clone, PartialEq)] pub struct DnsConfig { config: InnerDnsConfig, } impl Default for DnsConfig { fn default() -> Self { Self { config: InnerDnsConfig::Default, } } } impl DnsConfig { /// Use the specified addresses for DNS resolution pub fn from_addresses(tunnel_config: &[IpAddr], non_tunnel_config: &[IpAddr]) -> Self { DnsConfig { config: InnerDnsConfig::Override { tunnel_config: tunnel_config.to_owned(), non_tunnel_config: non_tunnel_config.to_owned(), }, } } } #[derive(Debug, Clone, PartialEq)] enum InnerDnsConfig { /// Use gateway addresses from the tunnel config Default, /// Use the specified addresses for DNS resolution Override { /// Addresses to configure on the tunnel interface tunnel_config: Vec, /// Addresses to allow on non-tunnel interface. /// For the most part, the tunnel state machine will not handle any of this configuration /// on non-tunnel interface, only allow them in the firewall. non_tunnel_config: Vec, }, } impl DnsConfig { pub(crate) fn resolve( &self, default_tun_config: &[IpAddr], #[cfg(target_os = "macos")] port: u16, ) -> ResolvedDnsConfig { match &self.config { InnerDnsConfig::Default => ResolvedDnsConfig { tunnel_config: default_tun_config.to_owned(), non_tunnel_config: vec![], #[cfg(target_os = "macos")] port, }, InnerDnsConfig::Override { tunnel_config, non_tunnel_config, } => ResolvedDnsConfig { tunnel_config: tunnel_config.to_owned(), non_tunnel_config: non_tunnel_config.to_owned(), #[cfg(target_os = "macos")] port, }, } } } /// DNS configuration with `DnsConfig::Default` resolved #[derive(Debug, Clone, PartialEq, Eq)] pub struct ResolvedDnsConfig { /// Addresses to configure on the tunnel interface tunnel_config: Vec, /// Addresses to allow on non-tunnel interface. /// For the most part, the tunnel state machine will not handle any of this configuration /// on non-tunnel interface, only allow them in the firewall. non_tunnel_config: Vec, /// Port to use #[cfg(target_os = "macos")] port: u16, } impl fmt::Display for ResolvedDnsConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Tunnel DNS: ")?; Self::fmt_addr_set(f, &self.tunnel_config)?; f.write_str(" Non-tunnel DNS: ")?; Self::fmt_addr_set(f, &self.non_tunnel_config)?; #[cfg(target_os = "macos")] write!(f, " Port: {}", self.port)?; Ok(()) } } impl ResolvedDnsConfig { fn fmt_addr_set(f: &mut fmt::Formatter<'_>, addrs: &[IpAddr]) -> fmt::Result { f.write_str("{")?; for (i, addr) in addrs.iter().enumerate() { if i > 0 { f.write_str(", ")?; } write!(f, "{addr}")?; } f.write_str("}") } /// Addresses to configure on the tunnel interface pub fn tunnel_config(&self) -> &[IpAddr] { &self.tunnel_config } /// Addresses to allow on non-tunnel interface. /// For the most part, the tunnel state machine will not handle any of this configuration /// on non-tunnel interface, only allow them in the firewall. pub fn non_tunnel_config(&self) -> &[IpAddr] { &self.non_tunnel_config } /// Consume `self` and return a vector of all addresses pub fn addresses(self) -> impl Iterator { self.non_tunnel_config.into_iter().chain(self.tunnel_config) } /// Return whether the config contains only (and at least one) loopback addresses, and zero /// non-loopback addresses pub fn is_loopback(&self) -> bool { let (loopback_addrs, non_loopback_addrs) = self .tunnel_config .iter() .chain(self.non_tunnel_config.iter()) .copied() .partition::, _>(|ip| ip.is_loopback()); !loopback_addrs.is_empty() && non_loopback_addrs.is_empty() } } /// Sets and monitors system DNS settings. Makes sure the desired DNS servers are being used. pub struct DnsMonitor { inner: imp::DnsMonitor, } impl DnsMonitor { /// Returns a new `DnsMonitor` that can set and monitor the system DNS. pub fn new( #[cfg(target_os = "linux")] handle: tokio::runtime::Handle, #[cfg(target_os = "linux")] route_manager: RouteManagerHandle, ) -> Result { Ok(DnsMonitor { inner: imp::DnsMonitor::new( #[cfg(target_os = "linux")] handle, #[cfg(target_os = "linux")] route_manager, )?, }) } /// Set DNS to the given servers. And start monitoring the system for changes. pub fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { log::info!("Setting DNS servers: {config}",); self.inner.set(interface, config) } /// Reset system DNS settings to what it was before being set by this instance. /// This succeeds if the interface does not exist. pub fn reset(&mut self) -> Result<(), Error> { log::info!("Resetting DNS"); self.inner.reset() } /// Reset DNS settings to what they were before being set by this instance. /// If the settings only affect a specific interface, this can be a no-op, /// as the interface will be destroyed. pub fn reset_before_interface_removal(&mut self) -> Result<(), Error> { log::info!("Resetting DNS"); self.inner.reset_before_interface_removal() } } trait DnsMonitorT: Sized { type Error: std::error::Error; fn new( #[cfg(target_os = "linux")] handle: tokio::runtime::Handle, #[cfg(target_os = "linux")] route_manager: RouteManagerHandle, ) -> Result; fn set(&mut self, interface: &str, servers: ResolvedDnsConfig) -> Result<(), Self::Error>; fn reset(&mut self) -> Result<(), Self::Error>; fn reset_before_interface_removal(&mut self) -> Result<(), Self::Error> { self.reset() } }