use std::{ ffi::c_void, io::{self, ErrorKind}, ptr::{null, null_mut}, }; use anyhow::{Context, Result, bail}; use lanparty_proto::MacAddr; use windows_sys::Win32::{ Foundation::{ CloseHandle, ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA, ERROR_NO_MORE_ITEMS, ERROR_SUCCESS, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, }, Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_SYSTEM, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, ReadFile, WriteFile, }, System::{ IO::DeviceIoControl, Registry::{ HKEY, HKEY_LOCAL_MACHINE, KEY_READ, KEY_SET_VALUE, REG_SZ, RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, RegQueryValueExW, RegSetValueExW, }, }, }; use crate::{ TAP_ADAPTER_KEY, TapAdapterInfo, is_tap_component_id, tap_ioctl_get_mac, tap_ioctl_get_mtu, tap_ioctl_set_media_status, tap_network_address_value, validate_tap_ethernet_frame, }; #[derive(Debug)] pub struct TapAdapter { info: TapAdapterInfo, handle: OwnedHandle, } impl TapAdapter { pub fn open(info: TapAdapterInfo) -> Result { let path = info.device_path(); let wide_path = wide_null(&path); let handle = unsafe { // SAFETY: wide_path is NUL-terminated and lives for the duration of the call. // The security attributes and template handle are intentionally null. CreateFileW( wide_path.as_ptr(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, null(), OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, null_mut(), ) }; let handle = OwnedHandle::new(handle) .with_context(|| format!("failed to open TAP adapter device {path}"))?; Ok(Self { info, handle }) } #[must_use] pub fn info(&self) -> &TapAdapterInfo { &self.info } pub fn set_media_connected(&self, connected: bool) -> Result<()> { let mut status = u32::from(connected); self.device_io_control( tap_ioctl_set_media_status(), (&mut status as *mut u32).cast::(), std::mem::size_of::() as u32, null_mut(), 0, ) .context("failed to set TAP media status")?; Ok(()) } pub fn driver_mac(&self) -> Result { let mut bytes = [0_u8; 6]; self.device_io_control( tap_ioctl_get_mac(), null_mut(), 0, bytes.as_mut_ptr().cast::(), bytes.len() as u32, ) .context("failed to read TAP driver MAC address")?; Ok(MacAddr::new(bytes)) } pub fn driver_mtu(&self) -> Result { let mut mtu = 0_u32; self.device_io_control( tap_ioctl_get_mtu(), null_mut(), 0, (&mut mtu as *mut u32).cast::(), std::mem::size_of::() as u32, ) .context("failed to read TAP driver MTU")?; Ok(mtu) } pub fn read_frame(&self, buffer: &mut [u8]) -> Result { let mut bytes_read = 0_u32; let ok = unsafe { // SAFETY: buffer is valid for writes of buffer.len() bytes and the handle is owned by // this adapter. The synchronous handle uses a null OVERLAPPED pointer. ReadFile( self.handle.raw(), buffer.as_mut_ptr(), buffer .len() .try_into() .context("TAP read buffer is too large")?, &mut bytes_read, null_mut(), ) }; if ok == 0 { return Err(io::Error::last_os_error()).context("failed to read TAP frame"); } Ok(bytes_read as usize) } pub fn read_ethernet_frame(&self, buffer: &mut [u8]) -> Result { let len = self.read_frame(buffer)?; validate_tap_ethernet_frame(&buffer[..len])?; Ok(len) } pub fn write_frame(&self, frame: &[u8]) -> Result { let mut bytes_written = 0_u32; let ok = unsafe { // SAFETY: frame is valid for reads of frame.len() bytes and the handle is owned by // this adapter. The synchronous handle uses a null OVERLAPPED pointer. WriteFile( self.handle.raw(), frame.as_ptr(), frame.len().try_into().context("TAP frame is too large")?, &mut bytes_written, null_mut(), ) }; if ok == 0 { return Err(io::Error::last_os_error()).context("failed to write TAP frame"); } Ok(bytes_written as usize) } pub fn write_ethernet_frame(&self, frame: &[u8]) -> Result<()> { validate_tap_ethernet_frame(frame)?; let written = self.write_frame(frame)?; if written != frame.len() { bail!("partial TAP frame write: {written}/{}", frame.len()); } Ok(()) } fn device_io_control( &self, code: u32, input: *mut c_void, input_len: u32, output: *mut c_void, output_len: u32, ) -> io::Result { let mut bytes_returned = 0_u32; let ok = unsafe { // SAFETY: input/output pointers and lengths are supplied by the typed public methods // above. The synchronous handle uses a null OVERLAPPED pointer. DeviceIoControl( self.handle.raw(), code, input.cast_const(), input_len, output, output_len, &mut bytes_returned, null_mut(), ) }; if ok == 0 { return Err(io::Error::last_os_error()); } Ok(bytes_returned) } } pub fn open_first_adapter() -> Result { let mut adapters = available_adapters()?; let info = adapters .drain(..) .next() .context("no TAP-Windows6 adapters found")?; TapAdapter::open(info) } pub fn configure_adapter_mac(info: &TapAdapterInfo, mac: MacAddr) -> Result<()> { let driver_key_name = info .driver_key_name() .context("TAP adapter was not discovered from the Windows registry")?; let registry_path = format!("{TAP_ADAPTER_KEY}\\{driver_key_name}"); let key = RegKey::open_with_access(HKEY_LOCAL_MACHINE, ®istry_path, KEY_SET_VALUE) .with_context(|| format!("failed to open TAP adapter registry key {registry_path}"))?; key.set_string("NetworkAddress", &tap_network_address_value(mac)?) .context("failed to configure TAP adapter NetworkAddress")?; Ok(()) } pub fn available_adapters() -> Result> { let adapters_key = RegKey::open(HKEY_LOCAL_MACHINE, TAP_ADAPTER_KEY) .context("failed to open TAP adapter registry key")?; let mut adapters = Vec::new(); for subkey_name in adapters_key.subkey_names()? { let subkey = adapters_key .open_subkey(&subkey_name) .with_context(|| format!("failed to open TAP adapter registry subkey {subkey_name}"))?; let Some(component_id) = subkey.query_string("ComponentId")? else { continue; }; if !is_tap_component_id(&component_id) { continue; } let Some(instance_id) = subkey.query_string("NetCfgInstanceId")? else { continue; }; adapters.push(TapAdapterInfo::from_registry( subkey_name, instance_id, component_id, )?); } Ok(adapters) } #[derive(Debug)] struct OwnedHandle(HANDLE); // SAFETY: Windows file handles are process-wide kernel object references that may be used from // multiple threads. `OwnedHandle` only closes the handle in `Drop`; callers must still uphold any // higher-level synchronization required by the device protocol. unsafe impl Send for OwnedHandle {} // SAFETY: Sharing references to `OwnedHandle` only exposes the raw handle to synchronous Windows // APIs. The handle value itself is immutable, and Windows permits issuing I/O on a file handle from // more than one thread. unsafe impl Sync for OwnedHandle {} impl OwnedHandle { fn new(handle: HANDLE) -> io::Result { if handle == INVALID_HANDLE_VALUE { Err(io::Error::last_os_error()) } else { Ok(Self(handle)) } } const fn raw(&self) -> HANDLE { self.0 } } impl Drop for OwnedHandle { fn drop(&mut self) { unsafe { // SAFETY: self.0 is a valid owned HANDLE created by CreateFileW. CloseHandle(self.0); } } } #[derive(Debug)] struct RegKey(HKEY); impl RegKey { fn open(root: HKEY, path: &str) -> io::Result { Self::open_with_access(root, path, KEY_READ) } fn open_with_access(root: HKEY, path: &str, access: u32) -> io::Result { let path = wide_null(path); let mut key = null_mut(); let status = unsafe { // SAFETY: path is NUL-terminated and phkresult points to valid storage. RegOpenKeyExW(root, path.as_ptr(), 0, access, &mut key) }; windows_status(status)?; Ok(Self(key)) } fn open_subkey(&self, path: &str) -> io::Result { Self::open(self.0, path) } fn subkey_names(&self) -> io::Result> { let mut names = Vec::new(); let mut index = 0_u32; loop { let mut buffer = vec![0_u16; 256]; let mut len = buffer.len() as u32; let status = unsafe { // SAFETY: buffer is valid for len UTF-16 code units and len points to storage // that RegEnumKeyExW updates with the returned name length. RegEnumKeyExW( self.0, index, buffer.as_mut_ptr(), &mut len, null(), null_mut(), null_mut(), null_mut(), ) }; match status { ERROR_SUCCESS => { buffer.truncate(len as usize); names.push(String::from_utf16_lossy(&buffer)); index += 1; } ERROR_NO_MORE_ITEMS => return Ok(names), ERROR_MORE_DATA => { return Err(io::Error::new( ErrorKind::InvalidData, "registry subkey name exceeded internal buffer", )); } status => return Err(windows_error(status)), } } } fn query_string(&self, name: &str) -> io::Result> { let name = wide_null(name); let mut value_type = 0_u32; let mut byte_len = 0_u32; let status = unsafe { // SAFETY: name is NUL-terminated. A null data buffer asks Windows for the byte size. RegQueryValueExW( self.0, name.as_ptr(), null(), &mut value_type, null_mut(), &mut byte_len, ) }; match status { ERROR_SUCCESS => {} ERROR_FILE_NOT_FOUND => return Ok(None), status => return Err(windows_error(status)), } if value_type != REG_SZ { return Ok(None); } if byte_len == 0 { return Ok(Some(String::new())); } let mut buffer = vec![0_u16; byte_len.div_ceil(2) as usize]; let status = unsafe { // SAFETY: buffer is valid for byte_len bytes and name remains NUL-terminated. RegQueryValueExW( self.0, name.as_ptr(), null(), &mut value_type, buffer.as_mut_ptr().cast::(), &mut byte_len, ) }; windows_status(status)?; let nul = buffer .iter() .position(|value| *value == 0) .unwrap_or(buffer.len()); buffer.truncate(nul); Ok(Some(String::from_utf16_lossy(&buffer))) } fn set_string(&self, name: &str, value: &str) -> io::Result<()> { let name = wide_null(name); let value = wide_null(value); let status = unsafe { // SAFETY: name and value are NUL-terminated UTF-16 buffers. REG_SZ data length is // measured in bytes and includes the trailing NUL. RegSetValueExW( self.0, name.as_ptr(), 0, REG_SZ, value.as_ptr().cast::(), (value.len() * std::mem::size_of::()) as u32, ) }; windows_status(status) } } impl Drop for RegKey { fn drop(&mut self) { unsafe { // SAFETY: self.0 is a valid open registry key owned by this value. RegCloseKey(self.0); } } } fn windows_status(status: u32) -> io::Result<()> { if status == ERROR_SUCCESS { Ok(()) } else { Err(windows_error(status)) } } fn windows_error(status: u32) -> io::Error { io::Error::from_raw_os_error(status as i32) } fn wide_null(value: &str) -> Vec { value.encode_utf16().chain(std::iter::once(0)).collect() }