diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-09-10 11:03:27 -0300 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2018-10-05 12:08:48 +0100 |
| commit | 89b29e2bc37fdbeb504fece4ff6a054bb05cd28c (patch) | |
| tree | 79b8b717a9bdffc8dd6e65f13bdb3c9b76a57349 /talpid-core | |
| parent | 00565eb2b1036c2bba0375490f66de2d8484c788 (diff) | |
| download | mullvadvpn-89b29e2bc37fdbeb504fece4ff6a054bb05cd28c.tar.xz mullvadvpn-89b29e2bc37fdbeb504fece4ff6a054bb05cd28c.zip | |
Implement support for systemd-resolved
Diffstat (limited to 'talpid-core')
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/src/security/linux/dns/mod.rs | 12 | ||||
| -rw-r--r-- | talpid-core/src/security/linux/dns/systemd_resolved.rs | 234 |
3 files changed, 245 insertions, 2 deletions
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 0221d6db4f..b0f047ef52 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -29,6 +29,7 @@ ipnetwork = "0.13" lazy_static = "1.0" [target.'cfg(target_os = "linux")'.dependencies] +dbus = "0.6" failure = "0.1" notify = "4.0" resolv-conf = "0.6.1" diff --git a/talpid-core/src/security/linux/dns/mod.rs b/talpid-core/src/security/linux/dns/mod.rs index 7cc004ce96..e854f4a3e1 100644 --- a/talpid-core/src/security/linux/dns/mod.rs +++ b/talpid-core/src/security/linux/dns/mod.rs @@ -1,11 +1,13 @@ mod resolvconf; mod static_resolv_conf; +mod systemd_resolved; use std::env; use std::net::IpAddr; use self::resolvconf::Resolvconf; use self::static_resolv_conf::StaticResolvConf; +use self::systemd_resolved::SystemdResolved; error_chain! { errors { @@ -17,12 +19,14 @@ error_chain! { links { Resolvconf(resolvconf::Error, resolvconf::ErrorKind); StaticResolvConf(static_resolv_conf::Error, static_resolv_conf::ErrorKind); + SystemdResolved(systemd_resolved::Error, systemd_resolved::ErrorKind); } } pub enum DnsSettings { Resolvconf(Resolvconf), StaticResolvConf(StaticResolvConf), + SystemdResolved(SystemdResolved), } impl DnsSettings { @@ -37,8 +41,8 @@ impl DnsSettings { } fn with_detected_dns_manager() -> Result<Self> { - Resolvconf::new() - .map(DnsSettings::Resolvconf) + SystemdResolved::new() + .map(DnsSettings::SystemdResolved) .or_else(|_| StaticResolvConf::new().map(DnsSettings::StaticResolvConf)) .chain_err(|| ErrorKind::NoDnsSettingsManager) } @@ -49,6 +53,9 @@ impl DnsSettings { match self { Resolvconf(ref mut resolvconf) => resolvconf.set_dns(interface, servers)?, StaticResolvConf(ref mut static_resolv_conf) => static_resolv_conf.set_dns(servers)?, + SystemdResolved(ref mut systemd_resolved) => { + systemd_resolved.set_dns(interface, servers)? + } } Ok(()) @@ -60,6 +67,7 @@ impl DnsSettings { match self { Resolvconf(ref mut resolvconf) => resolvconf.reset()?, StaticResolvConf(ref mut static_resolv_conf) => static_resolv_conf.reset()?, + SystemdResolved(ref mut systemd_resolved) => systemd_resolved.reset()?, } Ok(()) diff --git a/talpid-core/src/security/linux/dns/systemd_resolved.rs b/talpid-core/src/security/linux/dns/systemd_resolved.rs new file mode 100644 index 0000000000..f86ff9d7b5 --- /dev/null +++ b/talpid-core/src/security/linux/dns/systemd_resolved.rs @@ -0,0 +1,234 @@ +extern crate dbus; + +use std::collections::HashMap; +use std::net::IpAddr; + +use error_chain::ChainedError; +use libc::{AF_INET, AF_INET6}; + +use self::dbus::arg::RefArg; +use self::dbus::stdintf::*; +use self::dbus::{BusType, Interface, Member, MessageItem, MessageItemArray, Signature}; + +use super::super::iface_index; + +error_chain! { + errors { + NoSystemdResolved { + description("Systemd resolved not detected") + } + InvalidInterfaceName { + description("Invalid network interface name") + } + DbusRpcError { + description("Failed to perform RPC call on DBus") + } + GetLinkError { + description("Failed to find link interface in resolved manager") + } + SetDnsError { + description("Failed to configure DNS servers") + } + RevertDnsError { + description("Failed to revert DNS configuration") + } + DBusError { + description("Failed to initialize a connection to dbus") + } + } + +} + +const RESOLVED_BUS: &str = "org.freedesktop.resolve1"; +const RPC_TIMEOUT_MS: i32 = 1000; + +lazy_static! { + static ref LINK_INTERFACE: Interface<'static> = + Interface::from_slice("org.freedesktop.resolve1.Link".as_bytes()).unwrap(); + static ref MANAGER_INTERFACE: Interface<'static> = + Interface::from_slice("org.freedesktop.resolve1.Manager".as_bytes()).unwrap(); + static ref GET_LINK_METHOD: Member<'static> = Member::from_slice("GetLink".as_bytes()).unwrap(); + static ref SET_DNS_METHOD: Member<'static> = Member::from_slice("SetDNS".as_bytes()).unwrap(); + static ref REVERT_METHOD: Member<'static> = Member::from_slice("Revert".as_bytes()).unwrap(); +} + +pub struct SystemdResolved { + dbus_connection: dbus::Connection, + interface_links: HashMap<String, dbus::Path<'static>>, +} + +impl SystemdResolved { + pub fn new() -> Result<Self> { + let dbus_connection = + dbus::Connection::get_private(BusType::System).chain_err(|| ErrorKind::DBusError)?; + let systemd_resolved = SystemdResolved { + dbus_connection, + interface_links: HashMap::new(), + }; + + systemd_resolved.ensure_resolved_exists()?; + + Ok(systemd_resolved) + } + + fn ensure_resolved_exists(&self) -> Result<()> { + let _: Box<RefArg> = self + .as_manager_object() + .get(&MANAGER_INTERFACE, "DNS") + .chain_err(|| ErrorKind::NoSystemdResolved)?; + + Ok(()) + } + + fn as_manager_object<'a>(&'a self) -> dbus::ConnPath<'a, &'a dbus::Connection> { + self.dbus_connection + .with_path(RESOLVED_BUS, "/org/freedesktop/resolve1", RPC_TIMEOUT_MS) + } + + fn as_link_object<'a>( + &'a self, + link_object_path: dbus::Path<'a>, + ) -> dbus::ConnPath<'a, &'a dbus::Connection> { + self.dbus_connection + .with_path(RESOLVED_BUS, link_object_path, RPC_TIMEOUT_MS) + } + + pub fn set_dns(&mut self, interface_name: &str, servers: &[IpAddr]) -> Result<()> { + let new_entry = if let Some(link_object_path) = self.interface_links.get(interface_name) { + self.set_link_dns(&link_object_path, servers)?; + + None + } else { + let link_object_path = self.fetch_link(interface_name)?; + + self.set_link_dns(&link_object_path, servers)?; + + Some((interface_name.to_owned(), link_object_path)) + }; + + if let Some((interface_name, link_object_path)) = new_entry { + self.interface_links + .insert(interface_name, link_object_path); + } + + Ok(()) + } + + fn fetch_link(&self, interface_name: &str) -> Result<dbus::Path<'static>> { + let interface_index = + iface_index(interface_name).chain_err(|| ErrorKind::InvalidInterfaceName)?; + + let mut reply = self + .as_manager_object() + .method_call_with_args(&MANAGER_INTERFACE, &GET_LINK_METHOD, |message| { + message.append_items(&[MessageItem::Int32(interface_index as i32)]); + }) + .chain_err(|| ErrorKind::DbusRpcError)?; + + let result = reply.as_result().chain_err(|| ErrorKind::GetLinkError)?; + + result.read1().chain_err(|| ErrorKind::GetLinkError) + } + + fn set_link_dns<'a, 'b: 'a>( + &'a self, + link_object_path: &'b dbus::Path<'static>, + servers: &[IpAddr], + ) -> Result<()> { + + let server_addresses = build_addresses_argument(servers); + + let mut reply = self + .as_link_object(link_object_path.clone()) + .method_call_with_args(&LINK_INTERFACE, &SET_DNS_METHOD, |message| { + message.append_items(&[server_addresses]); + }) + .chain_err(|| ErrorKind::DbusRpcError)?; + + reply + .as_result() + .map(|_| ()) + .chain_err(|| ErrorKind::SetDnsError) + } + + pub fn reset(&mut self) -> Result<()> { + let mut result = Ok(()); + let interface_links: Vec<_> = self.interface_links.drain().collect(); + + for (interface_name, link_object_path) in interface_links { + if let Err(error) = self.revert_link(link_object_path, &interface_name) { + let chained_error = error.chain_err(|| { + format!( + "Failed to revert DNS settings of interface: {}", + interface_name + ) + }); + error!("{}", chained_error.display_chain()); + result = Err(Error::from(ErrorKind::RevertDnsError)); + } + } + + result + } + + fn revert_link( + &mut self, + link_object_path: dbus::Path<'static>, + interface_name: &str, + ) -> Result<()> { + let link = self.as_link_object(link_object_path); + + match link.method_call_with_args(&LINK_INTERFACE, &REVERT_METHOD, |_| {}) { + Ok(mut reply) => reply + .as_result() + .map(|_| ()) + .chain_err(|| ErrorKind::RevertDnsError), + Err(error) => { + if error.name() == Some("org.freedesktop.DBus.Error.UnknownObject") { + info!( + "Not reseting DNS of interface {} because it no longer exists", + interface_name + ); + Ok(()) + } else { + Err(error).chain_err(|| ErrorKind::DbusRpcError) + } + } + } + } +} + +fn build_addresses_argument(addresses: &[IpAddr]) -> MessageItem { + let addresses = addresses + .iter() + .map(ip_address_to_message_item) + .collect(); + + MessageItem::Array( + MessageItemArray::new(addresses, Signature::make::<Vec<(i32, Vec<u8>)>>()) + .expect("Invalid construction of DBus array of IP addresses argument"), + ) +} + +fn ip_address_to_message_item(address: &IpAddr) -> MessageItem { + let (protocol, octets) = match address { + IpAddr::V4(ipv4_address) => (AF_INET, bytes_to_message_item_array(&ipv4_address.octets())), + IpAddr::V6(ipv6_address) => ( + AF_INET6, + bytes_to_message_item_array(&ipv6_address.octets()), + ), + }; + + MessageItem::Struct(vec![ + MessageItem::Int32(protocol), + MessageItem::Array(octets), + ]) +} + +fn bytes_to_message_item_array(bytes: &[u8]) -> MessageItemArray { + MessageItemArray::new( + bytes.into_iter().cloned().map(MessageItem::Byte).collect(), + Signature::make::<Vec<u8>>(), + ) + .expect("Invalid construction of DBus array of bytes argument") +} |
