diff options
| -rw-r--r-- | Cargo.lock | 20 | ||||
| -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 |
4 files changed, 265 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock index 67dc2b6811..cd852af42b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,15 @@ dependencies = [ ] [[package]] +name = "dbus" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libdbus-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "derive_builder" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -700,6 +709,14 @@ version = "0.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "libdbus-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "linked-hash-map" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1625,6 +1642,7 @@ version = "0.1.0" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dbus 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "duct 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2187,6 +2205,7 @@ dependencies = [ "checksum crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2af0e75710d6181e234c8ecc79f14a97907850a541b13b0be1dd10992f2e4620" "checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b" "checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e" +"checksum dbus 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e34c238dfb3f5881d46ad301403cd8f8ecf946e2a4e89bdd1166728b68b5008" "checksum derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c998e6ab02a828dd9735c18f154e14100e674ed08cb4e1938f0e4177543f439" "checksum derive_builder_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "735e24ee9e5fa8e16b86da5007856e97d592e11867e45d76e0c0d0a164a0b757" "checksum dirs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "37a76dd8b997af7107d0bb69d43903cf37153a18266f8b3fdb9911f28efb5444" @@ -2238,6 +2257,7 @@ dependencies = [ "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum libdbus-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8720f9274907052cb50313f91201597868da9d625f8dd125f2aca5bddb7e83a1" "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum linked_hash_set 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7c91c4c7bbeb4f2f7c4e5be11e6a05bd6830bc37249c47ce1ad86ad453ff9c" "checksum lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "949826a5ccf18c1b3a7c3d57692778d21768b79e46eb9dd07bfc4c2160036c54" 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") +} |
