summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2020-11-12 14:25:08 +0000
committerEmīls <emils@mullvad.net>2020-11-19 11:53:23 +0000
commitab29a68c768a0d1dcaafe622cc7b5585700b9845 (patch)
treee085bc794c702bef8854397e7c7552b8e3e8561a
parent13325d6e976d2c5bdb9686fdcfb1d4814f439175 (diff)
downloadmullvadvpn-ab29a68c768a0d1dcaafe622cc7b5585700b9845.tar.xz
mullvadvpn-ab29a68c768a0d1dcaafe622cc7b5585700b9845.zip
Use refactored NM to create WireGuard tunnel
-rw-r--r--talpid-core/src/tunnel/wireguard/connectivity_check.rs6
-rw-r--r--talpid-core/src/tunnel/wireguard/mod.rs29
-rw-r--r--talpid-core/src/tunnel/wireguard/wireguard_kernel/mod.rs2
-rw-r--r--talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs415
4 files changed, 79 insertions, 373 deletions
diff --git a/talpid-core/src/tunnel/wireguard/connectivity_check.rs b/talpid-core/src/tunnel/wireguard/connectivity_check.rs
index 8a604fcf6f..9b62fc47e8 100644
--- a/talpid-core/src/tunnel/wireguard/connectivity_check.rs
+++ b/talpid-core/src/tunnel/wireguard/connectivity_check.rs
@@ -105,6 +105,12 @@ impl ConnectivityMonitor {
let start = Instant::now();
while start.elapsed() < PING_TIMEOUT {
if self.check_connectivity(Instant::now())? {
+ #[cfg(target_os = "linux")]
+ self.tunnel_handle.upgrade().and_then::<(), _>(|tunnel| {
+ let tunnel = tunnel.lock().ok()?;
+ tunnel.as_ref()?.slow_stats_refresh_rate();
+ None
+ });
return Ok(true);
}
if self.should_shut_down(DELAY_ON_INITIAL_SETUP) {
diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs
index 852e5b0996..24223b7603 100644
--- a/talpid-core/src/tunnel/wireguard/mod.rs
+++ b/talpid-core/src/tunnel/wireguard/mod.rs
@@ -21,7 +21,7 @@ mod logging;
mod stats;
mod wireguard_go;
#[cfg(target_os = "linux")]
-mod wireguard_kernel;
+pub(crate) mod wireguard_kernel;
use self::wireguard_go::WgGoTunnel;
@@ -154,13 +154,20 @@ impl WireguardMonitor {
#[cfg(target_os = "linux")]
if !*FORCE_USERSPACE_WIREGUARD {
if *FORCE_NM_WIREGUARD {
- if let Ok(tunnel) = wireguard_kernel::NetworkManagerTunnel::new(
- route_manager.runtime_handle(),
- config,
- ) {
- log::debug!("Using NetworkManager to use kernel WireGuard implementation");
- return Ok(Box::new(tunnel));
- }
+ match wireguard_kernel::NetworkManagerTunnel::new(config) {
+ Ok(tunnel) => {
+ log::debug!("Using NetworkManager to use kernel WireGuard implementation");
+ return Ok(Box::new(tunnel));
+ }
+ Err(err) => {
+ log::error!(
+ "{}",
+ err.display_chain_with_msg(
+ "Failed to initialize WireGuard tunnel via NetworkManager"
+ )
+ );
+ }
+ };
} else if !crate::dns::will_use_nm() {
match wireguard_kernel::NetlinkTunnel::new(route_manager.runtime_handle(), config) {
Ok(tunnel) => {
@@ -177,10 +184,10 @@ impl WireguardMonitor {
}
};
}
- } else {
- log::debug!("Using userspace WireGuard implementation");
}
+ #[cfg(traget_os = "linux")]
+ log::debug!("Using userspace WireGuard implementation");
Ok(Box::new(WgGoTunnel::start_tunnel(
&config,
log_path,
@@ -295,6 +302,8 @@ pub(crate) trait Tunnel: Send {
fn get_interface_name(&self) -> String;
fn stop(self: Box<Self>) -> std::result::Result<(), TunnelError>;
fn get_tunnel_stats(&self) -> std::result::Result<stats::Stats, TunnelError>;
+ #[cfg(target_os = "linux")]
+ fn slow_stats_refresh_rate(&self) {}
}
/// Errors to be returned from WireGuard implementations, namely implementers of the Tunnel trait
diff --git a/talpid-core/src/tunnel/wireguard/wireguard_kernel/mod.rs b/talpid-core/src/tunnel/wireguard/wireguard_kernel/mod.rs
index 6c3b86ee65..3982833ed6 100644
--- a/talpid-core/src/tunnel/wireguard/wireguard_kernel/mod.rs
+++ b/talpid-core/src/tunnel/wireguard/wireguard_kernel/mod.rs
@@ -85,7 +85,7 @@ pub enum Error {
NetworkManager(#[error(source)] nm_tunnel::Error),
}
-const MULLVAD_INTERFACE_NAME: &str = "wg-mullvad";
+pub(crate) const MULLVAD_INTERFACE_NAME: &str = "wg-mullvad";
#[derive(Debug)]
pub struct Handle {
diff --git a/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs b/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs
index a0ec39bddd..adaa8977dd 100644
--- a/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs
+++ b/talpid-core/src/tunnel/wireguard/wireguard_kernel/nm_tunnel.rs
@@ -1,365 +1,56 @@
use super::{
- super::stats::Stats, wg_message::DeviceNla, Config, Error as WgKernelError, Handle, Tunnel,
- TunnelError, MULLVAD_INTERFACE_NAME,
+ super::stats::{Error as StatsError, Stats},
+ Config, Error as WgKernelError, Tunnel, TunnelError, MULLVAD_INTERFACE_NAME,
};
-use dbus::{
- arg::{RefArg, Variant},
- blocking::{stdintf::org_freedesktop_dbus::Properties, BlockingSender, Connection, Proxy},
- message::Message,
- strings::Path,
+use crate::linux::network_manager::{
+ Error as NetworkManagerError, NetworkManager, WireguardTunnel,
};
-use std::{collections::HashMap, net::IpAddr};
+use talpid_types::ErrorExt;
-const NM_BUS: &str = "org.freedesktop.NetworkManager";
-const NM_INTERFACE_SETTINGS: &str = "org.freedesktop.NetworkManager.Settings";
-const NM_INTERFACE_SETTINGS_CONNECTION: &str = "org.freedesktop.NetworkManager.Settings.Connection";
-const NM_SETTINGS_PATH: &str = "/org/freedesktop/NetworkManager/Settings";
-const NM_DEVICE: &str = "org.freedesktop.NetworkManager.Device";
-const NM_CONNECTION_ACTIVE: &str = "org.freedesktop.NetworkManager.Connection.Active";
-const NM_MANAGER: &str = "org.freedesktop.NetworkManager";
-const NM_MANAGER_PATH: &str = "/org/freedesktop/NetworkManager";
-
-const NM_ADD_CONNECTION_VOLATILE: u32 = 0x2;
-
-const RPC_TIMEOUT: std::time::Duration = std::time::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;
-
-
-pub type Result<T> = std::result::Result<T, Error>;
#[derive(err_derive::Error, Debug)]
-#[error(no_from)]
pub enum Error {
#[error(display = "Error while communicating over Dbus")]
Dbus(#[error(source)] dbus::Error),
- #[error(display = "Failed to construct a DBus method call message")]
- DbusMethodCall(String),
-
- #[error(display = "Failed to match the returned D-Bus object with expected type")]
- MatchDBusTypeError(#[error(source)] dbus::arg::TypeMismatchError),
-
- #[error(display = "Error while removing ")]
- DeviceRemovalError(#[error(source)] dbus::Error),
-
- #[error(display = "Configuration has no device associated to it")]
- NoDevice,
-
- #[error(display = "NetworkManager is too old - {}", _0)]
- NMTooOld(String),
-
- #[error(display = "Cannot obtain the tunnel interface index")]
- FindInterfaceIndex,
+ #[error(display = "NetworkManager error")]
+ NetworkManager(#[error(source)] NetworkManagerError),
}
+const TRAFFIC_STATS_REFRESH_RATE_MS: u32 = 1000;
+const INITIAL_TRAFFIC_STATS_REFRESH_RATE_MS: u32 = 10;
+
pub struct NetworkManagerTunnel {
- dbus_connection: Connection,
+ network_manager: NetworkManager,
tunnel: Option<WireguardTunnel>,
- interface_index: u32,
- netlink_connections: Handle,
- tokio_handle: tokio::runtime::Handle,
}
-type VariantRefArg = Variant<Box<dyn RefArg>>;
-type VariantMap = HashMap<String, VariantRefArg>;
-// settings are a{sa{sv}}
-type DbusSettings = HashMap<String, VariantMap>;
-
impl NetworkManagerTunnel {
- pub fn new(
- tokio_handle: tokio::runtime::Handle,
- config: &Config,
- ) -> std::result::Result<Self, WgKernelError> {
- let mut dbus_connection = Connection::new_system()
- .map_err(|error| WgKernelError::NetworkManager(Error::Dbus(error)))?;
- Self::ensure_nm_is_new_enough(&dbus_connection).map_err(WgKernelError::NetworkManager)?;
- let tunnel = Some(
- Self::create_wg_tunnel(&mut dbus_connection, config)
- .map_err(WgKernelError::NetworkManager)?,
- );
-
- tokio_handle.clone().block_on(async move {
- let netlink_connections = Handle::connect().await?;
- let interface_index = Self::find_device_index(&netlink_connections)
- .await?
- .ok_or(WgKernelError::NetworkManager(Error::FindInterfaceIndex))?;
-
- Ok(NetworkManagerTunnel {
- dbus_connection,
- tunnel,
- interface_index,
- tokio_handle,
- netlink_connections,
- })
- })
- }
-
- async fn find_device_index(
- netlink_connections: &Handle,
- ) -> std::result::Result<Option<u32>, WgKernelError> {
- let mut wg = netlink_connections.wg_handle.clone();
- let device = wg.get_by_name(MULLVAD_INTERFACE_NAME.to_string()).await?;
-
- for nla in &device.nlas {
- if let DeviceNla::IfIndex(index) = nla {
- return Ok(Some(*index));
- }
- }
- Ok(None)
- }
-
- fn ensure_nm_is_new_enough(connection: &Connection) -> Result<()> {
- let manager = Self::nm_proxy(connection);
- let version_string: String = manager.get(NM_MANAGER, "Version").map_err(Error::Dbus)?;
- let version_too_old = || Error::NMTooOld(version_string.clone());
- let mut parts = version_string
- .split(".")
- .map(|part| part.parse().map_err(|_| version_too_old()));
+ pub fn new(config: &Config) -> std::result::Result<Self, WgKernelError> {
+ let network_manager = NetworkManager::new()
+ .map_err(Error::NetworkManager)
+ .map_err(WgKernelError::NetworkManager)?;
+ let tunnel = network_manager
+ .create_wg_tunnel(config)
+ .map_err(|err| WgKernelError::NetworkManager(err.into()))?;
- let major_version: u32 = parts.next().ok_or_else(|| version_too_old())??;
- let minor_version: u32 = parts.next().ok_or_else(|| version_too_old())??;
+ network_manager
+ .set_stats_refresh_rate(&tunnel, INITIAL_TRAFFIC_STATS_REFRESH_RATE_MS)
+ .map_err(|err| WgKernelError::NetworkManager(err.into()))?;
- if major_version < MINIMUM_SUPPORTED_MAJOR_VERSION
- || minor_version < MINIMUM_SUPPORTED_MINOR_VERSION
- {
- Err(version_too_old())
- } else {
- Ok(())
- }
- }
- fn nm_proxy<'a>(connection: &'a Connection) -> Proxy<'a, &Connection> {
- Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, connection)
- }
-
-
- fn create_wg_tunnel(dbus_connection: &Connection, config: &Config) -> Result<WireguardTunnel> {
- let settings_map = Self::convert_config_to_dbus(config);
-
- let config_path: Path<'static> = Self::add_connection_2(dbus_connection, &settings_map)
- .map(|(path, _result)| path)
- .or_else(|err| {
- log::error!("Failed to create a new interface via NM - {}", err);
- match err {
- Error::Dbus(dbus_error) if dbus_error.name() == Some(DBUS_UNKNOWN_METHOD) => {
- Self::add_connection_unsaved(dbus_connection, &settings_map)
- }
- err => Err(err),
- }
- })?;
-
- let manager = Self::nm_proxy(dbus_connection);
- let (connection_path,): (Path<'static>,) = manager
- .method_call(
- NM_MANAGER,
- "ActivateConnection",
- (
- &config_path,
- &Path::new("/").unwrap(),
- &Path::new("/").unwrap(),
- ),
- )
- .map_err(Error::Dbus)?;
-
- let connection = Proxy::new(NM_BUS, &connection_path, RPC_TIMEOUT, dbus_connection);
- let device_paths: Vec<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,
+ Ok(NetworkManagerTunnel {
+ network_manager,
+ tunnel: Some(tunnel),
})
}
-
- fn add_connection_2(
- connection: &Connection,
- settings_map: &DbusSettings,
- ) -> Result<(Path<'static>, DbusSettings)> {
- let args: VariantMap = HashMap::new();
- let new_device = Message::new_method_call(
- NM_BUS,
- NM_SETTINGS_PATH,
- NM_INTERFACE_SETTINGS,
- "AddConnection2",
- )
- .map_err(Error::DbusMethodCall)?
- .append3(settings_map, NM_ADD_CONNECTION_VOLATILE, args);
-
- connection
- .send_with_reply_and_block(new_device, RPC_TIMEOUT)
- .map_err(Error::Dbus)?
- .read2()
- .map_err(Error::MatchDBusTypeError)
- }
-
- fn add_connection_unsaved(
- connection: &Connection,
- settings_map: &DbusSettings,
- ) -> Result<Path<'static>> {
- let new_connection = Message::new_method_call(
- NM_BUS,
- NM_SETTINGS_PATH,
- NM_INTERFACE_SETTINGS,
- "AddConnectionUnsaved",
- )
- .map_err(Error::DbusMethodCall)?
- .append1(settings_map);
-
- connection
- .send_with_reply_and_block(new_connection, RPC_TIMEOUT)
- .map_err(Error::Dbus)?
- .read1()
- .map_err(Error::MatchDBusTypeError)
- }
-
- fn convert_config_to_dbus(config: &Config) -> DbusSettings {
- let mut ipv6_config: VariantMap = HashMap::new();
- let mut ipv4_config: VariantMap = HashMap::new();
- let mut wireguard_config: VariantMap = HashMap::new();
- let mut connection_config: VariantMap = HashMap::new();
- let mut peer_configs = vec![];
-
- wireguard_config.insert("mtu".into(), Variant(Box::new(config.mtu as u32)));
- wireguard_config.insert("fwmark".into(), Variant(Box::new(config.fwmark as u32)));
- wireguard_config.insert(
- "private-key".into(),
- Variant(Box::new(config.tunnel.private_key.to_base64())),
- );
- wireguard_config.insert("private-key-flags".into(), Variant(Box::new(0x0u32)));
-
- for peer in config.peers.iter() {
- let mut peer_config: VariantMap = HashMap::new();
- let allowed_ips = peer
- .allowed_ips
- .iter()
- .map(ToString::to_string)
- .collect::<Vec<_>>();
-
-
- peer_config.insert("allowed-ips".into(), Variant(Box::new(allowed_ips)));
- peer_config.insert(
- "endpoint".into(),
- Variant(Box::new(peer.endpoint.to_string())),
- );
- peer_config.insert(
- "public-key".into(),
- Variant(Box::new(peer.public_key.to_base64())),
- );
-
- peer_configs.push(peer_config);
- }
- wireguard_config.insert("peers".into(), Variant(Box::new(peer_configs)));
-
- connection_config.insert("type".into(), Variant(Box::new("wireguard".to_string())));
- connection_config.insert(
- "id".into(),
- Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())),
- );
- connection_config.insert(
- "interface-name".into(),
- Variant(Box::new(MULLVAD_INTERFACE_NAME.to_string())),
- );
- connection_config.insert("autoconnect".into(), Variant(Box::new(true)));
-
-
- let ipv4_addrs: Vec<_> = config
- .tunnel
- .addresses
- .iter()
- .filter(|ip| ip.is_ipv4())
- .map(Self::convert_address_to_dbus)
- .collect();
-
- let ipv6_addrs: Vec<_> = config
- .tunnel
- .addresses
- .iter()
- .filter(|ip| ip.is_ipv6())
- .map(Self::convert_address_to_dbus)
- .collect();
-
- ipv4_config.insert("address-data".into(), Variant(Box::new(ipv4_addrs)));
- ipv4_config.insert("ignore-auto-routes".into(), Variant(Box::new(true)));
- ipv4_config.insert("ignore-auto-dns".into(), Variant(Box::new(true)));
- ipv4_config.insert("may-fail".into(), Variant(Box::new(true)));
- ipv4_config.insert("method".into(), Variant(Box::new("manual".to_string())));
-
- if !ipv6_addrs.is_empty() {
- ipv6_config.insert("method".into(), Variant(Box::new("manual".to_string())));
- ipv6_config.insert("address-data".into(), Variant(Box::new(ipv6_addrs)));
- ipv6_config.insert("ignore-auto-routes".into(), Variant(Box::new(true)));
- ipv6_config.insert("ignore-auto-dns".into(), Variant(Box::new(true)));
- ipv6_config.insert("may-fail".into(), Variant(Box::new(true)));
- }
-
-
- let mut settings = HashMap::new();
- settings.insert("ipv4".into(), ipv4_config);
- if !ipv6_config.is_empty() {
- settings.insert("ipv6".into(), ipv6_config);
- }
- settings.insert("wireguard".into(), wireguard_config);
- settings.insert("connection".into(), connection_config);
-
- settings
- }
-
- fn convert_address_to_dbus(address: &IpAddr) -> VariantMap {
- let mut map: VariantMap = HashMap::new();
- map.insert(
- "address".to_string(),
- Variant(Box::new(address.to_string())),
- );
- let prefix: u32 = if address.is_ipv4() { 32 } else { 128 };
- map.insert("prefix".to_string(), Variant(Box::new(prefix)));
-
- map
- }
-
- fn remove_config(&mut self) -> Result<()> {
- if let Some(tunnel) = self.tunnel.take() {
- let deactivation_result: Result<()> =
- Proxy::new(NM_BUS, NM_MANAGER_PATH, RPC_TIMEOUT, &self.dbus_connection)
- .method_call(
- NM_MANAGER,
- "DeactivateConnection",
- (&tunnel.connection_path,),
- )
- .map_err(Error::Dbus);
-
- let device_result: Result<()> = tunnel
- .device_proxy(&self.dbus_connection)
- .method_call(NM_DEVICE, "Delete", ())
- .map_err(Error::DeviceRemovalError);
-
- let config_result: Result<()> = tunnel
- .config_proxy(&self.dbus_connection)
- .method_call(NM_INTERFACE_SETTINGS_CONNECTION, "Delete", ())
- .map_err(Error::DeviceRemovalError);
- deactivation_result?;
- device_result?;
- config_result?;
- }
- Ok(())
- }
}
impl Tunnel for NetworkManagerTunnel {
fn get_interface_name(&self) -> String {
if let Some(tunnel) = self.tunnel.as_ref() {
- let interface_name = tunnel
- .device_proxy(&self.dbus_connection)
- .get(NM_DEVICE, "Interface");
-
- match interface_name {
+ match self.network_manager.get_interface_name(tunnel) {
Ok(name) => {
return name;
}
@@ -370,42 +61,42 @@ impl Tunnel for NetworkManagerTunnel {
}
fn stop(mut self: Box<Self>) -> std::result::Result<(), TunnelError> {
- if let Err(err) = self.remove_config() {
- log::error!("Failed to remove WireGuard tunnel via NM: {}", err);
- Err(TunnelError::StopWireguardError { status: 0 })
+ 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 { status: 0 })
+ } else {
+ Ok(())
+ }
} else {
Ok(())
}
}
fn get_tunnel_stats(&self) -> std::result::Result<Stats, TunnelError> {
- let mut wg = self.netlink_connections.wg_handle.clone();
- let interface_index = self.interface_index;
- let result = self.tokio_handle.block_on(async move {
- let device = wg.get_by_index(interface_index).await.map_err(|err| {
- log::error!("Failed to fetch WireGuard device config: {}", err);
- TunnelError::GetConfigError
- })?;
- Ok(Stats::parse_device_message(&device))
- });
-
- result
- }
-}
-
-
-struct WireguardTunnel {
- config_path: Path<'static>,
- connection_path: Path<'static>,
- device_path: Path<'static>,
-}
+ let tunnel = self
+ .tunnel
+ .as_ref()
+ .ok_or(TunnelError::StatsError(StatsError::NoTunnelDevice))?;
+ let (tx_bytes, rx_bytes) = self
+ .network_manager
+ .get_tunnel_stats(tunnel)
+ .map_err(|_| TunnelError::StatsError(StatsError::KeyNotFoundError))?;
-impl WireguardTunnel {
- fn device_proxy<'a>(&'a self, connection: &'a Connection) -> Proxy<'a, &Connection> {
- Proxy::new(NM_BUS, &self.device_path, RPC_TIMEOUT, connection)
+ Ok(Stats { tx_bytes, rx_bytes })
}
- fn config_proxy<'a>(&'a self, connection: &'a Connection) -> Proxy<'a, &Connection> {
- Proxy::new(NM_BUS, &self.config_path, RPC_TIMEOUT, connection)
+ fn slow_stats_refresh_rate(&self) {
+ if let Some(tunnel) = self.tunnel.as_ref() {
+ if let Err(err) = self
+ .network_manager
+ .set_stats_refresh_rate(tunnel, TRAFFIC_STATS_REFRESH_RATE_MS)
+ {
+ log::error!(
+ "{}",
+ err.display_chain_with_msg("Failed to reset stats refresh rate: ")
+ );
+ }
+ }
}
}