#![allow(unsafe_code)]
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::task;
use wintun_bindings::{load_from_path, Adapter, Session, Wintun};
use crate::OverlayError;
const WINTUN_POOL: &str = "ZLayer";
const WINTUN_RING_BYTES: u32 = 4 * 1024 * 1024;
pub(crate) struct WindowsTun {
_wintun: Wintun,
adapter: Arc<Adapter>,
session: Arc<Session>,
name: String,
}
impl WindowsTun {
pub(crate) fn new(name: &str, _mtu: u32) -> Result<Self, OverlayError> {
let dll_path = locate_wintun_dll().ok_or_else(|| {
OverlayError::NetworkConfig(
"wintun.dll not found (looked in %ProgramData%\\ZLayer\\wintun, next \
to zlayer.exe, and the current directory). Download it from \
https://www.wintun.net and place it alongside the ZLayer binary."
.to_string(),
)
})?;
let wintun = unsafe { load_from_path(&dll_path) }.map_err(|e| {
OverlayError::NetworkConfig(format!(
"failed to load wintun.dll from {}: {e}",
dll_path.display()
))
})?;
let adapter = if let Ok(existing) = Adapter::open(&wintun, name) {
existing
} else {
Adapter::create(&wintun, WINTUN_POOL, name, None).map_err(|e| {
OverlayError::NetworkConfig(format!(
"failed to create Wintun adapter '{name}': {e}"
))
})?
};
let session = adapter.start_session(WINTUN_RING_BYTES).map_err(|e| {
OverlayError::NetworkConfig(format!("failed to start Wintun session on '{name}': {e}"))
})?;
Ok(Self {
_wintun: wintun,
adapter,
session,
name: name.to_string(),
})
}
pub(crate) fn luid_value(&self) -> u64 {
unsafe { self.adapter.get_luid().Value }
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) async fn recv(&self, buf: &mut [u8]) -> Result<usize, OverlayError> {
let session = Arc::clone(&self.session);
let buf_len = buf.len();
let out = task::spawn_blocking(move || -> Result<Vec<u8>, String> {
let pkt = session.receive_blocking().map_err(|e| e.to_string())?;
let bytes = pkt.bytes();
if bytes.len() > buf_len {
return Err(format!(
"packet too large for buffer: got {} bytes, buffer holds {}",
bytes.len(),
buf_len
));
}
Ok(bytes.to_vec())
})
.await
.map_err(|e| OverlayError::NetworkConfig(format!("wintun recv join error: {e}")))?
.map_err(OverlayError::NetworkConfig)?;
let n = out.len();
buf[..n].copy_from_slice(&out);
Ok(n)
}
pub(crate) async fn send(&self, pkt: &[u8]) -> Result<(), OverlayError> {
let session = Arc::clone(&self.session);
let owned = pkt.to_vec();
task::spawn_blocking(move || -> Result<(), String> {
let mut tx = session
.allocate_send_packet(u16::try_from(owned.len()).map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())?;
tx.bytes_mut().copy_from_slice(&owned);
session.send_packet(tx);
Ok(())
})
.await
.map_err(|e| OverlayError::NetworkConfig(format!("wintun send join error: {e}")))?
.map_err(OverlayError::NetworkConfig)?;
Ok(())
}
}
fn locate_wintun_dll() -> Option<PathBuf> {
let installed = zlayer_paths::ZLayerDirs::system_default()
.data_dir()
.join("wintun")
.join("wintun.dll");
if installed.exists() {
return Some(installed);
}
if let Ok(exe) = std::env::current_exe() {
if let Some(parent) = exe.parent() {
let sibling = parent.join("wintun.dll");
if sibling.exists() {
return Some(sibling);
}
}
}
let cwd = Path::new("wintun.dll");
if cwd.exists() {
return Some(cwd.to_path_buf());
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locate_dll_returns_none_in_clean_env() {
let _ = locate_wintun_dll();
}
}