Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nix = { version = "0.29", features = ["ioctl"] }

[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { version = "0.59", features = [
"Win32_Devices_DeviceAndDriverInstallation",
"Win32_Foundation",
"Win32_Security",
"Win32_Security_WinTrust",
Expand Down
62 changes: 59 additions & 3 deletions src/async/win_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,32 @@ impl AsyncWrite for WinSession {
mod tests {
use super::AsyncDevice;
use crate::{Configuration, Layer};
use std::{sync::Arc, time::Duration};
use packet::{ip, udp, Packet};
use std::{
net::UdpSocket,
sync::Arc,
time::{Duration, Instant},
};

#[test]
#[ignore = "requires Administrator privileges and wintun.dll"]
fn opened_adapter_must_match_requested_guid() {
let mut config = Configuration::default();
config.layer(Layer::L3).tun_name("tun-test-guid-match");
config.platform_config(|config| {
config.skip_config(true);
config.device_guid(0x9e917113_6d4d_4e03_b112_b10d70a251bb);
});
let first = crate::create(&config).unwrap();
let error = crate::create(&config).err().unwrap().to_string();
assert!(!error.contains("different GUID"), "{error}");
config.platform_config(|config| {
config.device_guid(0xe5782c6c_e108_4313_89bd_473226cb38f4);
});
let error = crate::create(&config).err().unwrap().to_string();
assert!(error.contains("different GUID"), "{error}");
drop(first);
}

// Run as Administrator with wintun.dll beside the test executable.
#[test]
Expand All @@ -208,11 +233,15 @@ mod tests {
});

let mut other_config = config.clone();
other_config.tun_name("tun-test-running");
other_config
.tun_name("tun-test-running")
.address((192, 0, 2, 1))
.netmask((255, 255, 255, 0));
other_config.platform_config(|config| {
config.skip_config(false);
config.device_guid(0x7a0c7f82_32c9_4c04_9680_cfc44fdc2136);
});
let other = AsyncDevice::new(crate::create(&other_config).unwrap()).unwrap();
let mut other = AsyncDevice::new(crate::create(&other_config).unwrap()).unwrap();

for _ in 0..100 {
let device = AsyncDevice::new(crate::create(&config).unwrap()).unwrap();
Expand All @@ -223,5 +252,32 @@ mod tests {
assert!(session.upgrade().is_none(), "reader retained the session");
assert!(!other.session.task.as_ref().unwrap().is_finished());
}

let payload = b"other adapter read probe";
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
assert_eq!(
socket.send_to(payload, "192.0.2.2:9").unwrap(),
payload.len()
);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Ok(packet) = other.session.receiver.try_recv() {
let Ok(ip::Packet::V4(ip_packet)) = ip::Packet::new(packet.as_slice()) else {
continue;
};
let Ok(udp_packet) = udp::Packet::new(ip_packet.payload()) else {
continue;
};
if udp_packet.destination() == 9 && udp_packet.payload() == payload {
println!("received other adapter UDP probe: {} bytes", packet.len());
break;
}
}
assert!(
Instant::now() < deadline,
"other adapter did not receive the UDP probe"
);
std::thread::sleep(Duration::from_millis(10));
}
}
}
29 changes: 26 additions & 3 deletions src/platform/windows/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,32 @@ impl Device {
};
let tun_name = config.tun_name.as_deref().unwrap_or("wintun");
let guid = config.platform_config.device_guid;
let adapter = match wintun::Adapter::open(&wintun, tun_name) {
Ok(a) => a,
Err(_) => wintun::Adapter::create(&wintun, tun_name, tun_name, guid)?,
let open_adapter = || -> Result<Option<Arc<wintun::Adapter>>> {
let adapter = match wintun::Adapter::open(&wintun, tun_name) {
Ok(adapter) => adapter,
Err(_) => return Ok(None),
};
if let Some(guid) = guid {
let requested = windows_sys::core::GUID::from_u128(guid);
let actual = adapter.get_guid().to_ne_bytes();
if actual[..4] != requested.data1.to_ne_bytes()
|| actual[4..6] != requested.data2.to_ne_bytes()
|| actual[6..8] != requested.data3.to_ne_bytes()
|| actual[8..] != requested.data4
{
return Err(format!("Wintun adapter {tun_name} has a different GUID").into());
}
}
Ok(Some(adapter))
};
let adapter = match open_adapter()? {
Some(a) => a,
None => {
if let Some(guid) = guid {
super::device_cleanup::wait_for_stale_device(guid)?;
}
wintun::Adapter::create(&wintun, tun_name, tun_name, guid)?
}
};

// on win7 guid will not be correctly assigned, user should skip the config step.
Expand Down
106 changes: 106 additions & 0 deletions src/platform/windows/device_cleanup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::{
thread,
time::{Duration, Instant},
};
use windows_sys::Win32::Devices::DeviceAndDriverInstallation::*;

const CLEANUP_TIMEOUT: Duration = Duration::from_secs(5);
const POLL_INTERVAL: Duration = Duration::from_millis(50);

fn device_instance_id(guid: u128) -> String {
let guid = windows_sys::core::GUID::from_u128(guid);
format!(
"SWD\\Wintun\\{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
guid.data1,
guid.data2,
guid.data3,
guid.data4[0],
guid.data4[1],
guid.data4[2],
guid.data4[3],
guid.data4[4],
guid.data4[5],
guid.data4[6],
guid.data4[7]
)
}

fn wide(value: &str) -> Vec<u16> {
value.encode_utf16().chain(Some(0)).collect()
}

fn locate_device(instance_id: &str) -> crate::error::Result<Option<(u32, u32, u32)>> {
let instance_id = wide(instance_id);
let mut dev_inst = 0;
let result = unsafe {
CM_Locate_DevNodeW(
&mut dev_inst,
instance_id.as_ptr(),
CM_LOCATE_DEVNODE_PHANTOM,
)
};
match result {
CR_SUCCESS => {}
CR_NO_SUCH_DEVNODE => return Ok(None),
_ => return Err(format!("CM_Locate_DevNodeW failed with CONFIGRET {result}").into()),
}

let mut status = 0;
let mut problem = 0;
let result = unsafe { CM_Get_DevNode_Status(&mut status, &mut problem, dev_inst, 0) };
match result {
CR_SUCCESS => {}
CR_NO_SUCH_DEVNODE => {
let mut present_dev_inst = 0;
let present_result =
unsafe { CM_Locate_DevNodeW(&mut present_dev_inst, instance_id.as_ptr(), 0) };
match present_result {
CR_SUCCESS => return Ok(Some((present_dev_inst, 0, 0))),
CR_NO_SUCH_DEVNODE => {
return Ok(Some((dev_inst, DN_HAS_PROBLEM, CM_PROB_PHANTOM)));
}
_ => {
return Err(format!(
"CM_Locate_DevNodeW failed with CONFIGRET {present_result}"
)
.into());
}
}
}
_ => return Err(format!("CM_Get_DevNode_Status failed with CONFIGRET {result}").into()),
}
Ok(Some((dev_inst, status, problem)))
}

pub(super) fn wait_for_stale_device(guid: u128) -> crate::error::Result<()> {
let instance_id = device_instance_id(guid);
let deadline = Instant::now() + CLEANUP_TIMEOUT;
loop {
if locate_device(&instance_id)?.is_none() {
return Ok(());
}
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Err(format!("timed out waiting for Wintun device {instance_id}").into());
}
thread::sleep(POLL_INTERVAL.min(remaining));
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn formats_the_requested_identity() {
assert_eq!(
device_instance_id(0xCFBAE5795EC3512AB6BBACE8DE243C2E),
"SWD\\Wintun\\{CFBAE579-5EC3-512A-B6BB-ACE8DE243C2E}"
);
}

#[test]
fn missing_requested_identity_is_not_an_error() {
wait_for_stale_device(u128::MAX).unwrap();
}
}
1 change: 1 addition & 0 deletions src/platform/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! Windows specific functionality.

mod device;
mod device_cleanup;
mod verify_dll_file;

use crate::configuration::Configuration;
Expand Down
Loading