//! TAP-Windows6 adapter discovery and raw Ethernet frame I/O. //! //! This crate deliberately stays below the relay session layer. It only knows //! how to find and open an installed TAP-Windows6 Ethernet adapter; the Windows //! client binary owns when to connect it to QUIC and how to protect routes. use anyhow::{Context, Result, bail}; use lanparty_proto::{EthernetFrame, MAX_STANDARD_ETHERNET_FRAME_LEN, MacAddr}; pub const TAP_COMPONENT_ID: &str = "tap0901"; pub const TAP_ADAPTER_KEY: &str = r"SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}"; pub const TAP_DEVICE_PREFIX: &str = r"\\.\Global\"; pub const TAP_DEVICE_SUFFIX: &str = ".tap"; pub const TAP_FRAME_BUFFER_LEN: usize = MAX_STANDARD_ETHERNET_FRAME_LEN; const FILE_DEVICE_UNKNOWN: u32 = 0x0000_0022; const METHOD_BUFFERED: u32 = 0; const FILE_ANY_ACCESS: u32 = 0; const TAP_IOCTL_GET_MAC_REQUEST: u32 = 1; const TAP_IOCTL_GET_MTU_REQUEST: u32 = 3; const TAP_IOCTL_SET_MEDIA_STATUS_REQUEST: u32 = 6; #[derive(Debug, Clone, PartialEq, Eq)] pub struct TapAdapterInfo { instance_id: String, component_id: String, driver_key_name: Option, } impl TapAdapterInfo { pub fn new(instance_id: impl Into, component_id: impl Into) -> Result { Self::from_parts(instance_id, component_id, None) } #[cfg_attr(not(windows), allow(dead_code))] fn from_registry( driver_key_name: impl Into, instance_id: impl Into, component_id: impl Into, ) -> Result { let driver_key_name = driver_key_name.into(); if driver_key_name.trim().is_empty() { bail!("TAP adapter registry key name cannot be empty"); } Self::from_parts(instance_id, component_id, Some(driver_key_name)) } fn from_parts( instance_id: impl Into, component_id: impl Into, driver_key_name: Option, ) -> Result { let instance_id = instance_id.into(); if instance_id.trim().is_empty() { bail!("TAP adapter instance id cannot be empty"); } let component_id = component_id.into(); if !is_tap_component_id(&component_id) { bail!("unsupported TAP adapter component id {component_id:?}"); } Ok(Self { instance_id, component_id, driver_key_name, }) } #[must_use] pub fn instance_id(&self) -> &str { &self.instance_id } #[must_use] pub fn component_id(&self) -> &str { &self.component_id } #[must_use] pub fn driver_key_name(&self) -> Option<&str> { self.driver_key_name.as_deref() } #[must_use] pub fn device_path(&self) -> String { tap_device_path(&self.instance_id) } } #[must_use] pub fn is_tap_component_id(component_id: &str) -> bool { let id = component_id.trim(); id.eq_ignore_ascii_case(TAP_COMPONENT_ID) || id.eq_ignore_ascii_case(concat!("root\\", "tap0901")) } #[must_use] pub fn tap_device_path(instance_id: &str) -> String { format!("{TAP_DEVICE_PREFIX}{instance_id}{TAP_DEVICE_SUFFIX}") } pub fn validate_tap_ethernet_frame(frame: &[u8]) -> Result<()> { let frame = EthernetFrame::parse(frame).context("TAP Ethernet frame is malformed")?; if frame.is_jumbo() { bail!( "TAP Ethernet frame length {} exceeds maximum {}", frame.len(), MAX_STANDARD_ETHERNET_FRAME_LEN ); } Ok(()) } pub fn tap_network_address_value(mac: MacAddr) -> Result { if !mac.is_valid_client_identity() { bail!("TAP MAC {mac} is not a locally administered unicast address"); } let [a, b, c, d, e, f] = mac.octets(); Ok(format!("{a:02X}{b:02X}{c:02X}{d:02X}{e:02X}{f:02X}")) } #[must_use] pub const fn tap_control_code(request: u32) -> u32 { (FILE_DEVICE_UNKNOWN << 16) | (FILE_ANY_ACCESS << 14) | (request << 2) | METHOD_BUFFERED } #[must_use] pub const fn tap_ioctl_get_mac() -> u32 { tap_control_code(TAP_IOCTL_GET_MAC_REQUEST) } #[must_use] pub const fn tap_ioctl_get_mtu() -> u32 { tap_control_code(TAP_IOCTL_GET_MTU_REQUEST) } #[must_use] pub const fn tap_ioctl_set_media_status() -> u32 { tap_control_code(TAP_IOCTL_SET_MEDIA_STATUS_REQUEST) } #[cfg(windows)] mod windows; #[cfg(windows)] pub use windows::{TapAdapter, available_adapters, configure_adapter_mac, open_first_adapter}; #[cfg(not(windows))] pub fn available_adapters() -> Result> { bail!("TAP-Windows6 adapter discovery is only available on Windows"); } #[cfg(test)] mod tests { use super::*; #[test] fn identifies_tap_windows_component_ids() { assert!(is_tap_component_id("tap0901")); assert!(is_tap_component_id("TAP0901")); assert!(is_tap_component_id("root\\tap0901")); assert!(!is_tap_component_id("wintun")); assert!(!is_tap_component_id("tap0801")); } #[test] fn builds_tap_device_path() { assert_eq!( tap_device_path("{01234567-89AB-CDEF-0123-456789ABCDEF}"), r"\\.\Global\{01234567-89AB-CDEF-0123-456789ABCDEF}.tap" ); } #[test] fn computes_tap_ioctl_codes() { assert_eq!(tap_ioctl_get_mac(), 0x0022_0004); assert_eq!(tap_ioctl_get_mtu(), 0x0022_000c); assert_eq!(tap_ioctl_set_media_status(), 0x0022_0018); } #[test] fn validates_tap_ethernet_frames() { let mut valid = vec![0; 14]; valid[12..14].copy_from_slice(&0x0800_u16.to_be_bytes()); assert!(validate_tap_ethernet_frame(&valid).is_ok()); assert!(validate_tap_ethernet_frame(&valid[..13]).is_err()); valid.resize(TAP_FRAME_BUFFER_LEN + 1, 0); assert!(validate_tap_ethernet_frame(&valid).is_err()); } #[test] fn validates_adapter_info() { let info = TapAdapterInfo::new("{01234567-89AB-CDEF-0123-456789ABCDEF}", "tap0901").unwrap(); assert_eq!(info.component_id(), "tap0901"); assert_eq!(info.driver_key_name(), None); assert_eq!( info.device_path(), r"\\.\Global\{01234567-89AB-CDEF-0123-456789ABCDEF}.tap" ); assert!(TapAdapterInfo::new("", "tap0901").is_err()); assert!(TapAdapterInfo::new("{01234567-89AB-CDEF-0123-456789ABCDEF}", "wintun").is_err()); } #[test] fn formats_tap_network_address_registry_value() { assert_eq!( tap_network_address_value(MacAddr::new([0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0xee])).unwrap(), "02AABBCCDDEE" ); assert!(tap_network_address_value(MacAddr::BROADCAST).is_err()); assert!(tap_network_address_value(MacAddr::new([0, 1, 2, 3, 4, 5])).is_err()); } }