summaryrefslogtreecommitdiffhomepage
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
parent2b26493e4e45d75d4d9a34a7bed0f017ad73bbc6 (diff)
downloadmullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.tar.xz
mullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.zip
Add wireguard-go support in talpid-core
-rw-r--r--Cargo.lock82
-rw-r--r--mullvad-cli/src/cmds/relay.rs3
-rw-r--r--mullvad-types/src/custom_tunnel.rs2
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-core/build.rs25
-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
-rw-r--r--talpid-types/Cargo.toml3
-rw-r--r--talpid-types/src/net.rs123
20 files changed, 1165 insertions, 273 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d1e769f3fb..efc89afafe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,14 @@ dependencies = [
]
[[package]]
+name = "base64"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -150,6 +158,14 @@ dependencies = [
]
[[package]]
+name = "clear_on_drop"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -221,6 +237,19 @@ dependencies = [
]
[[package]]
+name = "curve25519-dalek"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "subtle 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "dbus"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -258,6 +287,14 @@ dependencies = [
]
[[package]]
+name = "digest"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "dirs"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -452,6 +489,14 @@ version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "generic-array"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "globset"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -476,6 +521,11 @@ dependencies = [
]
[[package]]
+name = "hex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "httparse"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1690,6 +1740,11 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "subtle"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "syn"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1761,6 +1816,7 @@ dependencies = [
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ipnetwork 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"iproute2 0.0.2 (git+https://github.com/mullvad/netlink?branch=best-effort-nla-parsing)",
"jsonrpc-core 8.0.2 (git+https://github.com/mullvad/jsonrpc?branch=make-ipc-server-concurrent-part-deux)",
@@ -1835,7 +1891,10 @@ dependencies = [
name = "talpid-types"
version = "0.1.0"
dependencies = [
+ "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ipnetwork 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x25519-dalek 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2146,6 +2205,11 @@ dependencies = [
]
[[package]]
+name = "typenum"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "ucd-util"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2336,6 +2400,15 @@ dependencies = [
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "x25519-dalek"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "curve25519-dalek 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[metadata]
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
@@ -2346,6 +2419,7 @@ dependencies = [
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
+"checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2"
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
@@ -2356,6 +2430,7 @@ dependencies = [
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
+"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum colored 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc0a60679001b62fb628c4da80e574b9645ab4646056d7c9018885efffe45533"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
@@ -2365,10 +2440,12 @@ dependencies = [
"checksum crossbeam-epoch 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30fecfcac6abfef8771151f8be4abc9e4edc112c2bcb233314cafde2680536e9"
"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
+"checksum curve25519-dalek 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)" = "15d6d81c070d8090389f752510ce22c7d571100a78fa4e7c06e6f6d95585bb49"
"checksum dbus 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d975a175aa2dced1a6cd410b89a1bf23918f301eab2b6f7c5e608291b757639"
"checksum derive-try-from-primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81dbd65eb15734b6d50dc6ac86f14f928462be0a5df6bda17761e909071ede5d"
"checksum derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c998e6ab02a828dd9735c18f154e14100e674ed08cb4e1938f0e4177543f439"
"checksum derive_builder_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "735e24ee9e5fa8e16b86da5007856e97d592e11867e45d76e0c0d0a164a0b757"
+"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
"checksum duct 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f87f5af80601599209bff21146e4113e8c54471151049deebc37e75b78e42729"
"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
@@ -2392,8 +2469,10 @@ dependencies = [
"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
+"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum globset 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90d069fe6beb9be359ef505650b3f73228c5591a3c4b1f32be2f4f44459ffa3a"
"checksum globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4743617a7464bbda3c8aec8558ff2f9429047e025771037df561d383337ff865"
+"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
"checksum hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)" = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7"
@@ -2509,6 +2588,7 @@ dependencies = [
"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
+"checksum subtle 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5938f1b89f10d6356339f071eca74209deeae0b6891c2678d655feb78637e369"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.15.18 (registry+https://github.com/rust-lang/crates.io-index)" = "90c39a061e2f412a9f869540471ab679e85e50c6b05604daf28bc3060f75c430"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
@@ -2543,6 +2623,7 @@ dependencies = [
"checksum toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2ecc31b0351ea18b3fe11274b8db6e4d82bce861bbb22e6dbed40417902c65"
"checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2"
"checksum tun 0.4.2 (git+https://github.com/pinkisemils/rust-tun?branch=add-raw-fd-traits)" = "<none>"
+"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0f8bfa9ff0cadcd210129ad9d2c5f145c13e9ced3d3e5d948a6213487d52444"
"checksum unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3218ea14b4edcaccfa0df0a64a3792a2c32cc706f1b336e48867f9d3147f90"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
@@ -2571,3 +2652,4 @@ dependencies = [
"checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
"checksum winres 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f07dabda4e79413ecac65bc9a2234ad3d85dc49f9d289f868cd9d8611d88f28d"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+"checksum x25519-dalek 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538296831e9794ec5b3ec9d4a1a8c3e3c86271e9635a0e776e3214fec9c727de"
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index edabbe8b9b..5acee6fd39 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -151,7 +151,8 @@ impl Relay {
port,
protocol: value_t!(matches.value_of("protocol"), TransportProtocol).unwrap(),
}),
- "wireguard" => TunnelEndpointData::Wireguard(WireguardEndpointData { port }),
+ // TODO: Gather all the data to build a WireguardEndpointData properly.
+ // "wireguard" => TunnelEndpointData::Wireguard(WireguardEndpointData { port }),
_ => unreachable!("Invalid tunnel protocol"),
};
self.update_constraints(RelaySettingsUpdate::CustomTunnelEndpoint(
diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs
index 0c1da70c50..475dfa2eaf 100644
--- a/mullvad-types/src/custom_tunnel.rs
+++ b/mullvad-types/src/custom_tunnel.rs
@@ -24,7 +24,7 @@ impl CustomTunnelEndpoint {
pub fn to_tunnel_endpoint(&self) -> Result<TunnelEndpoint> {
Ok(TunnelEndpoint {
address: resolve_to_ip(&self.host)?,
- tunnel: self.tunnel,
+ tunnel: self.tunnel.clone(),
})
}
}
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index a393cb846d..c9e0f82088 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -25,6 +25,7 @@ talpid-ipc = { path = "../talpid-ipc" }
talpid-types = { path = "../talpid-types" }
[target.'cfg(unix)'.dependencies]
+hex = "0.3"
ipnetwork = "0.13"
lazy_static = "1.0"
tun = { git = "https://github.com/pinkisemils/rust-tun", branch = "add-raw-fd-traits" }
diff --git a/talpid-core/build.rs b/talpid-core/build.rs
index afd8eccefb..5441062edc 100644
--- a/talpid-core/build.rs
+++ b/talpid-core/build.rs
@@ -1,5 +1,8 @@
+use std::{env, path::PathBuf};
+
#[cfg(windows)]
mod win {
+ use super::manifest_dir;
use std::{env, path::PathBuf};
pub static WINFW_BUILD_DIR: &'static str = "..\\windows\\winfw\\bin";
@@ -21,12 +24,6 @@ mod win {
target_dir.into()
}
- fn manifest_dir() -> PathBuf {
- env::var("CARGO_MANIFEST_DIR")
- .map(PathBuf::from)
- .expect("CARGO_MANIFEST_DIR env var not set")
- }
-
fn get_build_mode() -> &'static str {
let profile = env::var("PROFILE").expect("PROFILE env var not set");
if profile == "release" {
@@ -59,4 +56,18 @@ fn main() {
}
#[cfg(not(windows))]
-fn main() {}
+fn main() {
+ let lib_dir = if cfg!(target_os = "macos") {
+ manifest_dir().join("../dist-assets/binaries/macos")
+ } else {
+ manifest_dir().join("../dist-assets/binaries/linux")
+ };
+ println!("cargo:rustc-link-search={}", &lib_dir.display());
+ println!("cargo:rustc-link-lib=static=wg");
+}
+
+fn manifest_dir() -> PathBuf {
+ env::var("CARGO_MANIFEST_DIR")
+ .map(PathBuf::from)
+ .expect("CARGO_MANIFEST_DIR env var not set")
+}
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)
diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml
index 39ef556cd3..8bf28fdcc7 100644
--- a/talpid-types/Cargo.toml
+++ b/talpid-types/Cargo.toml
@@ -8,3 +8,6 @@ edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
+ipnetwork = "0.13"
+base64 = "0.10"
+x25519-dalek = { version = "0.3", default-features = false, features = ["u64_backend"]}
diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs
index b7a67f37c5..f63705154d 100644
--- a/talpid-types/src/net.rs
+++ b/talpid-types/src/net.rs
@@ -7,7 +7,7 @@ use std::{
};
/// Represents one tunnel endpoint. Address, plus extra parameters specific to tunnel protocol.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TunnelEndpoint {
pub address: IpAddr,
pub tunnel: TunnelEndpointData,
@@ -26,7 +26,7 @@ impl TunnelEndpoint {
/// TunnelEndpointData contains data required to connect to a given tunnel endpoint.
/// Different endpoint types can require different types of data.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum TunnelEndpointData {
/// Extra parameters for an OpenVPN tunnel endpoint.
#[serde(rename = "openvpn")]
@@ -52,14 +52,14 @@ impl fmt::Display for TunnelEndpointData {
}
impl TunnelEndpointData {
- pub fn port(self) -> u16 {
+ pub fn port(&self) -> u16 {
match self {
TunnelEndpointData::OpenVpn(metadata) => metadata.port,
TunnelEndpointData::Wireguard(metadata) => metadata.port,
}
}
- pub fn transport_protocol(self) -> TransportProtocol {
+ pub fn transport_protocol(&self) -> TransportProtocol {
match self {
TunnelEndpointData::OpenVpn(metadata) => metadata.protocol,
TunnelEndpointData::Wireguard(_) => TransportProtocol::Udp,
@@ -79,14 +79,46 @@ impl fmt::Display for OpenVpnEndpointData {
}
}
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
+#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub struct WireguardEndpointData {
+ /// Port to connect to
pub port: u16,
+ /// Link addresses
+ pub addresses: Vec<IpAddr>,
+ /// Peer's IP address
+ pub gateway: IpAddr,
+ #[serde(skip)]
+ /// Client's private key
+ pub client_private_key: Option<WgPrivateKey>,
+ /// The peer's public key
+ pub peer_public_key: WgPublicKey,
+}
+
+impl fmt::Debug for WireguardEndpointData {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ f.debug_struct(&"WireguardEndpointData")
+ .field("port", &self.port)
+ .field("addresses", &self.addresses)
+ .field("gateway", &self.gateway)
+ .field("peer_public_key", &self.peer_public_key)
+ .finish()
+ }
}
impl fmt::Display for WireguardEndpointData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- write!(f, "port {}", self.port)
+ write!(
+ f,
+ "gateway {} port {} peer_public_key {} addresses {}",
+ self.gateway,
+ self.port,
+ self.peer_public_key,
+ self.addresses
+ .iter()
+ .map(|a| a.to_string())
+ .collect::<Vec<_>>()
+ .join(",")
+ )
}
}
@@ -169,6 +201,8 @@ impl Error for TransportProtocolParseError {
pub struct TunnelOptions {
/// openvpn holds OpenVPN specific tunnel options.
pub openvpn: OpenVpnTunnelOptions,
+ /// Contains wireguard tunnel options.
+ pub wireguard: WireguardTunnelOptions,
/// Enable configuration of IPv6 on the tunnel interface, allowing IPv6 communication to be
/// forwarded through the tunnel. By default, this is set to `true`.
pub enable_ipv6: bool,
@@ -178,12 +212,12 @@ impl Default for TunnelOptions {
fn default() -> Self {
TunnelOptions {
openvpn: OpenVpnTunnelOptions::default(),
+ wireguard: WireguardTunnelOptions::default(),
enable_ipv6: false,
}
}
}
-
/// OpenVpnTunnelOptions contains options for an openvpn tunnel that should be applied irrespective
/// of the relay parameters - i.e. have nothing to do with the particular OpenVPN server, but do
/// affect the connection.
@@ -252,3 +286,78 @@ impl OpenVpnProxySettingsValidation {
Ok(())
}
}
+
+/// Wireguard tunnel options
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(default)]
+pub struct WireguardTunnelOptions {
+ /// MTU for the wireguard tunnel
+ pub mtu: Option<u16>,
+ /// firewall mark
+ #[cfg(target_os = "linux")]
+ pub fwmark: i32,
+}
+
+impl Default for WireguardTunnelOptions {
+ fn default() -> WireguardTunnelOptions {
+ Self {
+ mtu: None,
+ // Magic value that should be different for different end user applications, used as a
+ // firewall marker on Linux.
+ #[cfg(target_os = "linux")]
+ fwmark: 787878,
+ }
+ }
+}
+
+/// Wireguard x25519 private key
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
+pub struct WgPrivateKey([u8; 32]);
+
+impl WgPrivateKey {
+ /// Get private key as bytes
+ pub fn as_bytes(&self) -> &[u8; 32] {
+ &self.0
+ }
+
+ /// Get public key from private key
+ pub fn public_key(&self) -> WgPublicKey {
+ WgPublicKey(x25519_dalek::generate_public(self.as_bytes()).to_bytes())
+ }
+}
+
+impl From<[u8; 32]> for WgPrivateKey {
+ fn from(private_key: [u8; 32]) -> WgPrivateKey {
+ WgPrivateKey(private_key)
+ }
+}
+
+/// Wireguard x25519 public key
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
+pub struct WgPublicKey([u8; 32]);
+
+impl WgPublicKey {
+ /// Get the public key as bytes
+ pub fn as_bytes(&self) -> &[u8; 32] {
+ &self.0
+ }
+}
+
+
+impl From<[u8; 32]> for WgPublicKey {
+ fn from(public_key: [u8; 32]) -> WgPublicKey {
+ WgPublicKey(public_key)
+ }
+}
+
+impl fmt::Debug for WgPublicKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ write!(f, "{}", &self)
+ }
+}
+
+impl fmt::Display for WgPublicKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ write!(f, "{}", &base64::encode(&self.0))
+ }
+}