diff options
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 4 | ||||
| -rw-r--r-- | talpid-dbus/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-dbus/src/lib.rs | 3 | ||||
| -rw-r--r-- | talpid-dbus/src/network_manager.rs | 756 | ||||
| -rw-r--r-- | talpid-dbus/src/network_manager/dbus_rs.rs | 753 | ||||
| -rw-r--r-- | talpid-dbus/src/network_manager/zbus.rs | 298 | ||||
| -rw-r--r-- | talpid-dns/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-dns/src/linux/network_manager.rs | 6 | ||||
| -rw-r--r-- | talpid-platform-metadata/src/linux.rs | 2 | ||||
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-wireguard/src/lib.rs | 12 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs | 153 |
13 files changed, 1155 insertions, 840 deletions
diff --git a/Cargo.toml b/Cargo.toml index 35e825dfb4..95b94ca1a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ sha2 = "0.10" shadowsocks = "1.23.2" socket2 = "0.5.7" strum = { version = "0.27" } -talpid-dbus = { path = "./talpid-dbus" } +talpid-dbus = { path = "./talpid-dbus", default-features = false } thiserror = "2.0" tipsy = "0.7.0" tokio = { version = "1.44" } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index fde54ecf08..1576d0a32c 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -672,7 +672,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) = talpid_dbus::network_manager::NetworkManager::new() { + if let Ok(nm) = talpid_dbus::network_manager::dbus_rs::NetworkManager::new() { self.connectivity_check_was_enabled = nm.disable_connectivity_check(); } } else { @@ -684,7 +684,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) = talpid_dbus::network_manager::NetworkManager::new() { + if let Ok(nm) = talpid_dbus::network_manager::dbus_rs::NetworkManager::new() { nm.enable_connectivity_check(); } } else { diff --git a/talpid-dbus/Cargo.toml b/talpid-dbus/Cargo.toml index d91d4ac04a..df351d3b27 100644 --- a/talpid-dbus/Cargo.toml +++ b/talpid-dbus/Cargo.toml @@ -16,7 +16,7 @@ zbus = { version = "5.12", optional = true } zvariant = { version = "5.8.0", optional = true } [features] -default = ["zbus"] +default = ["libdbus"] libdbus = ["dep:dbus"] zbus = ["dep:zbus", "dep:zvariant"] # experimental: Prefer zbus over libdbus diff --git a/talpid-dbus/src/lib.rs b/talpid-dbus/src/lib.rs index 260c6b0bc4..9dee681a7b 100644 --- a/talpid-dbus/src/lib.rs +++ b/talpid-dbus/src/lib.rs @@ -1,13 +1,14 @@ //! DBus system connection #![cfg(target_os = "linux")] -#[cfg(feature = "libdbus")] // TODO: implement network_manager using zbus pub mod network_manager; pub mod systemd; pub mod systemd_resolved; #[cfg(feature = "libdbus")] pub use dbus; +#[cfg(feature = "zbus")] +pub use zbus; #[cfg(feature = "libdbus")] use std::sync::{Arc, LazyLock, Mutex}; diff --git a/talpid-dbus/src/network_manager.rs b/talpid-dbus/src/network_manager.rs index 623aab3326..d87f4e5c13 100644 --- a/talpid-dbus/src/network_manager.rs +++ b/talpid-dbus/src/network_manager.rs @@ -1,754 +1,6 @@ //! NetworkManager is the one-stop-shop of network configuration on Linux. -use super::systemd_resolved; -pub use dbus::arg::{RefArg, Variant}; -use dbus::{ - arg, - blocking::{Proxy, SyncConnection, stdintf::org_freedesktop_dbus::Properties}, - message::MatchRule, -}; -use std::{ - collections::HashMap, - fs::File, - io::{BufRead, BufReader}, - net::IpAddr, - path::Path, - sync::{ - Arc, - atomic::{AtomicU32, Ordering}, - }, - 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_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 MAXIMUM_SUPPORTED_MAJOR_VERSION: u32 = 1; -const MAXIMUM_SUPPORTED_MINOR_VERSION: u32 = 26; - -const NM_DEVICE_STATE_CHANGED: &str = "StateChanged"; - -pub type Result<T> = std::result::Result<T, Error>; -type NetworkSettings<'a> = HashMap<String, HashMap<String, Variant<Box<dyn RefArg + 'a>>>>; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error while communicating over Dbus")] - Dbus(#[from] dbus::Error), - - #[error("Failed to match the returned D-Bus object with expected type")] - MatchDBusTypeError(#[from] dbus::arg::TypeMismatchError), - - #[error( - "NM is configured to manage DNS via systemd-resolved but systemd-resolved is not managing /etc/resolv.conf: {0}" - )] - SystemdResolvedNotManagingResolvconf(systemd_resolved::Error), - - #[error("Configuration has no device associated to it")] - NoDevice, - - #[error("NetworkManager is too old: {0}.{1}")] - NMTooOld(u32, u32), - - #[error("NetworkManager is too new to manage DNS: {0}.{1}")] - NMTooNewFroDns(u32, u32), - - #[error("Failed to parse NetworkManager version string: {0}")] - ParseNmVersionError(String), - - #[error("Device inactive: {0}")] - DeviceNotReady(u32), - - #[error("Device not found")] - DeviceNotFound, - - #[error("NetworkManager not detected")] - NetworkManagerNotDetected, - - #[error("NetworkManager is using dnsmasq to manage DNS")] - UsingDnsmasq, - - #[error("NetworkManager is too old: {0}")] - TooOldNetworkManager(String), - - #[error("NetworkManager is not managing DNS")] - NetworkManagerNotManagingDns, - - #[error("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::get_connection()?, - }) - } - - pub fn create_wg_tunnel(&self, config: &DeviceConfig) -> Result<WireguardTunnel> { - self.nm_supports_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: &DeviceConfig) -> Result<WireguardTunnel> { - let config_path: dbus::Path<'static> = match self.add_connection_2(config) { - Ok((path, _result)) => path, - Err(Error::Dbus(dbus_error)) if dbus_error.name() == Some(DBUS_UNKNOWN_METHOD) => { - self.add_connection_unsaved(config)?.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, - }) - } - - pub fn nm_supports_wireguard(&self) -> Result<()> { - let (major, minor) = self.version()?; - Self::ensure_nm_is_new_enough_for_wireguard(major, minor)?; - Self::ensure_nm_is_old_enough_for_dns(major, minor) - } - - pub fn nm_version_dns_works(&self) -> Result<()> { - let (major, minor) = self.version()?; - Self::ensure_nm_is_old_enough_for_dns(major, minor) - } - - pub fn version_string(&self) -> Result<String> { - let manager = self.nm_manager(); - manager.get(NM_MANAGER, "Version").map_err(Error::Dbus) - } - - fn ensure_nm_is_new_enough_for_wireguard(major: u32, minor: u32) -> Result<()> { - if major < MINIMUM_SUPPORTED_MAJOR_VERSION - || (minor < MINIMUM_SUPPORTED_MINOR_VERSION && major == MINIMUM_SUPPORTED_MAJOR_VERSION) - { - Err(Error::NMTooOld(major, minor)) - } else { - Ok(()) - } - } - - fn ensure_nm_is_old_enough_for_dns(major_version: u32, minor_version: u32) -> Result<()> { - if major_version > MAXIMUM_SUPPORTED_MAJOR_VERSION - || (minor_version > MAXIMUM_SUPPORTED_MINOR_VERSION - && major_version >= MAXIMUM_SUPPORTED_MAJOR_VERSION) - { - Err(Error::NMTooNewFroDns(major_version, minor_version)) - } else { - Ok(()) - } - } - - fn version(&self) -> Result<(u32, u32)> { - let version = self.version_string()?; - Self::parse_nm_version(&version).ok_or(Error::ParseNmVersionError(version)) - } - - fn parse_nm_version(version: &str) -> Option<(u32, u32)> { - let mut parts = version.split('.').map(|part| part.parse().ok()); - - let major_version: u32 = parts.next()??; - let minor_version: u32 = parts.next()??; - Some((major_version, minor_version)) - } - - 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) - } - - 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) - } - - 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_can_be_used_to_manage_dns(&self) -> Result<()> { - self.ensure_resolv_conf_is_managed()?; - self.ensure_network_manager_exists()?; - self.nm_version_dns_works()?; - Ok(()) - } - 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); - } - - if management_mode == "systemd-resolved" { - return match systemd_resolved::SystemdResolved::new() { - Ok(_) => Ok(()), - Err(err) => Err(Error::SystemdResolvedNotManagingResolvconf(err)), - }; - } - - 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(&'_ self) -> Proxy<'_, &SyncConnection> { - Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, &*self.connection) - } - - fn as_dns_manager(&'_ self) -> Proxy<'_, &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, &'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): (NetworkSettings<'_>, 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.clone(), Variant(variant.0.box_clone())); - } - settings_backup.insert(top_key.clone(), 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).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.contains_key("fwmark") - { - log::error!("WireGuard config doesn't contain the firewall mark"); - } - - 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 NetworkSettings<'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 iter = devices - .as_iter() - .ok_or(Error::ObtainDevices)? - .map(|device| device.box_clone()); - - for device_item in iter { - // 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) - } - - pub 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 - } -} - -#[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, &'a SyncConnection> { - Proxy::new(NM_BUS, &self.device_path, RPC_TIMEOUT, connection) - } - - fn config_proxy<'a>(&'a self, connection: &'a SyncConnection) -> Proxy<'a, &'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 expects 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 to 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 to 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, - }) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_valid_versions() { - NetworkManager::ensure_nm_is_new_enough_for_wireguard(1, 16).unwrap(); - NetworkManager::ensure_nm_is_old_enough_for_dns(1, 26).unwrap(); - assert!(NetworkManager::ensure_nm_is_new_enough_for_wireguard(1, 14).is_err()); - assert!(NetworkManager::ensure_nm_is_old_enough_for_dns(1, 28).is_err()); - } -} +#[cfg(feature = "libdbus")] +pub mod dbus_rs; +#[cfg(feature = "zbus")] +pub mod zbus; diff --git a/talpid-dbus/src/network_manager/dbus_rs.rs b/talpid-dbus/src/network_manager/dbus_rs.rs new file mode 100644 index 0000000000..b15dd7b45e --- /dev/null +++ b/talpid-dbus/src/network_manager/dbus_rs.rs @@ -0,0 +1,753 @@ +use crate::systemd_resolved; +pub use dbus::arg::{RefArg, Variant}; +use dbus::{ + arg, + blocking::{Proxy, SyncConnection, stdintf::org_freedesktop_dbus::Properties}, + message::MatchRule, +}; +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader}, + net::IpAddr, + path::Path, + sync::{ + Arc, + atomic::{AtomicU32, Ordering}, + }, + 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_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 MAXIMUM_SUPPORTED_MAJOR_VERSION: u32 = 1; +const MAXIMUM_SUPPORTED_MINOR_VERSION: u32 = 26; + +const NM_DEVICE_STATE_CHANGED: &str = "StateChanged"; + +pub type Result<T> = std::result::Result<T, Error>; +type NetworkSettings<'a> = HashMap<String, HashMap<String, Variant<Box<dyn RefArg + 'a>>>>; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Error while communicating over Dbus")] + Dbus(#[from] dbus::Error), + + #[error("Failed to match the returned D-Bus object with expected type")] + MatchDBusTypeError(#[from] dbus::arg::TypeMismatchError), + + #[error( + "NM is configured to manage DNS via systemd-resolved but systemd-resolved is not managing /etc/resolv.conf: {0}" + )] + SystemdResolvedNotManagingResolvconf(systemd_resolved::Error), + + #[error("Configuration has no device associated to it")] + NoDevice, + + #[error("NetworkManager is too old: {0}.{1}")] + NMTooOld(u32, u32), + + #[error("NetworkManager is too new to manage DNS: {0}.{1}")] + NMTooNewFroDns(u32, u32), + + #[error("Failed to parse NetworkManager version string: {0}")] + ParseNmVersionError(String), + + #[error("Device inactive: {0}")] + DeviceNotReady(u32), + + #[error("Device not found")] + DeviceNotFound, + + #[error("NetworkManager not detected")] + NetworkManagerNotDetected, + + #[error("NetworkManager is using dnsmasq to manage DNS")] + UsingDnsmasq, + + #[error("NetworkManager is too old: {0}")] + TooOldNetworkManager(String), + + #[error("NetworkManager is not managing DNS")] + NetworkManagerNotManagingDns, + + #[error("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::get_connection()?, + }) + } + + pub fn create_wg_tunnel(&self, config: &DeviceConfig) -> Result<WireguardTunnel> { + self.nm_supports_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: &DeviceConfig) -> Result<WireguardTunnel> { + let config_path: dbus::Path<'static> = match self.add_connection_2(config) { + Ok((path, _result)) => path, + Err(Error::Dbus(dbus_error)) if dbus_error.name() == Some(DBUS_UNKNOWN_METHOD) => { + self.add_connection_unsaved(config)?.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, + }) + } + + pub fn nm_supports_wireguard(&self) -> Result<()> { + let (major, minor) = self.version()?; + Self::ensure_nm_is_new_enough_for_wireguard(major, minor)?; + Self::ensure_nm_is_old_enough_for_dns(major, minor) + } + + pub fn nm_version_dns_works(&self) -> Result<()> { + let (major, minor) = self.version()?; + Self::ensure_nm_is_old_enough_for_dns(major, minor) + } + + pub fn version_string(&self) -> Result<String> { + let manager = self.nm_manager(); + manager.get(NM_MANAGER, "Version").map_err(Error::Dbus) + } + + fn ensure_nm_is_new_enough_for_wireguard(major: u32, minor: u32) -> Result<()> { + if major < MINIMUM_SUPPORTED_MAJOR_VERSION + || (minor < MINIMUM_SUPPORTED_MINOR_VERSION && major == MINIMUM_SUPPORTED_MAJOR_VERSION) + { + Err(Error::NMTooOld(major, minor)) + } else { + Ok(()) + } + } + + fn ensure_nm_is_old_enough_for_dns(major_version: u32, minor_version: u32) -> Result<()> { + if major_version > MAXIMUM_SUPPORTED_MAJOR_VERSION + || (minor_version > MAXIMUM_SUPPORTED_MINOR_VERSION + && major_version >= MAXIMUM_SUPPORTED_MAJOR_VERSION) + { + Err(Error::NMTooNewFroDns(major_version, minor_version)) + } else { + Ok(()) + } + } + + fn version(&self) -> Result<(u32, u32)> { + let version = self.version_string()?; + Self::parse_nm_version(&version).ok_or(Error::ParseNmVersionError(version)) + } + + fn parse_nm_version(version: &str) -> Option<(u32, u32)> { + let mut parts = version.split('.').map(|part| part.parse().ok()); + + let major_version: u32 = parts.next()??; + let minor_version: u32 = parts.next()??; + Some((major_version, minor_version)) + } + + 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) + } + + 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) + } + + 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_can_be_used_to_manage_dns(&self) -> Result<()> { + self.ensure_resolv_conf_is_managed()?; + self.ensure_network_manager_exists()?; + self.nm_version_dns_works()?; + Ok(()) + } + 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); + } + + if management_mode == "systemd-resolved" { + return match systemd_resolved::SystemdResolved::new() { + Ok(_) => Ok(()), + Err(err) => Err(Error::SystemdResolvedNotManagingResolvconf(err)), + }; + } + + 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(&'_ self) -> Proxy<'_, &SyncConnection> { + Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, &*self.connection) + } + + fn as_dns_manager(&'_ self) -> Proxy<'_, &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, &'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): (NetworkSettings<'_>, 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.clone(), Variant(variant.0.box_clone())); + } + settings_backup.insert(top_key.clone(), 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).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.contains_key("fwmark") + { + log::error!("WireGuard config doesn't contain the firewall mark"); + } + + 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 NetworkSettings<'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 iter = devices + .as_iter() + .ok_or(Error::ObtainDevices)? + .map(|device| device.box_clone()); + + for device_item in iter { + // 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) + } + + pub 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 + } +} + +#[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, &'a SyncConnection> { + Proxy::new(NM_BUS, &self.device_path, RPC_TIMEOUT, connection) + } + + fn config_proxy<'a>(&'a self, connection: &'a SyncConnection) -> Proxy<'a, &'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 expects 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 to 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 to 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, + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_valid_versions() { + NetworkManager::ensure_nm_is_new_enough_for_wireguard(1, 16).unwrap(); + NetworkManager::ensure_nm_is_old_enough_for_dns(1, 26).unwrap(); + assert!(NetworkManager::ensure_nm_is_new_enough_for_wireguard(1, 14).is_err()); + assert!(NetworkManager::ensure_nm_is_old_enough_for_dns(1, 28).is_err()); + } +} diff --git a/talpid-dbus/src/network_manager/zbus.rs b/talpid-dbus/src/network_manager/zbus.rs new file mode 100644 index 0000000000..6bea3ce4eb --- /dev/null +++ b/talpid-dbus/src/network_manager/zbus.rs @@ -0,0 +1,298 @@ +use std::collections::HashMap; + +use zbus::blocking::Connection; +use zvariant::{OwnedObjectPath, OwnedValue}; + +// TODO: Inline +type O = OwnedObjectPath; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to create a DBus connection")] + Connect(#[source] zbus::Error), + // A Proxy is a helper to interact with an interface on a remote object. + #[error("Failed to create proxy for object {0}")] + Proxy(#[source] zbus::Error), + // Failed to call some method on a proxy. + #[error("Failed to call method on proxy {0}")] + Call(#[source] zbus::Error), +} + +#[derive(Clone)] +pub struct NetworkManager { + pub dbus_connection: Connection, +} + +impl NetworkManager { + pub fn new() -> Result<Self, Error> { + let dbus_connection = crate::get_connection_zbus().map_err(Error::Connect)?; + let network_manager = NetworkManager { dbus_connection }; + // TODO: Assert a recent-enough version of network manager is installed. Not even sure if + // necessary. + network_manager.ensure_can_be_used_to_manage_dns()?; + + Ok(network_manager) + } + + /// Check if NetworkManager is capable of managing DNS. + pub fn ensure_can_be_used_to_manage_dns(&self) -> Result<bool, Error> { + Ok(true) // TODO: Add DNS capabilities. + } + + pub fn create_wg_tunnel(&self, wg_config: DeviceConfig) -> Result<WireguardTunnel, Error> { + WireguardTunnel::new(self.clone(), wg_config) + } + + fn activate_connection<'a>( + &self, + connection: OwnedObjectPath, + ) -> std::result::Result<ConnectionActiveProxyBlocking<'a>, Error> { + let device = OwnedObjectPath::try_from("/").unwrap(); // TODO: unwrap + let specified_object = OwnedObjectPath::try_from("/").unwrap(); // TODO: unwrap + as_nm(&self.dbus_connection)? + .activate_connection(connection, device, specified_object) + .map_err(Error::Call) + } + + fn add_connection_2( + &self, + settings: DeviceConfig, + ) -> Result<(OwnedObjectPath, DeviceConfig), Error> { + // Blocks auto-connect on the new profile. + const NM_ADD_CONNECTION_VOLATILE: u32 = 0x2; + let flags = NM_ADD_CONNECTION_VOLATILE; + let args = HashMap::default(); + as_settings(&self.dbus_connection)? + .add_connection_2(settings, flags, args) + .map_err(Error::Call) + } +} + +fn as_settings<'a>(conn: &Connection) -> Result<NetworkManagerSettingsProxyBlocking<'a>, Error> { + NetworkManagerSettingsProxyBlocking::new(conn).map_err(Error::Proxy) +} + +fn as_nm<'a>(conn: &Connection) -> Result<NetworkManagerProxyBlocking<'a>, Error> { + NetworkManagerProxyBlocking::new(conn).map_err(Error::Proxy) +} + +/// NetworkManager device configurations. A map from device to configuration options. +/// +/// # DBUS +/// +/// This corresponds to the DBUS type signature `a{sa{sv}}` +/// +/// ### Breakdown: +/// +/// - `s` : `string` +/// - `v` : `variant` +/// - `a{}` : `map` +/// - `a{sv}` : `map<string, variant>` +/// - `a{sa{sv}}`: `map<string, map<string, variant>` +pub type DeviceConfig = HashMap<String, HashMap<String, OwnedValue>>; + +#[derive(Debug)] +/// TODO: Document +pub struct WireguardTunnel { + /// TODO: Document + active: ConnectionActiveProxyBlocking<'static>, + /// TODO: Document + nm: NetworkManagerProxyBlocking<'static>, + /// TODO: Document + device: DeviceProxyBlocking<'static>, +} + +impl WireguardTunnel { + pub fn new(nm: NetworkManager, wg_config: DeviceConfig) -> Result<Self, Error> { + let connection = nm + .add_connection_2(wg_config) + .inspect_err(|err| { + log::error!( + "Failed to create a new interface via AddConnection2: {}", + err + ); + })? + .0; + + let active = nm.activate_connection(connection)?; + let devices = active.devices().map_err(Error::Proxy)?; + // TODO: Why do we only consider the first device? + let device = devices.into_iter().next().unwrap(); + + let nm = as_nm(&nm.dbus_connection)?; + + Ok(WireguardTunnel { active, nm, device }) + } + + pub fn remove(self) -> Result<(), Error> { + let device_object = OwnedObjectPath::from(self.device.into_inner().path().clone()); + let deactivation_result = self + .nm + .deactivate_connection(device_object) + .map_err(Error::Call); + + let config_result = self + .active + .connection() + .map_err(Error::Proxy)? + .delete() + .map_err(Error::Call); + + deactivation_result?; + config_result?; + Ok(()) + } + + pub fn get_interface_name(&self) -> Result<String, Error> { + self.device.interface().map_err(Error::Call) + } +} + +/// <https://people.freedesktop.org/~lkundrak/nm-docs/gdbus-org.freedesktop.NetworkManager> +#[zbus::proxy( + interface = "org.freedesktop.NetworkManager", + default_service = "org.freedesktop.NetworkManager", + default_path = "/org/freedesktop/NetworkManager" +)] +trait NetworkManager { + /// org.freedesktop.NetworkManager.Settings.ActivateConnection + /// + /// # Input + /// - `connection`: The connection to activate. + /// + /// If "/" is given, a valid device path must be given, and NetworkManager picks the best connection to activate for + /// the given device. VPN connections must always pass a valid connection path. + /// + /// - `device`: The object path of device to be activated for physical connections. + /// + /// This parameter is ignored for VPN connections, because the specific_object (if provided) specifies the device + /// to use. + /// + /// - `specific_object`: The path of a connection-type-specific object this activation should use. + /// + /// For VPN connections, pass the object path of an ActiveConnection object that should serve as the "base" + /// connection (to which the VPN connections lifetime will be tied), or pass "/" and NM will automatically use the + /// current default device. + /// + /// # Result + /// + /// Object path of the active connection object representing this active connection. + /// + /// # Documenation + /// + /// https://people.freedesktop.org/~lkundrak/nm-docs/gdbus-org.freedesktop.NetworkManager.html#gdbus-method-org-freedesktop-NetworkManager.ActivateConnection + #[zbus(object = "ConnectionActive")] + fn activate_connection(&self, connection: O, device: O, specified_object: O); + + fn deactivate_connection(&self, active: OwnedObjectPath) -> Result<(), zbus::Error>; + + // methods + // + // GetDevices (OUT ao devices); + // GetAllDevices (OUT ao devices); + // GetDeviceByIpIface (IN s iface, + // OUT o device); + // ActivateConnection (IN o connection, + // IN o device, + // IN o specific_object, + // OUT o active_connection); + // AddAndActivateConnection (IN a{sa{sv}} connection, + // IN o device, + // IN o specific_object, + // OUT o path, + // OUT o active_connection); + // DeactivateConnection (IN o active_connection); + // Sleep (IN b sleep); + // Enable (IN b enable); + // GetPermissions (OUT a{ss} permissions); + // SetLogging (IN s level, + // IN s domains); + // GetLogging (OUT s level, + // OUT s domains); + // CheckConnectivity (OUT u connectivity); + // state (OUT u state); + // + // properties + // + // Devices readable ao + // AllDevices readable ao + // NetworkingEnabled readable b + // WirelessEnabled readwrite b + // WirelessHardwareEnabled readable b + // WwanEnabled readwrite b + // WwanHardwareEnabled readable b + // WimaxEnabled readwrite b + // WimaxHardwareEnabled readable b + // ActiveConnections readable ao + // PrimaryConnection readable o + // PrimaryConnectionType readable s + // Metered readable u + // ActivatingConnection readable o + // Startup readable b + // Version readable s + // State readable u + // Connectivity readable u + // GlobalDnsConfiguration readwrite a{sv} +} + +/// https://networkmanager.pages.freedesktop.org/NetworkManager/NetworkManager/gdbus-org.freedesktop.NetworkManager.Settings +#[zbus::proxy( + interface = "org.freedesktop.NetworkManager.Settings", + default_service = "org.freedesktop.NetworkManager.Settings", + default_path = "/org/freedesktop/NetworkManager/Settings" +)] +trait NetworkManagerSettings { + /// org.freedesktop.NetworkManager.Settings.AddConnection2 (since 1.20) + /// + /// # Result + /// A tuple of + /// - Object path of the new connection that was just added. + /// - "Output argument, currently no additional results are returned" (lol). + /// + /// # Documenation + /// + /// https://networkmanager.pages.freedesktop.org/NetworkManager/NetworkManager/gdbus-org.freedesktop.NetworkManager.Settings.html#gdbus-method-org-freedesktop-NetworkManager-Settings.AddConnection2 + fn add_connection_2( + &self, + settings: DeviceConfig, + flags: u32, + args: HashMap<String, OwnedValue>, + ) -> Result<(OwnedObjectPath, DeviceConfig), zbus::Error>; +} + +#[zbus::proxy( + interface = "org.freedesktop.NetworkManager", + default_service = "org.freedesktop.NetworkManager.Connection.Active", // TODO: Check if makes sense. + default_path = "/org/freedesktop/NetworkManager/Connection/Active" +)] +trait ConnectionActive { + #[zbus(object = "Connection")] + fn connection(&self); + + /// Array of object paths representing devices which are part of this active connection. + /// + /// https://www.networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Connection.Active.html#gdbus-property-org-freedesktop-NetworkManager-Connection-Active.Devices + #[zbus(object = "Device", object_vec)] + fn devices(&self); +} + +/// <https://www.networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Settings.Connection> +#[zbus::proxy( + interface = "org.freedesktop.NetworkManager", + default_service = "org.freedesktop.NetworkManager.Settings.Connection", // TODO: Check if makes sense. + default_path = "/org/freedesktop/NetworkManager/Settings/Connection" +)] +trait Connection { + fn delete(&self) -> Result<(), zbus::Error>; +} + +/// https://people.freedesktop.org/~lkundrak/nm-docs/gdbus-org.freedesktop.NetworkManager.Device +#[zbus::proxy( + interface = "org.freedesktop.NetworkManager", + default_service = "org.freedesktop.NetworkManager.Device", // TODO: Check if makes sense. + default_path = "/org/freedesktop/NetworkManager/Device" +)] +trait Device { + #[zbus(property)] + fn interface(&self) -> Result<String, zbus::Error>; +} diff --git a/talpid-dns/Cargo.toml b/talpid-dns/Cargo.toml index 386a32fc46..472aef7c2a 100644 --- a/talpid-dns/Cargo.toml +++ b/talpid-dns/Cargo.toml @@ -17,7 +17,7 @@ inotify = "0.10" nix = { workspace = true, features = ["net"] } parking_lot = "0.12.0" resolv-conf = "0.7" -talpid-dbus = { workspace = true } +talpid-dbus = { workspace = true, features = ["libdbus"] } talpid-routing = { path = "../talpid-routing" } tokio = { workspace = true, features = ["macros"] } triggered = "0.1.1" diff --git a/talpid-dns/src/linux/network_manager.rs b/talpid-dns/src/linux/network_manager.rs index 14e3fe7178..e1d06295a6 100644 --- a/talpid-dns/src/linux/network_manager.rs +++ b/talpid-dns/src/linux/network_manager.rs @@ -1,6 +1,8 @@ use std::net::IpAddr; -pub use talpid_dbus::network_manager::Error; -use talpid_dbus::network_manager::{self, DeviceConfig, NetworkManager as DBus}; +pub use talpid_dbus::network_manager::dbus_rs::Error; +use talpid_dbus::network_manager::dbus_rs::{ + self as network_manager, DeviceConfig, NetworkManager as DBus, +}; pub type Result<T> = std::result::Result<T, Error>; diff --git a/talpid-platform-metadata/src/linux.rs b/talpid-platform-metadata/src/linux.rs index be3fafa6f3..3fea20ead7 100644 --- a/talpid-platform-metadata/src/linux.rs +++ b/talpid-platform-metadata/src/linux.rs @@ -97,7 +97,7 @@ fn kernel_version() -> Option<(String, String)> { /// > 1.26.0 #[cfg(feature = "network-manager")] fn nm_version() -> Option<(String, String)> { - let nm = talpid_dbus::network_manager::NetworkManager::new().ok()?; + let nm = talpid_dbus::network_manager::dbus_rs::NetworkManager::new().ok()?; Some(("nm".to_string(), nm.version_string().ok()?)) } diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 97c5bcfc77..096c7f6d96 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -47,7 +47,7 @@ netlink-packet-core = { workspace = true } netlink-packet-route = { workspace = true } netlink-proto = { workspace = true } rtnetlink = { workspace = true } -talpid-dbus = { workspace = true } +talpid-dbus = { workspace = true, features = ["zbus"] } zerocopy = { workspace = true, features = ["derive"] } [target.'cfg(unix)'.dependencies] diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 141146f531..e956c0b16e 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -1180,18 +1180,14 @@ pub enum TunnelError { #[cfg(target_os = "linux")] fn will_nm_manage_dns() -> bool { - use talpid_dbus::network_manager::NetworkManager; + use talpid_dbus::{network_manager::zbus::NetworkManager, systemd_resolved::SystemdResolved}; - if talpid_dbus::systemd_resolved::SystemdResolved::new().is_ok() { + if SystemdResolved::new().is_ok() { return false; } - NetworkManager::new() - .and_then(|nm| { - nm.ensure_can_be_used_to_manage_dns()?; - Ok(true) - }) - .unwrap_or(false) + .and_then(|nm| nm.ensure_can_be_used_to_manage_dns()) + .is_ok_and(|p| p) } // Set the MTU to the lowest possible whilst still allowing for IPv6 to help with wireless diff --git a/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs b/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs index c35503fa82..f57c82c009 100644 --- a/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs +++ b/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs @@ -6,27 +6,25 @@ use super::{ }; use futures::Future; use std::{collections::HashMap, pin::Pin}; -use talpid_dbus::{ - dbus, - network_manager::{ - DeviceConfig, Error as NetworkManagerError, NetworkManager, Variant, VariantMap, - WireguardTunnel, - }, + +use talpid_dbus::network_manager::zbus::{ + self as network_manager, DeviceConfig, NetworkManager, WireguardTunnel, }; +use talpid_dbus::zbus; + use talpid_net::unix::iface_index; use talpid_tunnel_config_client::DaitaSettings; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Error while communicating over Dbus")] - Dbus(#[from] dbus::Error), + Dbus(#[from] zbus::Error), #[error("NetworkManager error")] - NetworkManager(#[from] NetworkManagerError), + NetworkManager(#[from] network_manager::Error), } pub struct NetworkManagerTunnel { - network_manager: NetworkManager, tunnel: Option<WireguardTunnel>, netlink_connections: Handle, interface_name: String, @@ -40,22 +38,19 @@ impl NetworkManagerTunnel { let network_manager = NetworkManager::new() .map_err(Error::NetworkManager) .map_err(WgKernelError::NetworkManager)?; - let config_map = convert_config_to_dbus(config); + let wg_config = convert_config_to_dbus(config); let tunnel = network_manager - .create_wg_tunnel(&config_map) + .create_wg_tunnel(wg_config) .map_err(|err| WgKernelError::NetworkManager(err.into()))?; - let interface_name = match network_manager.get_interface_name(&tunnel) { - Ok(name) => name, - Err(error) => { - log::error!("Failed to fetch interface name from NM: {}", error); - MULLVAD_INTERFACE_NAME.to_string() - } - }; + let interface_name = tunnel + .get_interface_name() + .inspect_err(|error| log::error!("Failed to fetch interface name from NM: {}", error)) + .unwrap_or(MULLVAD_INTERFACE_NAME.to_string()); + let netlink_connections = tokio_handle.block_on(Handle::connect())?; Ok(NetworkManagerTunnel { - network_manager, tunnel: Some(tunnel), netlink_connections, interface_name, @@ -70,13 +65,11 @@ impl Tunnel for NetworkManagerTunnel { } fn stop(mut self: Box<Self>) -> std::result::Result<(), TunnelError> { - if let Some(tunnel) = self.tunnel.take() { - if let Err(err) = self.network_manager.remove_tunnel(tunnel) { - log::error!("Failed to remove WireGuard tunnel via NM: {}", err); - Err(TunnelError::StopWireguardError(Box::new(err))) - } else { - Ok(()) - } + if let Some(tunnel) = self.tunnel.take() + && let Err(err) = tunnel.remove() + { + log::error!("Failed to remove WireGuard tunnel via NM: {}", err); + Err(TunnelError::StopWireguardError(Box::new(err))) } else { Ok(()) } @@ -120,92 +113,112 @@ 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(); + use zbus::zvariant::{OwnedValue, Value}; + // let mut ipv6_config = HashMap::new(); + // let mut ipv4_config = HashMap::new(); + let mut wireguard_config: HashMap<String, OwnedValue> = HashMap::new(); + let mut connection_config: HashMap<String, OwnedValue> = HashMap::new(); let mut peer_configs = vec![]; - wireguard_config.insert("mtu".into(), Variant(Box::new(config.mtu as u32))); + wireguard_config.insert("mtu".into(), OwnedValue::from(config.mtu)); if let Some(fwmark) = config.fwmark { - wireguard_config.insert("fwmark".into(), Variant(Box::new(fwmark))); + wireguard_config.insert("fwmark".into(), OwnedValue::from(fwmark)); } - wireguard_config.insert("peer-routes".into(), Variant(Box::new(false))); + wireguard_config.insert("peer-routes".into(), OwnedValue::from(false)); + wireguard_config.insert( "private-key".into(), - Variant(Box::new(config.tunnel.private_key.to_base64())), + Value::new(config.tunnel.private_key.to_base64()) + .try_to_owned() + .unwrap(), ); - wireguard_config.insert("private-key-flags".into(), Variant(Box::new(0x0u32))); + wireguard_config.insert("private-key-flags".into(), OwnedValue::from(0x0u32)); for peer in config.peers() { - let mut peer_config: VariantMap = HashMap::new(); + let mut peer_config: HashMap<String, OwnedValue> = 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( + "allowed-ips".into(), + Value::new(allowed_ips).try_to_owned().unwrap(), + ); peer_config.insert( "endpoint".into(), - Variant(Box::new(peer.endpoint.to_string())), + Value::new(peer.endpoint.to_string()) + .try_to_owned() + .unwrap(), ); peer_config.insert( "public-key".into(), - Variant(Box::new(peer.public_key.to_base64())), + Value::new(peer.public_key.to_base64()) + .try_to_owned() + .unwrap(), ); peer_configs.push(peer_config); } - wireguard_config.insert("peers".into(), Variant(Box::new(peer_configs))); + wireguard_config.insert( + "peers".into(), + Value::new(peer_configs).try_to_owned().unwrap(), + ); - connection_config.insert("type".into(), Variant(Box::new("wireguard".to_string()))); + connection_config.insert( + "type".into(), + Value::new("wireguard").try_to_owned().unwrap(), + ); connection_config.insert( "id".into(), - Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), + Value::from(MULLVAD_INTERFACE_NAME).try_to_owned().unwrap(), ); connection_config.insert( "interface-name".into(), - Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())), + Value::from(MULLVAD_INTERFACE_NAME).try_to_owned().unwrap(), ); - connection_config.insert("autoconnect".into(), Variant(Box::new(true))); + connection_config.insert("autoconnect".into(), OwnedValue::from(true)); - let ipv4_addrs: Vec<_> = config - .tunnel - .addresses - .iter() - .filter(|ip| ip.is_ipv4()) - .map(NetworkManager::convert_address_to_dbus) - .collect(); + /* + 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(); + 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))); + ipv4_config.insert("address-data".into(), OwnedValue::from(ipv4_addrs)); + ipv4_config.insert("ignore-auto-routes".into(), OwnedValue::from(true)); + ipv4_config.insert("ignore-auto-dns".into(), OwnedValue::from(true)); + ipv4_config.insert("may-fail".into(), OwnedValue::from(true)); + ipv4_config.insert("method".into(), OwnedValue::from("manual".to_string())); + ipv4_config.insert("never-default".into(), OwnedValue::from(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))); + ipv6_config.insert("method".into(), OwnedValue::from("manual".to_string())); + ipv6_config.insert("address-data".into(), OwnedValue::from(ipv6_addrs)); + ipv6_config.insert("ignore-auto-routes".into(), OwnedValue::from(true)); + ipv6_config.insert("ignore-auto-dns".into(), OwnedValue::from(true)); + ipv6_config.insert("may-fail".into(), OwnedValue::from(true)); } - let mut settings = HashMap::new(); settings.insert("ipv4".into(), ipv4_config); if !ipv6_config.is_empty() { settings.insert("ipv6".into(), ipv6_config); } + */ + let mut settings = HashMap::new(); + settings.insert("wireguard".into(), wireguard_config); settings.insert("connection".into(), connection_config); |
