summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2018-12-04 21:34:46 +0000
committerEmīls Piņķis <emils@mullvad.net>2019-01-18 10:26:24 +0000
commite383870d147b7c8165109de91f4a8df2b9a5deb0 (patch)
tree54a70b85f054ec8863a98f2169d2bf0fe1f1a546 /talpid-core/src
parent2b26493e4e45d75d4d9a34a7bed0f017ad73bbc6 (diff)
downloadmullvadvpn-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.rs2
-rw-r--r--talpid-core/src/network_interface.rs2
-rw-r--r--talpid-core/src/routing/mod.rs1
-rw-r--r--talpid-core/src/security/linux/mod.rs2
-rw-r--r--talpid-core/src/security/mod.rs7
-rw-r--r--talpid-core/src/tunnel/mod.rs340
-rw-r--r--talpid-core/src/tunnel/openvpn.rs286
-rw-r--r--talpid-core/src/tunnel/wireguard/config.rs165
-rw-r--r--talpid-core/src/tunnel/wireguard/mod.rs198
-rw-r--r--talpid-core/src/tunnel/wireguard/ping_monitor.rs71
-rw-r--r--talpid-core/src/tunnel/wireguard/wireguard_go.rs115
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs8
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(&parameters, log_dir)?;
Ok(TunnelMonitor::start(
- parameters.endpoint,
+ parameters.endpoint.clone(),
&parameters.options,
TUNNEL_INTERFACE_ALIAS.to_owned().map(OsString::from),
&parameters.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)