summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-01-10 09:38:31 +0100
committerDavid Lönnhager <david.l@mullvad.net>2025-01-24 17:34:56 +0100
commit1874bbe83ae48c943f061639cfd505bab6c26b34 (patch)
tree64adfe2424fd175211258f97bc3084b49ab6f973
parent2fec6bb168e29ea47976b07c188d0cf792d70b77 (diff)
downloadmullvadvpn-1874bbe83ae48c943f061639cfd505bab6c26b34.tar.xz
mullvadvpn-1874bbe83ae48c943f061639cfd505bab6c26b34.zip
Build wireguard-go via wireguard-go-rs on Windows
-rw-r--r--Cargo.lock1
-rw-r--r--talpid-wireguard/src/ephemeral.rs5
-rw-r--r--talpid-wireguard/src/lib.rs17
-rw-r--r--talpid-wireguard/src/wireguard_go/mod.rs8
-rw-r--r--wireguard-go-rs/Cargo.toml6
-rw-r--r--wireguard-go-rs/build.rs277
-rw-r--r--wireguard-go-rs/src/lib.rs101
7 files changed, 382 insertions, 33 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2bf2110f63..110b6b197a 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 1df0820014..1a61148487 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 1b377e2f4f..205e150eee 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<dyn Tunnel + 'static>)
.map_err(Error::TunnelError)
diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs
index 2bfd8ef987..4698616d05 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 cfaef554cc..b1388ab146 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 1148d4010e..ad485be28d 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<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 = "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<Arch> {
}
}
-/// 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<Path>) -> 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<Path>) -> 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<PathBuf> {
+ 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<Path>,
+ condition: &impl Fn(&Path) -> bool,
+) -> anyhow::Result<Option<PathBuf>> {
+ 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<PathBuf> {
+ 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<Path>) -> 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<Path>) -> 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<Path>) -> anyhow::Result<Vec<String>> {
+ 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 3e75506f61..4386c1f48a 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<LoggingCallback>,
+ logging_context: LoggingContext,
+ ) -> Result<Self, Error> {
+ // 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<LoggingCallback>,
+ 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<LoggingCallback>,
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<LoggingCallback>,
+ logging_context: LoggingContext,
+ ) -> i32;
+
/// Creates a new wireguard tunnel, uses the specific interface name, and file descriptors
/// for the tunnel device and logging.
///