diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-12-04 21:34:46 +0000 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-01-18 10:26:24 +0000 |
| commit | e383870d147b7c8165109de91f4a8df2b9a5deb0 (patch) | |
| tree | 54a70b85f054ec8863a98f2169d2bf0fe1f1a546 | |
| parent | 2b26493e4e45d75d4d9a34a7bed0f017ad73bbc6 (diff) | |
| download | mullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.tar.xz mullvadvpn-e383870d147b7c8165109de91f4a8df2b9a5deb0.zip | |
Add wireguard-go support in talpid-core
| -rw-r--r-- | Cargo.lock | 82 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 3 | ||||
| -rw-r--r-- | mullvad-types/src/custom_tunnel.rs | 2 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/build.rs | 25 | ||||
| -rw-r--r-- | talpid-core/src/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/network_interface.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/routing/mod.rs | 1 | ||||
| -rw-r--r-- | talpid-core/src/security/linux/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/security/mod.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 340 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn.rs | 286 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/config.rs | 165 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/mod.rs | 198 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/ping_monitor.rs | 71 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/wireguard_go.rs | 115 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 8 | ||||
| -rw-r--r-- | talpid-types/Cargo.toml | 3 | ||||
| -rw-r--r-- | talpid-types/src/net.rs | 123 |
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(¶meters, log_dir)?; Ok(TunnelMonitor::start( - parameters.endpoint, + parameters.endpoint.clone(), ¶meters.options, TUNNEL_INTERFACE_ALIAS.to_owned().map(OsString::from), ¶meters.username, @@ -229,7 +229,7 @@ impl ConnectingState { match Self::set_security_policy( shared_values, &self.tunnel_parameters.options.openvpn.proxy, - self.tunnel_parameters.endpoint, + self.tunnel_parameters.endpoint.clone(), ) { Ok(()) => SameState(self), Err(error) => { @@ -367,11 +367,11 @@ impl TunnelState for ConnectingState { { None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay), Some(tunnel_parameters) => { - let tunnel_endpoint = tunnel_parameters.endpoint; + let tunnel_endpoint = tunnel_parameters.endpoint.clone(); if let Err(error) = Self::set_security_policy( shared_values, &tunnel_parameters.options.openvpn.proxy, - tunnel_endpoint, + tunnel_endpoint.clone(), ) { error!("{}", error.display_chain()); BlockedState::enter(shared_values, BlockReason::StartTunnelError) 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)) + } +} |
