From 696c60680a7e4a2c4c6f4778d2d63c135e4469d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 10 Jan 2025 11:08:40 +0100 Subject: [PATCH 01/44] Add Windows support to libwg --- wireguard-go-rs/libwg/README.md | 3 + wireguard-go-rs/libwg/libwg_windows.go | 138 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 wireguard-go-rs/libwg/libwg_windows.go diff --git a/wireguard-go-rs/libwg/README.md b/wireguard-go-rs/libwg/README.md index 39ad48e3e0b0..e5b96928f7cf 100644 --- a/wireguard-go-rs/libwg/README.md +++ b/wireguard-go-rs/libwg/README.md @@ -7,6 +7,7 @@ It currently offers support for the following platforms: - Linux - macOS - Android +- Windows # Organization @@ -16,6 +17,8 @@ It currently offers support for the following platforms: `libwg_android.go` has code specifically for Android. +`libwg_windows.go` has code specifically for Windows. + # Usage Call `wgTurnOn` to create and activate a tunnel. The prototype is different on different platforms, see the code for details. diff --git a/wireguard-go-rs/libwg/libwg_windows.go b/wireguard-go-rs/libwg/libwg_windows.go new file mode 100644 index 000000000000..f4ad7faa6db7 --- /dev/null +++ b/wireguard-go-rs/libwg/libwg_windows.go @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2024 Mullvad VPN AB. All Rights Reserved. + */ + +package main + +// #include +// #include +import "C" + +import ( + "bufio" + "strings" + "unsafe" + + "golang.org/x/sys/windows" + + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + + "github.com/mullvad/mullvadvpn-app/wireguard/libwg/logging" + "github.com/mullvad/mullvadvpn-app/wireguard/libwg/tunnelcontainer" +) + +// Redefined here because otherwise the compiler doesn't realize it's a type alias for a type that's safe to export. +// Taken from the contained logging package. +type LogSink = unsafe.Pointer +type LogContext = C.uint64_t + +//export wgTurnOn +func wgTurnOn(cIfaceName *C.char, cIfaceNameOut **C.char, cLuidOut *C.uint64_t, mtu C.uint16_t, cSettings *C.char, logSink LogSink, logContext LogContext) C.int32_t { + logger := logging.NewLogger(logSink, logging.LogContext(logContext)) + if cIfaceNameOut != nil { + *cIfaceNameOut = nil + } + + if cIfaceName == nil { + logger.Errorf("cIfaceName is null\n") + return ERROR_GENERAL_FAILURE + } + + if cSettings == nil { + logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + + settings := C.GoString(cSettings) + ifaceName := C.GoString(cIfaceName) + + // {AFE43773-E1F8-4EBB-8536-576AB86AFE9A} + networkId := windows.GUID{ + Data1: 0xafe43773, + Data2: 0xe1f8, + Data3: 0x4ebb, + Data4: [8]byte{0x85, 0x36, 0x57, 0x6a, 0xb8, 0x6a, 0xfe, 0x9a}, + } + + tun.WintunTunnelType = "Mullvad" + + wintun, err := tun.CreateTUNWithRequestedGUID(ifaceName, &networkId, mtu) + if err != nil { + logger.Errorf("Failed to create tunnel\n") + logger.Errorf("%s\n", err) + return ERROR_INTERMITTENT_FAILURE + } + + nativeTun := wintun.(*tun.NativeTun) + + actualInterfaceName, err := nativeTun.Name() + if err != nil { + nativeTun.Close() + logger.Errorf("Failed to determine name of wintun adapter\n") + return ERROR_GENERAL_FAILURE + } + if actualInterfaceName != ifaceName { + // WireGuard picked a different name for the adapter than the one we expected. + // This indicates there is already an adapter with the name we intended to use. + logger.Verbosef("Failed to create adapter with specific name\n") + } + + device := device.NewDevice(wintun, conn.NewDefaultBind(), logger) + + setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setError != nil { + logger.Errorf("Failed to set device configuration\n") + logger.Errorf("%s\n", setError) + device.Close() + return ERROR_GENERAL_FAILURE + } + + device.Up() + + context := tunnelcontainer.Context{ + Device: device, + Logger: logger, + } + + handle, err := tunnels.Insert(context) + if err != nil { + logger.Errorf("%s\n", err) + device.Close() + return ERROR_GENERAL_FAILURE + } + + if cIfaceNameOut != nil { + *cIfaceNameOut = C.CString(actualInterfaceName) + } + if cLuidOut != nil { + *cLuidOut = nativeTun.LUID() + } + + return C.int32_t(handle) +} + +//export wgRebindTunnelSocket +func wgRebindTunnelSocket(family uint16, interfaceIndex uint32) { + tunnels.ForEach(func(tunnel tunnelcontainer.Context) { + blackhole := (interfaceIndex == 0) + bind := tunnel.Device.Bind().(conn.BindSocketToInterface) + + if family == windows.AF_INET { + tunnel.Logger.Verbosef("Binding v4 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) + err := bind.BindSocketToInterface4(interfaceIndex, blackhole) + if err != nil { + tunnel.Logger.Verbosef("%s\n", err) + } + } else if family == windows.AF_INET6 { + tunnel.Logger.Verbosef("Binding v6 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) + err := bind.BindSocketToInterface6(interfaceIndex, blackhole) + if err != nil { + tunnel.Logger.Verbosef("%s\n", err) + } + } + }) +} From 2fec6bb168e29ea47976b07c188d0cf792d70b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 10 Jan 2025 11:45:30 +0100 Subject: [PATCH 02/44] Pass in interface buffer instead of allocating in libwg --- wireguard-go-rs/libwg/libwg_windows.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/wireguard-go-rs/libwg/libwg_windows.go b/wireguard-go-rs/libwg/libwg_windows.go index f4ad7faa6db7..fc3d9d18a2ab 100644 --- a/wireguard-go-rs/libwg/libwg_windows.go +++ b/wireguard-go-rs/libwg/libwg_windows.go @@ -8,6 +8,7 @@ package main // #include // #include +// #include import "C" import ( @@ -31,11 +32,8 @@ type LogSink = unsafe.Pointer type LogContext = C.uint64_t //export wgTurnOn -func wgTurnOn(cIfaceName *C.char, cIfaceNameOut **C.char, cLuidOut *C.uint64_t, mtu C.uint16_t, cSettings *C.char, logSink LogSink, logContext LogContext) C.int32_t { +func wgTurnOn(cIfaceName *C.char, cIfaceNameOut *C.char, cIfaceNameOutSize C.size_t, cLuidOut *C.uint64_t, mtu C.uint16_t, cSettings *C.char, logSink LogSink, logContext LogContext) C.int32_t { logger := logging.NewLogger(logSink, logging.LogContext(logContext)) - if cIfaceNameOut != nil { - *cIfaceNameOut = nil - } if cIfaceName == nil { logger.Errorf("cIfaceName is null\n") @@ -60,7 +58,7 @@ func wgTurnOn(cIfaceName *C.char, cIfaceNameOut **C.char, cLuidOut *C.uint64_t, tun.WintunTunnelType = "Mullvad" - wintun, err := tun.CreateTUNWithRequestedGUID(ifaceName, &networkId, mtu) + wintun, err := tun.CreateTUNWithRequestedGUID(ifaceName, &networkId, int(mtu)) if err != nil { logger.Errorf("Failed to create tunnel\n") logger.Errorf("%s\n", err) @@ -106,10 +104,15 @@ func wgTurnOn(cIfaceName *C.char, cIfaceNameOut **C.char, cLuidOut *C.uint64_t, } if cIfaceNameOut != nil { - *cIfaceNameOut = C.CString(actualInterfaceName) + if int(cIfaceNameOutSize) <= len(actualInterfaceName) { + logger.Errorf("Interface name buffer too small\n") + device.Close() + return ERROR_GENERAL_FAILURE + } + C.strcpy(cIfaceNameOut, C.CString(actualInterfaceName)) } if cLuidOut != nil { - *cLuidOut = nativeTun.LUID() + *cLuidOut = C.uint64_t(nativeTun.LUID()) } return C.int32_t(handle) From 1874bbe83ae48c943f061639cfd505bab6c26b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 10 Jan 2025 09:38:31 +0100 Subject: [PATCH 03/44] Build wireguard-go via wireguard-go-rs on Windows --- Cargo.lock | 1 + talpid-wireguard/src/ephemeral.rs | 5 +- talpid-wireguard/src/lib.rs | 17 ++ talpid-wireguard/src/wireguard_go/mod.rs | 8 +- wireguard-go-rs/Cargo.toml | 6 +- wireguard-go-rs/build.rs | 277 +++++++++++++++++++++-- wireguard-go-rs/src/lib.rs | 101 ++++++++- 7 files changed, 382 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bf2110f63c3..110b6b197a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5851,6 +5851,7 @@ dependencies = [ "log", "maybenot-ffi", "thiserror 2.0.9", + "windows-sys 0.52.0", "zeroize", ] diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 1df082001437..1a6114848743 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -145,10 +145,7 @@ async fn config_ephemeral_peers_inner( log::debug!("Successfully exchanged PSK with entry peer"); config.entry_peer.psk = entry_ephemeral_peer.psk; - #[cfg(not(target_os = "windows"))] - { - daita = entry_ephemeral_peer.daita; - } + daita = entry_ephemeral_peer.daita; } config.exit_peer_mut().psk = exit_ephemeral_peer.psk; diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 1b377e2f4f6f..205e150eee8e 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -692,6 +692,23 @@ impl WireguardMonitor { #[cfg(target_os = "windows")] { + #[cfg(wireguard_go)] + { + let use_userspace_wg = config.daita; + if use_userspace_wg { + log::debug!("Using userspace WireGuard implementation"); + let tunnel = Self::open_wireguard_go_tunnel( + runtime, + config, + log_path, + setup_done_tx, + route_manager, + ) + .map(Box::new)?; + return Ok(tunnel); + } + } + wireguard_nt::WgNtTunnel::start_tunnel(config, log_path, resource_dir, setup_done_tx) .map(|tun| Box::new(tun) as Box) .map_err(Error::TunnelError) diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 2bfd8ef9877d..4698616d058a 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -9,15 +9,18 @@ use crate::config::MULLVAD_INTERFACE_NAME; #[cfg(target_os = "android")] use crate::connectivity; use crate::logging::{clean_up_logging, initialize_logging}; +#[cfg(unix)] use ipnetwork::IpNetwork; #[cfg(daita)] use std::ffi::CString; +#[cfg(unix)] +use std::sync::{Arc, Mutex}; +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, RawFd}; use std::{ future::Future, - os::unix::io::{AsRawFd, RawFd}, path::{Path, PathBuf}, pin::Pin, - sync::{Arc, Mutex}, }; #[cfg(target_os = "android")] use talpid_tunnel::tun_provider::Error as TunProviderError; @@ -27,6 +30,7 @@ use talpid_tunnel_config_client::DaitaSettings; use talpid_types::net::wireguard::PeerConfig; use talpid_types::BoxedError; +#[cfg(unix)] const MAX_PREPARE_TUN_ATTEMPTS: usize = 4; /// Maximum number of events that can be stored in the underlying buffer diff --git a/wireguard-go-rs/Cargo.toml b/wireguard-go-rs/Cargo.toml index cfaef554cc40..b1388ab146b6 100644 --- a/wireguard-go-rs/Cargo.toml +++ b/wireguard-go-rs/Cargo.toml @@ -7,14 +7,16 @@ license.workspace = true [build-dependencies] anyhow = "1.0" -[target.'cfg(unix)'.dependencies] +[dependencies] thiserror.workspace = true log.workspace = true zeroize = "1.8.1" -[target.'cfg(not(target_os = "windows"))'.dependencies] # The app does not depend on maybenot-ffi itself, but adds it as a dependency to expose FFI symbols to wireguard-go. # This is done, instead of using the makefile in wireguard-go to build maybenot-ffi into its archive, to prevent # name clashes induced by link-time optimization. # NOTE: the version of maybenot-ffi below must be the same as the version checked into the wireguard-go submodule maybenot-ffi = "2.0.1" + +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { version = "0.52.0", features = ["Win32_NetworkManagement_Ndis"] } diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 1148d4010ed1..ad485be28daa 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -1,6 +1,8 @@ use std::{ borrow::BorrowMut, env, + fs::{self, File}, + io::{BufRead, BufReader, BufWriter, Write}, path::{Path, PathBuf}, process::Command, str, @@ -18,23 +20,24 @@ fn main() -> anyhow::Result<()> { println!("cargo::rerun-if-changed=libwg"); match target_os.as_str() { - "linux" => build_static_lib(Os::Linux, true)?, - "macos" => build_static_lib(Os::Macos, true)?, + "windows" => build_desktop_lib(Os::Windows, true)?, + "linux" => build_desktop_lib(Os::Linux, true)?, + "macos" => build_desktop_lib(Os::Macos, true)?, "android" => build_android_dynamic_lib(true)?, - // building wireguard-go-rs for windows is not implemented _ => {} } Ok(()) } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] enum Os { - Macos, + Windows, + MacOs, Linux, } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] enum Arch { Amd64, Arm64, @@ -64,7 +67,9 @@ impl AndroidTarget { fn host_os() -> anyhow::Result { // this ugliness is a limitation of rust, where we can't directly // access the target triple of the build script. - if cfg!(target_os = "linux") { + if cfg!(target_os = "windows") { + Ok(Os::Windows) + } else if cfg!(target_os = "linux") { Ok(Os::Linux) } else if cfg!(target_os = "macos") { Ok(Os::Macos) @@ -83,8 +88,8 @@ fn host_arch() -> anyhow::Result { } } -/// Compile libwg as a static library and place it in `OUT_DIR`. -fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { +/// Compile libwg as a library and place it in `OUT_DIR`. +fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; let target_arch = env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; @@ -95,14 +100,8 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { _ => bail!("Unsupported architecture: {target_arch}"), }; - let out_file = format!("{out_dir}/libwg.a"); let mut go_build = Command::new("go"); - go_build - .args(["build", "-v", "-o", &out_file]) - .args(["-buildmode", "c-archive"]) - .args(if daita { &["--tags", "daita"][..] } else { &[] }) - .env("CGO_ENABLED", "1") - .current_dir("./libwg"); + go_build.env("CGO_ENABLED", "1").current_dir("./libwg"); // are we cross compiling? let cross_compiling = host_os()? != target_os || host_arch()? != target_arch; @@ -113,7 +112,58 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { }; match target_os { + Os::Windows => { + let target_dir = Path::new(&out_dir) + .ancestors() + .nth(3) + .context("Failed to find target dir")?; + + if daita { + build_shared_maybenot_lib(target_dir).context("Failed to build maybenot")?; + } + + let dll_path = target_dir.join("libwg.dll"); + + println!("cargo::rerun-if-changed={}", dll_path.display()); + println!( + "cargo::rerun-if-changed={}", + target_dir.join("libwg.lib").display() + ); + + go_build + .args(["build", "-v"]) + .arg("-o") + .arg(&dll_path) + .args(if daita { &["--tags", "daita"][..] } else { &[] }); + // Build dynamic lib + go_build.args(["-buildmode", "c-shared"]); + + go_build.env("GOOS", "windows"); + + generate_windows_lib(target_arch, target_dir)?; + + println!("cargo::rustc-link-search={}", target_dir.to_str().unwrap()); + println!("cargo::rustc-link-lib=dylib=libwg"); + + // Build using zig + match target_arch { + Arch::Amd64 => { + go_build.env("CC", "zig cc -target x86_64-windows"); + } + Arch::Arm64 => { + go_build.env("CC", "zig cc -target aarch64-windows"); + } + } + } Os::Linux => { + let out_file = format!("{out_dir}/libwg.a"); + go_build + .args(["build", "-v", "-o", &out_file]) + .args(if daita { &["--tags", "daita"][..] } else { &[] }); + + // Build static lib + go_build.args(["-buildmode", "c-archive"]); + go_build.env("GOOS", "linux"); if cross_compiling { @@ -122,8 +172,20 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), }; } + + // make sure to link to the resulting binary + println!("cargo::rustc-link-search={out_dir}"); + println!("cargo::rustc-link-lib=static=wg"); } Os::Macos => { + let out_file = format!("{out_dir}/libwg.a"); + go_build + .args(["build", "-v", "-o", &out_file]) + .args(if daita { &["--tags", "daita"][..] } else { &[] }); + + // Build static lib + go_build.args(["-buildmode", "c-archive"]); + go_build.env("GOOS", "darwin"); if cross_compiling { @@ -144,15 +206,15 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); } + + // make sure to link to the resulting binary + println!("cargo::rustc-link-search={out_dir}"); + println!("cargo::rustc-link-lib=static=wg"); } } exec(go_build)?; - // make sure to link to the resulting binary - println!("cargo::rustc-link-search={out_dir}"); - println!("cargo::rustc-link-lib=static=wg"); - // if daita is enabled, also enable the corresponding rust feature flag if daita { println!(r#"cargo::rustc-cfg=daita"#); @@ -161,6 +223,181 @@ fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { Ok(()) } +// Build dynamically library for maybenot +fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { + let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; + let profile = env::var("PROFILE").context("Missing 'PROFILE'")?; + + let mut build_command = Command::new("cargo"); + + std::fs::create_dir_all("../build")?; + + let mut tmp_build_dir = Path::new("../build").canonicalize()?; + + // Strip \\?\ prefix. Note that doing this directly on Path/PathBuf fails + let path_str = tmp_build_dir.to_str().unwrap(); + if path_str.starts_with(r"\\?\") { + tmp_build_dir = PathBuf::from(&path_str[4..]); + } + + tmp_build_dir = tmp_build_dir.join("target"); + + build_command + .current_dir("./libwg/wireguard-go/maybenot/crates/maybenot-ffi") + .env("RUSTFLAGS", "-C metadata=maybenot-ffi -Ctarget-feature=+crt-static") + // Set temporary target dir to prevent deadlock + .env("CARGO_TARGET_DIR", &tmp_build_dir) + .arg("build") + .args(["--target", &target_triple]); + + exec(build_command)?; + + let artifacts_dir = tmp_build_dir.join(target_triple).join(profile); + + // Copy library to actual target dir + for filename in ["maybenot_ffi.dll", "maybenot_ffi.lib"] { + fs::copy( + artifacts_dir.join(filename), + out_dir.as_ref().join(filename), + ) + .with_context(|| format!("Failed to copy {filename}"))?; + } + + Ok(()) +} + +/// Generate a library for the exported functions. Required for linking. +/// This requires `lib.exe` in the path. +fn generate_windows_lib(arch: Arch, out_dir: impl AsRef) -> anyhow::Result<()> { + let exports_def_path = out_dir.as_ref().join("exports.def"); + generate_exports_def(&exports_def_path).context("Failed to generate exports.def")?; + generate_lib_from_exports_def(arch, &exports_def_path) + .context("Failed to generate lib from exports.def") +} + +fn find_lib_exe() -> anyhow::Result { + let msbuild_exe = find_msbuild_exe()?; + + // Find lib.exe relative to msbuild.exe, in ../../../../ relative to msbuild + let search_path = msbuild_exe + .ancestors() + .nth(3) + .context("Unexpected msbuild.exe path")?; + + let path_is_lib_exe = |file: &Path| file.ends_with("Hostx64/x64/lib.exe"); + + find_file(search_path, &path_is_lib_exe)?.context("No lib.exe relative to msbuild.exe") +} + +/// Recursively search for file until 'condition' returns true +fn find_file( + dir: impl AsRef, + condition: &impl Fn(&Path) -> bool, +) -> anyhow::Result> { + for path in std::fs::read_dir(dir).context("Failed to read dir")? { + let entry = path.context("Failed to read dir entry")?; + let path = entry.path(); + if path.is_dir() { + if let Some(result) = find_file(&path, condition)? { + // TODO: distinguish between err and no result + return Ok(Some(result)); + } + } + if condition(&path) { + return Ok(Some(path.to_owned())); + } + } + Ok(None) +} + +/// Find msbuild.exe in PATH +fn find_msbuild_exe() -> anyhow::Result { + let path = std::env::var_os("PATH").context("Missing PATH var")?; + std::env::split_paths(&path) + .find(|path| path.join("msbuild.exe").exists()) + .context("msbuild.exe not found in PATH") +} + +/// Generate exports.def from wireguard-go source +fn generate_lib_from_exports_def(arch: Arch, exports_path: impl AsRef) -> anyhow::Result<()> { + let lib_path = exports_path + .as_ref() + .parent() + .context("Missing parent")? + .join("libwg.lib"); + let path = exports_path.as_ref().to_str().context("Non-UTF8 path")?; + + let lib_exe = find_lib_exe()?; + + let mut lib_exe = Command::new(lib_exe); + lib_exe.args([ + format!("/def:{path}"), + format!("/out:{}", lib_path.to_str().context("Non-UTF8 lib path")?), + ]); + + match arch { + Arch::Amd64 => { + lib_exe.arg("/machine:X64"); + } + Arch::Arm64 => { + lib_exe.arg("/machine:ARM64"); + } + } + + exec(lib_exe)?; + + Ok(()) +} + +/// Generate exports.def from wireguard-go source +fn generate_exports_def(exports_path: impl AsRef) -> anyhow::Result<()> { + let file = File::create(exports_path).context("Failed to create file")?; + let mut file = BufWriter::new(file); + + writeln!(file, "LIBRARY libwg").context("Write LIBRARY statement")?; + writeln!(file, "EXPORTS").context("Write EXPORTS statement")?; + + let mut libwg_exports = vec![]; + for path in &[ + "./libwg/libwg.go", + "./libwg/libwg_windows.go", + "./libwg/libwg_daita.go", + ] { + libwg_exports.extend(gather_exports(path).context("Failed to find exports")?); + } + + for export in libwg_exports { + writeln!(file, "\t{export}").context("Failed to output exported function")?; + } + + Ok(()) +} + +/// Return functions exported from .go file +fn gather_exports(go_src_path: impl AsRef) -> anyhow::Result> { + let go_src_path = go_src_path.as_ref(); + let mut exports = vec![]; + let file = File::open(go_src_path) + .with_context(|| format!("Failed to open go source: {}", go_src_path.display()))?; + + for line in BufReader::new(file).lines() { + let line = line.context("Failed to read line in go src")?; + let mut words = line.split_whitespace(); + + // Is this an export? + let Some("//export") = words.next() else { + continue; + }; + + let exported_func = words + .next() + .with_context(|| format!("Invalid export on line: {line}"))?; + exports.push(exported_func.to_owned()); + } + + Ok(exports) +} + /// Compile libwg as a dynamic library for android and place it in [`android_output_path`]. // NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. fn build_android_dynamic_lib(daita: bool) -> anyhow::Result<()> { diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index 3e75506f6126..4386c1f48ab7 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -6,18 +6,23 @@ //! //! The [`Tunnel`] type provides a safe Rust wrapper around the C FFI. -#![cfg(unix)] - +#[cfg(target_os = "windows")] +use core::mem::MaybeUninit; use core::{ ffi::{c_char, CStr}, - mem::{ManuallyDrop, MaybeUninit}, + mem::ManuallyDrop, slice, }; +#[cfg(target_os = "windows")] +use std::ffi::CString; use util::OnDrop; +#[cfg(target_os = "windows")] +use windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH; use zeroize::Zeroize; mod util; +#[cfg(unix)] pub type Fd = std::os::unix::io::RawFd; pub type WgLogLevel = u32; @@ -34,6 +39,11 @@ use maybenot_ffi as _; pub struct Tunnel { /// wireguard-go handle to the tunnel. handle: i32, + + #[cfg(target_os = "windows")] + assigned_name: CString, + #[cfg(target_os = "windows")] + luid: NET_LUID_LH, } // NOTE: Must be kept in sync with libwg.go @@ -73,6 +83,7 @@ impl Tunnel { /// The `logging_callback` let's you provide a Rust function that receives any logging output /// from wireguard-go. `logging_context` is a value that will be passed to each invocation of /// `logging_callback`. + #[cfg(not(target_os = "windows"))] pub fn turn_on( #[cfg(not(target_os = "android"))] mtu: isize, settings: &CStr, @@ -96,6 +107,51 @@ impl Tunnel { Ok(Tunnel { handle: code }) } + /// Creates a new wireguard tunnel, uses the specific interface name, and file descriptors + /// for the tunnel device and logging. + /// + /// The `logging_callback` let's you provide a Rust function that receives any logging output + /// from wireguard-go. `logging_context` is a value that will be passed to each invocation of + /// `logging_callback`. + #[cfg(target_os = "windows")] + pub fn turn_on( + interface_name: &CStr, + mtu: u16, + settings: &CStr, + logging_callback: Option, + logging_context: LoggingContext, + ) -> Result { + // FIXME: use reasonable length + let mut assigned_name = [0u8; 128]; + let mut luid = MaybeUninit::uninit(); + + // SAFETY: pointers are valid for the the lifetime of this function + let code = unsafe { + ffi::wgTurnOn( + interface_name.as_ptr(), + assigned_name.as_mut_ptr() as *mut i8, + assigned_name.len(), + // SAFETY: This is a union of a u64 and `NET_LUID_LH_0` + luid.as_mut_ptr() as *mut u64, + mtu, + settings.as_ptr(), + logging_callback, + logging_context, + ) + }; + + result_from_code(code)?; + + let assigned_name = CStr::from_bytes_until_nul(&assigned_name).unwrap(); + + Ok(Tunnel { + handle: code, + assigned_name: assigned_name.to_owned(), + // SAFETY: wgTurnOn succeeded and the LUID is guaranteed to be intialized by wgTurnOn + luid: unsafe { luid.assume_init() }, + }) + } + /// Stop the wireguard tunnel. This also happens automatically if the [`Tunnel`] is dropped. pub fn turn_off(self) -> Result<(), Error> { // we manually turn off the tunnel here, so wrap it in ManuallyDrop to prevent the Drop @@ -105,6 +161,18 @@ impl Tunnel { result_from_code(code) } + /// Tunnel interface name + #[cfg(target_os = "windows")] + pub fn name(&self) -> &str { + self.assigned_name.to_str().expect("non-UTF8 name") + } + + /// Tunnel interface LUID + #[cfg(target_os = "windows")] + pub fn luid(&self) -> &NET_LUID_LH { + &self.luid + } + /// Special function for android multihop since that behavior is different from desktop /// and android non-multihop. /// @@ -276,7 +344,9 @@ impl Error { } mod ffi { - use super::{Fd, LoggingCallback, LoggingContext}; + #[cfg(not(target_os = "windows"))] + use super::Fd; + use super::{LoggingCallback, LoggingContext}; use core::ffi::{c_char, c_void}; extern "C" { @@ -286,14 +356,35 @@ mod ffi { /// /// Positive return values are tunnel handles for this specific wireguard tunnel instance. /// Negative return values signify errors. + #[cfg(any(target_os = "linux", target_os = "macos"))] + pub fn wgTurnOn( + mtu: isize, + settings: *const c_char, + fd: Fd, + logging_callback: Option, + logging_context: LoggingContext, + ) -> i32; + + #[cfg(target_os = "android")] pub fn wgTurnOn( - #[cfg(not(target_os = "android"))] mtu: isize, settings: *const c_char, fd: Fd, logging_callback: Option, logging_context: LoggingContext, ) -> i32; + #[cfg(target_os = "windows")] + pub fn wgTurnOn( + desired_name: *const c_char, + assigned_name: *mut c_char, + assigned_name_size: usize, + assigned_luid: *mut u64, + mtu: u16, + settings: *const c_char, + logging_callback: Option, + logging_context: LoggingContext, + ) -> i32; + /// Creates a new wireguard tunnel, uses the specific interface name, and file descriptors /// for the tunnel device and logging. /// From 4752a1600c01f0ff89c07507104cd7df92ca70f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Mon, 13 Jan 2025 13:36:57 +0100 Subject: [PATCH 04/44] Don't include maybenot_ffi on Windows --- wireguard-go-rs/Cargo.toml | 1 + wireguard-go-rs/src/lib.rs | 21 +-------------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/wireguard-go-rs/Cargo.toml b/wireguard-go-rs/Cargo.toml index b1388ab146b6..976b2bd6b1a7 100644 --- a/wireguard-go-rs/Cargo.toml +++ b/wireguard-go-rs/Cargo.toml @@ -16,6 +16,7 @@ zeroize = "1.8.1" # This is done, instead of using the makefile in wireguard-go to build maybenot-ffi into its archive, to prevent # name clashes induced by link-time optimization. # NOTE: the version of maybenot-ffi below must be the same as the version checked into the wireguard-go submodule +[target.'cfg(unix)'.dependencies] maybenot-ffi = "2.0.1" [target.'cfg(target_os = "windows")'.dependencies] diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index 4386c1f48ab7..037d17709d6a 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -32,7 +32,7 @@ pub type LoggingCallback = unsafe extern "system" fn(level: WgLogLevel, msg: *const c_char, context: LoggingContext); // Make symbols from maybenot-ffi visible to wireguard-go -#[cfg(daita)] +#[cfg(all(daita, unix))] use maybenot_ffi as _; /// A wireguard-go tunnel @@ -304,25 +304,6 @@ impl Drop for Tunnel { } } -/// Check whether `machines` contains a valid, LF-separated maybenot machines. Return an error -/// otherwise. -pub fn validate_maybenot_machines(machines: &CStr) -> Result<(), Error> { - use maybenot_ffi::MaybenotResult; - - let mut framework = MaybeUninit::uninit(); - // SAFETY: `machines` is a null-terminated string, and `&mut framework` is a valid pointer - let result = - unsafe { maybenot_ffi::maybenot_start(machines.as_ptr(), 0.0, 0.0, &mut framework) }; - - if result as u32 == MaybenotResult::Ok as u32 { - // SAFETY: `maybenot_start` succeeded, so `framework` points to a valid framework - unsafe { maybenot_ffi::maybenot_stop(framework.assume_init()) }; - Ok(()) - } else { - Err(Error::Other) - } -} - fn result_from_code(code: i32) -> Result<(), Error> { // NOTE: must be kept in sync with enum definition Err(match code { From bc05fb5f8ea1f4861df8b7eac133a1d543aa8e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 10 Jan 2025 14:46:58 +0100 Subject: [PATCH 05/44] Enable DAITA v2 for Windows via wireguard-go --- talpid-tunnel-config-client/src/lib.rs | 36 ++++--------- talpid-wireguard/Cargo.toml | 2 - talpid-wireguard/build.rs | 7 ++- talpid-wireguard/src/ephemeral.rs | 9 ---- talpid-wireguard/src/lib.rs | 28 +++++----- talpid-wireguard/src/wireguard_go/mod.rs | 68 +++++++++++++++++++++++- talpid-wireguard/src/wireguard_nt/mod.rs | 2 +- 7 files changed, 93 insertions(+), 59 deletions(-) diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index 381bc65a5365..0be95672595e 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -22,7 +22,6 @@ mod proto { tonic::include_proto!("ephemeralpeer"); } -#[cfg(unix)] const DAITA_VERSION: u32 = 2; #[derive(Debug)] @@ -88,7 +87,6 @@ pub const CONFIG_SERVICE_PORT: u16 = 1337; pub struct EphemeralPeer { pub psk: Option, - #[cfg(unix)] pub daita: Option, } @@ -141,15 +139,7 @@ pub async fn request_ephemeral_peer_with( wg_parent_pubkey: parent_pubkey.as_bytes().to_vec(), wg_ephemeral_peer_pubkey: ephemeral_pubkey.as_bytes().to_vec(), post_quantum: pq_request, - #[cfg(windows)] - daita: Some(proto::DaitaRequestV1 { - activate_daita: enable_daita, - }), - #[cfg(windows)] - daita_v2: None, - #[cfg(unix)] daita: None, - #[cfg(unix)] daita_v2: enable_daita.then(|| proto::DaitaRequestV2 { level: i32::from(proto::DaitaLevel::LevelDefault), platform: i32::from(get_platform()), @@ -204,29 +194,21 @@ pub async fn request_ephemeral_peer_with( None }; - #[cfg(unix)] - { - let daita = response.daita.map(|daita| DaitaSettings { - client_machines: daita.client_machines, - max_padding_frac: daita.max_padding_frac, - max_blocking_frac: daita.max_blocking_frac, - }); - if daita.is_none() && enable_daita { - return Err(Error::MissingDaitaResponse); - } - Ok(EphemeralPeer { psk, daita }) - } - - #[cfg(windows)] - { - Ok(EphemeralPeer { psk }) + let daita = response.daita.map(|daita| DaitaSettings { + client_machines: daita.client_machines, + max_padding_frac: daita.max_padding_frac, + max_blocking_frac: daita.max_blocking_frac, + }); + if daita.is_none() && enable_daita { + return Err(Error::MissingDaitaResponse); } + Ok(EphemeralPeer { psk, daita }) } -#[cfg(unix)] const fn get_platform() -> proto::DaitaPlatform { use proto::DaitaPlatform; const PLATFORM: DaitaPlatform = if cfg!(target_os = "windows") { + // FIXME: wggo DaitaPlatform::WindowsNative } else if cfg!(target_os = "linux") { DaitaPlatform::LinuxWgGo diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 3a19f5a70afa..6341c02bacff 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -30,8 +30,6 @@ tunnel-obfuscation = { path = "../tunnel-obfuscation" } rand = "0.8.5" surge-ping = "0.8.0" rand_chacha = "0.3.1" - -[target.'cfg(not(windows))'.dependencies] wireguard-go-rs = { path = "../wireguard-go-rs"} [target.'cfg(target_os="android")'.dependencies] diff --git a/talpid-wireguard/build.rs b/talpid-wireguard/build.rs index ab3500330c26..23c2f3bb67b2 100644 --- a/talpid-wireguard/build.rs +++ b/talpid-wireguard/build.rs @@ -6,11 +6,10 @@ fn main() { if target_os == "windows" { declare_libs_dir("../dist-assets/binaries"); } - // Wireguard-Go can be used on all platforms except Windows + // Wireguard-Go can be used on all platforms println!("cargo::rustc-check-cfg=cfg(wireguard_go)"); - if matches!(target_os.as_str(), "linux" | "macos" | "android") { - println!("cargo::rustc-cfg=wireguard_go"); - } + println!("cargo::rustc-cfg=wireguard_go"); + // Enable DAITA by default on desktop and android println!("cargo::rustc-check-cfg=cfg(daita)"); println!("cargo::rustc-cfg=daita"); diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 1a6114848743..04f24d5fbb88 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -110,7 +110,6 @@ async fn config_ephemeral_peers_inner( ) .await?; - #[cfg(not(target_os = "windows"))] let mut daita = exit_ephemeral_peer.daita; log::debug!("Retrieved ephemeral peer"); @@ -169,7 +168,6 @@ async fn config_ephemeral_peers_inner( #[cfg(daita)] if config.daita { - #[cfg(not(target_os = "windows"))] let Some(daita) = daita else { unreachable!("missing DAITA settings"); @@ -178,17 +176,10 @@ async fn config_ephemeral_peers_inner( // Start local DAITA machines let mut tunnel = tunnel.lock().await; if let Some(tunnel) = tunnel.as_mut() { - #[cfg(not(target_os = "windows"))] tunnel .start_daita(daita) .map_err(Error::TunnelError) .map_err(CloseMsg::SetupError)?; - - #[cfg(target_os = "windows")] - tunnel - .start_daita() - .map_err(Error::TunnelError) - .map_err(CloseMsg::SetupError)?; } } diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 205e150eee8e..6eecefbbb630 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -28,7 +28,7 @@ use talpid_tunnel::{ tun_provider::TunProvider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata, }; -#[cfg(not(target_os = "windows"))] +#[cfg(daita)] use talpid_tunnel_config_client::DaitaSettings; use talpid_types::{ net::{wireguard::TunnelParameters, AllowedTunnelTraffic, Endpoint, TransportProtocol}, @@ -714,7 +714,7 @@ impl WireguardMonitor { .map_err(Error::TunnelError) } - #[cfg(wireguard_go)] + #[cfg(all(wireguard_go, not(target_os = "windows")))] { #[cfg(target_os = "linux")] log::debug!("Using userspace WireGuard implementation"); @@ -738,23 +738,23 @@ impl WireguardMonitor { async fn open_wireguard_go_tunnel( config: &Config, log_path: Option<&Path>, - tun_provider: Arc>, + #[cfg(unix)] tun_provider: Arc>, + #[cfg(windows)] setup_done_tx: mpsc::Sender>, #[cfg(target_os = "android")] gateway_only: bool, #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, ) -> Result { + #[cfg(unix)] let routes = config .get_tunnel_destinations() .flat_map(Self::replace_default_prefixes); - #[cfg(not(target_os = "android"))] - let tunnel = WgGoTunnel::start_tunnel( - #[allow(clippy::needless_borrow)] - &config, - log_path, - tun_provider, - routes, - ) - .map_err(Error::TunnelError)?; + #[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, setup_done_tx) + .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 @@ -1035,10 +1035,8 @@ pub(crate) trait Tunnel: Send + Sync { ) -> Pin> + Send + 'a>>; #[cfg(daita)] /// A [`Tunnel`] capable of using DAITA. - #[cfg(not(target_os = "windows"))] + #[cfg(daita)] fn start_daita(&mut self, settings: DaitaSettings) -> std::result::Result<(), TunnelError>; - #[cfg(target_os = "windows")] - fn start_daita(&mut self) -> std::result::Result<(), TunnelError>; } /// Errors to be returned from WireGuard implementations, namely implementers of the Tunnel trait diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 4698616d058a..86646c1d0c34 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -24,7 +24,9 @@ use std::{ }; #[cfg(target_os = "android")] use talpid_tunnel::tun_provider::Error as TunProviderError; +#[cfg(not(target_os = "windows"))] use talpid_tunnel::tun_provider::{Tun, TunProvider}; +#[cfg(daita)] use talpid_tunnel_config_client::DaitaSettings; #[cfg(target_os = "android")] use talpid_types::net::wireguard::PeerConfig; @@ -165,6 +167,7 @@ pub(crate) struct WgGoTunnelState { tunnel_handle: wireguard_go_rs::Tunnel, // holding on to the tunnel device and the log file ensures that the associated file handles // live long enough and get closed when the tunnel is stopped + #[cfg(unix)] _tunnel_device: Tun, // context that maps to fs::File instance and stores the file path, used with logging callback _logging_context: LoggingContext, @@ -214,7 +217,7 @@ impl WgGoTunnelState { } impl WgGoTunnel { - #[cfg(not(target_os = "android"))] + #[cfg(all(not(target_os = "android"), unix))] pub fn start_tunnel( config: &Config, log_path: Option<&Path>, @@ -252,6 +255,69 @@ impl WgGoTunnel { })) } + #[cfg(target_os = "windows")] + pub fn start_tunnel( + config: &Config, + log_path: Option<&Path>, + mut setup_done_tx: futures::channel::mpsc::Sender>, + ) -> Result { + use futures::SinkExt; + use talpid_types::ErrorExt; + + let wg_config_str = config.to_userspace_format(); + let logging_context = initialize_logging(log_path) + .map(|ordinal| LoggingContext::new(ordinal, log_path.map(Path::to_owned))) + .map_err(TunnelError::LoggingError)?; + + // TODO: default route clalback + + let handle = wireguard_go_rs::Tunnel::turn_on( + c"Mullvad", + config.mtu, + &wg_config_str, + Some(logging::wg_go_logging_callback), + logging_context.ordinal, + ) + .map_err(|e| TunnelError::FatalStartWireguardError(Box::new(e)))?; + + let has_ipv6 = config.ipv6_gateway.is_some(); + + let luid = handle.luid().to_owned(); + + let setup_task = async move { + log::debug!("Waiting for tunnel IP interfaces to arrive"); + talpid_windows::net::wait_for_interfaces(luid, true, has_ipv6) + .await + .map_err(|e| BoxedError::new(TunnelError::SetupIpInterfaces(e)))?; + log::debug!("Waiting for tunnel IP interfaces: Done"); + + if let Err(error) = talpid_tunnel::network_interface::initialize_interfaces(luid, None) + { + log::error!( + "{}", + error.display_chain_with_msg("Failed to set tunnel interface metric"), + ); + } + + Ok(()) + }; + + tokio::spawn(async move { + let _ = setup_done_tx.send(setup_task.await).await; + }); + + let interface_name = handle.name(); + + Ok(WgGoTunnel(WgGoTunnelState { + interface_name: interface_name.to_owned(), + tunnel_handle: handle, + _logging_context: logging_context, + #[cfg(daita)] + config: config.clone(), + })) + } + + #[cfg(unix)] fn get_tunnel( tun_provider: Arc>, config: &Config, diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index 9243425cde87..e5f0aa7c63ec 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -1104,7 +1104,7 @@ impl Tunnel for WgNtTunnel { } #[cfg(daita)] - fn start_daita(&mut self) -> std::result::Result<(), crate::TunnelError> { + fn start_daita(&mut self, _: talpid_tunnel_config_client::DaitaSettings) -> std::result::Result<(), crate::TunnelError> { self.spawn_machinist().map_err(|error| { log::error!( "{}", From 3e10836afaa167571ea3be3a9922cc74f5acfdb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Mon, 13 Jan 2025 09:24:17 +0100 Subject: [PATCH 06/44] Expose endpoint rebind functions in wireguard-go-rs --- wireguard-go-rs/Cargo.toml | 2 +- wireguard-go-rs/src/lib.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/wireguard-go-rs/Cargo.toml b/wireguard-go-rs/Cargo.toml index 976b2bd6b1a7..375e248f1c08 100644 --- a/wireguard-go-rs/Cargo.toml +++ b/wireguard-go-rs/Cargo.toml @@ -20,4 +20,4 @@ zeroize = "1.8.1" maybenot-ffi = "2.0.1" [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { version = "0.52.0", features = ["Win32_NetworkManagement_Ndis"] } +windows-sys = { version = "0.52.0", features = ["Win32_Networking", "Win32_NetworkManagement", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] } diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index 037d17709d6a..d2f398536aeb 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -304,6 +304,22 @@ impl Drop for Tunnel { } } +/// Rebind WireGuard IPv4 endpoint sockets to use the given interface +#[cfg(target_os = "windows")] +pub fn rebind_tunnel_socket_v4(interface_index: u32) { + use windows_sys::Win32::Networking::WinSock::AF_INET; + // SAFETY: Passing an invalid interface is safe + unsafe { ffi::wgRebindTunnelSocket(AF_INET, interface_index) } +} + +/// Rebind WireGuard IPv6 endpoint sockets to use the given interface +#[cfg(target_os = "windows")] +pub fn rebind_tunnel_socket_v6(interface_index: u32) { + use windows_sys::Win32::Networking::WinSock::AF_INET6; + // SAFETY: Passing an invalid interface is safe + unsafe { ffi::wgRebindTunnelSocket(AF_INET6, interface_index) } +} + fn result_from_code(code: i32) -> Result<(), Error> { // NOTE: must be kept in sync with enum definition Err(match code { @@ -434,5 +450,9 @@ mod ffi { /// Get the file descriptor of the tunnel IPv6 socket. #[cfg(target_os = "android")] pub fn wgGetSocketV6(handle: i32) -> Fd; + + /// Rebind tunnel socket endpoint sockets + #[cfg(target_os = "windows")] + pub fn wgRebindTunnelSocket(family: u16, index: u32); } } From 81fe90fe201497d953fefc26357631ac4fd69d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Mon, 13 Jan 2025 09:25:36 +0100 Subject: [PATCH 07/44] Handle network changes for wireguard-go (rebind endpoint socket) --- talpid-wireguard/src/lib.rs | 8 ++-- talpid-wireguard/src/wireguard_go/mod.rs | 56 +++++++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 6eecefbbb630..9cf6082a48c6 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -697,13 +697,13 @@ impl WireguardMonitor { let use_userspace_wg = config.daita; if use_userspace_wg { log::debug!("Using userspace WireGuard implementation"); - let tunnel = Self::open_wireguard_go_tunnel( + let tunnel = runtime.block_on(Self::open_wireguard_go_tunnel( runtime, config, log_path, setup_done_tx, route_manager, - ) + )) .map(Box::new)?; return Ok(tunnel); } @@ -736,10 +736,12 @@ impl WireguardMonitor { #[cfg(wireguard_go)] #[allow(clippy::unused_async)] async fn open_wireguard_go_tunnel( + #[cfg(windows)] runtime: tokio::runtime::Handle, config: &Config, log_path: Option<&Path>, #[cfg(unix)] tun_provider: Arc>, #[cfg(windows)] setup_done_tx: mpsc::Sender>, + #[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle, #[cfg(target_os = "android")] gateway_only: bool, #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, ) -> Result { @@ -753,7 +755,7 @@ impl WireguardMonitor { .map_err(Error::TunnelError)?; #[cfg(target_os = "windows")] - let tunnel = WgGoTunnel::start_tunnel(config, log_path, setup_done_tx) + let tunnel = WgGoTunnel::start_tunnel(runtime, config, log_path, route_manager, setup_done_tx) .map_err(Error::TunnelError)?; // Android uses multihop implemented in Mullvad's wireguard-go fork. When negotiating diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 86646c1d0c34..6699b7a6ad92 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -178,6 +178,10 @@ pub(crate) struct WgGoTunnelState { /// This is used to cancel the connectivity checks that occur when toggling multihop #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, + /// Default route change callback. This is used to rebind the endpoint socket when the default + /// route (network) is changed. + #[cfg(target_os = "windows")] + _socket_update_cb: Option, } impl WgGoTunnelState { @@ -257,8 +261,10 @@ impl WgGoTunnel { #[cfg(target_os = "windows")] pub fn start_tunnel( + runtime: tokio::runtime::Handle, config: &Config, log_path: Option<&Path>, + route_manager: talpid_routing::RouteManagerHandle, mut setup_done_tx: futures::channel::mpsc::Sender>, ) -> Result { use futures::SinkExt; @@ -269,7 +275,16 @@ impl WgGoTunnel { .map(|ordinal| LoggingContext::new(ordinal, log_path.map(Path::to_owned))) .map_err(TunnelError::LoggingError)?; - // TODO: default route clalback + let socket_update_cb = runtime + .block_on( + route_manager.add_default_route_change_callback(Box::new( + Self::default_route_changed_callback, + )), + ) + .ok(); + if socket_update_cb.is_none() { + log::warn!("Failed to register default route callback"); + } let handle = wireguard_go_rs::Tunnel::turn_on( c"Mullvad", @@ -312,11 +327,50 @@ impl WgGoTunnel { 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 + #[cfg(target_os = "windows")] + fn default_route_changed_callback( + event_type: talpid_routing::EventType<'_>, + address_family: talpid_windows::net::AddressFamily, + ) { + use talpid_routing::EventType::*; + + let iface_idx: u32 = match event_type { + Updated(default_route) => { + let iface_luid = default_route.iface; + match talpid_windows::net::index_from_luid(&iface_luid) { + Ok(idx) => idx, + Err(err) => { + log::error!( + "Failed to convert interface LUID to interface index: {}", + err, + ); + return; + }, + } + } + // if there is no new default route, specify 0 as the interface index + Removed => 0, + // ignore interface updates that don't affect the interface to use + UpdatedDetails(_) => return, + }; + + match address_family { + talpid_windows::net::AddressFamily::Ipv4 => { + wireguard_go_rs::rebind_tunnel_socket_v4(iface_idx); + } + talpid_windows::net::AddressFamily::Ipv6 => { + wireguard_go_rs::rebind_tunnel_socket_v6(iface_idx); + } + } + } + #[cfg(unix)] fn get_tunnel( tun_provider: Arc>, From 9987218e6d376a321ba6a4aa8da068f91f29c092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 14 Jan 2025 14:33:23 +0100 Subject: [PATCH 08/44] Fix socket rebind on default route changes when using multihop --- talpid-wireguard/src/wireguard_go/mod.rs | 33 ++++-------------------- wireguard-go-rs/libwg/libwg_windows.go | 21 +++------------ wireguard-go-rs/src/lib.rs | 21 +++++---------- 3 files changed, 14 insertions(+), 61 deletions(-) diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 6699b7a6ad92..df2500c7be07 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -337,37 +337,14 @@ impl WgGoTunnel { #[cfg(target_os = "windows")] fn default_route_changed_callback( event_type: talpid_routing::EventType<'_>, - address_family: talpid_windows::net::AddressFamily, + _family: talpid_windows::net::AddressFamily, ) { use talpid_routing::EventType::*; - - let iface_idx: u32 = match event_type { - Updated(default_route) => { - let iface_luid = default_route.iface; - match talpid_windows::net::index_from_luid(&iface_luid) { - Ok(idx) => idx, - Err(err) => { - log::error!( - "Failed to convert interface LUID to interface index: {}", - err, - ); - return; - }, - } - } - // if there is no new default route, specify 0 as the interface index - Removed => 0, + match event_type { + // if there is no new default route, or if the route was removed, update the bind + Updated(_) | Removed => wireguard_go_rs::update_bind(), // ignore interface updates that don't affect the interface to use - UpdatedDetails(_) => return, - }; - - match address_family { - talpid_windows::net::AddressFamily::Ipv4 => { - wireguard_go_rs::rebind_tunnel_socket_v4(iface_idx); - } - talpid_windows::net::AddressFamily::Ipv6 => { - wireguard_go_rs::rebind_tunnel_socket_v6(iface_idx); - } + UpdatedDetails(_) => (), } } diff --git a/wireguard-go-rs/libwg/libwg_windows.go b/wireguard-go-rs/libwg/libwg_windows.go index fc3d9d18a2ab..f384b6609760 100644 --- a/wireguard-go-rs/libwg/libwg_windows.go +++ b/wireguard-go-rs/libwg/libwg_windows.go @@ -118,24 +118,9 @@ func wgTurnOn(cIfaceName *C.char, cIfaceNameOut *C.char, cIfaceNameOutSize C.siz return C.int32_t(handle) } -//export wgRebindTunnelSocket -func wgRebindTunnelSocket(family uint16, interfaceIndex uint32) { +//export wgUpdateBind +func wgUpdateBind() { tunnels.ForEach(func(tunnel tunnelcontainer.Context) { - blackhole := (interfaceIndex == 0) - bind := tunnel.Device.Bind().(conn.BindSocketToInterface) - - if family == windows.AF_INET { - tunnel.Logger.Verbosef("Binding v4 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) - err := bind.BindSocketToInterface4(interfaceIndex, blackhole) - if err != nil { - tunnel.Logger.Verbosef("%s\n", err) - } - } else if family == windows.AF_INET6 { - tunnel.Logger.Verbosef("Binding v6 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) - err := bind.BindSocketToInterface6(interfaceIndex, blackhole) - if err != nil { - tunnel.Logger.Verbosef("%s\n", err) - } - } + tunnel.Device.BindUpdate() }) } diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index d2f398536aeb..722e05c6136c 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -304,20 +304,11 @@ impl Drop for Tunnel { } } -/// Rebind WireGuard IPv4 endpoint sockets to use the given interface +/// Rebind WireGuard endpoint sockets. When the default interface changes, this needs to be called +/// so that the UDP socket can be rebound to use the new interface #[cfg(target_os = "windows")] -pub fn rebind_tunnel_socket_v4(interface_index: u32) { - use windows_sys::Win32::Networking::WinSock::AF_INET; - // SAFETY: Passing an invalid interface is safe - unsafe { ffi::wgRebindTunnelSocket(AF_INET, interface_index) } -} - -/// Rebind WireGuard IPv6 endpoint sockets to use the given interface -#[cfg(target_os = "windows")] -pub fn rebind_tunnel_socket_v6(interface_index: u32) { - use windows_sys::Win32::Networking::WinSock::AF_INET6; - // SAFETY: Passing an invalid interface is safe - unsafe { ffi::wgRebindTunnelSocket(AF_INET6, interface_index) } +pub fn update_bind() { + unsafe { ffi::wgUpdateBind() } } fn result_from_code(code: i32) -> Result<(), Error> { @@ -451,8 +442,8 @@ mod ffi { #[cfg(target_os = "android")] pub fn wgGetSocketV6(handle: i32) -> Fd; - /// Rebind tunnel socket endpoint sockets + /// Rebind endpoint sockets #[cfg(target_os = "windows")] - pub fn wgRebindTunnelSocket(family: u16, index: u32); + pub fn wgUpdateBind(); } } From ebd035676df303390130283fddfdc471d5996e38 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 14 Jan 2025 12:55:27 +0100 Subject: [PATCH 09/44] Update build instructions to accommodate WgGo on Windows --- BuildInstructions.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/BuildInstructions.md b/BuildInstructions.md index d3cf347c9c81..cc11d9289b9b 100644 --- a/BuildInstructions.md +++ b/BuildInstructions.md @@ -23,9 +23,8 @@ on your platform please submit an issue or a pull request. Install the `msi` hosted here: https://github.com/volta-cli/volta -- (Not Windows) Install Go (ideally version `1.21`) by following the [official - instructions](https://golang.org/doc/install). Newer versions may work - too. +- Install Go (ideally version `1.21`) by following the [official instructions](https://golang.org/doc/install). + Newer versions may work too. - Install a protobuf compiler (version 3.15 and up), it can be installed on most major Linux distros via the package name `protobuf-compiler`, `protobuf` on macOS via Homebrew, and on Windows @@ -96,6 +95,8 @@ The host has to have the following installed: - `bash` installed as well as a few base unix utilities, including `sed` and `tail`. You are recommended to use [Git for Windows]. +- `zig` installed and available in `%PATH%`. The latest official release should be fine: https://ziglang.org/download/. + - `msbuild.exe` available in `%PATH%`. If you installed Visual Studio Community edition, the binary can be found under: From 919ef3d1df2a41eabf7763e84935e1c6f162a90b Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 14 Jan 2025 14:55:11 +0100 Subject: [PATCH 10/44] Remove DAITA feature toggle from `wireguard-go-rs/build.rs` --- wireguard-go-rs/build.rs | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index ad485be28daa..a2129d86418b 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -20,10 +20,10 @@ fn main() -> anyhow::Result<()> { println!("cargo::rerun-if-changed=libwg"); match target_os.as_str() { - "windows" => build_desktop_lib(Os::Windows, true)?, - "linux" => build_desktop_lib(Os::Linux, true)?, - "macos" => build_desktop_lib(Os::Macos, true)?, - "android" => build_android_dynamic_lib(true)?, + "windows" => build_desktop_lib(Os::Windows)?, + "linux" => build_desktop_lib(Os::Linux)?, + "macos" => build_desktop_lib(Os::Macos)?, + "android" => build_android_dynamic_lib()?, _ => {} } @@ -33,7 +33,7 @@ fn main() -> anyhow::Result<()> { #[derive(PartialEq, Eq, Clone, Copy)] enum Os { Windows, - MacOs, + Macos, Linux, } @@ -89,7 +89,7 @@ fn host_arch() -> anyhow::Result { } /// Compile libwg as a library and place it in `OUT_DIR`. -fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { +fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; let target_arch = env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; @@ -118,9 +118,8 @@ fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { .nth(3) .context("Failed to find target dir")?; - if daita { - build_shared_maybenot_lib(target_dir).context("Failed to build maybenot")?; - } + // building maybenot is required for DAITA - wireguard-go will link to it at runtime. + build_shared_maybenot_lib(target_dir).context("Failed to build maybenot")?; let dll_path = target_dir.join("libwg.dll"); @@ -134,7 +133,7 @@ fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { .args(["build", "-v"]) .arg("-o") .arg(&dll_path) - .args(if daita { &["--tags", "daita"][..] } else { &[] }); + .args(["--tags", "daita"]); // Build dynamic lib go_build.args(["-buildmode", "c-shared"]); @@ -159,7 +158,7 @@ fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { let out_file = format!("{out_dir}/libwg.a"); go_build .args(["build", "-v", "-o", &out_file]) - .args(if daita { &["--tags", "daita"][..] } else { &[] }); + .args(["--tags", "daita"]); // Build static lib go_build.args(["-buildmode", "c-archive"]); @@ -181,7 +180,7 @@ fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { let out_file = format!("{out_dir}/libwg.a"); go_build .args(["build", "-v", "-o", &out_file]) - .args(if daita { &["--tags", "daita"][..] } else { &[] }); + .args(["--tags", "daita"]); // Build static lib go_build.args(["-buildmode", "c-archive"]); @@ -215,10 +214,8 @@ fn build_desktop_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { exec(go_build)?; - // if daita is enabled, also enable the corresponding rust feature flag - if daita { - println!(r#"cargo::rustc-cfg=daita"#); - } + // Enable the DAITA (rust) feature flag + println!(r#"cargo::rustc-cfg=daita"#); Ok(()) } @@ -400,7 +397,7 @@ fn gather_exports(go_src_path: impl AsRef) -> anyhow::Result> /// Compile libwg as a dynamic library for android and place it in [`android_output_path`]. // NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. -fn build_android_dynamic_lib(daita: bool) -> anyhow::Result<()> { +fn build_android_dynamic_lib() -> anyhow::Result<()> { let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; let target = AndroidTarget::from_str(&target_triple)?; @@ -451,10 +448,8 @@ fn build_android_dynamic_lib(daita: bool) -> anyhow::Result<()> { println!("cargo::rustc-link-search={}", android_output_path.display()); println!("cargo::rustc-link-lib=dylib=wg"); - // If daita is enabled, also enable the corresponding rust feature flag - if daita { - println!(r#"cargo::rustc-cfg=daita"#); - } + // Enable the DAITA (rust) feature flag + println!(r#"cargo::rustc-cfg=daita"#); Ok(()) } From 61abc12396eea20afaf9ca25ad57d5ac46cdba93 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 14 Jan 2025 15:13:13 +0100 Subject: [PATCH 11/44] Fix `wireguard-go-rs` build on non-Windows platforms --- wireguard-go-rs/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index 722e05c6136c..b080d307b2b1 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -6,13 +6,11 @@ //! //! The [`Tunnel`] type provides a safe Rust wrapper around the C FFI. +use core::ffi::{c_char, CStr}; +use core::mem::ManuallyDrop; #[cfg(target_os = "windows")] use core::mem::MaybeUninit; -use core::{ - ffi::{c_char, CStr}, - mem::ManuallyDrop, - slice, -}; +use core::slice; #[cfg(target_os = "windows")] use std::ffi::CString; use util::OnDrop; From 2db0cb4fdc6887c089f1504491590d3fbb4a5966 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 14 Jan 2025 15:48:24 +0100 Subject: [PATCH 12/44] Search for `lib.exe` in more paths --- wireguard-go-rs/build.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index a2129d86418b..03b78e172fb4 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -278,9 +278,10 @@ fn find_lib_exe() -> anyhow::Result { // Find lib.exe relative to msbuild.exe, in ../../../../ relative to msbuild let search_path = msbuild_exe .ancestors() - .nth(3) + .nth(4) .context("Unexpected msbuild.exe path")?; + // TODO: Make this arch agnostic (host AND target) let path_is_lib_exe = |file: &Path| file.ends_with("Hostx64/x64/lib.exe"); find_file(search_path, &path_is_lib_exe)?.context("No lib.exe relative to msbuild.exe") From b493d5b31b7ffdc70cebedb886826349d3e8c807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 14 Jan 2025 16:32:42 +0100 Subject: [PATCH 13/44] Do not strip prefix manually --- wireguard-go-rs/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 03b78e172fb4..7e87964b256f 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -233,8 +233,8 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { // Strip \\?\ prefix. Note that doing this directly on Path/PathBuf fails let path_str = tmp_build_dir.to_str().unwrap(); - if path_str.starts_with(r"\\?\") { - tmp_build_dir = PathBuf::from(&path_str[4..]); + if let Some(stripped) = path_str.strip_prefix(r"\\?\") { + tmp_build_dir = PathBuf::from(stripped); } tmp_build_dir = tmp_build_dir.join("target"); From 7ef4b567e3fb65c2954bc17bb8ce70f86e061920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 14 Jan 2025 17:12:03 +0100 Subject: [PATCH 14/44] Format code --- talpid-wireguard/src/connectivity/mock.rs | 2 +- talpid-wireguard/src/ephemeral.rs | 3 +-- talpid-wireguard/src/lib.rs | 22 ++++++++++++---------- talpid-wireguard/src/wireguard_go/mod.rs | 15 +++++++-------- talpid-wireguard/src/wireguard_nt/mod.rs | 5 ++++- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/talpid-wireguard/src/connectivity/mock.rs b/talpid-wireguard/src/connectivity/mock.rs index 5b7c98b18300..8149e0ced3a3 100644 --- a/talpid-wireguard/src/connectivity/mock.rs +++ b/talpid-wireguard/src/connectivity/mock.rs @@ -121,7 +121,7 @@ impl Tunnel for MockTunnel { #[cfg(daita)] fn start_daita( &mut self, - #[cfg(not(target_os = "windows"))] _: talpid_tunnel_config_client::DaitaSettings, + _: talpid_tunnel_config_client::DaitaSettings, ) -> std::result::Result<(), TunnelError> { Ok(()) } diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 04f24d5fbb88..903ccf4c3bcb 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -168,8 +168,7 @@ async fn config_ephemeral_peers_inner( #[cfg(daita)] if config.daita { - let Some(daita) = daita - else { + let Some(daita) = daita else { unreachable!("missing DAITA settings"); }; diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 9cf6082a48c6..3d6c2b4471f1 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -697,14 +697,15 @@ impl WireguardMonitor { let use_userspace_wg = config.daita; if use_userspace_wg { log::debug!("Using userspace WireGuard implementation"); - let tunnel = runtime.block_on(Self::open_wireguard_go_tunnel( - runtime, - config, - log_path, - setup_done_tx, - route_manager, - )) - .map(Box::new)?; + let tunnel = runtime + .block_on(Self::open_wireguard_go_tunnel( + runtime, + config, + log_path, + setup_done_tx, + route_manager, + )) + .map(Box::new)?; return Ok(tunnel); } } @@ -755,8 +756,9 @@ impl WireguardMonitor { .map_err(Error::TunnelError)?; #[cfg(target_os = "windows")] - let tunnel = WgGoTunnel::start_tunnel(runtime, config, log_path, route_manager, setup_done_tx) - .map_err(Error::TunnelError)?; + let tunnel = + WgGoTunnel::start_tunnel(runtime, config, log_path, route_manager, setup_done_tx) + .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 diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index df2500c7be07..9cfda42699ca 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -14,9 +14,9 @@ use ipnetwork::IpNetwork; #[cfg(daita)] use std::ffi::CString; #[cfg(unix)] -use std::sync::{Arc, Mutex}; -#[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(unix)] +use std::sync::{Arc, Mutex}; use std::{ future::Future, path::{Path, PathBuf}, @@ -275,13 +275,12 @@ impl WgGoTunnel { .map(|ordinal| LoggingContext::new(ordinal, log_path.map(Path::to_owned))) .map_err(TunnelError::LoggingError)?; - let socket_update_cb = runtime - .block_on( - route_manager.add_default_route_change_callback(Box::new( + let socket_update_cb = + runtime + .block_on(route_manager.add_default_route_change_callback(Box::new( Self::default_route_changed_callback, - )), - ) - .ok(); + ))) + .ok(); if socket_update_cb.is_none() { log::warn!("Failed to register default route callback"); } diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index e5f0aa7c63ec..fb4dfcbb2220 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -1104,7 +1104,10 @@ impl Tunnel for WgNtTunnel { } #[cfg(daita)] - fn start_daita(&mut self, _: talpid_tunnel_config_client::DaitaSettings) -> std::result::Result<(), crate::TunnelError> { + fn start_daita( + &mut self, + _: talpid_tunnel_config_client::DaitaSettings, + ) -> std::result::Result<(), crate::TunnelError> { self.spawn_machinist().map_err(|error| { log::error!( "{}", From 0f91887be47f62d906df7d43488fa4ad6bc35a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 14 Jan 2025 17:55:19 +0100 Subject: [PATCH 15/44] Fix maybenot-ffi linkage --- wireguard-go-rs/build.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 7e87964b256f..df879527fba1 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -153,6 +153,8 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { go_build.env("CC", "zig cc -target aarch64-windows"); } } + + go_build.env("CGO_LDFLAGS", format!("-L{}", target_dir.to_str().unwrap())); } Os::Linux => { let out_file = format!("{out_dir}/libwg.a"); @@ -252,12 +254,12 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { let artifacts_dir = tmp_build_dir.join(target_triple).join(profile); // Copy library to actual target dir - for filename in ["maybenot_ffi.dll", "maybenot_ffi.lib"] { - fs::copy( - artifacts_dir.join(filename), - out_dir.as_ref().join(filename), - ) - .with_context(|| format!("Failed to copy {filename}"))?; + for (src, dest) in [ + ("maybenot_ffi.dll", "maybenot.dll"), + ("maybenot_ffi.dll.lib", "maybenot.lib"), + ] { + fs::copy(artifacts_dir.join(src), out_dir.as_ref().join(dest)) + .with_context(|| format!("Failed to copy {src}"))?; } Ok(()) From 24d91c6530670fee211b7ad0106896521826c320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 14 Jan 2025 19:59:26 +0100 Subject: [PATCH 16/44] Don't trigger rerun if build artifacts change for wireguard-go-rs on Windows This speeds up the build considerably by not always triggering a rebuild. Replacing the artifacts and expecting a rebuild does not seem to be a legitimate use case anyway --- wireguard-go-rs/build.rs | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index df879527fba1..676927aef6e8 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -123,26 +123,16 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { let dll_path = target_dir.join("libwg.dll"); - println!("cargo::rerun-if-changed={}", dll_path.display()); - println!( - "cargo::rerun-if-changed={}", - target_dir.join("libwg.lib").display() - ); - go_build .args(["build", "-v"]) .arg("-o") .arg(&dll_path) - .args(["--tags", "daita"]); - // Build dynamic lib - go_build.args(["-buildmode", "c-shared"]); - - go_build.env("GOOS", "windows"); - - generate_windows_lib(target_arch, target_dir)?; - - println!("cargo::rustc-link-search={}", target_dir.to_str().unwrap()); - println!("cargo::rustc-link-lib=dylib=libwg"); + .args(["--tags", "daita"]) + // Build DLL + .args(["-buildmode", "c-shared"]) + // Needed for linking against maybenot-ffi + .env("CGO_LDFLAGS", format!("-L{}", target_dir.to_str().unwrap())) + .env("GOOS", "windows"); // Build using zig match target_arch { @@ -154,7 +144,10 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { } } - go_build.env("CGO_LDFLAGS", format!("-L{}", target_dir.to_str().unwrap())); + generate_windows_lib(target_arch, target_dir)?; + + println!("cargo::rustc-link-search={}", target_dir.to_str().unwrap()); + println!("cargo::rustc-link-lib=dylib=libwg"); } Os::Linux => { let out_file = format!("{out_dir}/libwg.a"); @@ -253,13 +246,14 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { let artifacts_dir = tmp_build_dir.join(target_triple).join(profile); - // Copy library to actual target dir - for (src, dest) in [ + // Copy library to desired target dir + for (src_filename, dest_filename) in [ ("maybenot_ffi.dll", "maybenot.dll"), ("maybenot_ffi.dll.lib", "maybenot.lib"), ] { - fs::copy(artifacts_dir.join(src), out_dir.as_ref().join(dest)) - .with_context(|| format!("Failed to copy {src}"))?; + let dest = out_dir.as_ref().join(dest_filename); + fs::copy(artifacts_dir.join(src_filename), &dest) + .with_context(|| format!("Failed to copy {src_filename}"))?; } Ok(()) From 0eb89ceb5272a8f9cd8d0715320f53413d5a3664 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 09:12:35 +0100 Subject: [PATCH 17/44] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b47012e17297..1508a76106d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ Line wrap the file at 100 chars. Th ### Added #### Windows - Add support for Windows ARM64. +- Add support for DAITA V2. +- Add back wireguard-go (userspace WireGuard) support. ### Changed - (Linux and macOS only) Update to DAITA v2. The main difference is that many different machines are From 04772fccb6229e97309096f62ca1fa86fbe3ccca Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 09:20:12 +0100 Subject: [PATCH 18/44] Remove more use of `#[cfg(daita)]` --- talpid-wireguard/src/ephemeral.rs | 2 -- talpid-wireguard/src/lib.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 903ccf4c3bcb..1d7f4f395505 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -148,7 +148,6 @@ async fn config_ephemeral_peers_inner( } config.exit_peer_mut().psk = exit_ephemeral_peer.psk; - #[cfg(daita)] if config.daita { log::trace!("Enabling constant packet size for entry peer"); config.entry_peer.constant_packet_size = true; @@ -166,7 +165,6 @@ async fn config_ephemeral_peers_inner( ) .await?; - #[cfg(daita)] if config.daita { let Some(daita) = daita else { unreachable!("missing DAITA settings"); diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 3d6c2b4471f1..863b767ae1bb 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -299,7 +299,6 @@ impl WireguardMonitor { let config = config.clone(); let iface_name = iface_name.clone(); tokio::task::spawn(async move { - #[cfg(daita)] if config.daita { // TODO: For now, we assume the MTU during the tunnel lifetime. // We could instead poke maybenot whenever we detect changes to it. @@ -1039,7 +1038,6 @@ pub(crate) trait Tunnel: Send + Sync { ) -> Pin> + Send + 'a>>; #[cfg(daita)] /// A [`Tunnel`] capable of using DAITA. - #[cfg(daita)] fn start_daita(&mut self, settings: DaitaSettings) -> std::result::Result<(), TunnelError>; } From c9c9e12376bb8e6691f4f56d970d23907637cfb2 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 09:31:00 +0100 Subject: [PATCH 19/44] Compile `wireguard-go-rs` from unsupported host is a hard error --- wireguard-go-rs/build.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 676927aef6e8..235cadbb5e09 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -64,18 +64,19 @@ impl AndroidTarget { } } -fn host_os() -> anyhow::Result { +const fn host_os() -> Os { // this ugliness is a limitation of rust, where we can't directly // access the target triple of the build script. - if cfg!(target_os = "windows") { - Ok(Os::Windows) + const HOST: Os = if cfg!(target_os = "windows") { + Os::Windows } else if cfg!(target_os = "linux") { - Ok(Os::Linux) + Os::Linux } else if cfg!(target_os = "macos") { - Ok(Os::Macos) + Os::Macos } else { - bail!("Unsupported host OS") - } + panic!("Unsupported host OS") + }; + HOST } fn host_arch() -> anyhow::Result { @@ -104,7 +105,7 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { go_build.env("CGO_ENABLED", "1").current_dir("./libwg"); // are we cross compiling? - let cross_compiling = host_os()? != target_os || host_arch()? != target_arch; + let cross_compiling = host_os() != target_os || host_arch()? != target_arch; match target_arch { Arch::Amd64 => go_build.env("GOARCH", "amd64"), From 938f8e2d343cb0e60de58575b9ba2fc415f3f896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 15 Jan 2025 09:43:16 +0100 Subject: [PATCH 20/44] Check out wireguard-go-rs submodule in Windows daemon workflow --- .github/workflows/daemon.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/daemon.yml b/.github/workflows/daemon.yml index 2727d6697b54..ee7dcc77aee1 100644 --- a/.github/workflows/daemon.yml +++ b/.github/workflows/daemon.yml @@ -130,7 +130,9 @@ jobs: uses: actions/checkout@v4 - name: Checkout submodules - run: git submodule update --init --depth=1 + run: | + git submodule update --init --depth=1 + git submodule update --init --recursive --depth=1 wireguard-go-rs - name: Install Protoc # NOTE: ARM runner already has protoc From ff91a94156ec0cda2c99767eb53ae3d7d647d324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 15 Jan 2025 09:50:02 +0100 Subject: [PATCH 21/44] Fix comments in wireguard-go-rs --- wireguard-go-rs/build.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 235cadbb5e09..6d7d82dad2de 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -294,7 +294,6 @@ fn find_file( let path = entry.path(); if path.is_dir() { if let Some(result) = find_file(&path, condition)? { - // TODO: distinguish between err and no result return Ok(Some(result)); } } @@ -313,7 +312,7 @@ fn find_msbuild_exe() -> anyhow::Result { .context("msbuild.exe not found in PATH") } -/// Generate exports.def from wireguard-go source +/// Generate lib from export fn generate_lib_from_exports_def(arch: Arch, exports_path: impl AsRef) -> anyhow::Result<()> { let lib_path = exports_path .as_ref() From d1b2a0bc18b2244cc9d27e2cccfa8a56c9d328e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 15 Jan 2025 09:52:55 +0100 Subject: [PATCH 22/44] Update udeps workflow for libwg --- .github/workflows/rust-unused-dependencies.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust-unused-dependencies.yml b/.github/workflows/rust-unused-dependencies.yml index 69e253231bf3..0f2d7ea0b2cc 100644 --- a/.github/workflows/rust-unused-dependencies.yml +++ b/.github/workflows/rust-unused-dependencies.yml @@ -103,11 +103,17 @@ jobs: uses: actions/checkout@v4 - name: Checkout wireguard-go submodule - if: matrix.os == 'macos-latest' run: | git config --global --add safe.directory '*' + git submodule update --init --depth=1 git submodule update --init --recursive --depth=1 wireguard-go-rs + - name: Install msbuild + if: matrix.os == 'windows-latest' + uses: microsoft/setup-msbuild@v1.0.2 + with: + vs-version: 16 + - name: Install Protoc uses: arduino/setup-protoc@v3 with: From 8311de52a8f081f1a84d555f59b6b1fe4451398c Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 09:53:33 +0100 Subject: [PATCH 23/44] Compiling `wireguard-go-rs` to unsupported target arch is a hard error --- wireguard-go-rs/build.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 6d7d82dad2de..396655305cf9 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -79,14 +79,15 @@ const fn host_os() -> Os { HOST } -fn host_arch() -> anyhow::Result { - if cfg!(target_arch = "x86_64") { - Ok(Arch::Amd64) +const fn host_arch() -> Arch { + const ARCH: Arch = if cfg!(target_arch = "x86_64") { + Arch::Amd64 } else if cfg!(target_arch = "aarch64") { - Ok(Arch::Arm64) + Arch::Arm64 } else { - bail!("Unsupported host architecture") - } + panic!("Unsupported host architecture") + }; + ARCH } /// Compile libwg as a library and place it in `OUT_DIR`. @@ -105,7 +106,7 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { go_build.env("CGO_ENABLED", "1").current_dir("./libwg"); // are we cross compiling? - let cross_compiling = host_os() != target_os || host_arch()? != target_arch; + let cross_compiling = host_os() != target_os || host_arch() != target_arch; match target_arch { Arch::Amd64 => go_build.env("GOARCH", "amd64"), From 26a36bd823d9b9fcb0653baf602822aada60f58c Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 10:15:08 +0100 Subject: [PATCH 24/44] Search for `lib.exe` when cross-compiling --- wireguard-go-rs/build.rs | 64 +++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 396655305cf9..230d6c3e529a 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -11,20 +11,16 @@ use std::{ use anyhow::{anyhow, bail, Context}; fn main() -> anyhow::Result<()> { - let target_os = env::var("CARGO_CFG_TARGET_OS").context("Missing 'CARGO_CFG_TARGET_OS")?; - // Mark "daita" as a conditional configuration flag println!("cargo::rustc-check-cfg=cfg(daita)"); // Rerun build-script if libwg (or wireguard-go) is changed println!("cargo::rerun-if-changed=libwg"); - match target_os.as_str() { - "windows" => build_desktop_lib(Os::Windows)?, - "linux" => build_desktop_lib(Os::Linux)?, - "macos" => build_desktop_lib(Os::Macos)?, - "android" => build_android_dynamic_lib()?, - _ => {} + let target_os = target_os()?; + match target_os { + Os::Windows | Os::Linux | Os::Macos => build_desktop_lib(target_os)?, + Os::Android => build_android_dynamic_lib()?, } Ok(()) @@ -35,6 +31,7 @@ enum Os { Windows, Macos, Linux, + Android, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -79,6 +76,17 @@ const fn host_os() -> Os { HOST } +fn target_os() -> anyhow::Result { + let target_os = env::var("CARGO_CFG_TARGET_OS").context("Missing 'CARGO_CFG_TARGET_OS")?; + match target_os.as_str() { + "windows" => Ok(Os::Windows), + "linux" => Ok(Os::Linux), + "macos" => Ok(Os::Macos), + "android" => Ok(Os::Android), + _ => bail!("Unsupported target os: {target_os}"), + } +} + const fn host_arch() -> Arch { const ARCH: Arch = if cfg!(target_arch = "x86_64") { Arch::Amd64 @@ -90,17 +98,21 @@ const fn host_arch() -> Arch { ARCH } +fn target_arch() -> anyhow::Result { + let target_arch = + env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; + match target_arch.as_str() { + "x86_64" => Ok(Arch::Amd64), + "aarch64" => Ok(Arch::Arm64), + _ => bail!("Unsupported architecture: {target_arch}"), + } +} + /// Compile libwg as a library and place it in `OUT_DIR`. fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; - let target_arch = - env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; - let target_arch = match target_arch.as_str() { - "x86_64" => Arch::Amd64, - "aarch64" => Arch::Arm64, - _ => bail!("Unsupported architecture: {target_arch}"), - }; + let target_arch = target_arch()?; let mut go_build = Command::new("go"); go_build.env("CGO_ENABLED", "1").current_dir("./libwg"); @@ -114,6 +126,7 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { }; match target_os { + Os::Android => bail!("Android is not a desktop platform!"), Os::Windows => { let target_dir = Path::new(&out_dir) .ancestors() @@ -270,6 +283,7 @@ fn generate_windows_lib(arch: Arch, out_dir: impl AsRef) -> anyhow::Result .context("Failed to generate lib from exports.def") } +/// Find the correct lib.exe for this host and the target arch. fn find_lib_exe() -> anyhow::Result { let msbuild_exe = find_msbuild_exe()?; @@ -279,8 +293,24 @@ fn find_lib_exe() -> anyhow::Result { .nth(4) .context("Unexpected msbuild.exe path")?; - // TODO: Make this arch agnostic (host AND target) - let path_is_lib_exe = |file: &Path| file.ends_with("Hostx64/x64/lib.exe"); + // This pattern can be found by browsing `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\\bin\` + let lib_exe_host = match host_arch() { + Arch::Amd64 => "Hostx64", + Arch::Arm64 => "Hostarm64", + }; + + // This pattern can be found by browsing `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\\bin\\` + let lib_exe_target = match target_arch()? { + Arch::Amd64 => "x64", + Arch::Arm64 => "arm64", + }; + + let lib_exe_pattern = format!( + "{host}/{target}/lib.exe", + host = lib_exe_host, + target = lib_exe_target, + ); + let path_is_lib_exe = |file: &Path| file.ends_with(&lib_exe_pattern); find_file(search_path, &path_is_lib_exe)?.context("No lib.exe relative to msbuild.exe") } From feb0229b43efe8bec1d89503f4d6eaf07625418f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 15 Jan 2025 09:54:25 +0100 Subject: [PATCH 25/44] Pack libwg.dll and maybenot.dll for wireguard-go --- build.sh | 2 ++ desktop/packages/mullvad-vpn/tasks/distribution.js | 2 ++ wireguard-go-rs/build.rs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 904ca3aabf91..6d76dc017f1f 100755 --- a/build.sh +++ b/build.sh @@ -265,6 +265,8 @@ function build { mullvad-problem-report.exe talpid_openvpn_plugin.dll mullvad-setup.exe + libwg.dll + maybenot_ffi.dll ) fi diff --git a/desktop/packages/mullvad-vpn/tasks/distribution.js b/desktop/packages/mullvad-vpn/tasks/distribution.js index d05fdd4a6991..adb95419fe91 100644 --- a/desktop/packages/mullvad-vpn/tasks/distribution.js +++ b/desktop/packages/mullvad-vpn/tasks/distribution.js @@ -177,6 +177,8 @@ function newConfig() { ), to: '.', }, + { from: distAssets(path.join('${env.DIST_SUBDIR}', 'libwg.dll')), to: '.' }, + { from: distAssets(path.join('${env.DIST_SUBDIR}', 'maybenot_ffi.dll')), to: '.' }, { from: distAssets('maybenot_machines'), to: '.' }, ], }, diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 230d6c3e529a..98eb4e64d189 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -263,7 +263,7 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { // Copy library to desired target dir for (src_filename, dest_filename) in [ - ("maybenot_ffi.dll", "maybenot.dll"), + ("maybenot_ffi.dll", "maybenot_ffi.dll"), ("maybenot_ffi.dll.lib", "maybenot.lib"), ] { let dest = out_dir.as_ref().join(dest_filename); From dc7fe2851a690dad7658261e3c4ebd2e5c7bc1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 15 Jan 2025 10:58:22 +0100 Subject: [PATCH 26/44] Do not pack maybenot_machines on Windows --- desktop/packages/mullvad-vpn/tasks/distribution.js | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/packages/mullvad-vpn/tasks/distribution.js b/desktop/packages/mullvad-vpn/tasks/distribution.js index adb95419fe91..8ab8a35882e1 100644 --- a/desktop/packages/mullvad-vpn/tasks/distribution.js +++ b/desktop/packages/mullvad-vpn/tasks/distribution.js @@ -179,7 +179,6 @@ function newConfig() { }, { from: distAssets(path.join('${env.DIST_SUBDIR}', 'libwg.dll')), to: '.' }, { from: distAssets(path.join('${env.DIST_SUBDIR}', 'maybenot_ffi.dll')), to: '.' }, - { from: distAssets('maybenot_machines'), to: '.' }, ], }, From a10121e9c5c9a4a1db76f3769398d199f228d7b2 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 15:08:18 +0100 Subject: [PATCH 27/44] Update build instructions for Windows ARM --- BuildInstructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BuildInstructions.md b/BuildInstructions.md index cc11d9289b9b..d06076ab64e8 100644 --- a/BuildInstructions.md +++ b/BuildInstructions.md @@ -154,7 +154,7 @@ In addition to the above requirements: the Electron app: ``` - pushd gui + pushd desktop/packages/mullvad-vpn npm install --target_arch=x64 grpc-tools popd ``` From 396f62c8d9c4aa8b428150d4a35705a27eb4b0a0 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 17:11:47 +0100 Subject: [PATCH 28/44] Add more elaborate error message --- wireguard-go-rs/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 98eb4e64d189..30da311da474 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -266,9 +266,9 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { ("maybenot_ffi.dll", "maybenot_ffi.dll"), ("maybenot_ffi.dll.lib", "maybenot.lib"), ] { + let src = artifacts_dir.join(src_filename); let dest = out_dir.as_ref().join(dest_filename); - fs::copy(artifacts_dir.join(src_filename), &dest) - .with_context(|| format!("Failed to copy {src_filename}"))?; + fs::copy(&src, &dest).with_context(|| format!("Failed to copy {src_filename}",))?; } Ok(()) From a205d81ab6853105121b87beaec4a4751ade6cfe Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 15 Jan 2025 17:30:23 +0100 Subject: [PATCH 29/44] Build `maybenot` with inherited `--profile` --- wireguard-go-rs/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 30da311da474..a0e3b4d1a5d1 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -255,6 +255,7 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { // Set temporary target dir to prevent deadlock .env("CARGO_TARGET_DIR", &tmp_build_dir) .arg("build") + .args(["--profile", &profile]) .args(["--target", &target_triple]); exec(build_command)?; From 30a730edc7b556721ae60668517063d22eecd14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 08:51:21 +0100 Subject: [PATCH 30/44] Mention zig 0.14 in build instructions --- BuildInstructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BuildInstructions.md b/BuildInstructions.md index d06076ab64e8..b81ba0d9c7b7 100644 --- a/BuildInstructions.md +++ b/BuildInstructions.md @@ -95,7 +95,7 @@ The host has to have the following installed: - `bash` installed as well as a few base unix utilities, including `sed` and `tail`. You are recommended to use [Git for Windows]. -- `zig` installed and available in `%PATH%`. The latest official release should be fine: https://ziglang.org/download/. +- `zig` installed and available in `%PATH%`. 0.14 or later is recommended: https://ziglang.org/download/. - `msbuild.exe` available in `%PATH%`. If you installed Visual Studio Community edition, the binary can be found under: From 0bb18d6a7866e9056f38fb706f5b70ca0e154c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 09:36:21 +0100 Subject: [PATCH 31/44] Fix debug profile selection in wireguard-go-rs --- wireguard-go-rs/build.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index a0e3b4d1a5d1..4a99a9a6d684 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -233,7 +233,11 @@ fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { // Build dynamically library for maybenot fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; - let profile = env::var("PROFILE").context("Missing 'PROFILE'")?; + let profile_category = env::var("PROFILE").context("Missing 'PROFILE'")?; + let profile = match profile_category.as_str() { + "release" => "release", + _ => "dev", + }; let mut build_command = Command::new("cargo"); @@ -255,12 +259,12 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { // Set temporary target dir to prevent deadlock .env("CARGO_TARGET_DIR", &tmp_build_dir) .arg("build") - .args(["--profile", &profile]) + .args(["--profile", profile]) .args(["--target", &target_triple]); exec(build_command)?; - let artifacts_dir = tmp_build_dir.join(target_triple).join(profile); + let artifacts_dir = tmp_build_dir.join(target_triple).join(profile_category); // Copy library to desired target dir for (src_filename, dest_filename) in [ From bb23752099fcb6f01f85203cc347fbf152990a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 13:05:38 +0100 Subject: [PATCH 32/44] Exclude maybenot_ffi from toml file on Android --- wireguard-go-rs/Cargo.toml | 2 +- wireguard-go-rs/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wireguard-go-rs/Cargo.toml b/wireguard-go-rs/Cargo.toml index 375e248f1c08..f7572ab14286 100644 --- a/wireguard-go-rs/Cargo.toml +++ b/wireguard-go-rs/Cargo.toml @@ -16,7 +16,7 @@ zeroize = "1.8.1" # This is done, instead of using the makefile in wireguard-go to build maybenot-ffi into its archive, to prevent # name clashes induced by link-time optimization. # NOTE: the version of maybenot-ffi below must be the same as the version checked into the wireguard-go submodule -[target.'cfg(unix)'.dependencies] +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] maybenot-ffi = "2.0.1" [target.'cfg(target_os = "windows")'.dependencies] diff --git a/wireguard-go-rs/src/lib.rs b/wireguard-go-rs/src/lib.rs index b080d307b2b1..a98c9e056a9f 100644 --- a/wireguard-go-rs/src/lib.rs +++ b/wireguard-go-rs/src/lib.rs @@ -30,7 +30,7 @@ pub type LoggingCallback = unsafe extern "system" fn(level: WgLogLevel, msg: *const c_char, context: LoggingContext); // Make symbols from maybenot-ffi visible to wireguard-go -#[cfg(all(daita, unix))] +#[cfg(all(daita, any(target_os = "linux", target_os = "macos")))] use maybenot_ffi as _; /// A wireguard-go tunnel From 9f046309f67c09ad20e4769b277a9a82490bee34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 13:19:23 +0100 Subject: [PATCH 33/44] Log DAITA v2 request --- talpid-tunnel-config-client/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index 0be95672595e..f518d05d2681 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -140,10 +140,14 @@ pub async fn request_ephemeral_peer_with( wg_ephemeral_peer_pubkey: ephemeral_pubkey.as_bytes().to_vec(), post_quantum: pq_request, daita: None, - daita_v2: enable_daita.then(|| proto::DaitaRequestV2 { - level: i32::from(proto::DaitaLevel::LevelDefault), - platform: i32::from(get_platform()), - version: DAITA_VERSION, + daita_v2: enable_daita.then(|| { + let platform = get_platform(); + log::trace!("DAITA v2 platform: {platform:?}"); + proto::DaitaRequestV2 { + level: i32::from(proto::DaitaLevel::LevelDefault), + platform: i32::from(platform), + version: DAITA_VERSION, + } }), }) .await From e9c583b97b01461630447a4d9b283efdb891c66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 13:33:16 +0100 Subject: [PATCH 34/44] Clean up wireguard-go-rs build script --- wireguard-go-rs/build.rs | 240 ++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 115 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 4a99a9a6d684..f13f0341162f 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -14,13 +14,18 @@ fn main() -> anyhow::Result<()> { // Mark "daita" as a conditional configuration flag println!("cargo::rustc-check-cfg=cfg(daita)"); + // Enable the DAITA (rust) feature flag + println!(r#"cargo::rustc-cfg=daita"#); + // Rerun build-script if libwg (or wireguard-go) is changed println!("cargo::rerun-if-changed=libwg"); - let target_os = target_os()?; - match target_os { - Os::Windows | Os::Linux | Os::Macos => build_desktop_lib(target_os)?, - Os::Android => build_android_dynamic_lib()?, + let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; + match target_os()? { + Os::Windows => build_windows_dynamic_lib(&out_dir)?, + Os::Linux => build_linux_static_lib(&out_dir)?, + Os::Macos => build_macos_static_lib(&out_dir)?, + Os::Android => build_android_dynamic_lib(&out_dir)?, } Ok(()) @@ -108,128 +113,137 @@ fn target_arch() -> anyhow::Result { } } -/// Compile libwg as a library and place it in `OUT_DIR`. -fn build_desktop_lib(target_os: Os) -> anyhow::Result<()> { - let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; +/// Compile libwg and maybenot and place them in the target dir relative to `OUT_DIR`. +fn build_windows_dynamic_lib(out_dir: &str) -> anyhow::Result<()> { + let target_dir = Path::new(out_dir) + .ancestors() + .nth(3) + .context("Failed to find target dir")?; + build_shared_maybenot_lib(target_dir).context("Failed to build maybenot")?; + + let dll_path = target_dir.join("libwg.dll"); + let mut go_build = Command::new("go"); + go_build + .env("CGO_ENABLED", "1") + .current_dir("./libwg") + .args(["build", "-v"]) + .arg("-o") + .arg(&dll_path) + .args(["--tags", "daita"]) + // Build DLL + .args(["-buildmode", "c-shared"]) + // Needed for linking against maybenot-ffi + .env("CGO_LDFLAGS", format!("-L{}", target_dir.to_str().unwrap())) + .env("GOOS", "windows"); let target_arch = target_arch()?; + // We explicitly use zig for compiling libwg. Any MinGW-compatible toolchain should work. + match target_arch { + Arch::Amd64 => { + go_build.env("CC", "zig cc -target x86_64-windows"); + go_build.env("GOARCH", "amd64"); + } + Arch::Arm64 => { + go_build.env("CC", "zig cc -target aarch64-windows"); + go_build.env("GOARCH", "arm64"); + } + } - let mut go_build = Command::new("go"); - go_build.env("CGO_ENABLED", "1").current_dir("./libwg"); + generate_windows_lib(target_arch, target_dir)?; + + exec(go_build)?; - // are we cross compiling? - let cross_compiling = host_os() != target_os || host_arch() != target_arch; + println!("cargo::rustc-link-search={}", target_dir.to_str().unwrap()); + println!("cargo::rustc-link-lib=dylib=libwg"); + Ok(()) +} + +/// Compile libwg and place it in `OUT_DIR`. +fn build_linux_static_lib(out_dir: &str) -> anyhow::Result<()> { + let out_file = format!("{out_dir}/libwg.a"); + let mut go_build = Command::new("go"); + go_build + .env("CGO_ENABLED", "1") + .current_dir("./libwg") + .args(["build", "-v", "-o", &out_file]) + .args(["--tags", "daita"]) + // Build static lib + .args(["-buildmode", "c-archive"]) + .env("GOOS", "linux"); + + let target_arch = target_arch()?; match target_arch { Arch::Amd64 => go_build.env("GOARCH", "amd64"), Arch::Arm64 => go_build.env("GOARCH", "arm64"), }; - match target_os { - Os::Android => bail!("Android is not a desktop platform!"), - Os::Windows => { - let target_dir = Path::new(&out_dir) - .ancestors() - .nth(3) - .context("Failed to find target dir")?; - - // building maybenot is required for DAITA - wireguard-go will link to it at runtime. - build_shared_maybenot_lib(target_dir).context("Failed to build maybenot")?; - - let dll_path = target_dir.join("libwg.dll"); - - go_build - .args(["build", "-v"]) - .arg("-o") - .arg(&dll_path) - .args(["--tags", "daita"]) - // Build DLL - .args(["-buildmode", "c-shared"]) - // Needed for linking against maybenot-ffi - .env("CGO_LDFLAGS", format!("-L{}", target_dir.to_str().unwrap())) - .env("GOOS", "windows"); - - // Build using zig - match target_arch { - Arch::Amd64 => { - go_build.env("CC", "zig cc -target x86_64-windows"); - } - Arch::Arm64 => { - go_build.env("CC", "zig cc -target aarch64-windows"); - } - } + if is_cross_compiling()? { + match target_arch { + Arch::Arm64 => go_build.env("CC", "aarch64-linux-gnu-gcc"), + Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), + }; + } + + exec(go_build)?; - generate_windows_lib(target_arch, target_dir)?; + // make sure to link to the resulting binary + println!("cargo::rustc-link-search={out_dir}"); + println!("cargo::rustc-link-lib=static=wg"); - println!("cargo::rustc-link-search={}", target_dir.to_str().unwrap()); - println!("cargo::rustc-link-lib=dylib=libwg"); - } - Os::Linux => { - let out_file = format!("{out_dir}/libwg.a"); - go_build - .args(["build", "-v", "-o", &out_file]) - .args(["--tags", "daita"]); - - // Build static lib - go_build.args(["-buildmode", "c-archive"]); - - go_build.env("GOOS", "linux"); - - if cross_compiling { - match target_arch { - Arch::Arm64 => go_build.env("CC", "aarch64-linux-gnu-gcc"), - Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), - }; - } + Ok(()) +} - // make sure to link to the resulting binary - println!("cargo::rustc-link-search={out_dir}"); - println!("cargo::rustc-link-lib=static=wg"); - } - Os::Macos => { - let out_file = format!("{out_dir}/libwg.a"); - go_build - .args(["build", "-v", "-o", &out_file]) - .args(["--tags", "daita"]); - - // Build static lib - go_build.args(["-buildmode", "c-archive"]); - - go_build.env("GOOS", "darwin"); - - if cross_compiling { - let sdkroot = env::var("SDKROOT").context("Missing 'SDKROOT'")?; - - let c_arch = match target_arch { - Arch::Amd64 => "x86_64", - Arch::Arm64 => "arm64", - }; - - let xcrun_output = - exec(Command::new("xcrun").args(["-sdk", &sdkroot, "--find", "clang"]))?; - go_build.env("CC", xcrun_output); - - let cflags = format!("-isysroot {sdkroot} -arch {c_arch} -I{sdkroot}/usr/include"); - go_build.env("CFLAGS", cflags); - go_build.env("CGO_CFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); - go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); - go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); - } +/// Compile libwg and place it in `OUT_DIR`. +fn build_macos_static_lib(out_dir: &str) -> anyhow::Result<()> { + let out_file = format!("{out_dir}/libwg.a"); + let mut go_build = Command::new("go"); + go_build + .env("CGO_ENABLED", "1") + .current_dir("./libwg") + .args(["build", "-v", "-o", &out_file]) + .args(["--tags", "daita"]) + // Build static lib + .args(["-buildmode", "c-archive"]) + .env("GOOS", "darwin"); - // make sure to link to the resulting binary - println!("cargo::rustc-link-search={out_dir}"); - println!("cargo::rustc-link-lib=static=wg"); - } + let target_arch = target_arch()?; + match target_arch { + Arch::Amd64 => go_build.env("GOARCH", "amd64"), + Arch::Arm64 => go_build.env("GOARCH", "arm64"), + }; + + if is_cross_compiling()? { + let sdkroot = env::var("SDKROOT").context("Missing 'SDKROOT'")?; + + let c_arch = match target_arch { + Arch::Amd64 => "x86_64", + Arch::Arm64 => "arm64", + }; + + let xcrun_output = exec(Command::new("xcrun").args(["-sdk", &sdkroot, "--find", "clang"]))?; + go_build.env("CC", xcrun_output); + + let cflags = format!("-isysroot {sdkroot} -arch {c_arch} -I{sdkroot}/usr/include"); + go_build.env("CFLAGS", cflags); + go_build.env("CGO_CFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); + go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); + go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); } exec(go_build)?; - // Enable the DAITA (rust) feature flag - println!(r#"cargo::rustc-cfg=daita"#); + println!("cargo::rustc-link-search={out_dir}"); + println!("cargo::rustc-link-lib=static=wg"); Ok(()) } +/// Return whether compiling for an architecture or OS other than the host +fn is_cross_compiling() -> anyhow::Result { + Ok(host_os() != target_os()? || host_arch() != target_arch()?) +} + // Build dynamically library for maybenot fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; @@ -279,8 +293,8 @@ fn build_shared_maybenot_lib(out_dir: impl AsRef) -> anyhow::Result<()> { Ok(()) } -/// Generate a library for the exported functions. Required for linking. -/// This requires `lib.exe` in the path. +/// Generate a library for the exported functions. Required for load-time linking. +/// This requires `msbuild.exe` in the path. fn generate_windows_lib(arch: Arch, out_dir: impl AsRef) -> anyhow::Result<()> { let exports_def_path = out_dir.as_ref().join("exports.def"); generate_exports_def(&exports_def_path).context("Failed to generate exports.def")?; @@ -288,7 +302,7 @@ fn generate_windows_lib(arch: Arch, out_dir: impl AsRef) -> anyhow::Result .context("Failed to generate lib from exports.def") } -/// Find the correct lib.exe for this host and the target arch. +/// Find the correct `lib.exe` for this host and the target arch. fn find_lib_exe() -> anyhow::Result { let msbuild_exe = find_msbuild_exe()?; @@ -387,17 +401,14 @@ fn generate_exports_def(exports_path: impl AsRef) -> anyhow::Result<()> { writeln!(file, "LIBRARY libwg").context("Write LIBRARY statement")?; writeln!(file, "EXPORTS").context("Write EXPORTS statement")?; - let mut libwg_exports = vec![]; for path in &[ "./libwg/libwg.go", "./libwg/libwg_windows.go", "./libwg/libwg_daita.go", ] { - libwg_exports.extend(gather_exports(path).context("Failed to find exports")?); - } - - for export in libwg_exports { - writeln!(file, "\t{export}").context("Failed to output exported function")?; + for export in gather_exports(path).context("Failed to find exports")? { + writeln!(file, "\t{export}").context("Failed to output exported function")?; + } } Ok(()) @@ -430,8 +441,7 @@ fn gather_exports(go_src_path: impl AsRef) -> anyhow::Result> /// Compile libwg as a dynamic library for android and place it in [`android_output_path`]. // NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. -fn build_android_dynamic_lib() -> anyhow::Result<()> { - let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; +fn build_android_dynamic_lib(out_dir: &str) -> anyhow::Result<()> { let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; let target = AndroidTarget::from_str(&target_triple)?; From a58b2620d0e2fd5114cc200dd2644800e217ceae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 13:43:19 +0100 Subject: [PATCH 35/44] Target macos and linux when conditionally compiling --- talpid-wireguard/src/wireguard_go/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 9cfda42699ca..0e64797ad537 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -221,7 +221,7 @@ impl WgGoTunnelState { } impl WgGoTunnel { - #[cfg(all(not(target_os = "android"), unix))] + #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn start_tunnel( config: &Config, log_path: Option<&Path>, From e0f6311950c40ab8cf7d57d3096c21a31492e991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 14:46:25 +0100 Subject: [PATCH 36/44] Install zig in actions workflows --- .github/workflows/daemon.yml | 3 +++ .github/workflows/desktop-e2e.yml | 2 ++ .github/workflows/rust-unused-dependencies.yml | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/daemon.yml b/.github/workflows/daemon.yml index ee7dcc77aee1..7977d8ab6862 100644 --- a/.github/workflows/daemon.yml +++ b/.github/workflows/daemon.yml @@ -185,6 +185,9 @@ jobs: with: vs-version: 16 + - name: Install latest zig + uses: mlugg/setup-zig@v1 + - name: Build Windows modules if: steps.cache-windows-modules.outputs.cache-hit != 'true' shell: bash diff --git a/.github/workflows/desktop-e2e.yml b/.github/workflows/desktop-e2e.yml index 20f2f96926a2..a23cdbd6023e 100644 --- a/.github/workflows/desktop-e2e.yml +++ b/.github/workflows/desktop-e2e.yml @@ -200,6 +200,8 @@ jobs: toolchain: stable target: i686-pc-windows-msvc default: true + - name: Install latest zig + uses: mlugg/setup-zig@v1 - name: Install msbuild uses: microsoft/setup-msbuild@v1.0.2 with: diff --git a/.github/workflows/rust-unused-dependencies.yml b/.github/workflows/rust-unused-dependencies.yml index 0f2d7ea0b2cc..eba2735f9c02 100644 --- a/.github/workflows/rust-unused-dependencies.yml +++ b/.github/workflows/rust-unused-dependencies.yml @@ -114,6 +114,10 @@ jobs: with: vs-version: 16 + - name: Install latest zig + if: matrix.os == 'windows-latest' + uses: mlugg/setup-zig@v1 + - name: Install Protoc uses: arduino/setup-protoc@v3 with: From c9f70bee5ad46add94010e282de0711b6340a582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 14:51:31 +0100 Subject: [PATCH 37/44] Update clippy workflow --- .github/workflows/clippy.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index ec021eb956e8..ec1ccb030ed4 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -63,8 +63,17 @@ jobs: sudo apt-get update sudo apt-get install libdbus-1-dev + - name: Install msbuild + if: matrix.os == 'windows-latest' + uses: microsoft/setup-msbuild@v1.0.2 + with: + vs-version: 16 + + - name: Install latest zig + if: matrix.os == 'windows-latest' + uses: mlugg/setup-zig@v1 + - name: Install Go - if: matrix.os == 'linux-latest' || matrix.os == 'macos-latest' uses: actions/setup-go@v5 with: go-version: 1.21.3 From dd31a2b2c16114ebd448a5b3f265f5d81911f18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Thu, 16 Jan 2025 14:53:24 +0100 Subject: [PATCH 38/44] Update copyright notices in libwg --- wireguard-go-rs/libwg/libwg.go | 2 +- wireguard-go-rs/libwg/libwg_android.go | 2 +- wireguard-go-rs/libwg/libwg_daita.go | 2 +- wireguard-go-rs/libwg/libwg_default.go | 2 +- wireguard-go-rs/libwg/libwg_windows.go | 2 +- wireguard-go-rs/libwg/logging/logging.go | 2 +- wireguard-go-rs/libwg/tunnelcontainer/tunnelcontainer.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wireguard-go-rs/libwg/libwg.go b/wireguard-go-rs/libwg/libwg.go index 5dcc9141b214..599234cc2e47 100644 --- a/wireguard-go-rs/libwg/libwg.go +++ b/wireguard-go-rs/libwg/libwg.go @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package main diff --git a/wireguard-go-rs/libwg/libwg_android.go b/wireguard-go-rs/libwg/libwg_android.go index caca9b04d057..06a0b2c81060 100644 --- a/wireguard-go-rs/libwg/libwg_android.go +++ b/wireguard-go-rs/libwg/libwg_android.go @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package main diff --git a/wireguard-go-rs/libwg/libwg_daita.go b/wireguard-go-rs/libwg/libwg_daita.go index b73be376a3e9..3904912bed46 100644 --- a/wireguard-go-rs/libwg/libwg_daita.go +++ b/wireguard-go-rs/libwg/libwg_daita.go @@ -3,7 +3,7 @@ /* SPDX-License-Identifier: Apache-2.0 * - * Copyright (C) 2024 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package main diff --git a/wireguard-go-rs/libwg/libwg_default.go b/wireguard-go-rs/libwg/libwg_default.go index 263c231a6104..3d0e74c1680e 100644 --- a/wireguard-go-rs/libwg/libwg_default.go +++ b/wireguard-go-rs/libwg/libwg_default.go @@ -5,7 +5,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package main diff --git a/wireguard-go-rs/libwg/libwg_windows.go b/wireguard-go-rs/libwg/libwg_windows.go index f384b6609760..aa65bebd875d 100644 --- a/wireguard-go-rs/libwg/libwg_windows.go +++ b/wireguard-go-rs/libwg/libwg_windows.go @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2024 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package main diff --git a/wireguard-go-rs/libwg/logging/logging.go b/wireguard-go-rs/libwg/logging/logging.go index a6782ec39ac4..eb8bed3c9fe3 100644 --- a/wireguard-go-rs/libwg/logging/logging.go +++ b/wireguard-go-rs/libwg/logging/logging.go @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package logging diff --git a/wireguard-go-rs/libwg/tunnelcontainer/tunnelcontainer.go b/wireguard-go-rs/libwg/tunnelcontainer/tunnelcontainer.go index 79eacc2a1756..63c931bf08d3 100644 --- a/wireguard-go-rs/libwg/tunnelcontainer/tunnelcontainer.go +++ b/wireguard-go-rs/libwg/tunnelcontainer/tunnelcontainer.go @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - * Copyright (C) 2020 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2025 Mullvad VPN AB. All Rights Reserved. */ package tunnelcontainer From db76b06e7a7020e7a5e6fb9d16ee36626355f945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 17 Jan 2025 10:22:33 +0100 Subject: [PATCH 39/44] Add build constraints for libwg_windows and libwg_android --- wireguard-go-rs/libwg/libwg_android.go | 3 +++ wireguard-go-rs/libwg/libwg_windows.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/wireguard-go-rs/libwg/libwg_android.go b/wireguard-go-rs/libwg/libwg_android.go index 06a0b2c81060..f34085b80ff8 100644 --- a/wireguard-go-rs/libwg/libwg_android.go +++ b/wireguard-go-rs/libwg/libwg_android.go @@ -1,3 +1,6 @@ +//go:build android +// +build android + /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. diff --git a/wireguard-go-rs/libwg/libwg_windows.go b/wireguard-go-rs/libwg/libwg_windows.go index aa65bebd875d..3d02209d2755 100644 --- a/wireguard-go-rs/libwg/libwg_windows.go +++ b/wireguard-go-rs/libwg/libwg_windows.go @@ -1,3 +1,6 @@ +//go:build windows +// +build windows + /* SPDX-License-Identifier: Apache-2.0 * * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. From d17e6bb8d8098b06c83500e85f0ee46bb79c763b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Wed, 22 Jan 2025 17:21:29 +0100 Subject: [PATCH 40/44] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e4ec60fcf0b3..2b1d8753f1f4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ /dist-assets/mullvad-setup.exe /dist-assets/mullvad-problem-report /dist-assets/mullvad-problem-report.exe +/dist-assets/libwg.dll +/dist-assets/maybenot_ffi.dll /dist-assets/libtalpid_openvpn_plugin.dylib /dist-assets/libtalpid_openvpn_plugin.so /dist-assets/talpid_openvpn_plugin.dll From 28b6fa8314f5ef880358bdb6f45a06e249273250 Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Thu, 23 Jan 2025 11:04:35 +0100 Subject: [PATCH 41/44] Change temporary `DaitaPlatform` for windows to `LinuxWgGo` --- talpid-tunnel-config-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index f518d05d2681..5d2d785dba4e 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -213,7 +213,7 @@ const fn get_platform() -> proto::DaitaPlatform { use proto::DaitaPlatform; const PLATFORM: DaitaPlatform = if cfg!(target_os = "windows") { // FIXME: wggo - DaitaPlatform::WindowsNative + DaitaPlatform::LinuxWgGo } else if cfg!(target_os = "linux") { DaitaPlatform::LinuxWgGo } else if cfg!(target_os = "macos") { From 50b31b950a1a533aeb580af1dbc334dedb0d04fd Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Thu, 23 Jan 2025 11:27:59 +0100 Subject: [PATCH 42/44] Support `FORCE_USERSPACE_WIREGUARD` on windows --- talpid-wireguard/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 863b767ae1bb..ea3207f12da2 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -18,7 +18,7 @@ use std::{ pin::Pin, sync::{mpsc as sync_mpsc, Arc, Mutex}, }; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "windows"))] use std::{env, sync::LazyLock}; #[cfg(not(target_os = "android"))] use talpid_routing::{self, RequiredRoute}; @@ -149,7 +149,7 @@ pub struct WireguardMonitor { obfuscator: Arc>>, } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "windows"))] /// Overrides the preference for the kernel module for WireGuard. static FORCE_USERSPACE_WIREGUARD: LazyLock = LazyLock::new(|| { env::var("TALPID_FORCE_USERSPACE_WIREGUARD") @@ -693,7 +693,7 @@ impl WireguardMonitor { { #[cfg(wireguard_go)] { - let use_userspace_wg = config.daita; + let use_userspace_wg = config.daita || *FORCE_USERSPACE_WIREGUARD; if use_userspace_wg { log::debug!("Using userspace WireGuard implementation"); let tunnel = runtime From 3a4efae3efd0c58559b652055402d14f1e9f14a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 24 Jan 2025 16:11:53 +0100 Subject: [PATCH 43/44] Make start_tunnel async on Windows --- talpid-wireguard/src/lib.rs | 8 +++----- talpid-wireguard/src/wireguard_go/mod.rs | 13 +++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index ea3207f12da2..330a2c76cf9d 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -698,7 +698,6 @@ impl WireguardMonitor { log::debug!("Using userspace WireGuard implementation"); let tunnel = runtime .block_on(Self::open_wireguard_go_tunnel( - runtime, config, log_path, setup_done_tx, @@ -736,7 +735,6 @@ impl WireguardMonitor { #[cfg(wireguard_go)] #[allow(clippy::unused_async)] async fn open_wireguard_go_tunnel( - #[cfg(windows)] runtime: tokio::runtime::Handle, config: &Config, log_path: Option<&Path>, #[cfg(unix)] tun_provider: Arc>, @@ -755,9 +753,9 @@ impl WireguardMonitor { .map_err(Error::TunnelError)?; #[cfg(target_os = "windows")] - let tunnel = - WgGoTunnel::start_tunnel(runtime, config, log_path, route_manager, setup_done_tx) - .map_err(Error::TunnelError)?; + 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 diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 0e64797ad537..1c6fc41fd34c 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -260,8 +260,7 @@ impl WgGoTunnel { } #[cfg(target_os = "windows")] - pub fn start_tunnel( - runtime: tokio::runtime::Handle, + pub async fn start_tunnel( config: &Config, log_path: Option<&Path>, route_manager: talpid_routing::RouteManagerHandle, @@ -275,12 +274,10 @@ impl WgGoTunnel { .map(|ordinal| LoggingContext::new(ordinal, log_path.map(Path::to_owned))) .map_err(TunnelError::LoggingError)?; - let socket_update_cb = - runtime - .block_on(route_manager.add_default_route_change_callback(Box::new( - Self::default_route_changed_callback, - ))) - .ok(); + let socket_update_cb = route_manager + .add_default_route_change_callback(Box::new(Self::default_route_changed_callback)) + .await + .ok(); if socket_update_cb.is_none() { log::warn!("Failed to register default route callback"); } From 0d5ba1a5b6ff2d3b8b37d36cd4997082a7ac5dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 24 Jan 2025 16:38:04 +0100 Subject: [PATCH 44/44] Remove block_in_place --- talpid-wireguard/src/wireguard_go/mod.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 1c6fc41fd34c..a3045659672e 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -548,15 +548,14 @@ impl Tunnel for WgGoTunnel { } async fn get_tunnel_stats(&self) -> Result { - tokio::task::block_in_place(|| { - self.as_state() - .tunnel_handle - .get_config(|cstr| { - Stats::parse_config_str(cstr.to_str().expect("Go strings are always UTF-8")) - }) - .ok_or(TunnelError::GetConfigError)? - .map_err(|error| TunnelError::StatsError(BoxedError::new(error))) - }) + // NOTE: wireguard-go might perform blocking I/O, but it's most likely not a problem + self.as_state() + .tunnel_handle + .get_config(|cstr| { + Stats::parse_config_str(cstr.to_str().expect("Go strings are always UTF-8")) + }) + .ok_or(TunnelError::GetConfigError)? + .map_err(|error| TunnelError::StatsError(BoxedError::new(error))) } fn set_config(