diff options
| author | Emīls <emils@mullvad.net> | 2020-12-18 12:25:24 +0000 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2021-01-12 10:24:38 +0000 |
| commit | 42db82ed22f388167ac09d7a49b9d22b6b4bd3b4 (patch) | |
| tree | 891e2c790415eeb9f3b3086eda2a8ac922f8bfcb | |
| parent | 071c39162078c4627b2e9f028c55a8d00bfbc812 (diff) | |
| download | mullvadvpn-42db82ed22f388167ac09d7a49b9d22b6b4bd3b4.tar.xz mullvadvpn-42db82ed22f388167ac09d7a49b9d22b6b4bd3b4.zip | |
Rely on talpid-dbus in talpid-core
| -rw-r--r-- | Cargo.lock | 12 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-core/src/dns/linux/network_manager.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/dns/linux/systemd_resolved.rs | 11 | ||||
| -rw-r--r-- | talpid-core/src/linux/dbus.rs | 19 | ||||
| -rw-r--r-- | talpid-core/src/linux/mod.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/linux/network_manager.rs | 804 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs | 106 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 4 |
9 files changed, 125 insertions, 840 deletions
diff --git a/Cargo.lock b/Cargo.lock index cc29caeba4..1232303077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2529,7 +2529,6 @@ dependencies = [ "byteorder", "cfg-if 1.0.0", "chrono", - "dbus", "duct", "err-derive", "failure", @@ -2565,6 +2564,7 @@ dependencies = [ "shell-escape", "socket2", "system-configuration", + "talpid-dbus", "talpid-types", "tempfile", "tokio", @@ -2581,6 +2581,16 @@ dependencies = [ ] [[package]] +name = "talpid-dbus" +version = "0.1.0" +dependencies = [ + "dbus", + "err-derive", + "lazy_static", + "log 0.4.11", +] + +[[package]] name = "talpid-openvpn-plugin" version = "2020.8.0-beta2" dependencies = [ diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 6c796f5049..6c03f1af90 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -46,7 +46,6 @@ jnix = { version = "0.3", features = ["derive"] } [target.'cfg(target_os = "linux")'.dependencies] -dbus = "0.9" failure = "0.1" notify = "4.0" resolv-conf = "0.7" @@ -61,6 +60,7 @@ nftnl = { version = "0.6", features = ["nftnl-1-1-0"] } mnl = { version = "0.2.0", features = ["mnl-1-0-4"] } which = { version = "4.0", default-features = false } tun = "0.5" +talpid-dbus = { path = "../talpid-dbus" } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/talpid-core/src/dns/linux/network_manager.rs b/talpid-core/src/dns/linux/network_manager.rs index 1112305468..b51308368b 100644 --- a/talpid-core/src/dns/linux/network_manager.rs +++ b/talpid-core/src/dns/linux/network_manager.rs @@ -1,6 +1,6 @@ -pub use crate::linux::network_manager::Error; -use crate::linux::network_manager::{self, DeviceConfig, NetworkManager as DBus}; use std::net::IpAddr; +pub use talpid_dbus::network_manager::Error; +use talpid_dbus::network_manager::{self, DeviceConfig, NetworkManager as DBus}; pub type Result<T> = std::result::Result<T, Error>; diff --git a/talpid-core/src/dns/linux/systemd_resolved.rs b/talpid-core/src/dns/linux/systemd_resolved.rs index 8cc7b0b02a..73b8756a37 100644 --- a/talpid-core/src/dns/linux/systemd_resolved.rs +++ b/talpid-core/src/dns/linux/systemd_resolved.rs @@ -1,12 +1,13 @@ use super::RESOLV_CONF_PATH; use crate::linux::iface_index; -use dbus::{ - arg::RefArg, - blocking::{stdintf::org_freedesktop_dbus::Properties, Proxy, SyncConnection}, -}; use lazy_static::lazy_static; use libc::{AF_INET, AF_INET6}; use std::{fs, io, net::IpAddr, path::Path, sync::Arc, time::Duration}; +use talpid_dbus::dbus::{ + self, + arg::RefArg, + blocking::{stdintf::org_freedesktop_dbus::Properties, Proxy, SyncConnection}, +}; use talpid_types::ErrorExt as _; pub type Result<T> = std::result::Result<T, Error>; @@ -76,7 +77,7 @@ pub struct SystemdResolved { impl SystemdResolved { pub fn new() -> Result<Self> { - let dbus_connection = crate::linux::dbus::get_connection().map_err(Error::ConnectDBus)?; + let dbus_connection = talpid_dbus::get_connection().map_err(Error::ConnectDBus)?; let systemd_resolved = SystemdResolved { dbus_connection, diff --git a/talpid-core/src/linux/dbus.rs b/talpid-core/src/linux/dbus.rs deleted file mode 100644 index 147574895d..0000000000 --- a/talpid-core/src/linux/dbus.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! DBus system connection -use dbus::blocking::SyncConnection; -use std::sync::{Arc, Mutex}; -lazy_static::lazy_static! { - static ref DBUS_CONNECTION: Mutex<Option<Arc<SyncConnection>>> = Mutex::new(None); -} - -/// Reuse or create a system DBus connection. -pub fn get_connection() -> Result<Arc<SyncConnection>, dbus::Error> { - let mut connection = DBUS_CONNECTION.lock().expect("DBus lock poisoned"); - match &*connection { - Some(existing_connection) => Ok(existing_connection.clone()), - None => { - let new_connection = Arc::new(SyncConnection::new_system()?); - *connection = Some(new_connection.clone()); - Ok(new_connection) - } - } -} diff --git a/talpid-core/src/linux/mod.rs b/talpid-core/src/linux/mod.rs index 0441e1f7bd..69328ff28a 100644 --- a/talpid-core/src/linux/mod.rs +++ b/talpid-core/src/linux/mod.rs @@ -3,9 +3,6 @@ use std::{ io, }; -pub mod dbus; -pub mod network_manager; - /// Converts an interface name into the corresponding index. pub fn iface_index(name: &str) -> Result<libc::c_uint, IfaceIndexLookupError> { let c_name = CString::new(name) diff --git a/talpid-core/src/linux/network_manager.rs b/talpid-core/src/linux/network_manager.rs deleted file mode 100644 index a09acb7e11..0000000000 --- a/talpid-core/src/linux/network_manager.rs +++ /dev/null @@ -1,804 +0,0 @@ -//! NetworkManager is the one-stop-shop of network configuration on Linux. -use crate::tunnel::wireguard::{ - config::Config as WireguardConfig, wireguard_kernel::MULLVAD_INTERFACE_NAME, -}; -use dbus::{ - arg::{self, RefArg, Variant}, - blocking::{stdintf::org_freedesktop_dbus::Properties, Proxy, SyncConnection}, - message::MatchRule, -}; -use std::{ - collections::HashMap, - fs::File, - io::{BufRead, BufReader}, - net::IpAddr, - path::Path, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, - }, - time::{Duration, Instant}, -}; - -const NM_BUS: &str = "org.freedesktop.NetworkManager"; -const NM_MANAGER: &str = "org.freedesktop.NetworkManager"; -const NM_MANAGER_PATH: &str = "/org/freedesktop/NetworkManager"; -const CONNECTIVITY_CHECK_KEY: &str = "ConnectivityCheckEnabled"; - -const NM_DNS_MANAGER: &str = "org.freedesktop.NetworkManager.DnsManager"; -const NM_DNS_MANAGER_PATH: &str = "/org/freedesktop/NetworkManager/DnsManager"; -const NM_DEVICE: &str = "org.freedesktop.NetworkManager.Device"; -const NM_DEVICE_STATISTICS: &str = "org.freedesktop.NetworkManager.Device.Statistics"; - -const NM_IP4_CONFIG: &str = "org.freedesktop.NetworkManager.IP4Config"; -const NM_IP6_CONFIG: &str = "org.freedesktop.NetworkManager.IP6Config"; -const DEVICE_READY_TIMEOUT: Duration = Duration::from_secs(15); -const RC_MANAGEMENT_MODE_KEY: &str = "RcManager"; -const DNS_MODE_KEY: &str = "Mode"; -const DNS_FIRST_PRIORITY: i32 = -2147483647; - -const NM_DEVICE_STATE_IP_CHECK: u32 = 80; -const NM_DEVICE_STATE_SECONDARY: u32 = 90; -const NM_DEVICE_STATE_ACTIVATED: u32 = 100; - -const NM_SETTINGS_INTERFACE: &str = "org.freedesktop.NetworkManager.Settings"; -const NM_SETTINGS_CONNECTION_INTERFACE: &str = "org.freedesktop.NetworkManager.Settings.Connection"; -const NM_SETTINGS_PATH: &str = "/org/freedesktop/NetworkManager/Settings"; -const NM_CONNECTION_ACTIVE: &str = "org.freedesktop.NetworkManager.Connection.Active"; - -const NM_ADD_CONNECTION_VOLATILE: u32 = 0x2; - -const RPC_TIMEOUT: std::time::Duration = Duration::from_secs(3); - -const DBUS_UNKNOWN_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod"; - -const MINIMUM_SUPPORTED_MAJOR_VERSION: u32 = 1; -const MINIMUM_SUPPORTED_MINOR_VERSION: u32 = 16; - - -const NM_DEVICE_STATE_CHANGED: &'static str = "StateChanged"; - -pub type Result<T> = std::result::Result<T, Error>; - -#[derive(err_derive::Error, Debug)] -pub enum Error { - #[error(display = "Error while communicating over Dbus")] - Dbus(#[error(source)] dbus::Error), - - #[error(display = "Failed to match the returned D-Bus object with expected type")] - MatchDBusTypeError(#[error(source)] dbus::arg::TypeMismatchError), - - #[error(display = "Configuration has no device associated to it")] - NoDevice, - - #[error(display = "NetworkManager is too old - {}", _0)] - NMTooOld(String), - - #[error(display = "Device inactive: {}", _0)] - DeviceNotReady(u32), - - #[error(display = "Device not found")] - DeviceNotFound, - - #[error(display = "NetworkManager not detected")] - NetworkManagerNotDetected, - - #[error(display = "NetworkManager is using dnsmasq to manage DNS")] - UsingDnsmasq, - - #[error(display = "NetworkManager is too old: {}", 0)] - TooOldNetworkManager(String), - - #[error(display = "NetworkManager is not managing DNS")] - NetworkManagerNotManagingDns, - - #[error(display = "Failed to get devices from NetworkManager object")] - ObtainDevices, -} - - -pub type VariantRefArg = Variant<Box<dyn RefArg>>; -pub type VariantMap = HashMap<String, VariantRefArg>; -// settings are a{sa{sv}} -pub type DeviceConfig = HashMap<String, VariantMap>; - -/// Implements functionality to control NetworkManager over DBus. -pub struct NetworkManager { - connection: Arc<SyncConnection>, -} - -impl NetworkManager { - pub fn new() -> Result<Self> { - Ok(Self { - connection: crate::linux::dbus::get_connection()?, - }) - } - - pub fn create_wg_tunnel(&self, config: &WireguardConfig) -> Result<WireguardTunnel> { - self.ensure_nm_is_new_enough_for_wireguard()?; - let tunnel = self.create_wg_tunnel_inner(config)?; - if let Err(err) = self.wait_until_device_is_ready(&tunnel.device_path) { - if let Err(removal_error) = self.remove_tunnel(tunnel) { - log::error!( - "Failed to remove WireGuard tunnel after it not becoming ready fast enough: {}", - removal_error - ); - } - return Err(err); - } - - Ok(tunnel) - } - - pub fn get_interface_name(&self, tunnel: &WireguardTunnel) -> Result<String> { - tunnel - .device_proxy(&*self.connection) - .get(NM_DEVICE, "Interface") - .map_err(Error::Dbus) - } - - pub fn get_device_state(&self, device: &dbus::Path<'_>) -> Result<u32> { - self.as_path(device) - .get(NM_DEVICE, "State") - .map_err(Error::Dbus) - } - - fn create_wg_tunnel_inner(&self, config: &WireguardConfig) -> Result<WireguardTunnel> { - let settings_map = Self::convert_config_to_dbus(config); - - let config_path: dbus::Path<'static> = match self.add_connection_2(&settings_map) { - Ok((path, _result)) => path, - Err(Error::Dbus(dbus_error)) if dbus_error.name() == Some(DBUS_UNKNOWN_METHOD) => { - self.add_connection_unsaved(&settings_map)?.0 - } - Err(err) => { - log::error!( - "Failed to create a new interface via AddConnection2 - {}", - err - ); - return Err(err); - } - }; - - let manager = self.nm_manager(); - let (connection_path,): (dbus::Path<'static>,) = manager - .method_call( - NM_MANAGER, - "ActivateConnection", - ( - &config_path, - &dbus::Path::new("/").unwrap(), - &dbus::Path::new("/").unwrap(), - ), - ) - .map_err(Error::Dbus)?; - - let connection = Proxy::new(NM_BUS, &connection_path, RPC_TIMEOUT, &*self.connection); - let device_paths: Vec<dbus::Path<'static>> = connection - .get(NM_CONNECTION_ACTIVE, "Devices") - .map_err(Error::Dbus)?; - let device_path = device_paths.into_iter().next().ok_or(Error::NoDevice)?; - - - Ok(WireguardTunnel { - config_path, - connection_path, - device_path, - }) - } - - fn ensure_nm_is_new_enough_for_wireguard(&self) -> Result<()> { - let manager = self.nm_manager(); - let version_string: String = manager.get(NM_MANAGER, "Version").map_err(Error::Dbus)?; - let version_too_old = || Error::NMTooOld(version_string.clone()); - let mut parts = version_string - .split(".") - .map(|part| part.parse().map_err(|_| version_too_old())); - - let major_version: u32 = parts.next().ok_or_else(|| version_too_old())??; - let minor_version: u32 = parts.next().ok_or_else(|| version_too_old())??; - - if major_version < MINIMUM_SUPPORTED_MAJOR_VERSION - || minor_version < MINIMUM_SUPPORTED_MINOR_VERSION - { - Err(version_too_old()) - } else { - Ok(()) - } - } - - fn add_connection_2( - &self, - settings_map: &DeviceConfig, - ) -> Result<(dbus::Path<'static>, DeviceConfig)> { - let args: VariantMap = HashMap::new(); - - Proxy::new(NM_BUS, NM_SETTINGS_PATH, RPC_TIMEOUT, &*self.connection) - .method_call( - NM_SETTINGS_INTERFACE, - "AddConnection2", - (settings_map, NM_ADD_CONNECTION_VOLATILE, args), - ) - .map_err(Error::Dbus) - } - - fn add_connection_unsaved( - &self, - settings_map: &DeviceConfig, - ) -> Result<(dbus::Path<'static>,)> { - Proxy::new(NM_BUS, NM_SETTINGS_PATH, RPC_TIMEOUT, &*self.connection) - .method_call( - NM_SETTINGS_INTERFACE, - "AddConnectionUnsaved", - (settings_map,), - ) - .map_err(Error::Dbus) - } - - pub fn set_stats_refresh_rate( - &self, - tunnel: &WireguardTunnel, - refresh_rate: u32, - ) -> Result<()> { - tunnel - .device_proxy(&*self.connection) - .set(NM_DEVICE_STATISTICS, "RefreshRateMs", refresh_rate) - .map_err(Error::Dbus) - } - - pub fn get_tunnel_stats(&self, tunnel: &WireguardTunnel) -> Result<(u64, u64)> { - let device = tunnel.device_proxy(&*self.connection); - let tx_bytes = device.get(NM_DEVICE_STATISTICS, "TxBytes")?; - let rx_bytes = device.get(NM_DEVICE_STATISTICS, "RxBytes")?; - - Ok((tx_bytes, rx_bytes)) - } - - fn wait_until_device_is_ready(&self, device: &dbus::Path<'_>) -> Result<()> { - let device_state = self.get_device_state(device)?; - - if !device_is_ready(device_state) { - let deadline = Instant::now() + DEVICE_READY_TIMEOUT; - - let mut match_rule = MatchRule::new_signal(NM_DEVICE, NM_DEVICE_STATE_CHANGED); - - match_rule.path = Some(device.clone().into_static()); - let device_state = Arc::new(AtomicU32::new(device_state)); - - { - let shared_device_state = device_state.clone(); - let device_matcher = self - .connection - .add_match( - match_rule, - move |state_change: DeviceStateChange, _connection, _message| { - log::debug!("Received new tunnel state change: {:?}", state_change); - let new_state = state_change.new_state; - shared_device_state.store(new_state, Ordering::Release); - true - }, - ) - .map_err(Error::Dbus)?; - while Instant::now() < deadline - && !device_is_ready(device_state.load(Ordering::Acquire)) - { - if let Err(err) = self.connection.process(RPC_TIMEOUT) { - log::error!( - "DBus connection failed while waiting for device to be ready: {}", - err - ); - } - } - - if let Err(err) = self.connection.remove_match(device_matcher) { - log::error!("Failed to remove match from DBus connection: {}", err); - } - } - - let final_device_state = device_state.load(Ordering::Acquire); - if !device_is_ready(final_device_state) { - return Err(Error::DeviceNotReady(final_device_state)); - } - } - Ok(()) - } - - pub fn remove_tunnel(&self, tunnel: WireguardTunnel) -> Result<()> { - let deactivation_result: Result<()> = self - .nm_manager() - .method_call( - NM_MANAGER, - "DeactivateConnection", - (&tunnel.connection_path,), - ) - .map_err(Error::Dbus); - - let config_result: Result<()> = tunnel - .config_proxy(&*self.connection) - .method_call(NM_SETTINGS_CONNECTION_INTERFACE, "Delete", ()) - .map_err(Error::Dbus); - deactivation_result?; - config_result?; - Ok(()) - } - - /// Ensures NetworkManager's connectivity check is disabled and returns the connectivity check - /// previous state. Returns true only if the connectivity check was enabled and is now - /// disabled. Disabling the connectivity check should be done before a firewall is applied - /// due to the fact that blocking DNS requests can make it hang: - /// https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/404 - pub fn disable_connectivity_check(&self) -> Option<bool> { - let nm_manager = self.nm_manager(); - match nm_manager.get(NM_MANAGER, CONNECTIVITY_CHECK_KEY) { - Ok(true) => { - if let Err(err) = nm_manager.set(NM_MANAGER, CONNECTIVITY_CHECK_KEY, false) { - log::error!( - "Failed to disable NetworkManager connectivity check: {}", - err - ); - Some(false) - } else { - Some(true) - } - } - Ok(false) => Some(false), - Err(_) => None, - } - } - - /// Enabled NetworkManager's connectivity check. Fails silently. - pub fn enable_connectivity_check(&self) { - if let Err(err) = self - .nm_manager() - .set(NM_MANAGER, CONNECTIVITY_CHECK_KEY, true) - { - log::error!("Failed to reset NetworkManager connectivity check: {}", err); - } - } - - fn nm_manager(&self) -> Proxy<'_, &SyncConnection> { - Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, &*self.connection) - } - - fn convert_config_to_dbus(config: &WireguardConfig) -> DeviceConfig { - let mut ipv6_config: VariantMap = HashMap::new(); - let mut ipv4_config: VariantMap = HashMap::new(); - let mut wireguard_config: VariantMap = HashMap::new(); - let mut connection_config: VariantMap = HashMap::new(); - let mut peer_configs = vec![]; - - wireguard_config.insert("mtu".into(), Variant(Box::new(config.mtu as u32))); - wireguard_config.insert("fwmark".into(), Variant(Box::new(config.fwmark as u32))); - wireguard_config.insert("peer-routes".into(), Variant(Box::new(false))); - wireguard_config.insert( - "private-key".into(), - Variant(Box::new(config.tunnel.private_key.to_base64())), - ); - wireguard_config.insert("private-key-flags".into(), Variant(Box::new(0x0u32))); - - for peer in config.peers.iter() { - let mut peer_config: VariantMap = HashMap::new(); - let allowed_ips = peer - .allowed_ips - .iter() - .map(ToString::to_string) - .collect::<Vec<_>>(); - - - peer_config.insert("allowed-ips".into(), Variant(Box::new(allowed_ips))); - peer_config.insert( - "endpoint".into(), - Variant(Box::new(peer.endpoint.to_string())), - ); - peer_config.insert( - "public-key".into(), - Variant(Box::new(peer.public_key.to_base64())), - ); - - peer_configs.push(peer_config); - } - wireguard_config.insert("peers".into(), Variant(Box::new(peer_configs))); - - connection_config.insert("type".into(), Variant(Box::new("wireguard".to_string()))); - connection_config.insert( - "id".into(), - Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), - ); - connection_config.insert( - "interface-name".into(), - Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), - ); - connection_config.insert("autoconnect".into(), Variant(Box::new(true))); - - - let ipv4_addrs: Vec<_> = config - .tunnel - .addresses - .iter() - .filter(|ip| ip.is_ipv4()) - .map(Self::convert_address_to_dbus) - .collect(); - - let ipv6_addrs: Vec<_> = config - .tunnel - .addresses - .iter() - .filter(|ip| ip.is_ipv6()) - .map(Self::convert_address_to_dbus) - .collect(); - - ipv4_config.insert("address-data".into(), Variant(Box::new(ipv4_addrs))); - ipv4_config.insert("ignore-auto-routes".into(), Variant(Box::new(true))); - ipv4_config.insert("ignore-auto-dns".into(), Variant(Box::new(true))); - ipv4_config.insert("may-fail".into(), Variant(Box::new(true))); - ipv4_config.insert("method".into(), Variant(Box::new("manual".to_string()))); - ipv4_config.insert("never-default".into(), Variant(Box::new(true))); - - if !ipv6_addrs.is_empty() { - ipv6_config.insert("method".into(), Variant(Box::new("manual".to_string()))); - ipv6_config.insert("address-data".into(), Variant(Box::new(ipv6_addrs))); - ipv6_config.insert("ignore-auto-routes".into(), Variant(Box::new(true))); - ipv6_config.insert("ignore-auto-dns".into(), Variant(Box::new(true))); - ipv6_config.insert("may-fail".into(), Variant(Box::new(true))); - } - - - let mut settings = HashMap::new(); - settings.insert("ipv4".into(), ipv4_config); - if !ipv6_config.is_empty() { - settings.insert("ipv6".into(), ipv6_config); - } - settings.insert("wireguard".into(), wireguard_config); - settings.insert("connection".into(), connection_config); - - settings - } - - fn convert_address_to_dbus(address: &IpAddr) -> VariantMap { - let mut map: VariantMap = HashMap::new(); - map.insert( - "address".to_string(), - Variant(Box::new(address.to_string())), - ); - let prefix: u32 = if address.is_ipv4() { 32 } else { 128 }; - map.insert("prefix".to_string(), Variant(Box::new(prefix))); - - map - } - - pub fn ensure_network_manager_exists(&self) -> Result<()> { - match self - .as_manager() - .get::<Box<dyn RefArg>>(&NM_MANAGER, "Version") - { - Ok(_) => Ok(()), - Err(err) => { - log::error!("Failed to read version of NetworkManager {}", err); - Err(Error::NetworkManagerNotDetected) - } - } - } - - pub fn ensure_resolv_conf_is_managed(&self) -> Result<()> { - // check if NM is set to manage resolv.conf - let management_mode: String = self - .as_dns_manager() - .get(NM_DNS_MANAGER, RC_MANAGEMENT_MODE_KEY)?; - if management_mode == "unmanaged" { - return Err(Error::NetworkManagerNotManagingDns); - } - - let dns_mode: String = self - .as_dns_manager() - .get(NM_DNS_MANAGER, DNS_MODE_KEY) - .map_err(Error::Dbus)?; - - match dns_mode.as_ref() { - // NM can't setup config for multiple interfaces with dnsmasq - "dnsmasq" => return Err(Error::UsingDnsmasq), - // If NetworkManager isn't managing DNS for us, it's useless. - "none" => return Err(Error::NetworkManagerNotManagingDns), - _ => (), - }; - - if !verify_etc_resolv_conf_contents() { - log::debug!("/etc/resolv.conf differs from reference resolv.conf, therefore NM is not managing DNS"); - return Err(Error::NetworkManagerNotManagingDns); - } - - Ok(()) - } - - fn as_manager<'a>(&'a self) -> Proxy<'a, &SyncConnection> { - Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, &*self.connection) - } - - fn as_dns_manager<'a>(&'a self) -> Proxy<'a, &SyncConnection> { - Proxy::new(NM_BUS, NM_DNS_MANAGER_PATH, RPC_TIMEOUT, &*self.connection) - } - - fn as_path<'a>(&'a self, device: &'a dbus::Path<'a>) -> Proxy<'a, &SyncConnection> { - Proxy::new(NM_BUS, device, RPC_TIMEOUT, &*self.connection) - } - - pub fn set_dns(&mut self, interface_name: &str, servers: &[IpAddr]) -> Result<DeviceConfig> { - let device_path = self.fetch_device(interface_name)?; - self.wait_until_device_is_ready(&device_path)?; - - - let device = self.as_path(&device_path); - // Get the last applied connection - let (mut settings, version_id): ( - HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>>, - u64, - ) = device.method_call(NM_DEVICE, "GetAppliedConnection", (0u32,))?; - - // Keep changed routes. - // These routes were modified outside NM, likely by RouteManager. - if let Some(ipv4_settings) = settings.get_mut("ipv4") { - let device_ip4_config: dbus::Path<'_> = - device.get(NM_DEVICE, "Ip4Config").map_err(Error::Dbus)?; - - let device_routes: Vec<Vec<u32>> = self - .as_path(&device_ip4_config) - .get(NM_IP4_CONFIG, "Routes") - .map_err(Error::Dbus)?; - - let device_route_data: Vec<HashMap<String, Variant<Box<dyn RefArg>>>> = self - .as_path(&device_ip4_config) - .get(NM_IP4_CONFIG, "RouteData") - .map_err(Error::Dbus)?; - - ipv4_settings.insert("route-metric".to_string(), Variant(Box::new(0u32))); - ipv4_settings.insert("routes".to_string(), Variant(Box::new(device_routes))); - ipv4_settings.insert( - "route-data".to_string(), - Variant(Box::new(device_route_data)), - ); - } - - if let Some(ipv6_settings) = settings.get_mut("ipv6") { - let device_ip6_config: dbus::Path<'_> = - device.get(NM_DEVICE, "Ip6Config").map_err(Error::Dbus)?; - - let device_addresses6: Vec<(Vec<u8>, u32, Vec<u8>)> = self - .as_path(&device_ip6_config) - .get(NM_IP6_CONFIG, "Addresses") - .map_err(Error::Dbus)?; - - let device_routes6: Vec<(Vec<u8>, u32, Vec<u8>, u32)> = self - .as_path(&device_ip6_config) - .get(NM_IP6_CONFIG, "Routes") - .map_err(Error::Dbus)?; - - let device_route6_data: Vec<HashMap<String, Variant<Box<dyn RefArg>>>> = self - .as_path(&device_ip6_config) - .get(NM_IP6_CONFIG, "RouteData") - .map_err(Error::Dbus)?; - - ipv6_settings.insert("route-metric".to_string(), Variant(Box::new(0u32))); - ipv6_settings.insert("routes".to_string(), Variant(Box::new(device_routes6))); - ipv6_settings.insert( - "route-data".to_string(), - Variant(Box::new(device_route6_data)), - ); - // if the link contains link local addresses, addresses shouldn't be reset - if ipv6_settings - .get("method") - .map(|method| { - // if IPv6 isn't enabled, IPv6 method will be set to "ignore", in which case we - // shouldn't reapply any config for ipv6 - method.as_str() != Some("link-local") && method.as_str() != Some("ignore") - }) - .unwrap_or(true) - { - ipv6_settings.insert( - "addresses".to_string(), - Variant(Box::new(device_addresses6)), - ); - } - } - - let mut settings_backup = - HashMap::<String, HashMap<String, Variant<Box<dyn RefArg>>>>::new(); - for (top_key, map) in settings.iter() { - let mut inner_dict = HashMap::<String, Variant<Box<dyn RefArg>>>::new(); - for (key, variant) in map.iter() { - inner_dict.insert(key.to_string(), Variant(variant.0.box_clone())); - } - settings_backup.insert(top_key.to_string(), inner_dict); - } - - // Update the DNS config - let v4_dns: Vec<u32> = servers - .iter() - .filter_map(|server| { - match server { - // Network-byte order - IpAddr::V4(server) => Some(u32::to_be(server.clone().into())), - IpAddr::V6(_) => None, - } - }) - .collect(); - if !v4_dns.is_empty() { - Self::update_dns_config(&mut settings, "ipv4", v4_dns); - } - - let v6_dns: Vec<Vec<u8>> = servers - .iter() - .filter_map(|server| match server { - IpAddr::V4(_) => None, - IpAddr::V6(server) => Some(server.octets().to_vec()), - }) - .collect(); - if !v6_dns.is_empty() { - Self::update_dns_config(&mut settings, "ipv6", v6_dns); - } - - if let Some(wg_config) = settings.get_mut("wireguard") { - wg_config.insert( - "fwmark".to_string(), - Variant(Box::new(crate::linux::TUNNEL_FW_MARK) as Box<dyn RefArg>), - ); - } - - self.reapply_settings(&device_path, settings, version_id)?; - Ok(settings_backup) - } - - pub fn reapply_settings<Settings: arg::Append>( - &self, - device: &dbus::Path<'_>, - settings: Settings, - version_id: u64, - ) -> Result<()> { - self.as_path(device) - .method_call(NM_DEVICE, "Reapply", (settings, version_id, 0u32))?; - Ok(()) - } - - fn update_dns_config<'a, T>( - settings: &mut HashMap<String, HashMap<String, Variant<Box<dyn RefArg + 'a>>>>, - ip_protocol: &'static str, - servers: T, - ) where - T: RefArg + 'a, - { - let settings = match settings.get_mut(ip_protocol) { - Some(ip_protocol) => ip_protocol, - None => { - settings.insert(ip_protocol.to_string(), HashMap::new()); - settings.get_mut(ip_protocol).unwrap() - } - }; - - settings.insert( - "method".to_string(), - Variant(Box::new("manual".to_string())), - ); - settings.insert( - "dns-priority".to_string(), - Variant(Box::new(DNS_FIRST_PRIORITY)), - ); - settings.insert("dns".to_string(), Variant(Box::new(servers))); - settings.insert( - "dns-search".to_string(), - Variant(Box::new(vec!["~.".to_string()])), - ); - } - - pub fn fetch_device(&self, interface_name: &str) -> Result<dbus::Path<'static>> { - let devices: Box<dyn RefArg> = self - .as_manager() - .get(NM_MANAGER, "Devices") - .map_err(Error::Dbus)?; - let mut iter = devices - .as_iter() - .ok_or(Error::ObtainDevices)? - .map(|device| device.box_clone()); - - while let Some(device_item) = iter.next() { - // Copy due to lifetime weirdness - let device_path = device_item - .as_any() - .downcast_ref::<dbus::Path<'_>>() - .ok_or(Error::ObtainDevices)?; - - let device_name: String = self - .as_path(&device_path) - .get(NM_DEVICE, "Interface") - .map_err(Error::Dbus)?; - - if device_name != interface_name { - continue; - } - - return Ok(device_path.clone()); - } - Err(Error::DeviceNotFound) - } -} - -#[derive(Debug)] -struct DeviceStateChange { - new_state: u32, - old_state: u32, - reason: u32, -} - -impl arg::ReadAll for DeviceStateChange { - fn read(i: &mut arg::Iter<'_>) -> std::result::Result<Self, arg::TypeMismatchError> { - Ok(DeviceStateChange { - new_state: i.read()?, - old_state: i.read()?, - reason: i.read()?, - }) - } -} - -impl dbus::message::SignalArgs for DeviceStateChange { - const NAME: &'static str = NM_DEVICE_STATE_CHANGED; - const INTERFACE: &'static str = NM_DEVICE; -} - -#[derive(Debug)] -pub struct WireguardTunnel { - config_path: dbus::Path<'static>, - connection_path: dbus::Path<'static>, - device_path: dbus::Path<'static>, -} - -impl WireguardTunnel { - fn device_proxy<'a>(&'a self, connection: &'a SyncConnection) -> Proxy<'a, &SyncConnection> { - Proxy::new(NM_BUS, &self.device_path, RPC_TIMEOUT, connection) - } - - fn config_proxy<'a>(&'a self, connection: &'a SyncConnection) -> Proxy<'a, &SyncConnection> { - Proxy::new(NM_BUS, &self.config_path, RPC_TIMEOUT, connection) - } -} - - -pub fn device_is_ready(device_state: u32) -> bool { - /// Any state above `NM_DEVICE_STATE_IP_CONFIG` is considered to be an OK state to change the - /// DNS config. For the enums, see https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceState - const READY_STATES: [u32; 3] = [ - NM_DEVICE_STATE_IP_CHECK, - NM_DEVICE_STATE_SECONDARY, - NM_DEVICE_STATE_ACTIVATED, - ]; - READY_STATES.contains(&device_state) -} - -// Verify that the contents of /etc/resolv.conf match what NM expectes them to be. -fn verify_etc_resolv_conf_contents() -> bool { - let expected_resolv_conf = "/var/run/NetworkManager/resolv.conf"; - let actual_resolv_conf = "/etc/resolv.conf"; - eq_file_content(&expected_resolv_conf, &actual_resolv_conf) -} - - -fn eq_file_content<P: AsRef<Path>>(a: &P, b: &P) -> bool { - let file_a = match File::open(a).map(BufReader::new) { - Ok(file) => file, - Err(e) => { - log::debug!("Failed top open file {}: {}", a.as_ref().display(), e); - return false; - } - }; - let file_b = match File::open(b).map(BufReader::new) { - Ok(file) => file, - Err(e) => { - log::debug!("Failed top open file {}: {}", b.as_ref().display(), e); - return false; - } - }; - - !file_a - .lines() - .zip(file_b.lines()) - .any(|(a, b)| match (a, b) { - (Ok(a), Ok(b)) => a != b, - _ => false, - }) -} diff --git a/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs b/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs index adaa8977dd..21f5f283cc 100644 --- a/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs +++ b/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs @@ -2,8 +2,13 @@ use super::{ super::stats::{Error as StatsError, Stats}, Config, Error as WgKernelError, Tunnel, TunnelError, MULLVAD_INTERFACE_NAME, }; -use crate::linux::network_manager::{ - Error as NetworkManagerError, NetworkManager, WireguardTunnel, +use std::collections::HashMap; +use talpid_dbus::{ + dbus, + network_manager::{ + DeviceConfig, Error as NetworkManagerError, NetworkManager, Variant, VariantMap, + WireguardTunnel, + }, }; use talpid_types::ErrorExt; @@ -31,8 +36,9 @@ impl NetworkManagerTunnel { let network_manager = NetworkManager::new() .map_err(Error::NetworkManager) .map_err(WgKernelError::NetworkManager)?; + let config_map = convert_config_to_dbus(config); let tunnel = network_manager - .create_wg_tunnel(config) + .create_wg_tunnel(&config_map) .map_err(|err| WgKernelError::NetworkManager(err.into()))?; network_manager @@ -100,3 +106,97 @@ impl Tunnel for NetworkManagerTunnel { } } } + +fn convert_config_to_dbus(config: &Config) -> DeviceConfig { + let mut ipv6_config: VariantMap = HashMap::new(); + let mut ipv4_config: VariantMap = HashMap::new(); + let mut wireguard_config: VariantMap = HashMap::new(); + let mut connection_config: VariantMap = HashMap::new(); + let mut peer_configs = vec![]; + + wireguard_config.insert("mtu".into(), Variant(Box::new(config.mtu as u32))); + wireguard_config.insert("fwmark".into(), Variant(Box::new(config.fwmark as u32))); + wireguard_config.insert("peer-routes".into(), Variant(Box::new(false))); + wireguard_config.insert( + "private-key".into(), + Variant(Box::new(config.tunnel.private_key.to_base64())), + ); + wireguard_config.insert("private-key-flags".into(), Variant(Box::new(0x0u32))); + + for peer in config.peers.iter() { + let mut peer_config: VariantMap = HashMap::new(); + let allowed_ips = peer + .allowed_ips + .iter() + .map(ToString::to_string) + .collect::<Vec<_>>(); + + + peer_config.insert("allowed-ips".into(), Variant(Box::new(allowed_ips))); + peer_config.insert( + "endpoint".into(), + Variant(Box::new(peer.endpoint.to_string())), + ); + peer_config.insert( + "public-key".into(), + Variant(Box::new(peer.public_key.to_base64())), + ); + + peer_configs.push(peer_config); + } + wireguard_config.insert("peers".into(), Variant(Box::new(peer_configs))); + + connection_config.insert("type".into(), Variant(Box::new("wireguard".to_string()))); + connection_config.insert( + "id".into(), + Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), + ); + connection_config.insert( + "interface-name".into(), + Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), + ); + connection_config.insert("autoconnect".into(), Variant(Box::new(true))); + + + let ipv4_addrs: Vec<_> = config + .tunnel + .addresses + .iter() + .filter(|ip| ip.is_ipv4()) + .map(NetworkManager::convert_address_to_dbus) + .collect(); + + let ipv6_addrs: Vec<_> = config + .tunnel + .addresses + .iter() + .filter(|ip| ip.is_ipv6()) + .map(NetworkManager::convert_address_to_dbus) + .collect(); + + ipv4_config.insert("address-data".into(), Variant(Box::new(ipv4_addrs))); + ipv4_config.insert("ignore-auto-routes".into(), Variant(Box::new(true))); + ipv4_config.insert("ignore-auto-dns".into(), Variant(Box::new(true))); + ipv4_config.insert("may-fail".into(), Variant(Box::new(true))); + ipv4_config.insert("method".into(), Variant(Box::new("manual".to_string()))); + ipv4_config.insert("never-default".into(), Variant(Box::new(true))); + + if !ipv6_addrs.is_empty() { + ipv6_config.insert("method".into(), Variant(Box::new("manual".to_string()))); + ipv6_config.insert("address-data".into(), Variant(Box::new(ipv6_addrs))); + ipv6_config.insert("ignore-auto-routes".into(), Variant(Box::new(true))); + ipv6_config.insert("ignore-auto-dns".into(), Variant(Box::new(true))); + ipv6_config.insert("may-fail".into(), Variant(Box::new(true))); + } + + + let mut settings = HashMap::new(); + settings.insert("ipv4".into(), ipv4_config); + if !ipv6_config.is_empty() { + settings.insert("ipv6".into(), ipv6_config); + } + settings.insert("wireguard".into(), wireguard_config); + settings.insert("connection".into(), connection_config); + + settings +} diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index b657ec5e36..5a86722c01 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -386,7 +386,7 @@ impl SharedTunnelStateValues { #[cfg(target_os = "linux")] pub fn disable_connectivity_check(&mut self) { if self.connectivity_check_was_enabled.is_none() { - if let Ok(nm) = crate::linux::network_manager::NetworkManager::new() { + if let Ok(nm) = talpid_dbus::network_manager::NetworkManager::new() { self.connectivity_check_was_enabled = nm.disable_connectivity_check(); } } else { @@ -398,7 +398,7 @@ impl SharedTunnelStateValues { #[cfg(target_os = "linux")] pub fn reset_connectivity_check(&mut self) { if self.connectivity_check_was_enabled.take() == Some(true) { - if let Ok(nm) = crate::linux::network_manager::NetworkManager::new() { + if let Ok(nm) = talpid_dbus::network_manager::NetworkManager::new() { nm.enable_connectivity_check(); } } else { |
