diff options
25 files changed, 1631 insertions, 531 deletions
diff --git a/Cargo.lock b/Cargo.lock index 6d761edd51..796002dbab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,18 @@ dependencies = [ ] [[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] name = "async-stream" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -205,6 +217,12 @@ dependencies = [ ] [[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] name = "async-tempfile" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -225,6 +243,12 @@ dependencies = [ ] [[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -300,6 +324,12 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" @@ -353,6 +383,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] name = "blake3" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -390,6 +429,49 @@ dependencies = [ ] [[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "boringtun" +version = "0.6.0" +source = "git+https://github.com/mullvad/boringtun?rev=a7e11fb46d4a#a7e11fb46d4ae65d4c7bf4efb9617548c072afa0" +dependencies = [ + "aead", + "base64 0.13.1", + "blake2", + "chacha20poly1305", + "eyre", + "hex", + "hmac", + "ip_network", + "ip_network_table", + "libc", + "log", + "nix 0.25.1", + "parking_lot", + "rand_core 0.6.4", + "ring", + "socket2 0.4.10", + "thiserror 1.0.59", + "tokio", + "tracing", + "tun 0.7.13", + "typed-builder 0.20.1", + "untrusted", + "x25519-dalek", +] + +[[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -414,6 +496,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] +name = "c2rust-bitfields" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367e5d1b30f28be590b6b3868da1578361d29d9bfac516d22f497d28ed7c9055" +dependencies = [ + "c2rust-bitfields-derive", +] + +[[package]] +name = "c2rust-bitfields-derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a279db9c50c4024eeca1a763b6e0f033848ce74e83e47454bcf8a8a98f7b0b56" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "cacao" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -647,10 +749,19 @@ dependencies = [ ] [[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "console" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", @@ -1180,6 +1291,37 @@ dependencies = [ ] [[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] name = "fastrand" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1327,6 +1469,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1821,7 +1973,7 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -1996,6 +2148,12 @@ dependencies = [ ] [[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2135,12 +2293,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" [[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + +[[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.8", "widestring", "windows-sys 0.48.0", "winreg 0.50.0", @@ -2765,7 +2945,7 @@ dependencies = [ "serde", "serde_json", "simple-signal", - "socket2", + "socket2 0.5.8", "talpid-core", "talpid-dbus", "talpid-future", @@ -2878,7 +3058,7 @@ dependencies = [ "pnet_packet 0.35.0", "reqwest", "serde", - "socket2", + "socket2 0.5.8", "talpid-windows", "tokio", "windows-sys 0.52.0", @@ -2923,10 +3103,10 @@ dependencies = [ "rand 0.8.5", "rustls 0.23.18", "rustls-pemfile 2.1.3", - "socket2", + "socket2 0.5.8", "thiserror 2.0.9", "tokio", - "typed-builder", + "typed-builder 0.21.0", ] [[package]] @@ -3240,6 +3420,19 @@ dependencies = [ [[package]] name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" @@ -3581,6 +3774,12 @@ dependencies = [ ] [[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3770,6 +3969,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4113,7 +4323,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.18", - "socket2", + "socket2 0.5.8", "thiserror 2.0.9", "tokio", "tracing", @@ -4150,7 +4360,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2", + "socket2 0.5.8", "tracing", "windows-sys 0.59.0", ] @@ -4789,7 +4999,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "shadowsocks-crypto", - "socket2", + "socket2 0.5.8", "spin", "thiserror 1.0.59", "tokio", @@ -4848,7 +5058,7 @@ dependencies = [ "regex", "serde", "shadowsocks", - "socket2", + "socket2 0.5.8", "spin", "thiserror 1.0.59", "tokio", @@ -4943,6 +5153,16 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" @@ -4998,7 +5218,7 @@ dependencies = [ "parking_lot", "pnet_packet 0.34.0", "rand 0.8.5", - "socket2", + "socket2 0.5.8", "thiserror 1.0.59", "tokio", "tracing", @@ -5131,7 +5351,7 @@ dependencies = [ "tokio", "tonic-build", "triggered", - "tun", + "tun 0.5.5", "which", "widestring", "windows 0.58.0", @@ -5178,7 +5398,7 @@ dependencies = [ "libc", "log", "nix 0.29.0", - "socket2", + "socket2 0.5.8", "talpid-types", "thiserror 2.0.9", ] @@ -5287,7 +5507,8 @@ dependencies = [ "talpid-windows", "thiserror 2.0.9", "tokio", - "tun", + "tun 0.5.5", + "tun 0.7.13", "windows-sys 0.52.0", ] @@ -5333,7 +5554,7 @@ name = "talpid-windows" version = "0.0.0" dependencies = [ "futures", - "socket2", + "socket2 0.5.8", "talpid-types", "thiserror 2.0.9", "windows-sys 0.52.0", @@ -5345,6 +5566,7 @@ version = "0.0.0" dependencies = [ "async-trait", "bitflags 1.3.2", + "boringtun", "byteorder", "chrono", "futures", @@ -5365,7 +5587,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rtnetlink", - "socket2", + "socket2 0.5.8", "surge-ping", "talpid-dbus", "talpid-net", @@ -5377,6 +5599,7 @@ dependencies = [ "thiserror 2.0.9", "tokio", "tokio-stream", + "tun 0.7.13", "tunnel-obfuscation", "widestring", "windows-sys 0.52.0", @@ -5525,7 +5748,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -5597,7 +5820,7 @@ dependencies = [ "log", "once_cell", "pin-project", - "socket2", + "socket2 0.5.8", "tokio", "windows-sys 0.52.0", ] @@ -5694,7 +5917,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.13.3", - "socket2", + "socket2 0.5.8", "tokio", "tokio-stream", "tower 0.4.13", @@ -5864,6 +6087,27 @@ dependencies = [ ] [[package]] +name = "tun" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9298ac5c7f0076908d7a168c634bf4867b4a7d5725eb6356863f8640c6c35ef1" +dependencies = [ + "bytes", + "cfg-if", + "futures", + "futures-core", + "ipnet", + "libc", + "log", + "nix 0.29.0", + "thiserror 2.0.9", + "tokio", + "tokio-util 0.7.10", + "windows-sys 0.59.0", + "wintun-bindings", +] + +[[package]] name = "tunnel-obfuscation" version = "0.0.0" dependencies = [ @@ -5879,11 +6123,31 @@ dependencies = [ [[package]] name = "typed-builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7" +dependencies = [ + "typed-builder-macro 0.20.1", +] + +[[package]] +name = "typed-builder" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce63bcaf7e9806c206f7d7b9c1f38e0dce8bb165a80af0898161058b19248534" dependencies = [ - "typed-builder-macro", + "typed-builder-macro 0.21.0", +] + +[[package]] +name = "typed-builder-macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -6749,6 +7013,16 @@ dependencies = [ ] [[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] name = "winres" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6758,6 +7032,22 @@ dependencies = [ ] [[package]] +name = "wintun-bindings" +version = "0.7.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a02981bed4592bcd271f9bfe154228ddbd2fd69e37a7d358da5d3a1251d696" +dependencies = [ + "blocking", + "c2rust-bitfields", + "futures", + "libloading", + "log", + "thiserror 2.0.9", + "windows-sys 0.59.0", + "winreg 0.55.0", +] + +[[package]] name = "wireguard-go-rs" version = "0.0.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 333772b495..e3c1c6a29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,10 +140,10 @@ strip = true # Selectively optimize packages where we know it makes a difference [profile.release.package] +boringtun.opt-level = 3 pqcrypto-hqc.opt-level = 3 quinn-proto.opt-level = 3 quinn-udp.opt-level = 3 quinn.opt-level = 3 mullvad-masque-proxy.opt-level = 3 ring.opt-level = 3 - diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 0dfc4d9236..a628476cb6 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -247,6 +247,7 @@ junitPlatform { cargo { val isReleaseBuild = isReleaseBuild() + val enableBoringTun = getBooleanProperty("mullvad.app.build.boringtun.enable") val enableApiOverride = !isReleaseBuild || isDevBuild() || isAlphaBuild() module = repoRootPath libname = "mullvad-jni" @@ -262,9 +263,15 @@ cargo { prebuiltToolchains = true targetDirectory = "$repoRootPath/target" features { - if (enableApiOverride) { - defaultAnd(arrayOf("api-override")) + val enabledFeatures = buildList { + if (enableApiOverride) { + add("api-override") + } + if (enableBoringTun) { + add("boringtun") + } } + defaultAnd(enabledFeatures.toTypedArray()) } targetIncludes = arrayOf("libmullvad_jni.so") extraCargoBuildArguments = buildList { diff --git a/android/gradle.properties b/android/gradle.properties index 07b1de2faf..22dc06c43c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -33,6 +33,9 @@ mullvad.app.build.cargo.cleanBuild=true # to be substantially larger. mullvad.app.build.keepDebugSymbols=false +# Enable/Disable boringtun +mullvad.app.build.boringtun.enable=false + ## E2E tests ## # To run e2e tests you need to provide credentails for the enviroment you diff --git a/building/container-run.sh b/building/container-run.sh index 639c711332..2defc76170 100755 --- a/building/container-run.sh +++ b/building/container-run.sh @@ -54,7 +54,7 @@ fi set -x exec "$CONTAINER_RUNNER" run --rm -it \ - -v "$REPO_DIR:$REPO_MOUNT_TARGET:Z" \ + -v "/$REPO_DIR:$REPO_MOUNT_TARGET:Z" \ -v "$CARGO_TARGET_VOLUME_NAME:/cargo-target:Z" \ -v "$CARGO_REGISTRY_VOLUME_NAME:/root/.cargo/registry:Z" \ "${optional_gradle_cache_volume[@]}" \ diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index ce5fd923da..bc22a2d1c8 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -13,6 +13,8 @@ workspace = true [features] # Allow the API server to use to be configured api-override = ["mullvad-api/api-override"] +boringtun = ["talpid-core/boringtun"] + [dependencies] anyhow = { workspace = true } diff --git a/mullvad-jni/Cargo.toml b/mullvad-jni/Cargo.toml index 3919719a82..9de530c4d9 100644 --- a/mullvad-jni/Cargo.toml +++ b/mullvad-jni/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [features] # Allow the API server to use to be configured api-override = ["mullvad-api/api-override", "mullvad-daemon/api-override"] +boringtun = ["mullvad-daemon/boringtun"] [lib] crate-type = ["cdylib"] diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index e2ef68cf86..35518f7ac0 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -10,6 +10,9 @@ rust-version.workspace = true [lints] workspace = true +[features] +boringtun = ["talpid-wireguard/boringtun"] + [dependencies] chrono = { workspace = true, features = ["clock"] } thiserror = { workspace = true } diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 70d496b031..1ac3fbb92c 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -175,14 +175,11 @@ impl TunnelMonitor { } fn start_wireguard_tunnel( - #[cfg(not(any(target_os = "linux", target_os = "windows")))] - params: &wireguard_types::TunnelParameters, - #[cfg(any(target_os = "linux", target_os = "windows"))] params: &wireguard_types::TunnelParameters, log: Option<path::PathBuf>, args: TunnelArgs<'_>, ) -> Result<Self> { - let monitor = talpid_wireguard::WireguardMonitor::start(params, log.as_deref(), args)?; + let monitor = talpid_wireguard::WireguardMonitor::start(params, args, log.as_deref())?; Ok(TunnelMonitor { monitor: InternalTunnelMonitor::Wireguard(monitor), }) diff --git a/talpid-tunnel/Cargo.toml b/talpid-tunnel/Cargo.toml index 542e30fb0f..1157ce54a3 100644 --- a/talpid-tunnel/Cargo.toml +++ b/talpid-tunnel/Cargo.toml @@ -10,6 +10,9 @@ rust-version.workspace = true [lints] workspace = true +[features] +boringtun = ["dep:tun07"] + [dependencies] thiserror = { workspace = true } cfg-if = "1.0" @@ -19,12 +22,17 @@ talpid-types = { path = "../talpid-types" } futures = { workspace = true } tokio = { workspace = true, features = ["process", "rt-multi-thread", "fs"] } +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] +tun = { workspace = true } # use tun 0.5.5 for wireguard-go + [target.'cfg(target_os = "android")'.dependencies] jnix = { version = "0.5.1", features = ["derive"] } log = { workspace = true } -[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] -tun = { workspace = true } +[target.'cfg(not(target_os = "android"))'.dependencies] +tun07 = { package = "tun", version = "0.7.11", optional = true, features = [ + "async", +] } [target.'cfg(windows)'.dependencies] talpid-windows = { path = "../talpid-windows" } @@ -32,7 +40,7 @@ talpid-windows = { path = "../talpid-windows" } [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ - "Win32_Foundation", - "Win32_Networking_WinSock", - "Win32_NetworkManagement_Ndis", + "Win32_Foundation", + "Win32_Networking_WinSock", + "Win32_NetworkManagement_Ndis", ] diff --git a/talpid-tunnel/src/tun_provider/mod.rs b/talpid-tunnel/src/tun_provider/mod.rs index d49b76147b..8942186e07 100644 --- a/talpid-tunnel/src/tun_provider/mod.rs +++ b/talpid-tunnel/src/tun_provider/mod.rs @@ -24,6 +24,14 @@ cfg_if! { pub type Tun = UnixTun; pub type TunProvider = UnixTunProvider; + } else if #[cfg(all(windows, feature = "boringtun"))] { + #[path = "windows.rs"] + mod imp; + use self::imp::{WindowsTun, WindowsTunProvider}; + pub use self::imp::Error; + + pub type Tun = WindowsTun; + pub type TunProvider = WindowsTunProvider; } else { mod stub; use self::stub::StubTunProvider; @@ -40,6 +48,10 @@ pub struct TunConfig { #[cfg(target_os = "linux")] pub name: Option<String>, + /// Whether to enable the packet_information option on the tun device. + #[cfg(target_os = "linux")] + pub packet_information: bool, + /// IP addresses for the tunnel interface. pub addresses: Vec<IpAddr>, @@ -95,6 +107,8 @@ pub fn blocking_config() -> TunConfig { TunConfig { #[cfg(target_os = "linux")] name: None, + #[cfg(target_os = "linux")] + packet_information: false, addresses: vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))], mtu: 1380, ipv4_gateway: Ipv4Addr::new(10, 64, 0, 1), diff --git a/talpid-tunnel/src/tun_provider/stub.rs b/talpid-tunnel/src/tun_provider/stub.rs index b5f779036e..6fa4436c51 100644 --- a/talpid-tunnel/src/tun_provider/stub.rs +++ b/talpid-tunnel/src/tun_provider/stub.rs @@ -1,7 +1,12 @@ use super::TunConfig; +#[derive(Debug, thiserror::Error)] /// Error stub. -pub enum Error {} +pub enum Error { + /// IO error + #[error("IO error")] + Io(#[from] std::io::Error), +} /// Factory stub of tunnel devices. pub struct StubTunProvider; diff --git a/talpid-tunnel/src/tun_provider/unix.rs b/talpid-tunnel/src/tun_provider/unix.rs index 0cd6d8f7be..0cee5328c0 100644 --- a/talpid-tunnel/src/tun_provider/unix.rs +++ b/talpid-tunnel/src/tun_provider/unix.rs @@ -1,189 +1,393 @@ -use super::TunConfig; -use std::{ - net::IpAddr, - ops::Deref, - os::unix::io::{AsRawFd, RawFd}, - process::Command, -}; -use tun::{Configuration, Device}; +#[cfg(not(feature = "boringtun"))] +pub use tun05_imp::{Error, UnixTun, UnixTunProvider}; +#[cfg(feature = "boringtun")] +pub use tun07_imp::{Error, UnixTun, UnixTunProvider}; +#[cfg(not(feature = "boringtun"))] +mod tun05_imp { + use std::{ + net::IpAddr, + ops::Deref, + os::unix::io::{AsRawFd, RawFd}, + process::Command, + }; + use tun::{Configuration, Device}; -/// Errors that can occur while setting up a tunnel device. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Failed to set IPv4 address on tunnel device - #[error("Failed to set IPv4 address")] - SetIpv4(#[source] tun::Error), + use crate::tun_provider::TunConfig; - /// Failed to set IPv6 address on tunnel device - #[error("Failed to set IPv6 address")] - SetIpv6(#[source] std::io::Error), + /// Errors that can occur while setting up a tunnel device. + #[derive(Debug, thiserror::Error)] + pub enum Error { + /// Failed to set IPv4 address on tunnel device + #[error("Failed to set IPv4 address")] + SetIpv4(#[source] tun::Error), - /// Unable to open a tunnel device - #[error("Unable to open a tunnel device")] - CreateDevice(#[source] tun::Error), + /// Failed to set IPv6 address on tunnel device + #[error("Failed to set IPv6 address")] + SetIpv6(#[source] std::io::Error), - /// Failed to enable/disable link device - #[error("Failed to enable/disable link device")] - ToggleDevice(#[source] tun::Error), + /// Unable to open a tunnel device + #[error("Unable to open a tunnel device")] + CreateDevice(#[source] tun::Error), - /// Failed to get device name - #[error("Failed to get tunnel device name")] - GetDeviceName(#[source] tun::Error), -} - -/// Factory of tunnel devices on Unix systems. -pub struct UnixTunProvider { - config: TunConfig, -} + /// Failed to enable/disable link device + #[error("Failed to enable/disable link device")] + ToggleDevice(#[source] tun::Error), -impl UnixTunProvider { - pub const fn new(config: TunConfig) -> Self { - UnixTunProvider { config } + /// Failed to get device name + #[error("Failed to get tunnel device name")] + GetDeviceName(#[source] tun::Error), } - /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to - /// take effect. - pub fn config_mut(&mut self) -> &mut TunConfig { - &mut self.config + /// Factory of tunnel devices on Unix systems. + pub struct UnixTunProvider { + config: TunConfig, } - /// Open a tunnel using the current tunnel config. - pub fn open_tun(&mut self) -> Result<UnixTun, Error> { - let mut tunnel_device = { - #[allow(unused_mut)] - let mut builder = TunnelDeviceBuilder::default(); - #[cfg(target_os = "linux")] - { - builder.enable_packet_information(); - if let Some(ref name) = self.config.name { - builder.name(name); + impl UnixTunProvider { + pub const fn new(config: TunConfig) -> Self { + UnixTunProvider { config } + } + + /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to + /// take effect. + pub fn config_mut(&mut self) -> &mut TunConfig { + &mut self.config + } + + /// Open a tunnel using the current tunnel config. + pub fn open_tun(&mut self) -> Result<UnixTun, Error> { + let mut tunnel_device = { + #[allow(unused_mut)] + let mut builder = TunnelDeviceBuilder::default(); + #[cfg(target_os = "linux")] + { + builder.enable_packet_information(); + if let Some(ref name) = self.config.name { + builder.name(name); + } } + builder.create()? + }; + + for ip in self.config.addresses.iter() { + tunnel_device.set_ip(*ip)?; } - builder.create()? - }; - for ip in self.config.addresses.iter() { - tunnel_device.set_ip(*ip)?; + tunnel_device.set_up(true)?; + + Ok(UnixTun(tunnel_device)) } + } - tunnel_device.set_up(true)?; + /// Generic tunnel device. + /// + /// Contains the file descriptor representing the device. + pub struct UnixTun(TunnelDevice); - Ok(UnixTun(tunnel_device)) + impl UnixTun { + /// Retrieve the tunnel interface name. + pub fn interface_name(&self) -> Result<String, Error> { + self.get_name() + } } -} -/// Generic tunnel device. -/// -/// Contains the file descriptor representing the device. -pub struct UnixTun(TunnelDevice); + impl Deref for UnixTun { + type Target = TunnelDevice; -impl UnixTun { - /// Retrieve the tunnel interface name. - pub fn interface_name(&self) -> Result<String, Error> { - self.get_name() + fn deref(&self) -> &Self::Target { + &self.0 + } } -} -impl Deref for UnixTun { - type Target = TunnelDevice; + /// A tunnel device + pub struct TunnelDevice { + dev: tun::AsyncDevice, + } - fn deref(&self) -> &Self::Target { - &self.0 + /// A tunnel device builder. + /// + /// Call [`Self::create`] to create [`TunnelDevice`] from the config. + #[derive(Default)] + pub struct TunnelDeviceBuilder { + config: Configuration, } -} -/// A tunnel device -pub struct TunnelDevice { - dev: tun::AsyncDevice, -} + impl TunnelDeviceBuilder { + /// Create a [`TunnelDevice`] from this builder. + pub fn create(self) -> Result<TunnelDevice, Error> { + let dev = tun::create_as_async(&self.config).map_err(Error::CreateDevice)?; + Ok(TunnelDevice { dev }) + } -/// A tunnel device builder. -/// -/// Call [`Self::create`] to create [`TunnelDevice`] from the config. -#[derive(Default)] -pub struct TunnelDeviceBuilder { - config: Configuration, -} + /// Set a custom name for this tunnel device. + #[cfg(target_os = "linux")] + pub fn name(&mut self, name: &str) -> &mut Self { + self.config.name(name); + self + } -impl TunnelDeviceBuilder { - /// Create a [`TunnelDevice`] from this builder. - pub fn create(self) -> Result<TunnelDevice, Error> { - let dev = tun::create_as_async(&self.config).map_err(Error::CreateDevice)?; - Ok(TunnelDevice { dev }) + /// Enable packet information. + /// When enabled the first 4 bytes of each packet is a header with flags and protocol type. + #[cfg(target_os = "linux")] + pub fn enable_packet_information(&mut self) -> &mut Self { + self.config.platform(|config| { + #[allow(deprecated)] + // NOTE: This function does seemingly have an effect on Linux, despite what the deprecation + // warning says. + config.packet_information(true); + }); + self + } } - /// Set a custom name for this tunnel device. - #[cfg(target_os = "linux")] - pub fn name(&mut self, name: &str) -> &mut Self { - self.config.name(name); - self + impl AsRawFd for TunnelDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.get_ref().as_raw_fd() + } } - /// Enable packet information. - /// When enabled the first 4 bytes of each packet is a header with flags and protocol type. - #[cfg(target_os = "linux")] - pub fn enable_packet_information(&mut self) -> &mut Self { - self.config.platform(|config| { - #[allow(deprecated)] - // NOTE: This function does seemingly have an effect on Linux, despite what the deprecation - // warning says. - config.packet_information(true); - }); - self + impl TunnelDevice { + fn set_ip(&mut self, ip: IpAddr) -> Result<(), Error> { + match ip { + IpAddr::V4(ipv4) => { + self.dev + .get_mut() + .set_address(ipv4) + .map_err(Error::SetIpv4)?; + } + + // NOTE: On MacOs, As of `tun 0.7`, `Device::set_address` accepts an `IpAddr` but + // only supports the `IpAddr::V4` address kind and panics if you pass it an + // `IpAddr::V6` value. + #[cfg(target_os = "macos")] + IpAddr::V6(ipv6) => { + // ifconfig <device> inet6 <ipv6 address> alias + let ipv6 = ipv6.to_string(); + let device = self.dev.get_ref().name(); + Command::new("ifconfig") + .args([device, "inet6", &ipv6, "alias"]) + .output() + .map_err(Error::SetIpv6)?; + } + + // NOTE: On Linux, As of `tun 0.7`, `Device::set_address` throws an I/O error if you + // pass it an IPv6-address. + #[cfg(target_os = "linux")] + IpAddr::V6(ipv6) => { + // ip -6 addr add <ipv6 address> dev <device> + let ipv6 = ipv6.to_string(); + let device = self.dev.get_ref().name(); + Command::new("ip") + .args(["-6", "addr", "add", &ipv6, "dev", device]) + .output() + .map_err(Error::SetIpv6)?; + } + } + Ok(()) + } + + fn set_up(&mut self, up: bool) -> Result<(), Error> { + self.dev.get_mut().enabled(up).map_err(Error::ToggleDevice) + } + + fn get_name(&self) -> Result<String, Error> { + Ok(self.dev.get_ref().name().to_owned()) + } } } -impl AsRawFd for TunnelDevice { - fn as_raw_fd(&self) -> RawFd { - self.dev.get_ref().as_raw_fd() +#[cfg(feature = "boringtun")] +mod tun07_imp { + use std::net::IpAddr; + use std::os::fd::{AsRawFd, RawFd}; + use std::process::Command; + + use std::ops::Deref; + + use tun07::{AbstractDevice, AsyncDevice}; + + use crate::tun_provider::TunConfig; + + /// Errors that can occur while setting up a tunnel device. + #[derive(Debug, thiserror::Error)] + pub enum Error { + /// Failed to set IPv4 address on tunnel device + #[error("Failed to set IPv4 address")] + SetIpv4(#[source] tun07::Error), + + /// Failed to set IPv6 address on tunnel device + #[error("Failed to set IPv6 address")] + SetIpv6(#[source] std::io::Error), + + /// Unable to open a tunnel device + #[error("Unable to open a tunnel device")] + CreateDevice(#[source] tun07::Error), + + /// Failed to enable/disable link device + #[error("Failed to enable/disable link device")] + ToggleDevice(#[source] tun07::Error), + + /// Failed to get device name + #[error("Failed to get tunnel device name")] + GetDeviceName(#[source] tun07::Error), } -} -impl TunnelDevice { - fn set_ip(&mut self, ip: IpAddr) -> Result<(), Error> { - match ip { - IpAddr::V4(ipv4) => { - self.dev - .get_mut() - .set_address(ipv4) - .map_err(Error::SetIpv4)?; - } + /// Factory of tunnel devices on Unix systems. + pub struct UnixTunProvider { + pub(crate) config: TunConfig, + } - // NOTE: On MacOs, As of `tun 0.7`, `Device::set_address` accepts an `IpAddr` but - // only supports the `IpAddr::V4` address kind and panics if you pass it an - // `IpAddr::V6` value. - #[cfg(target_os = "macos")] - IpAddr::V6(ipv6) => { - // ifconfig <device> inet6 <ipv6 address> alias - let ipv6 = ipv6.to_string(); - let device = self.dev.get_ref().name(); - Command::new("ifconfig") - .args([device, "inet6", &ipv6, "alias"]) - .output() - .map_err(Error::SetIpv6)?; - } + impl UnixTunProvider { + pub const fn new(config: TunConfig) -> Self { + UnixTunProvider { config } + } + + /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to + /// take effect. + pub fn config_mut(&mut self) -> &mut TunConfig { + &mut self.config + } + + /// Open a tunnel using the current tunnel config. + pub fn open_tun(&mut self) -> Result<UnixTun, Error> { + let mut tunnel_device = { + #[allow(unused_mut)] + let mut builder = TunnelDeviceBuilder::default(); + #[cfg(target_os = "linux")] + { + if self.config.packet_information { + builder.enable_packet_information(); + } - // NOTE: On Linux, As of `tun 0.7`, `Device::set_address` throws an I/O error if you - // pass it an IPv6-address. - #[cfg(target_os = "linux")] - IpAddr::V6(ipv6) => { - // ip -6 addr add <ipv6 address> dev <device> - let ipv6 = ipv6.to_string(); - let device = self.dev.get_ref().name(); - Command::new("ip") - .args(["-6", "addr", "add", &ipv6, "dev", device]) - .output() - .map_err(Error::SetIpv6)?; + if let Some(ref name) = self.config.name { + builder.name(name); + } + } + builder.create()? + }; + + for ip in self.config.addresses.iter() { + tunnel_device.set_ip(*ip)?; } + + tunnel_device.set_up(true)?; + + Ok(UnixTun(tunnel_device)) + } + } + + /// Generic tunnel device. + /// + /// Contains the file descriptor representing the device. + pub struct UnixTun(TunnelDevice); + + impl UnixTun { + /// Retrieve the tunnel interface name. + pub fn interface_name(&self) -> Result<String, Error> { + self.get_name() } - Ok(()) + + pub fn into_inner(self) -> TunnelDevice { + self.0 + } + } + + impl Deref for UnixTun { + type Target = TunnelDevice; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + /// A tunnel device + pub struct TunnelDevice { + pub(crate) dev: tun07::AsyncDevice, } - fn set_up(&mut self, up: bool) -> Result<(), Error> { - self.dev.get_mut().enabled(up).map_err(Error::ToggleDevice) + /// A tunnel device builder. + /// + /// Call [`Self::create`] to create [`TunnelDevice`] from the config. + #[derive(Default)] + pub struct TunnelDeviceBuilder { + pub(crate) config: tun07::Configuration, } - fn get_name(&self) -> Result<String, Error> { - Ok(self.dev.get_ref().name().to_owned()) + impl TunnelDeviceBuilder { + /// Create a [`TunnelDevice`] from this builder. + pub fn create(self) -> Result<TunnelDevice, Error> { + let dev = tun07::create_as_async(&self.config).map_err(Error::CreateDevice)?; + Ok(TunnelDevice { dev }) + } + + /// Set a custom name for this tunnel device. + #[cfg(target_os = "linux")] + pub fn name(&mut self, name: &str) -> &mut Self { + self.config.tun_name(name); + self + } + + /// Enable packet information. + /// When enabled the first 4 bytes of each packet is a header with flags and protocol type. + #[cfg(target_os = "linux")] + pub fn enable_packet_information(&mut self) -> &mut Self { + self.config.platform_config(|config| { + #[allow(deprecated)] + // NOTE: This function does seemingly have an effect on Linux, despite what the deprecation + // warning says. + config.packet_information(true); + }); + self + } + } + + impl AsRawFd for TunnelDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } + } + + impl TunnelDevice { + pub(crate) fn set_ip(&mut self, ip: IpAddr) -> Result<(), Error> { + match ip { + IpAddr::V4(ipv4) => { + self.dev.set_address(ipv4.into()).map_err(Error::SetIpv4)?; + } + + // NOTE: As of `tun 0.7`, `Device::set_address` accepts an `IpAddr` but + // only supports the `IpAddr::V4`. + // On MacOs, `Device::set_address` panics if you pass it an `IpAddr::V6` value. + // On Linux, `Device::set_address` throws an I/O error if you pass it an IPv6-address. + IpAddr::V6(ipv6) => { + let ipv6 = ipv6.to_string(); + let device = self.get_name()?; + // ifconfig <device> inet6 <ipv6 address> alias + #[cfg(target_os = "macos")] + Command::new("ifconfig") + .args([&device, "inet6", &ipv6, "alias"]) + .output() + .map_err(Error::SetIpv6)?; + // ip -6 addr add <ipv6 address> dev <device> + #[cfg(target_os = "linux")] + Command::new("ip") + .args(["-6", "addr", "add", &ipv6, "dev", &device]) + .output() + .map_err(Error::SetIpv6)?; + } + } + Ok(()) + } + + pub(crate) fn set_up(&mut self, up: bool) -> Result<(), Error> { + self.dev.enabled(up).map_err(Error::ToggleDevice) + } + + pub(crate) fn get_name(&self) -> Result<String, Error> { + self.dev.tun_name().map_err(Error::GetDeviceName) + } + + pub fn into_inner(self) -> AsyncDevice { + self.dev + } } } diff --git a/talpid-tunnel/src/tun_provider/windows.rs b/talpid-tunnel/src/tun_provider/windows.rs new file mode 100644 index 0000000000..646cf70125 --- /dev/null +++ b/talpid-tunnel/src/tun_provider/windows.rs @@ -0,0 +1,141 @@ +use super::TunConfig; +use std::{io, net::IpAddr, ops::Deref}; +use tun07 as tun; +use tun07::{AbstractDevice, AsyncDevice, Configuration}; + +/// Errors that can occur while setting up a tunnel device. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Failed to set IP address + #[error("Failed to set IPv4 address")] + SetIpv4(#[source] tun::Error), + + /// Failed to set IP address + #[error("Failed to set IPv6 address")] + SetIpv6(#[source] io::Error), + + /// Unable to open a tunnel device + #[error("Unable to open a tunnel device")] + CreateDevice(#[source] tun::Error), + + /// Failed to enable/disable link device + #[error("Failed to enable/disable link device")] + ToggleDevice(#[source] tun::Error), + + /// Failed to get device name + #[error("Failed to get tunnel device name")] + GetDeviceName(#[source] tun::Error), + + /// IO error + #[error("IO error")] + Io(#[from] io::Error), +} + +/// Factory of tunnel devices on Unix systems. +pub struct WindowsTunProvider { + config: TunConfig, +} + +impl WindowsTunProvider { + pub const fn new(config: TunConfig) -> Self { + WindowsTunProvider { config } + } + + /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to + /// take effect. + pub fn config_mut(&mut self) -> &mut TunConfig { + &mut self.config + } + + /// Open a tunnel using the current tunnel config. + pub fn open_tun(&mut self) -> Result<WindowsTun, Error> { + let mut tunnel_device = { + #[allow(unused_mut)] + let mut builder = TunnelDeviceBuilder::default(); + #[cfg(target_os = "linux")] + if let Some(ref name) = self.config.name { + builder.name(name); + } + builder.create()? + }; + + for ip in self.config.addresses.iter() { + tunnel_device.set_ip(*ip)?; + } + + tunnel_device.set_up(true)?; + + Ok(WindowsTun(tunnel_device)) + } +} + +/// Generic tunnel device. +/// +/// Contains the file descriptor representing the device. +pub struct WindowsTun(TunnelDevice); + +impl WindowsTun { + /// Retrieve the tunnel interface name. + pub fn interface_name(&self) -> Result<String, Error> { + self.get_name() + } + + pub fn into_inner(self) -> AsyncDevice { + AsyncDevice::new(self.0.dev).unwrap() + } +} + +impl Deref for WindowsTun { + type Target = TunnelDevice; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A tunnel device +pub struct TunnelDevice { + dev: tun::Device, +} + +/// A tunnel device builder. +/// +/// Call [`Self::create`] to create [`TunnelDevice`] from the config. +pub struct TunnelDeviceBuilder { + config: Configuration, +} + +impl TunnelDeviceBuilder { + /// Create a [`TunnelDevice`] from this builder. + pub fn create(self) -> Result<TunnelDevice, Error> { + let dev = tun::create(&self.config).map_err(Error::CreateDevice)?; + Ok(TunnelDevice { dev }) + } +} + +impl Default for TunnelDeviceBuilder { + fn default() -> Self { + let config = Configuration::default(); + Self { config } + } +} + +impl TunnelDevice { + fn set_ip(&mut self, ip: IpAddr) -> Result<(), Error> { + match ip { + IpAddr::V4(ipv4) => self.dev.set_address(ipv4.into()).map_err(Error::SetIpv4), + IpAddr::V6(_ipv6) => { + // TODO + todo!("ipv6 not implemented"); + } + } + } + + fn set_up(&mut self, up: bool) -> Result<(), Error> { + self.dev.enabled(up).map_err(Error::ToggleDevice) + } + + fn get_name(&self) -> Result<String, Error> { + self.dev.tun_name().map_err(Error::GetDeviceName) + } +} diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 9cac2c2970..b79ec503cd 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -10,6 +10,9 @@ rust-version.workspace = true [lints] workspace = true +[features] +boringtun = ["dep:boringtun", "dep:tun07", "talpid-tunnel/boringtun"] + [dependencies] async-trait = "0.1" thiserror = { workspace = true } @@ -30,12 +33,21 @@ tunnel-obfuscation = { path = "../tunnel-obfuscation" } rand = "0.8.5" surge-ping = "0.8.0" rand_chacha = "0.3.1" -wireguard-go-rs = { path = "../wireguard-go-rs"} +wireguard-go-rs = { path = "../wireguard-go-rs" } +tun07 = { package = "tun", version = "0.7.11", features = [ + "async", +], optional = true } byteorder = "1" internet-checksum = "0.2" socket2 = { workspace = true, features = ["all"] } tokio-stream = { version = "0.1", features = ["io-util"] } +[dependencies.boringtun] +optional = true +features = ["device"] +git = "https://github.com/mullvad/boringtun" +rev = "a7e11fb46d4a" + [target.'cfg(unix)'.dependencies] nix = "0.23" libc = "0.2.150" @@ -61,27 +73,27 @@ maybenot = "2.0.0" [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ - "Win32_Foundation", - "Win32_Globalization", - "Win32_Security", - "Win32_System_Com", - "Win32_System_Diagnostics_ToolHelp", - "Win32_System_Ioctl", - "Win32_System_IO", - "Win32_System_LibraryLoader", - "Win32_System_ProcessStatus", - "Win32_System_Registry", - "Win32_System_Services", - "Win32_System_SystemServices", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", - "Win32_Networking_WinSock", - "Win32_NetworkManagement_IpHelper", - "Win32_NetworkManagement_Ndis", - "Win32_UI_Shell", - "Win32_UI_WindowsAndMessaging", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Security", + "Win32_System_Com", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Ioctl", + "Win32_System_IO", + "Win32_System_LibraryLoader", + "Win32_System_ProcessStatus", + "Win32_System_Registry", + "Win32_System_Services", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_Networking_WinSock", + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] [dev-dependencies] proptest = { workspace = true } -tokio = { workspace = true, features = [ "test-util" ] } +tokio = { workspace = true, features = ["test-util"] } diff --git a/talpid-wireguard/build.rs b/talpid-wireguard/build.rs index 23c2f3bb67..deba05655e 100644 --- a/talpid-wireguard/build.rs +++ b/talpid-wireguard/build.rs @@ -6,9 +6,6 @@ fn main() { if target_os == "windows" { declare_libs_dir("../dist-assets/binaries"); } - // Wireguard-Go can be used on all platforms - println!("cargo::rustc-check-cfg=cfg(wireguard_go)"); - println!("cargo::rustc-cfg=wireguard_go"); // Enable DAITA by default on desktop and android println!("cargo::rustc-check-cfg=cfg(daita)"); diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs new file mode 100644 index 0000000000..7482a26301 --- /dev/null +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -0,0 +1,314 @@ +use crate::{ + config::Config, + stats::{Stats, StatsMap}, + Tunnel, TunnelError, +}; +use boringtun::device::{ + api::{command::*, ApiClient, ApiServer}, + peer::AllowedIP, + DeviceConfig, DeviceHandle, +}; + +#[cfg(not(target_os = "android"))] +use ipnetwork::IpNetwork; +#[cfg(target_os = "android")] +use std::os::fd::AsRawFd; +use std::{ + future::Future, + ops::Deref, + sync::{Arc, Mutex}, +}; +use talpid_tunnel::tun_provider::{self, Tun, TunProvider}; +use talpid_tunnel_config_client::DaitaSettings; +use tun07::AbstractDevice; + +pub struct BoringTun { + device_handle: DeviceHandle, + config_tx: ApiClient, + config: Config, + + /// Name of the tun interface. + interface_name: String, +} + +/// Configure and start a boringtun tunnel. +pub async fn open_boringtun_tunnel( + config: &Config, + tun_provider: Arc<Mutex<tun_provider::TunProvider>>, + #[cfg(target_os = "android")] route_manager_handle: talpid_routing::RouteManagerHandle, +) -> super::Result<BoringTun> { + log::info!("BoringTun::start_tunnel"); + let routes = config.get_tunnel_destinations(); + + log::info!("calling get_tunnel_for_userspace"); + #[cfg(not(target_os = "android"))] + let async_tun = { + let tun = get_tunnel_for_userspace(tun_provider, config, routes)?; + + #[cfg(unix)] + { + tun.into_inner().into_inner() + } + #[cfg(windows)] + { + tun.into_inner() + } + }; + + let (mut config_tx, config_rx) = ApiServer::new(); + + let boringtun_config = DeviceConfig { + n_threads: 4, + api: Some(config_rx), + on_bind: None, + }; + + #[cfg(target_os = "android")] + let mut boringtun_config = boringtun_config; + + #[cfg(target_os = "android")] + let async_tun = { + let _ = routes; // TODO: do we need this? + let (mut tun, fd) = get_tunnel_for_userspace(Arc::clone(&tun_provider), config)?; + let is_new_tunnel = tun.is_new; + + // TODO We should also wait for routes before sending any ping / connectivity check + + // There is a brief period of time between setting up a Wireguard-go tunnel and the tunnel being ready to serve + // traffic. This function blocks until the tunnel starts to serve traffic or until [connectivity::Check] times out. + if is_new_tunnel { + let expected_routes = tun_provider.lock().unwrap().real_routes(); + + route_manager_handle + .clone() + .wait_for_routes(expected_routes) + .await + .map_err(crate::Error::SetupRoutingError) + .map_err(|e| TunnelError::RecoverableStartWireguardError(Box::new(e)))?; + } + + let mut config = tun07::Configuration::default(); + config.raw_fd(fd); + + boringtun_config.on_bind = Some(Box::new(move |socket| { + tun.bypass(socket.as_raw_fd()).unwrap() + })); + + let device = tun07::Device::new(&config).unwrap(); + tun07::AsyncDevice::new(device).unwrap() + }; + + let interface_name = async_tun.deref().tun_name().unwrap(); + + log::info!("passing tunnel dev to boringtun"); + let device_handle: DeviceHandle = DeviceHandle::new(async_tun, boringtun_config) + .await + .map_err(TunnelError::BoringTunDevice)?; + + set_boringtun_config(&mut config_tx, config).await?; + + log::info!( + "This tunnel was brought to you by... +......................................................... +..*...*.. .--. .---. ..*....*. +...*..... | ) o | ......*.. +.*..*..*. |--: .-. .--.. .--. .-..|. . .--. ...*..... +...*..... | )( )| | | |( ||| | | | .*.....*. +*.....*.. '--' `-' ' -' `-' `-`-`|'`--`-' `- .....*... +......... ._.' ..*...*.. +..*...*.............................................*...." + ); + + Ok(BoringTun { + device_handle, + config: config.clone(), + config_tx, + interface_name, + }) +} + +#[async_trait::async_trait] +impl Tunnel for BoringTun { + fn get_interface_name(&self) -> String { + self.interface_name.clone() + } + + fn stop(self: Box<Self>) -> Result<(), TunnelError> { + log::info!("BoringTun::stop"); // remove me + tokio::runtime::Handle::current().block_on(self.device_handle.stop()); + Ok(()) + } + + async fn get_tunnel_stats(&self) -> Result<StatsMap, TunnelError> { + let response = self + .config_tx + .send(Get::default()) + .await + .expect("Failed to get peers"); + + let Response::Get(response) = response else { + return Err(TunnelError::GetConfigError); + }; + Ok(StatsMap::from_iter(response.peers.into_iter().map( + |peer| { + ( + peer.peer.public_key.0, + Stats { + tx_bytes: peer.tx_bytes.unwrap_or_default(), + rx_bytes: peer.rx_bytes.unwrap_or_default(), + }, + ) + }, + ))) + } + + fn set_config<'a>( + &'a mut self, + config: Config, + ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), TunnelError>> + Send + 'a>> { + Box::pin(async move { + self.config = config; + set_boringtun_config(&mut self.config_tx, &self.config).await?; + Ok(()) + }) + } + + fn start_daita(&mut self, _settings: DaitaSettings) -> Result<(), TunnelError> { + log::info!("Haha no"); + Ok(()) + } +} + +async fn set_boringtun_config( + tx: &mut ApiClient, + config: &Config, +) -> Result<(), crate::TunnelError> { + log::info!("configuring boringtun device"); + let mut set_cmd = Set::builder() + .private_key(config.tunnel.private_key.to_bytes()) + .listen_port(0u16) + .replace_peers() + .build(); + + #[cfg(target_os = "linux")] + { + set_cmd.fwmark = config.fwmark; + } + + for peer in config.peers() { + let mut boring_peer = Peer::builder() + .public_key(*peer.public_key.as_bytes()) + .endpoint(peer.endpoint) + .allowed_ip( + peer.allowed_ips + .iter() + .map(|net| AllowedIP { + addr: net.ip(), + cidr: net.prefix(), + }) + .collect(), + ) + .build(); + + if let Some(psk) = &peer.psk { + boring_peer.preshared_key = Some(SetUnset::Set((*psk.as_bytes()).into())); + } + + let boring_peer = SetPeer::builder().peer(boring_peer).build(); + + set_cmd.peers.push(boring_peer); + } + + tx.send(set_cmd).await.map_err(|err| { + log::error!("Failed to set boringtun config: {err:#}"); + TunnelError::SetConfigError + })?; + Ok(()) +} + +#[cfg(target_os = "windows")] +fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, + routes: impl Iterator<Item = IpNetwork>, +) -> Result<Tun, crate::TunnelError> { + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.mtu = config.mtu; + + let _ = routes; + + #[cfg(windows)] + tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice) +} + +#[cfg(all(not(target_os = "android"), unix))] +fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, + routes: impl Iterator<Item = IpNetwork>, +) -> Result<Tun, crate::TunnelError> { + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + #[cfg(target_os = "linux")] + { + tun_config.name = Some(crate::config::MULLVAD_INTERFACE_NAME.to_string()); + tun_config.packet_information = false; + } + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.routes = routes.collect(); + tun_config.mtu = config.mtu; + + tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice) +} + +#[cfg(target_os = "android")] +pub fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, +) -> Result<(Tun, std::os::fd::RawFd), TunnelError> { + let mut last_error = None; + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.mtu = config.mtu; + + // Route everything into the tunnel and have wireguard-go act as a firewall when + // blocking. These will not necessarily be the actual routes used by android. Those will + // be generated at a later stage e.g. if Local Network Sharing is enabled. + tun_config.routes = vec!["0.0.0.0/0".parse().unwrap(), "::/0".parse().unwrap()]; + + const MAX_PREPARE_TUN_ATTEMPTS: usize = 4; + + for _ in 1..=MAX_PREPARE_TUN_ATTEMPTS { + let tunnel_device = tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice)?; + + match nix::unistd::dup(tunnel_device.as_raw_fd()) { + Ok(fd) => return Ok((tunnel_device, fd)), + #[cfg(not(target_os = "macos"))] + Err(error @ nix::errno::Errno::EBADFD) => last_error = Some(error), + Err(error @ nix::errno::Errno::EBADF) => last_error = Some(error), + Err(error) => return Err(TunnelError::FdDuplicationError(error)), + } + } + + Err(TunnelError::FdDuplicationError( + last_error.expect("Should be collected in loop"), + )) +} diff --git a/talpid-wireguard/src/connectivity/check.rs b/talpid-wireguard/src/connectivity/check.rs index 9c029948bf..b67c5da497 100644 --- a/talpid-wireguard/src/connectivity/check.rs +++ b/talpid-wireguard/src/connectivity/check.rs @@ -1,18 +1,16 @@ -use std::net::Ipv4Addr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::broadcast; -use tokio::time::Instant; +use std::{ + net::Ipv4Addr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::{sync::broadcast, time::Instant}; -use super::constants::*; -use super::error::Error; -use super::pinger; +use super::{constants::*, error::Error, pinger}; -use crate::stats::StatsMap; -#[cfg(target_os = "android")] -use crate::Tunnel; -use crate::{TunnelError, TunnelType}; +use crate::{stats::StatsMap, Tunnel, TunnelError}; use pinger::Pinger; /// Verifies if a connection to a tunnel is working. @@ -132,7 +130,7 @@ impl Check { // successful at the start of a connection. pub async fn establish_connectivity( &mut self, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { // Send initial ping to prod WireGuard into connecting. self.ping_state @@ -161,7 +159,7 @@ impl Check { timeout_initial: Duration, timeout_multiplier: u32, max_timeout: Duration, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { if self.conn_state.connected() { return Ok(true); @@ -226,7 +224,7 @@ impl Check { pub(crate) async fn check_connectivity( &mut self, now: Instant, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { Self::check_connectivity_interval( &mut self.conn_state, @@ -244,7 +242,7 @@ impl Check { ping_state: &mut PingState, now: Instant, timeout: Duration, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { match Self::get_stats(tunnel_handle) .await @@ -265,7 +263,7 @@ impl Check { /// If None is returned, then the underlying tunnel has already been closed and all subsequent /// calls will also return None. - async fn get_stats(tunnel_handle: &TunnelType) -> Result<Option<StatsMap>, TunnelError> { + async fn get_stats(tunnel_handle: &dyn Tunnel) -> Result<Option<StatsMap>, TunnelError> { let stats = tunnel_handle.get_tunnel_stats().await?; if stats.is_empty() { log::error!("Tunnel unexpectedly shut down"); @@ -604,7 +602,10 @@ mod test { Check::maybe_send_ping(&mut checker.conn_state, &mut checker.ping_state, start) .await .unwrap(); - assert!(!checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(!checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test] @@ -617,7 +618,10 @@ mod test { let start = now.checked_sub(Duration::from_secs(1)).unwrap(); let (mut checker, _cancel_token) = mock_checker(start, Box::new(pinger)); - assert!(!checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(!checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test] @@ -633,7 +637,10 @@ mod test { // Mock the state - connectivity has been established checker.conn_state = connected_state(start); - assert!(checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test(start_paused = true)] @@ -671,7 +678,7 @@ mod test { ESTABLISH_TIMEOUT, ESTABLISH_TIMEOUT_MULTIPLIER, MAX_ESTABLISH_TIMEOUT, - &tunnel, + tunnel.as_ref(), ) .await, ) diff --git a/talpid-wireguard/src/connectivity/mod.rs b/talpid-wireguard/src/connectivity/mod.rs index 2da555ad45..709749abd6 100644 --- a/talpid-wireguard/src/connectivity/mod.rs +++ b/talpid-wireguard/src/connectivity/mod.rs @@ -6,7 +6,7 @@ mod mock; mod monitor; mod pinger; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", not(feature = "boringtun")))] pub use check::CancelReceiver; pub use check::{CancelToken, Check}; pub use error::Error; diff --git a/talpid-wireguard/src/connectivity/monitor.rs b/talpid-wireguard/src/connectivity/monitor.rs index 1272b43f4d..87c9ffacd0 100644 --- a/talpid-wireguard/src/connectivity/monitor.rs +++ b/talpid-wireguard/src/connectivity/monitor.rs @@ -1,12 +1,13 @@ use std::{sync::Weak, time::Duration}; -use tokio::sync::Mutex; -use tokio::time::{Instant, MissedTickBehavior}; +use tokio::{ + sync::Mutex, + time::{Instant, MissedTickBehavior}, +}; use crate::TunnelType; -use super::check::Check; -use super::error::Error; +use super::{check::Check, error::Error}; /// Sleep time used when checking if an established connection is still working. const REGULAR_LOOP_SLEEP: Duration = Duration::from_secs(1); @@ -66,7 +67,7 @@ impl Monitor { }; self.connectivity_check - .check_connectivity(Instant::now(), tunnel) + .check_connectivity(Instant::now(), tunnel.as_ref()) .await } } @@ -75,15 +76,17 @@ impl Monitor { mod test { use super::*; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - use std::time::Duration; + use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, + }; - use tokio::sync::mpsc; - use tokio::sync::Mutex; + use tokio::sync::{mpsc, Mutex}; - use crate::connectivity::constants::*; - use crate::connectivity::mock::*; + use crate::connectivity::{constants::*, mock::*}; #[tokio::test(start_paused = true)] /// Verify that the connectivity monitor doesn't fail if the tunnel constantly sends traffic, @@ -99,7 +102,7 @@ mod test { }; tokio::spawn(async move { - let start_result = checker.establish_connectivity(&tunnel).await; + let start_result = checker.establish_connectivity(tunnel.as_ref()).await; result_tx.send(start_result).await.unwrap(); // Pointer dance let tunnel = Arc::new(Mutex::new(Some(tunnel))); @@ -155,7 +158,7 @@ mod test { let start = now.checked_sub(Duration::from_secs(1)).unwrap(); mock_checker(start, Box::new(pinger)) }; - let start_result = checker.establish_connectivity(&tunnel).await; + let start_result = checker.establish_connectivity(tunnel.as_ref()).await; result_tx.send(start_result).await.unwrap(); // Pointer dance let _tunnel = Arc::new(Mutex::new(Some(tunnel))); diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 1d7f4f3955..b0431b94fd 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -1,8 +1,6 @@ //! This module takes care of obtaining ephemeral peers, updating the WireGuard configuration and //! restarting obfuscation and WG tunnels when necessary. -#[cfg(target_os = "android")] // On Android, the Tunnel trait is not imported by default. -use super::Tunnel; use super::{config::Config, obfuscation::ObfuscatorHandle, CloseMsg, Error, TunnelType}; #[cfg(target_os = "android")] @@ -207,15 +205,15 @@ async fn reconfigure_tunnel( } { let mut shared_tunnel = tunnel.lock().await; - let tunnel = shared_tunnel.take().expect("tunnel was None"); + let mut tunnel = shared_tunnel.take().expect("tunnel was None"); - let updated_tunnel = tunnel - .set_config(&config) + tunnel + .set_config(config.clone()) .await .map_err(Error::TunnelError) .map_err(CloseMsg::SetupError)?; - *shared_tunnel = Some(updated_tunnel); + *shared_tunnel = Some(tunnel); } Ok(config) } diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 308d8c335b..461a18eda4 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -7,29 +7,20 @@ use self::config::Config; use futures::channel::mpsc; use futures::future::Future; use obfuscation::ObfuscatorHandle; -#[cfg(target_os = "android")] -use std::borrow::Cow; #[cfg(windows)] use std::io; use std::{ convert::Infallible, - net::IpAddr, path::Path, pin::Pin, - sync::{mpsc as sync_mpsc, Arc, Mutex}, + sync::{mpsc as sync_mpsc, Arc}, }; -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(not(target_os = "android"))] use std::{env, sync::LazyLock}; #[cfg(not(target_os = "android"))] use talpid_routing::{self, RequiredRoute}; -#[cfg(not(windows))] -use talpid_tunnel::tun_provider; -use talpid_tunnel::{ - tun_provider::TunProvider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata, -}; +use talpid_tunnel::{tun_provider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata}; -#[cfg(target_os = "android")] -use talpid_routing::RouteManagerHandle; #[cfg(daita)] use talpid_tunnel_config_client::DaitaSettings; use talpid_types::{ @@ -38,6 +29,12 @@ use talpid_types::{ }; use tokio::sync::Mutex as AsyncMutex; +#[cfg(feature = "boringtun")] +mod boringtun; + +#[cfg(not(feature = "boringtun"))] +mod wireguard_go; + /// WireGuard config data-types pub mod config; mod connectivity; @@ -45,8 +42,6 @@ mod ephemeral; mod logging; mod obfuscation; mod stats; -#[cfg(wireguard_go)] -mod wireguard_go; #[cfg(target_os = "linux")] pub(crate) mod wireguard_kernel; #[cfg(windows)] @@ -55,14 +50,7 @@ mod wireguard_nt; #[cfg(not(target_os = "android"))] mod mtu_detection; -#[cfg(wireguard_go)] -use self::wireguard_go::WgGoTunnel; - -// On android we only have Wireguard Go tunnel -#[cfg(not(target_os = "android"))] type TunnelType = Box<dyn Tunnel>; -#[cfg(target_os = "android")] -type TunnelType = WgGoTunnel; type Result<T> = std::result::Result<T, Error>; @@ -83,7 +71,7 @@ pub enum Error { /// An interaction with a tunnel failed #[error("Tunnel failed")] - TunnelError(#[source] TunnelError), + TunnelError(#[from] TunnelError), /// Failed to run tunnel obfuscation #[error("Tunnel obfuscation failed")] @@ -122,9 +110,8 @@ impl Error { Error::TunnelError(TunnelError::BypassError(_)) => true, #[cfg(windows)] - _ => self.get_tunnel_device_error().is_some(), + Error::TunnelError(TunnelError::SetupTunnelDevice(_)) => true, - #[cfg(not(windows))] _ => false, } } @@ -133,7 +120,9 @@ impl Error { #[cfg(windows)] pub fn get_tunnel_device_error(&self) -> Option<&io::Error> { match self { - Error::TunnelError(TunnelError::SetupTunnelDevice(error)) => Some(error), + Error::TunnelError(TunnelError::SetupTunnelDevice(tun_provider::Error::Io(error))) => { + Some(error) + } _ => None, } } @@ -151,7 +140,7 @@ pub struct WireguardMonitor { obfuscator: Arc<AsyncMutex<Option<ObfuscatorHandle>>>, } -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(not(target_os = "android"))] /// Overrides the preference for the kernel module for WireGuard. static FORCE_USERSPACE_WIREGUARD: LazyLock<bool> = LazyLock::new(|| { env::var("TALPID_FORCE_USERSPACE_WIREGUARD") @@ -164,8 +153,8 @@ impl WireguardMonitor { #[cfg(not(target_os = "android"))] pub fn start( params: &TunnelParameters, - log_path: Option<&Path>, args: TunnelArgs<'_>, + _log_path: Option<&Path>, ) -> Result<WireguardMonitor> { #[cfg(any(target_os = "windows", target_os = "linux"))] let desired_mtu = args @@ -194,19 +183,27 @@ impl WireguardMonitor { config.mtu = clamp_mtu(params, config.mtu); } + // NOTE: We force userspace WireGuard while boringtun is enabled to more easily test + // the implementation, as DAITA is not currently supported by boringtun. + // TODO: Remove `cfg!(feature = "boringtun")`. + let userspace_wireguard = + *FORCE_USERSPACE_WIREGUARD || config.daita || cfg!(feature = "boringtun"); + #[cfg(target_os = "windows")] let (setup_done_tx, setup_done_rx) = mpsc::channel(0); let tunnel = Self::open_tunnel( args.runtime.clone(), &config, - log_path, #[cfg(target_os = "windows")] args.resource_dir, + #[cfg(not(all(target_os = "windows", not(feature = "boringtun"))))] args.tun_provider.clone(), - #[cfg(target_os = "windows")] + #[cfg(all(windows, not(feature = "boringtun")))] args.route_manager.clone(), #[cfg(target_os = "windows")] setup_done_tx, + userspace_wireguard, + _log_path, )?; let iface_name = tunnel.get_interface_name(); @@ -242,8 +239,15 @@ impl WireguardMonitor { let close_obfs_sender: sync_mpsc::Sender<CloseMsg> = moved_close_obfs_sender; let obfuscator = moved_obfuscator; #[cfg(windows)] - Self::add_device_ip_addresses(&iface_name, &config.tunnel.addresses, setup_done_rx) - .await?; + if cfg!(feature = "boringtun") && userspace_wireguard { + // NOTE: For boringtun, we use the `tun` crate to create our tunnel interface. + // It will automatically configure the IP address and DNS servers using `netsh`. + // This is quite slow, so we need to wait for the interface to be created. + Self::wait_for_ip_addresses(&config, &iface_name).await?; + } else { + Self::add_device_ip_addresses(&iface_name, &config.tunnel.addresses, setup_done_rx) + .await?; + } let metadata = Self::tunnel_metadata(&iface_name, &config); let allowed_traffic = Self::allowed_traffic_during_tunnel_config(&config); @@ -330,7 +334,7 @@ impl WireguardMonitor { let lock = tunnel.lock().await; let borrowed_tun = lock.as_ref().expect("The tunnel was dropped unexpectedly"); match connectivity_monitor - .establish_connectivity(borrowed_tun) + .establish_connectivity(borrowed_tun.as_ref()) .await { Ok(true) => Ok(()), @@ -399,8 +403,8 @@ impl WireguardMonitor { #[cfg(target_os = "android")] pub fn start( params: &TunnelParameters, - log_path: Option<&Path>, args: TunnelArgs<'_>, + #[allow(unused_variables)] log_path: Option<&Path>, ) -> Result<WireguardMonitor> { let desired_mtu = get_desired_mtu(params); let mut config = @@ -425,24 +429,39 @@ impl WireguardMonitor { let should_negotiate_ephemeral_peer = config.quantum_resistant || config.daita; let (cancel_token, cancel_receiver) = connectivity::CancelToken::new(); - let connectivity_check = connectivity::Check::new( + #[allow(unused_mut)] + let mut connectivity_monitor = connectivity::Check::new( config.ipv4_gateway, args.retry_attempt, cancel_receiver.clone(), ) .map_err(Error::ConnectivityMonitorError)?; - let tunnel = args.runtime.block_on(Self::open_wireguard_go_tunnel( - &config, - log_path, - args.tun_provider.clone(), - args.route_manager, - // In case we should negotiate an ephemeral peer, we should specify via AllowedIPs - // that we only allows traffic to/from the gateway. This is only needed on Android - // since we lack a firewall there. - should_negotiate_ephemeral_peer, - cancel_receiver, - ))?; + #[cfg(feature = "boringtun")] + let tunnel = args + .runtime + .block_on(boringtun::open_boringtun_tunnel( + &config, + args.tun_provider.clone(), + args.route_manager, + )) + .map(Box::new)? as Box<dyn Tunnel>; + + #[cfg(not(feature = "boringtun"))] + let tunnel = args + .runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( + &config, + log_path, + args.tun_provider.clone(), + args.route_manager, + // In case we should negotiate an ephemeral peer, we should specify via AllowedIPs + // that we only allows traffic to/from the gateway. This is only needed on Android + // since we lack a firewall there. + should_negotiate_ephemeral_peer, + cancel_receiver, + )) + .map(Box::new)? as Box<dyn Tunnel>; let iface_name = tunnel.get_interface_name(); let tunnel = Arc::new(AsyncMutex::new(Some(tunnel))); @@ -468,6 +487,29 @@ impl WireguardMonitor { .on_event(TunnelEvent::InterfaceUp(metadata.clone(), allowed_traffic)) .await; + #[cfg(feature = "boringtun")] + { + let lock = tunnel.lock().await; + let borrowed_tun = lock.as_ref().expect("The tunnel was dropped unexpectedly"); + match connectivity_monitor + .establish_connectivity(borrowed_tun.as_ref()) + .await + { + Ok(true) => Ok(()), + Ok(false) => { + log::warn!("Timeout while checking tunnel connection"); + Err(CloseMsg::PingErr) + } + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to check tunnel connection") + ); + Err(CloseMsg::PingErr) + } + }?; + } + if should_negotiate_ephemeral_peer { let ephemeral_obfs_sender = close_obfs_sender.clone(); @@ -501,7 +543,7 @@ impl WireguardMonitor { let metadata = Self::tunnel_metadata(&iface_name, &config); event_hook.on_event(TunnelEvent::Up(metadata)).await; - if let Err(error) = connectivity::Monitor::init(connectivity_check) + if let Err(error) = connectivity::Monitor::init(connectivity_monitor) .run(Arc::downgrade(&tunnel)) .await { @@ -561,45 +603,36 @@ impl WireguardMonitor { AllowedTunnelTraffic::All } - /// Replace `0.0.0.0/0`/`::/0` with the gateway IPs when `gateway_only` is true. - /// Used to block traffic to other destinations while connecting on Android. - #[cfg(target_os = "android")] - fn patch_allowed_ips(config: &Config, gateway_only: bool) -> Cow<'_, Config> { - if gateway_only { - let mut patched_config = config.clone(); - let gateway_net_v4 = ipnetwork::IpNetwork::from(IpAddr::from(config.ipv4_gateway)); - let gateway_net_v6 = config - .ipv6_gateway - .map(|net| ipnetwork::IpNetwork::from(IpAddr::from(net))); - for peer in patched_config.peers_mut() { - peer.allowed_ips = peer - .allowed_ips - .iter() - .cloned() - .filter_map(|mut allowed_ip| { - if allowed_ip.prefix() == 0 { - if allowed_ip.is_ipv4() { - allowed_ip = gateway_net_v4; - } else if let Some(net) = gateway_net_v6 { - allowed_ip = net; - } else { - return None; - } - } - Some(allowed_ip) - }) - .collect(); - } - Cow::Owned(patched_config) - } else { - Cow::Borrowed(config) - } + #[cfg(windows)] + async fn wait_for_ip_addresses( + config: &Config, + iface_name: &String, + ) -> std::result::Result<(), CloseMsg> { + log::debug!("Waiting for tunnel IP interfaces to arrive"); + let luid = talpid_windows::net::luid_from_alias(iface_name).map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + talpid_windows::net::wait_for_interfaces(luid, true, config.ipv6_gateway.is_some()) + .await + .map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + talpid_windows::net::wait_for_addresses(luid) + .await + .map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + log::debug!("Done waiting for tunnel IP interfaces to arrive"); + Ok(()) } #[cfg(windows)] async fn add_device_ip_addresses( iface_name: &str, - addresses: &[IpAddr], + addresses: &[std::net::IpAddr], mut setup_done_rx: mpsc::Receiver<std::result::Result<(), BoxedError>>, ) -> std::result::Result<(), CloseMsg> { use futures::StreamExt; @@ -631,27 +664,35 @@ impl WireguardMonitor { Ok(()) } + #[allow(clippy::too_many_arguments)] #[cfg(target_os = "windows")] fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, resource_dir: &Path, - _tun_provider: Arc<Mutex<TunProvider>>, - route_manager: talpid_routing::RouteManagerHandle, + #[cfg(feature = "boringtun")] tun_provider: Arc< + std::sync::Mutex<tun_provider::TunProvider>, + >, + #[cfg(not(feature = "boringtun"))] route_manager: talpid_routing::RouteManagerHandle, setup_done_tx: mpsc::Sender<std::result::Result<(), BoxedError>>, + userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); - let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || config.daita; - if userspace_wireguard { log::debug!("Using userspace WireGuard implementation"); + #[cfg(feature = "boringtun")] let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?; + + #[cfg(not(feature = "boringtun"))] + let tunnel = runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( config, - log_path, + _log_path, setup_done_tx, route_manager, )) @@ -660,7 +701,7 @@ impl WireguardMonitor { } else { log::debug!("Using kernel WireGuard implementation"); - wireguard_nt::WgNtTunnel::start_tunnel(config, log_path, resource_dir, setup_done_tx) + wireguard_nt::WgNtTunnel::start_tunnel(config, _log_path, resource_dir, setup_done_tx) .map(|tun| Box::new(tun) as Box<dyn Tunnel + 'static>) .map_err(Error::TunnelError) } @@ -670,20 +711,27 @@ impl WireguardMonitor { fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, - tun_provider: Arc<Mutex<TunProvider>>, + tun_provider: Arc<std::sync::Mutex<tun_provider::TunProvider>>, + _userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); log::debug!("Using userspace WireGuard implementation"); + #[cfg(not(feature = "boringtun"))] let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( + .block_on(wireguard_go::open_wireguard_go_tunnel( config, - log_path, + _log_path, tun_provider, )) .map(Box::new)?; + + #[cfg(feature = "boringtun")] + let tunnel = runtime + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?; Ok(tunnel) } @@ -691,22 +739,22 @@ impl WireguardMonitor { fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, - tun_provider: Arc<Mutex<TunProvider>>, + tun_provider: Arc<std::sync::Mutex<tun_provider::TunProvider>>, + userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); - let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || config.daita; if userspace_wireguard { log::debug!("Using userspace WireGuard implementation"); - let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( - config, - log_path, - tun_provider, - )) - .map(Box::new)?; + #[cfg(not(feature = "boringtun"))] + let f = wireguard_go::open_wireguard_go_tunnel(config, _log_path, tun_provider); + + #[cfg(feature = "boringtun")] + let f = boringtun::open_boringtun_tunnel(config, tun_provider); + + let tunnel = runtime.block_on(f).map(Box::new)?; Ok(tunnel) } else { let res = if will_nm_manage_dns() { @@ -721,81 +769,27 @@ impl WireguardMonitor { res.or_else(|err| { log::warn!("Failed to initialize kernel WireGuard tunnel, falling back to userspace WireGuard implementation:\n{}",err.display_chain() ); - Ok(runtime - .block_on(Self::open_wireguard_go_tunnel( - config, - log_path, - tun_provider, - )) - .map(Box::new)?) + + #[cfg(not(feature = "boringtun"))] + { + Ok(runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( + config, + _log_path, + tun_provider, + )) + .map(Box::new)?) + } + #[cfg(feature = "boringtun")] + { + Ok(runtime + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?) + } }) } } - /// Configure and start a Wireguard-go tunnel. - #[cfg(wireguard_go)] - #[allow(clippy::unused_async)] - async fn open_wireguard_go_tunnel( - config: &Config, - log_path: Option<&Path>, - #[cfg(unix)] tun_provider: Arc<Mutex<TunProvider>>, - #[cfg(target_os = "android")] route_manager: RouteManagerHandle, - #[cfg(windows)] setup_done_tx: mpsc::Sender<std::result::Result<(), BoxedError>>, - #[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle, - #[cfg(target_os = "android")] gateway_only: bool, - #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, - ) -> Result<WgGoTunnel> { - #[cfg(all(unix, not(target_os = "android")))] - let routes = config.get_tunnel_destinations(); - - #[cfg(all(unix, not(target_os = "android")))] - let tunnel = WgGoTunnel::start_tunnel(config, log_path, tun_provider, routes) - .map_err(Error::TunnelError)?; - - #[cfg(target_os = "windows")] - let tunnel = WgGoTunnel::start_tunnel(config, log_path, route_manager, setup_done_tx) - .await - .map_err(Error::TunnelError)?; - - // Android uses multihop implemented in Mullvad's wireguard-go fork. When negotiating - // with an ephemeral peer, this multihop strategy require us to restart the tunnel - // every time we want to reconfigure it. As such, we will actually start a multihop - // tunnel at a later stage, after we have negotiated with the first ephemeral peer. - // At this point, when the tunnel *is first started*, we establish a regular, singlehop - // tunnel to where the ephemeral peer resides. - // - // Refer to `docs/architecture.md` for details on how to use multihop + PQ. - #[cfg(target_os = "android")] - let config = Self::patch_allowed_ips(config, gateway_only); - - #[cfg(target_os = "android")] - let tunnel = if let Some(exit_peer) = &config.exit_peer { - WgGoTunnel::start_multihop_tunnel( - &config, - exit_peer, - log_path, - tun_provider, - route_manager, - cancel_receiver, - ) - .await - .map_err(Error::TunnelError)? - } else { - WgGoTunnel::start_tunnel( - #[allow(clippy::needless_borrow)] - &config, - log_path, - tun_provider, - route_manager, - cancel_receiver, - ) - .await - .map_err(Error::TunnelError)? - }; - - Ok(tunnel) - } - /// Blocks the current thread until tunnel disconnects pub fn wait(mut self) -> Result<()> { let wait_result = match self.close_msg_receiver.recv() { @@ -837,7 +831,9 @@ impl WireguardMonitor { /// Returns routes to the peer endpoints (through the physical interface). #[cfg_attr(target_os = "linux", allow(unused_variables))] #[cfg(not(target_os = "android"))] - fn get_endpoint_routes(endpoints: &[IpAddr]) -> impl Iterator<Item = RequiredRoute> + '_ { + fn get_endpoint_routes( + endpoints: &[std::net::IpAddr], + ) -> impl Iterator<Item = RequiredRoute> + '_ { #[cfg(target_os = "linux")] { // No need due to policy based routing. @@ -1065,15 +1061,9 @@ pub enum TunnelError { #[error("Failed to duplicate tunnel file descriptor for wireguard-go")] FdDuplicationError(#[source] nix::Error), - /// Failed to setup a tunnel device. - #[cfg(not(windows))] - #[error("Failed to create tunnel device")] - SetupTunnelDevice(#[source] tun_provider::Error), - /// Failed to set up a tunnel device - #[cfg(windows)] - #[error("Failed to create tunnel device")] - SetupTunnelDevice(#[source] io::Error), + #[error("Failed to setup a tunnel device")] + SetupTunnelDevice(#[source] tun_provider::Error), /// Failed to setup a tunnel device. #[cfg(windows)] @@ -1095,6 +1085,7 @@ pub enum TunnelError { InvalidAlias, /// Failure to set up logging + #[cfg(any(windows, not(feature = "boringtun")))] #[error("Failed to set up logging")] LoggingError(#[source] logging::Error), @@ -1107,6 +1098,11 @@ pub enum TunnelError { #[cfg(daita)] #[error("Failed to start DAITA - tunnel implemenation does not support DAITA")] DaitaNotSupported, + + /// BoringTun device error + #[cfg(feature = "boringtun")] + #[error("Boringtun: {0:?}")] + BoringTunDevice(::boringtun::device::Error), } #[cfg(target_os = "linux")] diff --git a/talpid-wireguard/src/logging.rs b/talpid-wireguard/src/logging.rs index dcb33b11e3..61000e7f37 100644 --- a/talpid-wireguard/src/logging.rs +++ b/talpid-wireguard/src/logging.rs @@ -1,3 +1,4 @@ +#![cfg(any(windows, not(feature = "boringtun")))] use parking_lot::Mutex; use std::{collections::HashMap, fmt, fs, io::Write, path::Path, sync::LazyLock}; @@ -44,12 +45,12 @@ pub fn clean_up_logging(ordinal: u64) { state.map.remove(&ordinal); } +#[allow(dead_code)] pub enum LogLevel { - #[cfg_attr(windows, allow(dead_code))] Verbose, - #[cfg_attr(wireguard_go, allow(dead_code))] + #[cfg_attr(not(feature = "boringtun"), allow(dead_code))] Info, - #[cfg_attr(wireguard_go, allow(dead_code))] + #[cfg_attr(not(feature = "boringtun"), allow(dead_code))] Warning, Error, } diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index cf99b8c50e..ced16ab9d3 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -13,6 +13,8 @@ use crate::connectivity; use crate::logging::{clean_up_logging, initialize_logging}; #[cfg(all(unix, not(target_os = "android")))] use ipnetwork::IpNetwork; +#[cfg(target_os = "android")] +use std::borrow::Cow; #[cfg(daita)] use std::ffi::CString; #[cfg(unix)] @@ -67,105 +69,191 @@ impl Drop for LoggingContext { } } -#[cfg(not(target_os = "android"))] -pub struct WgGoTunnel(WgGoTunnelState); +pub struct WgGoTunnel { + // This should never be [None] _unless_ we have just called [Self::stop] and + // we're restarting the tunnel. + inner: Option<WgGoTunnelState>, + #[cfg(target_os = "android")] + r#type: Circuit, +} #[cfg(target_os = "android")] -pub enum WgGoTunnel { - Multihop(WgGoTunnelState), - Singlehop(WgGoTunnelState), +#[derive(Clone, Copy, Debug)] +enum Circuit { + Singlehop, + Multihop, } -#[cfg(not(target_os = "android"))] -impl WgGoTunnel { - fn into_state(self) -> WgGoTunnelState { - self.0 - } +/// Configure and start a Wireguard-go tunnel. +#[allow(clippy::unused_async)] +pub(crate) async fn open_wireguard_go_tunnel( + config: &Config, + log_path: Option<&Path>, + #[cfg(unix)] tun_provider: Arc<std::sync::Mutex<talpid_tunnel::tun_provider::TunProvider>>, + #[cfg(target_os = "android")] route_manager: RouteManagerHandle, + #[cfg(windows)] setup_done_tx: futures::channel::mpsc::Sender< + std::result::Result<(), BoxedError>, + >, + #[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle, + #[cfg(target_os = "android")] gateway_only: bool, + #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, +) -> Result<WgGoTunnel> { + #[cfg(all(unix, not(target_os = "android")))] + let routes = config.get_tunnel_destinations(); - fn as_state(&self) -> &WgGoTunnelState { - &self.0 - } + #[cfg(all(unix, not(target_os = "android")))] + let tunnel = WgGoTunnel::start_tunnel(config, log_path, tun_provider, routes)?; - fn as_state_mut(&mut self) -> &mut WgGoTunnelState { - &mut self.0 - } + #[cfg(target_os = "windows")] + let tunnel = WgGoTunnel::start_tunnel(config, log_path, route_manager, setup_done_tx).await?; + + // Android uses multihop implemented in Mullvad's wireguard-go fork. When negotiating + // with an ephemeral peer, this multihop strategy require us to restart the tunnel + // every time we want to reconfigure it. As such, we will actually start a multihop + // tunnel at a later stage, after we have negotiated with the first ephemeral peer. + // At this point, when the tunnel *is first started*, we establish a regular, singlehop + // tunnel to where the ephemeral peer resides. + // + // Refer to `docs/architecture.md` for details on how to use multihop + PQ. + #[cfg(target_os = "android")] + let config = patch_allowed_ips(config, gateway_only); + + #[cfg(target_os = "android")] + let tunnel = if let Some(exit_peer) = &config.exit_peer { + WgGoTunnel::start_multihop_tunnel( + &config, + exit_peer, + log_path, + tun_provider, + route_manager, + cancel_receiver, + ) + .await? + } else { + WgGoTunnel::start_tunnel( + #[allow(clippy::needless_borrow)] + &config, + log_path, + tun_provider, + route_manager, + cancel_receiver, + ) + .await? + }; + + Ok(tunnel) } +/// Replace `0.0.0.0/0`/`::/0` with the gateway IPs when `gateway_only` is true. +/// Used to block traffic to other destinations while connecting on Android. #[cfg(target_os = "android")] -impl WgGoTunnel { - fn into_state(self) -> WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, +fn patch_allowed_ips(config: &Config, gateway_only: bool) -> Cow<'_, Config> { + use std::net::IpAddr; + + if gateway_only { + let mut patched_config = config.clone(); + let gateway_net_v4 = + ipnetwork::IpNetwork::from(std::net::IpAddr::from(config.ipv4_gateway)); + let gateway_net_v6 = config + .ipv6_gateway + .map(|net| ipnetwork::IpNetwork::from(IpAddr::from(net))); + for peer in patched_config.peers_mut() { + peer.allowed_ips = peer + .allowed_ips + .iter() + .cloned() + .filter_map(|mut allowed_ip| { + if allowed_ip.prefix() == 0 { + if allowed_ip.is_ipv4() { + allowed_ip = gateway_net_v4; + } else if let Some(net) = gateway_net_v6 { + allowed_ip = net; + } else { + return None; + } + } + Some(allowed_ip) + }) + .collect(); } + Cow::Owned(patched_config) + } else { + Cow::Borrowed(config) } +} - fn as_state(&self) -> &WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, - } +impl WgGoTunnel { + fn handle(&self) -> &WgGoTunnelState { + debug_assert!(&self.inner.is_some()); + self.inner.as_ref().unwrap() + } + + fn handle_mut(&mut self) -> &mut WgGoTunnelState { + debug_assert!(&self.inner.is_some()); + self.inner.as_mut().unwrap() } - fn as_state_mut(&mut self) -> &mut WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, + fn stop(&mut self) -> Result<()> { + if let Some(tunnel) = self.inner.take() { + tunnel + .tunnel_handle + .turn_off() + .map_err(|e| TunnelError::StopWireguardError(Box::new(e)))?; } + Ok(()) + } + + #[cfg(not(target_os = "android"))] + #[allow(clippy::unused_async)] + async fn set_config(&mut self, config: Config) -> Result<()> { + self.handle_mut().set_config(config) } - pub async fn set_config(self, config: &Config) -> Result<Self> { - let state = self.as_state(); - let log_path = state._logging_context.path.clone(); - let cancel_receiver = state.cancel_receiver.clone(); - let tun_provider = Arc::clone(&state.tun_provider); - let route_manager = state.route_manager.clone(); + #[cfg(target_os = "android")] + pub async fn set_config(&mut self, config: Config) -> Result<()> { + let log_path = self.handle()._logging_context.path.clone(); + let cancel_receiver = self.handle().cancel_receiver.clone(); + let tun_provider = Arc::clone(&self.handle().tun_provider); + let route_manager = self.handle().route_manager.clone(); - match self { - WgGoTunnel::Multihop(state) if !config.is_multihop() => { - state.stop()?; - Self::start_tunnel( - config, + match self.r#type { + Circuit::Multihop if !config.is_multihop() => { + self.stop()?; + *self = Self::start_tunnel( + &config, log_path.as_deref(), tun_provider, route_manager, cancel_receiver, ) - .await + .await?; } - WgGoTunnel::Singlehop(state) if config.is_multihop() => { - state.stop()?; - Self::start_multihop_tunnel( - config, + Circuit::Singlehop if config.is_multihop() => { + self.stop()?; + *self = Self::start_multihop_tunnel( + &config, &config.exit_peer.clone().unwrap().clone(), log_path.as_deref(), tun_provider, route_manager, cancel_receiver, ) - .await + .await?; } - WgGoTunnel::Singlehop(mut state) => { - state.set_config(config.clone())?; - let new_state = WgGoTunnel::Singlehop(state); + Circuit::Singlehop => { + self.handle_mut().set_config(config)?; // HACK: Check if the tunnel is working by sending a ping in the tunnel. // This check is needed for PQ connections to be established. - new_state.ensure_tunnel_is_running().await?; - Ok(new_state) + self.ensure_tunnel_is_running().await?; } - WgGoTunnel::Multihop(mut state) => { - state.set_config(config.clone())?; - let new_state = WgGoTunnel::Multihop(state); + Circuit::Multihop => { + self.handle_mut().set_config(config)?; // HACK: Check if the tunnel is working by sending a ping in the tunnel. // This check is needed for PQ connections to be established. - new_state.ensure_tunnel_is_running().await?; - Ok(new_state) + self.ensure_tunnel_is_running().await?; } - } - } - - pub fn stop(self) -> Result<()> { - self.into_state().stop() + }; + Ok(()) } } @@ -194,12 +282,6 @@ pub(crate) struct WgGoTunnelState { } impl WgGoTunnelState { - fn stop(self) -> Result<()> { - self.tunnel_handle - .turn_off() - .map_err(|e| TunnelError::StopWireguardError(Box::new(e))) - } - fn set_config(&mut self, config: Config) -> Result<()> { let wg_config_str = config.to_userspace_format(); @@ -231,7 +313,7 @@ impl WgGoTunnelState { impl WgGoTunnel { #[cfg(any(target_os = "linux", target_os = "macos"))] - pub fn start_tunnel( + fn start_tunnel( config: &Config, log_path: Option<&Path>, tun_provider: Arc<Mutex<TunProvider>>, @@ -258,14 +340,18 @@ impl WgGoTunnel { ) .map_err(|e| TunnelError::FatalStartWireguardError(Box::new(e)))?; - Ok(WgGoTunnel(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, _logging_context: logging_context, #[cfg(daita)] config: config.clone(), - })) + }; + + Ok(WgGoTunnel { + inner: Some(tunnel), + }) } #[cfg(target_os = "windows")] @@ -329,14 +415,16 @@ impl WgGoTunnel { let interface_name = handle.name(); - Ok(WgGoTunnel(WgGoTunnelState { - interface_name: interface_name.to_owned(), - tunnel_handle: handle, - _logging_context: logging_context, - _socket_update_cb: socket_update_cb, - #[cfg(daita)] - config: config.clone(), - })) + Ok(WgGoTunnel { + inner: Some(WgGoTunnelState { + interface_name: interface_name.to_owned(), + tunnel_handle: handle, + _logging_context: logging_context, + _socket_update_cb: socket_update_cb, + #[cfg(daita)] + config: config.clone(), + }), + }) } // Callback to be used to rebind the tunnel sockets when the default route changes @@ -367,6 +455,7 @@ impl WgGoTunnel { #[cfg(target_os = "linux")] { tun_config.name = Some(MULLVAD_INTERFACE_NAME.to_string()); + tun_config.packet_information = true; } tun_config.addresses = config.tunnel.addresses.clone(); tun_config.ipv4_gateway = config.ipv4_gateway; @@ -449,7 +538,7 @@ impl WgGoTunnel { Self::bypass_tunnel_sockets(&handle, &mut tunnel_device) .map_err(TunnelError::BypassError)?; - let tunnel = WgGoTunnel::Singlehop(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, @@ -459,7 +548,11 @@ impl WgGoTunnel { #[cfg(daita)] config: config.clone(), cancel_receiver, - }); + }; + let tunnel = Self { + inner: Some(tunnel), + r#type: Circuit::Singlehop, + }; if is_new_tunnel { tunnel.wait_for_routes().await?; @@ -527,7 +620,7 @@ impl WgGoTunnel { Self::bypass_tunnel_sockets(&handle, &mut tunnel_device) .map_err(TunnelError::BypassError)?; - let tunnel = WgGoTunnel::Multihop(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, @@ -537,7 +630,12 @@ impl WgGoTunnel { #[cfg(daita)] config: config.clone(), cancel_receiver: cancel_receiver.clone(), - }); + }; + + let tunnel = Self { + inner: Some(tunnel), + r#type: Circuit::Multihop, + }; if is_new_tunnel { tunnel.wait_for_routes().await?; @@ -569,12 +667,10 @@ impl WgGoTunnel { /// There is a brief period of time between setting up a Wireguard-go tunnel and the tunnel being ready to serve /// traffic. This function blocks until the tunnel starts to serve traffic or until [connectivity::Check] times out. async fn wait_for_routes(&self) -> Result<()> { - let state = self.as_state(); - - let expected_routes = state.tun_provider.lock().unwrap().real_routes(); + let expected_routes = self.handle().tun_provider.lock().unwrap().real_routes(); // Wait for routes to come up - state + self.handle() .route_manager .clone() .wait_for_routes(expected_routes) @@ -585,9 +681,8 @@ impl WgGoTunnel { Ok(()) } async fn ensure_tunnel_is_running(&self) -> Result<()> { - let state = self.as_state(); - let addr = state.config.ipv4_gateway; - let cancel_receiver = state.cancel_receiver.clone(); + let addr = self.handle().config.ipv4_gateway; + let cancel_receiver = self.handle().cancel_receiver.clone(); let mut check = connectivity::Check::new(addr, 0, cancel_receiver) .map_err(|err| TunnelError::RecoverableStartWireguardError(Box::new(err)))?; @@ -612,16 +707,17 @@ impl WgGoTunnel { #[async_trait::async_trait] impl Tunnel for WgGoTunnel { fn get_interface_name(&self) -> String { - self.as_state().interface_name.clone() + self.handle().interface_name.clone() } - fn stop(self: Box<Self>) -> Result<()> { - self.into_state().stop() + fn stop(mut self: Box<Self>) -> Result<()> { + WgGoTunnel::stop(&mut self)?; + Ok(()) } async fn get_tunnel_stats(&self) -> Result<StatsMap> { // NOTE: wireguard-go might perform blocking I/O, but it's most likely not a problem - self.as_state() + self.handle() .tunnel_handle .get_config(|cstr| { Stats::parse_config_str(cstr.to_str().expect("Go strings are always UTF-8")) @@ -634,20 +730,19 @@ impl Tunnel for WgGoTunnel { &mut self, config: Config, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> { - Box::pin(async move { self.as_state_mut().set_config(config) }) + Box::pin(async move { self.set_config(config).await }) } #[cfg(daita)] fn start_daita(&mut self, settings: DaitaSettings) -> Result<()> { log::info!("Initializing DAITA for wireguard device"); - let config = &self.as_state().config; - let peer_public_key = &config.entry_peer.public_key; + let peer_public_key = self.handle().config.entry_peer.public_key.clone(); let machines = settings.client_machines.join("\n"); let machines = CString::new(machines).map_err(|err| TunnelError::StartDaita(Box::new(err)))?; - self.as_state() + self.handle() .tunnel_handle .activate_daita( peer_public_key.as_bytes(), diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index baac2ddd69..43eb548491 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -440,7 +440,9 @@ impl WgNtTunnel { ); match error { - Error::CreateTunnelDevice(error) => super::TunnelError::SetupTunnelDevice(error), + Error::CreateTunnelDevice(error) => super::TunnelError::SetupTunnelDevice( + talpid_tunnel::tun_provider::Error::Io(error), + ), _ => super::TunnelError::FatalStartWireguardError(Box::new(error)), } }) |
