summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs4
-rw-r--r--talpid-dbus/Cargo.toml2
-rw-r--r--talpid-dbus/src/lib.rs3
-rw-r--r--talpid-dbus/src/network_manager.rs756
-rw-r--r--talpid-dbus/src/network_manager/dbus_rs.rs753
-rw-r--r--talpid-dbus/src/network_manager/zbus.rs298
-rw-r--r--talpid-dns/Cargo.toml2
-rw-r--r--talpid-dns/src/linux/network_manager.rs6
-rw-r--r--talpid-platform-metadata/src/linux.rs2
-rw-r--r--talpid-wireguard/Cargo.toml2
-rw-r--r--talpid-wireguard/src/lib.rs12
-rw-r--r--talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs153
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);