diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-12-04 21:34:46 +0000 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-01-18 10:26:24 +0000 |
| commit | e383870d147b7c8165109de91f4a8df2b9a5deb0 (patch) | |
| tree | 54a70b85f054ec8863a98f2169d2bf0fe1f1a546 /talpid-core/src | |
| parent | 2b26493e4e45d75d4d9a34a7bed0f017ad73bbc6 (diff) | |
| download | mullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.tar.xz mullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.zip | |
Add wireguard-go support in talpid-core
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/network_interface.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/routing/mod.rs | 1 | ||||
| -rw-r--r-- | talpid-core/src/security/linux/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/security/mod.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 340 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn.rs | 286 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/config.rs | 165 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/mod.rs | 198 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/ping_monitor.rs | 71 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/wireguard_go.rs | 115 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 8 |
13 files changed, 942 insertions, 257 deletions
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index 7e8ca9e26f..c8556ca0ec 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -20,6 +20,8 @@ extern crate error_chain; extern crate failure; extern crate futures; #[cfg(unix)] +extern crate hex; +#[cfg(unix)] extern crate ipnetwork; extern crate jsonrpc_core; extern crate jsonrpc_macros; diff --git a/talpid-core/src/network_interface.rs b/talpid-core/src/network_interface.rs index 761fcbf794..7117dadd69 100644 --- a/talpid-core/src/network_interface.rs +++ b/talpid-core/src/network_interface.rs @@ -89,6 +89,7 @@ impl NetworkInterface for TunnelDevice { { duct::cmd!( "ip", + "-6", "addr", "add", ipv6.to_string(), @@ -122,7 +123,6 @@ impl NetworkInterface for TunnelDevice { .chain_err(|| ErrorKind::ToggleDeviceError) } - fn set_mtu(&mut self, mtu: u16) -> Result<()> { self.dev .set_mtu(i32::from(mtu)) diff --git a/talpid-core/src/routing/mod.rs b/talpid-core/src/routing/mod.rs index 5a8fb34e64..68df1872b6 100644 --- a/talpid-core/src/routing/mod.rs +++ b/talpid-core/src/routing/mod.rs @@ -43,6 +43,7 @@ pub struct RequiredRoutes { pub routes: Vec<Route>, /// Optionally apply the routes to a specific table and only apply routes when a firewall mark /// is not used. Currently only used on Linux. + #[cfg(target_os = "linux")] pub fwmark: Option<String>, } diff --git a/talpid-core/src/security/linux/mod.rs b/talpid-core/src/security/linux/mod.rs index 6ebaabe628..5cbe786e46 100644 --- a/talpid-core/src/security/linux/mod.rs +++ b/talpid-core/src/security/linux/mod.rs @@ -298,7 +298,7 @@ impl<'a> PolicyBatch<'a> { check_iface(&mut allow_rule, Direction::Out, &tunnel.interface[..])?; check_port(&mut allow_rule, protocol, End::Dst, 53)?; - check_l3proto(&mut allow_rule, IpAddr::V4(tunnel.gateway))?; + check_l3proto(&mut allow_rule, tunnel.gateway)?; allow_rule.add_expr(&nft_expr!(payload ipv4 daddr))?; allow_rule.add_expr(&nft_expr!(cmp == tunnel.gateway))?; diff --git a/talpid-core/src/security/mod.rs b/talpid-core/src/security/mod.rs index 8a51155c97..892041b363 100644 --- a/talpid-core/src/security/mod.rs +++ b/talpid-core/src/security/mod.rs @@ -93,7 +93,12 @@ impl fmt::Display for SecurityPolicy { "Connected to {} over \"{}\" (ip: {}, gw: {}), {} LAN", peer_endpoint, tunnel.interface, - tunnel.ip, + tunnel + .ips + .iter() + .map(|ip| ip.to_string()) + .collect::<Vec<_>>() + .join(","), tunnel.gateway, if *allow_lan { "Allowing" } else { "Blocking" } ), diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index dc0d935ded..8461855954 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -1,77 +1,37 @@ -use crate::{mktemp, process::openvpn::OpenVpnCommand}; - use std::{ collections::HashMap, ffi::OsString, - fs, - io::{self, Write}, - net::Ipv4Addr, + fs, io, + net::IpAddr, path::{Path, PathBuf}, - result::Result as StdResult, }; -#[cfg(target_os = "linux")] -use failure::ResultExt as FailureResultExt; -#[cfg(target_os = "linux")] -use which; - -use talpid_types::net::{ - Endpoint, OpenVpnProxySettings, TunnelEndpoint, TunnelEndpointData, TunnelOptions, -}; +use talpid_types::net::{TunnelEndpoint, TunnelEndpointData, TunnelOptions}; /// A module for all OpenVPN related tunnel management. pub mod openvpn; -use self::openvpn::{OpenVpnCloseHandle, OpenVpnMonitor}; - -#[cfg(target_os = "macos")] -const OPENVPN_PLUGIN_FILENAME: &str = "libtalpid_openvpn_plugin.dylib"; -#[cfg(target_os = "linux")] -const OPENVPN_PLUGIN_FILENAME: &str = "libtalpid_openvpn_plugin.so"; -#[cfg(windows)] -const OPENVPN_PLUGIN_FILENAME: &str = "talpid_openvpn_plugin.dll"; - #[cfg(unix)] -const OPENVPN_BIN_FILENAME: &str = "openvpn"; -#[cfg(windows)] -const OPENVPN_BIN_FILENAME: &str = "openvpn.exe"; +mod wireguard; + error_chain! { errors { - /// There was an error preparing to listen for events from the VPN tunnel. + /// Failed to monitor the tunnel + TunnelMonitoringError { + description("Failed to monitor tunnel") + } + /// There was an error whilst preparing to listen for events from the VPN tunnel. TunnelMonitorSetUpError { description("Error while setting up to listen for events from the VPN tunnel") } - /// The OpenVPN binary was not found. - OpenVpnNotFound(path: PathBuf) { - description("No OpenVPN binary found") - display("No OpenVPN binary found at {}", path.display()) - } - /// The IP routing program was not found. - #[cfg(target_os = "linux")] - IpRouteNotFound { - description("The IP routing program `ip` was not found.") - } - /// The OpenVPN plugin was not found. - PluginNotFound(path: PathBuf) { - description("No OpenVPN plugin found") - display("No OpenVPN plugin found at {}", path.display()) - } - /// There was an error when writing authentication credentials to temporary file. - CredentialsWriteError { - description("Error while writing credentials to temporary file") - } /// Tunnel can't have IPv6 enabled because the system has disabled IPv6 support. EnableIpv6Error { description("Can't enable IPv6 on tunnel interface because IPv6 is disabled") } /// Running on an operating system which is not supported yet. UnsupportedPlatform { - description("Running on an unsupported operating system") - } - /// This type of VPN tunnel is not supported. - UnsupportedTunnelProtocol { - description("This tunnel protocol is not supported") + description("Tunnel type not supported on this operating system") } } @@ -99,10 +59,10 @@ pub enum TunnelEvent { pub struct TunnelMetadata { /// The name of the device which the tunnel is running on. pub interface: String, - /// The local IP on the tunnel interface. - pub ip: Ipv4Addr, + /// The local IPs on the tunnel interface. + pub ips: Vec<IpAddr>, /// The IP to the default gateway on the tunnel interface. - pub gateway: Ipv4Addr, + pub gateway: IpAddr, } impl TunnelEvent { @@ -122,11 +82,11 @@ impl TunnelEvent { .get("dev") .expect("No \"dev\" in tunnel up event") .to_owned(); - let ip = env + let ips = vec![env .get("ifconfig_local") .expect("No \"ifconfig_local\" in tunnel up event") .parse() - .expect("Tunnel IP not in valid format"); + .expect("Tunnel IP not in valid format")]; let gateway = env .get("route_vpn_gateway") .expect("No \"route_vpn_gateway\" in tunnel up event") @@ -134,7 +94,7 @@ impl TunnelEvent { .expect("Tunnel gateway IP not in valid format"); Some(TunnelEvent::Up(TunnelMetadata { interface, - ip, + ips, gateway, })) } @@ -143,17 +103,12 @@ impl TunnelEvent { } } } - - /// Abstraction for monitoring a generic VPN tunnel. pub struct TunnelMonitor { - monitor: OpenVpnMonitor, - /// Keep the `TempFile` for the user-pass file in the struct, so it's removed on drop. - _user_pass_file: mktemp::TempFile, - /// Keep the 'TempFile' for the proxy user-pass file in the struct, so it's removed on drop. - _proxy_auth_file: Option<mktemp::TempFile>, + monitor: InternalTunnelMonitor, } +// TODO(emilsp) move most of the openvpn tunnel details to OpenVpnTunnelMonitor impl TunnelMonitor { /// Creates a new `TunnelMonitor` that connects to the given remote and notifies `on_event` /// on tunnel state changes. @@ -169,71 +124,82 @@ impl TunnelMonitor { where L: Fn(TunnelEvent) + Send + Sync + 'static, { - Self::ensure_endpoint_is_openvpn(&tunnel_endpoint)?; Self::ensure_ipv6_can_be_used_if_enabled(tunnel_options)?; + match &tunnel_endpoint.tunnel { + TunnelEndpointData::OpenVpn(_) => Self::start_openvpn_tunnel( + tunnel_endpoint, + tunnel_options, + tunnel_alias, + username, + log, + resource_dir, + on_event, + ), + #[cfg(unix)] + TunnelEndpointData::Wireguard(_) => { + Self::start_wireguard_tunnel(tunnel_endpoint, tunnel_options, log, on_event) + } + #[cfg(windows)] + TunnelEndpointData::Wireguard(_) => bail!(ErrorKind::UnsupportedPlatform), + } + } - let user_pass_file = Self::create_credentials_file(username, "-") - .chain_err(|| ErrorKind::CredentialsWriteError)?; - - let proxy_auth_file = Self::create_proxy_auth_file(&tunnel_options.openvpn.proxy) - .chain_err(|| ErrorKind::CredentialsWriteError)?; - - let cmd = Self::create_openvpn_cmd( - tunnel_endpoint.to_endpoint(), - tunnel_alias, - &tunnel_options, - user_pass_file.as_ref(), - match proxy_auth_file { - Some(ref file) => Some(file.as_ref()), - _ => None, - }, - resource_dir, - )?; - - let user_pass_file_path = user_pass_file.to_path_buf(); - - let proxy_auth_file_path = match proxy_auth_file { - Some(ref file) => Some(file.to_path_buf()), - _ => None, + #[cfg(unix)] + fn start_wireguard_tunnel<L>( + tunnel_endpoint: TunnelEndpoint, + tunnel_options: &TunnelOptions, + log: Option<PathBuf>, + on_event: L, + ) -> Result<Self> + where + L: Fn(TunnelEvent) + Send + Sync + 'static, + { + let TunnelEndpoint { address, tunnel } = tunnel_endpoint; + let data = match tunnel { + TunnelEndpointData::Wireguard(data) => data, + _ => unreachable!("expected wireguard endpoint data"), }; - let on_openvpn_event = move |event, env| { - if event == openvpn_plugin::EventType::RouteUp { - // The user-pass file has been read. Try to delete it early. - let _ = fs::remove_file(&user_pass_file_path); - - // The proxy auth file has been read. Try to delete it early. - if let Some(ref file_path) = &proxy_auth_file_path { - let _ = fs::remove_file(file_path); - } - } - match TunnelEvent::from_openvpn_event(event, &env) { - Some(tunnel_event) => on_event(tunnel_event), - None => log::debug!("Ignoring OpenVpnEvent {:?}", event), - } - }; + let monitor = wireguard::WireguardMonitor::start( + address, + data, + tunnel_options, + log.as_ref().map(|p| p.as_path()), + on_event, + ) + .chain_err(|| ErrorKind::TunnelMonitoringError)?; + Ok(TunnelMonitor { + monitor: InternalTunnelMonitor::Wireguard(monitor), + }) + } + fn start_openvpn_tunnel<L>( + tunnel_endpoint: TunnelEndpoint, + tunnel_options: &TunnelOptions, + tunnel_alias: Option<OsString>, + username: &str, + log: Option<PathBuf>, + resource_dir: &Path, + on_event: L, + ) -> Result<Self> + where + L: Fn(TunnelEvent) + Send + Sync + 'static, + { let monitor = openvpn::OpenVpnMonitor::start( - cmd, - on_openvpn_event, - Self::get_plugin_path(resource_dir)?, + on_event, + tunnel_endpoint.to_endpoint(), + tunnel_options, + tunnel_alias, log, + resource_dir, + username, ) .chain_err(|| ErrorKind::TunnelMonitorSetUpError)?; Ok(TunnelMonitor { - monitor, - _user_pass_file: user_pass_file, - _proxy_auth_file: proxy_auth_file, + monitor: InternalTunnelMonitor::OpenVpn(monitor), }) } - fn ensure_endpoint_is_openvpn(endpoint: &TunnelEndpoint) -> Result<()> { - match endpoint.tunnel { - TunnelEndpointData::OpenVpn(_) => Ok(()), - TunnelEndpointData::Wireguard(_) => bail!(ErrorKind::UnsupportedTunnelProtocol), - } - } - fn ensure_ipv6_can_be_used_if_enabled(tunnel_options: &TunnelOptions) -> Result<()> { if tunnel_options.enable_ipv6 && !is_ipv6_enabled_in_os() { bail!(ErrorKind::EnableIpv6Error); @@ -242,106 +208,12 @@ impl TunnelMonitor { } } - fn create_openvpn_cmd( - remote: Endpoint, - tunnel_alias: Option<OsString>, - options: &TunnelOptions, - user_pass_file: &Path, - proxy_auth_file: Option<&Path>, - resource_dir: &Path, - ) -> Result<OpenVpnCommand> { - let mut cmd = OpenVpnCommand::new(Self::get_openvpn_bin(resource_dir)?); - if let Some(config) = Self::get_config_path(resource_dir) { - cmd.config(config); - } - #[cfg(target_os = "linux")] - cmd.iproute_bin( - which::which("ip") - .compat() - .chain_err(|| ErrorKind::IpRouteNotFound)?, - ); - cmd.remote(remote) - .user_pass(user_pass_file) - .tunnel_options(&options.openvpn) - .enable_ipv6(options.enable_ipv6) - .tunnel_alias(tunnel_alias) - .ca(resource_dir.join("ca.crt")); - if let Some(proxy_auth_file) = proxy_auth_file { - cmd.proxy_auth(proxy_auth_file); - } - - Ok(cmd) - } - - fn get_openvpn_bin(resource_dir: &Path) -> Result<PathBuf> { - let path = resource_dir.join(OPENVPN_BIN_FILENAME); - if path.exists() { - log::trace!("Using OpenVPN at {}", path.display()); - Ok(path) - } else { - bail!(ErrorKind::OpenVpnNotFound(path)); - } - } - - fn get_plugin_path(resource_dir: &Path) -> Result<PathBuf> { - let path = resource_dir.join(OPENVPN_PLUGIN_FILENAME); - if path.exists() { - log::trace!("Using OpenVPN plugin at {}", path.display()); - Ok(path) - } else { - bail!(ErrorKind::PluginNotFound(path)); - } - } - - fn get_config_path(resource_dir: &Path) -> Option<PathBuf> { - let path = resource_dir.join("openvpn.conf"); - if path.exists() { - Some(path) - } else { - None - } - } - - fn create_credentials_file(username: &str, password: &str) -> io::Result<mktemp::TempFile> { - let temp_file = mktemp::TempFile::new(); - log::debug!("Writing credentials to {}", temp_file.as_ref().display()); - let mut file = fs::File::create(&temp_file)?; - Self::set_user_pass_file_permissions(&file)?; - write!(file, "{}\n{}\n", username, password)?; - Ok(temp_file) - } - - fn create_proxy_auth_file( - proxy: &Option<OpenVpnProxySettings>, - ) -> StdResult<Option<mktemp::TempFile>, io::Error> { - if let Some(OpenVpnProxySettings::Remote(ref remote_proxy)) = proxy { - if let Some(ref proxy_auth) = remote_proxy.auth { - return Ok(Some(Self::create_credentials_file( - &proxy_auth.username, - &proxy_auth.password, - )?)); - } - } - Ok(None) - } - - #[cfg(unix)] - fn set_user_pass_file_permissions(file: &fs::File) -> io::Result<()> { - use std::os::unix::fs::PermissionsExt; - file.set_permissions(PermissionsExt::from_mode(0o400)) - } - - #[cfg(windows)] - fn set_user_pass_file_permissions(_file: &fs::File) -> io::Result<()> { - // TODO(linus): Lock permissions correctly on Windows. - Ok(()) - } /// Creates a handle to this monitor, allowing the tunnel to be closed while some other /// thread /// is blocked in `wait`. pub fn close_handle(&self) -> CloseHandle { - CloseHandle(self.monitor.close_handle()) + self.monitor.close_handle() } /// Consumes the monitor and blocks until the tunnel exits or there is an error. @@ -352,15 +224,57 @@ impl TunnelMonitor { /// A handle to a `TunnelMonitor` -pub struct CloseHandle(OpenVpnCloseHandle); +pub enum CloseHandle { + /// OpenVpn close handle + OpenVpn(openvpn::OpenVpnCloseHandle), + #[cfg(unix)] + /// Wireguard close handle + Wireguard(wireguard::CloseHandle), +} impl CloseHandle { /// Closes the underlying tunnel, making the `TunnelMonitor::wait` method return. pub fn close(self) -> io::Result<()> { - self.0.close() + match self { + CloseHandle::OpenVpn(handle) => handle.close(), + #[cfg(unix)] + CloseHandle::Wireguard(mut handle) => { + handle.close(); + Ok(()) + } + } } } +enum InternalTunnelMonitor { + OpenVpn(openvpn::OpenVpnMonitor), + #[cfg(unix)] + Wireguard(wireguard::WireguardMonitor), +} + +impl InternalTunnelMonitor { + fn close_handle(&self) -> CloseHandle { + match self { + InternalTunnelMonitor::OpenVpn(tun) => CloseHandle::OpenVpn(tun.close_handle()), + #[cfg(unix)] + InternalTunnelMonitor::Wireguard(tun) => CloseHandle::Wireguard(tun.close_handle()), + } + } + + fn wait(self) -> Result<()> { + match self { + InternalTunnelMonitor::OpenVpn(tun) => { + tun.wait().chain_err(|| ErrorKind::TunnelMonitoringError) + } + #[cfg(unix)] + InternalTunnelMonitor::Wireguard(tun) => { + tun.wait().chain_err(|| ErrorKind::TunnelMonitoringError) + } + } + } +} + + fn is_ipv6_enabled_in_os() -> bool { #[cfg(windows)] { diff --git a/talpid-core/src/tunnel/openvpn.rs b/talpid-core/src/tunnel/openvpn.rs index 9d7e6eed93..22cd11ee95 100644 --- a/talpid-core/src/tunnel/openvpn.rs +++ b/talpid-core/src/tunnel/openvpn.rs @@ -1,10 +1,14 @@ +use super::TunnelEvent; use crate::process::{ openvpn::{OpenVpnCommand, OpenVpnProcHandle}, stoppable_process::StoppableProcess, }; +use mktemp; use std::{ collections::HashMap, - io, + ffi::OsString, + fs, + io::{self, Write}, path::{Path, PathBuf}, process::ExitStatus, sync::{ @@ -14,35 +18,56 @@ use std::{ thread, time::Duration, }; - use talpid_ipc; +use talpid_types::net::{Endpoint, OpenVpnProxySettings, TunnelOptions}; -mod errors { - error_chain! { - errors { - /// Unable to start, wait for or kill the OpenVPN process. - ChildProcessError(msg: &'static str) { - description("Unable to start, wait for or kill the OpenVPN process") - display("OpenVPN process error: {}", msg) - } - /// Unable to start or manage the IPC server listening for events from OpenVPN. - EventDispatcherError { - description("Unable to start or manage the event dispatcher IPC server") - } - #[cfg(windows)] - /// No TAP adapter was detected - MissingTapAdapter { - description("No TAP adapter was detected") - } - #[cfg(windows)] - /// TAP adapter seems to be disabled - DisabledTapAdapter { - description("The TAP adapter appears to be disabled") - } +#[cfg(target_os = "linux")] +use failure::ResultExt as FailureResultExt; +#[cfg(target_os = "linux")] +use which; + +error_chain! { + errors { + /// Unable to start, wait for or kill the OpenVPN process. + ChildProcessError(msg: &'static str) { + description("Unable to start, wait for or kill the OpenVPN process") + display("OpenVPN process error: {}", msg) + } + /// Unable to start or manage the IPC server listening for events from OpenVPN. + EventDispatcherError { + description("Unable to start or manage the event dispatcher IPC server") + } + #[cfg(windows)] + /// No TAP adapter was detected + MissingTapAdapter { + description("No TAP adapter was detected") + } + #[cfg(windows)] + /// TAP adapter seems to be disabled + DisabledTapAdapter { + description("The TAP adapter appears to be disabled") + } + /// The IP routing program was not found. + #[cfg(target_os = "linux")] + IpRouteNotFound { + description("The IP routing program `ip` was not found.") + } + /// The OpenVPN binary was not found. + OpenVpnNotFound(path: PathBuf) { + description("No OpenVPN binary found") + display("No OpenVPN binary found at {}", path.display()) + } + /// The OpenVPN plugin was not found. + PluginNotFound(path: PathBuf) { + description("No OpenVPN plugin found") + display("No OpenVPN plugin found at {}", path.display()) + } + /// There was an error when writing authentication credentials to temporary file. + CredentialsWriteError { + description("Error while writing credentials to temporary file") } } } -pub use self::errors::*; #[cfg(unix)] @@ -51,6 +76,18 @@ static OPENVPN_DIE_TIMEOUT: Duration = Duration::from_secs(4); static OPENVPN_DIE_TIMEOUT: Duration = Duration::from_secs(30); +#[cfg(target_os = "macos")] +const OPENVPN_PLUGIN_FILENAME: &str = "libtalpid_openvpn_plugin.dylib"; +#[cfg(target_os = "linux")] +const OPENVPN_PLUGIN_FILENAME: &str = "libtalpid_openvpn_plugin.so"; +#[cfg(windows)] +const OPENVPN_PLUGIN_FILENAME: &str = "talpid_openvpn_plugin.dll"; + +#[cfg(unix)] +const OPENVPN_BIN_FILENAME: &str = "openvpn"; +#[cfg(windows)] +const OPENVPN_BIN_FILENAME: &str = "openvpn.exe"; + /// Struct for monitoring an OpenVPN process. #[derive(Debug)] pub struct OpenVpnMonitor<C: OpenVpnBuilder = OpenVpnCommand> { @@ -58,21 +95,78 @@ pub struct OpenVpnMonitor<C: OpenVpnBuilder = OpenVpnCommand> { event_dispatcher: Option<talpid_ipc::IpcServer>, log_path: Option<PathBuf>, closed: Arc<AtomicBool>, + /// Keep the `TempFile` for the user-pass file in the struct, so it's removed on drop. + _user_pass_file: mktemp::TempFile, + /// Keep the 'TempFile' for the proxy user-pass file in the struct, so it's removed on drop. + _proxy_auth_file: Option<mktemp::TempFile>, } impl OpenVpnMonitor<OpenVpnCommand> { /// Creates a new `OpenVpnMonitor` with the given listener and using the plugin at the given /// path. pub fn start<L>( - cmd: OpenVpnCommand, on_event: L, - plugin_path: impl AsRef<Path>, + endpoint: Endpoint, + tunnel_options: &TunnelOptions, + tunnel_alias: Option<OsString>, log_path: Option<PathBuf>, + resource_dir: &Path, + username: &str, ) -> Result<Self> where - L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, + L: Fn(TunnelEvent) + Send + Sync + 'static, { - Self::new_internal(cmd, on_event, plugin_path, log_path) + let user_pass_file = Self::create_credentials_file(username, "-") + .chain_err(|| ErrorKind::CredentialsWriteError)?; + + let proxy_auth_file = Self::create_proxy_auth_file(&tunnel_options.openvpn.proxy) + .chain_err(|| ErrorKind::CredentialsWriteError)?; + + + let user_pass_file_path = user_pass_file.to_path_buf(); + + let proxy_auth_file_path = match proxy_auth_file { + Some(ref file) => Some(file.to_path_buf()), + _ => None, + }; + + let on_openvpn_event = move |event, env| { + if event == openvpn_plugin::EventType::RouteUp { + // The user-pass file has been read. Try to delete it early. + let _ = fs::remove_file(&user_pass_file_path); + + // The proxy auth file has been read. Try to delete it early. + if let Some(ref file_path) = &proxy_auth_file_path { + let _ = fs::remove_file(file_path); + } + } + match TunnelEvent::from_openvpn_event(event, &env) { + Some(tunnel_event) => on_event(tunnel_event), + None => log::debug!("Ignoring OpenVpnEvent {:?}", event), + } + }; + let cmd = Self::create_openvpn_cmd( + endpoint, + tunnel_alias, + &tunnel_options, + user_pass_file.as_ref(), + match proxy_auth_file { + Some(ref file) => Some(file.as_ref()), + _ => None, + }, + resource_dir, + )?; + + let plugin_path = Self::get_plugin_path(resource_dir)?; + + Self::new_internal( + cmd, + on_openvpn_event, + &plugin_path, + log_path, + user_pass_file, + proxy_auth_file, + ) } } @@ -82,6 +176,8 @@ impl<C: OpenVpnBuilder> OpenVpnMonitor<C> { on_event: L, plugin_path: impl AsRef<Path>, log_path: Option<PathBuf>, + user_pass_file: mktemp::TempFile, + proxy_auth_file: Option<mktemp::TempFile>, ) -> Result<OpenVpnMonitor<C>> where L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, @@ -89,17 +185,21 @@ impl<C: OpenVpnBuilder> OpenVpnMonitor<C> { let event_dispatcher = event_server::start(on_event).chain_err(|| ErrorKind::EventDispatcherError)?; + let child = cmd .plugin(plugin_path, vec![event_dispatcher.path().to_owned()]) - .log(log_path.as_ref()) + .log(log_path.as_ref().map(|p| p.as_path())) .start() .chain_err(|| ErrorKind::ChildProcessError("Failed to start"))?; + Ok(OpenVpnMonitor { child: Arc::new(child), event_dispatcher: Some(event_dispatcher), log_path, closed: Arc::new(AtomicBool::new(false)), + _user_pass_file: user_pass_file, + _proxy_auth_file: proxy_auth_file, }) } @@ -190,6 +290,102 @@ impl<C: OpenVpnBuilder> OpenVpnMonitor<C> { ErrorKind::ChildProcessError("Died unexpectedly").into() } + + fn create_proxy_auth_file( + proxy: &Option<OpenVpnProxySettings>, + ) -> ::std::result::Result<Option<mktemp::TempFile>, io::Error> { + if let Some(OpenVpnProxySettings::Remote(ref remote_proxy)) = proxy { + if let Some(ref proxy_auth) = remote_proxy.auth { + return Ok(Some(Self::create_credentials_file( + &proxy_auth.username, + &proxy_auth.password, + )?)); + } + } + Ok(None) + } + + fn create_credentials_file(username: &str, password: &str) -> io::Result<mktemp::TempFile> { + let temp_file = mktemp::TempFile::new(); + log::debug!("Writing credentials to {}", temp_file.as_ref().display()); + let mut file = fs::File::create(&temp_file)?; + Self::set_user_pass_file_permissions(&file)?; + write!(file, "{}\n{}\n", username, password)?; + Ok(temp_file) + } + + + #[cfg(unix)] + fn set_user_pass_file_permissions(file: &fs::File) -> io::Result<()> { + use std::os::unix::fs::PermissionsExt; + file.set_permissions(PermissionsExt::from_mode(0o400)) + } + + #[cfg(windows)] + fn set_user_pass_file_permissions(_file: &fs::File) -> io::Result<()> { + // TODO(linus): Lock permissions correctly on Windows. + Ok(()) + } + + fn get_plugin_path(resource_dir: &Path) -> Result<PathBuf> { + let path = resource_dir.join(OPENVPN_PLUGIN_FILENAME); + if path.exists() { + log::trace!("Using OpenVPN plugin at {}", path.display()); + Ok(path) + } else { + bail!(ErrorKind::PluginNotFound(path)); + } + } + + fn create_openvpn_cmd( + remote: Endpoint, + tunnel_alias: Option<OsString>, + options: &TunnelOptions, + user_pass_file: &Path, + proxy_auth_file: Option<&Path>, + resource_dir: &Path, + ) -> Result<OpenVpnCommand> { + let mut cmd = OpenVpnCommand::new(Self::get_openvpn_bin(resource_dir)?); + if let Some(config) = Self::get_config_path(resource_dir) { + cmd.config(config); + } + #[cfg(target_os = "linux")] + cmd.iproute_bin( + which::which("ip") + .compat() + .chain_err(|| ErrorKind::IpRouteNotFound)?, + ); + cmd.remote(remote) + .user_pass(user_pass_file) + .tunnel_options(&options.openvpn) + .enable_ipv6(options.enable_ipv6) + .tunnel_alias(tunnel_alias) + .ca(resource_dir.join("ca.crt")); + if let Some(proxy_auth_file) = proxy_auth_file { + cmd.proxy_auth(proxy_auth_file); + } + + Ok(cmd) + } + + fn get_openvpn_bin(resource_dir: &Path) -> Result<PathBuf> { + let path = resource_dir.join(OPENVPN_BIN_FILENAME); + if path.exists() { + log::trace!("Using OpenVPN at {}", path.display()); + Ok(path) + } else { + bail!(ErrorKind::OpenVpnNotFound(path)); + } + } + + fn get_config_path(resource_dir: &Path) -> Option<PathBuf> { + let path = resource_dir.join("openvpn.conf"); + if path.exists() { + Some(path) + } else { + None + } + } } /// A handle to an `OpenVpnMonitor` for closing it. @@ -332,6 +528,7 @@ mod tests { use super::*; use std::path::{Path, PathBuf}; + use mktemp::TempFile; use std::sync::{Arc, Mutex}; #[derive(Debug, Default, Clone)] @@ -384,7 +581,14 @@ mod tests { #[test] fn sets_plugin() { let builder = TestOpenVpnBuilder::default(); - let _ = OpenVpnMonitor::new_internal(builder.clone(), |_, _| {}, "./my_test_plugin", None); + let _ = OpenVpnMonitor::new_internal( + builder.clone(), + |_, _| {}, + "./my_test_plugin", + None, + TempFile::new(), + None, + ); assert_eq!( Some(PathBuf::from("./my_test_plugin")), *builder.plugin.lock().unwrap() @@ -397,8 +601,10 @@ mod tests { let _ = OpenVpnMonitor::new_internal( builder.clone(), |_, _| {}, - "./my_test_plugin", + "", Some(PathBuf::from("./my_test_log_file")), + TempFile::new(), + None, ); assert_eq!( Some(PathBuf::from("./my_test_log_file")), @@ -410,7 +616,9 @@ mod tests { fn exit_successfully() { let mut builder = TestOpenVpnBuilder::default(); builder.process_handle = Some(TestProcessHandle(0)); - let testee = OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None).unwrap(); + let testee = + OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None, TempFile::new(), None) + .unwrap(); assert!(testee.wait().is_ok()); } @@ -418,7 +626,9 @@ mod tests { fn exit_error() { let mut builder = TestOpenVpnBuilder::default(); builder.process_handle = Some(TestProcessHandle(1)); - let testee = OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None).unwrap(); + let testee = + OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None, TempFile::new(), None) + .unwrap(); assert!(testee.wait().is_err()); } @@ -426,7 +636,9 @@ mod tests { fn wait_closed() { let mut builder = TestOpenVpnBuilder::default(); builder.process_handle = Some(TestProcessHandle(1)); - let testee = OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None).unwrap(); + let testee = + OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None, TempFile::new(), None) + .unwrap(); testee.close_handle().close().unwrap(); assert!(testee.wait().is_ok()); } @@ -434,7 +646,9 @@ mod tests { #[test] fn failed_process_start() { let builder = TestOpenVpnBuilder::default(); - let error = OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None).unwrap_err(); + let error = + OpenVpnMonitor::new_internal(builder, |_, _| {}, "", None, TempFile::new(), None) + .unwrap_err(); match error.kind() { ErrorKind::ChildProcessError(_) => (), _ => panic!("Wrong error"), diff --git a/talpid-core/src/tunnel/wireguard/config.rs b/talpid-core/src/tunnel/wireguard/config.rs new file mode 100644 index 0000000000..a99e46994f --- /dev/null +++ b/talpid-core/src/tunnel/wireguard/config.rs @@ -0,0 +1,165 @@ +use super::{ErrorKind, Result}; +use ipnetwork::IpNetwork; +use std::{ + borrow::Cow, + ffi::CString, + net::{IpAddr, SocketAddr}, +}; +use talpid_types::net::{TunnelOptions, WgPrivateKey, WgPublicKey, WireguardEndpointData}; + +pub struct Config { + pub interface: TunnelConfig, + pub gateway: IpAddr, + pub preferred_name: Option<String>, +} +// Smallest MTU that supports IPv6 +const MIN_IPV6_MTU: u16 = 1420; +const DEFAULT_MTU: u16 = MIN_IPV6_MTU; + +impl Config { + pub fn from_data( + ip: IpAddr, + data: WireguardEndpointData, + options: &TunnelOptions, + ) -> Result<Config> { + let private_key = match data.client_private_key { + Some(private_key) => private_key, + None => bail!(ErrorKind::NoKeyError), + }; + + let mtu = options.wireguard.mtu.unwrap_or(DEFAULT_MTU); + let ipv6_enabled = options.enable_ipv6 && mtu >= MIN_IPV6_MTU; + let peer = PeerConfig { + public_key: data.peer_public_key, + allowed_ips: all_of_the_internet() + .into_iter() + .filter(|ip| ip.is_ipv4() || ipv6_enabled) + .collect(), + endpoint: SocketAddr::new(ip, data.port), + }; + + let tunnel_config = TunnelConfig { + private_key, + addresses: data + .addresses + .into_iter() + .filter(|ip| ip.is_ipv4() || ipv6_enabled) + .collect(), + mtu, + #[cfg(target_os = "linux")] + fwmark: options.wireguard.fwmark, + peers: vec![peer], + }; + + Ok(Config { + interface: tunnel_config, + gateway: data.gateway, + preferred_name: Some("talpid".to_string()), + }) + } + + // should probably take a flag that alters between additive and overwriting conf + pub fn to_userspace_format(&self) -> CString { + // the order of insertion matters, public key entry denotes a new peer entry + let mut wg_conf = WgConfigBuffer::new(); + wg_conf + .add( + "private_key", + self.interface.private_key.as_bytes().as_ref(), + ) + .add("listen_port", "0"); + + #[cfg(target_os = "linux")] + { + wg_conf.add("fwmark", self.interface.fwmark.to_string().as_str()); + } + + wg_conf.add("replace_peers", "true"); + + for peer in &self.interface.peers { + wg_conf + .add("public_key", peer.public_key.as_bytes().as_ref()) + .add("endpoint", peer.endpoint.to_string().as_str()) + .add("replace_allowed_ips", "true"); + for addr in &peer.allowed_ips { + wg_conf.add("allowed_ip", addr.to_string().as_str()); + } + } + + let bytes = wg_conf.into_config(); + CString::new(bytes).expect("null bytes inside config") + } +} + +pub struct PeerConfig { + pub public_key: WgPublicKey, + pub allowed_ips: Vec<IpNetwork>, + pub endpoint: SocketAddr, +} + +pub struct TunnelConfig { + pub private_key: WgPrivateKey, + pub addresses: Vec<IpAddr>, + #[cfg(target_os = "linux")] + pub fwmark: i32, + pub mtu: u16, + pub peers: Vec<PeerConfig>, +} + + +fn all_of_the_internet() -> Vec<IpNetwork> { + vec![ + "::0/0".parse().expect("Failed to parse ipv6 network"), + "0.0.0.0/0".parse().expect("Failed to parse ipv4 network"), + ] +} + +pub enum ConfValue<'a> { + String(&'a str), + Bytes(&'a [u8]), +} + +impl<'a> From<&'a str> for ConfValue<'a> { + fn from(s: &'a str) -> ConfValue<'a> { + ConfValue::String(s) + } +} + +impl<'a> From<&'a [u8]> for ConfValue<'a> { + fn from(s: &'a [u8]) -> ConfValue<'a> { + ConfValue::Bytes(s) + } +} + + +impl<'a> ConfValue<'a> { + fn to_bytes(&self) -> Cow<'a, [u8]> { + match self { + ConfValue::String(s) => s.as_bytes().into(), + ConfValue::Bytes(bytes) => Cow::Owned(hex::encode(bytes).into_bytes()), + } + } +} + +pub struct WgConfigBuffer { + buf: Vec<u8>, +} + +impl WgConfigBuffer { + pub fn new() -> WgConfigBuffer { + WgConfigBuffer { buf: Vec::new() } + } + + pub fn add<'a, C: Into<ConfValue<'a>> + 'a>(&mut self, key: &str, value: C) -> &mut Self { + self.buf.extend(key.as_bytes()); + self.buf.extend(b"="); + self.buf.extend(value.into().to_bytes().as_ref()); + self.buf.extend(b"\n"); + self + } + + pub fn into_config(mut self) -> Vec<u8> { + self.buf.push(b'\n'); + self.buf + } +} diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs new file mode 100644 index 0000000000..c74dc8d41e --- /dev/null +++ b/talpid-core/src/tunnel/wireguard/mod.rs @@ -0,0 +1,198 @@ +use self::config::Config; +use super::{TunnelEvent, TunnelMetadata}; +use crate::routing; +use std::{net::IpAddr, path::Path, sync::mpsc}; +use talpid_types::net::{TunnelOptions, WireguardEndpointData}; + +pub mod config; +mod ping_monitor; +pub mod wireguard_go; + +pub use self::wireguard_go::WgGoTunnel; + +// amount of seconds to run `ping` until it returns. +const PING_TIMEOUT: u16 = 5; + +error_chain! { + errors { + /// Failed to setup a tunnel device + SetupTunnelDeviceError { + description("Failed to create tunnel device") + } + /// Failed to setup wireguard tunnel + StartWireguardError(status: i32) { + display("Failed to start wireguard tunnel - {}", status) + } + /// Failed to tear down wireguard tunnel + StopWireguardError(status: i32) { + display("Failed to stop wireguard tunnel - {}", status) + } + /// Failed to set up routing + SetupRoutingError { + display("Failed to setup routing") + } + /// Failed to move or craete a log file + PrepareLogFileError { + display("Failed to setup a logging file") + } + /// Tunnel interface name contained null bytes + InterfaceNameError { + display("Tunnel interface name contains null bytes") + } + /// No private key supplied + NoKeyError { + display("Config has no keys") + } + /// Pinging timed out + PingTimeoutError { + display("Ping timed out") + } + } +} + +/// Spawns and monitors a wireguard tunnel +pub struct WireguardMonitor { + /// Tunnel implementation + tunnel: Box<dyn Tunnel>, + /// Route manager + router: routing::RouteManager, + /// Callback to signal tunnel events + event_callback: Box<Fn(TunnelEvent) + Send + Sync + 'static>, + close_msg_sender: mpsc::Sender<CloseMsg>, + close_msg_receiver: mpsc::Receiver<CloseMsg>, +} + +impl WireguardMonitor { + pub fn start<F: Fn(TunnelEvent) + Send + Sync + 'static>( + address: IpAddr, + data: WireguardEndpointData, + options: &TunnelOptions, + log_path: Option<&Path>, + on_event: F, + ) -> Result<WireguardMonitor> { + let config = Config::from_data(address, data.clone(), options)?; + let tunnel = Box::new(WgGoTunnel::start_tunnel(&config, log_path)?); + let router = routing::RouteManager::new().chain_err(|| ErrorKind::SetupRoutingError)?; + let event_callback = Box::new(on_event); + let (close_msg_sender, close_msg_receiver) = mpsc::channel(); + let mut monitor = WireguardMonitor { + tunnel, + router, + event_callback, + close_msg_sender, + close_msg_receiver, + }; + monitor.setup_routing(&config)?; + monitor.start_pinger(&config); + monitor.tunnel_up(data); + + Ok(monitor) + } + + pub fn close_handle(&self) -> CloseHandle { + CloseHandle { + chan: self.close_msg_sender.clone(), + } + } + + pub fn wait(self) -> Result<()> { + let wait_result = match self.close_msg_receiver.recv() { + Ok(CloseMsg::PingErr) => Err(ErrorKind::PingTimeoutError.into()), + Ok(CloseMsg::Stop) => Ok(()), + Err(_) => Ok(()), + }; + if let Err(e) = self.tunnel.stop() { + log::error!("Failed to stop tunnel - {}", e); + } + (self.event_callback)(TunnelEvent::Down); + wait_result + } + + fn setup_routing(&mut self, config: &Config) -> Result<()> { + let iface_name = self.tunnel.get_interface_name(); + let mut routes: Vec<_> = config + .interface + .peers + .iter() + .flat_map(|peer| peer.allowed_ips.iter()) + .cloned() + .map(|allowed_ip| { + routing::Route::new(allowed_ip, routing::NetNode::Device(iface_name.to_string())) + }) + .collect(); + + if cfg!(target_os = "macos") { + // To survive network roaming on osx, we should listen for new routes and reapply them + // here - probably would need RouteManager be extended. Or maybe RouteManager can deal + // with it on it's own + let default_node = self + .router + .get_default_route_node() + .chain_err(|| ErrorKind::SetupRoutingError)?; + // route endpoints with specific routes + for peer in config.interface.peers.iter() { + let default_route = routing::Route::new( + peer.endpoint.ip().clone().into(), + routing::NetNode::Address(default_node.clone()), + ); + routes.push(default_route); + } + } + + let required_routes = routing::RequiredRoutes { + routes, + #[cfg(target_os = "linux")] + fwmark: Some(config.interface.fwmark.to_string()), + }; + self.router + .add_routes(required_routes) + .chain_err(|| ErrorKind::SetupRoutingError) + } + + fn start_pinger(&self, config: &Config) { + let close_sender = self.close_msg_sender.clone(); + + ping_monitor::spawn_ping_monitor( + config.gateway, + PING_TIMEOUT, + self.tunnel.get_interface_name().to_string(), + move || { + let _ = close_sender.send(CloseMsg::PingErr); + }, + ) + } + + fn tunnel_up(&self, data: WireguardEndpointData) { + let interface_name = self.tunnel.get_interface_name(); + let metadata = TunnelMetadata { + interface: interface_name.to_string(), + ips: data.addresses, + gateway: data.gateway, + }; + (self.event_callback)(TunnelEvent::Up(metadata)); + } +} + +enum CloseMsg { + Stop, + PingErr, +} + +#[derive(Clone, Debug)] +pub struct CloseHandle { + chan: mpsc::Sender<CloseMsg>, +} + + +impl CloseHandle { + pub fn close(&mut self) { + if let Err(e) = self.chan.send(CloseMsg::Stop) { + log::trace!("Failed to send close message to wireguard tunnel - {}", e); + } + } +} + +pub trait Tunnel: Send { + fn get_interface_name(&self) -> &str; + fn stop(self: Box<Self>) -> Result<()>; +} diff --git a/talpid-core/src/tunnel/wireguard/ping_monitor.rs b/talpid-core/src/tunnel/wireguard/ping_monitor.rs new file mode 100644 index 0000000000..58f2904c88 --- /dev/null +++ b/talpid-core/src/tunnel/wireguard/ping_monitor.rs @@ -0,0 +1,71 @@ +use std::{net::IpAddr, thread, time}; + +error_chain! { + errors { + PingError{ + description("Failed to run ping") + } + + TimeoutError { + description("Ping timed out") + } + } +} + +pub fn spawn_ping_monitor<F: FnOnce() + Send + 'static>( + ip: IpAddr, + timeout_secs: u16, + interface: String, + on_fail: F, +) { + thread::spawn(move || loop { + let start = time::Instant::now(); + if let Err(e) = ping(ip, timeout_secs, &interface) { + log::debug!("ping failed - {}", e); + on_fail(); + return; + } + if let Some(remaining) = + time::Duration::from_secs(timeout_secs.into()).checked_sub(start.elapsed()) + { + thread::sleep(remaining); + } + }); +} + +pub fn ping(ip: IpAddr, timeout_secs: u16, interface: &str) -> Result<()> { + let output = ping_cmd(ip, timeout_secs, interface) + .run() + .chain_err(|| ErrorKind::PingError)?; + if !output.status.success() { + bail!(ErrorKind::TimeoutError); + } + Ok(()) +} + +fn ping_cmd(ip: IpAddr, timeout_secs: u16, interface: &str) -> duct::Expression { + let interface_flag = if cfg!(target_os = "linux") { + "-I" + } else { + "-b" + }; + let timeout_flag = if cfg!(target_os = "linux") { + "-w" + } else { + "-t" + }; + duct::cmd!( + "ping", + "-n", + "-c", + "1", + &interface_flag, + &interface, + timeout_flag, + &timeout_secs.to_string(), + ip.to_string() + ) + .stdin_null() + .stdout_null() + .unchecked() +} diff --git a/talpid-core/src/tunnel/wireguard/wireguard_go.rs b/talpid-core/src/tunnel/wireguard/wireguard_go.rs new file mode 100644 index 0000000000..32d82889cc --- /dev/null +++ b/talpid-core/src/tunnel/wireguard/wireguard_go.rs @@ -0,0 +1,115 @@ +use super::{Config, ErrorKind, Result, ResultExt, Tunnel}; +use crate::{ + logging, + network_interface::{NetworkInterface, TunnelDevice}, +}; +use std::{ffi::CString, fs, os::unix::io::AsRawFd, path::Path}; + + +pub struct WgGoTunnel { + interface_name: String, + handle: i32, + // holding on to the tunnel device and the log file ensures that the associated file handles + // live long enough and get closed when the tunnel is stopped + _tunnel_device: TunnelDevice, + _log_file: fs::File, +} + +impl WgGoTunnel { + pub fn start_tunnel(config: &Config, log_path: Option<&Path>) -> Result<Self> { + let mut tunnel_device = + TunnelDevice::new().chain_err(|| ErrorKind::SetupTunnelDeviceError)?; + + for ip in config.interface.addresses.iter() { + tunnel_device + .set_ip(*ip) + .chain_err(|| ErrorKind::SetupTunnelDeviceError)?; + } + + tunnel_device + .set_up(true) + .chain_err(|| ErrorKind::SetupTunnelDeviceError)?; + + let interface_name: String = tunnel_device.get_name().to_string(); + let log_file = prepare_log_file(log_path)?; + + let wg_config_str = config.to_userspace_format(); + let iface_name = + CString::new(interface_name.as_bytes()).chain_err(|| ErrorKind::InterfaceNameError)?; + + let handle = unsafe { + wgTurnOnWithFd( + iface_name.as_ptr(), + config.interface.mtu as i64, + wg_config_str.as_ptr(), + tunnel_device.as_raw_fd(), + log_file.as_raw_fd(), + WG_GO_LOG_DEBUG, + ) + }; + + if handle < 0 { + bail!(ErrorKind::StartWireguardError(handle)); + } + + Ok(WgGoTunnel { + interface_name, + handle, + _tunnel_device: tunnel_device, + _log_file: log_file, + }) + } +} + +fn prepare_log_file(log_path: Option<&Path>) -> Result<fs::File> { + match log_path { + Some(path) => { + logging::rotate_log(path).chain_err(|| ErrorKind::PrepareLogFileError)?; + fs::File::open(&path).chain_err(|| ErrorKind::PrepareLogFileError) + } + None => fs::File::open("/dev/null").chain_err(|| ErrorKind::PrepareLogFileError), + } +} + +impl Tunnel for WgGoTunnel { + fn get_interface_name(&self) -> &str { + &self.interface_name + } + + fn stop(self: Box<Self>) -> Result<()> { + let status = unsafe { wgTurnOff(self.handle) }; + if status < 0 { + bail!(ErrorKind::StopWireguardError(status)) + } + Ok(()) + } +} + +#[cfg(unix)] +pub type Fd = std::os::unix::io::RawFd; + +#[cfg(windows)] +pub type Fd = std::os::windows::io::RawHandle; + +type WgLogLevel = i32; +// wireguard-go supports log levels 0 through 3 with 3 being the most verbose +const WG_GO_LOG_DEBUG: WgLogLevel = 3; + +#[link(name = "wg", kind = "static")] +extern "C" { + // Creates a new wireguard tunnel, uses the specific interface name, MTU and file descriptors + // for the tunnel device and logging. + // + // Positive return values are tunnel handles for this specific wireguard tunnel instance. + // Negative return values signify errors. All error codes are opaque. + fn wgTurnOnWithFd( + iface_name: *const i8, + mtu: i64, + settings: *const i8, + fd: Fd, + log_fd: Fd, + logLevel: WgLogLevel, + ) -> i32; + // Pass a handle that was created by wgTurnOnWithFd to stop a wireguard tunnel. + fn wgTurnOff(handle: i32) -> i32; +} diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index db28a200ef..4a65e7fadd 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -192,7 +192,7 @@ impl TunnelState for ConnectedState { shared_values: &mut SharedTunnelStateValues, bootstrap: Self::Bootstrap, ) -> (TunnelStateWrapper, TunnelStateTransition) { - let tunnel_endpoint = bootstrap.tunnel_parameters.endpoint; + let tunnel_endpoint = bootstrap.tunnel_parameters.endpoint.clone(); let connected_state = ConnectedState::from(bootstrap); if let Err(error) = connected_state.set_security_policy(shared_values) { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index a549145e90..3a4ea3d37a 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -120,7 +120,7 @@ impl ConnectingState { let log_file = Self::prepare_tunnel_log_file(¶meters, log_dir)?; Ok(TunnelMonitor::start( - parameters.endpoint, + parameters.endpoint.clone(), ¶meters.options, TUNNEL_INTERFACE_ALIAS.to_owned().map(OsString::from), ¶meters.username, @@ -229,7 +229,7 @@ impl ConnectingState { match Self::set_security_policy( shared_values, &self.tunnel_parameters.options.openvpn.proxy, - self.tunnel_parameters.endpoint, + self.tunnel_parameters.endpoint.clone(), ) { Ok(()) => SameState(self), Err(error) => { @@ -367,11 +367,11 @@ impl TunnelState for ConnectingState { { None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay), Some(tunnel_parameters) => { - let tunnel_endpoint = tunnel_parameters.endpoint; + let tunnel_endpoint = tunnel_parameters.endpoint.clone(); if let Err(error) = Self::set_security_policy( shared_values, &tunnel_parameters.options.openvpn.proxy, - tunnel_endpoint, + tunnel_endpoint.clone(), ) { error!("{}", error.display_chain()); BlockedState::enter(shared_values, BlockReason::StartTunnelError) |
