diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c04140a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Source files always use Linux-style line endings, regardless of the +# contributor's platform or core.autocrlf setting. Other files are left as-is. +*.rs text eol=lf +*.go text eol=lf +*.c text eol=lf +*.h text eol=lf diff --git a/c_helper/helper.c b/c_helper/helper.c index 93e893e..916f8c5 100644 --- a/c_helper/helper.c +++ b/c_helper/helper.c @@ -1,89 +1,89 @@ - -/* - * Name: helper.c - */ - -#include -#include - -#define NDIS640 1 // Windows 8 and Windows Server 2012 - -#include "Ntifs.h" -#include // Windows Driver Development Kit -#include // Windows Driver Foundation - -#pragma warning(push) -#pragma warning(disable: 4201) // Disable "Nameless struct/union" compiler warning for fwpsk.h only! -#include // Functions and enumerated types used to implement callouts in kernel mode -#pragma warning(pop) // Re-enable "Nameless struct/union" compiler warning - -#include // Functions used for managing IKE and AuthIP main mode (MM) policy and security associations -#include // Mappings of OS specific function versions (i.e. fn's that end in 0 or 1) -#include // Used to define GUID's -#include // Used to define GUID's -#include "devguid.h" -#include -#include -#include - -EVT_WDF_DRIVER_UNLOAD emptyEventUnload; - -NTSTATUS pm_InitDriverObject(DRIVER_OBJECT * driverObject, UNICODE_STRING * registryPath, WDFDRIVER * driver, WDFDEVICE * device, wchar_t *win_device_name, wchar_t *dos_device_name, WDF_OBJECT_ATTRIBUTES * objectAttributes, void (*wdfEventUnload)(WDFDRIVER)) { - UNICODE_STRING deviceName = { 0 }; - RtlInitUnicodeString(&deviceName, win_device_name); - - UNICODE_STRING deviceSymlink = { 0 }; - RtlInitUnicodeString(&deviceSymlink, dos_device_name); - - // Create a WDFDRIVER for this driver - WDF_DRIVER_CONFIG config = { 0 }; - WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK); - config.DriverInitFlags = WdfDriverInitNonPnpDriver; - config.EvtDriverUnload = wdfEventUnload; // <-- Necessary for this driver to unload correctly - NTSTATUS status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, driver); - if (!NT_SUCCESS(status)) { - return status; - } - - // Create a WDFDEVICE for this driver - PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(*driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); // only admins and kernel can access device - if (!deviceInit) { - return STATUS_INSUFFICIENT_RESOURCES; - } - - // Configure the WDFDEVICE_INIT with a name to allow for access from user mode - WdfDeviceInitSetDeviceType(deviceInit, FILE_DEVICE_NETWORK); - WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, false); - (void) WdfDeviceInitAssignName(deviceInit, &deviceName); - (void) WdfPdoInitAssignRawDevice(deviceInit, &GUID_DEVCLASS_NET); - WdfDeviceInitSetDeviceClass(deviceInit, &GUID_DEVCLASS_NET); - - status = WdfDeviceCreate(&deviceInit, objectAttributes, device); - if (!NT_SUCCESS(status)) { - WdfDeviceInitFree(deviceInit); - return status; - } - status = WdfDeviceCreateSymbolicLink(*device, &deviceSymlink); - if (!NT_SUCCESS(status)) { - return status; - } - - // The system will not send I/O requests or Windows Management Instrumentation (WMI) requests to a control device object unless the driver has called WdfControlFinishInitializing. - WdfControlFinishInitializing(*device); - - return STATUS_SUCCESS; -} - -void* pm_WdfObjectGetTypedContextWorker(WDFOBJECT wdfObject, PCWDF_OBJECT_CONTEXT_TYPE_INFO typeInfo) { - return WdfObjectGetTypedContextWorker(wdfObject, typeInfo->UniqueType); -} - -DEVICE_OBJECT* pm_GetDeviceObject(WDFDEVICE device) { - return WdfDeviceWdmGetDeviceObject(device); -} - -UINT64 pm_QuerySystemTime() { - UINT64 timestamp = 0; - KeQuerySystemTime(×tamp); - return timestamp; + +/* + * Name: helper.c + */ + +#include +#include + +#define NDIS640 1 // Windows 8 and Windows Server 2012 + +#include "Ntifs.h" +#include // Windows Driver Development Kit +#include // Windows Driver Foundation + +#pragma warning(push) +#pragma warning(disable: 4201) // Disable "Nameless struct/union" compiler warning for fwpsk.h only! +#include // Functions and enumerated types used to implement callouts in kernel mode +#pragma warning(pop) // Re-enable "Nameless struct/union" compiler warning + +#include // Functions used for managing IKE and AuthIP main mode (MM) policy and security associations +#include // Mappings of OS specific function versions (i.e. fn's that end in 0 or 1) +#include // Used to define GUID's +#include // Used to define GUID's +#include "devguid.h" +#include +#include +#include + +EVT_WDF_DRIVER_UNLOAD emptyEventUnload; + +NTSTATUS pm_InitDriverObject(DRIVER_OBJECT * driverObject, UNICODE_STRING * registryPath, WDFDRIVER * driver, WDFDEVICE * device, wchar_t *win_device_name, wchar_t *dos_device_name, WDF_OBJECT_ATTRIBUTES * objectAttributes, void (*wdfEventUnload)(WDFDRIVER)) { + UNICODE_STRING deviceName = { 0 }; + RtlInitUnicodeString(&deviceName, win_device_name); + + UNICODE_STRING deviceSymlink = { 0 }; + RtlInitUnicodeString(&deviceSymlink, dos_device_name); + + // Create a WDFDRIVER for this driver + WDF_DRIVER_CONFIG config = { 0 }; + WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK); + config.DriverInitFlags = WdfDriverInitNonPnpDriver; + config.EvtDriverUnload = wdfEventUnload; // <-- Necessary for this driver to unload correctly + NTSTATUS status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, driver); + if (!NT_SUCCESS(status)) { + return status; + } + + // Create a WDFDEVICE for this driver + PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(*driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); // only admins and kernel can access device + if (!deviceInit) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Configure the WDFDEVICE_INIT with a name to allow for access from user mode + WdfDeviceInitSetDeviceType(deviceInit, FILE_DEVICE_NETWORK); + WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, false); + (void) WdfDeviceInitAssignName(deviceInit, &deviceName); + (void) WdfPdoInitAssignRawDevice(deviceInit, &GUID_DEVCLASS_NET); + WdfDeviceInitSetDeviceClass(deviceInit, &GUID_DEVCLASS_NET); + + status = WdfDeviceCreate(&deviceInit, objectAttributes, device); + if (!NT_SUCCESS(status)) { + WdfDeviceInitFree(deviceInit); + return status; + } + status = WdfDeviceCreateSymbolicLink(*device, &deviceSymlink); + if (!NT_SUCCESS(status)) { + return status; + } + + // The system will not send I/O requests or Windows Management Instrumentation (WMI) requests to a control device object unless the driver has called WdfControlFinishInitializing. + WdfControlFinishInitializing(*device); + + return STATUS_SUCCESS; +} + +void* pm_WdfObjectGetTypedContextWorker(WDFOBJECT wdfObject, PCWDF_OBJECT_CONTEXT_TYPE_INFO typeInfo) { + return WdfObjectGetTypedContextWorker(wdfObject, typeInfo->UniqueType); +} + +DEVICE_OBJECT* pm_GetDeviceObject(WDFDEVICE device) { + return WdfDeviceWdmGetDeviceObject(device); +} + +UINT64 pm_QuerySystemTime() { + UINT64 timestamp = 0; + KeQuerySystemTime(×tamp); + return timestamp; } \ No newline at end of file diff --git a/driver/src/ale_callouts.rs b/driver/src/ale_callouts.rs index 3a8839e..0f90e2b 100644 --- a/driver/src/ale_callouts.rs +++ b/driver/src/ale_callouts.rs @@ -1,762 +1,762 @@ -use core::sync::atomic::Ordering; - -use crate::connection::{Connection, ConnectionV4, ConnectionV6, Direction, Key, Verdict}; -use crate::device::{Device, Packet}; - -use crate::dbg; -use crate::id_cache; -use smoltcp::wire::{ - IpAddress, IpProtocol, Ipv4Address, Ipv6Address, IPV4_HEADER_LEN, IPV6_HEADER_LEN, -}; -use wdk::filter_engine::callout_data::CalloutData; -use wdk::filter_engine::layer::{ - self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, FieldsAleAuthRecvAcceptV4, - FieldsAleAuthRecvAcceptV6, ValueType, -}; -use wdk::filter_engine::net_buffer::NetBufferList; -use wdk::filter_engine::packet::{Injector, TransportPacketList}; - -// ALE Layers - -#[derive(Debug)] -#[allow(dead_code)] -struct AleLayerData { - is_ipv6: bool, - reauthorize: bool, - process_id: u64, - protocol: IpProtocol, - direction: Direction, - local_ip: IpAddress, - local_port: u16, - remote_ip: IpAddress, - remote_port: u16, - interface_index: u32, - sub_interface_index: u32, -} - -impl AleLayerData { - fn as_key(&self) -> Key { - let mut local_port = 0; - let mut remote_port = 0; - match self.protocol { - IpProtocol::Tcp | IpProtocol::Udp => { - local_port = self.local_port; - remote_port = self.remote_port; - } - _ => {} - } - - Key { - protocol: self.protocol, - local_address: self.local_ip, - local_port, - remote_address: self.remote_ip, - remote_port, - } - } - - fn is_loopback(&self) -> bool { - match (self.local_ip, self.remote_ip) { - (IpAddress::Ipv4(local), IpAddress::Ipv4(remote)) => { - local.is_loopback() || remote.is_loopback() - } - (IpAddress::Ipv6(local), IpAddress::Ipv6(remote)) => { - local.is_loopback() || remote.is_loopback() - } - _ => false, - } - } -} - -fn get_protocol(data: &CalloutData, index: usize) -> IpProtocol { - IpProtocol::from(data.get_value_u8(index)) -} - -fn get_ipv4_address(data: &CalloutData, index: usize) -> IpAddress { - IpAddress::Ipv4(Ipv4Address::from_octets( - data.get_value_u32(index).to_be_bytes(), - )) -} - -fn get_ipv6_address(data: &CalloutData, index: usize) -> IpAddress { - IpAddress::Ipv6(Ipv6Address::from_octets( - *data.get_value_byte_array16(index), - )) -} - -pub fn ale_layer_connect_v4(data: CalloutData) { - type Fields = FieldsAleAuthConnectV4; - let ale_data = AleLayerData { - is_ipv6: false, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Outbound, - local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: 0, - sub_interface_index: 0, - }; - - ale_layer_auth_outbound(data, ale_data); -} - -pub fn ale_layer_accept_v4(data: CalloutData) { - type Fields = FieldsAleAuthRecvAcceptV4; - let ale_data = AleLayerData { - is_ipv6: false, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Inbound, - local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), - sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), - }; - ale_layer_auth_inbound(data, ale_data); -} - -pub fn ale_layer_connect_v6(data: CalloutData) { - type Fields = FieldsAleAuthConnectV6; - - let ale_data = AleLayerData { - is_ipv6: true, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Outbound, - local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), - sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), - }; - - ale_layer_auth_outbound(data, ale_data); -} - -pub fn ale_layer_accept_v6(data: CalloutData) { - type Fields = FieldsAleAuthRecvAcceptV6; - let ale_data = AleLayerData { - is_ipv6: true, - reauthorize: data.is_reauthorize(Fields::Flags as usize), - process_id: data.get_process_id().unwrap_or(0), - protocol: get_protocol(&data, Fields::IpProtocol as usize), - direction: Direction::Inbound, - local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), - sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), - }; - ale_layer_auth_inbound(data, ale_data); -} - -// Outbound connections (ALE Auth Connect layers). -// -// The ALE layer runs *before* the packet layer for outbound traffic, so permitting here means the -// packet layer gets the chance to send the real packet to user space and reinject it. -fn ale_layer_auth_outbound(mut data: CalloutData, ale_data: AleLayerData) { - let Some(device) = crate::entry::get_device() else { - return; - }; - - // Never pend once the teardown has started. The packet cache is being drained and user space - // is gone, so a pend made now would never be answered: its IRP would stay alive inside a - // callout that is about to be unregistered, leaving the connecting thread stuck in the kernel. - // Permit instead, the filters are being removed anyway. - if device.is_shutting_down() { - // This makes the connections created between driver unload and system shutdown invisible to user-space. - // TODO: Is there anything we can do about it? - data.action_permit(); - return; - } - - // Only TCP and UDP are associated with a connection and handled here. Everything else is - // permitted and handled by the packet layer. - if !matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) { - data.action_permit(); - return; - } - - let key = ale_data.as_key(); - - // Check if connection is already in cache. - let verdict = device.connection_cache.get_verdict(&key); - - // Connection already in cache. - if let Some(verdict) = verdict { - crate::dbg!("processing existing connection: {} {}", key, verdict); - match verdict { - // No verdict yet. User space may not know about this connection at all (e.g. it - // existed before the driver was loaded), so send an info-only event that carries the - // process id (missing packet id, nothing to reinject) and permit. The packet layer - // sends the real packet and applies the actual verdict after user space decides. - Verdict::Undecided => { - if let Some(info) = - id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) - { - let _ = device.event_queue.push(info); - } - data.action_permit(); - } - // A verdict exists: - // Temporary verdicts are applied in the packet layer - Verdict::Accept - | Verdict::RedirectNameServer - | Verdict::RedirectTunnel - | Verdict::Block - | Verdict::Drop => { - data.action_permit(); - } - // Permanent verdicts are applied here. - Verdict::PermanentAccept => { - data.action_permit(); - } - Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => { - // Packet layer will not see this connection. - crate::dbg!("permanent block {}", key); - data.action_block(); - } - Verdict::PermanentDrop => { - // Packet layer will not see this connection. - crate::dbg!("permanent drop {}", key); - data.block_and_absorb(); - } - } - } else { - // New connection. - if ale_data.reauthorize { - // A reauthorized connection cannot be pended: there is no completion handle during - // reauthorization. Resetting all filters to release it later works (that is what the - // inbound path does) but the outbound packet layer runs after this one and can hold the - // real packet without any of that, so it is the cheaper way round here. Just capture - // the process id, inform user space with an info-only event (missing packet id) and - // permit. The packet layer sends the real packet and reinjects it after the verdict. - crate::dbg!( - "reauthorized connection: {} PID: {}", - key, - ale_data.process_id - ); - add_connection(device, &key, &ale_data, None); - if let Some(info) = - id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) - { - let _ = device.event_queue.push(info); - } - data.action_permit(); - } else { - // First packet of a new connection (a genuine connect()). Pend it so the verdict can - // be returned as the result of the connect call; the packet is reinjected once user - // space decides. - crate::dbg!("pending connection: {} PID: {}", key, ale_data.process_id); - match save_packet_outbound(device, &mut data, &ale_data) { - Ok(packet) => { - let info = device.packet_cache.push( - (key, packet), - ale_data.process_id, - ale_data.direction, - true, - ); - if let Some(info) = info { - let _ = device.event_queue.push(info); - } - } - Err(err) => { - crate::err!("failed to pend packet: {}", err); - } - }; - add_connection(device, &key, &ale_data, None); - - // Drop the packet. It will be re-injected after user space returns a verdict. - data.block_and_absorb(); - } - } -} - -// Inbound connections (ALE Auth Recv-Accept layers). -// -// Note that the ordering is the opposite of the outbound case: the inbound packet layer runs -// *before* this layer, and it only forwards packets that have no connection in the cache. So this -// callout is the entry point for new inbound connections; once a cache entry exists, following -// packets are handled by the packet layer and mostly never reach this layer. -fn ale_layer_auth_inbound(mut data: CalloutData, ale_data: AleLayerData) { - let Some(device) = crate::entry::get_device() else { - return; - }; - - // Never pend once the teardown has started. The packet cache is being drained and user space - // is gone, so a pend made now would never be answered: its IRP would stay alive inside a - // callout that is about to be unregistered, leaving the accepting thread stuck in the kernel. - // Permit instead, the filters are being removed anyway. - if device.is_shutting_down() { - // This makes the connections created between driver unload and system shutdown invisible to user-space. - // TODO: Is there anything we can do about it? - data.action_permit(); - return; - } - - // Only TCP and UDP are associated with a connection and handled here. Everything else was - // already handled by the packet layer. - if !matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) { - data.action_permit(); - return; - } - - let key = ale_data.as_key(); - - // Check if connection is already in cache. - let verdict = device.connection_cache.get_verdict(&key); - - // Connection already in cache. - if let Some(verdict) = verdict { - crate::dbg!("processing existing connection: {} {}", key, verdict); - match verdict { - // No verdict yet: the connection is already pended and user space has not decided. - // Save this packet too so it is sent to user space and reinjected with the verdict. - Verdict::Undecided => { - crate::dbg!("saving packet: {}", key); - match save_packet_inbound(device, &mut data, &ale_data, false) { - Ok(packet) => { - let info = device.packet_cache.push( - (key, packet), - ale_data.process_id, - ale_data.direction, - true, - ); - if let Some(info) = info { - let _ = device.event_queue.push(info); - } - } - Err(err) => { - crate::err!("failed to pend packet: {}", err); - } - }; - data.block_and_absorb(); - } - // Accepting verdicts continue to the packet layer, which applies the redirects and - // keeps counting bandwidth. - Verdict::PermanentAccept - | Verdict::Accept - | Verdict::RedirectNameServer - | Verdict::RedirectTunnel => { - data.action_permit(); - } - Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => { - // Packet layer will not see this connection. - crate::dbg!("permanent block {}", key); - data.action_block(); - } - Verdict::PermanentDrop => { - // Packet layer will not see this connection. - crate::dbg!("permanent drop {}", key); - data.block_and_absorb(); - } - // Unlike the outbound case, blocking temporary verdicts are enforced here: the packet - // layer already ran for this packet, so permitting would let it through. - Verdict::Block => { - data.action_block(); - } - Verdict::Drop => { - data.block_and_absorb(); - } - } - } else { - // Special case for incoming loopback connections. - if ale_data.is_loopback() { - // Pending an inbound loopback connection does not work. Set the verdict to accept and - // send an info-only event (missing packet id, nothing to reinject) to user space. - crate::dbg!( - "loopback inbound connection: {} PID: {}", - key, - ale_data.process_id - ); - add_connection(device, &key, &ale_data, Some(Verdict::Accept)); - - if let Some(info) = - id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) - { - let _ = device.event_queue.push(info); - } - - data.action_permit(); - return; - } - - // Only the first packet of a connection can be pended: reauthorize == false. A - // reauthorized connection has no completion handle, so the packet is instead held by - // resetting all filters once the verdict arrives (see `pend_filter_rest`). That reset - // opens a WFP write transaction, of which only one can be open at a time, so it can lose - // the race against a concurrent reset; the verdict path queues those and retries them - // (see `Device::reset_filters_and_inject`) instead of dropping the packet. - crate::dbg!("pending connection: {} {}", key, ale_data.direction); - let can_pend_connection = !ale_data.reauthorize; - match save_packet_inbound(device, &mut data, &ale_data, can_pend_connection) { - Ok(packet) => { - let info = device.packet_cache.push( - (key, packet), - ale_data.process_id, - ale_data.direction, - true, - ); - if let Some(info) = info { - let _ = device.event_queue.push(info); - } - } - Err(err) => { - crate::err!("failed to pend packet: {}", err); - } - }; - - // Connection is not in cache, add it. - crate::dbg!("adding connection: {} PID: {}", key, ale_data.process_id); - add_connection(device, &key, &ale_data, None); - - // Drop the packet. It will be re-injected after user space returns a verdict. - data.block_and_absorb(); - } -} - -fn add_connection(device: &Device, key: &Key, ale_data: &AleLayerData, verdict: Option) { - if ale_data.is_ipv6 { - if let Ok(conn) = ConnectionV6::from_key(key, ale_data.process_id, ale_data.direction) { - if let Some(verdict) = verdict { - conn.set_verdict(verdict); - } - device.connection_cache.add_v6(conn); - } else { - crate::err!("failed to add ipv6 connection"); - } - } else { - if let Ok(conn) = ConnectionV4::from_key(key, ale_data.process_id, ale_data.direction) { - if let Some(verdict) = verdict { - conn.set_verdict(verdict); - } - device.connection_cache.add_v4(conn); - } else { - crate::err!("failed to add ipv4 connection"); - } - } -} - -// Pends the outbound connect operation, capturing the packet so it can be reinjected after the -// verdict. Only called for a genuine new connection (reauthorize == false). -fn save_packet_outbound( - device: &Device, - callout_data: &mut CalloutData, - ale_data: &AleLayerData, -) -> Result { - // During the connect state of an outbound TCP connection there is no packet data yet, so - // nothing is captured. UDP carries the datagram, which is captured for reinjection. - let packet_list = match ale_data.protocol { - IpProtocol::Udp => create_packet_list(device, callout_data, ale_data), - _ => None, - }; - match callout_data.pend_operation(packet_list) { - Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)), - Err(err) => Err(alloc::format!("failed to defer connection: {}", err)), - } -} - -// Captures the inbound packet so it can be reinjected after the verdict. -// -// `pend` selects how the operation is deferred: with a completion handle (only available when -// reauthorize == false) or, when there is none, by resetting the filters once the verdict arrives. -fn save_packet_inbound( - device: &Device, - callout_data: &mut CalloutData, - ale_data: &AleLayerData, - pend: bool, -) -> Result { - // Inbound packets always carry data, unlike the connect state of an outbound TCP connection. - let packet_list = create_packet_list(device, callout_data, ale_data); - - if pend { - match callout_data.pend_operation(packet_list) { - Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)), - Err(err) => Err(alloc::format!("failed to defer connection: {}", err)), - } - } else { - Ok(Packet::AleLayer(callout_data.pend_filter_rest(packet_list))) - } -} - -fn create_packet_list( - device: &Device, - callout_data: &mut CalloutData, - ale_data: &AleLayerData, -) -> Option { - let nbl = NetBufferList::new(callout_data.get_layer_data() as _); - - // The ALE layers hand over a single packet, so only the first net buffer of the list is ever - // relevant here (unlike the packet layers, which walk the whole chain). - let Some(mut nb) = nbl.iter_net_buffers().next() else { - crate::err!("no net buffer in ale layer data"); - return None; - }; - - let mut inbound = false; - if let Direction::Inbound = ale_data.direction { - // At this layer the net buffer starts at the transport payload. Retreat it back over the - // transport and IP headers so the whole packet can be cloned and reinjected. The net - // buffer will auto advance after it loses scope. - // - // The IP header is not a fixed size: IPv4 may carry options and IPv6 a chain of extension - // headers. WFP reports the real size in the ip header size metadata field; fall back to - // the fixed length only when the field is absent, and ignore values smaller than it as - // malformed (same handling as the inbound packet layer). - let fixed_ip_header_len = if ale_data.is_ipv6 { - IPV6_HEADER_LEN as u32 - } else { - IPV4_HEADER_LEN as u32 - }; - let ip_header_size = match callout_data.get_ip_header_size() { - Some(size) if size >= fixed_ip_header_len => size, - _ => fixed_ip_header_len, - }; - let retreat_size = ip_header_size + callout_data.get_transport_header_size(); - // A failed retreat leaves the offset at the transport header, so the clone would be - // missing the headers it has to be reinjected with. - if let Err(err) = nb.retreat(retreat_size, true) { - crate::err!("failed to retreat net buffer: {}", err); - return None; - } - inbound = true; - } - - let address: &[u8] = match &ale_data.remote_ip { - IpAddress::Ipv4(address) => &address.octets(), - IpAddress::Ipv6(address) => &address.octets(), - }; - if let Ok(clone) = nb.clone_as_nbl(&device.network_allocator) { - return Some(Injector::from_ale_callout( - ale_data.is_ipv6, - callout_data, - clone, - address, - inbound, - ale_data.interface_index, - ale_data.sub_interface_index, - )); - } - return None; -} - -pub fn endpoint_closure_v4(data: CalloutData) { - type Fields = layer::FieldsAleEndpointClosureV4; - let Some(device) = crate::entry::get_device() else { - return; - }; - let ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); - if let ValueType::FwpUint32 = ip_address_type { - let key = Key { - protocol: get_protocol(&data, Fields::IpProtocol as usize), - local_address: get_ipv4_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_address: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - }; - - if let Some(conn) = device.connection_cache.end_v4(key) { - let info = protocol::info::connection_end_event_v4_info( - data.get_process_id().unwrap_or(0), - conn.get_direction() as u8, - u8::from(get_protocol(&data, Fields::IpProtocol as usize)), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } else { - // Invalid ip address type. Just ignore the error. - // err!( - // device.logger, - // "unknown ipv4 address type: {:?}", - // ip_address_type - // ); - } -} - -pub fn endpoint_closure_v6(data: CalloutData) { - type Fields = layer::FieldsAleEndpointClosureV6; - let Some(device) = crate::entry::get_device() else { - return; - }; - let local_ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); - let remote_ip_address_type = data.get_value_type(Fields::IpRemoteAddress as usize); - - if let ValueType::FwpByteArray16Type = local_ip_address_type { - if let ValueType::FwpByteArray16Type = remote_ip_address_type { - let key = Key { - protocol: get_protocol(&data, Fields::IpProtocol as usize), - local_address: get_ipv6_address(&data, Fields::IpLocalAddress as usize), - local_port: data.get_value_u16(Fields::IpLocalPort as usize), - remote_address: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), - remote_port: data.get_value_u16(Fields::IpRemotePort as usize), - }; - - if let Some(conn) = device.connection_cache.end_v6(key) { - let info = protocol::info::connection_end_event_v6_info( - data.get_process_id().unwrap_or(0), - conn.get_direction() as u8, - u8::from(get_protocol(&data, Fields::IpProtocol as usize)), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } - } -} - -pub fn ale_resource_monitor(data: CalloutData) { - let Some(device) = crate::entry::get_device() else { - return; - }; - match data.layer { - layer::Layer::AleResourceAssignmentV4Discard => { - type Fields = layer::FieldsAleResourceAssignmentV4; - if let Some(conns) = device.connection_cache.end_all_on_port_v4(( - get_protocol(&data, Fields::IpProtocol as usize), - data.get_value_u16(Fields::IpLocalPort as usize), - )) { - let process_id = data.get_process_id().unwrap_or(0); - dbg!( - "Port {}/{} Ipv4 assign request discarded pid={}", - data.get_value_u16(Fields::IpLocalPort as usize), - get_protocol(&data, Fields::IpProtocol as usize), - process_id, - ); - for conn in conns { - let info = protocol::info::connection_end_event_v4_info( - process_id, - conn.get_direction() as u8, - data.get_value_u8(Fields::IpProtocol as usize), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } - } - layer::Layer::AleResourceAssignmentV6Discard => { - type Fields = layer::FieldsAleResourceAssignmentV6; - if let Some(conns) = device.connection_cache.end_all_on_port_v6(( - get_protocol(&data, Fields::IpProtocol as usize), - data.get_value_u16(Fields::IpLocalPort as usize), - )) { - let process_id = data.get_process_id().unwrap_or(0); - dbg!( - "Port {}/{} Ipv6 assign request discarded pid={}", - data.get_value_u16(Fields::IpLocalPort as usize), - get_protocol(&data, Fields::IpProtocol as usize), - process_id, - ); - for conn in conns { - let info = protocol::info::connection_end_event_v6_info( - process_id, - conn.get_direction() as u8, - data.get_value_u8(Fields::IpProtocol as usize), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } - } - layer::Layer::AleResourceReleaseV4 => { - type Fields = layer::FieldsAleResourceReleaseV4; - if let Some(conns) = device.connection_cache.end_all_on_port_v4(( - get_protocol(&data, Fields::IpProtocol as usize), - data.get_value_u16(Fields::IpLocalPort as usize), - )) { - let process_id = data.get_process_id().unwrap_or(0); - dbg!( - "Port {}/{} ipv4 released pid={}", - data.get_value_u16(Fields::IpLocalPort as usize), - get_protocol(&data, Fields::IpProtocol as usize), - process_id, - ); - for conn in conns { - let info = protocol::info::connection_end_event_v4_info( - process_id, - conn.get_direction() as u8, - data.get_value_u8(Fields::IpProtocol as usize), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } - } - layer::Layer::AleResourceReleaseV6 => { - type Fields = layer::FieldsAleResourceReleaseV6; - if let Some(conns) = device.connection_cache.end_all_on_port_v6(( - get_protocol(&data, Fields::IpProtocol as usize), - data.get_value_u16(Fields::IpLocalPort as usize), - )) { - let process_id = data.get_process_id().unwrap_or(0); - dbg!( - "Port {}/{} ipv6 released pid={}", - data.get_value_u16(Fields::IpLocalPort as usize), - get_protocol(&data, Fields::IpProtocol as usize), - process_id, - ); - for conn in conns { - let info = protocol::info::connection_end_event_v6_info( - process_id, - conn.get_direction() as u8, - data.get_value_u8(Fields::IpProtocol as usize), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - let _ = device.event_queue.push(info); - } - } - } - _ => {} - } -} +use core::sync::atomic::Ordering; + +use crate::connection::{Connection, ConnectionV4, ConnectionV6, Direction, Key, Verdict}; +use crate::device::{Device, Packet}; + +use crate::dbg; +use crate::id_cache; +use smoltcp::wire::{ + IpAddress, IpProtocol, Ipv4Address, Ipv6Address, IPV4_HEADER_LEN, IPV6_HEADER_LEN, +}; +use wdk::filter_engine::callout_data::CalloutData; +use wdk::filter_engine::layer::{ + self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, FieldsAleAuthRecvAcceptV4, + FieldsAleAuthRecvAcceptV6, ValueType, +}; +use wdk::filter_engine::net_buffer::NetBufferList; +use wdk::filter_engine::packet::{Injector, TransportPacketList}; + +// ALE Layers + +#[derive(Debug)] +#[allow(dead_code)] +struct AleLayerData { + is_ipv6: bool, + reauthorize: bool, + process_id: u64, + protocol: IpProtocol, + direction: Direction, + local_ip: IpAddress, + local_port: u16, + remote_ip: IpAddress, + remote_port: u16, + interface_index: u32, + sub_interface_index: u32, +} + +impl AleLayerData { + fn as_key(&self) -> Key { + let mut local_port = 0; + let mut remote_port = 0; + match self.protocol { + IpProtocol::Tcp | IpProtocol::Udp => { + local_port = self.local_port; + remote_port = self.remote_port; + } + _ => {} + } + + Key { + protocol: self.protocol, + local_address: self.local_ip, + local_port, + remote_address: self.remote_ip, + remote_port, + } + } + + fn is_loopback(&self) -> bool { + match (self.local_ip, self.remote_ip) { + (IpAddress::Ipv4(local), IpAddress::Ipv4(remote)) => { + local.is_loopback() || remote.is_loopback() + } + (IpAddress::Ipv6(local), IpAddress::Ipv6(remote)) => { + local.is_loopback() || remote.is_loopback() + } + _ => false, + } + } +} + +fn get_protocol(data: &CalloutData, index: usize) -> IpProtocol { + IpProtocol::from(data.get_value_u8(index)) +} + +fn get_ipv4_address(data: &CalloutData, index: usize) -> IpAddress { + IpAddress::Ipv4(Ipv4Address::from_octets( + data.get_value_u32(index).to_be_bytes(), + )) +} + +fn get_ipv6_address(data: &CalloutData, index: usize) -> IpAddress { + IpAddress::Ipv6(Ipv6Address::from_octets( + *data.get_value_byte_array16(index), + )) +} + +pub fn ale_layer_connect_v4(data: CalloutData) { + type Fields = FieldsAleAuthConnectV4; + let ale_data = AleLayerData { + is_ipv6: false, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Outbound, + local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: 0, + sub_interface_index: 0, + }; + + ale_layer_auth_outbound(data, ale_data); +} + +pub fn ale_layer_accept_v4(data: CalloutData) { + type Fields = FieldsAleAuthRecvAcceptV4; + let ale_data = AleLayerData { + is_ipv6: false, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Inbound, + local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + ale_layer_auth_inbound(data, ale_data); +} + +pub fn ale_layer_connect_v6(data: CalloutData) { + type Fields = FieldsAleAuthConnectV6; + + let ale_data = AleLayerData { + is_ipv6: true, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Outbound, + local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + + ale_layer_auth_outbound(data, ale_data); +} + +pub fn ale_layer_accept_v6(data: CalloutData) { + type Fields = FieldsAleAuthRecvAcceptV6; + let ale_data = AleLayerData { + is_ipv6: true, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Inbound, + local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + ale_layer_auth_inbound(data, ale_data); +} + +// Outbound connections (ALE Auth Connect layers). +// +// The ALE layer runs *before* the packet layer for outbound traffic, so permitting here means the +// packet layer gets the chance to send the real packet to user space and reinject it. +fn ale_layer_auth_outbound(mut data: CalloutData, ale_data: AleLayerData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + + // Never pend once the teardown has started. The packet cache is being drained and user space + // is gone, so a pend made now would never be answered: its IRP would stay alive inside a + // callout that is about to be unregistered, leaving the connecting thread stuck in the kernel. + // Permit instead, the filters are being removed anyway. + if device.is_shutting_down() { + // This makes the connections created between driver unload and system shutdown invisible to user-space. + // TODO: Is there anything we can do about it? + data.action_permit(); + return; + } + + // Only TCP and UDP are associated with a connection and handled here. Everything else is + // permitted and handled by the packet layer. + if !matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) { + data.action_permit(); + return; + } + + let key = ale_data.as_key(); + + // Check if connection is already in cache. + let verdict = device.connection_cache.get_verdict(&key); + + // Connection already in cache. + if let Some(verdict) = verdict { + crate::dbg!("processing existing connection: {} {}", key, verdict); + match verdict { + // No verdict yet. User space may not know about this connection at all (e.g. it + // existed before the driver was loaded), so send an info-only event that carries the + // process id (missing packet id, nothing to reinject) and permit. The packet layer + // sends the real packet and applies the actual verdict after user space decides. + Verdict::Undecided => { + if let Some(info) = + id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) + { + let _ = device.event_queue.push(info); + } + data.action_permit(); + } + // A verdict exists: + // Temporary verdicts are applied in the packet layer + Verdict::Accept + | Verdict::RedirectNameServer + | Verdict::RedirectTunnel + | Verdict::Block + | Verdict::Drop => { + data.action_permit(); + } + // Permanent verdicts are applied here. + Verdict::PermanentAccept => { + data.action_permit(); + } + Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => { + // Packet layer will not see this connection. + crate::dbg!("permanent block {}", key); + data.action_block(); + } + Verdict::PermanentDrop => { + // Packet layer will not see this connection. + crate::dbg!("permanent drop {}", key); + data.block_and_absorb(); + } + } + } else { + // New connection. + if ale_data.reauthorize { + // A reauthorized connection cannot be pended: there is no completion handle during + // reauthorization. Resetting all filters to release it later works (that is what the + // inbound path does) but the outbound packet layer runs after this one and can hold the + // real packet without any of that, so it is the cheaper way round here. Just capture + // the process id, inform user space with an info-only event (missing packet id) and + // permit. The packet layer sends the real packet and reinjects it after the verdict. + crate::dbg!( + "reauthorized connection: {} PID: {}", + key, + ale_data.process_id + ); + add_connection(device, &key, &ale_data, None); + if let Some(info) = + id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) + { + let _ = device.event_queue.push(info); + } + data.action_permit(); + } else { + // First packet of a new connection (a genuine connect()). Pend it so the verdict can + // be returned as the result of the connect call; the packet is reinjected once user + // space decides. + crate::dbg!("pending connection: {} PID: {}", key, ale_data.process_id); + match save_packet_outbound(device, &mut data, &ale_data) { + Ok(packet) => { + let info = device.packet_cache.push( + (key, packet), + ale_data.process_id, + ale_data.direction, + true, + ); + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + } + Err(err) => { + crate::err!("failed to pend packet: {}", err); + } + }; + add_connection(device, &key, &ale_data, None); + + // Drop the packet. It will be re-injected after user space returns a verdict. + data.block_and_absorb(); + } + } +} + +// Inbound connections (ALE Auth Recv-Accept layers). +// +// Note that the ordering is the opposite of the outbound case: the inbound packet layer runs +// *before* this layer, and it only forwards packets that have no connection in the cache. So this +// callout is the entry point for new inbound connections; once a cache entry exists, following +// packets are handled by the packet layer and mostly never reach this layer. +fn ale_layer_auth_inbound(mut data: CalloutData, ale_data: AleLayerData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + + // Never pend once the teardown has started. The packet cache is being drained and user space + // is gone, so a pend made now would never be answered: its IRP would stay alive inside a + // callout that is about to be unregistered, leaving the accepting thread stuck in the kernel. + // Permit instead, the filters are being removed anyway. + if device.is_shutting_down() { + // This makes the connections created between driver unload and system shutdown invisible to user-space. + // TODO: Is there anything we can do about it? + data.action_permit(); + return; + } + + // Only TCP and UDP are associated with a connection and handled here. Everything else was + // already handled by the packet layer. + if !matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) { + data.action_permit(); + return; + } + + let key = ale_data.as_key(); + + // Check if connection is already in cache. + let verdict = device.connection_cache.get_verdict(&key); + + // Connection already in cache. + if let Some(verdict) = verdict { + crate::dbg!("processing existing connection: {} {}", key, verdict); + match verdict { + // No verdict yet: the connection is already pended and user space has not decided. + // Save this packet too so it is sent to user space and reinjected with the verdict. + Verdict::Undecided => { + crate::dbg!("saving packet: {}", key); + match save_packet_inbound(device, &mut data, &ale_data, false) { + Ok(packet) => { + let info = device.packet_cache.push( + (key, packet), + ale_data.process_id, + ale_data.direction, + true, + ); + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + } + Err(err) => { + crate::err!("failed to pend packet: {}", err); + } + }; + data.block_and_absorb(); + } + // Accepting verdicts continue to the packet layer, which applies the redirects and + // keeps counting bandwidth. + Verdict::PermanentAccept + | Verdict::Accept + | Verdict::RedirectNameServer + | Verdict::RedirectTunnel => { + data.action_permit(); + } + Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => { + // Packet layer will not see this connection. + crate::dbg!("permanent block {}", key); + data.action_block(); + } + Verdict::PermanentDrop => { + // Packet layer will not see this connection. + crate::dbg!("permanent drop {}", key); + data.block_and_absorb(); + } + // Unlike the outbound case, blocking temporary verdicts are enforced here: the packet + // layer already ran for this packet, so permitting would let it through. + Verdict::Block => { + data.action_block(); + } + Verdict::Drop => { + data.block_and_absorb(); + } + } + } else { + // Special case for incoming loopback connections. + if ale_data.is_loopback() { + // Pending an inbound loopback connection does not work. Set the verdict to accept and + // send an info-only event (missing packet id, nothing to reinject) to user space. + crate::dbg!( + "loopback inbound connection: {} PID: {}", + key, + ale_data.process_id + ); + add_connection(device, &key, &ale_data, Some(Verdict::Accept)); + + if let Some(info) = + id_cache::build_info_only(&key, ale_data.process_id, ale_data.direction) + { + let _ = device.event_queue.push(info); + } + + data.action_permit(); + return; + } + + // Only the first packet of a connection can be pended: reauthorize == false. A + // reauthorized connection has no completion handle, so the packet is instead held by + // resetting all filters once the verdict arrives (see `pend_filter_rest`). That reset + // opens a WFP write transaction, of which only one can be open at a time, so it can lose + // the race against a concurrent reset; the verdict path queues those and retries them + // (see `Device::reset_filters_and_inject`) instead of dropping the packet. + crate::dbg!("pending connection: {} {}", key, ale_data.direction); + let can_pend_connection = !ale_data.reauthorize; + match save_packet_inbound(device, &mut data, &ale_data, can_pend_connection) { + Ok(packet) => { + let info = device.packet_cache.push( + (key, packet), + ale_data.process_id, + ale_data.direction, + true, + ); + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + } + Err(err) => { + crate::err!("failed to pend packet: {}", err); + } + }; + + // Connection is not in cache, add it. + crate::dbg!("adding connection: {} PID: {}", key, ale_data.process_id); + add_connection(device, &key, &ale_data, None); + + // Drop the packet. It will be re-injected after user space returns a verdict. + data.block_and_absorb(); + } +} + +fn add_connection(device: &Device, key: &Key, ale_data: &AleLayerData, verdict: Option) { + if ale_data.is_ipv6 { + if let Ok(conn) = ConnectionV6::from_key(key, ale_data.process_id, ale_data.direction) { + if let Some(verdict) = verdict { + conn.set_verdict(verdict); + } + device.connection_cache.add_v6(conn); + } else { + crate::err!("failed to add ipv6 connection"); + } + } else { + if let Ok(conn) = ConnectionV4::from_key(key, ale_data.process_id, ale_data.direction) { + if let Some(verdict) = verdict { + conn.set_verdict(verdict); + } + device.connection_cache.add_v4(conn); + } else { + crate::err!("failed to add ipv4 connection"); + } + } +} + +// Pends the outbound connect operation, capturing the packet so it can be reinjected after the +// verdict. Only called for a genuine new connection (reauthorize == false). +fn save_packet_outbound( + device: &Device, + callout_data: &mut CalloutData, + ale_data: &AleLayerData, +) -> Result { + // During the connect state of an outbound TCP connection there is no packet data yet, so + // nothing is captured. UDP carries the datagram, which is captured for reinjection. + let packet_list = match ale_data.protocol { + IpProtocol::Udp => create_packet_list(device, callout_data, ale_data), + _ => None, + }; + match callout_data.pend_operation(packet_list) { + Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)), + Err(err) => Err(alloc::format!("failed to defer connection: {}", err)), + } +} + +// Captures the inbound packet so it can be reinjected after the verdict. +// +// `pend` selects how the operation is deferred: with a completion handle (only available when +// reauthorize == false) or, when there is none, by resetting the filters once the verdict arrives. +fn save_packet_inbound( + device: &Device, + callout_data: &mut CalloutData, + ale_data: &AleLayerData, + pend: bool, +) -> Result { + // Inbound packets always carry data, unlike the connect state of an outbound TCP connection. + let packet_list = create_packet_list(device, callout_data, ale_data); + + if pend { + match callout_data.pend_operation(packet_list) { + Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)), + Err(err) => Err(alloc::format!("failed to defer connection: {}", err)), + } + } else { + Ok(Packet::AleLayer(callout_data.pend_filter_rest(packet_list))) + } +} + +fn create_packet_list( + device: &Device, + callout_data: &mut CalloutData, + ale_data: &AleLayerData, +) -> Option { + let nbl = NetBufferList::new(callout_data.get_layer_data() as _); + + // The ALE layers hand over a single packet, so only the first net buffer of the list is ever + // relevant here (unlike the packet layers, which walk the whole chain). + let Some(mut nb) = nbl.iter_net_buffers().next() else { + crate::err!("no net buffer in ale layer data"); + return None; + }; + + let mut inbound = false; + if let Direction::Inbound = ale_data.direction { + // At this layer the net buffer starts at the transport payload. Retreat it back over the + // transport and IP headers so the whole packet can be cloned and reinjected. The net + // buffer will auto advance after it loses scope. + // + // The IP header is not a fixed size: IPv4 may carry options and IPv6 a chain of extension + // headers. WFP reports the real size in the ip header size metadata field; fall back to + // the fixed length only when the field is absent, and ignore values smaller than it as + // malformed (same handling as the inbound packet layer). + let fixed_ip_header_len = if ale_data.is_ipv6 { + IPV6_HEADER_LEN as u32 + } else { + IPV4_HEADER_LEN as u32 + }; + let ip_header_size = match callout_data.get_ip_header_size() { + Some(size) if size >= fixed_ip_header_len => size, + _ => fixed_ip_header_len, + }; + let retreat_size = ip_header_size + callout_data.get_transport_header_size(); + // A failed retreat leaves the offset at the transport header, so the clone would be + // missing the headers it has to be reinjected with. + if let Err(err) = nb.retreat(retreat_size, true) { + crate::err!("failed to retreat net buffer: {}", err); + return None; + } + inbound = true; + } + + let address: &[u8] = match &ale_data.remote_ip { + IpAddress::Ipv4(address) => &address.octets(), + IpAddress::Ipv6(address) => &address.octets(), + }; + if let Ok(clone) = nb.clone_as_nbl(&device.network_allocator) { + return Some(Injector::from_ale_callout( + ale_data.is_ipv6, + callout_data, + clone, + address, + inbound, + ale_data.interface_index, + ale_data.sub_interface_index, + )); + } + return None; +} + +pub fn endpoint_closure_v4(data: CalloutData) { + type Fields = layer::FieldsAleEndpointClosureV4; + let Some(device) = crate::entry::get_device() else { + return; + }; + let ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); + if let ValueType::FwpUint32 = ip_address_type { + let key = Key { + protocol: get_protocol(&data, Fields::IpProtocol as usize), + local_address: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_address: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + }; + + if let Some(conn) = device.connection_cache.end_v4(key) { + let info = protocol::info::connection_end_event_v4_info( + data.get_process_id().unwrap_or(0), + conn.get_direction() as u8, + u8::from(get_protocol(&data, Fields::IpProtocol as usize)), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } else { + // Invalid ip address type. Just ignore the error. + // err!( + // device.logger, + // "unknown ipv4 address type: {:?}", + // ip_address_type + // ); + } +} + +pub fn endpoint_closure_v6(data: CalloutData) { + type Fields = layer::FieldsAleEndpointClosureV6; + let Some(device) = crate::entry::get_device() else { + return; + }; + let local_ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); + let remote_ip_address_type = data.get_value_type(Fields::IpRemoteAddress as usize); + + if let ValueType::FwpByteArray16Type = local_ip_address_type { + if let ValueType::FwpByteArray16Type = remote_ip_address_type { + let key = Key { + protocol: get_protocol(&data, Fields::IpProtocol as usize), + local_address: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_address: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + }; + + if let Some(conn) = device.connection_cache.end_v6(key) { + let info = protocol::info::connection_end_event_v6_info( + data.get_process_id().unwrap_or(0), + conn.get_direction() as u8, + u8::from(get_protocol(&data, Fields::IpProtocol as usize)), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } + } +} + +pub fn ale_resource_monitor(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + match data.layer { + layer::Layer::AleResourceAssignmentV4Discard => { + type Fields = layer::FieldsAleResourceAssignmentV4; + if let Some(conns) = device.connection_cache.end_all_on_port_v4(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + dbg!( + "Port {}/{} Ipv4 assign request discarded pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v4_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceAssignmentV6Discard => { + type Fields = layer::FieldsAleResourceAssignmentV6; + if let Some(conns) = device.connection_cache.end_all_on_port_v6(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + dbg!( + "Port {}/{} Ipv6 assign request discarded pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v6_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceReleaseV4 => { + type Fields = layer::FieldsAleResourceReleaseV4; + if let Some(conns) = device.connection_cache.end_all_on_port_v4(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + dbg!( + "Port {}/{} ipv4 released pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v4_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceReleaseV6 => { + type Fields = layer::FieldsAleResourceReleaseV6; + if let Some(conns) = device.connection_cache.end_all_on_port_v6(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + dbg!( + "Port {}/{} ipv6 released pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v6_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + let _ = device.event_queue.push(info); + } + } + } + _ => {} + } +} diff --git a/driver/src/array_holder.rs b/driver/src/array_holder.rs index 5de3e53..077a249 100644 --- a/driver/src/array_holder.rs +++ b/driver/src/array_holder.rs @@ -1,25 +1,25 @@ -use core::cell::RefCell; - -use alloc::vec::Vec; - -pub struct ArrayHolder(RefCell>>); -unsafe impl Sync for ArrayHolder {} - -impl ArrayHolder { - pub const fn default() -> Self { - Self(RefCell::new(None)) - } - - pub fn save(&self, data: &[u8]) { - if let Ok(mut opt) = self.0.try_borrow_mut() { - opt.replace(data.to_vec()); - } - } - - pub fn load(&self) -> Option> { - if let Ok(mut opt) = self.0.try_borrow_mut() { - return opt.take(); - } - None - } -} +use core::cell::RefCell; + +use alloc::vec::Vec; + +pub struct ArrayHolder(RefCell>>); +unsafe impl Sync for ArrayHolder {} + +impl ArrayHolder { + pub const fn default() -> Self { + Self(RefCell::new(None)) + } + + pub fn save(&self, data: &[u8]) { + if let Ok(mut opt) = self.0.try_borrow_mut() { + opt.replace(data.to_vec()); + } + } + + pub fn load(&self) -> Option> { + if let Ok(mut opt) = self.0.try_borrow_mut() { + return opt.take(); + } + None + } +} diff --git a/driver/src/callouts.rs b/driver/src/callouts.rs index 48082fd..aef76c8 100644 --- a/driver/src/callouts.rs +++ b/driver/src/callouts.rs @@ -1,147 +1,147 @@ -use alloc::vec::Vec; -use wdk::filter_engine::callout::FilterType; -use wdk::{ - consts, - filter_engine::{callout::Callout, layer::Layer}, -}; - -use crate::{ale_callouts, packet_callouts}; - -pub fn get_callout_vec() -> Vec { - alloc::vec![ - // ----------------------------------------- - // ALE Auth layers - Callout::new( - "AleLayerOutboundV4", - "ALE layer for outbound connection for ipv4", - 0x3a090083_27e3_497f_ae5c_8172504f421e, - Layer::AleAuthConnectV4, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_connect_v4, - ), - Callout::new( - "AleLayerInboundV4", - "ALE layer for inbound connections for ipv4", - 0xf27704a5_172f_4306_9a49_dcfefe6aa236, - Layer::AleAuthRecvAcceptV4, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_accept_v4, - ), - Callout::new( - "AleLayerOutboundV6", - "ALE layer for outbound connections for ipv6", - 0x897a8324_f7e8_4713_b2d8_4084773ab47f, - Layer::AleAuthConnectV6, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_connect_v6, - ), - Callout::new( - "AleLayerInboundV6", - "ALE layer for inbound connections for ipv6", - 0x56874651_3785_4dc9_b134_4849a54de6d0, - Layer::AleAuthRecvAcceptV6, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::Resettable, - ale_callouts::ale_layer_accept_v6, - ), - // ----------------------------------------- - // ALE connection end layers - Callout::new( - "AleEndpointClosureV4", - "ALE layer for indicating closing of connection for ipv4", - 0x7aa9954a_253e_416e_949d_829ce0c215d7, - Layer::AleEndpointClosureV4, - consts::FWP_ACTION_CALLOUT_INSPECTION, - FilterType::NonResettable, - ale_callouts::endpoint_closure_v4, - ), - Callout::new( - "AleEndpointClosureV6", - "ALE layer for indicating closing of connection for ipv6", - 0x021a53c4_e149_4ba9_aa5f_7159a1466100, - Layer::AleEndpointClosureV6, - consts::FWP_ACTION_CALLOUT_INSPECTION, - FilterType::NonResettable, - ale_callouts::endpoint_closure_v6, - ), - // ----------------------------------------- - // ALE resource assignment and release. - // Callout::new( - // "AleResourceAssignmentV4", - // "Ipv4 Port assignment monitoring", - // 0x9d2d7ed8_42d4_4d73_ae9a_c888e0decbf1, - // Layer::AleResourceAssignmentV4Discard, - // consts::FWP_ACTION_CALLOUT_INSPECTION, - // FilterType::NonResettable, - // ale_callouts::ale_resource_monitor, - // ), - Callout::new( - "AleResourceReleaseV4", - "Ipv4 Port release monitor", - 0x23b3f70f_f6a4_4330_b7d0_43426a59237b, - Layer::AleResourceReleaseV4, - consts::FWP_ACTION_CALLOUT_INSPECTION, - FilterType::NonResettable, - ale_callouts::ale_resource_monitor, - ), - // Callout::new( - // "AleResourceAssignmentV6", - // "Ipv4 Port assignment monitor", - // 0x7f0246f8_dddf_4b60_b693_76943b5e5fe4, - // Layer::AleResourceAssignmentV6Discard, - // consts::FWP_ACTION_CALLOUT_INSPECTION, - // FilterType::NonResettable, - // ale_callouts::ale_resource_monitor, - // ), - Callout::new( - "AleResourceReleaseV6", - "Ipv6 Port release monitor", - 0xff60e5b5_f00b_46fd_be35_04aa316b8ff8, - Layer::AleResourceReleaseV6, - consts::FWP_ACTION_CALLOUT_INSPECTION, - FilterType::NonResettable, - ale_callouts::ale_resource_monitor, - ), - // ----------------------------------------- - // Packet layers - Callout::new( - "IPPacketOutboundV4", - "IP packet outbound network layer callout for Ipv4", - 0x6081493f_9032_49a7_9cca_5e61c28d428d, - Layer::OutboundIppacketV4, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::NonResettable, - packet_callouts::ip_packet_layer_outbound_v4, - ), - Callout::new( - "IPPacketInboundV4", - "IP packet inbound network layer callout for Ipv4", - 0x92a81d84_7942_4281_a29e_f9de59e8d844, - Layer::InboundIppacketV4, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::NonResettable, - packet_callouts::ip_packet_layer_inbound_v4, - ), - Callout::new( - "IPPacketOutboundV6", - "IP packet outbound network layer callout for Ipv6", - 0xfad4e2a7_0ed0_4332_a5e5_634ee2fb1329, - Layer::OutboundIppacketV6, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::NonResettable, - packet_callouts::ip_packet_layer_outbound_v6, - ), - Callout::new( - "IPPacketInboundV6", - "IP packet inbound network layer callout for Ipv6", - 0xf2605e9c_c72d_4375_9ebd_2aabbbaec54c, - Layer::InboundIppacketV6, - consts::FWP_ACTION_CALLOUT_TERMINATING, - FilterType::NonResettable, - packet_callouts::ip_packet_layer_inbound_v6, - ) - ] -} +use alloc::vec::Vec; +use wdk::filter_engine::callout::FilterType; +use wdk::{ + consts, + filter_engine::{callout::Callout, layer::Layer}, +}; + +use crate::{ale_callouts, packet_callouts}; + +pub fn get_callout_vec() -> Vec { + alloc::vec![ + // ----------------------------------------- + // ALE Auth layers + Callout::new( + "AleLayerOutboundV4", + "ALE layer for outbound connection for ipv4", + 0x3a090083_27e3_497f_ae5c_8172504f421e, + Layer::AleAuthConnectV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_connect_v4, + ), + Callout::new( + "AleLayerInboundV4", + "ALE layer for inbound connections for ipv4", + 0xf27704a5_172f_4306_9a49_dcfefe6aa236, + Layer::AleAuthRecvAcceptV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_accept_v4, + ), + Callout::new( + "AleLayerOutboundV6", + "ALE layer for outbound connections for ipv6", + 0x897a8324_f7e8_4713_b2d8_4084773ab47f, + Layer::AleAuthConnectV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_connect_v6, + ), + Callout::new( + "AleLayerInboundV6", + "ALE layer for inbound connections for ipv6", + 0x56874651_3785_4dc9_b134_4849a54de6d0, + Layer::AleAuthRecvAcceptV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_accept_v6, + ), + // ----------------------------------------- + // ALE connection end layers + Callout::new( + "AleEndpointClosureV4", + "ALE layer for indicating closing of connection for ipv4", + 0x7aa9954a_253e_416e_949d_829ce0c215d7, + Layer::AleEndpointClosureV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::endpoint_closure_v4, + ), + Callout::new( + "AleEndpointClosureV6", + "ALE layer for indicating closing of connection for ipv6", + 0x021a53c4_e149_4ba9_aa5f_7159a1466100, + Layer::AleEndpointClosureV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::endpoint_closure_v6, + ), + // ----------------------------------------- + // ALE resource assignment and release. + // Callout::new( + // "AleResourceAssignmentV4", + // "Ipv4 Port assignment monitoring", + // 0x9d2d7ed8_42d4_4d73_ae9a_c888e0decbf1, + // Layer::AleResourceAssignmentV4Discard, + // consts::FWP_ACTION_CALLOUT_INSPECTION, + // FilterType::NonResettable, + // ale_callouts::ale_resource_monitor, + // ), + Callout::new( + "AleResourceReleaseV4", + "Ipv4 Port release monitor", + 0x23b3f70f_f6a4_4330_b7d0_43426a59237b, + Layer::AleResourceReleaseV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::ale_resource_monitor, + ), + // Callout::new( + // "AleResourceAssignmentV6", + // "Ipv4 Port assignment monitor", + // 0x7f0246f8_dddf_4b60_b693_76943b5e5fe4, + // Layer::AleResourceAssignmentV6Discard, + // consts::FWP_ACTION_CALLOUT_INSPECTION, + // FilterType::NonResettable, + // ale_callouts::ale_resource_monitor, + // ), + Callout::new( + "AleResourceReleaseV6", + "Ipv6 Port release monitor", + 0xff60e5b5_f00b_46fd_be35_04aa316b8ff8, + Layer::AleResourceReleaseV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::ale_resource_monitor, + ), + // ----------------------------------------- + // Packet layers + Callout::new( + "IPPacketOutboundV4", + "IP packet outbound network layer callout for Ipv4", + 0x6081493f_9032_49a7_9cca_5e61c28d428d, + Layer::OutboundIppacketV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_outbound_v4, + ), + Callout::new( + "IPPacketInboundV4", + "IP packet inbound network layer callout for Ipv4", + 0x92a81d84_7942_4281_a29e_f9de59e8d844, + Layer::InboundIppacketV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_inbound_v4, + ), + Callout::new( + "IPPacketOutboundV6", + "IP packet outbound network layer callout for Ipv6", + 0xfad4e2a7_0ed0_4332_a5e5_634ee2fb1329, + Layer::OutboundIppacketV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_outbound_v6, + ), + Callout::new( + "IPPacketInboundV6", + "IP packet inbound network layer callout for Ipv6", + 0xf2605e9c_c72d_4375_9ebd_2aabbbaec54c, + Layer::InboundIppacketV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_inbound_v6, + ) + ] +} diff --git a/driver/src/common.rs b/driver/src/common.rs index 70c01b4..8c2726a 100644 --- a/driver/src/common.rs +++ b/driver/src/common.rs @@ -1,59 +1,59 @@ -#![allow(dead_code)] - -use core::fmt::Display; - -use num_derive::{FromPrimitive, ToPrimitive}; - -pub const ICMPV4_CODE_DESTINATION_UNREACHABLE: u32 = 3; -pub const ICMPV4_CODE_DU_PORT_UNREACHABLE: u32 = 3; // Destination Unreachable (Port unreachable) ; -pub const ICMPV4_CODE_DU_ADMINISTRATIVELY_PROHIBITED: u32 = 13; // Destination Unreachable (Communication Administratively Prohibited) ; - -pub const ICMPV6_CODE_DESTINATION_UNREACHABLE: u32 = 1; -pub const ICMPV6_CODE_DU_PORT_UNREACHABLE: u32 = 4; // Destination Unreachable (Port unreachable) ; - -enum Direction { - Outbound = 0, - Inbound = 1, -} - -const SIOCTL_TYPE: u32 = 40000; -macro_rules! ctl_code { - ($device_type:expr, $function:expr, $method:expr, $access:expr) => { - ($device_type << 16) | ($access << 14) | ($function << 2) | $method - }; -} - -pub const METHOD_BUFFERED: u32 = 0; -pub const METHOD_IN_DIRECT: u32 = 1; -pub const METHOD_OUT_DIRECT: u32 = 2; -pub const METHOD_NEITHER: u32 = 3; - -pub const FILE_READ_DATA: u32 = 0x0001; // file & pipe -pub const FILE_WRITE_DATA: u32 = 0x0002; // file & pipe - -#[repr(u32)] -#[derive(FromPrimitive, ToPrimitive)] -pub enum ControlCode { - Version = ctl_code!( - SIOCTL_TYPE, - 0x800, - METHOD_BUFFERED, - FILE_READ_DATA | FILE_WRITE_DATA - ), - ShutdownRequest = ctl_code!( - SIOCTL_TYPE, - 0x801, - METHOD_BUFFERED, - FILE_READ_DATA | FILE_WRITE_DATA - ), -} - -impl Display for ControlCode { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - ControlCode::Version => _ = write!(f, "Version"), - ControlCode::ShutdownRequest => _ = write!(f, "Shutdown"), - }; - return Ok(()); - } -} +#![allow(dead_code)] + +use core::fmt::Display; + +use num_derive::{FromPrimitive, ToPrimitive}; + +pub const ICMPV4_CODE_DESTINATION_UNREACHABLE: u32 = 3; +pub const ICMPV4_CODE_DU_PORT_UNREACHABLE: u32 = 3; // Destination Unreachable (Port unreachable) ; +pub const ICMPV4_CODE_DU_ADMINISTRATIVELY_PROHIBITED: u32 = 13; // Destination Unreachable (Communication Administratively Prohibited) ; + +pub const ICMPV6_CODE_DESTINATION_UNREACHABLE: u32 = 1; +pub const ICMPV6_CODE_DU_PORT_UNREACHABLE: u32 = 4; // Destination Unreachable (Port unreachable) ; + +enum Direction { + Outbound = 0, + Inbound = 1, +} + +const SIOCTL_TYPE: u32 = 40000; +macro_rules! ctl_code { + ($device_type:expr, $function:expr, $method:expr, $access:expr) => { + ($device_type << 16) | ($access << 14) | ($function << 2) | $method + }; +} + +pub const METHOD_BUFFERED: u32 = 0; +pub const METHOD_IN_DIRECT: u32 = 1; +pub const METHOD_OUT_DIRECT: u32 = 2; +pub const METHOD_NEITHER: u32 = 3; + +pub const FILE_READ_DATA: u32 = 0x0001; // file & pipe +pub const FILE_WRITE_DATA: u32 = 0x0002; // file & pipe + +#[repr(u32)] +#[derive(FromPrimitive, ToPrimitive)] +pub enum ControlCode { + Version = ctl_code!( + SIOCTL_TYPE, + 0x800, + METHOD_BUFFERED, + FILE_READ_DATA | FILE_WRITE_DATA + ), + ShutdownRequest = ctl_code!( + SIOCTL_TYPE, + 0x801, + METHOD_BUFFERED, + FILE_READ_DATA | FILE_WRITE_DATA + ), +} + +impl Display for ControlCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ControlCode::Version => _ = write!(f, "Version"), + ControlCode::ShutdownRequest => _ = write!(f, "Shutdown"), + }; + return Ok(()); + } +} diff --git a/driver/src/device.rs b/driver/src/device.rs index 0b96517..db00716 100644 --- a/driver/src/device.rs +++ b/driver/src/device.rs @@ -1,651 +1,651 @@ -use core::sync::atomic::{AtomicBool, Ordering}; - -use alloc::string::String; -use num_traits::FromPrimitive; -use protocol::{command::CommandType, info::Info}; -use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address}; -use wdk::{ - driver::Driver, - filter_engine::{ - callout_data::{ClassifyDefer, DeferResolution}, - net_buffer::{NetBufferList, NetworkAllocator}, - packet::{InjectInfo, Injector}, - FilterEngine, ResetError, - }, - ioqueue::{self, IOQueue}, - irp_helpers::{ReadRequest, WriteRequest}, -}; - -use crate::{ - array_holder::ArrayHolder, - callouts, - connection::{Connection, ConnectionV4, ConnectionV6, Key}, - connection_cache::ConnectionCache, - dbg, err, - filter_reset_queue::{FilterResetQueue, PendingReset}, - id_cache::IdCache, - info, logger, - packet_util::Redirect, -}; - -pub enum Packet { - PacketLayer(NetBufferList, InjectInfo), - AleLayer(ClassifyDefer), -} - -// Device Context -pub struct Device { - pub(crate) filter_engine: FilterEngine, - pub(crate) read_leftover: ArrayHolder, - pub(crate) event_queue: IOQueue, - pub(crate) packet_cache: IdCache, - pub(crate) connection_cache: ConnectionCache, - pub(crate) injector: Injector, - pub(crate) network_allocator: NetworkAllocator, - /// Connections that were deferred without a completion handle and are waiting for the filter - /// reset that releases them. See `reset_filters_and_inject`. - filter_reset_queue: FilterResetQueue, - /// Set once the teardown has begun. Callouts check it and stop pending operations, because - /// from that point on nothing is left to answer a pend. - shutdown_started: AtomicBool, -} - -impl Device { - /// Initialize all members of the device. Memory is handled by windows. - /// Make sure everything is initialized here. - pub fn new(driver: &Driver) -> Result { - let mut filter_engine = - match FilterEngine::new(driver, 0xf19f6eef_9031_4339_b90a_613da727ee29) { - Ok(fe) => fe, - Err(err) => return Err(alloc::format!("filter engine error: {}", err)), - }; - - if let Err(err) = filter_engine.commit(callouts::get_callout_vec()) { - return Err(err); - } - - Ok(Self { - filter_engine, - read_leftover: ArrayHolder::default(), - event_queue: IOQueue::new(), - packet_cache: IdCache::new(), - connection_cache: ConnectionCache::new(), - injector: Injector::new(), - network_allocator: NetworkAllocator::new(), - filter_reset_queue: FilterResetQueue::new(), - shutdown_started: AtomicBool::new(false), - }) - } - - /// Reports whether the teardown has started. Callouts must not pend a new operation once this - /// is set: the packet cache is being drained and the user space handle is on its way out, so - /// a pend made now would never be completed. - pub fn is_shutting_down(&self) -> bool { - self.shutdown_started.load(Ordering::SeqCst) - } - - /// Cleanup is called just before drop. - // pub fn cleanup(&mut self) {} - - fn write_buffer(&mut self, read_request: &mut ReadRequest, info: Info) { - let bytes = info.as_bytes(); - let count = read_request.write(bytes); - - // Check if the full buffer was written. - if count < bytes.len() { - // Save the leftovers for later. - self.read_leftover.save(&bytes[count..]); - } - } - - /// Called when handle. Read is called from user-space. - pub fn read(&mut self, read_request: &mut ReadRequest) { - if let Some(data) = self.read_leftover.load() { - // There are leftovers from previous request. - let count = read_request.write(&data); - - // Check if full command was written. - if count < data.len() { - // Save the leftovers for later. - self.read_leftover.save(&data[count..]); - } - } else { - // Noting left from before. Wait for next commands. - match self.event_queue.wait_and_pop() { - Ok(info) => { - self.write_buffer(read_request, info); - } - Err(ioqueue::Status::Timeout) => { - // Timeout. This will only trigger if pop function is called with timeout. - read_request.timeout(); - return; - } - Err(err) => { - // Queue failed. Send EOF, to notify user-space. Usually happens on rundown. - err!("failed to pop value: {}", err); - read_request.end_of_file(); - return; - } - } - } - - // Check if we have more space. InfoType + data_size == 5 bytes - while read_request.free_space() > 5 { - match self.event_queue.pop() { - Ok(info) => { - self.write_buffer(read_request, info); - } - Err(_) => { - break; - } - } - } - read_request.complete(); - } - - // Called when handle.Write is called from user-space. - pub fn write(&mut self, write_request: &mut WriteRequest) { - // Try parsing the command. - let mut buffer = write_request.get_buffer(); - let command = protocol::command::parse_type(buffer); - let Some(command) = command else { - err!("Unknown command number: {}", buffer[0]); - return; - }; - - // Skip command byte. - buffer = &buffer[1..]; - - let mut _classify_defer = None; - - match command { - CommandType::Shutdown => { - wdk::dbg!("Shutdown command"); - self.shutdown(); - } - CommandType::Verdict => { - let verdict = protocol::command::parse_verdict(buffer); - wdk::dbg!("Verdict command"); - // Received verdict decision for a specific connection. - if let Some((key, mut packet)) = self.packet_cache.pop_id(verdict.id) { - if let Some(verdict) = FromPrimitive::from_u8(verdict.verdict) { - dbg!("Verdict received {}: {}", key, verdict); - // Add verdict in the cache. - let redirect_info = self.connection_cache.update_connection(key, verdict); - - match verdict { - crate::connection::Verdict::Accept - | crate::connection::Verdict::PermanentAccept => { - if let Err(err) = self.inject_packet(packet, false) { - err!("failed to inject packet: {} key={}", err, key); - } else { - dbg!("packet injected: {}", key); - } - } - crate::connection::Verdict::RedirectNameServer - | crate::connection::Verdict::RedirectTunnel => { - if let Some(redirect_info) = redirect_info { - if let Err(err) = packet.redirect(redirect_info) { - err!("failed to redirect packet: {} key={}", err, key); - } - if let Err(err) = self.inject_packet(packet, false) { - err!("failed to inject packet: {} key={}", err, key); - } - } - } - _ => { - if let Err(err) = self.inject_packet(packet, true) { - err!("failed to inject packet: {} key={}", err, key); - } - } - } - }; - } else { - // Id was not in the packet cache. - let id = verdict.id; - err!("Verdict invalid id: {}", id); - } - } - CommandType::UpdateV4 => { - let update = protocol::command::parse_update_v4(buffer); - // Build the new action. - if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { - // Update with new action. - dbg!("Verdict update received {:?}: {}", update, verdict); - let key = Key { - protocol: IpProtocol::from(update.protocol), - local_address: IpAddress::Ipv4(Ipv4Address::from_octets( - update.local_address, - )), - local_port: update.local_port, - remote_address: IpAddress::Ipv4(Ipv4Address::from_octets( - update.remote_address, - )), - remote_port: update.remote_port, - }; - _classify_defer = self.connection_cache.update_connection(key, verdict); - } else { - err!("invalid verdict value: {}", update.verdict); - } - } - CommandType::UpdateV6 => { - let update = protocol::command::parse_update_v6(buffer); - // Build the new action. - if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { - // Update with new action. - dbg!("Verdict update received {:?}: {}", update, verdict); - let key = Key { - protocol: IpProtocol::from(update.protocol), - local_address: IpAddress::Ipv6(Ipv6Address::from_octets( - update.local_address, - )), - local_port: update.local_port, - remote_address: IpAddress::Ipv6(Ipv6Address::from_octets( - update.remote_address, - )), - remote_port: update.remote_port, - }; - _classify_defer = self.connection_cache.update_connection(key, verdict); - } else { - err!("invalid verdict value: {}", update.verdict); - } - } - CommandType::ClearCache => { - wdk::dbg!("ClearCache command"); - self.connection_cache.clear(); - // Goes through the same queue as the deferred reauthorizations: it is the same - // reset, and only one of them can run at a time. Carries no packet of its own. - self.reset_filters_and_inject(None); - } - CommandType::GetConnectionsUpdate => { - let update = protocol::command::parse_update_info(buffer); - let timestamp = update.timestamp; - wdk::dbg!("GetConnectionsUpdate command"); - - let send_event_v4 = |conn: &ConnectionV4| { - // Function is behind spin lock. Dont do expensive operations. - if conn.last_accessed_timestamp.load(Ordering::SeqCst) > timestamp { - return; - } - - let info = protocol::info::connection_update_event_v4_info( - conn.protocol.into(), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - _ = self.event_queue.push(info); - }; - - let send_event_v6 = |conn: &ConnectionV6| { - // Function is behind spin lock. Dont do expensive operations. - if conn.last_accessed_timestamp.load(Ordering::SeqCst) > timestamp { - return; - } - - let info = protocol::info::connection_update_event_v6_info( - conn.protocol.into(), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - _ = self.event_queue.push(info); - }; - - self.connection_cache - .walk_over_connections_v4(send_event_v4); - self.connection_cache - .walk_over_connections_v6(send_event_v6); - - _ = self - .event_queue - .push(protocol::info::connection_update_end_info()); - } - CommandType::GetLogs => { - wdk::dbg!("GetLogs command"); - let lines_vec = logger::flush(); - for line in lines_vec { - let _ = self.event_queue.push(line); - } - } - CommandType::PrintMemoryStats => { - wdk::dbg!("PrintMemoryStats command"); - use core::fmt::Write; - - let (active, ended) = self.connection_cache.get_entries_count(); - let packet_cache_count = self.packet_cache.get_entries_count(); - let (unlinked_v4, unlinked_v6) = self.connection_cache.get_unlinked_queue_counts(); - let filter_reset_count = self.filter_reset_queue.get_entries_count(); - - { - let mut log_line = protocol::info::log_line( - protocol::info::Severity::Info, - logger::MAX_LOG_LINE_SIZE, - ); - _ = write!( - log_line, - "MemStats: connections active={} ended={} | packet_cache={} | unlinked_ports v4={} v6={} | filter_reset_queue={}", - active, ended, packet_cache_count, unlinked_v4, unlinked_v6, filter_reset_count - ); - logger::add_line(log_line); - } - - self.connection_cache - .walk_over_connections_v4(|conn: &ConnectionV4| { - let proto = match conn.protocol { - IpProtocol::Tcp => "TCP", - IpProtocol::Udp => "UDP", - _ => "???", - }; - let status = if conn.has_ended() { " [ENDED]" } else { "" }; - let mut log_line = protocol::info::log_line( - protocol::info::Severity::Info, - logger::MAX_LOG_LINE_SIZE, - ); - _ = write!( - log_line, - "[{}][{}] {}:{}-{}:{} pid={} {} rx={}B tx={}B{}", - proto, - conn.direction, - conn.local_address, - conn.local_port, - conn.remote_address, - conn.remote_port, - conn.process_id, - conn.get_verdict(), - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - status, - ); - logger::add_line(log_line); - }); - - self.connection_cache - .walk_over_connections_v6(|conn: &ConnectionV6| { - let proto = match conn.protocol { - IpProtocol::Tcp => "TCP", - IpProtocol::Udp => "UDP", - _ => "???", - }; - let status = if conn.has_ended() { " [ENDED]" } else { "" }; - let mut log_line = protocol::info::log_line( - protocol::info::Severity::Info, - logger::MAX_LOG_LINE_SIZE, - ); - _ = write!( - log_line, - "[{}][{}] {}:{}-{}:{} pid={} {} rx={}B tx={}B{}", - proto, - conn.direction, - conn.local_address, - conn.local_port, - conn.remote_address, - conn.remote_port, - conn.process_id, - conn.get_verdict(), - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - status, - ); - logger::add_line(log_line); - }); - } - CommandType::CleanEndedConnections => { - wdk::dbg!("CleanEndedConnections command"); - let (conn_v4, conn_v6) = self.connection_cache.clean_ended_connections(); - - // Process ended ipv4 connections - for conn in conn_v4.iter() { - let info = protocol::info::connection_end_event_v4_info( - conn.get_process_id(), - conn.get_direction() as u8, - conn.protocol.into(), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - _ = self.event_queue.push(info); - } - - conn_v4.clear(); - - // Process ended ipv6 connections - for conn in conn_v6.iter() { - let info = protocol::info::connection_end_event_v6_info( - conn.get_process_id(), - conn.get_direction() as u8, - conn.protocol.into(), - conn.local_address.octets(), - conn.remote_address.octets(), - conn.local_port, - conn.remote_port, - conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), - conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), - ); - _ = self.event_queue.push(info); - } - conn_v6.clear(); - } - } - } - - /// Tears the device down, in the only order that leaves nothing behind for the unload: - /// remove the filters, complete everything that is still pended, then remove the callouts. - /// - /// Reachable from the shutdown command (write and device-control) and from the unload path, - /// and safe to call more than once: the one-time part runs once, the rest runs on every call - /// so anything that raced in afterwards is still resolved. - pub fn shutdown(&mut self) { - if !self.shutdown_started.swap(true, Ordering::SeqCst) { - // Remove the filters before anything else. Once they are gone no new classify call can - // reach the callouts, so no new connection can be pended while the teardown runs. The - // flag set above covers the classify calls that are already in flight. - if let Err(err) = self.filter_engine.unregister_filters() { - err!("failed to unregister filters on shutdown: {}", err); - } - - // End blocking operations from the queue. This will end pending read requests. - self.event_queue.rundown(); - } - - // Resolve all pending packets. This is important for proper driver unload. - self.complete_pending_packets(); - - // Nothing is pended anymore, so the callouts can be removed. This has to happen here and - // not be left to the filter engine being dropped: unregistering a callout fails while an - // operation is still pended on it, and the drop has no way to complete one. - if let Err(err) = self.filter_engine.unregister_callouts() { - err!("failed to unregister callouts on shutdown: {}", err); - } - } - - /// Completes every operation still held in the packet cache. A pended ALE operation keeps its - /// IRP alive inside the callout: if the driver unloads while one is still outstanding, that - /// IRP can never be completed, the thread that owns it never leaves kernel mode, and its - /// process can no longer be terminated. Everything is resolved with a verdict here so the - /// callouts can be unregistered cleanly. - fn complete_pending_packets(&mut self) { - for _ in 0..10 { - // Wait before every pass, including the first one, so a classify that was still on its - // way to pend_operation when the filters were removed has time to land in the cache. - if let Err(err) = wdk::utils::sleep_ms(10) { - err!("failed to wait between drain passes: {}", err); - } - - let pending_packets = self.packet_cache.pop_all(); - if pending_packets.is_empty() { - // Keep going rather than stopping here: an empty pass does not prove the cache - // will stay empty, only that nothing had arrived yet. - continue; - } - - for el in pending_packets { - let key = el.value.0; - let packet = el.value.1; - // Set any verdict. Driver will unload after that and the filter will not be active. - _ = self - .connection_cache - .update_connection(key, crate::connection::Verdict::PermanentBlock); - _ = self.inject_packet(packet, true); // Blocked must be set, so it only handles the ALE layer. - } - } - } - - pub fn inject_packet(&mut self, packet: Packet, blocked: bool) -> Result<(), String> { - match packet { - Packet::PacketLayer(nbl, inject_info) => { - if !blocked { - self.injector.inject_net_buffer_list(nbl, inject_info) - } else { - Ok(()) - } - } - Packet::AleLayer(defer) => match defer.resolve() { - // The pended operation has been completed, so the packet can go out right away. - DeferResolution::Completed(packet_list) => self.inject_pending(packet_list), - // The connection is still held by the filter that blocked it. Reset the filters to - // let it go, then inject. - DeferResolution::HeldByFilter(packet_list) => { - self.reset_filters_and_inject(packet_list); - Ok(()) - } - }, - } - } - - /// Resets the filters to let go of the connections that are still held by one, and injects the - /// packets that were captured with them. - /// - /// This is the only place the filters are reset while the driver is running, and the reset only - /// ever happens on the thread holding the drain claim, so two of them can never run at once. - /// That matters because a reset opens a WFP write transaction and only one transaction can be - /// open on the filter engine session at a time. - /// - /// The connection is therefore always queued first, never reset for on the spot. Whether the - /// calling thread goes on to do the work depends only on whether another thread is already doing - /// it: - /// - The claim is free: this thread takes it and works the queue until it runs empty, so its - /// own packet is injected before this returns. - /// - The claim is taken: this returns right away. The holder's next reset releases this - /// connection too — it was deferred before that reset, which is all it takes. - /// - /// Nothing is lost either way: the entry is queued before the claim is even looked at, and the - /// claim is only given back while the queue is empty (see `filter_reset_queue`). - /// - /// A reset that still loses the race for the transaction — against the teardown removing the - /// filters, or the initial commit registering them — is retried every - /// `FILTER_RESET_RETRY_INTERVAL_MS`, up to `FILTER_RESET_MAX_ATTEMPTS` times, with everything - /// that piled up in the meantime released by the same reset. Waiting means this is only usable - /// at IRQL <= APC_LEVEL; every caller reaches it from a user space write or from the teardown, - /// so that holds. - /// - /// Reports failures through the log rather than to the caller: a reset serves a whole batch, so - /// a failed one is not attributable to the connection that happened to trigger it. - fn reset_filters_and_inject(&mut self, pending: PendingReset) { - if !self.filter_reset_queue.push_and_claim(pending) { - return; - } - - // Attempts spent in a row on the current batch. - let mut attempts = 0; - - loop { - // Take everything that is queued: one reset releases all of it. Only what was queued - // before the reset, though — whatever arrives while the transaction below runs is not - // released by it and stays queued for the next pass. - // - // This is also the only way out of the loop, and it gives the claim back under the same - // lock that guards the entries, so the queue can never be left with work and no worker. - let Some(batch) = self.filter_reset_queue.take_all_or_release() else { - return; - }; - - match self.filter_engine.reset_all_filters() { - Ok(()) => { - attempts = 0; - let len = batch.len(); - for pending in batch { - if let Err(err) = self.inject_pending(pending) { - err!("failed to inject packet after filter reset: {}", err); - } - } - info!( - "injected {} reauthorized packets after {} attempts", - len, attempts - ) - } - Err(ResetError::TransactionInProgress) => { - attempts += 1; - if attempts >= 20 { - // Give up on this batch. The packets are freed with it and the connections - // stay held; the peer retransmits and the next packet is classified again. - err!( - "giving up on the filter reset after {} attempts, dropping {} packet(s)", - attempts, - batch.len() - ); - attempts = 0; - continue; - } - - // Nothing was changed by the failed attempt, so put the batch back and try the - // same reset again. A failed wait only makes the attempts come faster, not - // endlessly: they stay bounded and the loop still ends once the queue is empty. - self.filter_reset_queue.push_front_all(batch); - if let Err(err) = wdk::utils::sleep_ms(10) { - err!("failed to wait before the next filter reset: {}", err); - } - } - Err(err) => { - // Not transient. Drop the batch with its packets and carry on with the queue. - err!( - "failed to reset filters: {}, dropping {} packet(s)", - String::from(err), - batch.len() - ); - attempts = 0; - } - } - } - } - - /// Injects a packet captured with a deferred connection, if there was one to capture. - fn inject_pending(&self, pending: PendingReset) -> Result<(), String> { - let Some(packet_list) = pending else { - return Ok(()); - }; - - return self.injector.inject_packet_list_transport(packet_list); - } -} - -impl Drop for Device { - fn drop(&mut self) { - // The driver can also be unloaded without ever receiving a shutdown command (service stop, - // driver removal, a user space process that died). Run the same teardown here so no pended - // operation is ever left behind. This still runs before the filter engine is dropped, so - // completing the operations and removing the callouts is valid at this point; by the time - // the engine drops there is nothing left for its own failsafe to do. - self.shutdown(); - - _ = logger::flush(); - // dbg!("Device Context drop called."); - } -} +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::string::String; +use num_traits::FromPrimitive; +use protocol::{command::CommandType, info::Info}; +use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address}; +use wdk::{ + driver::Driver, + filter_engine::{ + callout_data::{ClassifyDefer, DeferResolution}, + net_buffer::{NetBufferList, NetworkAllocator}, + packet::{InjectInfo, Injector}, + FilterEngine, ResetError, + }, + ioqueue::{self, IOQueue}, + irp_helpers::{ReadRequest, WriteRequest}, +}; + +use crate::{ + array_holder::ArrayHolder, + callouts, + connection::{Connection, ConnectionV4, ConnectionV6, Key}, + connection_cache::ConnectionCache, + dbg, err, + filter_reset_queue::{FilterResetQueue, PendingReset}, + id_cache::IdCache, + info, logger, + packet_util::Redirect, +}; + +pub enum Packet { + PacketLayer(NetBufferList, InjectInfo), + AleLayer(ClassifyDefer), +} + +// Device Context +pub struct Device { + pub(crate) filter_engine: FilterEngine, + pub(crate) read_leftover: ArrayHolder, + pub(crate) event_queue: IOQueue, + pub(crate) packet_cache: IdCache, + pub(crate) connection_cache: ConnectionCache, + pub(crate) injector: Injector, + pub(crate) network_allocator: NetworkAllocator, + /// Connections that were deferred without a completion handle and are waiting for the filter + /// reset that releases them. See `reset_filters_and_inject`. + filter_reset_queue: FilterResetQueue, + /// Set once the teardown has begun. Callouts check it and stop pending operations, because + /// from that point on nothing is left to answer a pend. + shutdown_started: AtomicBool, +} + +impl Device { + /// Initialize all members of the device. Memory is handled by windows. + /// Make sure everything is initialized here. + pub fn new(driver: &Driver) -> Result { + let mut filter_engine = + match FilterEngine::new(driver, 0xf19f6eef_9031_4339_b90a_613da727ee29) { + Ok(fe) => fe, + Err(err) => return Err(alloc::format!("filter engine error: {}", err)), + }; + + if let Err(err) = filter_engine.commit(callouts::get_callout_vec()) { + return Err(err); + } + + Ok(Self { + filter_engine, + read_leftover: ArrayHolder::default(), + event_queue: IOQueue::new(), + packet_cache: IdCache::new(), + connection_cache: ConnectionCache::new(), + injector: Injector::new(), + network_allocator: NetworkAllocator::new(), + filter_reset_queue: FilterResetQueue::new(), + shutdown_started: AtomicBool::new(false), + }) + } + + /// Reports whether the teardown has started. Callouts must not pend a new operation once this + /// is set: the packet cache is being drained and the user space handle is on its way out, so + /// a pend made now would never be completed. + pub fn is_shutting_down(&self) -> bool { + self.shutdown_started.load(Ordering::SeqCst) + } + + /// Cleanup is called just before drop. + // pub fn cleanup(&mut self) {} + + fn write_buffer(&mut self, read_request: &mut ReadRequest, info: Info) { + let bytes = info.as_bytes(); + let count = read_request.write(bytes); + + // Check if the full buffer was written. + if count < bytes.len() { + // Save the leftovers for later. + self.read_leftover.save(&bytes[count..]); + } + } + + /// Called when handle. Read is called from user-space. + pub fn read(&mut self, read_request: &mut ReadRequest) { + if let Some(data) = self.read_leftover.load() { + // There are leftovers from previous request. + let count = read_request.write(&data); + + // Check if full command was written. + if count < data.len() { + // Save the leftovers for later. + self.read_leftover.save(&data[count..]); + } + } else { + // Noting left from before. Wait for next commands. + match self.event_queue.wait_and_pop() { + Ok(info) => { + self.write_buffer(read_request, info); + } + Err(ioqueue::Status::Timeout) => { + // Timeout. This will only trigger if pop function is called with timeout. + read_request.timeout(); + return; + } + Err(err) => { + // Queue failed. Send EOF, to notify user-space. Usually happens on rundown. + err!("failed to pop value: {}", err); + read_request.end_of_file(); + return; + } + } + } + + // Check if we have more space. InfoType + data_size == 5 bytes + while read_request.free_space() > 5 { + match self.event_queue.pop() { + Ok(info) => { + self.write_buffer(read_request, info); + } + Err(_) => { + break; + } + } + } + read_request.complete(); + } + + // Called when handle.Write is called from user-space. + pub fn write(&mut self, write_request: &mut WriteRequest) { + // Try parsing the command. + let mut buffer = write_request.get_buffer(); + let command = protocol::command::parse_type(buffer); + let Some(command) = command else { + err!("Unknown command number: {}", buffer[0]); + return; + }; + + // Skip command byte. + buffer = &buffer[1..]; + + let mut _classify_defer = None; + + match command { + CommandType::Shutdown => { + wdk::dbg!("Shutdown command"); + self.shutdown(); + } + CommandType::Verdict => { + let verdict = protocol::command::parse_verdict(buffer); + wdk::dbg!("Verdict command"); + // Received verdict decision for a specific connection. + if let Some((key, mut packet)) = self.packet_cache.pop_id(verdict.id) { + if let Some(verdict) = FromPrimitive::from_u8(verdict.verdict) { + dbg!("Verdict received {}: {}", key, verdict); + // Add verdict in the cache. + let redirect_info = self.connection_cache.update_connection(key, verdict); + + match verdict { + crate::connection::Verdict::Accept + | crate::connection::Verdict::PermanentAccept => { + if let Err(err) = self.inject_packet(packet, false) { + err!("failed to inject packet: {} key={}", err, key); + } else { + dbg!("packet injected: {}", key); + } + } + crate::connection::Verdict::RedirectNameServer + | crate::connection::Verdict::RedirectTunnel => { + if let Some(redirect_info) = redirect_info { + if let Err(err) = packet.redirect(redirect_info) { + err!("failed to redirect packet: {} key={}", err, key); + } + if let Err(err) = self.inject_packet(packet, false) { + err!("failed to inject packet: {} key={}", err, key); + } + } + } + _ => { + if let Err(err) = self.inject_packet(packet, true) { + err!("failed to inject packet: {} key={}", err, key); + } + } + } + }; + } else { + // Id was not in the packet cache. + let id = verdict.id; + err!("Verdict invalid id: {}", id); + } + } + CommandType::UpdateV4 => { + let update = protocol::command::parse_update_v4(buffer); + // Build the new action. + if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { + // Update with new action. + dbg!("Verdict update received {:?}: {}", update, verdict); + let key = Key { + protocol: IpProtocol::from(update.protocol), + local_address: IpAddress::Ipv4(Ipv4Address::from_octets( + update.local_address, + )), + local_port: update.local_port, + remote_address: IpAddress::Ipv4(Ipv4Address::from_octets( + update.remote_address, + )), + remote_port: update.remote_port, + }; + _classify_defer = self.connection_cache.update_connection(key, verdict); + } else { + err!("invalid verdict value: {}", update.verdict); + } + } + CommandType::UpdateV6 => { + let update = protocol::command::parse_update_v6(buffer); + // Build the new action. + if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { + // Update with new action. + dbg!("Verdict update received {:?}: {}", update, verdict); + let key = Key { + protocol: IpProtocol::from(update.protocol), + local_address: IpAddress::Ipv6(Ipv6Address::from_octets( + update.local_address, + )), + local_port: update.local_port, + remote_address: IpAddress::Ipv6(Ipv6Address::from_octets( + update.remote_address, + )), + remote_port: update.remote_port, + }; + _classify_defer = self.connection_cache.update_connection(key, verdict); + } else { + err!("invalid verdict value: {}", update.verdict); + } + } + CommandType::ClearCache => { + wdk::dbg!("ClearCache command"); + self.connection_cache.clear(); + // Goes through the same queue as the deferred reauthorizations: it is the same + // reset, and only one of them can run at a time. Carries no packet of its own. + self.reset_filters_and_inject(None); + } + CommandType::GetConnectionsUpdate => { + let update = protocol::command::parse_update_info(buffer); + let timestamp = update.timestamp; + wdk::dbg!("GetConnectionsUpdate command"); + + let send_event_v4 = |conn: &ConnectionV4| { + // Function is behind spin lock. Dont do expensive operations. + if conn.last_accessed_timestamp.load(Ordering::SeqCst) > timestamp { + return; + } + + let info = protocol::info::connection_update_event_v4_info( + conn.protocol.into(), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + _ = self.event_queue.push(info); + }; + + let send_event_v6 = |conn: &ConnectionV6| { + // Function is behind spin lock. Dont do expensive operations. + if conn.last_accessed_timestamp.load(Ordering::SeqCst) > timestamp { + return; + } + + let info = protocol::info::connection_update_event_v6_info( + conn.protocol.into(), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + _ = self.event_queue.push(info); + }; + + self.connection_cache + .walk_over_connections_v4(send_event_v4); + self.connection_cache + .walk_over_connections_v6(send_event_v6); + + _ = self + .event_queue + .push(protocol::info::connection_update_end_info()); + } + CommandType::GetLogs => { + wdk::dbg!("GetLogs command"); + let lines_vec = logger::flush(); + for line in lines_vec { + let _ = self.event_queue.push(line); + } + } + CommandType::PrintMemoryStats => { + wdk::dbg!("PrintMemoryStats command"); + use core::fmt::Write; + + let (active, ended) = self.connection_cache.get_entries_count(); + let packet_cache_count = self.packet_cache.get_entries_count(); + let (unlinked_v4, unlinked_v6) = self.connection_cache.get_unlinked_queue_counts(); + let filter_reset_count = self.filter_reset_queue.get_entries_count(); + + { + let mut log_line = protocol::info::log_line( + protocol::info::Severity::Info, + logger::MAX_LOG_LINE_SIZE, + ); + _ = write!( + log_line, + "MemStats: connections active={} ended={} | packet_cache={} | unlinked_ports v4={} v6={} | filter_reset_queue={}", + active, ended, packet_cache_count, unlinked_v4, unlinked_v6, filter_reset_count + ); + logger::add_line(log_line); + } + + self.connection_cache + .walk_over_connections_v4(|conn: &ConnectionV4| { + let proto = match conn.protocol { + IpProtocol::Tcp => "TCP", + IpProtocol::Udp => "UDP", + _ => "???", + }; + let status = if conn.has_ended() { " [ENDED]" } else { "" }; + let mut log_line = protocol::info::log_line( + protocol::info::Severity::Info, + logger::MAX_LOG_LINE_SIZE, + ); + _ = write!( + log_line, + "[{}][{}] {}:{}-{}:{} pid={} {} rx={}B tx={}B{}", + proto, + conn.direction, + conn.local_address, + conn.local_port, + conn.remote_address, + conn.remote_port, + conn.process_id, + conn.get_verdict(), + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + status, + ); + logger::add_line(log_line); + }); + + self.connection_cache + .walk_over_connections_v6(|conn: &ConnectionV6| { + let proto = match conn.protocol { + IpProtocol::Tcp => "TCP", + IpProtocol::Udp => "UDP", + _ => "???", + }; + let status = if conn.has_ended() { " [ENDED]" } else { "" }; + let mut log_line = protocol::info::log_line( + protocol::info::Severity::Info, + logger::MAX_LOG_LINE_SIZE, + ); + _ = write!( + log_line, + "[{}][{}] {}:{}-{}:{} pid={} {} rx={}B tx={}B{}", + proto, + conn.direction, + conn.local_address, + conn.local_port, + conn.remote_address, + conn.remote_port, + conn.process_id, + conn.get_verdict(), + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + status, + ); + logger::add_line(log_line); + }); + } + CommandType::CleanEndedConnections => { + wdk::dbg!("CleanEndedConnections command"); + let (conn_v4, conn_v6) = self.connection_cache.clean_ended_connections(); + + // Process ended ipv4 connections + for conn in conn_v4.iter() { + let info = protocol::info::connection_end_event_v4_info( + conn.get_process_id(), + conn.get_direction() as u8, + conn.protocol.into(), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + _ = self.event_queue.push(info); + } + + conn_v4.clear(); + + // Process ended ipv6 connections + for conn in conn_v6.iter() { + let info = protocol::info::connection_end_event_v6_info( + conn.get_process_id(), + conn.get_direction() as u8, + conn.protocol.into(), + conn.local_address.octets(), + conn.remote_address.octets(), + conn.local_port, + conn.remote_port, + conn.bandwidth_usage.rx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.rx_packets.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_bytes.load(Ordering::SeqCst), + conn.bandwidth_usage.tx_packets.load(Ordering::SeqCst), + ); + _ = self.event_queue.push(info); + } + conn_v6.clear(); + } + } + } + + /// Tears the device down, in the only order that leaves nothing behind for the unload: + /// remove the filters, complete everything that is still pended, then remove the callouts. + /// + /// Reachable from the shutdown command (write and device-control) and from the unload path, + /// and safe to call more than once: the one-time part runs once, the rest runs on every call + /// so anything that raced in afterwards is still resolved. + pub fn shutdown(&mut self) { + if !self.shutdown_started.swap(true, Ordering::SeqCst) { + // Remove the filters before anything else. Once they are gone no new classify call can + // reach the callouts, so no new connection can be pended while the teardown runs. The + // flag set above covers the classify calls that are already in flight. + if let Err(err) = self.filter_engine.unregister_filters() { + err!("failed to unregister filters on shutdown: {}", err); + } + + // End blocking operations from the queue. This will end pending read requests. + self.event_queue.rundown(); + } + + // Resolve all pending packets. This is important for proper driver unload. + self.complete_pending_packets(); + + // Nothing is pended anymore, so the callouts can be removed. This has to happen here and + // not be left to the filter engine being dropped: unregistering a callout fails while an + // operation is still pended on it, and the drop has no way to complete one. + if let Err(err) = self.filter_engine.unregister_callouts() { + err!("failed to unregister callouts on shutdown: {}", err); + } + } + + /// Completes every operation still held in the packet cache. A pended ALE operation keeps its + /// IRP alive inside the callout: if the driver unloads while one is still outstanding, that + /// IRP can never be completed, the thread that owns it never leaves kernel mode, and its + /// process can no longer be terminated. Everything is resolved with a verdict here so the + /// callouts can be unregistered cleanly. + fn complete_pending_packets(&mut self) { + for _ in 0..10 { + // Wait before every pass, including the first one, so a classify that was still on its + // way to pend_operation when the filters were removed has time to land in the cache. + if let Err(err) = wdk::utils::sleep_ms(10) { + err!("failed to wait between drain passes: {}", err); + } + + let pending_packets = self.packet_cache.pop_all(); + if pending_packets.is_empty() { + // Keep going rather than stopping here: an empty pass does not prove the cache + // will stay empty, only that nothing had arrived yet. + continue; + } + + for el in pending_packets { + let key = el.value.0; + let packet = el.value.1; + // Set any verdict. Driver will unload after that and the filter will not be active. + _ = self + .connection_cache + .update_connection(key, crate::connection::Verdict::PermanentBlock); + _ = self.inject_packet(packet, true); // Blocked must be set, so it only handles the ALE layer. + } + } + } + + pub fn inject_packet(&mut self, packet: Packet, blocked: bool) -> Result<(), String> { + match packet { + Packet::PacketLayer(nbl, inject_info) => { + if !blocked { + self.injector.inject_net_buffer_list(nbl, inject_info) + } else { + Ok(()) + } + } + Packet::AleLayer(defer) => match defer.resolve() { + // The pended operation has been completed, so the packet can go out right away. + DeferResolution::Completed(packet_list) => self.inject_pending(packet_list), + // The connection is still held by the filter that blocked it. Reset the filters to + // let it go, then inject. + DeferResolution::HeldByFilter(packet_list) => { + self.reset_filters_and_inject(packet_list); + Ok(()) + } + }, + } + } + + /// Resets the filters to let go of the connections that are still held by one, and injects the + /// packets that were captured with them. + /// + /// This is the only place the filters are reset while the driver is running, and the reset only + /// ever happens on the thread holding the drain claim, so two of them can never run at once. + /// That matters because a reset opens a WFP write transaction and only one transaction can be + /// open on the filter engine session at a time. + /// + /// The connection is therefore always queued first, never reset for on the spot. Whether the + /// calling thread goes on to do the work depends only on whether another thread is already doing + /// it: + /// - The claim is free: this thread takes it and works the queue until it runs empty, so its + /// own packet is injected before this returns. + /// - The claim is taken: this returns right away. The holder's next reset releases this + /// connection too — it was deferred before that reset, which is all it takes. + /// + /// Nothing is lost either way: the entry is queued before the claim is even looked at, and the + /// claim is only given back while the queue is empty (see `filter_reset_queue`). + /// + /// A reset that still loses the race for the transaction — against the teardown removing the + /// filters, or the initial commit registering them — is retried every + /// `FILTER_RESET_RETRY_INTERVAL_MS`, up to `FILTER_RESET_MAX_ATTEMPTS` times, with everything + /// that piled up in the meantime released by the same reset. Waiting means this is only usable + /// at IRQL <= APC_LEVEL; every caller reaches it from a user space write or from the teardown, + /// so that holds. + /// + /// Reports failures through the log rather than to the caller: a reset serves a whole batch, so + /// a failed one is not attributable to the connection that happened to trigger it. + fn reset_filters_and_inject(&mut self, pending: PendingReset) { + if !self.filter_reset_queue.push_and_claim(pending) { + return; + } + + // Attempts spent in a row on the current batch. + let mut attempts = 0; + + loop { + // Take everything that is queued: one reset releases all of it. Only what was queued + // before the reset, though — whatever arrives while the transaction below runs is not + // released by it and stays queued for the next pass. + // + // This is also the only way out of the loop, and it gives the claim back under the same + // lock that guards the entries, so the queue can never be left with work and no worker. + let Some(batch) = self.filter_reset_queue.take_all_or_release() else { + return; + }; + + match self.filter_engine.reset_all_filters() { + Ok(()) => { + attempts = 0; + let len = batch.len(); + for pending in batch { + if let Err(err) = self.inject_pending(pending) { + err!("failed to inject packet after filter reset: {}", err); + } + } + info!( + "injected {} reauthorized packets after {} attempts", + len, attempts + ) + } + Err(ResetError::TransactionInProgress) => { + attempts += 1; + if attempts >= 20 { + // Give up on this batch. The packets are freed with it and the connections + // stay held; the peer retransmits and the next packet is classified again. + err!( + "giving up on the filter reset after {} attempts, dropping {} packet(s)", + attempts, + batch.len() + ); + attempts = 0; + continue; + } + + // Nothing was changed by the failed attempt, so put the batch back and try the + // same reset again. A failed wait only makes the attempts come faster, not + // endlessly: they stay bounded and the loop still ends once the queue is empty. + self.filter_reset_queue.push_front_all(batch); + if let Err(err) = wdk::utils::sleep_ms(10) { + err!("failed to wait before the next filter reset: {}", err); + } + } + Err(err) => { + // Not transient. Drop the batch with its packets and carry on with the queue. + err!( + "failed to reset filters: {}, dropping {} packet(s)", + String::from(err), + batch.len() + ); + attempts = 0; + } + } + } + } + + /// Injects a packet captured with a deferred connection, if there was one to capture. + fn inject_pending(&self, pending: PendingReset) -> Result<(), String> { + let Some(packet_list) = pending else { + return Ok(()); + }; + + return self.injector.inject_packet_list_transport(packet_list); + } +} + +impl Drop for Device { + fn drop(&mut self) { + // The driver can also be unloaded without ever receiving a shutdown command (service stop, + // driver removal, a user space process that died). Run the same teardown here so no pended + // operation is ever left behind. This still runs before the filter engine is dropped, so + // completing the operations and removing the callouts is valid at this point; by the time + // the engine drops there is nothing left for its own failsafe to do. + self.shutdown(); + + _ = logger::flush(); + // dbg!("Device Context drop called."); + } +} diff --git a/driver/src/entry.rs b/driver/src/entry.rs index a1a9241..384a0fb 100644 --- a/driver/src/entry.rs +++ b/driver/src/entry.rs @@ -1,139 +1,139 @@ -use crate::common::ControlCode; -use crate::device; -use alloc::boxed::Box; -use num_traits::FromPrimitive; -use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest}; -use wdk::{err, info, interface}; -use windows_sys::Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}; -use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; - -static VERSION: [u8; 4] = include!("../../kext_interface/version.txt"); - -static mut DEVICE: *mut device::Device = core::ptr::null_mut(); -pub fn get_device() -> Option<&'static mut device::Device> { - return unsafe { DEVICE.as_mut() }; -} - -// DriverEntry is the entry point of the driver (main function). Will be called when driver is loaded. -// Name should not be changed -#[export_name = "DriverEntry"] -pub extern "system" fn driver_entry( - driver_object: *mut windows_sys::Wdk::Foundation::DRIVER_OBJECT, - registry_path: *mut windows_sys::Win32::Foundation::UNICODE_STRING, -) -> windows_sys::Win32::Foundation::NTSTATUS { - info!("Starting initialization..."); - - // Initialize driver object. - let mut driver = match interface::init_driver_object( - driver_object, - registry_path, - "ZenithFence", - core::ptr::null_mut(), - ) { - Ok(driver) => driver, - Err(status) => { - err!("driver_entry: failed to initialize driver: {}", status); - return windows_sys::Win32::Foundation::STATUS_FAILED_DRIVER_ENTRY; - } - }; - - // Set driver functions. - driver.set_driver_unload(driver_unload); - driver.set_read_fn(driver_read); - driver.set_write_fn(driver_write); - driver.set_device_control_fn(device_control); - - // Initialize device. - unsafe { - let device = match device::Device::new(&driver) { - Ok(device) => Box::new(device), - Err(err) => { - wdk::err!("filed to initialize device: {}", err); - return -1; - } - }; - DEVICE = Box::into_raw(device); - } - - STATUS_SUCCESS -} - -// driver_unload function is called when service delete is called from user-space. -unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) { - info!("Unloading complete"); - unsafe { - if !DEVICE.is_null() { - // Dropping the device completes every pending operation and unregisters the callouts, - // so no callout can still be running once this returns. Only then is it safe to clear - // the pointer the callouts read through. - _ = Box::from_raw(DEVICE); - DEVICE = core::ptr::null_mut(); - } - } -} - -// driver_read event triggered from user-space on file.Read. -unsafe extern "system" fn driver_read( - _device_object: &mut DEVICE_OBJECT, - irp: &mut IRP, -) -> NTSTATUS { - let mut read_request = ReadRequest::new(irp); - let Some(device) = get_device() else { - read_request.complete(); - - return read_request.get_status(); - }; - - device.read(&mut read_request); - read_request.get_status() -} - -/// driver_write event triggered from user-space on file.Write. -unsafe extern "system" fn driver_write( - _device_object: &mut DEVICE_OBJECT, - irp: &mut IRP, -) -> NTSTATUS { - let mut write_request = WriteRequest::new(irp); - let Some(device) = get_device() else { - write_request.complete(); - return write_request.get_status(); - }; - - device.write(&mut write_request); - - write_request.mark_all_as_read(); - write_request.complete(); - write_request.get_status() -} - -/// device_control event triggered from user-space on file.deviceIOControl. -unsafe extern "system" fn device_control( - _device_object: &mut DEVICE_OBJECT, - irp: &mut IRP, -) -> NTSTATUS { - let mut control_request = DeviceControlRequest::new(irp); - let Some(device) = get_device() else { - control_request.complete(); - return control_request.get_status(); - }; - - let Some(control_code): Option = - FromPrimitive::from_u32(control_request.get_control_code()) - else { - wdk::info!("Unknown IOCTL code: {}", control_request.get_control_code()); - control_request.not_implemented(); - return control_request.get_status(); - }; - - wdk::info!("IOCTL: {}", control_code); - - match control_code { - ControlCode::Version => { - control_request.write(&VERSION); - } - ControlCode::ShutdownRequest => device.shutdown(), - }; - - control_request.complete(); - control_request.get_status() -} +use crate::common::ControlCode; +use crate::device; +use alloc::boxed::Box; +use num_traits::FromPrimitive; +use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest}; +use wdk::{err, info, interface}; +use windows_sys::Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}; +use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; + +static VERSION: [u8; 4] = include!("../../kext_interface/version.txt"); + +static mut DEVICE: *mut device::Device = core::ptr::null_mut(); +pub fn get_device() -> Option<&'static mut device::Device> { + return unsafe { DEVICE.as_mut() }; +} + +// DriverEntry is the entry point of the driver (main function). Will be called when driver is loaded. +// Name should not be changed +#[export_name = "DriverEntry"] +pub extern "system" fn driver_entry( + driver_object: *mut windows_sys::Wdk::Foundation::DRIVER_OBJECT, + registry_path: *mut windows_sys::Win32::Foundation::UNICODE_STRING, +) -> windows_sys::Win32::Foundation::NTSTATUS { + info!("Starting initialization..."); + + // Initialize driver object. + let mut driver = match interface::init_driver_object( + driver_object, + registry_path, + "ZenithFence", + core::ptr::null_mut(), + ) { + Ok(driver) => driver, + Err(status) => { + err!("driver_entry: failed to initialize driver: {}", status); + return windows_sys::Win32::Foundation::STATUS_FAILED_DRIVER_ENTRY; + } + }; + + // Set driver functions. + driver.set_driver_unload(driver_unload); + driver.set_read_fn(driver_read); + driver.set_write_fn(driver_write); + driver.set_device_control_fn(device_control); + + // Initialize device. + unsafe { + let device = match device::Device::new(&driver) { + Ok(device) => Box::new(device), + Err(err) => { + wdk::err!("filed to initialize device: {}", err); + return -1; + } + }; + DEVICE = Box::into_raw(device); + } + + STATUS_SUCCESS +} + +// driver_unload function is called when service delete is called from user-space. +unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) { + info!("Unloading complete"); + unsafe { + if !DEVICE.is_null() { + // Dropping the device completes every pending operation and unregisters the callouts, + // so no callout can still be running once this returns. Only then is it safe to clear + // the pointer the callouts read through. + _ = Box::from_raw(DEVICE); + DEVICE = core::ptr::null_mut(); + } + } +} + +// driver_read event triggered from user-space on file.Read. +unsafe extern "system" fn driver_read( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut read_request = ReadRequest::new(irp); + let Some(device) = get_device() else { + read_request.complete(); + + return read_request.get_status(); + }; + + device.read(&mut read_request); + read_request.get_status() +} + +/// driver_write event triggered from user-space on file.Write. +unsafe extern "system" fn driver_write( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut write_request = WriteRequest::new(irp); + let Some(device) = get_device() else { + write_request.complete(); + return write_request.get_status(); + }; + + device.write(&mut write_request); + + write_request.mark_all_as_read(); + write_request.complete(); + write_request.get_status() +} + +/// device_control event triggered from user-space on file.deviceIOControl. +unsafe extern "system" fn device_control( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut control_request = DeviceControlRequest::new(irp); + let Some(device) = get_device() else { + control_request.complete(); + return control_request.get_status(); + }; + + let Some(control_code): Option = + FromPrimitive::from_u32(control_request.get_control_code()) + else { + wdk::info!("Unknown IOCTL code: {}", control_request.get_control_code()); + control_request.not_implemented(); + return control_request.get_status(); + }; + + wdk::info!("IOCTL: {}", control_code); + + match control_code { + ControlCode::Version => { + control_request.write(&VERSION); + } + ControlCode::ShutdownRequest => device.shutdown(), + }; + + control_request.complete(); + control_request.get_status() +} diff --git a/driver/src/id_cache.rs b/driver/src/id_cache.rs index 3cf757e..32263d9 100644 --- a/driver/src/id_cache.rs +++ b/driver/src/id_cache.rs @@ -1,195 +1,195 @@ -use core::mem; - -use alloc::collections::VecDeque; -use protocol::info::Info; -use smoltcp::wire::{IpAddress, IpProtocol}; -use wdk::rw_spin_lock::Mutex; - -use crate::{ - connection::{Direction, Key}, - device::Packet, -}; - -pub const PACKET_MISSING_ID: u64 = u64::MAX; - -pub struct Entry { - pub value: T, - id: u64, -} - -pub struct IdCache { - values: Mutex>>, - next_id: u64, -} - -impl IdCache { - pub fn new() -> Self { - Self { - values: Mutex::new(VecDeque::with_capacity(1000)), - next_id: 1, // 0 is invalid id - } - } - - pub fn push( - &mut self, - value: (Key, Packet), - process_id: u64, - direction: Direction, - ale_layer: bool, - ) -> Option { - let mut values = self.values.write_lock(); - let id = self.next_id; - let info = build_info(&value.0, id, process_id, direction, &value.1, ale_layer); - values.push_back(Entry { value, id }); - self.next_id = self.next_id.wrapping_add(1); // Assuming this will not overflow. - - // PACKET_MISSING_ID is not checked since there needs to be 18446744073709551614 connection created until it reaches that id. - // Practically impossible and the impact is just one dropped packet. - - return info; - } - - pub fn pop_id(&mut self, id: u64) -> Option<(Key, Packet)> { - if id == PACKET_MISSING_ID { - return None; - } - - let mut values = self.values.write_lock(); - if let Ok(index) = values.binary_search_by_key(&id, |val| val.id) { - return Some(values.remove(index).unwrap().value); - } - None - } - - #[allow(dead_code)] - pub fn get_entries_count(&self) -> usize { - let values = self.values.read_lock(); - return values.len(); - } - - pub fn pop_all(&mut self) -> VecDeque> { - let mut new_values = VecDeque::with_capacity(1); - let mut values = self.values.write_lock(); - mem::swap(&mut *values, &mut new_values); - - return new_values; - } -} - -fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> { - match packet { - Packet::PacketLayer(nbl, _) => nbl.get_data(), - Packet::AleLayer(defer) => { - let p = match defer { - wdk::filter_engine::callout_data::ClassifyDefer::Initial(_, p) => p, - wdk::filter_engine::callout_data::ClassifyDefer::Reauthorization(_, p) => p, - }; - if let Some(tpl) = p { - tpl.net_buffer_list_queue.get_data() - } else { - None - } - } - } -} - -/// Builds an informational-only event that carries no packet to reinject (it uses -/// [`PACKET_MISSING_ID`]). Used when the ALE layer only needs to tell user space about a -/// connection and its process id, leaving the real packet to be sent and reinjected by the -/// packet layer. This is the case for inbound loopback and for reauthorized outbound -/// connections (which cannot be pended). -pub fn build_info_only(key: &Key, process_id: u64, direction: Direction) -> Option { - let (local_port, remote_port) = match key.protocol { - IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), - _ => (0, 0), - }; - - match (key.local_address, key.remote_address) { - (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { - Some(protocol::info::connection_info_v6( - PACKET_MISSING_ID, - process_id, - direction as u8, - u8::from(key.protocol), - local_ip.octets(), - remote_ip.octets(), - local_port, - remote_port, - 4, // Transport layer - &[], - )) - } - (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { - Some(protocol::info::connection_info_v4( - PACKET_MISSING_ID, - process_id, - direction as u8, - u8::from(key.protocol), - local_ip.octets(), - remote_ip.octets(), - local_port, - remote_port, - 4, // Transport layer - &[], - )) - } - _ => None, - } -} - -pub fn build_info( - key: &Key, - packet_id: u64, - process_id: u64, - direction: Direction, - packet: &Packet, - ale_layer: bool, -) -> Option { - let (local_port, remote_port) = match key.protocol { - IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), - _ => (0, 0), - }; - - let payload_layer = if ale_layer { - 4 // Transport layer - } else { - 3 // Network layer - }; - - let mut payload = &[][..]; - if let Some(p) = get_payload(packet) { - payload = p; - } - - match (key.local_address, key.remote_address) { - (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { - Some(protocol::info::connection_info_v6( - packet_id, - process_id, - direction as u8, - u8::from(key.protocol), - local_ip.octets(), - remote_ip.octets(), - local_port, - remote_port, - payload_layer, - payload, - )) - } - (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { - Some(protocol::info::connection_info_v4( - packet_id, - process_id, - direction as u8, - u8::from(key.protocol), - local_ip.octets(), - remote_ip.octets(), - local_port, - remote_port, - payload_layer, - payload, - )) - } - _ => None, - } -} +use core::mem; + +use alloc::collections::VecDeque; +use protocol::info::Info; +use smoltcp::wire::{IpAddress, IpProtocol}; +use wdk::rw_spin_lock::Mutex; + +use crate::{ + connection::{Direction, Key}, + device::Packet, +}; + +pub const PACKET_MISSING_ID: u64 = u64::MAX; + +pub struct Entry { + pub value: T, + id: u64, +} + +pub struct IdCache { + values: Mutex>>, + next_id: u64, +} + +impl IdCache { + pub fn new() -> Self { + Self { + values: Mutex::new(VecDeque::with_capacity(1000)), + next_id: 1, // 0 is invalid id + } + } + + pub fn push( + &mut self, + value: (Key, Packet), + process_id: u64, + direction: Direction, + ale_layer: bool, + ) -> Option { + let mut values = self.values.write_lock(); + let id = self.next_id; + let info = build_info(&value.0, id, process_id, direction, &value.1, ale_layer); + values.push_back(Entry { value, id }); + self.next_id = self.next_id.wrapping_add(1); // Assuming this will not overflow. + + // PACKET_MISSING_ID is not checked since there needs to be 18446744073709551614 connection created until it reaches that id. + // Practically impossible and the impact is just one dropped packet. + + return info; + } + + pub fn pop_id(&mut self, id: u64) -> Option<(Key, Packet)> { + if id == PACKET_MISSING_ID { + return None; + } + + let mut values = self.values.write_lock(); + if let Ok(index) = values.binary_search_by_key(&id, |val| val.id) { + return Some(values.remove(index).unwrap().value); + } + None + } + + #[allow(dead_code)] + pub fn get_entries_count(&self) -> usize { + let values = self.values.read_lock(); + return values.len(); + } + + pub fn pop_all(&mut self) -> VecDeque> { + let mut new_values = VecDeque::with_capacity(1); + let mut values = self.values.write_lock(); + mem::swap(&mut *values, &mut new_values); + + return new_values; + } +} + +fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> { + match packet { + Packet::PacketLayer(nbl, _) => nbl.get_data(), + Packet::AleLayer(defer) => { + let p = match defer { + wdk::filter_engine::callout_data::ClassifyDefer::Initial(_, p) => p, + wdk::filter_engine::callout_data::ClassifyDefer::Reauthorization(_, p) => p, + }; + if let Some(tpl) = p { + tpl.net_buffer_list_queue.get_data() + } else { + None + } + } + } +} + +/// Builds an informational-only event that carries no packet to reinject (it uses +/// [`PACKET_MISSING_ID`]). Used when the ALE layer only needs to tell user space about a +/// connection and its process id, leaving the real packet to be sent and reinjected by the +/// packet layer. This is the case for inbound loopback and for reauthorized outbound +/// connections (which cannot be pended). +pub fn build_info_only(key: &Key, process_id: u64, direction: Direction) -> Option { + let (local_port, remote_port) = match key.protocol { + IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), + _ => (0, 0), + }; + + match (key.local_address, key.remote_address) { + (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { + Some(protocol::info::connection_info_v6( + PACKET_MISSING_ID, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.octets(), + remote_ip.octets(), + local_port, + remote_port, + 4, // Transport layer + &[], + )) + } + (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { + Some(protocol::info::connection_info_v4( + PACKET_MISSING_ID, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.octets(), + remote_ip.octets(), + local_port, + remote_port, + 4, // Transport layer + &[], + )) + } + _ => None, + } +} + +pub fn build_info( + key: &Key, + packet_id: u64, + process_id: u64, + direction: Direction, + packet: &Packet, + ale_layer: bool, +) -> Option { + let (local_port, remote_port) = match key.protocol { + IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), + _ => (0, 0), + }; + + let payload_layer = if ale_layer { + 4 // Transport layer + } else { + 3 // Network layer + }; + + let mut payload = &[][..]; + if let Some(p) = get_payload(packet) { + payload = p; + } + + match (key.local_address, key.remote_address) { + (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { + Some(protocol::info::connection_info_v6( + packet_id, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.octets(), + remote_ip.octets(), + local_port, + remote_port, + payload_layer, + payload, + )) + } + (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { + Some(protocol::info::connection_info_v4( + packet_id, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.octets(), + remote_ip.octets(), + local_port, + remote_port, + payload_layer, + payload, + )) + } + _ => None, + } +} diff --git a/driver/src/lib.rs b/driver/src/lib.rs index 19a1e53..62c9e49 100644 --- a/driver/src/lib.rs +++ b/driver/src/lib.rs @@ -1,45 +1,45 @@ -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), no_main)] -#![allow(clippy::needless_return)] - -extern crate alloc; - -mod ale_callouts; -mod array_holder; -pub mod mpsc_queue; -mod callouts; -mod common; -mod connection; -mod connection_cache; -mod rcu_port; -mod device; -mod entry; -mod filter_reset_queue; -mod id_cache; -pub mod logger; -mod packet_callouts; -mod packet_util; - -#[cfg(not(test))] -use wdk::allocator::WindowsAllocator; - -#[cfg(not(test))] -use core::panic::PanicInfo; - -// Declaration of the global memory allocator -#[cfg(not(test))] -#[global_allocator] -static HEAP: WindowsAllocator = WindowsAllocator {}; - -#[cfg(not(test))] -#[no_mangle] -pub extern "system" fn _DllMainCRTStartup() {} - -#[cfg(not(test))] -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - use wdk::err; - - err!("{}", info); - loop {} -} +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] +#![allow(clippy::needless_return)] + +extern crate alloc; + +mod ale_callouts; +mod array_holder; +pub mod mpsc_queue; +mod callouts; +mod common; +mod connection; +mod connection_cache; +mod rcu_port; +mod device; +mod entry; +mod filter_reset_queue; +mod id_cache; +pub mod logger; +mod packet_callouts; +mod packet_util; + +#[cfg(not(test))] +use wdk::allocator::WindowsAllocator; + +#[cfg(not(test))] +use core::panic::PanicInfo; + +// Declaration of the global memory allocator +#[cfg(not(test))] +#[global_allocator] +static HEAP: WindowsAllocator = WindowsAllocator {}; + +#[cfg(not(test))] +#[no_mangle] +pub extern "system" fn _DllMainCRTStartup() {} + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + use wdk::err; + + err!("{}", info); + loop {} +} diff --git a/driver/src/logger.rs b/driver/src/logger.rs index 695169b..8b6886e 100644 --- a/driver/src/logger.rs +++ b/driver/src/logger.rs @@ -1,111 +1,111 @@ -use alloc::boxed::Box; -use alloc::vec::Vec; -use core::{ - mem::MaybeUninit, - sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, -}; -use protocol::info::{Info, Severity}; - -pub const LOG_LEVEL: u8 = Severity::Error as u8; - -pub const MAX_LOG_LINE_SIZE: usize = 150; -const SIZE_OF_LOG_LINE_BUFFER: usize = 1024; -static mut LOG_LINES: [AtomicPtr; SIZE_OF_LOG_LINE_BUFFER] = - unsafe { MaybeUninit::zeroed().assume_init() }; -static START_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; -static END_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; - -pub fn add_line(log_line: Info) { - let mut index = END_INDEX.fetch_add(1, Ordering::SeqCst); - unsafe { - index %= SIZE_OF_LOG_LINE_BUFFER; - let ptr = &mut LOG_LINES[index]; - let line = Box::new(log_line); - let old = ptr.swap(Box::into_raw(line), Ordering::SeqCst); - if !old.is_null() { - _ = Box::from_raw(old); - } - } -} - -pub fn flush() -> Vec { - let mut vec = Vec::new(); - let end_index = END_INDEX.load(Ordering::SeqCst); - let start_index = START_INDEX.load(Ordering::SeqCst); - if end_index <= start_index { - return vec; - } - unsafe { - let count = end_index - start_index; - for i in start_index..start_index + count { - let index = i % SIZE_OF_LOG_LINE_BUFFER; - let ptr = LOG_LINES[index].swap(core::ptr::null_mut(), Ordering::SeqCst); - if !ptr.is_null() { - vec.push(*Box::from_raw(ptr)); - } - } - } - - START_INDEX.store(end_index, Ordering::SeqCst); - vec -} - -#[macro_export] -macro_rules! log_internal { - ($log_line:expr, $($arg:tt)*) => ({ - use core::fmt::Write; - _ = write!($log_line, "{}:{} ", file!(), line!()); - _ = write!($log_line, $($arg)*); - $crate::logger::add_line($log_line); - }); -} - -#[macro_export] -macro_rules! crit { - ($($arg:tt)*) => ({ - if protocol::info::Severity::Critical as u8 >= $crate::logger::LOG_LEVEL { - let message = alloc::format!($($arg)*); - $crate::logger::add_line(protocol::info::Severity::Critical, alloc::format!("{}:{} ", file!(), line!()), message) - } - }); -} - -#[macro_export] -macro_rules! err { - ($($arg:tt)*) => ({ - if protocol::info::Severity::Error as u8 >= $crate::logger::LOG_LEVEL { - let mut log_line = protocol::info::log_line(protocol::info::Severity::Error, $crate::logger::MAX_LOG_LINE_SIZE); - $crate::log_internal!(log_line, $($arg)*); - } - }); -} - -#[macro_export] -macro_rules! warn { - ($($arg:tt)*) => ({ - if protocol::info::Severity::Warning as u8 >= $crate::logger::LOG_LEVEL { - let mut log_line = protocol::info::log_line(protocol::info::Severity::Warning, $crate::logger::MAX_LOG_LINE_SIZE); - $crate::log_internal!(log_line, $($arg)*); - } - }); -} - -#[macro_export] -macro_rules! dbg { - ($($arg:tt)*) => ({ - if protocol::info::Severity::Debug as u8 >= $crate::logger::LOG_LEVEL { - let mut log_line = protocol::info::log_line(protocol::info::Severity::Debug, $crate::logger::MAX_LOG_LINE_SIZE); - $crate::log_internal!(log_line, $($arg)*); - } - }); -} - -#[macro_export] -macro_rules! info { - ($($arg:tt)*) => ({ - if protocol::info::Severity::Info as u8 >= $crate::logger::LOG_LEVEL { - let mut log_line = protocol::info::log_line(protocol::info::Severity::Info, $crate::logger::MAX_LOG_LINE_SIZE); - $crate::log_internal!(log_line, $($arg)*); - } - }); -} +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, +}; +use protocol::info::{Info, Severity}; + +pub const LOG_LEVEL: u8 = Severity::Error as u8; + +pub const MAX_LOG_LINE_SIZE: usize = 150; +const SIZE_OF_LOG_LINE_BUFFER: usize = 1024; +static mut LOG_LINES: [AtomicPtr; SIZE_OF_LOG_LINE_BUFFER] = + unsafe { MaybeUninit::zeroed().assume_init() }; +static START_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; +static END_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; + +pub fn add_line(log_line: Info) { + let mut index = END_INDEX.fetch_add(1, Ordering::SeqCst); + unsafe { + index %= SIZE_OF_LOG_LINE_BUFFER; + let ptr = &mut LOG_LINES[index]; + let line = Box::new(log_line); + let old = ptr.swap(Box::into_raw(line), Ordering::SeqCst); + if !old.is_null() { + _ = Box::from_raw(old); + } + } +} + +pub fn flush() -> Vec { + let mut vec = Vec::new(); + let end_index = END_INDEX.load(Ordering::SeqCst); + let start_index = START_INDEX.load(Ordering::SeqCst); + if end_index <= start_index { + return vec; + } + unsafe { + let count = end_index - start_index; + for i in start_index..start_index + count { + let index = i % SIZE_OF_LOG_LINE_BUFFER; + let ptr = LOG_LINES[index].swap(core::ptr::null_mut(), Ordering::SeqCst); + if !ptr.is_null() { + vec.push(*Box::from_raw(ptr)); + } + } + } + + START_INDEX.store(end_index, Ordering::SeqCst); + vec +} + +#[macro_export] +macro_rules! log_internal { + ($log_line:expr, $($arg:tt)*) => ({ + use core::fmt::Write; + _ = write!($log_line, "{}:{} ", file!(), line!()); + _ = write!($log_line, $($arg)*); + $crate::logger::add_line($log_line); + }); +} + +#[macro_export] +macro_rules! crit { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Critical as u8 >= $crate::logger::LOG_LEVEL { + let message = alloc::format!($($arg)*); + $crate::logger::add_line(protocol::info::Severity::Critical, alloc::format!("{}:{} ", file!(), line!()), message) + } + }); +} + +#[macro_export] +macro_rules! err { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Error as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Error, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Warning as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Warning, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! dbg { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Debug as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Debug, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Info as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Info, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} diff --git a/driver/src/packet_util.rs b/driver/src/packet_util.rs index 848d7bc..c072f7d 100644 --- a/driver/src/packet_util.rs +++ b/driver/src/packet_util.rs @@ -1,557 +1,557 @@ -use alloc::string::{String, ToString}; -use smoltcp::wire::{ - IpAddress, IpProtocol, Ipv4Address, Ipv4Packet, Ipv6Address, Ipv6Packet, TcpPacket, UdpPacket, - IPV4_HEADER_LEN, IPV6_HEADER_LEN, -}; -use wdk::filter_engine::net_buffer::NetBuffer; - -use crate::device::Packet; -use crate::{ - connection::{Direction, Key, RedirectInfo}, - dbg, err, -}; - -/// `Redirect` is a trait that defines a method for redirecting network packets. -/// -/// This trait is used to implement different strategies for redirecting packets, -/// depending on the specific requirements of the application. -pub trait Redirect { - /// Redirects a network packet based on the provided `RedirectInfo`. - /// - /// # Arguments - /// - /// * `redirect_info` - A struct containing information about how to redirect the packet. - /// - /// # Returns - /// - /// * `Ok(())` if the packet was successfully redirected. - /// * `Err(String)` if there was an error redirecting the packet. - fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String>; -} - -impl Redirect for Packet { - fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String> { - if let Packet::PacketLayer(nbl, inject_info) = self { - let Some(data) = nbl.get_data_mut() else { - return Err("trying to redirect immutable NBL".to_string()); - }; - - if inject_info.inbound { - redirect_inbound_packet( - data, - redirect_info.local_address, - redirect_info.remote_address, - redirect_info.remote_port, - ) - } else { - redirect_outbound_packet( - data, - redirect_info.redirect_address, - redirect_info.redirect_port, - redirect_info.unify, - ) - } - return Ok(()); - } - // return Err("can't redirect from non packet layer".to_string()); - return Ok(()); - } -} - -/// Redirects an outbound packet to a specified remote address and port. -/// -/// # Arguments -/// -/// * `packet` - A mutable reference to the packet data. -/// * `remote_address` - The IP address to redirect the packet to. -/// * `remote_port` - The port to redirect the packet to. -/// * `unify` - If true, the source and destination addresses of the packet will be set to the same value. -/// -/// This function modifies the packet in-place to change its destination address and port. -/// It also updates the checksums for the IP and transport layer headers. -/// If the `unify` parameter is true, it sets the source and destination addresses to be the same. -/// If the remote address is a loopback address, it sets the source address to the loopback address. -fn redirect_outbound_packet( - packet: &mut [u8], - remote_address: IpAddress, - remote_port: u16, - unify: bool, -) { - match remote_address { - IpAddress::Ipv4(remote_address) => { - if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { - if unify { - ip_packet.set_dst_addr(ip_packet.src_addr()); - } else { - ip_packet.set_dst_addr(remote_address); - if remote_address.is_loopback() { - ip_packet.set_src_addr(Ipv4Address::new(127, 0, 0, 1)); - } - } - ip_packet.fill_checksum(); - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet.set_dst_port(remote_port); - udp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet.set_dst_port(remote_port); - tcp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - } - } - IpAddress::Ipv6(remote_address) => { - if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { - ip_packet.set_dst_addr(remote_address); - if unify { - ip_packet.set_dst_addr(ip_packet.src_addr()); - } else { - ip_packet.set_dst_addr(remote_address); - if remote_address.is_loopback() { - ip_packet.set_src_addr(Ipv6Address::LOCALHOST); - } - } - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet.set_dst_port(remote_port); - udp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet.set_dst_port(remote_port); - tcp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - } - } - } -} - -/// Redirects an inbound packet to a local address. -/// -/// This function takes a mutable reference to a packet and modifies it in place. -/// It changes the destination address to the provided local address and the source address -/// to the original remote address. It also sets the source port to the original remote port. -/// The function handles both IPv4 and IPv6 addresses. -/// -/// # Arguments -/// -/// * `packet` - A mutable reference to the packet data. -/// * `local_address` - The local IP address to redirect the packet to. -/// * `original_remote_address` - The original remote IP address of the packet. -/// * `original_remote_port` - The original remote port of the packet. -/// -fn redirect_inbound_packet( - packet: &mut [u8], - local_address: IpAddress, - original_remote_address: IpAddress, - original_remote_port: u16, -) { - match local_address { - IpAddress::Ipv4(local_address) => { - let IpAddress::Ipv4(original_remote_address) = original_remote_address else { - return; - }; - - if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { - ip_packet.set_dst_addr(local_address); - ip_packet.set_src_addr(original_remote_address); - ip_packet.fill_checksum(); - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet.set_src_port(original_remote_port); - udp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet.set_src_port(original_remote_port); - tcp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - } - } - IpAddress::Ipv6(local_address) => { - if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { - let IpAddress::Ipv6(original_remote_address) = original_remote_address else { - return; - }; - ip_packet.set_dst_addr(local_address); - ip_packet.set_src_addr(original_remote_address); - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet.set_src_port(original_remote_port); - udp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet.set_src_port(original_remote_port); - tcp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - } - } - } -} - -pub fn recalc_header_checksums(packet: &mut [u8], ipv6: bool) { - if ipv6 { - if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet - .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); - } - } - } - } else { - if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { - ip_packet.fill_checksum(); - let src_addr = ip_packet.src_addr(); - let dst_addr = ip_packet.dst_addr(); - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { - udp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { - tcp_packet - .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); - } - } - } - } -} - -#[allow(dead_code)] -fn print_packet(packet: &[u8]) { - if let Ok(ip_packet) = Ipv4Packet::new_checked(packet) { - if ip_packet.next_header() == IpProtocol::Udp { - if let Ok(udp_packet) = UdpPacket::new_checked(ip_packet.payload()) { - dbg!("packet {} {}", ip_packet, udp_packet); - } - } - if ip_packet.next_header() == IpProtocol::Tcp { - if let Ok(tcp_packet) = TcpPacket::new_checked(ip_packet.payload()) { - dbg!("packet {} {}", ip_packet, tcp_packet); - } - } - } else { - err!("failed to print packet: invalid ip header: {:?}", packet); - } -} - -/// This function extracts a key from a given IPv4 net buffer. -/// The key contains the protocol, local and remote addresses and ports. -/// -/// # Arguments -/// -/// * `nb` - A reference to the net buffer from which the key will be extracted. -/// * `direction` - The direction of the packet (Inbound or Outbound). -/// -/// # Returns -/// -/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. -/// * `Err(String)` - An error message if the function fails to get net_buffer data. -fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) { - match protocol { - smoltcp::wire::IpProtocol::Tcp => { - let tcp_packet = TcpPacket::new_unchecked(packet); - (tcp_packet.src_port(), tcp_packet.dst_port()) - } - smoltcp::wire::IpProtocol::Udp => { - let udp_packet = UdpPacket::new_unchecked(packet); - (udp_packet.src_port(), udp_packet.dst_port()) - } - _ => (0, 0), // No ports for other protocols - } -} - -/// Upper bound on an IPv4 header: the fixed 20 bytes plus the 40 bytes of -/// options that the 4-bit IHL field can express. -const MAX_IPV4_HEADER_LEN: usize = 60; - -/// Reads the first `len` bytes of the net buffer into `buffer[..len]`. -/// -/// `NetBuffer::read_bytes` always reads from the start of the buffer and -/// fails if the requested length exceeds the packet, so anything past the fixed -/// header (IPv4 options, IPv6 extension headers, the transport header) is -/// reached by reading successively larger prefixes and indexing into them. -fn read_prefix(nb: &NetBuffer, buffer: &mut [u8], len: usize) -> Result<(), ()> { - if len > buffer.len() { - return Err(()); - } - nb.read_bytes(&mut buffer[..len]) -} - -pub fn get_key_from_nb_v4(nb: &NetBuffer, direction: Direction) -> Result { - // Buffer large enough for the largest possible IPv4 header, options included, and the - // first 4 transport bytes (source + destination ports). - let mut headers = [0u8; MAX_IPV4_HEADER_LEN + 4]; - - // Read the fixed part of the header; it carries the IHL that locates the transport header. - if read_prefix(nb, &mut headers, IPV4_HEADER_LEN).is_err() { - return Err("failed to get net_buffer data".to_string()); - } - let (src_addr, dst_addr, protocol, header_len) = { - let ip_packet = Ipv4Packet::new_unchecked(&headers[..IPV4_HEADER_LEN]); - ( - ip_packet.src_addr(), - ip_packet.dst_addr(), - ip_packet.next_header(), - ip_packet.header_len() as usize, - ) - }; - - // The header length comes from the packet itself, so validate it before using it as an - // offset. IHL < 5 is malformed; IHL > 15 cannot be expressed in 4 bits. - if header_len < IPV4_HEADER_LEN || header_len > MAX_IPV4_HEADER_LEN { - return Err("invalid ipv4 header length".to_string()); - } - - // Parse the layer-4 ports for TCP and UDP. Options sit between the fixed header and the - // transport header, so the ports are at header_len, which is only IPV4_HEADER_LEN when the - // packet carries no options. - let (src_port, dst_port) = if matches!(protocol, IpProtocol::Tcp | IpProtocol::Udp) { - let needed = header_len + 4; - if read_prefix(nb, &mut headers, needed).is_err() { - return Err("failed to read ipv4 transport header".to_string()); - } - get_ports(&headers[header_len..needed], protocol) - } else { - (0, 0) - }; - - // Build key - match direction { - Direction::Outbound => Ok(Key { - protocol, - local_address: IpAddress::Ipv4(src_addr), - local_port: src_port, - remote_address: IpAddress::Ipv4(dst_addr), - remote_port: dst_port, - }), - Direction::Inbound => Ok(Key { - protocol, - local_address: IpAddress::Ipv4(dst_addr), - local_port: dst_port, - remote_address: IpAddress::Ipv4(src_addr), - remote_port: src_port, - }), - } -} - -// NOTE: The IPv6 extension-header parsing below is duplicated in the Linux eBPF -// parser (`get_key_skb` in cmds/ebpf-firewall/modules/headers/key.h). Keep the -// two implementations in sync. - -// IPv6 extension-header protocol numbers. -const IPPROTO_HOPOPTS: u8 = 0; -const IPPROTO_ROUTING: u8 = 43; -const IPPROTO_FRAGMENT: u8 = 44; -const IPPROTO_DSTOPT: u8 = 60; - -/// Bounds the extension-header walk. This is the chain depth (headers stacked in -/// one packet), not the number of header types; a real-world packet almost never -/// stacks more than 2-3. -const MAX_IPV6_EXT_HEADERS: usize = 4; - -/// Upper bound on a single extension header's length; anything larger is treated -/// as malformed. -const MAX_IPV6_EXT_HEADER_LEN: usize = 256; - -/// This function extracts a key from a given IPv6 net buffer. -/// The key contains the protocol, local and remote addresses and ports. -/// -/// IPv6 packets may carry a chain of extension headers (Hop-by-Hop, Routing, -/// Fragment, Destination Options) between the fixed header and the transport -/// header. The chain is walked so the key reflects the real transport protocol -/// and ports rather than the first extension header. -/// -/// # Arguments -/// -/// * `nb` - A reference to the net buffer from which the key will be extracted. -/// * `direction` - The direction of the packet (Inbound or Outbound). -/// -/// # Returns -/// -/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. -/// * `Err(String)` - An error message if the function fails to get net_buffer data -/// or the packet carries a malformed extension-header chain. -pub fn get_key_from_nb_v6(nb: &NetBuffer, direction: Direction) -> Result { - // Buffer large enough for the fixed IPv6 header, the bounded extension-header - // chain, and the first 4 transport bytes (source + destination ports). - let mut headers = [0u8; IPV6_HEADER_LEN + MAX_IPV6_EXT_HEADERS * MAX_IPV6_EXT_HEADER_LEN + 4]; - - // Read the fixed IPv6 header to get the addresses and the first Next Header. - if read_prefix(nb, &mut headers, IPV6_HEADER_LEN).is_err() { - return Err("failed to get net_buffer data".to_string()); - } - let (src_addr, dst_addr, mut nexthdr) = { - let ip_packet = Ipv6Packet::new_unchecked(&headers[..IPV6_HEADER_LEN]); - ( - ip_packet.src_addr(), - ip_packet.dst_addr(), - u8::from(ip_packet.next_header()), - ) - }; - - // l4_offset starts right after the fixed 40-byte IPv6 header. - let mut l4_offset = IPV6_HEADER_LEN; - - // Walk the extension-header chain until the Next Header is an L4 protocol. - for _ in 0..MAX_IPV6_EXT_HEADERS { - // Stop once the current Next Header is not a known extension header; it - // is then the L4 protocol. - if nexthdr != IPPROTO_HOPOPTS - && nexthdr != IPPROTO_ROUTING - && nexthdr != IPPROTO_FRAGMENT - && nexthdr != IPPROTO_DSTOPT - { - break; - } - - // Each extension header starts with [next_header:u8][hdr_ext_len:u8]. - if read_prefix(nb, &mut headers, l4_offset + 2).is_err() { - return Err("failed to read ipv6 extension header".to_string()); - } - let ext_next_header = headers[l4_offset]; - let ext_hdr_len_units = headers[l4_offset + 1]; - - // Extension-header length is given in 8-octet units, not counting the - // first 8 octets, so the total length is (hdrlen + 1) * 8. - let ext_hdr_len = (ext_hdr_len_units as usize + 1) * 8; - - // Reject ridiculously large / malformed headers. - if ext_hdr_len > MAX_IPV6_EXT_HEADER_LEN { - return Err("ipv6 extension header too large".to_string()); - } - - // Move past this extension header; its Next Header drives the next step. - l4_offset += ext_hdr_len; - nexthdr = ext_next_header; - } - - // nexthdr now holds the L4 protocol number and l4_offset points at the L4 header. - let protocol = IpProtocol::from(nexthdr); - - // Parse the layer-4 ports for TCP and UDP. - let (src_port, dst_port) = if matches!(protocol, IpProtocol::Tcp | IpProtocol::Udp) { - let needed = l4_offset + 4; - if read_prefix(nb, &mut headers, needed).is_err() { - return Err("failed to read ipv6 transport header".to_string()); - } - get_ports(&headers[l4_offset..needed], protocol) - } else { - (0, 0) - }; - - // Build key - match direction { - Direction::Outbound => Ok(Key { - protocol, - local_address: IpAddress::Ipv6(src_addr), - local_port: src_port, - remote_address: IpAddress::Ipv6(dst_addr), - remote_port: dst_port, - }), - Direction::Inbound => Ok(Key { - protocol, - local_address: IpAddress::Ipv6(dst_addr), - local_port: dst_port, - remote_address: IpAddress::Ipv6(src_addr), - remote_port: src_port, - }), - } -} - -// Converts a given key into connection information. -// -// This function takes a key, packet id, process id, and direction as input. -// It then uses these to create a new `ConnectionInfoV6` or `ConnectionInfoV4` object, -// depending on whether the IP addresses in the key are IPv6 or IPv4 respectively. -// -// # Arguments -// -// * `key` - A reference to the key object containing the connection details. -// * `packet_id` - The id of the packet. -// * `process_id` - The id of the process. -// * `direction` - The direction of the connection. -// -// # Returns -// -// * `Some(Box)` - A boxed `Info` trait object if the key contains valid IPv4 or IPv6 addresses. -// * `None` - If the key does not contain valid IPv4 or IPv6 addresses. -// pub fn key_to_connection_info( -// key: &Key, -// packet_id: u64, -// process_id: u64, -// direction: Direction, -// payload: &[u8], -// ) -> Option { -// let (local_port, remote_port) = match key.protocol { -// IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), -// _ => (0, 0), -// }; - -// match (key.local_address, key.remote_address) { -// (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { -// Some(protocol::info::connection_info_v6( -// packet_id, -// process_id, -// direction as u8, -// u8::from(key.protocol), -// local_ip.0, -// remote_ip.0, -// local_port, -// remote_port, -// payload, -// )) -// } -// (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { -// Some(protocol::info::connection_info_v4( -// packet_id, -// process_id, -// direction as u8, -// u8::from(key.protocol), -// local_ip.0, -// remote_ip.0, -// local_port, -// remote_port, -// payload, -// )) -// } -// _ => None, -// } -// } +use alloc::string::{String, ToString}; +use smoltcp::wire::{ + IpAddress, IpProtocol, Ipv4Address, Ipv4Packet, Ipv6Address, Ipv6Packet, TcpPacket, UdpPacket, + IPV4_HEADER_LEN, IPV6_HEADER_LEN, +}; +use wdk::filter_engine::net_buffer::NetBuffer; + +use crate::device::Packet; +use crate::{ + connection::{Direction, Key, RedirectInfo}, + dbg, err, +}; + +/// `Redirect` is a trait that defines a method for redirecting network packets. +/// +/// This trait is used to implement different strategies for redirecting packets, +/// depending on the specific requirements of the application. +pub trait Redirect { + /// Redirects a network packet based on the provided `RedirectInfo`. + /// + /// # Arguments + /// + /// * `redirect_info` - A struct containing information about how to redirect the packet. + /// + /// # Returns + /// + /// * `Ok(())` if the packet was successfully redirected. + /// * `Err(String)` if there was an error redirecting the packet. + fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String>; +} + +impl Redirect for Packet { + fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String> { + if let Packet::PacketLayer(nbl, inject_info) = self { + let Some(data) = nbl.get_data_mut() else { + return Err("trying to redirect immutable NBL".to_string()); + }; + + if inject_info.inbound { + redirect_inbound_packet( + data, + redirect_info.local_address, + redirect_info.remote_address, + redirect_info.remote_port, + ) + } else { + redirect_outbound_packet( + data, + redirect_info.redirect_address, + redirect_info.redirect_port, + redirect_info.unify, + ) + } + return Ok(()); + } + // return Err("can't redirect from non packet layer".to_string()); + return Ok(()); + } +} + +/// Redirects an outbound packet to a specified remote address and port. +/// +/// # Arguments +/// +/// * `packet` - A mutable reference to the packet data. +/// * `remote_address` - The IP address to redirect the packet to. +/// * `remote_port` - The port to redirect the packet to. +/// * `unify` - If true, the source and destination addresses of the packet will be set to the same value. +/// +/// This function modifies the packet in-place to change its destination address and port. +/// It also updates the checksums for the IP and transport layer headers. +/// If the `unify` parameter is true, it sets the source and destination addresses to be the same. +/// If the remote address is a loopback address, it sets the source address to the loopback address. +fn redirect_outbound_packet( + packet: &mut [u8], + remote_address: IpAddress, + remote_port: u16, + unify: bool, +) { + match remote_address { + IpAddress::Ipv4(remote_address) => { + if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { + if unify { + ip_packet.set_dst_addr(ip_packet.src_addr()); + } else { + ip_packet.set_dst_addr(remote_address); + if remote_address.is_loopback() { + ip_packet.set_src_addr(Ipv4Address::new(127, 0, 0, 1)); + } + } + ip_packet.fill_checksum(); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_dst_port(remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_dst_port(remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + } + } + IpAddress::Ipv6(remote_address) => { + if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { + ip_packet.set_dst_addr(remote_address); + if unify { + ip_packet.set_dst_addr(ip_packet.src_addr()); + } else { + ip_packet.set_dst_addr(remote_address); + if remote_address.is_loopback() { + ip_packet.set_src_addr(Ipv6Address::LOCALHOST); + } + } + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_dst_port(remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_dst_port(remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + } + } + } +} + +/// Redirects an inbound packet to a local address. +/// +/// This function takes a mutable reference to a packet and modifies it in place. +/// It changes the destination address to the provided local address and the source address +/// to the original remote address. It also sets the source port to the original remote port. +/// The function handles both IPv4 and IPv6 addresses. +/// +/// # Arguments +/// +/// * `packet` - A mutable reference to the packet data. +/// * `local_address` - The local IP address to redirect the packet to. +/// * `original_remote_address` - The original remote IP address of the packet. +/// * `original_remote_port` - The original remote port of the packet. +/// +fn redirect_inbound_packet( + packet: &mut [u8], + local_address: IpAddress, + original_remote_address: IpAddress, + original_remote_port: u16, +) { + match local_address { + IpAddress::Ipv4(local_address) => { + let IpAddress::Ipv4(original_remote_address) = original_remote_address else { + return; + }; + + if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { + ip_packet.set_dst_addr(local_address); + ip_packet.set_src_addr(original_remote_address); + ip_packet.fill_checksum(); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_src_port(original_remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_src_port(original_remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + } + } + IpAddress::Ipv6(local_address) => { + if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { + let IpAddress::Ipv6(original_remote_address) = original_remote_address else { + return; + }; + ip_packet.set_dst_addr(local_address); + ip_packet.set_src_addr(original_remote_address); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_src_port(original_remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_src_port(original_remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + } + } + } +} + +pub fn recalc_header_checksums(packet: &mut [u8], ipv6: bool) { + if ipv6 { + if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + } + } else { + if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { + ip_packet.fill_checksum(); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + } + } +} + +#[allow(dead_code)] +fn print_packet(packet: &[u8]) { + if let Ok(ip_packet) = Ipv4Packet::new_checked(packet) { + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(udp_packet) = UdpPacket::new_checked(ip_packet.payload()) { + dbg!("packet {} {}", ip_packet, udp_packet); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(tcp_packet) = TcpPacket::new_checked(ip_packet.payload()) { + dbg!("packet {} {}", ip_packet, tcp_packet); + } + } + } else { + err!("failed to print packet: invalid ip header: {:?}", packet); + } +} + +/// This function extracts a key from a given IPv4 net buffer. +/// The key contains the protocol, local and remote addresses and ports. +/// +/// # Arguments +/// +/// * `nb` - A reference to the net buffer from which the key will be extracted. +/// * `direction` - The direction of the packet (Inbound or Outbound). +/// +/// # Returns +/// +/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. +/// * `Err(String)` - An error message if the function fails to get net_buffer data. +fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) { + match protocol { + smoltcp::wire::IpProtocol::Tcp => { + let tcp_packet = TcpPacket::new_unchecked(packet); + (tcp_packet.src_port(), tcp_packet.dst_port()) + } + smoltcp::wire::IpProtocol::Udp => { + let udp_packet = UdpPacket::new_unchecked(packet); + (udp_packet.src_port(), udp_packet.dst_port()) + } + _ => (0, 0), // No ports for other protocols + } +} + +/// Upper bound on an IPv4 header: the fixed 20 bytes plus the 40 bytes of +/// options that the 4-bit IHL field can express. +const MAX_IPV4_HEADER_LEN: usize = 60; + +/// Reads the first `len` bytes of the net buffer into `buffer[..len]`. +/// +/// `NetBuffer::read_bytes` always reads from the start of the buffer and +/// fails if the requested length exceeds the packet, so anything past the fixed +/// header (IPv4 options, IPv6 extension headers, the transport header) is +/// reached by reading successively larger prefixes and indexing into them. +fn read_prefix(nb: &NetBuffer, buffer: &mut [u8], len: usize) -> Result<(), ()> { + if len > buffer.len() { + return Err(()); + } + nb.read_bytes(&mut buffer[..len]) +} + +pub fn get_key_from_nb_v4(nb: &NetBuffer, direction: Direction) -> Result { + // Buffer large enough for the largest possible IPv4 header, options included, and the + // first 4 transport bytes (source + destination ports). + let mut headers = [0u8; MAX_IPV4_HEADER_LEN + 4]; + + // Read the fixed part of the header; it carries the IHL that locates the transport header. + if read_prefix(nb, &mut headers, IPV4_HEADER_LEN).is_err() { + return Err("failed to get net_buffer data".to_string()); + } + let (src_addr, dst_addr, protocol, header_len) = { + let ip_packet = Ipv4Packet::new_unchecked(&headers[..IPV4_HEADER_LEN]); + ( + ip_packet.src_addr(), + ip_packet.dst_addr(), + ip_packet.next_header(), + ip_packet.header_len() as usize, + ) + }; + + // The header length comes from the packet itself, so validate it before using it as an + // offset. IHL < 5 is malformed; IHL > 15 cannot be expressed in 4 bits. + if header_len < IPV4_HEADER_LEN || header_len > MAX_IPV4_HEADER_LEN { + return Err("invalid ipv4 header length".to_string()); + } + + // Parse the layer-4 ports for TCP and UDP. Options sit between the fixed header and the + // transport header, so the ports are at header_len, which is only IPV4_HEADER_LEN when the + // packet carries no options. + let (src_port, dst_port) = if matches!(protocol, IpProtocol::Tcp | IpProtocol::Udp) { + let needed = header_len + 4; + if read_prefix(nb, &mut headers, needed).is_err() { + return Err("failed to read ipv4 transport header".to_string()); + } + get_ports(&headers[header_len..needed], protocol) + } else { + (0, 0) + }; + + // Build key + match direction { + Direction::Outbound => Ok(Key { + protocol, + local_address: IpAddress::Ipv4(src_addr), + local_port: src_port, + remote_address: IpAddress::Ipv4(dst_addr), + remote_port: dst_port, + }), + Direction::Inbound => Ok(Key { + protocol, + local_address: IpAddress::Ipv4(dst_addr), + local_port: dst_port, + remote_address: IpAddress::Ipv4(src_addr), + remote_port: src_port, + }), + } +} + +// NOTE: The IPv6 extension-header parsing below is duplicated in the Linux eBPF +// parser (`get_key_skb` in cmds/ebpf-firewall/modules/headers/key.h). Keep the +// two implementations in sync. + +// IPv6 extension-header protocol numbers. +const IPPROTO_HOPOPTS: u8 = 0; +const IPPROTO_ROUTING: u8 = 43; +const IPPROTO_FRAGMENT: u8 = 44; +const IPPROTO_DSTOPT: u8 = 60; + +/// Bounds the extension-header walk. This is the chain depth (headers stacked in +/// one packet), not the number of header types; a real-world packet almost never +/// stacks more than 2-3. +const MAX_IPV6_EXT_HEADERS: usize = 4; + +/// Upper bound on a single extension header's length; anything larger is treated +/// as malformed. +const MAX_IPV6_EXT_HEADER_LEN: usize = 256; + +/// This function extracts a key from a given IPv6 net buffer. +/// The key contains the protocol, local and remote addresses and ports. +/// +/// IPv6 packets may carry a chain of extension headers (Hop-by-Hop, Routing, +/// Fragment, Destination Options) between the fixed header and the transport +/// header. The chain is walked so the key reflects the real transport protocol +/// and ports rather than the first extension header. +/// +/// # Arguments +/// +/// * `nb` - A reference to the net buffer from which the key will be extracted. +/// * `direction` - The direction of the packet (Inbound or Outbound). +/// +/// # Returns +/// +/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. +/// * `Err(String)` - An error message if the function fails to get net_buffer data +/// or the packet carries a malformed extension-header chain. +pub fn get_key_from_nb_v6(nb: &NetBuffer, direction: Direction) -> Result { + // Buffer large enough for the fixed IPv6 header, the bounded extension-header + // chain, and the first 4 transport bytes (source + destination ports). + let mut headers = [0u8; IPV6_HEADER_LEN + MAX_IPV6_EXT_HEADERS * MAX_IPV6_EXT_HEADER_LEN + 4]; + + // Read the fixed IPv6 header to get the addresses and the first Next Header. + if read_prefix(nb, &mut headers, IPV6_HEADER_LEN).is_err() { + return Err("failed to get net_buffer data".to_string()); + } + let (src_addr, dst_addr, mut nexthdr) = { + let ip_packet = Ipv6Packet::new_unchecked(&headers[..IPV6_HEADER_LEN]); + ( + ip_packet.src_addr(), + ip_packet.dst_addr(), + u8::from(ip_packet.next_header()), + ) + }; + + // l4_offset starts right after the fixed 40-byte IPv6 header. + let mut l4_offset = IPV6_HEADER_LEN; + + // Walk the extension-header chain until the Next Header is an L4 protocol. + for _ in 0..MAX_IPV6_EXT_HEADERS { + // Stop once the current Next Header is not a known extension header; it + // is then the L4 protocol. + if nexthdr != IPPROTO_HOPOPTS + && nexthdr != IPPROTO_ROUTING + && nexthdr != IPPROTO_FRAGMENT + && nexthdr != IPPROTO_DSTOPT + { + break; + } + + // Each extension header starts with [next_header:u8][hdr_ext_len:u8]. + if read_prefix(nb, &mut headers, l4_offset + 2).is_err() { + return Err("failed to read ipv6 extension header".to_string()); + } + let ext_next_header = headers[l4_offset]; + let ext_hdr_len_units = headers[l4_offset + 1]; + + // Extension-header length is given in 8-octet units, not counting the + // first 8 octets, so the total length is (hdrlen + 1) * 8. + let ext_hdr_len = (ext_hdr_len_units as usize + 1) * 8; + + // Reject ridiculously large / malformed headers. + if ext_hdr_len > MAX_IPV6_EXT_HEADER_LEN { + return Err("ipv6 extension header too large".to_string()); + } + + // Move past this extension header; its Next Header drives the next step. + l4_offset += ext_hdr_len; + nexthdr = ext_next_header; + } + + // nexthdr now holds the L4 protocol number and l4_offset points at the L4 header. + let protocol = IpProtocol::from(nexthdr); + + // Parse the layer-4 ports for TCP and UDP. + let (src_port, dst_port) = if matches!(protocol, IpProtocol::Tcp | IpProtocol::Udp) { + let needed = l4_offset + 4; + if read_prefix(nb, &mut headers, needed).is_err() { + return Err("failed to read ipv6 transport header".to_string()); + } + get_ports(&headers[l4_offset..needed], protocol) + } else { + (0, 0) + }; + + // Build key + match direction { + Direction::Outbound => Ok(Key { + protocol, + local_address: IpAddress::Ipv6(src_addr), + local_port: src_port, + remote_address: IpAddress::Ipv6(dst_addr), + remote_port: dst_port, + }), + Direction::Inbound => Ok(Key { + protocol, + local_address: IpAddress::Ipv6(dst_addr), + local_port: dst_port, + remote_address: IpAddress::Ipv6(src_addr), + remote_port: src_port, + }), + } +} + +// Converts a given key into connection information. +// +// This function takes a key, packet id, process id, and direction as input. +// It then uses these to create a new `ConnectionInfoV6` or `ConnectionInfoV4` object, +// depending on whether the IP addresses in the key are IPv6 or IPv4 respectively. +// +// # Arguments +// +// * `key` - A reference to the key object containing the connection details. +// * `packet_id` - The id of the packet. +// * `process_id` - The id of the process. +// * `direction` - The direction of the connection. +// +// # Returns +// +// * `Some(Box)` - A boxed `Info` trait object if the key contains valid IPv4 or IPv6 addresses. +// * `None` - If the key does not contain valid IPv4 or IPv6 addresses. +// pub fn key_to_connection_info( +// key: &Key, +// packet_id: u64, +// process_id: u64, +// direction: Direction, +// payload: &[u8], +// ) -> Option { +// let (local_port, remote_port) = match key.protocol { +// IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), +// _ => (0, 0), +// }; + +// match (key.local_address, key.remote_address) { +// (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { +// Some(protocol::info::connection_info_v6( +// packet_id, +// process_id, +// direction as u8, +// u8::from(key.protocol), +// local_ip.0, +// remote_ip.0, +// local_port, +// remote_port, +// payload, +// )) +// } +// (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { +// Some(protocol::info::connection_info_v4( +// packet_id, +// process_id, +// direction as u8, +// u8::from(key.protocol), +// local_ip.0, +// remote_ip.0, +// local_port, +// remote_port, +// payload, +// )) +// } +// _ => None, +// } +// } diff --git a/kext_interface/command.go b/kext_interface/command.go index 544a9f5..e234a9e 100644 --- a/kext_interface/command.go +++ b/kext_interface/command.go @@ -1,52 +1,52 @@ -package kext_interface - -import ( - "encoding/binary" - "io" -) - -const ( - CommandShutdown = 0 - CommandVerdict = 1 - CommandUpdateV4 = 2 - CommandUpdateV6 = 3 - CommandClearCache = 4 - CommandGetConnectionsUpdate = 5 - CommandGetLogs = 6 - CommandPrintMemoryStats = 7 - CommandCleanEndedConnections = 8 -) - -type KextVerdict uint8 - -// Make sure this is in sync with the Rust version. -const ( - // VerdictUndecided is the default status of new connections. - VerdictUndecided KextVerdict = 0 - VerdictUndeterminable KextVerdict = 1 - VerdictAccept KextVerdict = 2 - VerdictPermanentAccept KextVerdict = 3 - VerdictBlock KextVerdict = 4 - VerdictPermanentBlock KextVerdict = 5 - VerdictDrop KextVerdict = 6 - VerdictPermanentDrop KextVerdict = 7 - VerdictRerouteToNameserver KextVerdict = 8 - VerdictRerouteToTunnel KextVerdict = 9 - VerdictFailed KextVerdict = 10 -) - -type Verdict struct { - command uint8 - Id uint64 - Verdict uint8 -} - -type RedirectV4 struct { - command uint8 - Id uint64 - RemoteAddress [4]byte - RemotePort uint16 -} +package kext_interface + +import ( + "encoding/binary" + "io" +) + +const ( + CommandShutdown = 0 + CommandVerdict = 1 + CommandUpdateV4 = 2 + CommandUpdateV6 = 3 + CommandClearCache = 4 + CommandGetConnectionsUpdate = 5 + CommandGetLogs = 6 + CommandPrintMemoryStats = 7 + CommandCleanEndedConnections = 8 +) + +type KextVerdict uint8 + +// Make sure this is in sync with the Rust version. +const ( + // VerdictUndecided is the default status of new connections. + VerdictUndecided KextVerdict = 0 + VerdictUndeterminable KextVerdict = 1 + VerdictAccept KextVerdict = 2 + VerdictPermanentAccept KextVerdict = 3 + VerdictBlock KextVerdict = 4 + VerdictPermanentBlock KextVerdict = 5 + VerdictDrop KextVerdict = 6 + VerdictPermanentDrop KextVerdict = 7 + VerdictRerouteToNameserver KextVerdict = 8 + VerdictRerouteToTunnel KextVerdict = 9 + VerdictFailed KextVerdict = 10 +) + +type Verdict struct { + command uint8 + Id uint64 + Verdict uint8 +} + +type RedirectV4 struct { + command uint8 + Id uint64 + RemoteAddress [4]byte + RemotePort uint16 +} type RedirectV6 struct { command uint8 @@ -81,49 +81,49 @@ type ConnectionsUpdate struct { } func SendShutdownCommand(writer io.Writer) error { - _, err := writer.Write([]byte{CommandShutdown}) - return err -} - -func SendVerdictCommand(writer io.Writer, verdict Verdict) error { - verdict.command = CommandVerdict - return binary.Write(writer, binary.LittleEndian, verdict) -} - -func SendUpdateV4Command(writer io.Writer, update UpdateV4) error { - update.command = CommandUpdateV4 - return binary.Write(writer, binary.LittleEndian, update) -} - -func SendUpdateV6Command(writer io.Writer, update UpdateV6) error { - update.command = CommandUpdateV6 - return binary.Write(writer, binary.LittleEndian, update) -} - -func SendClearCacheCommand(writer io.Writer) error { - _, err := writer.Write([]byte{CommandClearCache}) - return err -} - -func SendGetConnectionsUpdateCommand(writer io.Writer, timestamp uint64) error { - req := ConnectionsUpdate{ + _, err := writer.Write([]byte{CommandShutdown}) + return err +} + +func SendVerdictCommand(writer io.Writer, verdict Verdict) error { + verdict.command = CommandVerdict + return binary.Write(writer, binary.LittleEndian, verdict) +} + +func SendUpdateV4Command(writer io.Writer, update UpdateV4) error { + update.command = CommandUpdateV4 + return binary.Write(writer, binary.LittleEndian, update) +} + +func SendUpdateV6Command(writer io.Writer, update UpdateV6) error { + update.command = CommandUpdateV6 + return binary.Write(writer, binary.LittleEndian, update) +} + +func SendClearCacheCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandClearCache}) + return err +} + +func SendGetConnectionsUpdateCommand(writer io.Writer, timestamp uint64) error { + req := ConnectionsUpdate{ command: CommandGetConnectionsUpdate, timestamp: timestamp, - } - return binary.Write(writer, binary.LittleEndian, req) -} - -func SendGetLogsCommand(writer io.Writer) error { - _, err := writer.Write([]byte{CommandGetLogs}) - return err -} - -func SendPrintMemoryStatsCommand(writer io.Writer) error { - _, err := writer.Write([]byte{CommandPrintMemoryStats}) - return err -} - -func SendCleanEndedConnectionsCommand(writer io.Writer) error { - _, err := writer.Write([]byte{CommandCleanEndedConnections}) - return err -} + } + return binary.Write(writer, binary.LittleEndian, req) +} + +func SendGetLogsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandGetLogs}) + return err +} + +func SendPrintMemoryStatsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandPrintMemoryStats}) + return err +} + +func SendCleanEndedConnectionsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandCleanEndedConnections}) + return err +} diff --git a/kext_interface/info.go b/kext_interface/info.go index f2d0c18..fa6271b 100644 --- a/kext_interface/info.go +++ b/kext_interface/info.go @@ -1,56 +1,56 @@ -package kext_interface - +package kext_interface + import ( - "bytes" - "encoding/binary" - "errors" + "bytes" + "encoding/binary" + "errors" "io" - "math" -) - -const ( - infoLogLine byte = 0 - infoConnectionIpv4 byte = 1 - infoConnectionIpv6 byte = 2 + "math" +) + +const ( + infoLogLine byte = 0 + infoConnectionIpv4 byte = 1 + infoConnectionIpv6 byte = 2 infoConnectionEndEventV4 byte = 3 infoConnectionEndEventV6 byte = 4 - infoConnectionUpdateEventV4 byte = 5 - infoConnectionUpdateEventV6 byte = 6 - infoConnectionUpdateEnd byte = 7 -) - -const ( - SeverityTrace byte = 1 - SeverityDebug byte = 2 - SeverityInfo byte = 3 - SeverityWarn byte = 4 - SeverityError byte = 5 - SeverityFatal byte = 6 + infoConnectionUpdateEventV4 byte = 5 + infoConnectionUpdateEventV6 byte = 6 + infoConnectionUpdateEnd byte = 7 ) -var ErrorUnknownInfoType = errors.New("unknown info type") - -const INFO_ONLY_PACKET_ID = math.MaxUint64 - -type LogLine struct { - Severity byte - Line string -} - -type connectionV4Internal struct { - Id uint64 - ProcessId uint64 - Direction byte - Protocol byte - LocalIp [4]byte - RemoteIp [4]byte - LocalPort uint16 +const ( + SeverityTrace byte = 1 + SeverityDebug byte = 2 + SeverityInfo byte = 3 + SeverityWarn byte = 4 + SeverityError byte = 5 + SeverityFatal byte = 6 +) + +var ErrorUnknownInfoType = errors.New("unknown info type") + +const INFO_ONLY_PACKET_ID = math.MaxUint64 + +type LogLine struct { + Severity byte + Line string +} + +type connectionV4Internal struct { + Id uint64 + ProcessId uint64 + Direction byte + Protocol byte + LocalIp [4]byte + RemoteIp [4]byte + LocalPort uint16 RemotePort uint16 PayloadLayer uint8 } type ConnectionV4 struct { - connectionV4Internal + connectionV4Internal Payload []byte } @@ -63,14 +63,14 @@ func (c *ConnectionV4) Compare(other *ConnectionV4) bool { c.RemoteIp == other.RemoteIp && c.LocalPort == other.LocalPort && c.RemotePort == other.RemotePort -} - -func (c *ConnectionV4) IsInfoOnly() bool { - return c.Id == INFO_ONLY_PACKET_ID } -type connectionV6Internal struct { - Id uint64 +func (c *ConnectionV4) IsInfoOnly() bool { + return c.Id == INFO_ONLY_PACKET_ID +} + +type connectionV6Internal struct { + Id uint64 ProcessId uint64 Direction byte Protocol byte @@ -95,10 +95,10 @@ func (c ConnectionV6) Compare(other *ConnectionV6) bool { c.RemoteIp == other.RemoteIp && c.LocalPort == other.LocalPort && c.RemotePort == other.RemotePort -} +} -func (c *ConnectionV6) IsInfoOnly() bool { - return c.Id == INFO_ONLY_PACKET_ID +func (c *ConnectionV6) IsInfoOnly() bool { + return c.Id == INFO_ONLY_PACKET_ID } type ConnectionEndV4 struct { @@ -131,14 +131,14 @@ type ConnectionEndV6 struct { type ConnectionUpdateV4 struct { Protocol byte - LocalIp [4]byte - RemoteIp [4]byte - LocalPort uint16 - RemotePort uint16 - RxBytes uint64 - RxPackets uint64 - TxBytes uint64 - TxPackets uint64 + LocalIp [4]byte + RemoteIp [4]byte + LocalPort uint16 + RemotePort uint16 + RxBytes uint64 + RxPackets uint64 + TxBytes uint64 + TxPackets uint64 } type ConnectionUpdateV6 struct { @@ -152,7 +152,7 @@ type ConnectionUpdateV6 struct { TxBytes uint64 TxPackets uint64 } - + type ConnectionUpdateEnd struct{} func parseGenericInfo[T any](data []byte) (Info, error) { @@ -164,11 +164,11 @@ func parseGenericInfo[T any](data []byte) (Info, error) { return nil, err } return &new, nil -} - -func parseEmptyInfo[T any](data []byte) (Info, error) { - var new T - return &new, nil +} + +func parseEmptyInfo[T any](data []byte) (Info, error) { + var new T + return &new, nil } func parseConnectionV4(data []byte) (Info, error) { @@ -226,21 +226,21 @@ func parseConnectionV6(data []byte) (Info, error) { func parseLogLine(data []byte) (Info, error) { var logLine LogLine reader := bytes.NewReader(data) - - err := binary.Read(reader, binary.LittleEndian, &logLine.Severity) - if err != nil { - return nil, err - } - // Read string - line := make([]byte, len(data)-1) // -1 for the severity enum. - err = binary.Read(reader, binary.LittleEndian, &line) - if err != nil { - return nil, err - } - logLine.Line = string(line) - return &logLine, nil -} - + + err := binary.Read(reader, binary.LittleEndian, &logLine.Severity) + if err != nil { + return nil, err + } + // Read string + line := make([]byte, len(data)-1) // -1 for the severity enum. + err = binary.Read(reader, binary.LittleEndian, &line) + if err != nil { + return nil, err + } + logLine.Line = string(line) + return &logLine, nil +} + type Info any func RecvInfo(reader io.Reader) (Info, error) { @@ -252,9 +252,9 @@ func RecvInfo(reader io.Reader) (Info, error) { // Read size of data var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) - if err != nil { - return nil, err + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, err } data := make([]byte, size) @@ -267,22 +267,22 @@ func RecvInfo(reader io.Reader) (Info, error) { return nil, errors.New("not enough data read") } - // Map of infoType to parser functions - parsers := map[byte]func([]byte) (Info, error){ - infoLogLine: parseLogLine, - infoConnectionIpv4: parseConnectionV4, - infoConnectionIpv6: parseConnectionV6, - infoConnectionEndEventV4: parseGenericInfo[ConnectionEndV4], - infoConnectionEndEventV6: parseGenericInfo[ConnectionEndV6], - infoConnectionUpdateEventV4: parseGenericInfo[ConnectionUpdateV4], - infoConnectionUpdateEventV6: parseGenericInfo[ConnectionUpdateV6], - infoConnectionUpdateEnd: parseEmptyInfo[ConnectionUpdateEnd], - } - - parser, ok := parsers[infoType] - if ok { - return parser(data) - } - - return nil, ErrorUnknownInfoType -} + // Map of infoType to parser functions + parsers := map[byte]func([]byte) (Info, error){ + infoLogLine: parseLogLine, + infoConnectionIpv4: parseConnectionV4, + infoConnectionIpv6: parseConnectionV6, + infoConnectionEndEventV4: parseGenericInfo[ConnectionEndV4], + infoConnectionEndEventV6: parseGenericInfo[ConnectionEndV6], + infoConnectionUpdateEventV4: parseGenericInfo[ConnectionUpdateV4], + infoConnectionUpdateEventV6: parseGenericInfo[ConnectionUpdateV6], + infoConnectionUpdateEnd: parseEmptyInfo[ConnectionUpdateEnd], + } + + parser, ok := parsers[infoType] + if ok { + return parser(data) + } + + return nil, ErrorUnknownInfoType +} diff --git a/kext_interface/ioctl.go b/kext_interface/ioctl.go index 64b3b87..1440a1d 100644 --- a/kext_interface/ioctl.go +++ b/kext_interface/ioctl.go @@ -1,36 +1,36 @@ -//go:build windows -// +build windows - -package kext_interface - -import ( - "golang.org/x/sys/windows" -) - -const ( - METHOD_BUFFERED = 0 - METHOD_IN_DIRECT = 1 - METHOD_OUT_DIRECT = 2 - METHOD_NEITHER = 3 - - SIOCTL_TYPE = 40000 -) - -func ctlCode(device_type, function, method, access uint32) uint32 { - return (device_type << 16) | (access << 14) | (function << 2) | method -} - -var ( - IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) - IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) -) - -func ReadVersion(file *KextFile) ([]uint8, error) { - data := make([]uint8, 4) - err := file.deviceIOControl(IOCTL_VERSION, nil, data) - - if err != nil { - return nil, err - } - return data, nil -} +//go:build windows +// +build windows + +package kext_interface + +import ( + "golang.org/x/sys/windows" +) + +const ( + METHOD_BUFFERED = 0 + METHOD_IN_DIRECT = 1 + METHOD_OUT_DIRECT = 2 + METHOD_NEITHER = 3 + + SIOCTL_TYPE = 40000 +) + +func ctlCode(device_type, function, method, access uint32) uint32 { + return (device_type << 16) | (access << 14) | (function << 2) | method +} + +var ( + IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) + IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) +) + +func ReadVersion(file *KextFile) ([]uint8, error) { + data := make([]uint8, 4) + err := file.deviceIOControl(IOCTL_VERSION, nil, data) + + if err != nil { + return nil, err + } + return data, nil +} diff --git a/kext_interface/kext.go b/kext_interface/kext.go index 8269b94..b319e35 100644 --- a/kext_interface/kext.go +++ b/kext_interface/kext.go @@ -1,261 +1,261 @@ -//go:build windows -// +build windows - -package kext_interface - -import ( - _ "embed" - "errors" - "fmt" - "strconv" - "strings" - "syscall" - "time" - - "golang.org/x/sys/windows" -) - -var ( - //go:embed version.txt - versionTxt string - - // 4 byte version of the Kext interface - InterfaceVersion = func() (v [4]byte) { - // Parse version from file "version.txt". Expected format: [0, 1, 2, 3] - s := strings.TrimSpace(versionTxt) - s = strings.TrimPrefix(s, "[") - s = strings.TrimSuffix(s, "]") - str_ver := strings.Split(s, ",") - for i := range v { - n, err := strconv.Atoi(strings.TrimSpace(str_ver[i])) - if err != nil { - panic(err) - } - v[i] = byte(n) - } - return - }() -) - -const winInvalidHandleValue = windows.Handle(^uintptr(0)) // Max value -const stopServiceTimeoutDuration = time.Duration(30 * time.Second) - -type KextService struct { - handle windows.Handle - driverName string -} - -func (s *KextService) isValid() bool { - return s != nil && s.handle != winInvalidHandleValue && s.handle != 0 -} - -func (s *KextService) isRunning() (bool, error) { - if !s.isValid() { - return false, fmt.Errorf("kext service not initialized") - } - var status windows.SERVICE_STATUS - err := windows.QueryServiceStatus(s.handle, &status) - if err != nil { - return false, err - } - return status.CurrentState == windows.SERVICE_RUNNING, nil -} - -func (s *KextService) waitForServiceStatus(neededStatus uint32, timeLimit time.Duration) (bool, error) { - start := time.Now() - for { - // Read the current service state. - var status windows.SERVICE_STATUS - if err := windows.QueryServiceStatus(s.handle, &status); err != nil { - return false, fmt.Errorf("failed while waiting for service status change: %w", err) - } - - // Reached the target state. - if status.CurrentState == neededStatus { - return true, nil - } - - if time.Since(start) > timeLimit { - return false, fmt.Errorf("time limit reached") - } - - // Sleep for 1/10 of the wait hint, as recommended by Microsoft, with a - // floor so a zero wait hint (common for driver services) does not turn - // this into a busy loop. - sleep := time.Duration(status.WaitHint/10) * time.Millisecond - if sleep < 100*time.Millisecond { - sleep = 100 * time.Millisecond - } - time.Sleep(sleep) - } -} - -func (s *KextService) Start(wait bool) error { - if !s.isValid() { - return fmt.Errorf("kext service not initialized") - } - - // Start the service: - err := windows.StartService(s.handle, 0, nil) - - if err != nil { - err = windows.GetLastError() - if err != windows.ERROR_SERVICE_ALREADY_RUNNING { - // Failed to start service; clean-up: - var status windows.SERVICE_STATUS - _ = windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) - _ = windows.DeleteService(s.handle) - _ = windows.CloseServiceHandle(s.handle) - s.handle = winInvalidHandleValue - return err - } - } - - // Wait for service to start - if wait { - success, err := s.waitForServiceStatus(windows.SERVICE_RUNNING, stopServiceTimeoutDuration) - if err != nil || !success { - return fmt.Errorf("service did not start: %w", err) - } - } - - return nil -} - -func (s *KextService) GetHandle() windows.Handle { - return s.handle -} - -func (s *KextService) Stop(wait bool) error { - if !s.isValid() { - return fmt.Errorf("kext service not initialized") - } - - // Stop the service - var status windows.SERVICE_STATUS - err := windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) - if err != nil { - // The driver already being stopped is exactly the state we want; treat - // it as success so teardown does not report a spurious error. - if errors.Is(err, windows.ERROR_SERVICE_NOT_ACTIVE) { - return nil - } - return fmt.Errorf("service failed to stop: %w", err) - } - - // Wait for service to stop - if wait { - success, err := s.waitForServiceStatus(windows.SERVICE_STOPPED, time.Duration(10*time.Second)) - if err != nil || !success { - return fmt.Errorf("service did not stop: %w", err) - } - } - - return nil -} - -func (s *KextService) Delete() error { - if !s.isValid() { - return fmt.Errorf("kext service not initialized") - } - - err := windows.DeleteService(s.handle) - if err != nil { - return fmt.Errorf("failed to delete service: %s", err) - } - - // Service wont be deleted until all handles are closed. - err = windows.CloseServiceHandle(s.handle) - if err != nil { - return fmt.Errorf("failed to close service handle: %s", err) - } - - s.handle = winInvalidHandleValue - return nil -} - -func (s *KextService) WaitUntilDeleted(serviceManager windows.Handle) error { - driverNameU16, err := syscall.UTF16FromString(s.driverName) - if err != nil { - return fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) - } - // Wait until we can no longer open the old service. - // Not very efficient but NotifyServiceStatusChange cannot be used with driver service. - start := time.Now() - timeLimit := time.Duration(30 * time.Second) - for { - handle, err := windows.OpenService(serviceManager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) - if err != nil { - break - } - _ = windows.CloseServiceHandle(handle) - - if time.Since(start) > timeLimit { - return fmt.Errorf("time limit reached") - } - - time.Sleep(100 * time.Millisecond) - } - - return nil -} - -func (s *KextService) OpenFile(readBufferSize int) (*KextFile, error) { - if !s.isValid() { - return nil, fmt.Errorf("invalid kext object") - } - - driverNameU16, err := syscall.UTF16FromString(`\\.\` + s.driverName) - if err != nil { - return nil, fmt.Errorf("failed to convert driver driverName to UTF16 string %w", err) - } - - handle, err := windows.CreateFile(&driverNameU16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, 0) - if err != nil { - return nil, err - } - - return &KextFile{handle: handle, buffer: make([]byte, readBufferSize)}, nil -} - -func CreateKextService(driverName string, driverPath string) (*KextService, error) { - // Open the service manager: - manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) - if err != nil { - return nil, fmt.Errorf("failed to open service manager: %d", err) - } - defer windows.CloseServiceHandle(manager) - - driverNameU16, err := syscall.UTF16FromString(driverName) - if err != nil { - return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) - } - - // Check if there is an old service. - service, err := windows.OpenService(manager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) - if err == nil { - fmt.Println("kext: old driver service was found") - oldService := &KextService{handle: service, driverName: driverName} - oldService.Stop(true) - err = oldService.Delete() - if err != nil { - return nil, err - } - err := oldService.WaitUntilDeleted(manager) - if err != nil { - return nil, err - } - - service = winInvalidHandleValue - fmt.Println("kext: old driver service was deleted successfully") - } - - driverPathU16, err := syscall.UTF16FromString(driverPath) - - // Create the service - service, err = windows.CreateService(manager, &driverNameU16[0], &driverNameU16[0], windows.SERVICE_ALL_ACCESS, windows.SERVICE_KERNEL_DRIVER, windows.SERVICE_DEMAND_START, windows.SERVICE_ERROR_NORMAL, &driverPathU16[0], nil, nil, nil, nil, nil) - if err != nil { - return nil, err - } - return &KextService{handle: service, driverName: driverName}, nil -} +//go:build windows +// +build windows + +package kext_interface + +import ( + _ "embed" + "errors" + "fmt" + "strconv" + "strings" + "syscall" + "time" + + "golang.org/x/sys/windows" +) + +var ( + //go:embed version.txt + versionTxt string + + // 4 byte version of the Kext interface + InterfaceVersion = func() (v [4]byte) { + // Parse version from file "version.txt". Expected format: [0, 1, 2, 3] + s := strings.TrimSpace(versionTxt) + s = strings.TrimPrefix(s, "[") + s = strings.TrimSuffix(s, "]") + str_ver := strings.Split(s, ",") + for i := range v { + n, err := strconv.Atoi(strings.TrimSpace(str_ver[i])) + if err != nil { + panic(err) + } + v[i] = byte(n) + } + return + }() +) + +const winInvalidHandleValue = windows.Handle(^uintptr(0)) // Max value +const stopServiceTimeoutDuration = time.Duration(30 * time.Second) + +type KextService struct { + handle windows.Handle + driverName string +} + +func (s *KextService) isValid() bool { + return s != nil && s.handle != winInvalidHandleValue && s.handle != 0 +} + +func (s *KextService) isRunning() (bool, error) { + if !s.isValid() { + return false, fmt.Errorf("kext service not initialized") + } + var status windows.SERVICE_STATUS + err := windows.QueryServiceStatus(s.handle, &status) + if err != nil { + return false, err + } + return status.CurrentState == windows.SERVICE_RUNNING, nil +} + +func (s *KextService) waitForServiceStatus(neededStatus uint32, timeLimit time.Duration) (bool, error) { + start := time.Now() + for { + // Read the current service state. + var status windows.SERVICE_STATUS + if err := windows.QueryServiceStatus(s.handle, &status); err != nil { + return false, fmt.Errorf("failed while waiting for service status change: %w", err) + } + + // Reached the target state. + if status.CurrentState == neededStatus { + return true, nil + } + + if time.Since(start) > timeLimit { + return false, fmt.Errorf("time limit reached") + } + + // Sleep for 1/10 of the wait hint, as recommended by Microsoft, with a + // floor so a zero wait hint (common for driver services) does not turn + // this into a busy loop. + sleep := time.Duration(status.WaitHint/10) * time.Millisecond + if sleep < 100*time.Millisecond { + sleep = 100 * time.Millisecond + } + time.Sleep(sleep) + } +} + +func (s *KextService) Start(wait bool) error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + // Start the service: + err := windows.StartService(s.handle, 0, nil) + + if err != nil { + err = windows.GetLastError() + if err != windows.ERROR_SERVICE_ALREADY_RUNNING { + // Failed to start service; clean-up: + var status windows.SERVICE_STATUS + _ = windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) + _ = windows.DeleteService(s.handle) + _ = windows.CloseServiceHandle(s.handle) + s.handle = winInvalidHandleValue + return err + } + } + + // Wait for service to start + if wait { + success, err := s.waitForServiceStatus(windows.SERVICE_RUNNING, stopServiceTimeoutDuration) + if err != nil || !success { + return fmt.Errorf("service did not start: %w", err) + } + } + + return nil +} + +func (s *KextService) GetHandle() windows.Handle { + return s.handle +} + +func (s *KextService) Stop(wait bool) error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + // Stop the service + var status windows.SERVICE_STATUS + err := windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) + if err != nil { + // The driver already being stopped is exactly the state we want; treat + // it as success so teardown does not report a spurious error. + if errors.Is(err, windows.ERROR_SERVICE_NOT_ACTIVE) { + return nil + } + return fmt.Errorf("service failed to stop: %w", err) + } + + // Wait for service to stop + if wait { + success, err := s.waitForServiceStatus(windows.SERVICE_STOPPED, time.Duration(10*time.Second)) + if err != nil || !success { + return fmt.Errorf("service did not stop: %w", err) + } + } + + return nil +} + +func (s *KextService) Delete() error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + err := windows.DeleteService(s.handle) + if err != nil { + return fmt.Errorf("failed to delete service: %s", err) + } + + // Service wont be deleted until all handles are closed. + err = windows.CloseServiceHandle(s.handle) + if err != nil { + return fmt.Errorf("failed to close service handle: %s", err) + } + + s.handle = winInvalidHandleValue + return nil +} + +func (s *KextService) WaitUntilDeleted(serviceManager windows.Handle) error { + driverNameU16, err := syscall.UTF16FromString(s.driverName) + if err != nil { + return fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) + } + // Wait until we can no longer open the old service. + // Not very efficient but NotifyServiceStatusChange cannot be used with driver service. + start := time.Now() + timeLimit := time.Duration(30 * time.Second) + for { + handle, err := windows.OpenService(serviceManager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) + if err != nil { + break + } + _ = windows.CloseServiceHandle(handle) + + if time.Since(start) > timeLimit { + return fmt.Errorf("time limit reached") + } + + time.Sleep(100 * time.Millisecond) + } + + return nil +} + +func (s *KextService) OpenFile(readBufferSize int) (*KextFile, error) { + if !s.isValid() { + return nil, fmt.Errorf("invalid kext object") + } + + driverNameU16, err := syscall.UTF16FromString(`\\.\` + s.driverName) + if err != nil { + return nil, fmt.Errorf("failed to convert driver driverName to UTF16 string %w", err) + } + + handle, err := windows.CreateFile(&driverNameU16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, 0) + if err != nil { + return nil, err + } + + return &KextFile{handle: handle, buffer: make([]byte, readBufferSize)}, nil +} + +func CreateKextService(driverName string, driverPath string) (*KextService, error) { + // Open the service manager: + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return nil, fmt.Errorf("failed to open service manager: %d", err) + } + defer windows.CloseServiceHandle(manager) + + driverNameU16, err := syscall.UTF16FromString(driverName) + if err != nil { + return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) + } + + // Check if there is an old service. + service, err := windows.OpenService(manager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) + if err == nil { + fmt.Println("kext: old driver service was found") + oldService := &KextService{handle: service, driverName: driverName} + oldService.Stop(true) + err = oldService.Delete() + if err != nil { + return nil, err + } + err := oldService.WaitUntilDeleted(manager) + if err != nil { + return nil, err + } + + service = winInvalidHandleValue + fmt.Println("kext: old driver service was deleted successfully") + } + + driverPathU16, err := syscall.UTF16FromString(driverPath) + + // Create the service + service, err = windows.CreateService(manager, &driverNameU16[0], &driverNameU16[0], windows.SERVICE_ALL_ACCESS, windows.SERVICE_KERNEL_DRIVER, windows.SERVICE_DEMAND_START, windows.SERVICE_ERROR_NORMAL, &driverPathU16[0], nil, nil, nil, nil, nil) + if err != nil { + return nil, err + } + return &KextService{handle: service, driverName: driverName}, nil +} diff --git a/kext_interface/kext_file_test.go b/kext_interface/kext_file_test.go index cea6149..0731df7 100644 --- a/kext_interface/kext_file_test.go +++ b/kext_interface/kext_file_test.go @@ -1,12 +1,12 @@ -//go:build linux -// +build linux - -package kext_interface - -type KextFile struct{} - -func (f *KextFile) Read(buffer []byte) (int, error) { - return 0, nil -} - -func (f *KextFile) flush_buffer() {} +//go:build linux +// +build linux + +package kext_interface + +type KextFile struct{} + +func (f *KextFile) Read(buffer []byte) (int, error) { + return 0, nil +} + +func (f *KextFile) flush_buffer() {} diff --git a/kext_interface/protocol_test.go b/kext_interface/protocol_test.go index c3d5ef6..140a3b5 100644 --- a/kext_interface/protocol_test.go +++ b/kext_interface/protocol_test.go @@ -1,53 +1,53 @@ -package kext_interface - -import ( - "io" - "math/rand" - "os" - "testing" -) - -func TestRustInfoFile(t *testing.T) { - file, err := os.Open("../protocol/rust_info_test.bin") - if err != nil { - panic(err) - } - defer file.Close() //nolint:errcheck - for { - info, err := RecvInfo(file) - if err != nil { - if err != io.EOF { - t.Errorf("unexpected error: %s\n", err) - } - return - } - switch v := info.(type) { - case *LogLine: - t.Logf("LogLine: Severity=%d, Line=%s\n", v.Severity, v.Line) - if v.Severity != 1 { - t.Errorf("unexpected Log severity: %d\n", v.Severity) - } - if v.Line != "prefix: test log" { - t.Errorf("unexpected Log line: %s\n", v.Line) - } - - case *ConnectionV4: - t.Logf("ConnectionV4: %+v\n", v) - expected := ConnectionV4{ - connectionV4Internal: connectionV4Internal{ - Id: 1, - ProcessId: 2, - Direction: 3, - Protocol: 4, - LocalIp: [4]byte{1, 2, 3, 4}, - RemoteIp: [4]byte{2, 3, 4, 5}, - LocalPort: 5, - RemotePort: 6, - PayloadLayer: 7, - }, - Payload: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - } - if !v.Compare(&expected) { +package kext_interface + +import ( + "io" + "math/rand" + "os" + "testing" +) + +func TestRustInfoFile(t *testing.T) { + file, err := os.Open("../protocol/rust_info_test.bin") + if err != nil { + panic(err) + } + defer file.Close() //nolint:errcheck + for { + info, err := RecvInfo(file) + if err != nil { + if err != io.EOF { + t.Errorf("unexpected error: %s\n", err) + } + return + } + switch v := info.(type) { + case *LogLine: + t.Logf("LogLine: Severity=%d, Line=%s\n", v.Severity, v.Line) + if v.Severity != 1 { + t.Errorf("unexpected Log severity: %d\n", v.Severity) + } + if v.Line != "prefix: test log" { + t.Errorf("unexpected Log line: %s\n", v.Line) + } + + case *ConnectionV4: + t.Logf("ConnectionV4: %+v\n", v) + expected := ConnectionV4{ + connectionV4Internal: connectionV4Internal{ + Id: 1, + ProcessId: 2, + Direction: 3, + Protocol: 4, + LocalIp: [4]byte{1, 2, 3, 4}, + RemoteIp: [4]byte{2, 3, 4, 5}, + LocalPort: 5, + RemotePort: 6, + PayloadLayer: 7, + }, + Payload: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + } + if !v.Compare(&expected) { t.Errorf("unexpected ConnectionV4: %+v\n", v) } @@ -141,10 +141,10 @@ func TestRustInfoFile(t *testing.T) { } case *ConnectionUpdateEnd: t.Logf("ConnectionUpdateEnd: %+v\n", v) - // Empty struct + // Empty struct default: - t.Errorf("unexpected info type: %T\n", v) - } + t.Errorf("unexpected info type: %T\n", v) + } } } @@ -159,7 +159,7 @@ func TestGenerateCommandFile(t *testing.T) { CommandVerdict, CommandUpdateV4, CommandUpdateV6, - CommandClearCache, + CommandClearCache, CommandGetConnectionsUpdate, CommandGetLogs, CommandCleanEndedConnections, @@ -181,50 +181,50 @@ func TestGenerateCommandFile(t *testing.T) { _ = SendVerdictCommand(file, Verdict{ Id: 1, Verdict: 2, - }) - } - case CommandUpdateV4: - { - _ = SendUpdateV4Command(file, UpdateV4{ - Protocol: 1, - LocalAddress: [4]byte{1, 2, 3, 4}, - LocalPort: 2, - RemoteAddress: [4]byte{2, 3, 4, 5}, - RemotePort: 3, - Verdict: 4, - }) - } - case CommandUpdateV6: - { - _ = SendUpdateV6Command(file, UpdateV6{ - Protocol: 1, - LocalAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, - LocalPort: 2, - RemoteAddress: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, - RemotePort: 3, - Verdict: 4, - }) - } - case CommandClearCache: - { - _ = SendClearCacheCommand(file) - } - case CommandGetLogs: - { - _ = SendGetLogsCommand(file) - } - case CommandGetConnectionsUpdate: - { - _ = SendGetConnectionsUpdateCommand(file, 1234567890) - } - case CommandPrintMemoryStats: - { - _ = SendPrintMemoryStatsCommand(file) - } - case CommandCleanEndedConnections: - { - _ = SendCleanEndedConnectionsCommand(file) - } - } - } -} + }) + } + case CommandUpdateV4: + { + _ = SendUpdateV4Command(file, UpdateV4{ + Protocol: 1, + LocalAddress: [4]byte{1, 2, 3, 4}, + LocalPort: 2, + RemoteAddress: [4]byte{2, 3, 4, 5}, + RemotePort: 3, + Verdict: 4, + }) + } + case CommandUpdateV6: + { + _ = SendUpdateV6Command(file, UpdateV6{ + Protocol: 1, + LocalAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + LocalPort: 2, + RemoteAddress: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + RemotePort: 3, + Verdict: 4, + }) + } + case CommandClearCache: + { + _ = SendClearCacheCommand(file) + } + case CommandGetLogs: + { + _ = SendGetLogsCommand(file) + } + case CommandGetConnectionsUpdate: + { + _ = SendGetConnectionsUpdateCommand(file, 1234567890) + } + case CommandPrintMemoryStats: + { + _ = SendPrintMemoryStatsCommand(file) + } + case CommandCleanEndedConnections: + { + _ = SendCleanEndedConnectionsCommand(file) + } + } + } +} diff --git a/kext_tester/main.go b/kext_tester/main.go index f4ff67e..16012ce 100644 --- a/kext_tester/main.go +++ b/kext_tester/main.go @@ -1,41 +1,41 @@ -//go:build windows - -package main - -import ( - "bufio" - "fmt" +//go:build windows + +package main + +import ( + "bufio" + "fmt" "log" "net" - "os" + "os" "time" - "github.com/vlabo/zenithfence/kext_interface" -) - -type Verdict int8 - -var protocols = map[int]string{ - 1: "icmp", - 2: "igmp", - 6: "tcp", - 17: "udp", - 58: "ipv6-icmp", -} - -func main() { - driverName := "ZenithFence" - sysPath := "C:\\Dev\\driver.sys" - kext, err := kext_interface.CreateKextService(driverName, sysPath) - if err != nil { - log.Panicf("failed to create driver service: %s", err) - } - defer kext.Delete() - - err = kext.Start(true) - if err != nil { - log.Panicf("failed to start service: %s", err) - } + "github.com/vlabo/zenithfence/kext_interface" +) + +type Verdict int8 + +var protocols = map[int]string{ + 1: "icmp", + 2: "igmp", + 6: "tcp", + 17: "udp", + 58: "ipv6-icmp", +} + +func main() { + driverName := "ZenithFence" + sysPath := "C:\\Dev\\driver.sys" + kext, err := kext_interface.CreateKextService(driverName, sysPath) + if err != nil { + log.Panicf("failed to create driver service: %s", err) + } + defer kext.Delete() + + err = kext.Start(true) + if err != nil { + log.Panicf("failed to start service: %s", err) + } defer kext.Stop(true) file, err := kext.OpenFile(1024) @@ -78,56 +78,56 @@ func main() { // kext_interface.WriteVerdictCommand(file, kext_interface.BuildVerdict(kext_interface.Verdict{Id: connection.Id, Verdict: uint8(VerdictBlock)})) kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) } else { - if info.RemoteIp == [4]byte{1, 1, 1, 1} { - kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentBlock)}) - log.Printf("blocked packet: %d pid=%d %+v:%d %s %+v:%d %s\n", info.Id, info.ProcessId, net.IP(info.LocalIp[:]), info.LocalPort, "->", net.IP(info.RemoteIp[:]), info.RemotePort, protocols[int(info.Protocol)]) - } else { - time.Sleep(200 * time.Millisecond) - kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) - } - } - - // log.Printf("infov4: %d pid=%d %+v:%d %s %+v:%d %s\n", conn.Id, conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) - } - case *kext_interface.ConnectionV6: - { - // direction := "->" - if info.Direction == 1 { - kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) - // direction = "<-" - } else { - kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) - } - // log.Printf("infov6: %d pid=%d [%+v]:%d %s [%+v]:%d %s\n", conn.Id, conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) - } - case *kext_interface.ConnectionEndV4: - { - // conn := info.ConnectionEndV4 - // direction := "->" - // if conn.Direction == 1 { - // direction = "<-" - // } - // log.Printf("conn end v4: pid=%d %+v:%d %s %+v:%d %s\n", conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) - } - case *kext_interface.ConnectionEndV6: - { - // conn := info.ConnectionEndV6 - // direction := "->" - // if conn.Direction == 1 { - // direction = "<-" - // } - // log.Printf("conn end v6: pid=%d [%+v]:%d %s [%+v]:%d %s\n", conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) - } - case *kext_interface.LogLine: - { - log.Println(info.Line) - } - } - } - }() - - fmt.Print("Press enter to exit\n") - input := bufio.NewScanner(os.Stdin) - input.Scan() - kext_interface.SendShutdownCommand(file) -} + if info.RemoteIp == [4]byte{1, 1, 1, 1} { + kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentBlock)}) + log.Printf("blocked packet: %d pid=%d %+v:%d %s %+v:%d %s\n", info.Id, info.ProcessId, net.IP(info.LocalIp[:]), info.LocalPort, "->", net.IP(info.RemoteIp[:]), info.RemotePort, protocols[int(info.Protocol)]) + } else { + time.Sleep(200 * time.Millisecond) + kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) + } + } + + // log.Printf("infov4: %d pid=%d %+v:%d %s %+v:%d %s\n", conn.Id, conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) + } + case *kext_interface.ConnectionV6: + { + // direction := "->" + if info.Direction == 1 { + kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) + // direction = "<-" + } else { + kext_interface.SendVerdictCommand(file, kext_interface.Verdict{Id: info.Id, Verdict: uint8(kext_interface.VerdictPermanentAccept)}) + } + // log.Printf("infov6: %d pid=%d [%+v]:%d %s [%+v]:%d %s\n", conn.Id, conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) + } + case *kext_interface.ConnectionEndV4: + { + // conn := info.ConnectionEndV4 + // direction := "->" + // if conn.Direction == 1 { + // direction = "<-" + // } + // log.Printf("conn end v4: pid=%d %+v:%d %s %+v:%d %s\n", conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) + } + case *kext_interface.ConnectionEndV6: + { + // conn := info.ConnectionEndV6 + // direction := "->" + // if conn.Direction == 1 { + // direction = "<-" + // } + // log.Printf("conn end v6: pid=%d [%+v]:%d %s [%+v]:%d %s\n", conn.ProcessId, net.IP(conn.LocalIp[:]), conn.LocalPort, direction, net.IP(conn.RemoteIp[:]), conn.RemotePort, protocols[int(conn.Protocol)]) + } + case *kext_interface.LogLine: + { + log.Println(info.Line) + } + } + } + }() + + fmt.Print("Press enter to exit\n") + input := bufio.NewScanner(os.Stdin) + input.Scan() + kext_interface.SendShutdownCommand(file) +} diff --git a/protocol/src/command.rs b/protocol/src/command.rs index 00c7647..bed51bb 100644 --- a/protocol/src/command.rs +++ b/protocol/src/command.rs @@ -1,181 +1,181 @@ -// Commands from user space - -use num::FromPrimitive; -use num_derive::FromPrimitive; - -#[repr(u8)] -#[derive(Clone, Copy, FromPrimitive)] -#[rustfmt::skip] -pub enum CommandType { - Shutdown = 0, - Verdict = 1, - UpdateV4 = 2, - UpdateV6 = 3, - ClearCache = 4, - GetConnectionsUpdate = 5, - GetLogs = 6, - PrintMemoryStats = 7, - CleanEndedConnections = 8, -} - -#[repr(C, packed)] -pub struct Command { - pub command_type: CommandType, - value: [u8; 0], -} - -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq)] -pub struct Verdict { - pub id: u64, - pub verdict: u8, -} - -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq)] -pub struct UpdateV4 { - pub protocol: u8, - pub local_address: [u8; 4], - pub local_port: u16, - pub remote_address: [u8; 4], - pub remote_port: u16, - pub verdict: u8, -} - -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq)] -pub struct UpdateV6 { - pub protocol: u8, - pub local_address: [u8; 16], - pub local_port: u16, - pub remote_address: [u8; 16], - pub remote_port: u16, - pub verdict: u8, -} - -#[repr(C, packed)] -#[derive(Debug, PartialEq, Eq)] -pub struct ConnectionsUpdate { - pub timestamp: u64, -} - -pub fn parse_type(bytes: &[u8]) -> Option { - FromPrimitive::from_u8(bytes[0]) -} - -pub fn parse_verdict(bytes: &[u8]) -> &Verdict { - as_type(bytes) -} - -pub fn parse_update_v4(bytes: &[u8]) -> &UpdateV4 { - as_type(bytes) -} - -pub fn parse_update_v6(bytes: &[u8]) -> &UpdateV6 { - as_type(bytes) -} - -pub fn parse_update_info(bytes: &[u8]) -> &ConnectionsUpdate { - as_type(bytes) -} - -fn as_type(bytes: &[u8]) -> &T { - let ptr: *const u8 = &bytes[0]; - let t_ptr: *const T = ptr as _; - unsafe { t_ptr.as_ref().unwrap() } -} - -#[cfg(test)] -use std::fs::File; -#[cfg(test)] -use std::io::Read; -#[cfg(test)] -use std::mem::size_of; -#[cfg(test)] -use std::panic; - -#[test] -fn test_go_command_file() { - let mut file = File::open("../kext_interface/go_command_test.bin").unwrap(); - loop { - let mut command: [u8; 1] = [0]; - let bytes_count = file.read(&mut command).unwrap(); - if bytes_count == 0 { - return; - } - if let Some(command) = parse_type(&command) { - match command { - CommandType::Shutdown => {} - CommandType::Verdict => { - let mut buf = [0; size_of::()]; - let bytes_count = file.read(&mut buf).unwrap(); - if bytes_count != size_of::() { - panic!("unexpected bytes count") - } - - assert_eq!(parse_verdict(&buf), &Verdict { id: 1, verdict: 2 }) - } - CommandType::UpdateV4 => { - let mut buf = [0; size_of::()]; - let bytes_count = file.read(&mut buf).unwrap(); - if bytes_count != size_of::() { - panic!("unexpected bytes count") - } - - assert_eq!( - parse_update_v4(&buf), - &UpdateV4 { - protocol: 1, - local_address: [1, 2, 3, 4], - local_port: 2, - remote_address: [2, 3, 4, 5], - remote_port: 3, - verdict: 4 - } - ) - } - CommandType::UpdateV6 => { - let mut buf = [0; size_of::()]; - let bytes_count = file.read(&mut buf).unwrap(); - if bytes_count != size_of::() { - panic!("unexpected bytes count") - } - - assert_eq!( - parse_update_v6(&buf), - &UpdateV6 { - protocol: 1, - local_address: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - local_port: 2, - remote_address: [ - 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 - ], - remote_port: 3, - verdict: 4 - } - ) - } - CommandType::ClearCache => {} - CommandType::GetLogs => {} - CommandType::PrintMemoryStats => {} - CommandType::CleanEndedConnections => {} - CommandType::GetConnectionsUpdate => { - let mut buf = [0; size_of::()]; - let bytes_count = file.read(&mut buf).unwrap(); - if bytes_count != size_of::() { - panic!("unexpected bytes count") - } - - assert_eq!( - parse_update_info(&buf), - &ConnectionsUpdate { - timestamp: 1234567890 - } - ) - } - } - } else { - panic!("Unknown command: {}", command[0]); - } - } -} +// Commands from user space + +use num::FromPrimitive; +use num_derive::FromPrimitive; + +#[repr(u8)] +#[derive(Clone, Copy, FromPrimitive)] +#[rustfmt::skip] +pub enum CommandType { + Shutdown = 0, + Verdict = 1, + UpdateV4 = 2, + UpdateV6 = 3, + ClearCache = 4, + GetConnectionsUpdate = 5, + GetLogs = 6, + PrintMemoryStats = 7, + CleanEndedConnections = 8, +} + +#[repr(C, packed)] +pub struct Command { + pub command_type: CommandType, + value: [u8; 0], +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct Verdict { + pub id: u64, + pub verdict: u8, +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct UpdateV4 { + pub protocol: u8, + pub local_address: [u8; 4], + pub local_port: u16, + pub remote_address: [u8; 4], + pub remote_port: u16, + pub verdict: u8, +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct UpdateV6 { + pub protocol: u8, + pub local_address: [u8; 16], + pub local_port: u16, + pub remote_address: [u8; 16], + pub remote_port: u16, + pub verdict: u8, +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct ConnectionsUpdate { + pub timestamp: u64, +} + +pub fn parse_type(bytes: &[u8]) -> Option { + FromPrimitive::from_u8(bytes[0]) +} + +pub fn parse_verdict(bytes: &[u8]) -> &Verdict { + as_type(bytes) +} + +pub fn parse_update_v4(bytes: &[u8]) -> &UpdateV4 { + as_type(bytes) +} + +pub fn parse_update_v6(bytes: &[u8]) -> &UpdateV6 { + as_type(bytes) +} + +pub fn parse_update_info(bytes: &[u8]) -> &ConnectionsUpdate { + as_type(bytes) +} + +fn as_type(bytes: &[u8]) -> &T { + let ptr: *const u8 = &bytes[0]; + let t_ptr: *const T = ptr as _; + unsafe { t_ptr.as_ref().unwrap() } +} + +#[cfg(test)] +use std::fs::File; +#[cfg(test)] +use std::io::Read; +#[cfg(test)] +use std::mem::size_of; +#[cfg(test)] +use std::panic; + +#[test] +fn test_go_command_file() { + let mut file = File::open("../kext_interface/go_command_test.bin").unwrap(); + loop { + let mut command: [u8; 1] = [0]; + let bytes_count = file.read(&mut command).unwrap(); + if bytes_count == 0 { + return; + } + if let Some(command) = parse_type(&command) { + match command { + CommandType::Shutdown => {} + CommandType::Verdict => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!(parse_verdict(&buf), &Verdict { id: 1, verdict: 2 }) + } + CommandType::UpdateV4 => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!( + parse_update_v4(&buf), + &UpdateV4 { + protocol: 1, + local_address: [1, 2, 3, 4], + local_port: 2, + remote_address: [2, 3, 4, 5], + remote_port: 3, + verdict: 4 + } + ) + } + CommandType::UpdateV6 => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!( + parse_update_v6(&buf), + &UpdateV6 { + protocol: 1, + local_address: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + local_port: 2, + remote_address: [ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 + ], + remote_port: 3, + verdict: 4 + } + ) + } + CommandType::ClearCache => {} + CommandType::GetLogs => {} + CommandType::PrintMemoryStats => {} + CommandType::CleanEndedConnections => {} + CommandType::GetConnectionsUpdate => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!( + parse_update_info(&buf), + &ConnectionsUpdate { + timestamp: 1234567890 + } + ) + } + } + } else { + panic!("Unknown command: {}", command[0]); + } + } +} diff --git a/protocol/src/info.rs b/protocol/src/info.rs index 98e718a..02a243b 100644 --- a/protocol/src/info.rs +++ b/protocol/src/info.rs @@ -1,552 +1,552 @@ -use alloc::vec::Vec; - -#[repr(u8)] -#[derive(Clone, Copy)] -enum InfoType { - LogLine = 0, - ConnectionIpv4 = 1, - ConnectionIpv6 = 2, - ConnectionEndEventV4 = 3, - ConnectionEndEventV6 = 4, - ConnectionUpdateEventV4 = 5, - ConnectionUpdateEventV6 = 6, - ConnectionUpdateEnd = 7, -} - -// Fallow this pattern when adding new packets: [InfoType: u8, data_size_in_bytes: u32, data: ...] - -trait PushBytes { - fn push(self, vec: &mut Vec); -} - -impl PushBytes for u8 { - fn push(self, vec: &mut Vec) { - vec.push(self); - } -} - -impl PushBytes for InfoType { - fn push(self, vec: &mut Vec) { - vec.push(self as u8); - } -} - -impl PushBytes for u16 { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&u16::to_le_bytes(self)); - } -} - -impl PushBytes for u32 { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&u32::to_le_bytes(self)); - } -} - -impl PushBytes for u64 { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&u64::to_le_bytes(self)); - } -} - -impl PushBytes for usize { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&u64::to_le_bytes(self as u64)); - } -} - -impl PushBytes for [u8; 4] { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&self); - } -} - -impl PushBytes for [u8; 16] { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(&self); - } -} - -impl PushBytes for &[u8] { - fn push(self, vec: &mut Vec) { - vec.extend_from_slice(self); - } -} - -macro_rules! push_bytes { - ($vec:expr,$value:expr) => { - PushBytes::push($value, $vec); - }; -} - -macro_rules! get_combined_size{ - ($($a:expr),*)=>{{0 $(+core::mem::size_of_val(&$a))*}} -} - -pub struct Info(Vec); - -impl Info { - fn new(info_type: InfoType, size: usize) -> Self { - let mut vec = Vec::with_capacity(size + 5); // +1 for the info type +4 for the size. - push_bytes!(&mut vec, info_type); - push_bytes!(&mut vec, size as u32); - Self(vec) - } - - fn with_capacity(info_type: InfoType, capacity: usize) -> Self { - let mut vec = Vec::with_capacity(capacity + 5); // +1 for the info type + 4 for the size. - push_bytes!(&mut vec, info_type); - push_bytes!(&mut vec, 0 as u32); - Self(vec) - } - - #[cfg(test)] - fn assert_size(&self) { - let size = u32::from_le_bytes([self.0[1], self.0[2], self.0[3], self.0[4]]) as usize; - assert_eq!(size, self.0.len() - 5); - } - - fn update_size(&mut self) { - let size = (self.0.len() - 5) as u32; - self.0[1..5].copy_from_slice(&size.to_le_bytes()); - } - - pub fn as_bytes(&self) -> &[u8] { - return self.0.as_slice(); - } -} - -impl core::fmt::Write for Info { - fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { - const MAX_CAPACITY: usize = 500; - - let space_left = self.0.capacity() - self.0.len(); - if s.len() > space_left { - if self.0.capacity() < MAX_CAPACITY { - self.0.reserve(MAX_CAPACITY); - } else { - return Ok(()); - } - } - - self.0.extend_from_slice(s.as_bytes()); - self.update_size(); - Ok(()) - } -} - -// connection_info_v4 creates an Info packet for a connection (IPv4). -pub fn connection_info_v4( - id: u64, - process_id: u64, - direction: u8, - protocol: u8, - local_ip: [u8; 4], - remote_ip: [u8; 4], - local_port: u16, - remote_port: u16, - payload_layer: u8, - payload: &[u8], -) -> Info { - let mut size = get_combined_size!( - id, - process_id, - direction, - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - payload_layer, - payload.len() as u32 - ); - size += payload.len(); - - let mut info = Info::new(InfoType::ConnectionIpv4, size); - let vec = &mut info.0; - push_bytes!(vec, id); - push_bytes!(vec, process_id); - push_bytes!(vec, direction); - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, payload_layer); - push_bytes!(vec, payload.len() as u32); - push_bytes!(vec, payload); - info -} - -// connection_info_v6 creates an Info packet for a connection (IPv6). -pub fn connection_info_v6( - id: u64, - process_id: u64, - direction: u8, - protocol: u8, - local_ip: [u8; 16], - remote_ip: [u8; 16], - local_port: u16, - remote_port: u16, - payload_layer: u8, - payload: &[u8], -) -> Info { - let mut size = get_combined_size!( - id, - process_id, - direction, - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - payload_layer, - payload.len() as u32 - ); - size += payload.len(); - let mut info = Info::new(InfoType::ConnectionIpv6, size); - let vec = &mut info.0; - push_bytes!(vec, id); - push_bytes!(vec, process_id); - push_bytes!(vec, direction); - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, payload_layer); - push_bytes!(vec, payload.len() as u32); - if !payload.is_empty() { - push_bytes!(vec, payload); - } - info -} - -// connection_end_event_v4_info creates an Info packet for a connection end event (IPv4). -pub fn connection_end_event_v4_info( - process_id: u64, - direction: u8, - protocol: u8, - local_ip: [u8; 4], - remote_ip: [u8; 4], - local_port: u16, - remote_port: u16, - rx_bytes: u64, - rx_packets: u64, - tx_bytes: u64, - tx_packets: u64, -) -> Info { - let size = get_combined_size!( - process_id, - direction, - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - rx_bytes, - rx_packets, - tx_bytes, - tx_packets - ); - let mut info = Info::new(InfoType::ConnectionEndEventV4, size); - let vec = &mut info.0; - push_bytes!(vec, process_id); - push_bytes!(vec, direction); - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, rx_bytes); - push_bytes!(vec, rx_packets); - push_bytes!(vec, tx_bytes); - push_bytes!(vec, tx_packets); - info -} - -// connection_end_event_v6_info creates an Info packet for a connection end event (IPv6). -pub fn connection_end_event_v6_info( - process_id: u64, - direction: u8, - protocol: u8, - local_ip: [u8; 16], - remote_ip: [u8; 16], - local_port: u16, - remote_port: u16, - rx_bytes: u64, - rx_packets: u64, - tx_bytes: u64, - tx_packets: u64, -) -> Info { - let size = get_combined_size!( - process_id, - direction, - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - rx_bytes, - rx_packets, - tx_bytes, - tx_packets - ); - let mut info = Info::new(InfoType::ConnectionEndEventV6, size); - let vec = &mut info.0; - push_bytes!(vec, process_id); - push_bytes!(vec, direction); - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, rx_bytes); - push_bytes!(vec, rx_packets); - push_bytes!(vec, tx_bytes); - push_bytes!(vec, tx_packets); - info -} - -// connection_update_event_v4_info creates an Info packet for a connection update event (IPv4). -pub fn connection_update_event_v4_info( - protocol: u8, - local_ip: [u8; 4], - remote_ip: [u8; 4], - local_port: u16, - remote_port: u16, - rx_bytes: u64, - rx_packets: u64, - tx_bytes: u64, - tx_packets: u64, -) -> Info { - let size = get_combined_size!( - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - rx_bytes, - rx_packets, - tx_bytes, - tx_packets - ); - let mut info = Info::new(InfoType::ConnectionUpdateEventV4, size); - let vec = &mut info.0; - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, rx_bytes); - push_bytes!(vec, rx_packets); - push_bytes!(vec, tx_bytes); - push_bytes!(vec, tx_packets); - info -} - -// connection_update_event_v6_info creates an Info packet for a connection update event (IPv6). -pub fn connection_update_event_v6_info( - protocol: u8, - local_ip: [u8; 16], - remote_ip: [u8; 16], - local_port: u16, - remote_port: u16, - rx_bytes: u64, - rx_packets: u64, - tx_bytes: u64, - tx_packets: u64, -) -> Info { - let size = get_combined_size!( - protocol, - local_ip, - remote_ip, - local_port, - remote_port, - rx_bytes, - rx_packets, - tx_bytes, - tx_packets - ); - let mut info = Info::new(InfoType::ConnectionUpdateEventV6, size); - let vec = &mut info.0; - push_bytes!(vec, protocol); - push_bytes!(vec, local_ip); - push_bytes!(vec, remote_ip); - push_bytes!(vec, local_port); - push_bytes!(vec, remote_port); - push_bytes!(vec, rx_bytes); - push_bytes!(vec, rx_packets); - push_bytes!(vec, tx_bytes); - push_bytes!(vec, tx_packets); - info -} - -// connection_update_end_info signals the end of connection updates. -pub fn connection_update_end_info() -> Info { - let info = Info::new(InfoType::ConnectionUpdateEnd, 0); - info -} - -#[repr(u8)] -#[derive(Clone, Copy)] -pub enum Severity { - Trace = 1, - Debug = 2, - Info = 3, - Warning = 4, - Error = 5, - Critical = 6, - Disabled = 7, -} - -// log_line creates an Info packet for a log line. -pub fn log_line(severity: Severity, capacity: usize) -> Info { - let mut info = Info::with_capacity(InfoType::LogLine, capacity); - let vec = &mut info.0; - push_bytes!(vec, severity as u8); - info -} - -#[cfg(test)] -use std::fs::File; -#[cfg(test)] -use std::io::Write; - -#[cfg(test)] -use rand::seq::SliceRandom; - -#[test] -fn generate_test_info_file() -> Result<(), std::io::Error> { - let mut file = File::create("rust_info_test.bin")?; - let enums = [ - InfoType::LogLine, - InfoType::ConnectionIpv4, - InfoType::ConnectionIpv6, - InfoType::ConnectionEndEventV4, - InfoType::ConnectionEndEventV6, - InfoType::ConnectionUpdateEventV4, - InfoType::ConnectionUpdateEventV6, - InfoType::ConnectionUpdateEnd, - ]; - - let mut selected: Vec = Vec::with_capacity(1000); - let mut rng = rand::thread_rng(); - for _ in 0..selected.capacity() { - selected.push(enums.choose(&mut rng).unwrap().clone()); - } - - for value in selected { - file.write_all(&match value { - InfoType::LogLine => { - let mut info = log_line(Severity::Trace, 5); - use std::fmt::Write; - _ = write!(info, "prefix: test log"); - info.assert_size(); - info.0 - } - InfoType::ConnectionIpv4 => { - let info = connection_info_v4( - 1, - 2, - 3, - 4, - [1, 2, 3, 4], - [2, 3, 4, 5], - 5, - 6, - 7, - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionIpv6 => { - let info = connection_info_v6( - 1, - 2, - 3, - 4, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], - 5, - 6, - 7, - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionEndEventV4 => { - let info = connection_end_event_v4_info( - 1, - 2, - 3, - [1, 2, 3, 4], - [2, 3, 4, 5], - 4, - 5, - 6, - 7, - 8, - 9, - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionEndEventV6 => { - let info = connection_end_event_v6_info( - 1, - 2, - 3, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], - 4, - 5, - 6, - 7, - 8, - 9, - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionUpdateEventV4 => { - let info = connection_update_event_v4_info( - 1, - [1, 2, 3, 4], - [2, 3, 4, 5], - 2, - 3, - 4, - 5, - 6, - 7, - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionUpdateEventV6 => { - let info = connection_update_event_v6_info( - 1, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], - 2, - 3, - 4, - 5, - 6, - 7, - ); - info.assert_size(); - info.0 - } - InfoType::ConnectionUpdateEnd => { - let info = connection_update_end_info(); - info.assert_size(); - info.0 - } - })?; - } - return Ok(()); -} +use alloc::vec::Vec; + +#[repr(u8)] +#[derive(Clone, Copy)] +enum InfoType { + LogLine = 0, + ConnectionIpv4 = 1, + ConnectionIpv6 = 2, + ConnectionEndEventV4 = 3, + ConnectionEndEventV6 = 4, + ConnectionUpdateEventV4 = 5, + ConnectionUpdateEventV6 = 6, + ConnectionUpdateEnd = 7, +} + +// Fallow this pattern when adding new packets: [InfoType: u8, data_size_in_bytes: u32, data: ...] + +trait PushBytes { + fn push(self, vec: &mut Vec); +} + +impl PushBytes for u8 { + fn push(self, vec: &mut Vec) { + vec.push(self); + } +} + +impl PushBytes for InfoType { + fn push(self, vec: &mut Vec) { + vec.push(self as u8); + } +} + +impl PushBytes for u16 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u16::to_le_bytes(self)); + } +} + +impl PushBytes for u32 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u32::to_le_bytes(self)); + } +} + +impl PushBytes for u64 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u64::to_le_bytes(self)); + } +} + +impl PushBytes for usize { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u64::to_le_bytes(self as u64)); + } +} + +impl PushBytes for [u8; 4] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&self); + } +} + +impl PushBytes for [u8; 16] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&self); + } +} + +impl PushBytes for &[u8] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(self); + } +} + +macro_rules! push_bytes { + ($vec:expr,$value:expr) => { + PushBytes::push($value, $vec); + }; +} + +macro_rules! get_combined_size{ + ($($a:expr),*)=>{{0 $(+core::mem::size_of_val(&$a))*}} +} + +pub struct Info(Vec); + +impl Info { + fn new(info_type: InfoType, size: usize) -> Self { + let mut vec = Vec::with_capacity(size + 5); // +1 for the info type +4 for the size. + push_bytes!(&mut vec, info_type); + push_bytes!(&mut vec, size as u32); + Self(vec) + } + + fn with_capacity(info_type: InfoType, capacity: usize) -> Self { + let mut vec = Vec::with_capacity(capacity + 5); // +1 for the info type + 4 for the size. + push_bytes!(&mut vec, info_type); + push_bytes!(&mut vec, 0 as u32); + Self(vec) + } + + #[cfg(test)] + fn assert_size(&self) { + let size = u32::from_le_bytes([self.0[1], self.0[2], self.0[3], self.0[4]]) as usize; + assert_eq!(size, self.0.len() - 5); + } + + fn update_size(&mut self) { + let size = (self.0.len() - 5) as u32; + self.0[1..5].copy_from_slice(&size.to_le_bytes()); + } + + pub fn as_bytes(&self) -> &[u8] { + return self.0.as_slice(); + } +} + +impl core::fmt::Write for Info { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + const MAX_CAPACITY: usize = 500; + + let space_left = self.0.capacity() - self.0.len(); + if s.len() > space_left { + if self.0.capacity() < MAX_CAPACITY { + self.0.reserve(MAX_CAPACITY); + } else { + return Ok(()); + } + } + + self.0.extend_from_slice(s.as_bytes()); + self.update_size(); + Ok(()) + } +} + +// connection_info_v4 creates an Info packet for a connection (IPv4). +pub fn connection_info_v4( + id: u64, + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 4], + remote_ip: [u8; 4], + local_port: u16, + remote_port: u16, + payload_layer: u8, + payload: &[u8], +) -> Info { + let mut size = get_combined_size!( + id, + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + payload_layer, + payload.len() as u32 + ); + size += payload.len(); + + let mut info = Info::new(InfoType::ConnectionIpv4, size); + let vec = &mut info.0; + push_bytes!(vec, id); + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, payload_layer); + push_bytes!(vec, payload.len() as u32); + push_bytes!(vec, payload); + info +} + +// connection_info_v6 creates an Info packet for a connection (IPv6). +pub fn connection_info_v6( + id: u64, + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 16], + remote_ip: [u8; 16], + local_port: u16, + remote_port: u16, + payload_layer: u8, + payload: &[u8], +) -> Info { + let mut size = get_combined_size!( + id, + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + payload_layer, + payload.len() as u32 + ); + size += payload.len(); + let mut info = Info::new(InfoType::ConnectionIpv6, size); + let vec = &mut info.0; + push_bytes!(vec, id); + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, payload_layer); + push_bytes!(vec, payload.len() as u32); + if !payload.is_empty() { + push_bytes!(vec, payload); + } + info +} + +// connection_end_event_v4_info creates an Info packet for a connection end event (IPv4). +pub fn connection_end_event_v4_info( + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 4], + remote_ip: [u8; 4], + local_port: u16, + remote_port: u16, + rx_bytes: u64, + rx_packets: u64, + tx_bytes: u64, + tx_packets: u64, +) -> Info { + let size = get_combined_size!( + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + rx_bytes, + rx_packets, + tx_bytes, + tx_packets + ); + let mut info = Info::new(InfoType::ConnectionEndEventV4, size); + let vec = &mut info.0; + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, rx_bytes); + push_bytes!(vec, rx_packets); + push_bytes!(vec, tx_bytes); + push_bytes!(vec, tx_packets); + info +} + +// connection_end_event_v6_info creates an Info packet for a connection end event (IPv6). +pub fn connection_end_event_v6_info( + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 16], + remote_ip: [u8; 16], + local_port: u16, + remote_port: u16, + rx_bytes: u64, + rx_packets: u64, + tx_bytes: u64, + tx_packets: u64, +) -> Info { + let size = get_combined_size!( + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + rx_bytes, + rx_packets, + tx_bytes, + tx_packets + ); + let mut info = Info::new(InfoType::ConnectionEndEventV6, size); + let vec = &mut info.0; + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, rx_bytes); + push_bytes!(vec, rx_packets); + push_bytes!(vec, tx_bytes); + push_bytes!(vec, tx_packets); + info +} + +// connection_update_event_v4_info creates an Info packet for a connection update event (IPv4). +pub fn connection_update_event_v4_info( + protocol: u8, + local_ip: [u8; 4], + remote_ip: [u8; 4], + local_port: u16, + remote_port: u16, + rx_bytes: u64, + rx_packets: u64, + tx_bytes: u64, + tx_packets: u64, +) -> Info { + let size = get_combined_size!( + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + rx_bytes, + rx_packets, + tx_bytes, + tx_packets + ); + let mut info = Info::new(InfoType::ConnectionUpdateEventV4, size); + let vec = &mut info.0; + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, rx_bytes); + push_bytes!(vec, rx_packets); + push_bytes!(vec, tx_bytes); + push_bytes!(vec, tx_packets); + info +} + +// connection_update_event_v6_info creates an Info packet for a connection update event (IPv6). +pub fn connection_update_event_v6_info( + protocol: u8, + local_ip: [u8; 16], + remote_ip: [u8; 16], + local_port: u16, + remote_port: u16, + rx_bytes: u64, + rx_packets: u64, + tx_bytes: u64, + tx_packets: u64, +) -> Info { + let size = get_combined_size!( + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + rx_bytes, + rx_packets, + tx_bytes, + tx_packets + ); + let mut info = Info::new(InfoType::ConnectionUpdateEventV6, size); + let vec = &mut info.0; + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, rx_bytes); + push_bytes!(vec, rx_packets); + push_bytes!(vec, tx_bytes); + push_bytes!(vec, tx_packets); + info +} + +// connection_update_end_info signals the end of connection updates. +pub fn connection_update_end_info() -> Info { + let info = Info::new(InfoType::ConnectionUpdateEnd, 0); + info +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum Severity { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Disabled = 7, +} + +// log_line creates an Info packet for a log line. +pub fn log_line(severity: Severity, capacity: usize) -> Info { + let mut info = Info::with_capacity(InfoType::LogLine, capacity); + let vec = &mut info.0; + push_bytes!(vec, severity as u8); + info +} + +#[cfg(test)] +use std::fs::File; +#[cfg(test)] +use std::io::Write; + +#[cfg(test)] +use rand::seq::SliceRandom; + +#[test] +fn generate_test_info_file() -> Result<(), std::io::Error> { + let mut file = File::create("rust_info_test.bin")?; + let enums = [ + InfoType::LogLine, + InfoType::ConnectionIpv4, + InfoType::ConnectionIpv6, + InfoType::ConnectionEndEventV4, + InfoType::ConnectionEndEventV6, + InfoType::ConnectionUpdateEventV4, + InfoType::ConnectionUpdateEventV6, + InfoType::ConnectionUpdateEnd, + ]; + + let mut selected: Vec = Vec::with_capacity(1000); + let mut rng = rand::thread_rng(); + for _ in 0..selected.capacity() { + selected.push(enums.choose(&mut rng).unwrap().clone()); + } + + for value in selected { + file.write_all(&match value { + InfoType::LogLine => { + let mut info = log_line(Severity::Trace, 5); + use std::fmt::Write; + _ = write!(info, "prefix: test log"); + info.assert_size(); + info.0 + } + InfoType::ConnectionIpv4 => { + let info = connection_info_v4( + 1, + 2, + 3, + 4, + [1, 2, 3, 4], + [2, 3, 4, 5], + 5, + 6, + 7, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionIpv6 => { + let info = connection_info_v6( + 1, + 2, + 3, + 4, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 5, + 6, + 7, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionEndEventV4 => { + let info = connection_end_event_v4_info( + 1, + 2, + 3, + [1, 2, 3, 4], + [2, 3, 4, 5], + 4, + 5, + 6, + 7, + 8, + 9, + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionEndEventV6 => { + let info = connection_end_event_v6_info( + 1, + 2, + 3, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 4, + 5, + 6, + 7, + 8, + 9, + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionUpdateEventV4 => { + let info = connection_update_event_v4_info( + 1, + [1, 2, 3, 4], + [2, 3, 4, 5], + 2, + 3, + 4, + 5, + 6, + 7, + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionUpdateEventV6 => { + let info = connection_update_event_v6_info( + 1, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 2, + 3, + 4, + 5, + 6, + 7, + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionUpdateEnd => { + let info = connection_update_end_info(); + info.assert_size(); + info.0 + } + })?; + } + return Ok(()); +} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index e55fb31..d4d09d5 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(not(test), no_std)] -extern crate alloc; - -pub mod command; -pub mod info; +#![cfg_attr(not(test), no_std)] +extern crate alloc; + +pub mod command; +pub mod info; diff --git a/wdk/build.rs b/wdk/build.rs index 5c8b91d..3fe4d81 100644 --- a/wdk/build.rs +++ b/wdk/build.rs @@ -1,17 +1,17 @@ -#[cfg(target_arch = "x86_64")] -fn main() { - println!("cargo:rustc-link-search=native=../libs/x64"); - - // C Helper - println!("cargo:rerun-if-changed=../c_helper/x64/c_helper.lib"); - println!("cargo:rustc-link-search=native=../c_helper/x64"); -} - -#[cfg(target_arch = "aarch64")] -fn main() { - println!("cargo:rustc-link-search=native=../libs/arm64"); - - // C Helper - println!("cargo:rerun-if-changed=../c_helper/ARM64/c_helper.lib"); - println!("cargo:rustc-link-search=native=../c_helper/ARM64"); -} +#[cfg(target_arch = "x86_64")] +fn main() { + println!("cargo:rustc-link-search=native=../libs/x64"); + + // C Helper + println!("cargo:rerun-if-changed=../c_helper/x64/c_helper.lib"); + println!("cargo:rustc-link-search=native=../c_helper/x64"); +} + +#[cfg(target_arch = "aarch64")] +fn main() { + println!("cargo:rustc-link-search=native=../libs/arm64"); + + // C Helper + println!("cargo:rerun-if-changed=../c_helper/ARM64/c_helper.lib"); + println!("cargo:rustc-link-search=native=../c_helper/ARM64"); +} diff --git a/wdk/src/allocator.rs b/wdk/src/allocator.rs index 5b1ae8e..de7cf51 100644 --- a/wdk/src/allocator.rs +++ b/wdk/src/allocator.rs @@ -1,48 +1,48 @@ -extern crate alloc; - -use core::alloc::{GlobalAlloc, Layout}; - -use alloc::alloc::handle_alloc_error; -use windows_sys::Wdk::System::SystemServices::{ExAllocatePool2, ExFreePoolWithTag}; - -// For reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/pool_flags -#[allow(dead_code)] -#[repr(u64)] -enum PoolType { - RequiredStartUseQuota = 0x0000000000000001, - Uninitialized = 0x0000000000000002, // Don't zero-initialize allocation - Session = 0x0000000000000004, // Use session specific pool - CacheAligned = 0x0000000000000008, // Cache aligned allocation - RaiseOnFailure = 0x0000000000000020, // Raise exception on failure - NonPaged = 0x0000000000000040, // Non paged pool NX - NonPagedExecute = 0x0000000000000080, // Non paged pool executable - Paged = 0x0000000000000100, // Paged pool - RequiredEnd = 0x0000000080000000, - OptionalStart = 0x0000000100000000, - OptionalEnd = 0x8000000000000000, -} - -pub struct WindowsAllocator {} - -unsafe impl Sync for WindowsAllocator {} - -pub(crate) const POOL_TAG: u32 = u32::from_ne_bytes(*b"znfc"); - -unsafe impl GlobalAlloc for WindowsAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let pool = ExAllocatePool2(PoolType::NonPaged as u64, layout.size(), POOL_TAG); - if pool.is_null() { - handle_alloc_error(layout); - } - - pool as *mut u8 - } - - unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { - ExFreePoolWithTag(ptr as _, POOL_TAG); - } - - unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - return self.alloc(layout); - } -} +extern crate alloc; + +use core::alloc::{GlobalAlloc, Layout}; + +use alloc::alloc::handle_alloc_error; +use windows_sys::Wdk::System::SystemServices::{ExAllocatePool2, ExFreePoolWithTag}; + +// For reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/pool_flags +#[allow(dead_code)] +#[repr(u64)] +enum PoolType { + RequiredStartUseQuota = 0x0000000000000001, + Uninitialized = 0x0000000000000002, // Don't zero-initialize allocation + Session = 0x0000000000000004, // Use session specific pool + CacheAligned = 0x0000000000000008, // Cache aligned allocation + RaiseOnFailure = 0x0000000000000020, // Raise exception on failure + NonPaged = 0x0000000000000040, // Non paged pool NX + NonPagedExecute = 0x0000000000000080, // Non paged pool executable + Paged = 0x0000000000000100, // Paged pool + RequiredEnd = 0x0000000080000000, + OptionalStart = 0x0000000100000000, + OptionalEnd = 0x8000000000000000, +} + +pub struct WindowsAllocator {} + +unsafe impl Sync for WindowsAllocator {} + +pub(crate) const POOL_TAG: u32 = u32::from_ne_bytes(*b"znfc"); + +unsafe impl GlobalAlloc for WindowsAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let pool = ExAllocatePool2(PoolType::NonPaged as u64, layout.size(), POOL_TAG); + if pool.is_null() { + handle_alloc_error(layout); + } + + pool as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { + ExFreePoolWithTag(ptr as _, POOL_TAG); + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + return self.alloc(layout); + } +} diff --git a/wdk/src/consts.rs b/wdk/src/consts.rs index 5fa3b1e..78e7c1f 100644 --- a/wdk/src/consts.rs +++ b/wdk/src/consts.rs @@ -1,62 +1,62 @@ -// Actions -pub const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; -pub const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; -pub const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; - -pub const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; -pub const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; -pub const FWP_ACTION_CALLOUT_TERMINATING: u32 = - 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; -pub const FWP_ACTION_CALLOUT_INSPECTION: u32 = - 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; -pub const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; -pub const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; -pub const FWP_ACTION_NONE: u32 = 0x00000007; -pub const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; -pub const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; -pub const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; -pub const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; -pub const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; -pub const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; -pub const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; -pub const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; -pub const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; -pub const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; -pub const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; -pub const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; -pub const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; -pub const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; -pub const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; -pub const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; -pub const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; -pub const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; -pub const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; - -// Filter engine status codes. -/// Returned by `FwpmTransactionBegin0` when a transaction is already in progress on the same -/// filter engine session. Nothing was changed, so the call can simply be repeated later. -pub const STATUS_FWP_TXN_IN_PROGRESS: i32 = 0xC022000Eu32 as i32; - -// Processor modes (KPROCESSOR_MODE) -pub const KERNEL_MODE: i8 = 0; - -// Interrupt request levels (KIRQL) -pub const PASSIVE_LEVEL: u8 = 0; -pub const APC_LEVEL: u8 = 1; - -// Driver -pub const METHOD_BUFFERED: u32 = 0; -pub const METHOD_IN_DIRECT: u32 = 1; -pub const METHOD_OUT_DIRECT: u32 = 2; -pub const METHOD_NEITHER: u32 = 3; - -pub const SIOCTL_TYPE: u32 = 40000; - -pub const FILE_READ_DATA: u32 = 0x00000001; -pub const FILE_READ_ATTRIBUTES: u32 = 0x00000080; -pub const FILE_READ_EA: u32 = 0x00000008; -pub const FILE_WRITE_DATA: u32 = 0x00000002; -pub const FILE_WRITE_ATTRIBUTES: u32 = 0x00000100; -pub const FILE_WRITE_EA: u32 = 0x00000010; -pub const FILE_APPEND_DATA: u32 = 0x00000004; -pub const FILE_EXECUTE: u32 = 0x00000020; +// Actions +pub const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; +pub const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; +pub const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; + +pub const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_CALLOUT_TERMINATING: u32 = + 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_CALLOUT_INSPECTION: u32 = + 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; +pub const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; +pub const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; +pub const FWP_ACTION_NONE: u32 = 0x00000007; +pub const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; +pub const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; +pub const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; +pub const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; +pub const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; +pub const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; +pub const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; +pub const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; +pub const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; +pub const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; +pub const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; +pub const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; +pub const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; +pub const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; +pub const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; +pub const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; +pub const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; +pub const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; +pub const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; + +// Filter engine status codes. +/// Returned by `FwpmTransactionBegin0` when a transaction is already in progress on the same +/// filter engine session. Nothing was changed, so the call can simply be repeated later. +pub const STATUS_FWP_TXN_IN_PROGRESS: i32 = 0xC022000Eu32 as i32; + +// Processor modes (KPROCESSOR_MODE) +pub const KERNEL_MODE: i8 = 0; + +// Interrupt request levels (KIRQL) +pub const PASSIVE_LEVEL: u8 = 0; +pub const APC_LEVEL: u8 = 1; + +// Driver +pub const METHOD_BUFFERED: u32 = 0; +pub const METHOD_IN_DIRECT: u32 = 1; +pub const METHOD_OUT_DIRECT: u32 = 2; +pub const METHOD_NEITHER: u32 = 3; + +pub const SIOCTL_TYPE: u32 = 40000; + +pub const FILE_READ_DATA: u32 = 0x00000001; +pub const FILE_READ_ATTRIBUTES: u32 = 0x00000080; +pub const FILE_READ_EA: u32 = 0x00000008; +pub const FILE_WRITE_DATA: u32 = 0x00000002; +pub const FILE_WRITE_ATTRIBUTES: u32 = 0x00000100; +pub const FILE_WRITE_EA: u32 = 0x00000010; +pub const FILE_APPEND_DATA: u32 = 0x00000004; +pub const FILE_EXECUTE: u32 = 0x00000020; diff --git a/wdk/src/debug.rs b/wdk/src/debug.rs index bc8ef76..835c154 100644 --- a/wdk/src/debug.rs +++ b/wdk/src/debug.rs @@ -1,33 +1,33 @@ -#[cfg(debug_assertions)] -#[macro_export] -macro_rules! log { - ($level:expr, $($arg:tt)*) => ({ - let message = alloc::format!($($arg)*); - $crate::interface::dbg_print(alloc::format!("{} {}: {}\n", $level, core::module_path!(), message)); - }); -} - -#[cfg(not(debug_assertions))] -#[macro_export] -macro_rules! log { - ($($arg:expr),*) => {{ - $( - _ = $arg; - )* - }}; -} - -#[macro_export] -macro_rules! err { - ($($arg:tt)*) => ($crate::log!("ERROR", $($arg)*)); -} - -#[macro_export] -macro_rules! dbg { - ($($arg:tt)*) => ($crate::log!("DEBUG", $($arg)*)); -} - -#[macro_export] -macro_rules! info { - ($($arg:tt)*) => ($crate::log!("INFO", $($arg)*)); -} +#[cfg(debug_assertions)] +#[macro_export] +macro_rules! log { + ($level:expr, $($arg:tt)*) => ({ + let message = alloc::format!($($arg)*); + $crate::interface::dbg_print(alloc::format!("{} {}: {}\n", $level, core::module_path!(), message)); + }); +} + +#[cfg(not(debug_assertions))] +#[macro_export] +macro_rules! log { + ($($arg:expr),*) => {{ + $( + _ = $arg; + )* + }}; +} + +#[macro_export] +macro_rules! err { + ($($arg:tt)*) => ($crate::log!("ERROR", $($arg)*)); +} + +#[macro_export] +macro_rules! dbg { + ($($arg:tt)*) => ($crate::log!("DEBUG", $($arg)*)); +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ($crate::log!("INFO", $($arg)*)); +} diff --git a/wdk/src/driver.rs b/wdk/src/driver.rs index 46d2fcb..a8b7440 100644 --- a/wdk/src/driver.rs +++ b/wdk/src/driver.rs @@ -1,103 +1,103 @@ -use windows_sys::{ - Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}, - Win32::Foundation::{HANDLE, NTSTATUS}, -}; - -use crate::{ - interface, - irp_helpers::{ReadRequest, WriteRequest}, -}; - -pub trait Device { - fn new(driver: &Driver) -> Self; - fn cleanup(&mut self); - fn read(&mut self, read_request: &mut ReadRequest); - fn write(&mut self, write_request: &mut WriteRequest); - fn shutdown(&mut self); -} - -pub struct Driver { - _device_handle: HANDLE, - driver_object: *mut DRIVER_OBJECT, - device_object: *mut DEVICE_OBJECT, -} -unsafe impl Sync for Driver {} - -// This is a workaround for current state of wdk bindings. -// TODO: replace with official version when they are correct: https://github.com/microsoft/wdkmetadata/issues/59 -pub type UnloadFnType = unsafe extern "system" fn(driver_object: *const DRIVER_OBJECT); -pub type MjFnType = unsafe extern "system" fn(&mut DEVICE_OBJECT, &mut IRP) -> NTSTATUS; - -impl Driver { - pub(crate) fn new( - driver_object: *mut DRIVER_OBJECT, - _driver_handle: HANDLE, - device_handle: HANDLE, - ) -> Driver { - return Driver { - // driver_handle, - _device_handle: device_handle, - driver_object, - device_object: interface::wdf_device_wdm_get_device_object(device_handle), - }; - } - - pub fn get_device_object(&self) -> *mut DEVICE_OBJECT { - return self.device_object; - } - - pub fn get_device_object_ref(&self) -> Option<&mut DEVICE_OBJECT> { - return unsafe { self.device_object.as_mut() }; - } - - pub fn set_driver_unload(&mut self, driver_unload: UnloadFnType) { - if let Some(driver) = unsafe { self.driver_object.as_mut() } { - driver.DriverUnload = Some(unsafe { core::mem::transmute(driver_unload) }) - } - } - - pub fn set_read_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn(windows_sys::Wdk::System::SystemServices::IRP_MJ_READ, mj_fn); - } - - pub fn set_write_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn( - windows_sys::Wdk::System::SystemServices::IRP_MJ_WRITE, - mj_fn, - ); - } - - pub fn set_create_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn( - windows_sys::Wdk::System::SystemServices::IRP_MJ_CREATE, - mj_fn, - ); - } - - pub fn set_device_control_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn( - windows_sys::Wdk::System::SystemServices::IRP_MJ_DEVICE_CONTROL, - mj_fn, - ); - } - - pub fn set_close_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn( - windows_sys::Wdk::System::SystemServices::IRP_MJ_CLOSE, - mj_fn, - ); - } - - pub fn set_cleanup_fn(&mut self, mj_fn: MjFnType) { - self.set_major_fn( - windows_sys::Wdk::System::SystemServices::IRP_MJ_CLEANUP, - mj_fn, - ); - } - - fn set_major_fn(&mut self, fn_index: u32, mj_fn: MjFnType) { - if let Some(driver) = unsafe { self.driver_object.as_mut() } { - driver.MajorFunction[fn_index as usize] = Some(unsafe { core::mem::transmute(mj_fn) }) - } - } -} +use windows_sys::{ + Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}, + Win32::Foundation::{HANDLE, NTSTATUS}, +}; + +use crate::{ + interface, + irp_helpers::{ReadRequest, WriteRequest}, +}; + +pub trait Device { + fn new(driver: &Driver) -> Self; + fn cleanup(&mut self); + fn read(&mut self, read_request: &mut ReadRequest); + fn write(&mut self, write_request: &mut WriteRequest); + fn shutdown(&mut self); +} + +pub struct Driver { + _device_handle: HANDLE, + driver_object: *mut DRIVER_OBJECT, + device_object: *mut DEVICE_OBJECT, +} +unsafe impl Sync for Driver {} + +// This is a workaround for current state of wdk bindings. +// TODO: replace with official version when they are correct: https://github.com/microsoft/wdkmetadata/issues/59 +pub type UnloadFnType = unsafe extern "system" fn(driver_object: *const DRIVER_OBJECT); +pub type MjFnType = unsafe extern "system" fn(&mut DEVICE_OBJECT, &mut IRP) -> NTSTATUS; + +impl Driver { + pub(crate) fn new( + driver_object: *mut DRIVER_OBJECT, + _driver_handle: HANDLE, + device_handle: HANDLE, + ) -> Driver { + return Driver { + // driver_handle, + _device_handle: device_handle, + driver_object, + device_object: interface::wdf_device_wdm_get_device_object(device_handle), + }; + } + + pub fn get_device_object(&self) -> *mut DEVICE_OBJECT { + return self.device_object; + } + + pub fn get_device_object_ref(&self) -> Option<&mut DEVICE_OBJECT> { + return unsafe { self.device_object.as_mut() }; + } + + pub fn set_driver_unload(&mut self, driver_unload: UnloadFnType) { + if let Some(driver) = unsafe { self.driver_object.as_mut() } { + driver.DriverUnload = Some(unsafe { core::mem::transmute(driver_unload) }) + } + } + + pub fn set_read_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn(windows_sys::Wdk::System::SystemServices::IRP_MJ_READ, mj_fn); + } + + pub fn set_write_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_WRITE, + mj_fn, + ); + } + + pub fn set_create_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CREATE, + mj_fn, + ); + } + + pub fn set_device_control_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_DEVICE_CONTROL, + mj_fn, + ); + } + + pub fn set_close_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CLOSE, + mj_fn, + ); + } + + pub fn set_cleanup_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CLEANUP, + mj_fn, + ); + } + + fn set_major_fn(&mut self, fn_index: u32, mj_fn: MjFnType) { + if let Some(driver) = unsafe { self.driver_object.as_mut() } { + driver.MajorFunction[fn_index as usize] = Some(unsafe { core::mem::transmute(mj_fn) }) + } + } +} diff --git a/wdk/src/error.rs b/wdk/src/error.rs index ef3665b..e9d2900 100644 --- a/wdk/src/error.rs +++ b/wdk/src/error.rs @@ -1,9 +1,9 @@ -// use anyhow::anyhow; - -// pub fn anyhow_ntstatus(status: i32) -> anyhow::Error { -// if let Some(value) = ntstatus::ntstatus::NtStatus::from_u32(status as u32) { -// return anyhow!(value); -// } - -// return anyhow!("UNKNOWN_NTSTATUS_CODE"); -// } +// use anyhow::anyhow; + +// pub fn anyhow_ntstatus(status: i32) -> anyhow::Error { +// if let Some(value) = ntstatus::ntstatus::NtStatus::from_u32(status as u32) { +// return anyhow!(value); +// } + +// return anyhow!("UNKNOWN_NTSTATUS_CODE"); +// } diff --git a/wdk/src/fast_mutex.rs b/wdk/src/fast_mutex.rs index cf15546..d762d8d 100644 --- a/wdk/src/fast_mutex.rs +++ b/wdk/src/fast_mutex.rs @@ -1,143 +1,143 @@ -use alloc::boxed::Box; -use core::{ - cell::UnsafeCell, - ops::{Deref, DerefMut}, -}; -use windows_sys::{ - Wdk::{ - Foundation::{FAST_MUTEX, KEVENT}, - System::SystemServices::FM_LOCK_BIT, - }, - Win32::System::Kernel::{SynchronizationEvent, EVENT_TYPE}, -}; - -#[link(name = "NtosKrnl", kind = "static")] -extern "C" { - fn KeInitializeEvent(event: *mut KEVENT, event_type: EVENT_TYPE, state: bool); - - /// The ExAcquireFastMutex routine acquires the given fast mutex with APCs to the current thread disabled. - fn ExAcquireFastMutex(kmutex: *mut FAST_MUTEX); - - /// The ExTryToAcquireFastMutex routine acquires the given fast mutex, if possible, with APCs to the current thread disabled. - fn ExTryToAcquireFastMutex(kmutex: *mut FAST_MUTEX) -> bool; - - // The ExReleaseFastMutex routine releases ownership of a fast mutex that was acquired with ExAcquireFastMutex or ExTryToAcquireFastMutex. - fn ExReleaseFastMutex(kmutex: *mut FAST_MUTEX); -} - -/// The ExInitializeFastMutex routine initializes a fast mutex variable, used to synchronize mutually exclusive access by a set of threads to a shared resource. -/// ExInitializeFastMutex must be called before any calls to other ExXxxFastMutex routines occur. -#[allow(non_snake_case)] -unsafe fn ExInitializeFastMutex(kmutex: *mut FAST_MUTEX) { - core::ptr::write_volatile(&mut (*kmutex).Count, FM_LOCK_BIT as i32); - // (*kmutex).Count = FM_LOCK_BIT as i32; - - (*kmutex).Owner = core::ptr::null_mut(); - (*kmutex).Contention = 0; - KeInitializeEvent(&mut (*kmutex).Event, SynchronizationEvent, false) -} - -pub struct FastMutex { - kmutex: UnsafeCell>, - val: UnsafeCell, -} - -impl FastMutex { - pub const fn default(val: T) -> Self { - Self { - kmutex: UnsafeCell::new(None), - val: UnsafeCell::new(val), - } - } - - pub fn init(&self) { - let mutex = Box::into_raw(Box::new(unsafe { - MaybeUninit::zeroed().assume_init(); - })); - unsafe { - ExInitializeFastMutex(mutex); - *self.kmutex.get() = Some(mutex); - } - } - - pub fn deinit(&self) { - unsafe { - let opt = &mut (*self.kmutex.get()); - if let Some(mutex) = opt { - _ = Box::from_raw(mutex); - } - opt.take(); - } - } - - pub fn lock(&self) -> Result, ()> { - unsafe { - if let Some(mutex) = *self.kmutex.get() { - ExAcquireFastMutex(mutex); - return Ok(LockGuard::new(self)); - } - } - - return Err(()); - } - - pub fn try_lock(&self) -> Option> { - unsafe { - if let Some(mutex) = *self.kmutex.get() { - ExTryToAcquireFastMutex(mutex); - return Some(LockGuard::new(self)); - } - } - return None; - } - - fn get<'a>(&self) -> *mut T { - self.val.get() - } - - fn unlock(&self) { - unsafe { - if let Some(mutex) = *self.kmutex.get() { - ExReleaseFastMutex(mutex); - } else { - panic!("Mutex not initialized"); - } - } - } -} - -impl Drop for FastMutex { - fn drop(&mut self) { - self.deinit(); - } -} - -pub struct LockGuard<'a, T> { - mutex: &'a FastMutex, -} - -impl<'a, T> LockGuard<'a, T> { - fn new(mutex: &'a FastMutex) -> Self { - return LockGuard { mutex }; - } -} - -impl<'a, T> Drop for LockGuard<'a, T> { - fn drop(&mut self) { - self.mutex.unlock(); - } -} - -impl<'a, T> Deref for LockGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { &*self.mutex.get() } - } -} - -impl<'a, T> DerefMut for LockGuard<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.mutex.get() } - } -} +use alloc::boxed::Box; +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, +}; +use windows_sys::{ + Wdk::{ + Foundation::{FAST_MUTEX, KEVENT}, + System::SystemServices::FM_LOCK_BIT, + }, + Win32::System::Kernel::{SynchronizationEvent, EVENT_TYPE}, +}; + +#[link(name = "NtosKrnl", kind = "static")] +extern "C" { + fn KeInitializeEvent(event: *mut KEVENT, event_type: EVENT_TYPE, state: bool); + + /// The ExAcquireFastMutex routine acquires the given fast mutex with APCs to the current thread disabled. + fn ExAcquireFastMutex(kmutex: *mut FAST_MUTEX); + + /// The ExTryToAcquireFastMutex routine acquires the given fast mutex, if possible, with APCs to the current thread disabled. + fn ExTryToAcquireFastMutex(kmutex: *mut FAST_MUTEX) -> bool; + + // The ExReleaseFastMutex routine releases ownership of a fast mutex that was acquired with ExAcquireFastMutex or ExTryToAcquireFastMutex. + fn ExReleaseFastMutex(kmutex: *mut FAST_MUTEX); +} + +/// The ExInitializeFastMutex routine initializes a fast mutex variable, used to synchronize mutually exclusive access by a set of threads to a shared resource. +/// ExInitializeFastMutex must be called before any calls to other ExXxxFastMutex routines occur. +#[allow(non_snake_case)] +unsafe fn ExInitializeFastMutex(kmutex: *mut FAST_MUTEX) { + core::ptr::write_volatile(&mut (*kmutex).Count, FM_LOCK_BIT as i32); + // (*kmutex).Count = FM_LOCK_BIT as i32; + + (*kmutex).Owner = core::ptr::null_mut(); + (*kmutex).Contention = 0; + KeInitializeEvent(&mut (*kmutex).Event, SynchronizationEvent, false) +} + +pub struct FastMutex { + kmutex: UnsafeCell>, + val: UnsafeCell, +} + +impl FastMutex { + pub const fn default(val: T) -> Self { + Self { + kmutex: UnsafeCell::new(None), + val: UnsafeCell::new(val), + } + } + + pub fn init(&self) { + let mutex = Box::into_raw(Box::new(unsafe { + MaybeUninit::zeroed().assume_init(); + })); + unsafe { + ExInitializeFastMutex(mutex); + *self.kmutex.get() = Some(mutex); + } + } + + pub fn deinit(&self) { + unsafe { + let opt = &mut (*self.kmutex.get()); + if let Some(mutex) = opt { + _ = Box::from_raw(mutex); + } + opt.take(); + } + } + + pub fn lock(&self) -> Result, ()> { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExAcquireFastMutex(mutex); + return Ok(LockGuard::new(self)); + } + } + + return Err(()); + } + + pub fn try_lock(&self) -> Option> { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExTryToAcquireFastMutex(mutex); + return Some(LockGuard::new(self)); + } + } + return None; + } + + fn get<'a>(&self) -> *mut T { + self.val.get() + } + + fn unlock(&self) { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExReleaseFastMutex(mutex); + } else { + panic!("Mutex not initialized"); + } + } + } +} + +impl Drop for FastMutex { + fn drop(&mut self) { + self.deinit(); + } +} + +pub struct LockGuard<'a, T> { + mutex: &'a FastMutex, +} + +impl<'a, T> LockGuard<'a, T> { + fn new(mutex: &'a FastMutex) -> Self { + return LockGuard { mutex }; + } +} + +impl<'a, T> Drop for LockGuard<'a, T> { + fn drop(&mut self) { + self.mutex.unlock(); + } +} + +impl<'a, T> Deref for LockGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.mutex.get() } + } +} + +impl<'a, T> DerefMut for LockGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.mutex.get() } + } +} diff --git a/wdk/src/ffi.rs b/wdk/src/ffi.rs index e05bfef..a00145e 100644 --- a/wdk/src/ffi.rs +++ b/wdk/src/ffi.rs @@ -1,559 +1,559 @@ -use core::ffi::c_void; - -use windows_sys::{ - core::{GUID, PCWSTR}, - Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, MDL}, - Win32::{ - Foundation::{HANDLE, NTSTATUS, UNICODE_STRING}, - NetworkManagement::WindowsFilteringPlatform::{ - FWPM_PROVIDER_CONTEXT2, FWP_CONDITION_VALUE0, FWP_MATCH_TYPE, FWP_VALUE0, - }, - Networking::WinSock::{ADDRESS_FAMILY, SCOPE_ID}, - System::Kernel::COMPARTMENT_ID, - }, -}; - -use crate::filter_engine::{ - classify::ClassifyOut, layer::IncomingValues, metadata::FwpsIncomingMetadataValues, -}; - -#[allow(non_camel_case_types, non_snake_case)] -pub(crate) type FwpsCalloutClassifyFn = unsafe extern "C" fn( - inFixedValues: *const IncomingValues, - inMetaValues: *const FwpsIncomingMetadataValues, - layerData: *mut c_void, - classifyContext: *mut c_void, - filter: *const FWPS_FILTER2, - flowContext: u64, - classifyOut: *mut ClassifyOut, -); - -#[allow(non_camel_case_types, non_snake_case)] -pub(crate) type FwpsCalloutNotifyFn = unsafe extern "C" fn( - notifyType: u32, - filterKey: *const GUID, - filter: *mut FWPS_FILTER2, -) -> NTSTATUS; - -#[allow(non_camel_case_types, non_snake_case)] -pub(crate) type FwpsCalloutFlowDeleteNotifyFn = - unsafe extern "C" fn(layerId: u16, calloutId: u32, flowContext: u64); - -/// The FWPS_ACTION0 structure specifies the run-time action that the filter engine takes if all of the filter's filtering conditions are true. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct FWPS_ACTION0 { - r#type: u32, - calloutId: u32, -} - -/// The FWPS_FILTER_CONDITION0 structure defines a run-time filtering condition for a filter. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct FWPS_FILTER_CONDITION0 { - fieldId: u16, - reserved: u16, - matchType: FWP_MATCH_TYPE, - conditionValue: FWP_CONDITION_VALUE0, -} - -/// The WdfExecutionLevel enumeration type specifies the maximum IRQL at which the framework will call the event callback functions that a driver has supplied for a framework object. -#[repr(C)] -enum WdfExecutionLevel { - Invalid = 0, - InheritFromParent, - Passive, - Dispatch, -} - -/// The WDF_SYNCHRONIZATION_SCOPE enumeration type specifies how the framework will synchronize execution of an object's event callback functions. -#[repr(C)] -enum WdfSynchronizationScope { - Invalid = 0x00, - InheritFromParent, - Device, - Queue, - None, -} - -unsafe impl Sync for WdfObjectContextTypeInfo {} - -/// The FWPS_FILTER2 structure defines a run-time filter in the filter engine. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct FWPS_FILTER2 { - pub(crate) filterId: u64, - pub(crate) weight: FWP_VALUE0, - pub(crate) subLayerWeight: u16, - pub(crate) flags: u16, - pub(crate) numFilterConditions: u32, - pub(crate) filterCondition: *mut FWPS_FILTER_CONDITION0, - pub(crate) action: FWPS_ACTION0, - pub(crate) context: u64, - pub(crate) providerContext: *mut FWPM_PROVIDER_CONTEXT2, -} - -/// The FWPS_CALLOUT3 structure defines the data that is required for a callout driver to register a callout with the filter engine. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct FWPS_CALLOUT3 { - pub(crate) calloutKey: GUID, - pub(crate) flags: u32, - pub(crate) classifyFn: Option, - pub(crate) notifyFn: Option, - pub(crate) flowDeleteFn: Option, -} - -/// The filter engine calls a callout's completionFn callout function whenever packet data, described by the netBufferList parameter in one of the packet injection functions, has been injected into the network stack. -#[allow(non_camel_case_types)] -type FWPS_INJECT_COMPLETE0 = unsafe extern "C" fn( - context: *mut c_void, - net_buffer_list: *mut NET_BUFFER_LIST, - dispatch_level: bool, -); - -/// The FWPS_TRANSPORT_SEND_PARAMS1 structure defines properties of an outbound transport layer packet. -#[allow(non_camel_case_types)] -#[repr(C)] -pub(crate) struct FWPS_TRANSPORT_SEND_PARAMS1 { - pub(crate) remote_address: *const u8, - pub(crate) remote_scope_id: SCOPE_ID, - pub(crate) control_data: *mut c_void, //WSACMSGHDR, - pub(crate) control_data_length: u32, - pub(crate) header_include_header: *mut u8, - pub(crate) header_include_header_length: u32, -} - -/// The FWPS_PACKET_INJECTION_STATE enumeration type specifies the injection state of a network buffer list. -#[allow(non_camel_case_types)] -#[repr(C)] -pub(crate) enum FWPS_PACKET_INJECTION_STATE { - FWPS_PACKET_NOT_INJECTED, - FWPS_PACKET_INJECTED_BY_SELF, - FWPS_PACKET_INJECTED_BY_OTHER, - FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF, - FWPS_PACKET_INJECTION_STATE_MAX, -} - -pub(crate) const FWPS_INJECTION_TYPE_STREAM: u32 = 0x00000001; -pub(crate) const FWPS_INJECTION_TYPE_TRANSPORT: u32 = 0x00000002; -pub(crate) const FWPS_INJECTION_TYPE_NETWORK: u32 = 0x00000004; -pub(crate) const FWPS_INJECTION_TYPE_FORWARD: u32 = 0x00000008; -pub(crate) const FWPS_INJECTION_TYPE_L2: u32 = 0x00000010; -pub(crate) const FWPS_INJECTION_TYPE_VSWITCH_TRANSPORT: u32 = 0x00000020; - -pub(crate) const NDIS_OBJECT_TYPE_DEFAULT: u8 = 0x80; // used when object type is implicit in the API call -pub(crate) const NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1: u8 = 1; - -/// The NBListHeader is the header of NET_BUFFER_LIST struct. -#[repr(C)] -pub(crate) struct NBListHeader { - pub(crate) next: *mut NET_BUFFER_LIST, - pub(crate) first_net_buffer: *mut NET_BUFFER, -} - -/// The NET_BUFFER_LIST structure specifies a linked list of NET_BUFFER structures. -/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub struct NET_BUFFER_LIST { - pub(crate) Header: NBListHeader, - pub(crate) Context: *mut c_void, - pub(crate) ParentNetBufferList: *mut NET_BUFFER_LIST, - pub(crate) NdisPoolHandle: NDIS_HANDLE, - pub(crate) NdisReserved: [*mut c_void; 2], - pub(crate) ProtocolReserved: [*mut c_void; 4], - pub(crate) MiniportReserved: [*mut c_void; 2], - pub(crate) Scratch: *mut c_void, - pub(crate) SourceHandle: NDIS_HANDLE, - pub(crate) NblFlags: u32, - pub(crate) ChildRefCount: i32, - pub(crate) Flags: u32, - pub(crate) Status: NDIS_STATUS, - pub(crate) NetBufferListInfo: [*mut c_void; 20], // Extra data at the end of the struct. The size of the array is not fixed. -} - -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub union NBSize { - pub DataLength: u32, - pub stDataLength: u64, -} - -/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. -/// The NET_BUFFER structure specifies data that is transmitted or received over the network. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub struct NET_BUFFER { - pub(crate) Next: *mut NET_BUFFER, - pub(crate) CurrentMdl: *mut MDL, - pub(crate) CurrentMdlOffset: u32, - pub(crate) nbSize: NBSize, - pub(crate) MdlChain: *mut MDL, - pub(crate) DataOffset: u32, - pub(crate) ChecksumBias: u16, - pub(crate) Reserved: u16, - pub(crate) NdisPoolHandle: NDIS_HANDLE, - pub(crate) NdisReserved: [*mut c_void; 2], - pub(crate) ProtocolReserved: [*mut c_void; 6], - pub(crate) MiniportReserved: [*mut c_void; 4], - pub(crate) DataPhysicalAddress: u64, - pub(crate) SharedMemoryInfo: *mut c_void, -} - -/// This data type is used as the generic handle type in NDIS function calls. -#[allow(non_camel_case_types)] -pub type NDIS_HANDLE = *mut c_void; - -/// This data type is used to indicate success and error states in numerous functions and object identifiers. -#[allow(non_camel_case_types)] -pub type NDIS_STATUS = i32; - -/// The NDIS_OBJECT_HEADER structure packages the object type, version, and size information that is required in many NDIS 6.0 structures. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct NDIS_OBJECT_HEADER { - pub(crate) Type: u8, - pub(crate) Revision: u8, - pub(crate) Size: u16, -} - -/// The NET_BUFFER_LIST_POOL_PARAMETERS structure defines the parameters for a pool of NET_BUFFER_LIST structures. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -pub(crate) struct NET_BUFFER_LIST_POOL_PARAMETERS { - pub(crate) Header: NDIS_OBJECT_HEADER, - pub(crate) ProtocolId: u8, - pub(crate) fAllocateNetBuffer: bool, - pub(crate) ContextSize: u16, - pub(crate) PoolTag: u32, - pub(crate) DataSize: u32, - pub(crate) Flags: u32, -} -/// WdfObjectContextTypeInfo is a description of the device context. -#[repr(C)] -pub struct WdfObjectContextTypeInfo { - size: u32, - context_name: *const u8, - context_size: usize, - unique_type: *const WdfObjectContextTypeInfo, - _evt_driver_get_unique_context_type: *const c_void, // Internal use -} - -impl WdfObjectContextTypeInfo { - pub const fn default(null_terminated_name: &'static str) -> Self { - Self { - size: core::mem::size_of::() as u32, - context_name: null_terminated_name.as_ptr(), - context_size: 0, - unique_type: core::ptr::null(), - _evt_driver_get_unique_context_type: core::ptr::null(), - } - } -} - -/// WdfObjectAttributes contains attributes for the device context. -#[repr(C)] -pub struct WdfObjectAttributes { - size: u32, - evt_cleanup_callback: Option, - evt_destroy_callback: Option, - execution_level: WdfExecutionLevel, - synchronization_scope: WdfSynchronizationScope, - parent_object: HANDLE, - context_size_override: usize, - context_type_info: *const WdfObjectContextTypeInfo, -} - -impl WdfObjectAttributes { - pub fn new() -> Self { - Self { - size: core::mem::size_of::() as u32, - evt_cleanup_callback: None, - evt_destroy_callback: None, - execution_level: WdfExecutionLevel::InheritFromParent, - synchronization_scope: WdfSynchronizationScope::InheritFromParent, - parent_object: 0, - context_size_override: 0, - context_type_info: core::ptr::null(), - } - } - - pub fn add_context(&mut self, context_info: &'static mut WdfObjectContextTypeInfo) { - context_info.context_size = core::mem::size_of::(); - context_info.unique_type = context_info; - self.context_size_override = 0; - self.context_type_info = context_info.unique_type; - } - - pub fn set_cleanup_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { - self.evt_cleanup_callback = Some(callback); - } - - pub fn set_destroy_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { - self.evt_destroy_callback = Some(callback); - } -} - -#[link(name = "Fwpkclnt", kind = "static")] -#[link(name = "Fwpuclnt", kind = "static")] -#[link(name = "WdfDriverEntry", kind = "static")] -#[link(name = "WdfLdr", kind = "static")] -#[link(name = "BufferOverflowK", kind = "static")] -#[link(name = "uuid", kind = "static")] -#[link(name = "wdmsec", kind = "static")] -#[link(name = "wmilib", kind = "static")] -#[link(name = "NtosKrnl", kind = "static")] -#[link(name = "ndis", kind = "static")] -#[link(name = "hal", kind = "static")] -#[link(name = "c_helper", kind = "static")] -extern "C" { - /// The FwpsCalloutUnregisterById0 function unregisters a callout from the filter engine. - pub(crate) fn FwpsCalloutUnregisterById0(id: u32) -> NTSTATUS; - - /// The FwpsCalloutRegister3 function registers a callout with the filter engine. - pub(crate) fn FwpsCalloutRegister3( - deviceObject: *mut c_void, - callout: *const FWPS_CALLOUT3, - calloutId: *mut u32, - ) -> NTSTATUS; - - /// The FwpsPendOperation0 function is called by a callout to suspend packet processing pending completion of another operation. - pub(crate) fn FwpsPendOperation0( - completionHandle: HANDLE, - completionContext: *mut HANDLE, - ) -> NTSTATUS; - - /// The FwpsCompleteOperation0 function is called by a callout to resume packet processing that was suspended pending completion of another operation. - pub(crate) fn FwpsCompleteOperation0( - completionContext: HANDLE, - netBufferList: *mut NET_BUFFER_LIST, - ); - - /// The FwpsAcquireClassifyHandle0 function generates a classification handle that is used to identify asynchronous classification operations and requests for writable layer data. - pub(crate) fn FwpsAcquireClassifyHandle0( - classify_context: *mut c_void, - reserved: u32, // Must be zero. - classify_handle: *mut u64, - ) -> NTSTATUS; - - /// A callout driver calls FwpsReleaseClassifyHandle0 to release a classification handle that was previously acquired through a call to FwpsAcquireClassifyHandle0. - pub(crate) fn FwpsReleaseClassifyHandle0(classify_handle: u64); - - /// A callout's classifyFn function calls FwpsPendClassify0 to pend the current classify request. After the request is pended, the callout driver must complete the processing of the classify request asynchronously by calling FwpsCompleteClassify0. - pub(crate) fn FwpsPendClassify0( - classify_handle: u64, - filterId: u64, - flags: u32, // Must be zero. - classifyOut: *const ClassifyOut, - ) -> NTSTATUS; - - /// A callout driver calls FwpsCompleteClassify0 to asynchronously complete a pended classify request. The callout driver's classifyFn function must have previously called FwpsPendClassify0 to pend the classify request. - pub(crate) fn FwpsCompleteClassify0( - classify_handle: u64, - flags: u32, // Must be zero. - classifyOut: *const ClassifyOut, - ); - - /// The FwpsAcquireWritableLayerDataPointer0 function returns layer-specific data that can be inspected and changed. - pub(crate) fn FwpsAcquireWritableLayerDataPointer0( - classify_handle: u64, - filter_id: u64, - flags: u32, - writable_layer_data: *mut c_void, - classify_out: *mut ClassifyOut, - ) -> NTSTATUS; - - /// The FwpsApplyModifiedLayerData0 function applies changes to layer-specific data made after a call to FwpsAcquireWritableLayerDataPointer0. - pub(crate) fn FwpsApplyModifiedLayerData0( - classifyHandle: u64, - modifiedLayerData: *mut *mut c_void, - flags: u32, - ); - - /// pm_InitDriverObject initialize driver object. This function initializes requerd memory for the device context. - pub(crate) fn pm_InitDriverObject( - driver_object: *mut DRIVER_OBJECT, - registry_path: *mut UNICODE_STRING, - wdf_driver: *mut HANDLE, - wdf_device: *mut HANDLE, - win_driver_path: PCWSTR, - dos_driver_path: PCWSTR, - object_attributes: *mut WdfObjectAttributes, - wdf_driver_unload: extern "C" fn(HANDLE), - ) -> NTSTATUS; - - /// pm_WdfObjectGetTypedContextWerker 1to1 reference to the WdfObjectGetTypedContextWorker macro. The WdfObjectGetTypedContext macro returns a pointer to an object's context space. - pub(crate) fn pm_WdfObjectGetTypedContextWorker( - wdf_object: HANDLE, - type_info: *const WdfObjectContextTypeInfo, - ) -> *mut c_void; - - /// WdfObjectGetTypedContext 1to1 reference to WdfDeviceWdmGetDeviceObject. The WdfDeviceWdmGetDeviceObject method returns the Windows Driver Model (WDM) device object that is associated with a specified framework device object. - pub(crate) fn pm_GetDeviceObject(wdf_device: HANDLE) -> *mut DEVICE_OBJECT; - - /// The FwpsInjectNetworkSendAsync0 function injects packet data into the send data path. - pub(crate) fn FwpsInjectNetworkSendAsync0( - injectionHandle: HANDLE, - injectionContext: HANDLE, - flags: u32, - compartmentId: COMPARTMENT_ID, - netBufferList: *mut NET_BUFFER_LIST, - completionFn: FWPS_INJECT_COMPLETE0, - completionContext: *mut c_void, - ) -> NTSTATUS; - - /// The FwpsInjectNetworkReceiveAsync0 function injects packet data into the receive data path. - pub(crate) fn FwpsInjectNetworkReceiveAsync0( - injectionHandle: HANDLE, - injectionContext: HANDLE, - flags: u32, - compartmentId: COMPARTMENT_ID, - interfaceIndex: u32, - subInterfaceIndex: u32, - netBufferList: *mut NET_BUFFER_LIST, - completionFn: FWPS_INJECT_COMPLETE0, - completionContext: *mut c_void, - ) -> NTSTATUS; - - /// The FwpsInjectTransportSendAsync1 function injects packet data from the transport, datagram data, or ICMP error layers into the send data path. This function differs from the previous version (FwpsInjectTransportSendAsync0) in that it takes an updated parameters structure as an argument. - pub(crate) fn FwpsInjectTransportSendAsync1( - injectionHandle: HANDLE, - injectionContext: HANDLE, - endpointHandle: u64, - flags: u32, - sendArgs: *mut FWPS_TRANSPORT_SEND_PARAMS1, - addressFamily: ADDRESS_FAMILY, - compartmentId: COMPARTMENT_ID, - netBufferList: *mut NET_BUFFER_LIST, - completionFn: FWPS_INJECT_COMPLETE0, - completionContext: *mut c_void, - ) -> NTSTATUS; - - /// The FwpsInjectTransportReceiveAsync0 function injects packet data from the transport, datagram data, or ICMP error layers into the receive data path. - pub(crate) fn FwpsInjectTransportReceiveAsync0( - injectionHandle: HANDLE, - injectionContext: HANDLE, - reserved: *const c_void, - flags: u32, - addressFamily: ADDRESS_FAMILY, - compartmentId: COMPARTMENT_ID, - interfaceIndex: u32, - subInterfaceIndex: u32, - netBufferList: *mut NET_BUFFER_LIST, - completionFn: FWPS_INJECT_COMPLETE0, - completionContext: *mut c_void, - ) -> NTSTATUS; - - /// The FwpsInjectionHandleCreate0 function creates a handle that can be used by packet injection functions to inject packet or stream data into the TCP/IP network stack and by the FwpsQueryPacketInjectionState0 function to query the packet injection state. - pub(crate) fn FwpsInjectionHandleCreate0( - addressFamily: ADDRESS_FAMILY, - flags: u32, - injectionHandle: &mut HANDLE, - ) -> NTSTATUS; - - /// The FwpsQueryPacketInjectionState0 function is called by a callout to query the injection state of packet data. - pub(crate) fn FwpsQueryPacketInjectionState0( - injectionHandle: HANDLE, - netBufferList: *const NET_BUFFER_LIST, - injectionContext: *mut HANDLE, - ) -> FWPS_PACKET_INJECTION_STATE; - - /// The FwpsInjectionHandleDestroy0 function destroys an injection handle that was previously created by calling the FwpsInjectionHandleCreate0 function. - pub(crate) fn FwpsInjectionHandleDestroy0(injectionHandle: HANDLE) -> NTSTATUS; - - /// The FwpsReferenceNetBufferList0 function increments the reference count for a NET_BUFFER_LIST structure. - pub(crate) fn FwpsReferenceNetBufferList0( - netBufferList: *mut NET_BUFFER_LIST, - intendToModify: bool, - ); - - /// The FwpsDereferenceNetBufferList0 function decrements the reference count for a NET_BUFFER_LIST structure that a callout driver had acquired earlier using the FwpsReferenceNetBufferList0 function. - pub(crate) fn FwpsDereferenceNetBufferList0( - netBufferList: *mut NET_BUFFER_LIST, - dispatchLevel: bool, - ); - - /// Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure. - pub(crate) fn NdisGetDataBuffer( - NetBuffer: *const NET_BUFFER, - BytesNeeded: u32, - Storage: *mut u8, - AlignMultiple: u32, - AlignOffset: u32, - ) -> *mut u8; - - /// Call the NdisAllocateCloneNetBufferList function to create a new clone NET_BUFFER_LIST structure. - pub(crate) fn NdisAllocateCloneNetBufferList( - OriginalNetBufferList: *mut NET_BUFFER_LIST, - NetBufferListPoolHandle: NDIS_HANDLE, - NetBufferPoolHandle: NDIS_HANDLE, - AllocateCloneFlag: u32, - ) -> *mut NET_BUFFER_LIST; - - /// Call the NdisFreeCloneNetBufferList function to free a NET_BUFFER_LIST structure and all associated NET_BUFFER structures and MDL chains that were previously allocated by calling the NdisAllocateCloneNetBufferList function. - pub(crate) fn NdisFreeCloneNetBufferList( - CloneNetBufferList: *mut NET_BUFFER_LIST, - FreeCloneFlags: u32, - ); - - /// The FwpsAllocateNetBufferAndNetBufferList0 function allocates a new NET_BUFFER_LIST structure. - pub(crate) fn FwpsAllocateNetBufferAndNetBufferList0( - poolHandle: NDIS_HANDLE, - contextSize: u16, - contextBackFill: u16, - mdlChain: *mut MDL, - dataOffset: u32, - dataLength: u64, - netBufferList: *mut *mut NET_BUFFER_LIST, - ) -> NTSTATUS; - - /// The FwpsFreeNetBufferList0 function frees a NET_BUFFER_LIST structure that was previously allocated by a call to the FwpsAllocateNetBufferAndNetBufferList0 function. - pub(crate) fn FwpsFreeNetBufferList0(netBufferList: *mut NET_BUFFER_LIST); - - /// Call the NdisAllocateNetBufferListPool function to allocate a pool of NET_BUFFER_LIST structures. - pub(crate) fn NdisAllocateNetBufferListPool( - NdisHandle: NDIS_HANDLE, - Parameters: *const NET_BUFFER_LIST_POOL_PARAMETERS, - ) -> NDIS_HANDLE; - - /// Call the NdisFreeNetBufferListPool function to free a NET_BUFFER_LIST structure pool. - pub(crate) fn NdisFreeNetBufferListPool(PoolHandle: NDIS_HANDLE); - - /// Call the NdisRetreatNetBufferDataStart function to access more used data space in the MDL chain of a NET_BUFFER structure. - pub(crate) fn NdisRetreatNetBufferDataStart( - NetBuffer: *mut NET_BUFFER, - DataOffsetDelta: u32, - DataBackFill: u32, - AllocateMdlHandler: *mut c_void, - ) -> NDIS_STATUS; - - /// Call the NdisAdvanceNetBufferDataStart function to release the used data space that was added with the NdisRetreatNetBufferDataStart function. - pub(crate) fn NdisAdvanceNetBufferDataStart( - NetBuffer: *mut NET_BUFFER, - DataOffsetDelta: u32, - FreeMdl: bool, - FreeMdlHandler: *mut c_void, - ); - - /// The KeQuerySystemTime routine obtains the current system time. - /// System time is a count of 100-nanosecond intervals since January 1, 1601. System time is typically updated approximately every ten milliseconds. This value is computed for the GMT time zone. - pub(crate) fn pm_QuerySystemTime() -> u64; - - /// The KeQueryPerformanceCounter routine retrieves the current value and frequency of the performance counter. - /// Use KeQueryPerformanceCounter to acquire high resolution (<1µs) time stamps for time interval measurements. - pub(crate) fn KeQueryPerformanceCounter(frequency: *mut i64) -> i64; - - /// The KeGetCurrentIrql routine returns the current IRQL. - pub(crate) fn KeGetCurrentIrql() -> u8; - - /// The KeDelayExecutionThread routine puts the current thread into an alertable or - /// nonalertable wait state for a given interval. - /// `interval` is a count of 100-nanosecond units: negative is relative to now, positive is an - /// absolute system time. Callable only at IRQL <= APC_LEVEL (PASSIVE_LEVEL if alertable). - pub(crate) fn KeDelayExecutionThread( - wait_mode: i8, - alertable: u8, - interval: *const i64, - ) -> NTSTATUS; -} +use core::ffi::c_void; + +use windows_sys::{ + core::{GUID, PCWSTR}, + Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, MDL}, + Win32::{ + Foundation::{HANDLE, NTSTATUS, UNICODE_STRING}, + NetworkManagement::WindowsFilteringPlatform::{ + FWPM_PROVIDER_CONTEXT2, FWP_CONDITION_VALUE0, FWP_MATCH_TYPE, FWP_VALUE0, + }, + Networking::WinSock::{ADDRESS_FAMILY, SCOPE_ID}, + System::Kernel::COMPARTMENT_ID, + }, +}; + +use crate::filter_engine::{ + classify::ClassifyOut, layer::IncomingValues, metadata::FwpsIncomingMetadataValues, +}; + +#[allow(non_camel_case_types, non_snake_case)] +pub(crate) type FwpsCalloutClassifyFn = unsafe extern "C" fn( + inFixedValues: *const IncomingValues, + inMetaValues: *const FwpsIncomingMetadataValues, + layerData: *mut c_void, + classifyContext: *mut c_void, + filter: *const FWPS_FILTER2, + flowContext: u64, + classifyOut: *mut ClassifyOut, +); + +#[allow(non_camel_case_types, non_snake_case)] +pub(crate) type FwpsCalloutNotifyFn = unsafe extern "C" fn( + notifyType: u32, + filterKey: *const GUID, + filter: *mut FWPS_FILTER2, +) -> NTSTATUS; + +#[allow(non_camel_case_types, non_snake_case)] +pub(crate) type FwpsCalloutFlowDeleteNotifyFn = + unsafe extern "C" fn(layerId: u16, calloutId: u32, flowContext: u64); + +/// The FWPS_ACTION0 structure specifies the run-time action that the filter engine takes if all of the filter's filtering conditions are true. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_ACTION0 { + r#type: u32, + calloutId: u32, +} + +/// The FWPS_FILTER_CONDITION0 structure defines a run-time filtering condition for a filter. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_FILTER_CONDITION0 { + fieldId: u16, + reserved: u16, + matchType: FWP_MATCH_TYPE, + conditionValue: FWP_CONDITION_VALUE0, +} + +/// The WdfExecutionLevel enumeration type specifies the maximum IRQL at which the framework will call the event callback functions that a driver has supplied for a framework object. +#[repr(C)] +enum WdfExecutionLevel { + Invalid = 0, + InheritFromParent, + Passive, + Dispatch, +} + +/// The WDF_SYNCHRONIZATION_SCOPE enumeration type specifies how the framework will synchronize execution of an object's event callback functions. +#[repr(C)] +enum WdfSynchronizationScope { + Invalid = 0x00, + InheritFromParent, + Device, + Queue, + None, +} + +unsafe impl Sync for WdfObjectContextTypeInfo {} + +/// The FWPS_FILTER2 structure defines a run-time filter in the filter engine. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_FILTER2 { + pub(crate) filterId: u64, + pub(crate) weight: FWP_VALUE0, + pub(crate) subLayerWeight: u16, + pub(crate) flags: u16, + pub(crate) numFilterConditions: u32, + pub(crate) filterCondition: *mut FWPS_FILTER_CONDITION0, + pub(crate) action: FWPS_ACTION0, + pub(crate) context: u64, + pub(crate) providerContext: *mut FWPM_PROVIDER_CONTEXT2, +} + +/// The FWPS_CALLOUT3 structure defines the data that is required for a callout driver to register a callout with the filter engine. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_CALLOUT3 { + pub(crate) calloutKey: GUID, + pub(crate) flags: u32, + pub(crate) classifyFn: Option, + pub(crate) notifyFn: Option, + pub(crate) flowDeleteFn: Option, +} + +/// The filter engine calls a callout's completionFn callout function whenever packet data, described by the netBufferList parameter in one of the packet injection functions, has been injected into the network stack. +#[allow(non_camel_case_types)] +type FWPS_INJECT_COMPLETE0 = unsafe extern "C" fn( + context: *mut c_void, + net_buffer_list: *mut NET_BUFFER_LIST, + dispatch_level: bool, +); + +/// The FWPS_TRANSPORT_SEND_PARAMS1 structure defines properties of an outbound transport layer packet. +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct FWPS_TRANSPORT_SEND_PARAMS1 { + pub(crate) remote_address: *const u8, + pub(crate) remote_scope_id: SCOPE_ID, + pub(crate) control_data: *mut c_void, //WSACMSGHDR, + pub(crate) control_data_length: u32, + pub(crate) header_include_header: *mut u8, + pub(crate) header_include_header_length: u32, +} + +/// The FWPS_PACKET_INJECTION_STATE enumeration type specifies the injection state of a network buffer list. +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) enum FWPS_PACKET_INJECTION_STATE { + FWPS_PACKET_NOT_INJECTED, + FWPS_PACKET_INJECTED_BY_SELF, + FWPS_PACKET_INJECTED_BY_OTHER, + FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF, + FWPS_PACKET_INJECTION_STATE_MAX, +} + +pub(crate) const FWPS_INJECTION_TYPE_STREAM: u32 = 0x00000001; +pub(crate) const FWPS_INJECTION_TYPE_TRANSPORT: u32 = 0x00000002; +pub(crate) const FWPS_INJECTION_TYPE_NETWORK: u32 = 0x00000004; +pub(crate) const FWPS_INJECTION_TYPE_FORWARD: u32 = 0x00000008; +pub(crate) const FWPS_INJECTION_TYPE_L2: u32 = 0x00000010; +pub(crate) const FWPS_INJECTION_TYPE_VSWITCH_TRANSPORT: u32 = 0x00000020; + +pub(crate) const NDIS_OBJECT_TYPE_DEFAULT: u8 = 0x80; // used when object type is implicit in the API call +pub(crate) const NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1: u8 = 1; + +/// The NBListHeader is the header of NET_BUFFER_LIST struct. +#[repr(C)] +pub(crate) struct NBListHeader { + pub(crate) next: *mut NET_BUFFER_LIST, + pub(crate) first_net_buffer: *mut NET_BUFFER, +} + +/// The NET_BUFFER_LIST structure specifies a linked list of NET_BUFFER structures. +/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub struct NET_BUFFER_LIST { + pub(crate) Header: NBListHeader, + pub(crate) Context: *mut c_void, + pub(crate) ParentNetBufferList: *mut NET_BUFFER_LIST, + pub(crate) NdisPoolHandle: NDIS_HANDLE, + pub(crate) NdisReserved: [*mut c_void; 2], + pub(crate) ProtocolReserved: [*mut c_void; 4], + pub(crate) MiniportReserved: [*mut c_void; 2], + pub(crate) Scratch: *mut c_void, + pub(crate) SourceHandle: NDIS_HANDLE, + pub(crate) NblFlags: u32, + pub(crate) ChildRefCount: i32, + pub(crate) Flags: u32, + pub(crate) Status: NDIS_STATUS, + pub(crate) NetBufferListInfo: [*mut c_void; 20], // Extra data at the end of the struct. The size of the array is not fixed. +} + +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub union NBSize { + pub DataLength: u32, + pub stDataLength: u64, +} + +/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. +/// The NET_BUFFER structure specifies data that is transmitted or received over the network. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub struct NET_BUFFER { + pub(crate) Next: *mut NET_BUFFER, + pub(crate) CurrentMdl: *mut MDL, + pub(crate) CurrentMdlOffset: u32, + pub(crate) nbSize: NBSize, + pub(crate) MdlChain: *mut MDL, + pub(crate) DataOffset: u32, + pub(crate) ChecksumBias: u16, + pub(crate) Reserved: u16, + pub(crate) NdisPoolHandle: NDIS_HANDLE, + pub(crate) NdisReserved: [*mut c_void; 2], + pub(crate) ProtocolReserved: [*mut c_void; 6], + pub(crate) MiniportReserved: [*mut c_void; 4], + pub(crate) DataPhysicalAddress: u64, + pub(crate) SharedMemoryInfo: *mut c_void, +} + +/// This data type is used as the generic handle type in NDIS function calls. +#[allow(non_camel_case_types)] +pub type NDIS_HANDLE = *mut c_void; + +/// This data type is used to indicate success and error states in numerous functions and object identifiers. +#[allow(non_camel_case_types)] +pub type NDIS_STATUS = i32; + +/// The NDIS_OBJECT_HEADER structure packages the object type, version, and size information that is required in many NDIS 6.0 structures. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct NDIS_OBJECT_HEADER { + pub(crate) Type: u8, + pub(crate) Revision: u8, + pub(crate) Size: u16, +} + +/// The NET_BUFFER_LIST_POOL_PARAMETERS structure defines the parameters for a pool of NET_BUFFER_LIST structures. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct NET_BUFFER_LIST_POOL_PARAMETERS { + pub(crate) Header: NDIS_OBJECT_HEADER, + pub(crate) ProtocolId: u8, + pub(crate) fAllocateNetBuffer: bool, + pub(crate) ContextSize: u16, + pub(crate) PoolTag: u32, + pub(crate) DataSize: u32, + pub(crate) Flags: u32, +} +/// WdfObjectContextTypeInfo is a description of the device context. +#[repr(C)] +pub struct WdfObjectContextTypeInfo { + size: u32, + context_name: *const u8, + context_size: usize, + unique_type: *const WdfObjectContextTypeInfo, + _evt_driver_get_unique_context_type: *const c_void, // Internal use +} + +impl WdfObjectContextTypeInfo { + pub const fn default(null_terminated_name: &'static str) -> Self { + Self { + size: core::mem::size_of::() as u32, + context_name: null_terminated_name.as_ptr(), + context_size: 0, + unique_type: core::ptr::null(), + _evt_driver_get_unique_context_type: core::ptr::null(), + } + } +} + +/// WdfObjectAttributes contains attributes for the device context. +#[repr(C)] +pub struct WdfObjectAttributes { + size: u32, + evt_cleanup_callback: Option, + evt_destroy_callback: Option, + execution_level: WdfExecutionLevel, + synchronization_scope: WdfSynchronizationScope, + parent_object: HANDLE, + context_size_override: usize, + context_type_info: *const WdfObjectContextTypeInfo, +} + +impl WdfObjectAttributes { + pub fn new() -> Self { + Self { + size: core::mem::size_of::() as u32, + evt_cleanup_callback: None, + evt_destroy_callback: None, + execution_level: WdfExecutionLevel::InheritFromParent, + synchronization_scope: WdfSynchronizationScope::InheritFromParent, + parent_object: 0, + context_size_override: 0, + context_type_info: core::ptr::null(), + } + } + + pub fn add_context(&mut self, context_info: &'static mut WdfObjectContextTypeInfo) { + context_info.context_size = core::mem::size_of::(); + context_info.unique_type = context_info; + self.context_size_override = 0; + self.context_type_info = context_info.unique_type; + } + + pub fn set_cleanup_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { + self.evt_cleanup_callback = Some(callback); + } + + pub fn set_destroy_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { + self.evt_destroy_callback = Some(callback); + } +} + +#[link(name = "Fwpkclnt", kind = "static")] +#[link(name = "Fwpuclnt", kind = "static")] +#[link(name = "WdfDriverEntry", kind = "static")] +#[link(name = "WdfLdr", kind = "static")] +#[link(name = "BufferOverflowK", kind = "static")] +#[link(name = "uuid", kind = "static")] +#[link(name = "wdmsec", kind = "static")] +#[link(name = "wmilib", kind = "static")] +#[link(name = "NtosKrnl", kind = "static")] +#[link(name = "ndis", kind = "static")] +#[link(name = "hal", kind = "static")] +#[link(name = "c_helper", kind = "static")] +extern "C" { + /// The FwpsCalloutUnregisterById0 function unregisters a callout from the filter engine. + pub(crate) fn FwpsCalloutUnregisterById0(id: u32) -> NTSTATUS; + + /// The FwpsCalloutRegister3 function registers a callout with the filter engine. + pub(crate) fn FwpsCalloutRegister3( + deviceObject: *mut c_void, + callout: *const FWPS_CALLOUT3, + calloutId: *mut u32, + ) -> NTSTATUS; + + /// The FwpsPendOperation0 function is called by a callout to suspend packet processing pending completion of another operation. + pub(crate) fn FwpsPendOperation0( + completionHandle: HANDLE, + completionContext: *mut HANDLE, + ) -> NTSTATUS; + + /// The FwpsCompleteOperation0 function is called by a callout to resume packet processing that was suspended pending completion of another operation. + pub(crate) fn FwpsCompleteOperation0( + completionContext: HANDLE, + netBufferList: *mut NET_BUFFER_LIST, + ); + + /// The FwpsAcquireClassifyHandle0 function generates a classification handle that is used to identify asynchronous classification operations and requests for writable layer data. + pub(crate) fn FwpsAcquireClassifyHandle0( + classify_context: *mut c_void, + reserved: u32, // Must be zero. + classify_handle: *mut u64, + ) -> NTSTATUS; + + /// A callout driver calls FwpsReleaseClassifyHandle0 to release a classification handle that was previously acquired through a call to FwpsAcquireClassifyHandle0. + pub(crate) fn FwpsReleaseClassifyHandle0(classify_handle: u64); + + /// A callout's classifyFn function calls FwpsPendClassify0 to pend the current classify request. After the request is pended, the callout driver must complete the processing of the classify request asynchronously by calling FwpsCompleteClassify0. + pub(crate) fn FwpsPendClassify0( + classify_handle: u64, + filterId: u64, + flags: u32, // Must be zero. + classifyOut: *const ClassifyOut, + ) -> NTSTATUS; + + /// A callout driver calls FwpsCompleteClassify0 to asynchronously complete a pended classify request. The callout driver's classifyFn function must have previously called FwpsPendClassify0 to pend the classify request. + pub(crate) fn FwpsCompleteClassify0( + classify_handle: u64, + flags: u32, // Must be zero. + classifyOut: *const ClassifyOut, + ); + + /// The FwpsAcquireWritableLayerDataPointer0 function returns layer-specific data that can be inspected and changed. + pub(crate) fn FwpsAcquireWritableLayerDataPointer0( + classify_handle: u64, + filter_id: u64, + flags: u32, + writable_layer_data: *mut c_void, + classify_out: *mut ClassifyOut, + ) -> NTSTATUS; + + /// The FwpsApplyModifiedLayerData0 function applies changes to layer-specific data made after a call to FwpsAcquireWritableLayerDataPointer0. + pub(crate) fn FwpsApplyModifiedLayerData0( + classifyHandle: u64, + modifiedLayerData: *mut *mut c_void, + flags: u32, + ); + + /// pm_InitDriverObject initialize driver object. This function initializes requerd memory for the device context. + pub(crate) fn pm_InitDriverObject( + driver_object: *mut DRIVER_OBJECT, + registry_path: *mut UNICODE_STRING, + wdf_driver: *mut HANDLE, + wdf_device: *mut HANDLE, + win_driver_path: PCWSTR, + dos_driver_path: PCWSTR, + object_attributes: *mut WdfObjectAttributes, + wdf_driver_unload: extern "C" fn(HANDLE), + ) -> NTSTATUS; + + /// pm_WdfObjectGetTypedContextWerker 1to1 reference to the WdfObjectGetTypedContextWorker macro. The WdfObjectGetTypedContext macro returns a pointer to an object's context space. + pub(crate) fn pm_WdfObjectGetTypedContextWorker( + wdf_object: HANDLE, + type_info: *const WdfObjectContextTypeInfo, + ) -> *mut c_void; + + /// WdfObjectGetTypedContext 1to1 reference to WdfDeviceWdmGetDeviceObject. The WdfDeviceWdmGetDeviceObject method returns the Windows Driver Model (WDM) device object that is associated with a specified framework device object. + pub(crate) fn pm_GetDeviceObject(wdf_device: HANDLE) -> *mut DEVICE_OBJECT; + + /// The FwpsInjectNetworkSendAsync0 function injects packet data into the send data path. + pub(crate) fn FwpsInjectNetworkSendAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + flags: u32, + compartmentId: COMPARTMENT_ID, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectNetworkReceiveAsync0 function injects packet data into the receive data path. + pub(crate) fn FwpsInjectNetworkReceiveAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + flags: u32, + compartmentId: COMPARTMENT_ID, + interfaceIndex: u32, + subInterfaceIndex: u32, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectTransportSendAsync1 function injects packet data from the transport, datagram data, or ICMP error layers into the send data path. This function differs from the previous version (FwpsInjectTransportSendAsync0) in that it takes an updated parameters structure as an argument. + pub(crate) fn FwpsInjectTransportSendAsync1( + injectionHandle: HANDLE, + injectionContext: HANDLE, + endpointHandle: u64, + flags: u32, + sendArgs: *mut FWPS_TRANSPORT_SEND_PARAMS1, + addressFamily: ADDRESS_FAMILY, + compartmentId: COMPARTMENT_ID, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectTransportReceiveAsync0 function injects packet data from the transport, datagram data, or ICMP error layers into the receive data path. + pub(crate) fn FwpsInjectTransportReceiveAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + reserved: *const c_void, + flags: u32, + addressFamily: ADDRESS_FAMILY, + compartmentId: COMPARTMENT_ID, + interfaceIndex: u32, + subInterfaceIndex: u32, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectionHandleCreate0 function creates a handle that can be used by packet injection functions to inject packet or stream data into the TCP/IP network stack and by the FwpsQueryPacketInjectionState0 function to query the packet injection state. + pub(crate) fn FwpsInjectionHandleCreate0( + addressFamily: ADDRESS_FAMILY, + flags: u32, + injectionHandle: &mut HANDLE, + ) -> NTSTATUS; + + /// The FwpsQueryPacketInjectionState0 function is called by a callout to query the injection state of packet data. + pub(crate) fn FwpsQueryPacketInjectionState0( + injectionHandle: HANDLE, + netBufferList: *const NET_BUFFER_LIST, + injectionContext: *mut HANDLE, + ) -> FWPS_PACKET_INJECTION_STATE; + + /// The FwpsInjectionHandleDestroy0 function destroys an injection handle that was previously created by calling the FwpsInjectionHandleCreate0 function. + pub(crate) fn FwpsInjectionHandleDestroy0(injectionHandle: HANDLE) -> NTSTATUS; + + /// The FwpsReferenceNetBufferList0 function increments the reference count for a NET_BUFFER_LIST structure. + pub(crate) fn FwpsReferenceNetBufferList0( + netBufferList: *mut NET_BUFFER_LIST, + intendToModify: bool, + ); + + /// The FwpsDereferenceNetBufferList0 function decrements the reference count for a NET_BUFFER_LIST structure that a callout driver had acquired earlier using the FwpsReferenceNetBufferList0 function. + pub(crate) fn FwpsDereferenceNetBufferList0( + netBufferList: *mut NET_BUFFER_LIST, + dispatchLevel: bool, + ); + + /// Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure. + pub(crate) fn NdisGetDataBuffer( + NetBuffer: *const NET_BUFFER, + BytesNeeded: u32, + Storage: *mut u8, + AlignMultiple: u32, + AlignOffset: u32, + ) -> *mut u8; + + /// Call the NdisAllocateCloneNetBufferList function to create a new clone NET_BUFFER_LIST structure. + pub(crate) fn NdisAllocateCloneNetBufferList( + OriginalNetBufferList: *mut NET_BUFFER_LIST, + NetBufferListPoolHandle: NDIS_HANDLE, + NetBufferPoolHandle: NDIS_HANDLE, + AllocateCloneFlag: u32, + ) -> *mut NET_BUFFER_LIST; + + /// Call the NdisFreeCloneNetBufferList function to free a NET_BUFFER_LIST structure and all associated NET_BUFFER structures and MDL chains that were previously allocated by calling the NdisAllocateCloneNetBufferList function. + pub(crate) fn NdisFreeCloneNetBufferList( + CloneNetBufferList: *mut NET_BUFFER_LIST, + FreeCloneFlags: u32, + ); + + /// The FwpsAllocateNetBufferAndNetBufferList0 function allocates a new NET_BUFFER_LIST structure. + pub(crate) fn FwpsAllocateNetBufferAndNetBufferList0( + poolHandle: NDIS_HANDLE, + contextSize: u16, + contextBackFill: u16, + mdlChain: *mut MDL, + dataOffset: u32, + dataLength: u64, + netBufferList: *mut *mut NET_BUFFER_LIST, + ) -> NTSTATUS; + + /// The FwpsFreeNetBufferList0 function frees a NET_BUFFER_LIST structure that was previously allocated by a call to the FwpsAllocateNetBufferAndNetBufferList0 function. + pub(crate) fn FwpsFreeNetBufferList0(netBufferList: *mut NET_BUFFER_LIST); + + /// Call the NdisAllocateNetBufferListPool function to allocate a pool of NET_BUFFER_LIST structures. + pub(crate) fn NdisAllocateNetBufferListPool( + NdisHandle: NDIS_HANDLE, + Parameters: *const NET_BUFFER_LIST_POOL_PARAMETERS, + ) -> NDIS_HANDLE; + + /// Call the NdisFreeNetBufferListPool function to free a NET_BUFFER_LIST structure pool. + pub(crate) fn NdisFreeNetBufferListPool(PoolHandle: NDIS_HANDLE); + + /// Call the NdisRetreatNetBufferDataStart function to access more used data space in the MDL chain of a NET_BUFFER structure. + pub(crate) fn NdisRetreatNetBufferDataStart( + NetBuffer: *mut NET_BUFFER, + DataOffsetDelta: u32, + DataBackFill: u32, + AllocateMdlHandler: *mut c_void, + ) -> NDIS_STATUS; + + /// Call the NdisAdvanceNetBufferDataStart function to release the used data space that was added with the NdisRetreatNetBufferDataStart function. + pub(crate) fn NdisAdvanceNetBufferDataStart( + NetBuffer: *mut NET_BUFFER, + DataOffsetDelta: u32, + FreeMdl: bool, + FreeMdlHandler: *mut c_void, + ); + + /// The KeQuerySystemTime routine obtains the current system time. + /// System time is a count of 100-nanosecond intervals since January 1, 1601. System time is typically updated approximately every ten milliseconds. This value is computed for the GMT time zone. + pub(crate) fn pm_QuerySystemTime() -> u64; + + /// The KeQueryPerformanceCounter routine retrieves the current value and frequency of the performance counter. + /// Use KeQueryPerformanceCounter to acquire high resolution (<1µs) time stamps for time interval measurements. + pub(crate) fn KeQueryPerformanceCounter(frequency: *mut i64) -> i64; + + /// The KeGetCurrentIrql routine returns the current IRQL. + pub(crate) fn KeGetCurrentIrql() -> u8; + + /// The KeDelayExecutionThread routine puts the current thread into an alertable or + /// nonalertable wait state for a given interval. + /// `interval` is a count of 100-nanosecond units: negative is relative to now, positive is an + /// absolute system time. Callable only at IRQL <= APC_LEVEL (PASSIVE_LEVEL if alertable). + pub(crate) fn KeDelayExecutionThread( + wait_mode: i8, + alertable: u8, + interval: *const i64, + ) -> NTSTATUS; +} diff --git a/wdk/src/filter_engine/callout.rs b/wdk/src/filter_engine/callout.rs index e9b2f2a..ad4fcf4 100644 --- a/wdk/src/filter_engine/callout.rs +++ b/wdk/src/filter_engine/callout.rs @@ -1,101 +1,101 @@ -use super::{callout_data::CalloutData, ffi, layer::Layer}; -use crate::ffi::FwpsCalloutClassifyFn; -use alloc::{borrow::ToOwned, format, string::String}; -use windows_sys::Wdk::Foundation::DEVICE_OBJECT; - -pub enum FilterType { - Resettable, - NonResettable, -} - -pub struct Callout { - pub(crate) id: u32, - pub(super) address: u64, - pub(crate) name: String, - pub(crate) description: String, - pub(crate) guid: u128, - pub(crate) layer: Layer, - pub(crate) action: u32, - pub(crate) registered: bool, - pub(crate) filter_type: FilterType, - pub(crate) filter_id: u64, - pub(crate) callout_fn: fn(CalloutData), -} - -impl Callout { - pub fn new( - name: &str, - description: &str, - guid: u128, - layer: Layer, - action: u32, - filter_type: FilterType, - callout_fn: fn(CalloutData), - ) -> Self { - Self { - id: 0, - address: 0, - name: name.to_owned(), - description: description.to_owned(), - guid, - layer, - action, - registered: false, - filter_type, - filter_id: 0, - callout_fn, - } - } - - pub fn register_filter( - &mut self, - filter_engine_handle: isize, - sublayer_guid: u128, - ) -> Result<(), String> { - match ffi::register_filter( - filter_engine_handle, - sublayer_guid, - &format!("{}-filter", self.name), - &self.description, - self.guid, - self.layer, - self.action, - self.address, // The address of the callout is passed as context. - ) { - Ok(id) => { - self.filter_id = id; - } - Err(error) => { - return Err(format!("failed to register filter: {}", error)); - } - }; - - return Ok(()); - } - - pub(crate) fn register_callout( - &mut self, - filter_engine_handle: isize, - device_object: *mut DEVICE_OBJECT, - callout_fn: FwpsCalloutClassifyFn, - ) -> Result<(), String> { - match ffi::register_callout( - device_object, - filter_engine_handle, - &format!("{}-callout", self.name), - &self.description, - self.guid, - self.layer, - callout_fn, - ) { - Ok(id) => { - self.registered = true; - self.id = id; - } - Err(code) => { - return Err(format!("failed to register callout: {}", code)); - } - }; - return Ok(()); - } -} +use super::{callout_data::CalloutData, ffi, layer::Layer}; +use crate::ffi::FwpsCalloutClassifyFn; +use alloc::{borrow::ToOwned, format, string::String}; +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; + +pub enum FilterType { + Resettable, + NonResettable, +} + +pub struct Callout { + pub(crate) id: u32, + pub(super) address: u64, + pub(crate) name: String, + pub(crate) description: String, + pub(crate) guid: u128, + pub(crate) layer: Layer, + pub(crate) action: u32, + pub(crate) registered: bool, + pub(crate) filter_type: FilterType, + pub(crate) filter_id: u64, + pub(crate) callout_fn: fn(CalloutData), +} + +impl Callout { + pub fn new( + name: &str, + description: &str, + guid: u128, + layer: Layer, + action: u32, + filter_type: FilterType, + callout_fn: fn(CalloutData), + ) -> Self { + Self { + id: 0, + address: 0, + name: name.to_owned(), + description: description.to_owned(), + guid, + layer, + action, + registered: false, + filter_type, + filter_id: 0, + callout_fn, + } + } + + pub fn register_filter( + &mut self, + filter_engine_handle: isize, + sublayer_guid: u128, + ) -> Result<(), String> { + match ffi::register_filter( + filter_engine_handle, + sublayer_guid, + &format!("{}-filter", self.name), + &self.description, + self.guid, + self.layer, + self.action, + self.address, // The address of the callout is passed as context. + ) { + Ok(id) => { + self.filter_id = id; + } + Err(error) => { + return Err(format!("failed to register filter: {}", error)); + } + }; + + return Ok(()); + } + + pub(crate) fn register_callout( + &mut self, + filter_engine_handle: isize, + device_object: *mut DEVICE_OBJECT, + callout_fn: FwpsCalloutClassifyFn, + ) -> Result<(), String> { + match ffi::register_callout( + device_object, + filter_engine_handle, + &format!("{}-callout", self.name), + &self.description, + self.guid, + self.layer, + callout_fn, + ) { + Ok(id) => { + self.registered = true; + self.id = id; + } + Err(code) => { + return Err(format!("failed to register callout: {}", code)); + } + }; + return Ok(()); + } +} diff --git a/wdk/src/filter_engine/classify.rs b/wdk/src/filter_engine/classify.rs index 696deac..1acff2e 100644 --- a/wdk/src/filter_engine/classify.rs +++ b/wdk/src/filter_engine/classify.rs @@ -1,87 +1,87 @@ -#![allow(dead_code)] - -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPS_CLASSIFY_OUT_FLAG_ABSORB; - -const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; -const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; -const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; - -const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; -const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; -const FWP_ACTION_CALLOUT_TERMINATING: u32 = - 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; -const FWP_ACTION_CALLOUT_INSPECTION: u32 = - 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; -const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; -const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; -const FWP_ACTION_NONE: u32 = 0x00000007; -const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; - -const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; -const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; -const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; -const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; -const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; -const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; -const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; -const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; -const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; -const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; -const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; -const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; -const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; -const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; -const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; -const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; -const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; -const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; - -const FWPS_RIGHT_ACTION_WRITE: u32 = 0x00000001; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct ClassifyOut { - action_type: u32, - _out_context: u64, // System use - _filter_id: u64, // System use - rights: u32, - flags: u32, - reserved: u32, -} - -impl ClassifyOut { - // Checks if write action flag is set. Indicates if the callout can change the action. - pub fn can_set_action(&self) -> bool { - self.rights & FWPS_RIGHT_ACTION_WRITE > 0 - } - - /// Set block action. Write flag should be cleared, after this. - pub fn action_block(&mut self) { - self.action_type = FWP_ACTION_BLOCK; - } - - /// Set permit action. - pub fn action_permit(&mut self) { - self.action_type = FWP_ACTION_PERMIT; - } - - // Set continue action. - pub fn action_continue(&mut self) { - self.action_type = FWP_ACTION_CONTINUE; - } - - // Set none action. - pub fn set_none(&mut self) { - self.action_type = FWP_ACTION_NONE; - } - - // Set absorb flag. This will drop the packet. Used when the packets will be reinjected in the future. - pub fn set_absorb(&mut self) { - self.flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB; - } - - // Clear the write flag permission. Next filter in the chain will not change the action. - pub fn clear_write_flag(&mut self) { - self.rights &= !FWPS_RIGHT_ACTION_WRITE; - } -} +#![allow(dead_code)] + +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPS_CLASSIFY_OUT_FLAG_ABSORB; + +const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; +const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; +const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; + +const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_CALLOUT_TERMINATING: u32 = + 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_CALLOUT_INSPECTION: u32 = + 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; +const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; +const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; +const FWP_ACTION_NONE: u32 = 0x00000007; +const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; + +const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; +const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; +const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; +const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; +const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; +const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; +const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; +const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; +const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; +const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; +const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; +const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; +const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; +const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; +const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; +const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; +const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; +const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; + +const FWPS_RIGHT_ACTION_WRITE: u32 = 0x00000001; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ClassifyOut { + action_type: u32, + _out_context: u64, // System use + _filter_id: u64, // System use + rights: u32, + flags: u32, + reserved: u32, +} + +impl ClassifyOut { + // Checks if write action flag is set. Indicates if the callout can change the action. + pub fn can_set_action(&self) -> bool { + self.rights & FWPS_RIGHT_ACTION_WRITE > 0 + } + + /// Set block action. Write flag should be cleared, after this. + pub fn action_block(&mut self) { + self.action_type = FWP_ACTION_BLOCK; + } + + /// Set permit action. + pub fn action_permit(&mut self) { + self.action_type = FWP_ACTION_PERMIT; + } + + // Set continue action. + pub fn action_continue(&mut self) { + self.action_type = FWP_ACTION_CONTINUE; + } + + // Set none action. + pub fn set_none(&mut self) { + self.action_type = FWP_ACTION_NONE; + } + + // Set absorb flag. This will drop the packet. Used when the packets will be reinjected in the future. + pub fn set_absorb(&mut self) { + self.flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB; + } + + // Clear the write flag permission. Next filter in the chain will not change the action. + pub fn clear_write_flag(&mut self) { + self.rights &= !FWPS_RIGHT_ACTION_WRITE; + } +} diff --git a/wdk/src/filter_engine/connect_request.rs b/wdk/src/filter_engine/connect_request.rs index a458a2c..8ec0688 100644 --- a/wdk/src/filter_engine/connect_request.rs +++ b/wdk/src/filter_engine/connect_request.rs @@ -1,79 +1,79 @@ -use core::ffi::c_void; - -use windows_sys::Win32::{ - Foundation::HANDLE, - Networking::WinSock::{AF_INET, AF_INET6}, -}; - -use crate::info; - -#[repr(C)] -pub(crate) struct FwpsConnectRequest0 { - pub(crate) local_address_and_port: [u8; 128], - pub(crate) remote_address_and_port: [u8; 128], - pub(crate) port_reservation_token: u64, - pub(crate) local_redirect_target_pid: u32, - pub(crate) previous_version: *const FwpsConnectRequest0, - pub(crate) modifier_filter_id: u64, - pub(crate) local_redirect_handle: HANDLE, - pub(crate) local_redirect_context: *mut c_void, - pub(crate) local_redirect_context_size: usize, -} - -#[repr(C)] -struct SocketAddressGeneric { - family: u16, - padding: [u8; 128 - 2], -} - -#[repr(C)] -struct SocketAddressIPv4 { - family: u16, - port: u16, - addr: [u8; 4], - zero: [u8; 8], - padding: [u8; 128 - 2 - 2 - 4 - 8], -} - -#[repr(C)] -struct SocketAddressIPv6 { - family: u16, - port: u16, - flowinfo: u16, - addr: [u8; 16], - scope_id: u32, - padding: [u8; 128 - 2 - 2 - 2 - 16 - 4], -} - -impl FwpsConnectRequest0 { - pub(crate) fn set_remote(&mut self, ip: &[u8], port: u16) { - info!("local: {:?}", self.local_address_and_port); - info!("remote: {:?}", self.remote_address_and_port); - unsafe { - let generic_socket: &mut SocketAddressGeneric = - core::mem::transmute(&mut self.remote_address_and_port); - match generic_socket.family { - AF_INET => { - info!("Socket type AF_INET"); - let socket_ipv4: &mut SocketAddressIPv4 = core::mem::transmute(generic_socket); - for i in 0..4 { - socket_ipv4.addr[i] = ip[i]; - } - socket_ipv4.port = u16::to_be(port); - } - AF_INET6 => { - info!("Socket type AF_INET6"); - let socket_ipv6: &mut SocketAddressIPv6 = core::mem::transmute(generic_socket); - for i in 0..16 { - socket_ipv6.addr[i] = ip[i]; - } - socket_ipv6.port = u16::to_be(port); - } - _ => { - info!("Unsupported socket type: {}", generic_socket.family); - } - } - } - info!("after: {:?}", self.remote_address_and_port); - } -} +use core::ffi::c_void; + +use windows_sys::Win32::{ + Foundation::HANDLE, + Networking::WinSock::{AF_INET, AF_INET6}, +}; + +use crate::info; + +#[repr(C)] +pub(crate) struct FwpsConnectRequest0 { + pub(crate) local_address_and_port: [u8; 128], + pub(crate) remote_address_and_port: [u8; 128], + pub(crate) port_reservation_token: u64, + pub(crate) local_redirect_target_pid: u32, + pub(crate) previous_version: *const FwpsConnectRequest0, + pub(crate) modifier_filter_id: u64, + pub(crate) local_redirect_handle: HANDLE, + pub(crate) local_redirect_context: *mut c_void, + pub(crate) local_redirect_context_size: usize, +} + +#[repr(C)] +struct SocketAddressGeneric { + family: u16, + padding: [u8; 128 - 2], +} + +#[repr(C)] +struct SocketAddressIPv4 { + family: u16, + port: u16, + addr: [u8; 4], + zero: [u8; 8], + padding: [u8; 128 - 2 - 2 - 4 - 8], +} + +#[repr(C)] +struct SocketAddressIPv6 { + family: u16, + port: u16, + flowinfo: u16, + addr: [u8; 16], + scope_id: u32, + padding: [u8; 128 - 2 - 2 - 2 - 16 - 4], +} + +impl FwpsConnectRequest0 { + pub(crate) fn set_remote(&mut self, ip: &[u8], port: u16) { + info!("local: {:?}", self.local_address_and_port); + info!("remote: {:?}", self.remote_address_and_port); + unsafe { + let generic_socket: &mut SocketAddressGeneric = + core::mem::transmute(&mut self.remote_address_and_port); + match generic_socket.family { + AF_INET => { + info!("Socket type AF_INET"); + let socket_ipv4: &mut SocketAddressIPv4 = core::mem::transmute(generic_socket); + for i in 0..4 { + socket_ipv4.addr[i] = ip[i]; + } + socket_ipv4.port = u16::to_be(port); + } + AF_INET6 => { + info!("Socket type AF_INET6"); + let socket_ipv6: &mut SocketAddressIPv6 = core::mem::transmute(generic_socket); + for i in 0..16 { + socket_ipv6.addr[i] = ip[i]; + } + socket_ipv6.port = u16::to_be(port); + } + _ => { + info!("Unsupported socket type: {}", generic_socket.family); + } + } + } + info!("after: {:?}", self.remote_address_and_port); + } +} diff --git a/wdk/src/filter_engine/ffi.rs b/wdk/src/filter_engine/ffi.rs index 1246582..2c65e55 100644 --- a/wdk/src/filter_engine/ffi.rs +++ b/wdk/src/filter_engine/ffi.rs @@ -1,260 +1,260 @@ -use crate::alloc::borrow::ToOwned; -use crate::ffi::FwpsCalloutClassifyFn; -use crate::ffi::{FwpsCalloutRegister3, FwpsCalloutUnregisterById0, FWPS_CALLOUT3, FWPS_FILTER2}; -use crate::utils::check_ntstatus; -use alloc::string::String; - -use core::mem::MaybeUninit; -use core::ptr; -use widestring::U16CString; - -use windows_sys::Wdk::Foundation::DEVICE_OBJECT; -use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::{ - FwpmCalloutAdd0, FwpmEngineClose0, FwpmEngineOpen0, FwpmFilterAdd0, FwpmFilterDeleteById0, - FwpmSubLayerAdd0, FwpmSubLayerDeleteByKey0, FwpmTransactionAbort0, FwpmTransactionBegin0, - FwpmTransactionCommit0, FWPM_CALLOUT0, FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT, - FWPM_DISPLAY_DATA0, FWPM_FILTER0, FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT, FWPM_SESSION0, - FWPM_SESSION_FLAG_DYNAMIC, FWPM_SUBLAYER0, FWP_UINT8, -}; -use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_WINNT; -use windows_sys::{ - core::GUID, - Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}, -}; - -use super::layer::Layer; - -pub(crate) fn create_filter_engine() -> Result { - unsafe { - let mut handle: HANDLE = INVALID_HANDLE_VALUE; - let mut wdf_session: FWPM_SESSION0 = MaybeUninit::zeroed().assume_init(); - wdf_session.flags = FWPM_SESSION_FLAG_DYNAMIC; - let status = FwpmEngineOpen0( - core::ptr::null(), - RPC_C_AUTHN_WINNT, - core::ptr::null_mut(), - &wdf_session, - &mut handle, - ); - check_ntstatus(status as i32)?; - - return Ok(handle); - } -} - -pub(crate) fn register_sublayer( - filter_engine_handle: HANDLE, - name: &str, - description: &str, - guid: u128, -) -> Result<(), String> { - let Ok(name) = U16CString::from_str(name) else { - return Err("invalid argument name".to_owned()); - }; - let Ok(description) = U16CString::from_str(description) else { - return Err("invalid argument description".to_owned()); - }; - - unsafe { - let mut sublayer: FWPM_SUBLAYER0 = MaybeUninit::zeroed().assume_init(); - sublayer.subLayerKey = GUID::from_u128(guid); - sublayer.displayData.name = name.as_ptr() as _; - sublayer.displayData.description = description.as_ptr() as _; - sublayer.flags = 0; - sublayer.weight = 0xFFFF; - - let status = FwpmSubLayerAdd0(filter_engine_handle, &sublayer, core::ptr::null_mut()); - check_ntstatus(status as i32)?; - - return Ok(()); - } -} - -pub(crate) fn unregister_sublayer(filter_engine_handle: HANDLE, guid: u128) -> Result<(), String> { - let guid = GUID::from_u128(guid); - unsafe { - let status = FwpmSubLayerDeleteByKey0(filter_engine_handle, ptr::addr_of!(guid)); - check_ntstatus(status as i32)?; - return Ok(()); - } -} - -unsafe extern "C" fn generic_notify( - _notify_type: u32, - _filter_key: *const GUID, - _filter: *mut FWPS_FILTER2, -) -> NTSTATUS { - return STATUS_SUCCESS; -} - -unsafe extern "C" fn generic_delete_notify(_layer_id: u16, _callout_id: u32, _flow_context: u64) {} - -pub(crate) fn register_callout( - device_object: *mut DEVICE_OBJECT, - filter_engine_handle: HANDLE, - name: &str, - description: &str, - guid: u128, - layer: Layer, - callout_fn: FwpsCalloutClassifyFn, -) -> Result { - let s_callout = FWPS_CALLOUT3 { - calloutKey: GUID::from_u128(guid), - flags: 0, - classifyFn: Some(callout_fn), - notifyFn: Some(generic_notify), - flowDeleteFn: Some(generic_delete_notify), - }; - - unsafe { - let mut callout_id: u32 = 0; - let status = FwpsCalloutRegister3(device_object as _, &s_callout, &mut callout_id); - - check_ntstatus(status)?; - - if let Err(err) = callout_add(filter_engine_handle, guid, layer, name, description) { - return Err(err); - } - - return Ok(callout_id); - } -} - -fn callout_add( - filter_engine_handle: HANDLE, - guid: u128, - layer: Layer, - name: &str, - description: &str, -) -> Result<(), String> { - let Ok(name) = U16CString::from_str(name) else { - return Err("invalid argument name".to_owned()); - }; - let Ok(description) = U16CString::from_str(description) else { - return Err("invalid argument description".to_owned()); - }; - let display_data = FWPM_DISPLAY_DATA0 { - name: name.as_ptr() as _, - description: description.as_ptr() as _, - }; - - unsafe { - let mut callout: FWPM_CALLOUT0 = MaybeUninit::zeroed().assume_init(); - callout.calloutKey = GUID::from_u128(guid); - callout.displayData = display_data; - callout.applicableLayer = layer.get_guid(); - callout.flags = FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT; - let status = FwpmCalloutAdd0( - filter_engine_handle, - &callout, - core::ptr::null_mut(), - core::ptr::null_mut(), - ); - check_ntstatus(status as i32)?; - }; - return Ok(()); -} - -pub(crate) fn unregister_callout(callout_id: u32) -> Result<(), String> { - unsafe { - let status = FwpsCalloutUnregisterById0(callout_id); - - check_ntstatus(status as i32)?; - return Ok(()); - } -} - -pub(crate) fn register_filter( - filter_engine_handle: HANDLE, - sublayer_guid: u128, - name: &str, - description: &str, - callout_guid: u128, - layer: Layer, - action: u32, - context: u64, -) -> Result { - let Ok(name) = U16CString::from_str(name) else { - return Err("invalid argument name".to_owned()); - }; - let Ok(description) = U16CString::from_str(description) else { - return Err("invalid argument description".to_owned()); - }; - let mut filter_id: u64 = 0; - unsafe { - let mut filter: FWPM_FILTER0 = MaybeUninit::zeroed().assume_init(); - filter.displayData.name = name.as_ptr() as _; - filter.displayData.description = description.as_ptr() as _; - filter.action.r#type = action; // Says this filter's callout MUST make a block/permit decision. Also see doc excerpts below. - filter.subLayerKey = GUID::from_u128(sublayer_guid); - filter.weight.r#type = FWP_UINT8; - filter.weight.Anonymous.uint8 = 15; // The weight of this filter within its sublayer - filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; - filter.numFilterConditions = 0; // If you specify 0, this filter invokes its callout for all traffic in its layer - filter.layerKey = layer.get_guid(); // This layer must match the layer that ExampleCallout is registered to - filter.action.Anonymous.calloutKey = GUID::from_u128(callout_guid); - filter.Anonymous.rawContext = context; - let status = FwpmFilterAdd0( - filter_engine_handle, - &filter, - core::ptr::null_mut(), - &mut filter_id, - ); - - check_ntstatus(status as i32)?; - - return Ok(filter_id); - } -} - -pub(crate) fn unregister_filter( - filter_engine_handle: HANDLE, - filter_id: u64, -) -> Result<(), String> { - unsafe { - let status = FwpmFilterDeleteById0(filter_engine_handle, filter_id); - check_ntstatus(status as i32)?; - return Ok(()); - } -} - -pub(crate) fn filter_engine_close(filter_engine_handle: HANDLE) -> Result<(), String> { - unsafe { - let status = FwpmEngineClose0(filter_engine_handle); - check_ntstatus(status as i32)?; - return Ok(()); - } -} - -/// Returns the raw NTSTATUS on failure, unlike the other wrappers here: the caller has to tell -/// `STATUS_FWP_TXN_IN_PROGRESS` (another transaction is open on this session, try again later) -/// apart from the failures that are not worth repeating. -pub(crate) fn filter_engine_transaction_begin( - filter_engine_handle: HANDLE, - flags: u32, -) -> Result<(), i32> { - unsafe { - let status = FwpmTransactionBegin0(filter_engine_handle, flags) as i32; - if status == STATUS_SUCCESS { - return Ok(()); - } - return Err(status); - } -} - -pub(crate) fn filter_engine_transaction_commit(filter_engine_handle: HANDLE) -> Result<(), String> { - unsafe { - let status = FwpmTransactionCommit0(filter_engine_handle); - check_ntstatus(status as i32)?; - return Ok(()); - } -} - -pub(crate) fn filter_engine_transaction_abort(filter_engine_handle: HANDLE) -> Result<(), String> { - unsafe { - let status = FwpmTransactionAbort0(filter_engine_handle); - check_ntstatus(status as i32)?; - return Ok(()); - } -} +use crate::alloc::borrow::ToOwned; +use crate::ffi::FwpsCalloutClassifyFn; +use crate::ffi::{FwpsCalloutRegister3, FwpsCalloutUnregisterById0, FWPS_CALLOUT3, FWPS_FILTER2}; +use crate::utils::check_ntstatus; +use alloc::string::String; + +use core::mem::MaybeUninit; +use core::ptr; +use widestring::U16CString; + +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; +use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::{ + FwpmCalloutAdd0, FwpmEngineClose0, FwpmEngineOpen0, FwpmFilterAdd0, FwpmFilterDeleteById0, + FwpmSubLayerAdd0, FwpmSubLayerDeleteByKey0, FwpmTransactionAbort0, FwpmTransactionBegin0, + FwpmTransactionCommit0, FWPM_CALLOUT0, FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT, + FWPM_DISPLAY_DATA0, FWPM_FILTER0, FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT, FWPM_SESSION0, + FWPM_SESSION_FLAG_DYNAMIC, FWPM_SUBLAYER0, FWP_UINT8, +}; +use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_WINNT; +use windows_sys::{ + core::GUID, + Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}, +}; + +use super::layer::Layer; + +pub(crate) fn create_filter_engine() -> Result { + unsafe { + let mut handle: HANDLE = INVALID_HANDLE_VALUE; + let mut wdf_session: FWPM_SESSION0 = MaybeUninit::zeroed().assume_init(); + wdf_session.flags = FWPM_SESSION_FLAG_DYNAMIC; + let status = FwpmEngineOpen0( + core::ptr::null(), + RPC_C_AUTHN_WINNT, + core::ptr::null_mut(), + &wdf_session, + &mut handle, + ); + check_ntstatus(status as i32)?; + + return Ok(handle); + } +} + +pub(crate) fn register_sublayer( + filter_engine_handle: HANDLE, + name: &str, + description: &str, + guid: u128, +) -> Result<(), String> { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + + unsafe { + let mut sublayer: FWPM_SUBLAYER0 = MaybeUninit::zeroed().assume_init(); + sublayer.subLayerKey = GUID::from_u128(guid); + sublayer.displayData.name = name.as_ptr() as _; + sublayer.displayData.description = description.as_ptr() as _; + sublayer.flags = 0; + sublayer.weight = 0xFFFF; + + let status = FwpmSubLayerAdd0(filter_engine_handle, &sublayer, core::ptr::null_mut()); + check_ntstatus(status as i32)?; + + return Ok(()); + } +} + +pub(crate) fn unregister_sublayer(filter_engine_handle: HANDLE, guid: u128) -> Result<(), String> { + let guid = GUID::from_u128(guid); + unsafe { + let status = FwpmSubLayerDeleteByKey0(filter_engine_handle, ptr::addr_of!(guid)); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +unsafe extern "C" fn generic_notify( + _notify_type: u32, + _filter_key: *const GUID, + _filter: *mut FWPS_FILTER2, +) -> NTSTATUS { + return STATUS_SUCCESS; +} + +unsafe extern "C" fn generic_delete_notify(_layer_id: u16, _callout_id: u32, _flow_context: u64) {} + +pub(crate) fn register_callout( + device_object: *mut DEVICE_OBJECT, + filter_engine_handle: HANDLE, + name: &str, + description: &str, + guid: u128, + layer: Layer, + callout_fn: FwpsCalloutClassifyFn, +) -> Result { + let s_callout = FWPS_CALLOUT3 { + calloutKey: GUID::from_u128(guid), + flags: 0, + classifyFn: Some(callout_fn), + notifyFn: Some(generic_notify), + flowDeleteFn: Some(generic_delete_notify), + }; + + unsafe { + let mut callout_id: u32 = 0; + let status = FwpsCalloutRegister3(device_object as _, &s_callout, &mut callout_id); + + check_ntstatus(status)?; + + if let Err(err) = callout_add(filter_engine_handle, guid, layer, name, description) { + return Err(err); + } + + return Ok(callout_id); + } +} + +fn callout_add( + filter_engine_handle: HANDLE, + guid: u128, + layer: Layer, + name: &str, + description: &str, +) -> Result<(), String> { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + let display_data = FWPM_DISPLAY_DATA0 { + name: name.as_ptr() as _, + description: description.as_ptr() as _, + }; + + unsafe { + let mut callout: FWPM_CALLOUT0 = MaybeUninit::zeroed().assume_init(); + callout.calloutKey = GUID::from_u128(guid); + callout.displayData = display_data; + callout.applicableLayer = layer.get_guid(); + callout.flags = FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT; + let status = FwpmCalloutAdd0( + filter_engine_handle, + &callout, + core::ptr::null_mut(), + core::ptr::null_mut(), + ); + check_ntstatus(status as i32)?; + }; + return Ok(()); +} + +pub(crate) fn unregister_callout(callout_id: u32) -> Result<(), String> { + unsafe { + let status = FwpsCalloutUnregisterById0(callout_id); + + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn register_filter( + filter_engine_handle: HANDLE, + sublayer_guid: u128, + name: &str, + description: &str, + callout_guid: u128, + layer: Layer, + action: u32, + context: u64, +) -> Result { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + let mut filter_id: u64 = 0; + unsafe { + let mut filter: FWPM_FILTER0 = MaybeUninit::zeroed().assume_init(); + filter.displayData.name = name.as_ptr() as _; + filter.displayData.description = description.as_ptr() as _; + filter.action.r#type = action; // Says this filter's callout MUST make a block/permit decision. Also see doc excerpts below. + filter.subLayerKey = GUID::from_u128(sublayer_guid); + filter.weight.r#type = FWP_UINT8; + filter.weight.Anonymous.uint8 = 15; // The weight of this filter within its sublayer + filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; + filter.numFilterConditions = 0; // If you specify 0, this filter invokes its callout for all traffic in its layer + filter.layerKey = layer.get_guid(); // This layer must match the layer that ExampleCallout is registered to + filter.action.Anonymous.calloutKey = GUID::from_u128(callout_guid); + filter.Anonymous.rawContext = context; + let status = FwpmFilterAdd0( + filter_engine_handle, + &filter, + core::ptr::null_mut(), + &mut filter_id, + ); + + check_ntstatus(status as i32)?; + + return Ok(filter_id); + } +} + +pub(crate) fn unregister_filter( + filter_engine_handle: HANDLE, + filter_id: u64, +) -> Result<(), String> { + unsafe { + let status = FwpmFilterDeleteById0(filter_engine_handle, filter_id); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_close(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmEngineClose0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +/// Returns the raw NTSTATUS on failure, unlike the other wrappers here: the caller has to tell +/// `STATUS_FWP_TXN_IN_PROGRESS` (another transaction is open on this session, try again later) +/// apart from the failures that are not worth repeating. +pub(crate) fn filter_engine_transaction_begin( + filter_engine_handle: HANDLE, + flags: u32, +) -> Result<(), i32> { + unsafe { + let status = FwpmTransactionBegin0(filter_engine_handle, flags) as i32; + if status == STATUS_SUCCESS { + return Ok(()); + } + return Err(status); + } +} + +pub(crate) fn filter_engine_transaction_commit(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmTransactionCommit0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_transaction_abort(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmTransactionAbort0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} diff --git a/wdk/src/filter_engine/layer.rs b/wdk/src/filter_engine/layer.rs index 1821634..7ea3391 100644 --- a/wdk/src/filter_engine/layer.rs +++ b/wdk/src/filter_engine/layer.rs @@ -1,1519 +1,1519 @@ -use core::fmt::Debug; - -use windows_sys::{ - core::GUID, - Win32::NetworkManagement::WindowsFilteringPlatform::{ - FWPM_LAYER_ALE_AUTH_CONNECT_V4, FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, - FWPM_LAYER_ALE_AUTH_CONNECT_V6, FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, - FWPM_LAYER_ALE_AUTH_LISTEN_V4, FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, - FWPM_LAYER_ALE_AUTH_LISTEN_V6, FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, - FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, - FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, - FWPM_LAYER_ALE_BIND_REDIRECT_V4, FWPM_LAYER_ALE_BIND_REDIRECT_V6, - FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, - FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, - FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, - FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, - FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, - FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, - FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, - FWPM_LAYER_DATAGRAM_DATA_V4, FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, - FWPM_LAYER_DATAGRAM_DATA_V6, FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, - FWPM_LAYER_INBOUND_ICMP_ERROR_V4, FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, - FWPM_LAYER_INBOUND_ICMP_ERROR_V6, FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, - FWPM_LAYER_INBOUND_IPPACKET_V4, FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, - FWPM_LAYER_INBOUND_IPPACKET_V6, FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, - FWPM_LAYER_INBOUND_TRANSPORT_V4, FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, - FWPM_LAYER_INBOUND_TRANSPORT_V6, FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, - FWPM_LAYER_IPFORWARD_V4, FWPM_LAYER_IPFORWARD_V4_DISCARD, FWPM_LAYER_IPFORWARD_V6, - FWPM_LAYER_IPFORWARD_V6_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, - FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, - FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V4, - FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V6, - FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V4, - FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V6, - FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, FWPM_LAYER_STREAM_V4, - FWPM_LAYER_STREAM_V4_DISCARD, FWPM_LAYER_STREAM_V6, FWPM_LAYER_STREAM_V6_DISCARD, - }, -}; - -#[repr(C)] -pub(crate) struct Value { - pub(crate) value_type: ValueType, - pub(crate) value: ValueData, -} - -#[repr(C)] -pub(crate) struct IncomingValues { - pub(crate) layer_id: u16, - pub(crate) value_count: u32, - pub(crate) incoming_value_array: *const Value, -} - -#[repr(C)] -pub(crate) union ValueData { - pub(crate) uint8: u8, - pub(crate) uint16: u16, - pub(crate) uint32: u32, - pub(crate) uint64: *const u64, - pub(crate) byte_array16: *const [u8; 16], - // TODO: add the rest of possible values. -} - -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub enum ValueType { - FwpEmpty = 0, - FwpUint8 = 1, - FwpUint16 = 2, - FwpUint32 = 3, - FwpUint64 = 4, - FwpInt8 = 5, - FwpInt16 = 6, - FwpInt32 = 7, - FwpInt64 = 8, - FwpFloat = 9, - FwpDouble = 10, - FwpByteArray16Type = 11, - FwpByteBlobType = 12, - FwpSid = 13, - FwpSecurityDescriptorType = 14, - FwpTokenInformationType = 15, - FwpTokenAccessInformationType = 16, - FwpUnicodeStringType = 17, - FwpByteArray6Type = 18, - FwpSingleDataTypeMax = 0xff, - FwpV4AddrMask = 0xff + 1, - FwpV6AddrMask = 0xff + 2, - FwpRangeType = 0xff + 3, - FwpDataTypeMax = 0xff + 4, -} - -#[derive(Copy, Clone, Debug)] -pub enum Layer { - InboundIppacketV4, - InboundIppacketV4Discard, - InboundIppacketV6, - InboundIppacketV6Discard, - OutboundIppacketV4, - OutboundIppacketV4Discard, - OutboundIppacketV6, - OutboundIppacketV6Discard, - IpforwardV4, - IpforwardV4Discard, - IpforwardV6, - IpforwardV6Discard, - InboundTransportV4, - InboundTransportV4Discard, - InboundTransportV6, - InboundTransportV6Discard, - OutboundTransportV4, - OutboundTransportV4Discard, - OutboundTransportV6, - OutboundTransportV6Discard, - StreamV4, - StreamV4Discard, - StreamV6, - StreamV6Discard, - DatagramDataV4, - DatagramDataV4Discard, - DatagramDataV6, - DatagramDataV6Discard, - InboundIcmpErrorV4, - InboundIcmpErrorV4Discard, - InboundIcmpErrorV6, - InboundIcmpErrorV6Discard, - OutboundIcmpErrorV4, - OutboundIcmpErrorV4Discard, - OutboundIcmpErrorV6, - OutboundIcmpErrorV6Discard, - AleResourceAssignmentV4, - AleResourceAssignmentV4Discard, - AleResourceAssignmentV6, - AleResourceAssignmentV6Discard, - AleAuthListenV4, - AleAuthListenV4Discard, - AleAuthListenV6, - AleAuthListenV6Discard, - AleAuthRecvAcceptV4, - AleAuthRecvAcceptV4Discard, - AleAuthRecvAcceptV6, - AleAuthRecvAcceptV6Discard, - AleAuthConnectV4, - AleAuthConnectV4Discard, - AleAuthConnectV6, - AleAuthConnectV6Discard, - AleFlowEstablishedV4, - AleFlowEstablishedV4Discard, - AleFlowEstablishedV6, - AleFlowEstablishedV6Discard, - AleConnectRedirectV4, - AleConnectRedirectV6, - AleBindRedirectV4, - AleBindRedirectV6, - AleResourceReleaseV4, - AleResourceReleaseV6, - AleEndpointClosureV4, - AleEndpointClosureV6, -} - -impl Layer { - pub fn get_guid(&self) -> GUID { - match self { - Layer::InboundIppacketV4 => FWPM_LAYER_INBOUND_IPPACKET_V4, - Layer::InboundIppacketV4Discard => FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, - Layer::InboundIppacketV6 => FWPM_LAYER_INBOUND_IPPACKET_V6, - Layer::InboundIppacketV6Discard => FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, - Layer::OutboundIppacketV4 => FWPM_LAYER_OUTBOUND_IPPACKET_V4, - Layer::OutboundIppacketV4Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, - Layer::OutboundIppacketV6 => FWPM_LAYER_OUTBOUND_IPPACKET_V6, - Layer::OutboundIppacketV6Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, - Layer::IpforwardV4 => FWPM_LAYER_IPFORWARD_V4, - Layer::IpforwardV4Discard => FWPM_LAYER_IPFORWARD_V4_DISCARD, - Layer::IpforwardV6 => FWPM_LAYER_IPFORWARD_V6, - Layer::IpforwardV6Discard => FWPM_LAYER_IPFORWARD_V6_DISCARD, - Layer::InboundTransportV4 => FWPM_LAYER_INBOUND_TRANSPORT_V4, - Layer::InboundTransportV4Discard => FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, - Layer::InboundTransportV6 => FWPM_LAYER_INBOUND_TRANSPORT_V6, - Layer::InboundTransportV6Discard => FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, - Layer::OutboundTransportV4 => FWPM_LAYER_OUTBOUND_TRANSPORT_V4, - Layer::OutboundTransportV4Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, - Layer::OutboundTransportV6 => FWPM_LAYER_OUTBOUND_TRANSPORT_V6, - Layer::OutboundTransportV6Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, - Layer::StreamV4 => FWPM_LAYER_STREAM_V4, - Layer::StreamV4Discard => FWPM_LAYER_STREAM_V4_DISCARD, - Layer::StreamV6 => FWPM_LAYER_STREAM_V6, - Layer::StreamV6Discard => FWPM_LAYER_STREAM_V6_DISCARD, - Layer::DatagramDataV4 => FWPM_LAYER_DATAGRAM_DATA_V4, - Layer::DatagramDataV4Discard => FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, - Layer::DatagramDataV6 => FWPM_LAYER_DATAGRAM_DATA_V6, - Layer::DatagramDataV6Discard => FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, - Layer::InboundIcmpErrorV4 => FWPM_LAYER_INBOUND_ICMP_ERROR_V4, - Layer::InboundIcmpErrorV4Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, - Layer::InboundIcmpErrorV6 => FWPM_LAYER_INBOUND_ICMP_ERROR_V6, - Layer::InboundIcmpErrorV6Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, - Layer::OutboundIcmpErrorV4 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, - Layer::OutboundIcmpErrorV4Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, - Layer::OutboundIcmpErrorV6 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, - Layer::OutboundIcmpErrorV6Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, - Layer::AleResourceAssignmentV4 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, - Layer::AleResourceAssignmentV4Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, - Layer::AleResourceAssignmentV6 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, - Layer::AleResourceAssignmentV6Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, - Layer::AleAuthListenV4 => FWPM_LAYER_ALE_AUTH_LISTEN_V4, - Layer::AleAuthListenV4Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, - Layer::AleAuthListenV6 => FWPM_LAYER_ALE_AUTH_LISTEN_V6, - Layer::AleAuthListenV6Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, - Layer::AleAuthRecvAcceptV4 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, - Layer::AleAuthRecvAcceptV4Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, - Layer::AleAuthRecvAcceptV6 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, - Layer::AleAuthRecvAcceptV6Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, - Layer::AleAuthConnectV4 => FWPM_LAYER_ALE_AUTH_CONNECT_V4, - Layer::AleAuthConnectV4Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, - Layer::AleAuthConnectV6 => FWPM_LAYER_ALE_AUTH_CONNECT_V6, - Layer::AleAuthConnectV6Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, - Layer::AleFlowEstablishedV4 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, - Layer::AleFlowEstablishedV4Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, - Layer::AleFlowEstablishedV6 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, - Layer::AleFlowEstablishedV6Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, - Layer::AleConnectRedirectV4 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, - Layer::AleConnectRedirectV6 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, - Layer::AleBindRedirectV4 => FWPM_LAYER_ALE_BIND_REDIRECT_V4, - Layer::AleBindRedirectV6 => FWPM_LAYER_ALE_BIND_REDIRECT_V6, - Layer::AleResourceReleaseV4 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, - Layer::AleResourceReleaseV6 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, - Layer::AleEndpointClosureV4 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, - Layer::AleEndpointClosureV6 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, - } - } -} - -#[repr(usize)] -pub enum FieldsInboundIppacketV4 { - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundIppacketV6 { - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundIppacketV4 { - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundIppacketV6 { - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpforwardV4 { - IpSourceAddress, - IpDestinationAddress, - IpDestinationAddressType, - IpLocalInterface, - IpForwardInterface, - SourceInterfaceIndex, - SourceSubInterfaceIndex, - DestinationInterfaceIndex, - DestinationSubInterfaceIndex, - Flags, - IpPhysicalArrivalInterface, - ArrivalInterfaceProfileId, - IpPhysicalNexthopInterface, - NexthopInterfaceProfileId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpforwardV6 { - IpSourceAddress, - IpDestinationAddress, - IpDestinationAddressType, - IpLocalInterface, - IpForwardInterface, - SourceInterfaceIndex, - SourceSubInterfaceIndex, - DestinationInterfaceIndex, - DestinationSubInterfaceIndex, - Flags, - IpPhysicalArrivalInterface, - ArrivalInterfaceProfileId, - IpPhysicalNexthopInterface, - NexthopInterfaceProfileId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundTransportV4 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - ProfileId, - IpsecSecurityRealmId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundTransportFas { - FieldInboundTransportFastMax, -} - -#[repr(usize)] -pub enum FieldsOutboundTransportFas { - FieldOutboundTransportFastMax, -} - -#[repr(usize)] -pub enum FieldsInboundTransportV6 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Flags, - InterfaceType, - TunnelType, - ProfileId, - IpsecSecurityRealmId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundTransportV4 { - IpProtocol, - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - IpDestinationAddressType, - Flags, - InterfaceType, - TunnelType, - ProfileId, - IpsecSecurityRealmId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundTransportV6 { - IpProtocol, - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - IpDestinationAddressType, - Flags, - InterfaceType, - TunnelType, - ProfileId, - IpsecSecurityRealmId, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsStreamV4 { - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - Direction, - Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsStreamV6 { - IpLocalAddress, - IpLocalAddressType, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - Direction, - Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsDatagramDataV4 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Direction, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsDatagramDataV6 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Direction, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsStreamPacketV4 { - IpLocalAddress, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Direction, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsStreamPacketV6 { - IpLocalAddress, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - InterfaceIndex, - SubInterfaceIndex, - Direction, - Flags, - InterfaceType, - TunnelType, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundIcmpErrorV4 { - EmbeddedProtocol, - IpLocalAddress, - IpRemoteAddress, - EmbeddedRemoteAddress, - EmbeddedLocalAddressType, - EmbeddedLocalPort, - EmbeddedRemotePort, - IpLocalInterface, - IcmpType, - IcmpCode, - InterfaceIndex, // of local/delivery interface - SubInterfaceIndex, // of arrival interface - InterfaceType, // of local/delivery interface - TunnelType, // of local/delivery interface - IpArrivalInterface, - ArrivalInterfaceIndex, - ArrivalInterfaceType, - ArrivalTunnelType, - Flags, - ArrivalInterfaceProfileId, - InterfaceQuarantineEpoch, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundIcmpErrorV6 { - EmbeddedProtocol, - IpLocalAddress, - IpRemoteAddress, - EmbeddedRemoteAddress, - EmbeddedLocalAddressType, - EmbeddedLocalPort, - EmbeddedRemotePort, - IpLocalInterface, - IcmpType, - IcmpCode, - InterfaceIndex, // of local/delivery interface - SubInterfaceIndex, // of arrival interface - InterfaceType, // of local/delivery interface - TunnelType, // of local/delivery interface - IpArrivalInterface, - ArrivalInterfaceIndex, - ArrivalInterfaceType, - ArrivalTunnelType, - Flags, - ArrivalInterfaceProfileId, - InterfaceQuarantineEpoch, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundIcmpErrorV4 { - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalInterface, - IcmpType, - IcmpCode, - InterfaceIndex, - SubInterfaceIndex, - InterfaceType, - TunnelType, - Flags, - NexthopInterfaceProfileId, - InterfaceQuarantineEpoch, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundIcmpErrorV6 { - IpLocalAddress, - IpRemoteAddress, - IpLocalAddressType, - IpLocalInterface, - IpLocalPort, - IpRemotePort, - InterfaceIndex, - SubInterfaceIndex, - InterfaceType, - TunnelType, - Flags, - NexthopInterfaceProfileId, - InterfaceQuarantineEpoch, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleResourceAssignmentV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - AlePromiscuousMode, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - LocalInterfaceProfileId, - SioFirewallSocketProperty, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Max, -} - -#[repr(usize)] -pub enum FieldsAleResourceAssignmentV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - AlePromiscuousMode, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - LocalInterfaceProfileId, - SioFirewallSocketProperty, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Max, -} - -#[repr(usize)] -pub enum FieldsAleResourceReleaseV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpLocalInterface, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleResourceReleaseV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpLocalInterface, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleEndpointClosureV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - IpLocalInterface, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleEndpointClosureV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - IpLocalInterface, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthListenV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - LocalInterfaceProfileId, - SioFirewallSocketProperty, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthListenV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - LocalInterfaceProfileId, - SioFirewallSocketProperty, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthRecvAcceptV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpLocalInterface, - Flags, - SioFirewallSystemPort, - NapContext, - InterfaceType, // of local/delivery interface - TunnelType, // of local/delivery interface - InterfaceIndex, // of local/delivery interface - SubInterfaceIndex, // of arrival interface - IpArrivalInterface, - ArrivalInterfaceType, - ArrivalTunnelType, - ArrivalInterfaceIndex, - NexthopSubInterfaceIndex, - IpNexthopInterface, - NexthopInterfaceType, - NexthopTunnelType, - NexthopInterfaceIndex, - OriginalProfileId, - CurrentProfileId, - ReauthorizeReason, - OriginalIcmpType, - InterfaceQuarantineEpoch, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthRecvAcceptV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpLocalInterface, - Flags, - SioFirewallSystemPort, - NapContext, - InterfaceType, // of local/delivery interface - TunnelType, // of local/delivery interface - InterfaceIndex, // of local/delivery interface - SubInterfaceIndex, // of arrival interface - IpArrivalInterface, - ArrivalInterfaceType, - ArrivalTunnelType, - ArrivalInterfaceIndex, - NexthopSubInterfaceIndex, - IpNexthopInterface, - NexthopInterfaceType, - NexthopTunnelType, - NexthopInterfaceIndex, - OriginalProfileId, - CurrentProfileId, - ReauthorizeReason, - OriginalIcmpType, - InterfaceQuarantineEpoch, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsAleBindRedirectV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleBindRedirectV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - Flags, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleConnectRedirectV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpDestinationAddressType, - IpRemotePort, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleConnectRedirectV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpDestinationAddressType, - IpRemotePort, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthConnectV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpDestinationAddressType, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - InterfaceIndex, - SubInterfaceIndex, - IpArrivalInterface, - ArrivalInterfaceType, - ArrivalTunnelType, - ArrivalInterfaceIndex, - NexthopSubInterfaceIndex, - IpNexthopInterface, - NexthopInterfaceType, - NexthopTunnelType, - NexthopInterfaceIndex, - OriginalProfileId, - CurrentProfileId, - ReauthorizeReason, - PeerName, - OriginalIcmpType, - InterfaceQuarantineEpoch, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - AleEffectiveName, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsAleAuthConnectV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpDestinationAddressType, - IpLocalInterface, - Flags, - InterfaceType, - TunnelType, - InterfaceIndex, - SubInterfaceIndex, - IpArrivalInterface, - ArrivalInterfaceType, - ArrivalTunnelType, - ArrivalInterfaceIndex, - NexthopSubInterfaceIndex, - IpNexthopInterface, - NexthopInterfaceType, - NexthopTunnelType, - NexthopInterfaceIndex, - OriginalProfileId, - CurrentProfileId, - ReauthorizeReason, - PeerName, - OriginalIcmpType, - InterfaceQuarantineEpoch, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - AleEffectiveName, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsAleFlowEstablishedV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpDestinationAddressType, - IpLocalInterface, - Direction, - InterfaceType, - TunnelType, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsAleFlowEstablishedV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpRemotePort, - AleRemoteUserId, - AleRemoteMachineId, - IpDestinationAddressType, - IpLocalInterface, - Direction, - InterfaceType, - TunnelType, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - // - // These reserved fields MUST be in this order. DO NOT change their order - // - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Max, -} - -#[repr(usize)] -pub enum FieldsNameResolutionCacheV4 { - AleUserId, - AleAppId, - IpRemoteAddress, - PeerName, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsNameResolutionCacheV6 { - AleUserId, - AleAppId, - IpRemoteAddress, - PeerName, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundMacFrameEthernet { - InterfaceMacAddress, - MacLocalAddress, - MacRemoteAddress, - MacLocalAddressType, - MacRemoteAddressType, - EtherType, - VlanId, - Interface, - InterfaceIndex, - NdisPort, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundMacFrameEthernet { - InterfaceMacAddress, - MacLocalAddress, - MacRemoteAddress, - MacLocalAddressType, - MacRemoteAddressType, - EtherType, - VlanId, - Interface, - InterfaceIndex, - NdisPort, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundMacFrameNative { - NdisMediaType, - NdisPhysicalMediaType, - Interface, - InterfaceType, - InterfaceIndex, - NdisPort, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundMacFrameNativeFast { - FastMax, -} - -#[repr(usize)] -pub enum FieldsOutboundMacFrameNative { - NdisMediaType, - NdisPhysicalMediaType, - Interface, - InterfaceType, - InterfaceIndex, - NdisPort, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundMacFrameNativeFast { - Max, -} - -#[repr(usize)] -pub enum FieldsIngressVswitchEthernet { - MacSourceAddress, - MacSourceAddressType, - MacDestinationAddress, - MacDestinationAddressType, - EtherType, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsEgressVswitchEthernet { - MacSourceAddress, - MacSourceAddressType, - MacDestinationAddress, - MacDestinationAddressType, - EtherType, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - VswitchDestinationInterfaceId, - VswitchDestinationInterfaceType, - VswitchDestinationVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsIngressVswitchTransportV4 { - IpSourceAddress, - IpDestinationAddress, - IpProtocol, - IpSourcePort, - IpDestinationPort, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsIngressVswitchTransportV6 { - IpSourceAddress, - IpDestinationAddress, - IpProtocol, - IpSourcePort, - IpDestinationPort, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsEgressVswitchTransportV4 { - IpSourceAddress, - IpDestinationAddress, - IpProtocol, - IpSourcePort, - IpDestinationPort, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - VswitchDestinationInterfaceId, - VswitchDestinationInterfaceType, - VswitchDestinationVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsEgressVswitchTransportV6 { - IpSourceAddress, - IpDestinationAddress, - IpProtocol, - IpSourcePort, - IpDestinationPort, - VlanId, - VswitchTenantNetworkId, - VswitchId, - VswitchNetworkType, - VswitchSourceInterfaceId, - VswitchSourceInterfaceType, - VswitchSourceVmId, - VswitchDestinationInterfaceId, - VswitchDestinationInterfaceType, - VswitchDestinationVmId, - L2Flags, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpsecKmDemuxV4 { - IpLocalAddress, - IpRemoteAddress, - QmMode, - IpLocalInterface, - CurrentProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpsecKmDemuxV6 { - IpLocalAddress, - IpRemoteAddress, - QmMode, - IpLocalInterface, - CurrentProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpsecV4 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - ProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsIpsecV6 { - IpProtocol, - IpLocalAddress, - IpRemoteAddress, - IpLocalPort, - IpRemotePort, - IpLocalInterface, - ProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsIkeextV4 { - IpLocalAddress, - IpRemoteAddress, - IpLocalInterface, - ProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsIkeextV6 { - IpLocalAddress, - IpRemoteAddress, - IpLocalInterface, - ProfileId, - IpsecSecurityRealmId, - Max, -} - -#[repr(usize)] -pub enum FieldsRpcUm { - RemoteUserToken, - AuthLevel, - AuthType, - DcomAppId, - IfFlag, - IfUuid, - IfVersion, - ImageName, - LocalAddrV4, - LocalAddrV6, - LocalPort, - Max, - Pipe, - Protocol, - RemoteAddrV4, - RemoteAddrV6, - SecEncryptAlgorithm, - SecKeySize, -} - -#[repr(usize)] -pub enum FieldsRpcEpmap { - RemoteUserToken, - IfUuid, - IfVersion, - Protocol, - AuthType, - AuthLevel, - SecEncryptAlgorithm, - SecKeySize, - LocalAddrV4, - LocalAddrV6, - LocalPort, - Pipe, - RemoteAddrV4, - RemoteAddrV6, - Max, -} - -#[repr(usize)] -pub enum FieldsRpcEpAdd { - ProcessWithRpcIfUuid, - Protocol, - EpValue, - EpFlags, - Max, -} - -#[repr(usize)] -pub enum FieldsRpcProxyConn { - ClientToken, - ServerName, - ServerPort, - ProxyAuthType, - ClientCertKeyLength, - ClientCertOid, - Max, -} - -#[repr(usize)] -pub enum FieldsRpcProxyIf { - ClientToken, - IfUuid, - IfVersion, - ServerName, - ServerPort, - ProxyAuthType, - ClientCertKeyLength, - ClientCertOid, - Max, -} - -#[repr(usize)] -pub enum FieldsKmAuthorization { - RemoteId, - AuthenticationType, - KmType, - Direction, - KmMode, - IpsecPolicyKey, - NapContext, - Max, -} - -#[repr(usize)] -pub enum FieldsInboundReserved2 { - Reserved0, - Reserved1, - Reserved2, - Reserved3, - Reserved4, - Reserved5, - Reserved6, - Reserved7, - Reserved8, - Reserved9, - Reserved10, - Reserved11, - Reserved12, - Reserved13, - Reserved14, - Reserved15, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundNetworkConnectionPolicyV4 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpDestinationAddressType, - IpRemotePort, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} - -#[repr(usize)] -pub enum FieldsOutboundNetworkConnectionPolicyV6 { - AleAppId, - AleUserId, - IpLocalAddress, - IpLocalAddressType, - IpLocalPort, - IpProtocol, - IpRemoteAddress, - IpDestinationAddressType, - IpRemotePort, - Flags, - AleOriginalAppId, - AlePackageId, - AleSecurityAttributeFqbnValue, - CompartmentId, - Max, -} +use core::fmt::Debug; + +use windows_sys::{ + core::GUID, + Win32::NetworkManagement::WindowsFilteringPlatform::{ + FWPM_LAYER_ALE_AUTH_CONNECT_V4, FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_CONNECT_V6, FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, + FWPM_LAYER_ALE_AUTH_LISTEN_V4, FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_LISTEN_V6, FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, + FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, + FWPM_LAYER_ALE_BIND_REDIRECT_V4, FWPM_LAYER_ALE_BIND_REDIRECT_V6, + FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, + FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, + FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, + FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, + FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, + FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, + FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, + FWPM_LAYER_DATAGRAM_DATA_V4, FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, + FWPM_LAYER_DATAGRAM_DATA_V6, FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, + FWPM_LAYER_INBOUND_ICMP_ERROR_V4, FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, + FWPM_LAYER_INBOUND_ICMP_ERROR_V6, FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, + FWPM_LAYER_INBOUND_IPPACKET_V4, FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, + FWPM_LAYER_INBOUND_IPPACKET_V6, FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, + FWPM_LAYER_INBOUND_TRANSPORT_V4, FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, + FWPM_LAYER_INBOUND_TRANSPORT_V6, FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, + FWPM_LAYER_IPFORWARD_V4, FWPM_LAYER_IPFORWARD_V4_DISCARD, FWPM_LAYER_IPFORWARD_V6, + FWPM_LAYER_IPFORWARD_V6_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, + FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, + FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V4, + FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V6, + FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, FWPM_LAYER_STREAM_V4, + FWPM_LAYER_STREAM_V4_DISCARD, FWPM_LAYER_STREAM_V6, FWPM_LAYER_STREAM_V6_DISCARD, + }, +}; + +#[repr(C)] +pub(crate) struct Value { + pub(crate) value_type: ValueType, + pub(crate) value: ValueData, +} + +#[repr(C)] +pub(crate) struct IncomingValues { + pub(crate) layer_id: u16, + pub(crate) value_count: u32, + pub(crate) incoming_value_array: *const Value, +} + +#[repr(C)] +pub(crate) union ValueData { + pub(crate) uint8: u8, + pub(crate) uint16: u16, + pub(crate) uint32: u32, + pub(crate) uint64: *const u64, + pub(crate) byte_array16: *const [u8; 16], + // TODO: add the rest of possible values. +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum ValueType { + FwpEmpty = 0, + FwpUint8 = 1, + FwpUint16 = 2, + FwpUint32 = 3, + FwpUint64 = 4, + FwpInt8 = 5, + FwpInt16 = 6, + FwpInt32 = 7, + FwpInt64 = 8, + FwpFloat = 9, + FwpDouble = 10, + FwpByteArray16Type = 11, + FwpByteBlobType = 12, + FwpSid = 13, + FwpSecurityDescriptorType = 14, + FwpTokenInformationType = 15, + FwpTokenAccessInformationType = 16, + FwpUnicodeStringType = 17, + FwpByteArray6Type = 18, + FwpSingleDataTypeMax = 0xff, + FwpV4AddrMask = 0xff + 1, + FwpV6AddrMask = 0xff + 2, + FwpRangeType = 0xff + 3, + FwpDataTypeMax = 0xff + 4, +} + +#[derive(Copy, Clone, Debug)] +pub enum Layer { + InboundIppacketV4, + InboundIppacketV4Discard, + InboundIppacketV6, + InboundIppacketV6Discard, + OutboundIppacketV4, + OutboundIppacketV4Discard, + OutboundIppacketV6, + OutboundIppacketV6Discard, + IpforwardV4, + IpforwardV4Discard, + IpforwardV6, + IpforwardV6Discard, + InboundTransportV4, + InboundTransportV4Discard, + InboundTransportV6, + InboundTransportV6Discard, + OutboundTransportV4, + OutboundTransportV4Discard, + OutboundTransportV6, + OutboundTransportV6Discard, + StreamV4, + StreamV4Discard, + StreamV6, + StreamV6Discard, + DatagramDataV4, + DatagramDataV4Discard, + DatagramDataV6, + DatagramDataV6Discard, + InboundIcmpErrorV4, + InboundIcmpErrorV4Discard, + InboundIcmpErrorV6, + InboundIcmpErrorV6Discard, + OutboundIcmpErrorV4, + OutboundIcmpErrorV4Discard, + OutboundIcmpErrorV6, + OutboundIcmpErrorV6Discard, + AleResourceAssignmentV4, + AleResourceAssignmentV4Discard, + AleResourceAssignmentV6, + AleResourceAssignmentV6Discard, + AleAuthListenV4, + AleAuthListenV4Discard, + AleAuthListenV6, + AleAuthListenV6Discard, + AleAuthRecvAcceptV4, + AleAuthRecvAcceptV4Discard, + AleAuthRecvAcceptV6, + AleAuthRecvAcceptV6Discard, + AleAuthConnectV4, + AleAuthConnectV4Discard, + AleAuthConnectV6, + AleAuthConnectV6Discard, + AleFlowEstablishedV4, + AleFlowEstablishedV4Discard, + AleFlowEstablishedV6, + AleFlowEstablishedV6Discard, + AleConnectRedirectV4, + AleConnectRedirectV6, + AleBindRedirectV4, + AleBindRedirectV6, + AleResourceReleaseV4, + AleResourceReleaseV6, + AleEndpointClosureV4, + AleEndpointClosureV6, +} + +impl Layer { + pub fn get_guid(&self) -> GUID { + match self { + Layer::InboundIppacketV4 => FWPM_LAYER_INBOUND_IPPACKET_V4, + Layer::InboundIppacketV4Discard => FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, + Layer::InboundIppacketV6 => FWPM_LAYER_INBOUND_IPPACKET_V6, + Layer::InboundIppacketV6Discard => FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, + Layer::OutboundIppacketV4 => FWPM_LAYER_OUTBOUND_IPPACKET_V4, + Layer::OutboundIppacketV4Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, + Layer::OutboundIppacketV6 => FWPM_LAYER_OUTBOUND_IPPACKET_V6, + Layer::OutboundIppacketV6Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, + Layer::IpforwardV4 => FWPM_LAYER_IPFORWARD_V4, + Layer::IpforwardV4Discard => FWPM_LAYER_IPFORWARD_V4_DISCARD, + Layer::IpforwardV6 => FWPM_LAYER_IPFORWARD_V6, + Layer::IpforwardV6Discard => FWPM_LAYER_IPFORWARD_V6_DISCARD, + Layer::InboundTransportV4 => FWPM_LAYER_INBOUND_TRANSPORT_V4, + Layer::InboundTransportV4Discard => FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, + Layer::InboundTransportV6 => FWPM_LAYER_INBOUND_TRANSPORT_V6, + Layer::InboundTransportV6Discard => FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, + Layer::OutboundTransportV4 => FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + Layer::OutboundTransportV4Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, + Layer::OutboundTransportV6 => FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + Layer::OutboundTransportV6Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, + Layer::StreamV4 => FWPM_LAYER_STREAM_V4, + Layer::StreamV4Discard => FWPM_LAYER_STREAM_V4_DISCARD, + Layer::StreamV6 => FWPM_LAYER_STREAM_V6, + Layer::StreamV6Discard => FWPM_LAYER_STREAM_V6_DISCARD, + Layer::DatagramDataV4 => FWPM_LAYER_DATAGRAM_DATA_V4, + Layer::DatagramDataV4Discard => FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, + Layer::DatagramDataV6 => FWPM_LAYER_DATAGRAM_DATA_V6, + Layer::DatagramDataV6Discard => FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, + Layer::InboundIcmpErrorV4 => FWPM_LAYER_INBOUND_ICMP_ERROR_V4, + Layer::InboundIcmpErrorV4Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, + Layer::InboundIcmpErrorV6 => FWPM_LAYER_INBOUND_ICMP_ERROR_V6, + Layer::InboundIcmpErrorV6Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, + Layer::OutboundIcmpErrorV4 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, + Layer::OutboundIcmpErrorV4Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, + Layer::OutboundIcmpErrorV6 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, + Layer::OutboundIcmpErrorV6Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, + Layer::AleResourceAssignmentV4 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, + Layer::AleResourceAssignmentV4Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, + Layer::AleResourceAssignmentV6 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, + Layer::AleResourceAssignmentV6Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, + Layer::AleAuthListenV4 => FWPM_LAYER_ALE_AUTH_LISTEN_V4, + Layer::AleAuthListenV4Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, + Layer::AleAuthListenV6 => FWPM_LAYER_ALE_AUTH_LISTEN_V6, + Layer::AleAuthListenV6Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, + Layer::AleAuthRecvAcceptV4 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, + Layer::AleAuthRecvAcceptV4Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, + Layer::AleAuthRecvAcceptV6 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, + Layer::AleAuthRecvAcceptV6Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, + Layer::AleAuthConnectV4 => FWPM_LAYER_ALE_AUTH_CONNECT_V4, + Layer::AleAuthConnectV4Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, + Layer::AleAuthConnectV6 => FWPM_LAYER_ALE_AUTH_CONNECT_V6, + Layer::AleAuthConnectV6Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, + Layer::AleFlowEstablishedV4 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, + Layer::AleFlowEstablishedV4Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, + Layer::AleFlowEstablishedV6 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, + Layer::AleFlowEstablishedV6Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, + Layer::AleConnectRedirectV4 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, + Layer::AleConnectRedirectV6 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, + Layer::AleBindRedirectV4 => FWPM_LAYER_ALE_BIND_REDIRECT_V4, + Layer::AleBindRedirectV6 => FWPM_LAYER_ALE_BIND_REDIRECT_V6, + Layer::AleResourceReleaseV4 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, + Layer::AleResourceReleaseV6 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, + Layer::AleEndpointClosureV4 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, + Layer::AleEndpointClosureV6 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, + } + } +} + +#[repr(usize)] +pub enum FieldsInboundIppacketV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIppacketV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIppacketV4 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIppacketV6 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpforwardV4 { + IpSourceAddress, + IpDestinationAddress, + IpDestinationAddressType, + IpLocalInterface, + IpForwardInterface, + SourceInterfaceIndex, + SourceSubInterfaceIndex, + DestinationInterfaceIndex, + DestinationSubInterfaceIndex, + Flags, + IpPhysicalArrivalInterface, + ArrivalInterfaceProfileId, + IpPhysicalNexthopInterface, + NexthopInterfaceProfileId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpforwardV6 { + IpSourceAddress, + IpDestinationAddress, + IpDestinationAddressType, + IpLocalInterface, + IpForwardInterface, + SourceInterfaceIndex, + SourceSubInterfaceIndex, + DestinationInterfaceIndex, + DestinationSubInterfaceIndex, + Flags, + IpPhysicalArrivalInterface, + ArrivalInterfaceProfileId, + IpPhysicalNexthopInterface, + NexthopInterfaceProfileId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundTransportV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundTransportFas { + FieldInboundTransportFastMax, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportFas { + FieldOutboundTransportFastMax, +} + +#[repr(usize)] +pub enum FieldsInboundTransportV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportV4 { + IpProtocol, + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + IpDestinationAddressType, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportV6 { + IpProtocol, + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + IpDestinationAddressType, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamV4 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + Direction, + Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamV6 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + Direction, + Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsDatagramDataV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsDatagramDataV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamPacketV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamPacketV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIcmpErrorV4 { + EmbeddedProtocol, + IpLocalAddress, + IpRemoteAddress, + EmbeddedRemoteAddress, + EmbeddedLocalAddressType, + EmbeddedLocalPort, + EmbeddedRemotePort, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + IpArrivalInterface, + ArrivalInterfaceIndex, + ArrivalInterfaceType, + ArrivalTunnelType, + Flags, + ArrivalInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIcmpErrorV6 { + EmbeddedProtocol, + IpLocalAddress, + IpRemoteAddress, + EmbeddedRemoteAddress, + EmbeddedLocalAddressType, + EmbeddedLocalPort, + EmbeddedRemotePort, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + IpArrivalInterface, + ArrivalInterfaceIndex, + ArrivalInterfaceType, + ArrivalTunnelType, + Flags, + ArrivalInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIcmpErrorV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, + SubInterfaceIndex, + InterfaceType, + TunnelType, + Flags, + NexthopInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIcmpErrorV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + IpLocalPort, + IpRemotePort, + InterfaceIndex, + SubInterfaceIndex, + InterfaceType, + TunnelType, + Flags, + NexthopInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceAssignmentV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + AlePromiscuousMode, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceAssignmentV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + AlePromiscuousMode, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceReleaseV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceReleaseV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleEndpointClosureV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleEndpointClosureV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthListenV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthListenV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthRecvAcceptV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpLocalInterface, + Flags, + SioFirewallSystemPort, + NapContext, + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthRecvAcceptV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpLocalInterface, + Flags, + SioFirewallSystemPort, + NapContext, + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleBindRedirectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleBindRedirectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleConnectRedirectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleConnectRedirectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthConnectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + InterfaceIndex, + SubInterfaceIndex, + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + PeerName, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + AleEffectiveName, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthConnectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + InterfaceIndex, + SubInterfaceIndex, + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + PeerName, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + AleEffectiveName, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleFlowEstablishedV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Direction, + InterfaceType, + TunnelType, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleFlowEstablishedV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Direction, + InterfaceType, + TunnelType, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsNameResolutionCacheV4 { + AleUserId, + AleAppId, + IpRemoteAddress, + PeerName, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsNameResolutionCacheV6 { + AleUserId, + AleAppId, + IpRemoteAddress, + PeerName, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameEthernet { + InterfaceMacAddress, + MacLocalAddress, + MacRemoteAddress, + MacLocalAddressType, + MacRemoteAddressType, + EtherType, + VlanId, + Interface, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameEthernet { + InterfaceMacAddress, + MacLocalAddress, + MacRemoteAddress, + MacLocalAddressType, + MacRemoteAddressType, + EtherType, + VlanId, + Interface, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameNative { + NdisMediaType, + NdisPhysicalMediaType, + Interface, + InterfaceType, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameNativeFast { + FastMax, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameNative { + NdisMediaType, + NdisPhysicalMediaType, + Interface, + InterfaceType, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameNativeFast { + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchEthernet { + MacSourceAddress, + MacSourceAddressType, + MacDestinationAddress, + MacDestinationAddressType, + EtherType, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchEthernet { + MacSourceAddress, + MacSourceAddressType, + MacDestinationAddress, + MacDestinationAddressType, + EtherType, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchTransportV4 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchTransportV6 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchTransportV4 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchTransportV6 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecKmDemuxV4 { + IpLocalAddress, + IpRemoteAddress, + QmMode, + IpLocalInterface, + CurrentProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecKmDemuxV6 { + IpLocalAddress, + IpRemoteAddress, + QmMode, + IpLocalInterface, + CurrentProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIkeextV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIkeextV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcUm { + RemoteUserToken, + AuthLevel, + AuthType, + DcomAppId, + IfFlag, + IfUuid, + IfVersion, + ImageName, + LocalAddrV4, + LocalAddrV6, + LocalPort, + Max, + Pipe, + Protocol, + RemoteAddrV4, + RemoteAddrV6, + SecEncryptAlgorithm, + SecKeySize, +} + +#[repr(usize)] +pub enum FieldsRpcEpmap { + RemoteUserToken, + IfUuid, + IfVersion, + Protocol, + AuthType, + AuthLevel, + SecEncryptAlgorithm, + SecKeySize, + LocalAddrV4, + LocalAddrV6, + LocalPort, + Pipe, + RemoteAddrV4, + RemoteAddrV6, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcEpAdd { + ProcessWithRpcIfUuid, + Protocol, + EpValue, + EpFlags, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcProxyConn { + ClientToken, + ServerName, + ServerPort, + ProxyAuthType, + ClientCertKeyLength, + ClientCertOid, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcProxyIf { + ClientToken, + IfUuid, + IfVersion, + ServerName, + ServerPort, + ProxyAuthType, + ClientCertKeyLength, + ClientCertOid, + Max, +} + +#[repr(usize)] +pub enum FieldsKmAuthorization { + RemoteId, + AuthenticationType, + KmType, + Direction, + KmMode, + IpsecPolicyKey, + NapContext, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundReserved2 { + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Reserved4, + Reserved5, + Reserved6, + Reserved7, + Reserved8, + Reserved9, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundNetworkConnectionPolicyV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundNetworkConnectionPolicyV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} diff --git a/wdk/src/filter_engine/metadata.rs b/wdk/src/filter_engine/metadata.rs index fbde273..7c7e1c3 100644 --- a/wdk/src/filter_engine/metadata.rs +++ b/wdk/src/filter_engine/metadata.rs @@ -1,193 +1,193 @@ -use core::{ffi::c_void, ptr::NonNull}; - -use alloc::string::String; -use widestring::U16CString; -use windows_sys::Win32::{ - Foundation::HANDLE, - NetworkManagement::{ - IpHelper::IP_ADDRESS_PREFIX, - WindowsFilteringPlatform::{ - FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_IP_HEADER_SIZE, - FWPS_METADATA_FIELD_PROCESS_ID, FWPS_METADATA_FIELD_PROCESS_PATH, - FWPS_METADATA_FIELD_REMOTE_SCOPE_ID, - FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA, - FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE, FWP_BYTE_BLOB, FWP_DIRECTION, - }, - }, - Networking::WinSock::SCOPE_ID, -}; - -#[repr(C)] -pub(crate) struct FwpsIncomingMetadataValues { - /// Bitmask representing which values are set. - current_metadata_values: u32, - /// Internal flags; - flags: u32, - /// Reserved for system use. - reserved: u64, - /// Discard module and reason. - discard_metadata: FwpsDiscardMetadata0, - /// Flow Handle. - flow_handle: u64, - /// IP Header size. - ip_header_size: u32, - /// Transport Header size - transport_header_size: u32, - /// Process Path. - process_path: *const FWP_BYTE_BLOB, - /// Token used for authorization. - token: u64, - /// Process Id. - process_id: u64, - /// Source and Destination interface indices for discard indications. - source_interface_index: u32, - destination_interface_index: u32, - /// Compartment Id for injection APIs. - compartment_id: u32, - /// Fragment data for inbound fragments. - fragment_metadata: FwpsInboundFragmentMetadata0, - /// Path MTU for outbound packets (to enable calculation of fragments). - path_mtu: u32, - /// Completion handle (required in order to be able to pend at this layer). - completion_handle: HANDLE, - /// Endpoint handle for use in outbound transport layer injection. - transport_endpoint_handle: u64, - /// Remote scope id for use in outbound transport layer injection. - remote_scope_id: SCOPE_ID, - /// Socket control data (and length) for use in outbound transport layer injection. - control_data: *const u8, - control_data_length: u32, - /// Direction for the current packet. Only specified for ALE re-authorization. - packet_direction: FWP_DIRECTION, - /// Raw IP header (and length) if the packet is sent with IP header from a RAW socket. - header_include_header: *mut c_void, - header_include_header_length: u32, - destination_prefix: IP_ADDRESS_PREFIX, - frame_length: u16, - parent_endpoint_handle: u64, - icmp_id_and_sequence: u32, - /// PID of the process that will be accepting the redirected connection - local_redirect_target_pid: u64, - /// original destination of a redirected connection - original_destination: *mut c_void, - redirect_records: HANDLE, - /// Bitmask representing which L2 values are set. - current_l2_metadata_values: u32, - /// L2 layer Flags; - l2_flags: u32, - ethernet_mac_header_size: u32, - wifi_operation_mode: u32, - padding0: u32, - padding1: u16, - padding2: u32, - v_switch_packet_context: HANDLE, - sub_process_tag: *mut c_void, - // Reserved for system use. - reserved1: u64, -} - -impl FwpsIncomingMetadataValues { - pub(crate) fn has_field(&self, field: u32) -> bool { - self.current_metadata_values & field > 0 - } - - /// Size of the IP header, including any options / extension headers. - /// - /// Only meaningful when the corresponding metadata field is set; at the - /// inbound IP packet layers it is the amount the net buffer has to be - /// retreated by to expose the IP header. - pub(crate) fn get_ip_header_size(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_IP_HEADER_SIZE) { - return Some(self.ip_header_size); - } - - None - } - - pub(crate) fn get_transport_header_size(&self) -> u32 { - self.transport_header_size - } - - pub(crate) fn get_process_id(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_PROCESS_ID) { - return Some(self.process_id); - } - - None - } - - pub(crate) unsafe fn get_process_path(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_PROCESS_PATH) { - if let Ok(path16) = U16CString::from_ptr( - core::mem::transmute((*self.process_path).data), - (*self.process_path).size as usize / 2, - ) { - if let Ok(path) = path16.to_string() { - return Some(path); - } - } - } - - None - } - - pub(crate) fn get_completion_handle(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_COMPLETION_HANDLE) { - return Some(self.completion_handle); - } - - None - } - - pub(crate) fn get_transport_endpoint_handle(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE) { - return Some(self.transport_endpoint_handle); - } - - None - } - - pub(crate) fn get_remote_scope_id(&self) -> Option { - if self.has_field(FWPS_METADATA_FIELD_REMOTE_SCOPE_ID) { - return Some(self.remote_scope_id); - } - - None - } - - pub(crate) unsafe fn get_control_data(&self) -> Option> { - if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA) { - if self.control_data.is_null() || self.control_data_length == 0 { - return None; - } - let ptr = NonNull::new(self.control_data as *mut u8).unwrap(); - let slice = NonNull::slice_from_raw_parts(ptr, self.control_data_length as usize); - return Some(slice); - } - - None - } -} - -#[allow(dead_code)] -#[repr(C)] -enum FwpsDiscardModule0 { - FwpsDiscardModuleNetwork = 0, - FwpsDiscardModuleTransport = 1, - FwpsDiscardModuleGeneral = 2, - FwpsDiscardModuleMax = 3, -} - -#[repr(C)] -struct FwpsDiscardMetadata0 { - discard_module: FwpsDiscardModule0, - discard_reason: u32, - filter_id: u64, -} - -#[repr(C)] -struct FwpsInboundFragmentMetadata0 { - fragment_identification: u32, - fragment_offset: u16, - fragment_length: u32, -} +use core::{ffi::c_void, ptr::NonNull}; + +use alloc::string::String; +use widestring::U16CString; +use windows_sys::Win32::{ + Foundation::HANDLE, + NetworkManagement::{ + IpHelper::IP_ADDRESS_PREFIX, + WindowsFilteringPlatform::{ + FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_IP_HEADER_SIZE, + FWPS_METADATA_FIELD_PROCESS_ID, FWPS_METADATA_FIELD_PROCESS_PATH, + FWPS_METADATA_FIELD_REMOTE_SCOPE_ID, + FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA, + FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE, FWP_BYTE_BLOB, FWP_DIRECTION, + }, + }, + Networking::WinSock::SCOPE_ID, +}; + +#[repr(C)] +pub(crate) struct FwpsIncomingMetadataValues { + /// Bitmask representing which values are set. + current_metadata_values: u32, + /// Internal flags; + flags: u32, + /// Reserved for system use. + reserved: u64, + /// Discard module and reason. + discard_metadata: FwpsDiscardMetadata0, + /// Flow Handle. + flow_handle: u64, + /// IP Header size. + ip_header_size: u32, + /// Transport Header size + transport_header_size: u32, + /// Process Path. + process_path: *const FWP_BYTE_BLOB, + /// Token used for authorization. + token: u64, + /// Process Id. + process_id: u64, + /// Source and Destination interface indices for discard indications. + source_interface_index: u32, + destination_interface_index: u32, + /// Compartment Id for injection APIs. + compartment_id: u32, + /// Fragment data for inbound fragments. + fragment_metadata: FwpsInboundFragmentMetadata0, + /// Path MTU for outbound packets (to enable calculation of fragments). + path_mtu: u32, + /// Completion handle (required in order to be able to pend at this layer). + completion_handle: HANDLE, + /// Endpoint handle for use in outbound transport layer injection. + transport_endpoint_handle: u64, + /// Remote scope id for use in outbound transport layer injection. + remote_scope_id: SCOPE_ID, + /// Socket control data (and length) for use in outbound transport layer injection. + control_data: *const u8, + control_data_length: u32, + /// Direction for the current packet. Only specified for ALE re-authorization. + packet_direction: FWP_DIRECTION, + /// Raw IP header (and length) if the packet is sent with IP header from a RAW socket. + header_include_header: *mut c_void, + header_include_header_length: u32, + destination_prefix: IP_ADDRESS_PREFIX, + frame_length: u16, + parent_endpoint_handle: u64, + icmp_id_and_sequence: u32, + /// PID of the process that will be accepting the redirected connection + local_redirect_target_pid: u64, + /// original destination of a redirected connection + original_destination: *mut c_void, + redirect_records: HANDLE, + /// Bitmask representing which L2 values are set. + current_l2_metadata_values: u32, + /// L2 layer Flags; + l2_flags: u32, + ethernet_mac_header_size: u32, + wifi_operation_mode: u32, + padding0: u32, + padding1: u16, + padding2: u32, + v_switch_packet_context: HANDLE, + sub_process_tag: *mut c_void, + // Reserved for system use. + reserved1: u64, +} + +impl FwpsIncomingMetadataValues { + pub(crate) fn has_field(&self, field: u32) -> bool { + self.current_metadata_values & field > 0 + } + + /// Size of the IP header, including any options / extension headers. + /// + /// Only meaningful when the corresponding metadata field is set; at the + /// inbound IP packet layers it is the amount the net buffer has to be + /// retreated by to expose the IP header. + pub(crate) fn get_ip_header_size(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_IP_HEADER_SIZE) { + return Some(self.ip_header_size); + } + + None + } + + pub(crate) fn get_transport_header_size(&self) -> u32 { + self.transport_header_size + } + + pub(crate) fn get_process_id(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_PROCESS_ID) { + return Some(self.process_id); + } + + None + } + + pub(crate) unsafe fn get_process_path(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_PROCESS_PATH) { + if let Ok(path16) = U16CString::from_ptr( + core::mem::transmute((*self.process_path).data), + (*self.process_path).size as usize / 2, + ) { + if let Ok(path) = path16.to_string() { + return Some(path); + } + } + } + + None + } + + pub(crate) fn get_completion_handle(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_COMPLETION_HANDLE) { + return Some(self.completion_handle); + } + + None + } + + pub(crate) fn get_transport_endpoint_handle(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE) { + return Some(self.transport_endpoint_handle); + } + + None + } + + pub(crate) fn get_remote_scope_id(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_REMOTE_SCOPE_ID) { + return Some(self.remote_scope_id); + } + + None + } + + pub(crate) unsafe fn get_control_data(&self) -> Option> { + if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA) { + if self.control_data.is_null() || self.control_data_length == 0 { + return None; + } + let ptr = NonNull::new(self.control_data as *mut u8).unwrap(); + let slice = NonNull::slice_from_raw_parts(ptr, self.control_data_length as usize); + return Some(slice); + } + + None + } +} + +#[allow(dead_code)] +#[repr(C)] +enum FwpsDiscardModule0 { + FwpsDiscardModuleNetwork = 0, + FwpsDiscardModuleTransport = 1, + FwpsDiscardModuleGeneral = 2, + FwpsDiscardModuleMax = 3, +} + +#[repr(C)] +struct FwpsDiscardMetadata0 { + discard_module: FwpsDiscardModule0, + discard_reason: u32, + filter_id: u64, +} + +#[repr(C)] +struct FwpsInboundFragmentMetadata0 { + fragment_identification: u32, + fragment_offset: u16, + fragment_length: u32, +} diff --git a/wdk/src/filter_engine/mod.rs b/wdk/src/filter_engine/mod.rs index 349e87b..1ce82da 100644 --- a/wdk/src/filter_engine/mod.rs +++ b/wdk/src/filter_engine/mod.rs @@ -1,359 +1,359 @@ -use core::ffi::c_void; - -use crate::alloc::borrow::ToOwned; -use crate::driver::Driver; -use crate::ffi::FWPS_FILTER2; -use crate::filter_engine::transaction::Transaction; -use crate::{dbg, info}; -use alloc::boxed::Box; -use alloc::string::String; -use alloc::{format, vec::Vec}; -use windows_sys::Wdk::Foundation::DEVICE_OBJECT; -use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; - -use self::callout::{Callout, FilterType}; -use self::callout_data::CalloutData; -use self::classify::ClassifyOut; -use self::layer::IncomingValues; -use self::metadata::FwpsIncomingMetadataValues; - -pub mod callout; -pub mod callout_data; -pub(crate) mod classify; -#[allow(dead_code)] -pub mod ffi; -pub mod layer; -pub(crate) mod metadata; -pub mod net_buffer; -pub mod packet; -pub mod stream_data; -pub mod transaction; -// Helper functions for ALE Readirect layers. Not needed for the current implementation. -// pub mod connect_request; - -/// Why `FilterEngine::reset_all_filters` did not apply the reset. -pub enum ResetError { - /// Another transaction is in progress on the filter engine session and only one can be open at - /// a time. Nothing was changed: no filter was removed and none was re-registered, so the reset - /// can be repeated as-is once the other transaction is done. - TransactionInProgress, - /// Anything else. Not transient, repeating it is pointless. - Failed(String), -} - -impl From for String { - fn from(err: ResetError) -> Self { - match err { - ResetError::TransactionInProgress => String::from(transaction::BeginError::InProgress), - ResetError::Failed(message) => message, - } - } -} - -pub struct FilterEngine { - device_object: *mut DEVICE_OBJECT, - handle: HANDLE, - sublayer_guid: u128, - committed: bool, - callouts: Option>>, -} - -impl FilterEngine { - pub fn new(driver: &Driver, layer_guid: u128) -> Result { - let filter_engine_handle: HANDLE; - match ffi::create_filter_engine() { - Ok(handle) => { - filter_engine_handle = handle; - } - Err(code) => { - return Err(format!("failed to initialize filter engine {}", code).to_owned()); - } - } - Ok(Self { - device_object: driver.get_device_object(), - handle: filter_engine_handle, - sublayer_guid: layer_guid, - committed: false, - callouts: None, - }) - } - - pub fn commit(&mut self, callouts: Vec) -> Result<(), String> { - { - // Begin write transaction. This is also a lock guard. - let mut filter_engine = match Transaction::begin_write_retrying(self) { - Ok(transaction) => transaction, - Err(err) => { - return Err(err); - } - }; - - if let Err(err) = filter_engine.register_sublayer() { - return Err(format!("filter_engine: {}", err)); - } - - dbg!("Callouts count: {}", callouts.len()); - let mut boxed_callouts = Vec::new(); - // Register all callouts - for callout in callouts { - let mut callout = Box::new(callout); - callout.address = callout.as_ref() as *const Callout as u64; - - if let Err(err) = callout.register_callout( - filter_engine.handle, - filter_engine.device_object, - catch_all_callout, - ) { - // This will destroy the callout structs. - return Err(err); - } - if let Err(err) = - callout.register_filter(filter_engine.handle, filter_engine.sublayer_guid) - { - // This will destroy the callout structs. - return Err(err); - } - dbg!( - "registering callout: {} -> {}", - callout.name, - callout.filter_id - ); - boxed_callouts.push(callout) - } - if let Some(callouts) = &mut filter_engine.callouts { - callouts.append(&mut boxed_callouts); - } else { - filter_engine.callouts = Some(boxed_callouts); - } - - if let Err(err) = filter_engine.commit() { - return Err(err); - } - } - self.committed = true; - info!("transaction committed"); - - return Ok(()); - } - - /// Reports whether any filter is still registered. - fn has_registered_filters(&self) -> bool { - match &self.callouts { - Some(callouts) => callouts.iter().any(|callout| callout.filter_id != 0), - None => false, - } - } - - /// Removes all registered filters, leaving the callouts in place. Once the transaction is - /// committed no new classify call can reach the callouts. This is the first step of the - /// teardown: complete the operations that are still pended, then call `unregister_callouts`, - /// which `FwpsCalloutUnregisterById0` refuses to do while one is outstanding. - /// - /// Does nothing when the filters are already gone, so it is safe to call more than once. - pub fn unregister_filters(&mut self) -> Result<(), String> { - if !self.has_registered_filters() { - // Nothing to remove; do not open a transaction for it. - return Ok(()); - } - - // Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed. - // Waits out a transaction that is in progress instead of failing: leaving the filters - // registered here would make `unregister_callouts` fail too, and the teardown has no - // second chance to remove them. - let mut filter_engine = match Transaction::begin_write_retrying(self) { - Ok(transaction) => transaction, - Err(err) => { - return Err(err); - } - }; - - let filter_engine_handle = filter_engine.handle; - if let Some(callouts) = &mut filter_engine.callouts { - for callout in callouts { - if callout.filter_id != 0 { - if let Err(err) = ffi::unregister_filter(filter_engine_handle, callout.filter_id) - { - return Err(format!("filter_engine: {}", err)); - } - callout.filter_id = 0; - } - } - } - - // Commit transaction. - if let Err(err) = filter_engine.commit() { - return Err(err); - } - - return Ok(()); - } - - /// Unregisters all registered callouts. This is the last step of the teardown and must run - /// after `unregister_filters` and after every pended operation has been completed: - /// `FwpsCalloutUnregisterById0` fails with STATUS_DEVICE_BUSY while an operation is still - /// pended, and letting the driver unload over a callout that is still registered strands that - /// operation for good — nothing can complete its IRP afterwards, so the thread waiting on it - /// never returns from the kernel and its process can no longer be terminated. - /// - /// Skips the callouts that are already gone, so it is safe to call more than once. - pub fn unregister_callouts(&mut self) -> Result<(), String> { - let mut first_failure = None; - - if let Some(callouts) = &mut self.callouts { - for callout in callouts { - if !callout.registered { - continue; - } - - match ffi::unregister_callout(callout.id) { - Ok(()) => callout.registered = false, - // Keep going so the remaining callouts are still removed, and report the first - // failure once the whole set has been tried. - Err(err) => { - if first_failure.is_none() { - first_failure = Some(format!("{}: {}", callout.name, err)); - } - } - } - } - } - - match first_failure { - Some(err) => Err(format!("filter_engine: failed to unregister callout {}", err)), - None => Ok(()), - } - } - - /// Removes and re-registers every resettable filter, which makes WFP reauthorize the existing - /// connections against them. This is the only way to release a connection that was deferred - /// without a completion handle (see `CalloutData::pend_filter_rest`), because a single filter - /// cannot be reset on its own. - /// - /// Does not retry on its own. Only one transaction can be open on the filter engine session, so - /// concurrent callers collide; `ResetError::TransactionInProgress` tells them apart from the - /// real failures and leaves the decision of when to try again to the caller, which can batch - /// several deferred connections into one reset instead of racing per connection. - pub fn reset_all_filters(&mut self) -> Result<(), ResetError> { - if !self.has_registered_filters() { - // The filters are gone, which only happens once the teardown has removed them. There - // is nothing to reset, and re-registering them here would bring the callouts back to - // life right before they are unregistered. - return Ok(()); - } - - // Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed. - let mut filter_engine = match Transaction::begin_write(self) { - Ok(transaction) => transaction, - Err(transaction::BeginError::InProgress) => { - return Err(ResetError::TransactionInProgress) - } - Err(err) => return Err(ResetError::Failed(String::from(err))), - }; - let filter_engine_handle = filter_engine.handle; - let sublayer_guid = filter_engine.sublayer_guid; - if let Some(callouts) = &mut filter_engine.callouts { - for callout in callouts { - if let FilterType::Resettable = callout.filter_type { - if callout.filter_id != 0 { - // Remove old filter. - if let Err(err) = - ffi::unregister_filter(filter_engine_handle, callout.filter_id) - { - return Err(ResetError::Failed(format!("filter_engine: {}", err))); - } - callout.filter_id = 0; - } - // Create new filter. - if let Err(err) = callout.register_filter(filter_engine_handle, sublayer_guid) { - return Err(ResetError::Failed(format!("filter_engine: {}", err))); - } - } - } - } - // Commit transaction. - if let Err(err) = filter_engine.commit() { - return Err(ResetError::Failed(err)); - } - return Ok(()); - } - - fn register_sublayer(&self) -> Result<(), String> { - let result = ffi::register_sublayer( - self.handle, - "ZenithFenceSublayer", - "The ZenithFence sublayer holds all it's filters.", - self.sublayer_guid, - ); - if let Err(code) = result { - return Err(format!("failed to register sublayer: {}", code)); - } - - return Ok(()); - } -} - -impl Drop for FilterEngine { - fn drop(&mut self) { - dbg!("Unregistering callouts"); - - // Failsafe only. The teardown in Device::shutdown removes the filters and the callouts - // explicitly, in the order that lets the pended operations be completed in between, so - // both calls below are no-ops by the time we get here. They only do anything if the - // teardown never ran, and then the order still matters: filters first, so no new classify - // call can arrive while the callouts are being unregistered. - if let Err(err) = self.unregister_filters() { - dbg!("failed to unregister filters: {}", err); - } - if let Err(err) = self.unregister_callouts() { - // Reaching this means a pended operation was never completed and the driver is about - // to unload over a live callout, which leaves the thread waiting on that operation - // stuck in the kernel for good. Report it through DbgPrint directly, because the log - // macros are compiled out in release builds and the event queue that carries the - // driver log to user space is already gone by this point. - crate::interface::dbg_print(format!("ERROR ZenithFence: {}\n", err)); - } - - if self.committed { - if let Err(code) = ffi::unregister_sublayer(self.handle, self.sublayer_guid) { - dbg!("Failed to unregister sublayer: {}", code); - } - } - - if self.handle != 0 && self.handle != INVALID_HANDLE_VALUE { - _ = ffi::filter_engine_close(self.handle); - } - } -} - -#[no_mangle] -unsafe extern "C" fn catch_all_callout( - fixed_values: *const IncomingValues, - meta_values: *const FwpsIncomingMetadataValues, - layer_data: *mut c_void, - _context: *mut c_void, - filter: *const FWPS_FILTER2, - _flow_context: u64, - classify_out: *mut ClassifyOut, -) { - let filter = &(*filter); - // Filter context is the address of the callout. - let callout = filter.context as *mut Callout; - - if let Some(callout) = callout.as_ref() { - // Setup callout data. - let array = core::slice::from_raw_parts( - (*fixed_values).incoming_value_array, - (*fixed_values).value_count as usize, - ); - let data = CalloutData { - layer: callout.layer, - callout_id: filter.context as usize, - values: array, - metadata: meta_values, - classify_out, - layer_data, - }; - // Call the defined function. - (callout.callout_fn)(data); - } -} +use core::ffi::c_void; + +use crate::alloc::borrow::ToOwned; +use crate::driver::Driver; +use crate::ffi::FWPS_FILTER2; +use crate::filter_engine::transaction::Transaction; +use crate::{dbg, info}; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::{format, vec::Vec}; +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; +use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; + +use self::callout::{Callout, FilterType}; +use self::callout_data::CalloutData; +use self::classify::ClassifyOut; +use self::layer::IncomingValues; +use self::metadata::FwpsIncomingMetadataValues; + +pub mod callout; +pub mod callout_data; +pub(crate) mod classify; +#[allow(dead_code)] +pub mod ffi; +pub mod layer; +pub(crate) mod metadata; +pub mod net_buffer; +pub mod packet; +pub mod stream_data; +pub mod transaction; +// Helper functions for ALE Readirect layers. Not needed for the current implementation. +// pub mod connect_request; + +/// Why `FilterEngine::reset_all_filters` did not apply the reset. +pub enum ResetError { + /// Another transaction is in progress on the filter engine session and only one can be open at + /// a time. Nothing was changed: no filter was removed and none was re-registered, so the reset + /// can be repeated as-is once the other transaction is done. + TransactionInProgress, + /// Anything else. Not transient, repeating it is pointless. + Failed(String), +} + +impl From for String { + fn from(err: ResetError) -> Self { + match err { + ResetError::TransactionInProgress => String::from(transaction::BeginError::InProgress), + ResetError::Failed(message) => message, + } + } +} + +pub struct FilterEngine { + device_object: *mut DEVICE_OBJECT, + handle: HANDLE, + sublayer_guid: u128, + committed: bool, + callouts: Option>>, +} + +impl FilterEngine { + pub fn new(driver: &Driver, layer_guid: u128) -> Result { + let filter_engine_handle: HANDLE; + match ffi::create_filter_engine() { + Ok(handle) => { + filter_engine_handle = handle; + } + Err(code) => { + return Err(format!("failed to initialize filter engine {}", code).to_owned()); + } + } + Ok(Self { + device_object: driver.get_device_object(), + handle: filter_engine_handle, + sublayer_guid: layer_guid, + committed: false, + callouts: None, + }) + } + + pub fn commit(&mut self, callouts: Vec) -> Result<(), String> { + { + // Begin write transaction. This is also a lock guard. + let mut filter_engine = match Transaction::begin_write_retrying(self) { + Ok(transaction) => transaction, + Err(err) => { + return Err(err); + } + }; + + if let Err(err) = filter_engine.register_sublayer() { + return Err(format!("filter_engine: {}", err)); + } + + dbg!("Callouts count: {}", callouts.len()); + let mut boxed_callouts = Vec::new(); + // Register all callouts + for callout in callouts { + let mut callout = Box::new(callout); + callout.address = callout.as_ref() as *const Callout as u64; + + if let Err(err) = callout.register_callout( + filter_engine.handle, + filter_engine.device_object, + catch_all_callout, + ) { + // This will destroy the callout structs. + return Err(err); + } + if let Err(err) = + callout.register_filter(filter_engine.handle, filter_engine.sublayer_guid) + { + // This will destroy the callout structs. + return Err(err); + } + dbg!( + "registering callout: {} -> {}", + callout.name, + callout.filter_id + ); + boxed_callouts.push(callout) + } + if let Some(callouts) = &mut filter_engine.callouts { + callouts.append(&mut boxed_callouts); + } else { + filter_engine.callouts = Some(boxed_callouts); + } + + if let Err(err) = filter_engine.commit() { + return Err(err); + } + } + self.committed = true; + info!("transaction committed"); + + return Ok(()); + } + + /// Reports whether any filter is still registered. + fn has_registered_filters(&self) -> bool { + match &self.callouts { + Some(callouts) => callouts.iter().any(|callout| callout.filter_id != 0), + None => false, + } + } + + /// Removes all registered filters, leaving the callouts in place. Once the transaction is + /// committed no new classify call can reach the callouts. This is the first step of the + /// teardown: complete the operations that are still pended, then call `unregister_callouts`, + /// which `FwpsCalloutUnregisterById0` refuses to do while one is outstanding. + /// + /// Does nothing when the filters are already gone, so it is safe to call more than once. + pub fn unregister_filters(&mut self) -> Result<(), String> { + if !self.has_registered_filters() { + // Nothing to remove; do not open a transaction for it. + return Ok(()); + } + + // Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed. + // Waits out a transaction that is in progress instead of failing: leaving the filters + // registered here would make `unregister_callouts` fail too, and the teardown has no + // second chance to remove them. + let mut filter_engine = match Transaction::begin_write_retrying(self) { + Ok(transaction) => transaction, + Err(err) => { + return Err(err); + } + }; + + let filter_engine_handle = filter_engine.handle; + if let Some(callouts) = &mut filter_engine.callouts { + for callout in callouts { + if callout.filter_id != 0 { + if let Err(err) = ffi::unregister_filter(filter_engine_handle, callout.filter_id) + { + return Err(format!("filter_engine: {}", err)); + } + callout.filter_id = 0; + } + } + } + + // Commit transaction. + if let Err(err) = filter_engine.commit() { + return Err(err); + } + + return Ok(()); + } + + /// Unregisters all registered callouts. This is the last step of the teardown and must run + /// after `unregister_filters` and after every pended operation has been completed: + /// `FwpsCalloutUnregisterById0` fails with STATUS_DEVICE_BUSY while an operation is still + /// pended, and letting the driver unload over a callout that is still registered strands that + /// operation for good — nothing can complete its IRP afterwards, so the thread waiting on it + /// never returns from the kernel and its process can no longer be terminated. + /// + /// Skips the callouts that are already gone, so it is safe to call more than once. + pub fn unregister_callouts(&mut self) -> Result<(), String> { + let mut first_failure = None; + + if let Some(callouts) = &mut self.callouts { + for callout in callouts { + if !callout.registered { + continue; + } + + match ffi::unregister_callout(callout.id) { + Ok(()) => callout.registered = false, + // Keep going so the remaining callouts are still removed, and report the first + // failure once the whole set has been tried. + Err(err) => { + if first_failure.is_none() { + first_failure = Some(format!("{}: {}", callout.name, err)); + } + } + } + } + } + + match first_failure { + Some(err) => Err(format!("filter_engine: failed to unregister callout {}", err)), + None => Ok(()), + } + } + + /// Removes and re-registers every resettable filter, which makes WFP reauthorize the existing + /// connections against them. This is the only way to release a connection that was deferred + /// without a completion handle (see `CalloutData::pend_filter_rest`), because a single filter + /// cannot be reset on its own. + /// + /// Does not retry on its own. Only one transaction can be open on the filter engine session, so + /// concurrent callers collide; `ResetError::TransactionInProgress` tells them apart from the + /// real failures and leaves the decision of when to try again to the caller, which can batch + /// several deferred connections into one reset instead of racing per connection. + pub fn reset_all_filters(&mut self) -> Result<(), ResetError> { + if !self.has_registered_filters() { + // The filters are gone, which only happens once the teardown has removed them. There + // is nothing to reset, and re-registering them here would bring the callouts back to + // life right before they are unregistered. + return Ok(()); + } + + // Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed. + let mut filter_engine = match Transaction::begin_write(self) { + Ok(transaction) => transaction, + Err(transaction::BeginError::InProgress) => { + return Err(ResetError::TransactionInProgress) + } + Err(err) => return Err(ResetError::Failed(String::from(err))), + }; + let filter_engine_handle = filter_engine.handle; + let sublayer_guid = filter_engine.sublayer_guid; + if let Some(callouts) = &mut filter_engine.callouts { + for callout in callouts { + if let FilterType::Resettable = callout.filter_type { + if callout.filter_id != 0 { + // Remove old filter. + if let Err(err) = + ffi::unregister_filter(filter_engine_handle, callout.filter_id) + { + return Err(ResetError::Failed(format!("filter_engine: {}", err))); + } + callout.filter_id = 0; + } + // Create new filter. + if let Err(err) = callout.register_filter(filter_engine_handle, sublayer_guid) { + return Err(ResetError::Failed(format!("filter_engine: {}", err))); + } + } + } + } + // Commit transaction. + if let Err(err) = filter_engine.commit() { + return Err(ResetError::Failed(err)); + } + return Ok(()); + } + + fn register_sublayer(&self) -> Result<(), String> { + let result = ffi::register_sublayer( + self.handle, + "ZenithFenceSublayer", + "The ZenithFence sublayer holds all it's filters.", + self.sublayer_guid, + ); + if let Err(code) = result { + return Err(format!("failed to register sublayer: {}", code)); + } + + return Ok(()); + } +} + +impl Drop for FilterEngine { + fn drop(&mut self) { + dbg!("Unregistering callouts"); + + // Failsafe only. The teardown in Device::shutdown removes the filters and the callouts + // explicitly, in the order that lets the pended operations be completed in between, so + // both calls below are no-ops by the time we get here. They only do anything if the + // teardown never ran, and then the order still matters: filters first, so no new classify + // call can arrive while the callouts are being unregistered. + if let Err(err) = self.unregister_filters() { + dbg!("failed to unregister filters: {}", err); + } + if let Err(err) = self.unregister_callouts() { + // Reaching this means a pended operation was never completed and the driver is about + // to unload over a live callout, which leaves the thread waiting on that operation + // stuck in the kernel for good. Report it through DbgPrint directly, because the log + // macros are compiled out in release builds and the event queue that carries the + // driver log to user space is already gone by this point. + crate::interface::dbg_print(format!("ERROR ZenithFence: {}\n", err)); + } + + if self.committed { + if let Err(code) = ffi::unregister_sublayer(self.handle, self.sublayer_guid) { + dbg!("Failed to unregister sublayer: {}", code); + } + } + + if self.handle != 0 && self.handle != INVALID_HANDLE_VALUE { + _ = ffi::filter_engine_close(self.handle); + } + } +} + +#[no_mangle] +unsafe extern "C" fn catch_all_callout( + fixed_values: *const IncomingValues, + meta_values: *const FwpsIncomingMetadataValues, + layer_data: *mut c_void, + _context: *mut c_void, + filter: *const FWPS_FILTER2, + _flow_context: u64, + classify_out: *mut ClassifyOut, +) { + let filter = &(*filter); + // Filter context is the address of the callout. + let callout = filter.context as *mut Callout; + + if let Some(callout) = callout.as_ref() { + // Setup callout data. + let array = core::slice::from_raw_parts( + (*fixed_values).incoming_value_array, + (*fixed_values).value_count as usize, + ); + let data = CalloutData { + layer: callout.layer, + callout_id: filter.context as usize, + values: array, + metadata: meta_values, + classify_out, + layer_data, + }; + // Call the defined function. + (callout.callout_fn)(data); + } +} diff --git a/wdk/src/filter_engine/net_buffer.rs b/wdk/src/filter_engine/net_buffer.rs index 1cb163a..66e8151 100644 --- a/wdk/src/filter_engine/net_buffer.rs +++ b/wdk/src/filter_engine/net_buffer.rs @@ -1,481 +1,481 @@ -use core::mem::MaybeUninit; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use windows_sys::Wdk::System::SystemServices::{ - IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool, -}; - -use crate::{ - allocator::POOL_TAG, - ffi::{ - FwpsAllocateNetBufferAndNetBufferList0, FwpsFreeNetBufferList0, - NdisAdvanceNetBufferDataStart, NdisAllocateNetBufferListPool, NdisFreeNetBufferListPool, - NdisGetDataBuffer, NdisRetreatNetBufferDataStart, NDIS_HANDLE, NDIS_OBJECT_TYPE_DEFAULT, - NET_BUFFER, NET_BUFFER_LIST, NET_BUFFER_LIST_POOL_PARAMETERS, - NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1, - }, - utils::check_ntstatus, -}; - -/// A single NET_BUFFER inside a NET_BUFFER_LIST. -/// -/// A list can carry a chain of net buffers, and each one is an independent -/// packet with its own data offset, length and MDL chain. Everything that acts -/// on packet bytes -- retreating the data start, reading headers, cloning -- -/// is therefore per net buffer; doing it at the list level only ever touches -/// the head of the chain and silently ignores the rest. -pub struct NetBuffer { - nb: *mut NET_BUFFER, - advance_on_drop: Option, -} - -impl NetBuffer { - pub fn new(nb: *mut NET_BUFFER) -> NetBuffer { - NetBuffer { - nb, - advance_on_drop: None, - } - } - - pub fn get_data_length(&self) -> u32 { - unsafe { - match self.nb.as_ref() { - Some(nb) => nb.nbSize.DataLength, - None => 0, - } - } - } - - /// Reads `buffer.len()` bytes from the current data start of the net buffer. - /// - /// Fails if the net buffer holds fewer bytes than requested. - pub fn read_bytes(&self, buffer: &mut [u8]) -> Result<(), ()> { - unsafe { - let Some(nb) = self.nb.as_ref() else { - return Err(()); - }; - let data_length = nb.nbSize.DataLength; - if data_length == 0 { - return Err(()); - } - - if buffer.len() > data_length as usize { - return Err(()); - } - - // Two options: returns a pointer to the raw packet buffer if the - // requested range is contiguous, or copies into the supplied buffer. - let mut ptr = NdisGetDataBuffer(nb, buffer.len() as u32, core::ptr::null_mut(), 1, 0); - if !ptr.is_null() { - buffer.copy_from_slice(core::slice::from_raw_parts(ptr, buffer.len())); - return Ok(()); - } - - ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); - if !ptr.is_null() { - return Ok(()); - } - } - return Err(()); - } - - /// Copies this net buffer into a new, standalone NET_BUFFER_LIST holding a - /// single net buffer. - /// - /// Each net buffer of a chain is cloned into its own list rather than into a - /// chain of its own, because that is what the injection path takes: one - /// packet per injected list. - pub fn clone_as_nbl(&self, net_allocator: &NetworkAllocator) -> Result { - unsafe { - let Some(nb) = self.nb.as_ref() else { - return Err("net buffer is null".to_string()); - }; - - let data_length = nb.nbSize.DataLength; - if data_length == 0 { - return Err("can't clone empty packet".to_string()); - } - - let mut buffer = alloc::vec![0 as u8; data_length as usize]; - let buffer_ptr = buffer.as_mut_ptr(); - - let ptr = NdisGetDataBuffer(nb, data_length, buffer_ptr, 1, 0); - if ptr.is_null() { - return Err("failed to copy packet buffer".to_string()); - } - - // If the pointers differ the data is not in the correct place. - if ptr != buffer_ptr { - buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize)); - } - - let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?; - - return Ok(NetBufferList { - nbl: new_nbl, - data: Some(buffer), - }); - } - } - - /// Retreats the data start of the net buffer, exposing bytes that precede it. - /// - /// With `auto_advance` the retreat is undone when the value is dropped. - /// Returns an error if the retreat failed, in which case the data offset is - /// unchanged and reads would still return data from the old offset. - pub fn retreat(&mut self, size: u32, auto_advance: bool) -> Result<(), String> { - unsafe { - let Some(nb) = self.nb.as_mut() else { - return Err("net buffer is null".to_string()); - }; - - let status = NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); - check_ntstatus(status)?; - - if auto_advance { - self.advance_on_drop = Some(size); - } - - Ok(()) - } - } - - /// Advances the data start of the net buffer, undoing a retreat. - pub fn advance(&self, size: u32) { - unsafe { - if let Some(nb) = self.nb.as_mut() { - NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); - } - } - } -} - -impl Drop for NetBuffer { - fn drop(&mut self) { - if let Some(advance_amount) = self.advance_on_drop { - self.advance(advance_amount); - } - } -} - -pub struct NetBufferIter(*mut NET_BUFFER); - -impl NetBufferIter { - pub fn new(nb: *mut NET_BUFFER) -> Self { - Self(nb) - } -} - -impl Iterator for NetBufferIter { - type Item = NetBuffer; - - fn next(&mut self) -> Option { - unsafe { - if let Some(nb) = self.0.as_mut() { - self.0 = nb.Next; - return Some(NetBuffer { - nb, - advance_on_drop: None, - }); - } - None - } - } -} - -pub struct NetBufferList { - pub(crate) nbl: *mut NET_BUFFER_LIST, - data: Option>, -} - -impl NetBufferList { - pub fn new(nbl: *mut NET_BUFFER_LIST) -> NetBufferList { - NetBufferList { nbl, data: None } - } - - pub fn iter(&self) -> NetBufferListIter { - NetBufferListIter(self.nbl) - } - - /// Iterates over every net buffer in this list. - pub fn iter_net_buffers(&self) -> NetBufferIter { - unsafe { - match self.nbl.as_ref() { - Some(nbl) => NetBufferIter(nbl.Header.first_net_buffer), - None => NetBufferIter(core::ptr::null_mut()), - } - } - } - - /// Clones the first net buffer of the list. - /// - /// Only valid where the list is known to hold a single net buffer. Use - /// [`NetBufferList::iter_net_buffers`] with [`NetBuffer::clone_as_nbl`] to - /// clone a chain without dropping everything past the head. - pub fn clone(&self, net_allocator: &NetworkAllocator) -> Result { - unsafe { - let Some(nbl) = self.nbl.as_ref() else { - return Err("net buffer list is null".to_string()); - }; - - let nb = nbl.Header.first_net_buffer; - if let Some(nb) = nb.as_ref() { - let data_length = nb.nbSize.DataLength; - if data_length == 0 { - return Err("can't clone empty packet".to_string()); - } - - // Allocate space in buffer, if buffer is too small. - let mut buffer = alloc::vec![0 as u8; data_length as usize]; - - let buffer_ptr = buffer.as_mut_ptr(); - - // Two options returns a pointer to the raw packet buffer, - // or copies the data to the supplied buffer - // and returns a pointer to the supplied buffer. - let ptr = NdisGetDataBuffer(nb, data_length, buffer_ptr, 1, 0); - - if ptr.is_null() { - return Err("failed to copy packet buffer".to_string()); - } - - // If the pointers differ the data is not in the correct place. - if ptr != buffer_ptr { - buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize)); - } - - let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?; - - return Ok(NetBufferList { - nbl: new_nbl, - data: Some(buffer), - }); - } else { - return Err("net buffer is null".to_string()); - } - } - } - - pub fn get_data_mut(&mut self) -> Option<&mut [u8]> { - if let Some(data) = &mut self.data { - return Some(data.as_mut_slice()); - } - return None; - } - - pub fn get_data(&self) -> Option<&[u8]> { - if let Some(data) = &self.data { - return Some(data.as_slice()); - } - return None; - } - - pub fn get_data_length(&self) -> u64 { - unsafe { - if let Some(nbl) = self.nbl.as_ref() { - let mut nb = nbl.Header.first_net_buffer; - let mut data_length: u64 = 0; - while !nb.is_null() { - let mut next = core::ptr::null_mut(); - if let Some(nb) = nb.as_ref() { - data_length += nb.nbSize.DataLength as u64; - next = nb.Next; - } - nb = next; - } - - data_length - } else { - 0 - } - } - } - -} - -impl Drop for NetBufferList { - fn drop(&mut self) { - if self.data.is_some() { - NetworkAllocator::free_net_buffer(self.nbl); - } - } -} - -pub struct NetBufferListIter(*mut NET_BUFFER_LIST); - -impl NetBufferListIter { - pub fn new(nbl: *mut NET_BUFFER_LIST) -> Self { - Self(nbl) - } -} - -impl Iterator for NetBufferListIter { - type Item = NetBufferList; - - fn next(&mut self) -> Option { - unsafe { - if let Some(nbl) = self.0.as_mut() { - self.0 = nbl.Header.next as _; - return Some(NetBufferList { nbl, data: None }); - } - None - } - } -} - -pub fn read_packet_partial<'a>(nbl: *mut NET_BUFFER_LIST, buffer: &'a mut [u8]) -> Result<(), ()> { - unsafe { - let Some(nbl) = nbl.as_ref() else { - return Err(()); - }; - let nb = nbl.Header.first_net_buffer; - if let Some(nb) = nb.as_ref() { - let data_length = nb.nbSize.DataLength; - if data_length == 0 { - return Err(()); - } - - if buffer.len() > data_length as usize { - return Err(()); - } - - let ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); - if !ptr.is_null() { - return Ok(()); - } - } - } - return Err(()); -} - -pub struct RetreatGuard { - size: u32, - nbl: *mut NET_BUFFER_LIST, -} - -impl Drop for RetreatGuard { - fn drop(&mut self) { - NetworkAllocator::advance_net_buffer(self.nbl, self.size); - } -} - -pub struct NetworkAllocator { - pool_handle: NDIS_HANDLE, -} - -impl NetworkAllocator { - pub fn new() -> Self { - unsafe { - let mut params: NET_BUFFER_LIST_POOL_PARAMETERS = MaybeUninit::zeroed().assume_init(); - params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; - params.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; - params.Header.Size = core::mem::size_of::() as u16; - params.fAllocateNetBuffer = true; - params.PoolTag = POOL_TAG; - params.DataSize = 0; - - let pool_handle = NdisAllocateNetBufferListPool(core::ptr::null_mut(), ¶ms); - Self { pool_handle } - } - } - - pub fn wrap_packet_in_nbl(&self, packet_data: &[u8]) -> Result<*mut NET_BUFFER_LIST, String> { - if self.pool_handle.is_null() { - return Err("allocator not initialized".to_string()); - } - unsafe { - // Create MDL struct that will hold the buffer. - let mdl = IoAllocateMdl( - packet_data.as_ptr() as _, - packet_data.len() as u32, - 0, - 0, - core::ptr::null_mut(), - ); - if mdl.is_null() { - return Err("failed to allocate mdl".to_string()); - } - - // Build mdl with packet_data buffer. - MmBuildMdlForNonPagedPool(mdl); - - // Initialize NBL structure. - let mut nbl = core::ptr::null_mut(); - let status = FwpsAllocateNetBufferAndNetBufferList0( - self.pool_handle, - 0, - 0, - mdl, - 0, - packet_data.len() as u64, - &mut nbl, - ); - if let Err(err) = check_ntstatus(status) { - IoFreeMdl(mdl); - return Err(err); - } - return Ok(nbl); - } - } - - pub fn free_net_buffer(nbl: *mut NET_BUFFER_LIST) { - NetBufferListIter::new(nbl).for_each(|nbl| unsafe { - if let Some(nbl) = nbl.nbl.as_mut() { - if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { - IoFreeMdl(nb.MdlChain); - } - FwpsFreeNetBufferList0(nbl); - } - }); - } - - /// Retreats the data start of the first net buffer. - /// - /// Returns an error if the retreat failed; the returned guard is `None` when - /// the retreat succeeded but `auto_advance` was not requested. - pub fn retreat_net_buffer( - nbl: *mut NET_BUFFER_LIST, - size: u32, - auto_advance: bool, - ) -> Result, String> { - unsafe { - let Some(nbl_ref) = nbl.as_mut() else { - return Err("net buffer list is null".to_string()); - }; - let Some(nb) = nbl_ref.Header.first_net_buffer.as_mut() else { - return Err("net buffer is null".to_string()); - }; - - let status = NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); - check_ntstatus(status)?; - - if auto_advance { - return Ok(Some(RetreatGuard { size, nbl })); - } - } - - return Ok(None); - } - pub fn advance_net_buffer(nbl: *mut NET_BUFFER_LIST, size: u32) { - unsafe { - if let Some(nbl) = nbl.as_mut() { - if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { - NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); - } - } - } - } -} - -impl Drop for NetworkAllocator { - fn drop(&mut self) { - unsafe { - if !self.pool_handle.is_null() { - NdisFreeNetBufferListPool(self.pool_handle); - } - } - } -} +use core::mem::MaybeUninit; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use windows_sys::Wdk::System::SystemServices::{ + IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool, +}; + +use crate::{ + allocator::POOL_TAG, + ffi::{ + FwpsAllocateNetBufferAndNetBufferList0, FwpsFreeNetBufferList0, + NdisAdvanceNetBufferDataStart, NdisAllocateNetBufferListPool, NdisFreeNetBufferListPool, + NdisGetDataBuffer, NdisRetreatNetBufferDataStart, NDIS_HANDLE, NDIS_OBJECT_TYPE_DEFAULT, + NET_BUFFER, NET_BUFFER_LIST, NET_BUFFER_LIST_POOL_PARAMETERS, + NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1, + }, + utils::check_ntstatus, +}; + +/// A single NET_BUFFER inside a NET_BUFFER_LIST. +/// +/// A list can carry a chain of net buffers, and each one is an independent +/// packet with its own data offset, length and MDL chain. Everything that acts +/// on packet bytes -- retreating the data start, reading headers, cloning -- +/// is therefore per net buffer; doing it at the list level only ever touches +/// the head of the chain and silently ignores the rest. +pub struct NetBuffer { + nb: *mut NET_BUFFER, + advance_on_drop: Option, +} + +impl NetBuffer { + pub fn new(nb: *mut NET_BUFFER) -> NetBuffer { + NetBuffer { + nb, + advance_on_drop: None, + } + } + + pub fn get_data_length(&self) -> u32 { + unsafe { + match self.nb.as_ref() { + Some(nb) => nb.nbSize.DataLength, + None => 0, + } + } + } + + /// Reads `buffer.len()` bytes from the current data start of the net buffer. + /// + /// Fails if the net buffer holds fewer bytes than requested. + pub fn read_bytes(&self, buffer: &mut [u8]) -> Result<(), ()> { + unsafe { + let Some(nb) = self.nb.as_ref() else { + return Err(()); + }; + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err(()); + } + + if buffer.len() > data_length as usize { + return Err(()); + } + + // Two options: returns a pointer to the raw packet buffer if the + // requested range is contiguous, or copies into the supplied buffer. + let mut ptr = NdisGetDataBuffer(nb, buffer.len() as u32, core::ptr::null_mut(), 1, 0); + if !ptr.is_null() { + buffer.copy_from_slice(core::slice::from_raw_parts(ptr, buffer.len())); + return Ok(()); + } + + ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); + if !ptr.is_null() { + return Ok(()); + } + } + return Err(()); + } + + /// Copies this net buffer into a new, standalone NET_BUFFER_LIST holding a + /// single net buffer. + /// + /// Each net buffer of a chain is cloned into its own list rather than into a + /// chain of its own, because that is what the injection path takes: one + /// packet per injected list. + pub fn clone_as_nbl(&self, net_allocator: &NetworkAllocator) -> Result { + unsafe { + let Some(nb) = self.nb.as_ref() else { + return Err("net buffer is null".to_string()); + }; + + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err("can't clone empty packet".to_string()); + } + + let mut buffer = alloc::vec![0 as u8; data_length as usize]; + let buffer_ptr = buffer.as_mut_ptr(); + + let ptr = NdisGetDataBuffer(nb, data_length, buffer_ptr, 1, 0); + if ptr.is_null() { + return Err("failed to copy packet buffer".to_string()); + } + + // If the pointers differ the data is not in the correct place. + if ptr != buffer_ptr { + buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize)); + } + + let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?; + + return Ok(NetBufferList { + nbl: new_nbl, + data: Some(buffer), + }); + } + } + + /// Retreats the data start of the net buffer, exposing bytes that precede it. + /// + /// With `auto_advance` the retreat is undone when the value is dropped. + /// Returns an error if the retreat failed, in which case the data offset is + /// unchanged and reads would still return data from the old offset. + pub fn retreat(&mut self, size: u32, auto_advance: bool) -> Result<(), String> { + unsafe { + let Some(nb) = self.nb.as_mut() else { + return Err("net buffer is null".to_string()); + }; + + let status = NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); + check_ntstatus(status)?; + + if auto_advance { + self.advance_on_drop = Some(size); + } + + Ok(()) + } + } + + /// Advances the data start of the net buffer, undoing a retreat. + pub fn advance(&self, size: u32) { + unsafe { + if let Some(nb) = self.nb.as_mut() { + NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); + } + } + } +} + +impl Drop for NetBuffer { + fn drop(&mut self) { + if let Some(advance_amount) = self.advance_on_drop { + self.advance(advance_amount); + } + } +} + +pub struct NetBufferIter(*mut NET_BUFFER); + +impl NetBufferIter { + pub fn new(nb: *mut NET_BUFFER) -> Self { + Self(nb) + } +} + +impl Iterator for NetBufferIter { + type Item = NetBuffer; + + fn next(&mut self) -> Option { + unsafe { + if let Some(nb) = self.0.as_mut() { + self.0 = nb.Next; + return Some(NetBuffer { + nb, + advance_on_drop: None, + }); + } + None + } + } +} + +pub struct NetBufferList { + pub(crate) nbl: *mut NET_BUFFER_LIST, + data: Option>, +} + +impl NetBufferList { + pub fn new(nbl: *mut NET_BUFFER_LIST) -> NetBufferList { + NetBufferList { nbl, data: None } + } + + pub fn iter(&self) -> NetBufferListIter { + NetBufferListIter(self.nbl) + } + + /// Iterates over every net buffer in this list. + pub fn iter_net_buffers(&self) -> NetBufferIter { + unsafe { + match self.nbl.as_ref() { + Some(nbl) => NetBufferIter(nbl.Header.first_net_buffer), + None => NetBufferIter(core::ptr::null_mut()), + } + } + } + + /// Clones the first net buffer of the list. + /// + /// Only valid where the list is known to hold a single net buffer. Use + /// [`NetBufferList::iter_net_buffers`] with [`NetBuffer::clone_as_nbl`] to + /// clone a chain without dropping everything past the head. + pub fn clone(&self, net_allocator: &NetworkAllocator) -> Result { + unsafe { + let Some(nbl) = self.nbl.as_ref() else { + return Err("net buffer list is null".to_string()); + }; + + let nb = nbl.Header.first_net_buffer; + if let Some(nb) = nb.as_ref() { + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err("can't clone empty packet".to_string()); + } + + // Allocate space in buffer, if buffer is too small. + let mut buffer = alloc::vec![0 as u8; data_length as usize]; + + let buffer_ptr = buffer.as_mut_ptr(); + + // Two options returns a pointer to the raw packet buffer, + // or copies the data to the supplied buffer + // and returns a pointer to the supplied buffer. + let ptr = NdisGetDataBuffer(nb, data_length, buffer_ptr, 1, 0); + + if ptr.is_null() { + return Err("failed to copy packet buffer".to_string()); + } + + // If the pointers differ the data is not in the correct place. + if ptr != buffer_ptr { + buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize)); + } + + let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?; + + return Ok(NetBufferList { + nbl: new_nbl, + data: Some(buffer), + }); + } else { + return Err("net buffer is null".to_string()); + } + } + } + + pub fn get_data_mut(&mut self) -> Option<&mut [u8]> { + if let Some(data) = &mut self.data { + return Some(data.as_mut_slice()); + } + return None; + } + + pub fn get_data(&self) -> Option<&[u8]> { + if let Some(data) = &self.data { + return Some(data.as_slice()); + } + return None; + } + + pub fn get_data_length(&self) -> u64 { + unsafe { + if let Some(nbl) = self.nbl.as_ref() { + let mut nb = nbl.Header.first_net_buffer; + let mut data_length: u64 = 0; + while !nb.is_null() { + let mut next = core::ptr::null_mut(); + if let Some(nb) = nb.as_ref() { + data_length += nb.nbSize.DataLength as u64; + next = nb.Next; + } + nb = next; + } + + data_length + } else { + 0 + } + } + } + +} + +impl Drop for NetBufferList { + fn drop(&mut self) { + if self.data.is_some() { + NetworkAllocator::free_net_buffer(self.nbl); + } + } +} + +pub struct NetBufferListIter(*mut NET_BUFFER_LIST); + +impl NetBufferListIter { + pub fn new(nbl: *mut NET_BUFFER_LIST) -> Self { + Self(nbl) + } +} + +impl Iterator for NetBufferListIter { + type Item = NetBufferList; + + fn next(&mut self) -> Option { + unsafe { + if let Some(nbl) = self.0.as_mut() { + self.0 = nbl.Header.next as _; + return Some(NetBufferList { nbl, data: None }); + } + None + } + } +} + +pub fn read_packet_partial<'a>(nbl: *mut NET_BUFFER_LIST, buffer: &'a mut [u8]) -> Result<(), ()> { + unsafe { + let Some(nbl) = nbl.as_ref() else { + return Err(()); + }; + let nb = nbl.Header.first_net_buffer; + if let Some(nb) = nb.as_ref() { + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err(()); + } + + if buffer.len() > data_length as usize { + return Err(()); + } + + let ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); + if !ptr.is_null() { + return Ok(()); + } + } + } + return Err(()); +} + +pub struct RetreatGuard { + size: u32, + nbl: *mut NET_BUFFER_LIST, +} + +impl Drop for RetreatGuard { + fn drop(&mut self) { + NetworkAllocator::advance_net_buffer(self.nbl, self.size); + } +} + +pub struct NetworkAllocator { + pool_handle: NDIS_HANDLE, +} + +impl NetworkAllocator { + pub fn new() -> Self { + unsafe { + let mut params: NET_BUFFER_LIST_POOL_PARAMETERS = MaybeUninit::zeroed().assume_init(); + params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + params.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + params.Header.Size = core::mem::size_of::() as u16; + params.fAllocateNetBuffer = true; + params.PoolTag = POOL_TAG; + params.DataSize = 0; + + let pool_handle = NdisAllocateNetBufferListPool(core::ptr::null_mut(), ¶ms); + Self { pool_handle } + } + } + + pub fn wrap_packet_in_nbl(&self, packet_data: &[u8]) -> Result<*mut NET_BUFFER_LIST, String> { + if self.pool_handle.is_null() { + return Err("allocator not initialized".to_string()); + } + unsafe { + // Create MDL struct that will hold the buffer. + let mdl = IoAllocateMdl( + packet_data.as_ptr() as _, + packet_data.len() as u32, + 0, + 0, + core::ptr::null_mut(), + ); + if mdl.is_null() { + return Err("failed to allocate mdl".to_string()); + } + + // Build mdl with packet_data buffer. + MmBuildMdlForNonPagedPool(mdl); + + // Initialize NBL structure. + let mut nbl = core::ptr::null_mut(); + let status = FwpsAllocateNetBufferAndNetBufferList0( + self.pool_handle, + 0, + 0, + mdl, + 0, + packet_data.len() as u64, + &mut nbl, + ); + if let Err(err) = check_ntstatus(status) { + IoFreeMdl(mdl); + return Err(err); + } + return Ok(nbl); + } + } + + pub fn free_net_buffer(nbl: *mut NET_BUFFER_LIST) { + NetBufferListIter::new(nbl).for_each(|nbl| unsafe { + if let Some(nbl) = nbl.nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + IoFreeMdl(nb.MdlChain); + } + FwpsFreeNetBufferList0(nbl); + } + }); + } + + /// Retreats the data start of the first net buffer. + /// + /// Returns an error if the retreat failed; the returned guard is `None` when + /// the retreat succeeded but `auto_advance` was not requested. + pub fn retreat_net_buffer( + nbl: *mut NET_BUFFER_LIST, + size: u32, + auto_advance: bool, + ) -> Result, String> { + unsafe { + let Some(nbl_ref) = nbl.as_mut() else { + return Err("net buffer list is null".to_string()); + }; + let Some(nb) = nbl_ref.Header.first_net_buffer.as_mut() else { + return Err("net buffer is null".to_string()); + }; + + let status = NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); + check_ntstatus(status)?; + + if auto_advance { + return Ok(Some(RetreatGuard { size, nbl })); + } + } + + return Ok(None); + } + pub fn advance_net_buffer(nbl: *mut NET_BUFFER_LIST, size: u32) { + unsafe { + if let Some(nbl) = nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); + } + } + } + } +} + +impl Drop for NetworkAllocator { + fn drop(&mut self) { + unsafe { + if !self.pool_handle.is_null() { + NdisFreeNetBufferListPool(self.pool_handle); + } + } + } +} diff --git a/wdk/src/filter_engine/packet.rs b/wdk/src/filter_engine/packet.rs index 52e1393..d6a0492 100644 --- a/wdk/src/filter_engine/packet.rs +++ b/wdk/src/filter_engine/packet.rs @@ -1,392 +1,392 @@ -use alloc::{ - boxed::Box, - string::{String, ToString}, -}; -use core::{ffi::c_void, mem::MaybeUninit}; -use windows_sys::Win32::{ - Foundation::{HANDLE, INVALID_HANDLE_VALUE}, - Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SCOPE_ID}, - System::Kernel::{COMPARTMENT_ID, UNSPECIFIED_COMPARTMENT_ID}, -}; - -use crate::{ - ffi::{ - FwpsInjectNetworkReceiveAsync0, FwpsInjectNetworkSendAsync0, - FwpsInjectTransportReceiveAsync0, FwpsInjectTransportSendAsync1, - FwpsInjectionHandleCreate0, FwpsInjectionHandleDestroy0, FwpsQueryPacketInjectionState0, - FWPS_INJECTION_TYPE_NETWORK, FWPS_INJECTION_TYPE_TRANSPORT, FWPS_PACKET_INJECTION_STATE, - FWPS_TRANSPORT_SEND_PARAMS1, NET_BUFFER_LIST, - }, - utils::check_ntstatus, -}; - -use super::{callout_data::CalloutData, net_buffer::NetBufferList}; - -pub struct TransportPacketList { - ipv6: bool, - pub net_buffer_list_queue: NetBufferList, - remote_ip: [u8; 16], - endpoint_handle: u64, - remote_scope_id: SCOPE_ID, - control_data: Option>, - inbound: bool, - interface_index: u32, - sub_interface_index: u32, -} - -/// Owns everything an asynchronous transport injection references. -/// -/// `FwpsInjectTransport*Async*` returns before the packet is actually sent, and -/// the network stack keeps reading the NET_BUFFER_LIST, the control data blob -/// (`FWPS_TRANSPORT_SEND_PARAMS1.control_data`) and the remote address buffer -/// (`remote_address`) after the call returns. All three must therefore stay -/// alive until the injection completion routine (`free_transport_packet`) runs, -/// not just for the duration of the inject call. The `FWPS_TRANSPORT_SEND_PARAMS1` -/// struct itself does not need to outlive the call — WFP copies it synchronously — -/// only the buffers it points into. -struct TransportInjectContext { - nbl: NetBufferList, - control_data: Option>, - remote_ip: [u8; 16], -} - -pub struct InjectInfo { - pub ipv6: bool, - pub inbound: bool, - pub loopback: bool, - pub interface_index: u32, - pub sub_interface_index: u32, - pub compartment_id: COMPARTMENT_ID, -} - -pub struct Injector { - transport_inject_handle: HANDLE, - packet_inject_handle_v4: HANDLE, - packet_inject_handle_v6: HANDLE, -} - -// TODO: Implement custom allocator for the packet buffers for reusing memory and reducing allocations. This should improve latency. -impl Injector { - pub fn new() -> Self { - let mut transport_inject_handle: HANDLE = INVALID_HANDLE_VALUE; - let mut packet_inject_handle_v4: HANDLE = INVALID_HANDLE_VALUE; - let mut packet_inject_handle_v6: HANDLE = INVALID_HANDLE_VALUE; - unsafe { - let status = FwpsInjectionHandleCreate0( - AF_UNSPEC, - FWPS_INJECTION_TYPE_TRANSPORT, - &mut transport_inject_handle, - ); - if let Err(err) = check_ntstatus(status) { - crate::err!("error allocating transport inject handle: {}", err); - } - let status = FwpsInjectionHandleCreate0( - AF_INET, - FWPS_INJECTION_TYPE_NETWORK, - &mut packet_inject_handle_v4, - ); - - if let Err(err) = check_ntstatus(status) { - crate::err!("error allocating network inject handle: {}", err); - } - let status = FwpsInjectionHandleCreate0( - AF_INET6, - FWPS_INJECTION_TYPE_NETWORK, - &mut packet_inject_handle_v6, - ); - - if let Err(err) = check_ntstatus(status) { - crate::err!("error allocating network inject handle: {}", err); - } - } - Self { - transport_inject_handle, - packet_inject_handle_v4, - packet_inject_handle_v6, - } - } - - // TODO: pick a better name - pub fn from_ale_callout( - ipv6: bool, - callout_data: &CalloutData, - net_buffer_list: NetBufferList, - remote_ip_slice: &[u8], - inbound: bool, - interface_index: u32, - sub_interface_index: u32, - ) -> TransportPacketList { - // Copy the transport control data into a buffer we own. The pointer in - // the WFP metadata is only valid for the duration of this classify - // callout, but the packet is injected later, once user space returns a - // verdict. - let control_data = callout_data.get_control_data_copy(); - let mut remote_ip: [u8; 16] = [0; 16]; - if ipv6 { - remote_ip[0..16].copy_from_slice(&remote_ip_slice); - } else { - remote_ip[0..4].copy_from_slice(&remote_ip_slice); - } - - TransportPacketList { - ipv6, - net_buffer_list_queue: net_buffer_list, - remote_ip, - endpoint_handle: callout_data.get_transport_endpoint_handle().unwrap_or(0), - remote_scope_id: callout_data - .get_remote_scope_id() - .unwrap_or(unsafe { MaybeUninit::zeroed().assume_init() }), - control_data, - inbound, - interface_index, - sub_interface_index, - } - } - - // TODO: pick a better name. This is not transport - pub fn inject_packet_list_transport( - &self, - packet_list: TransportPacketList, - ) -> Result<(), String> { - if self.transport_inject_handle == INVALID_HANDLE_VALUE { - return Err("failed to inject packet: invalid handle value".to_string()); - } - unsafe { - let address_family = if packet_list.ipv6 { AF_INET6 } else { AF_INET }; - let inbound = packet_list.inbound; - let endpoint_handle = packet_list.endpoint_handle; - let remote_scope_id = packet_list.remote_scope_id; - let interface_index = packet_list.interface_index; - let sub_interface_index = packet_list.sub_interface_index; - - // Escape the stack. The NBL, the control data blob and the remote - // address are all read by the network stack *after* this asynchronous - // inject call returns, so they must remain valid until the completion - // routine (`free_transport_packet`) runs. Move them onto the heap and - // hand ownership to WFP as the completion context. - let context = Box::new(TransportInjectContext { - nbl: packet_list.net_buffer_list_queue, - control_data: packet_list.control_data, - remote_ip: packet_list.remote_ip, - }); - let raw_ptr = Box::into_raw(context); - let raw_nbl = (*raw_ptr).nbl.nbl; - - // Derive the pointers from the heap-stable context, not the stack. - let (control_data, control_data_length): (*mut c_void, u32) = - match &(*raw_ptr).control_data { - Some(cd) => (cd.as_ptr() as *mut c_void, cd.len() as u32), - None => (core::ptr::null_mut(), 0), - }; - - // `send_params` itself may live on the stack: WFP copies the struct - // synchronously. Only the buffers it points into must outlive the call. - let mut send_params = FWPS_TRANSPORT_SEND_PARAMS1 { - remote_address: &(*raw_ptr).remote_ip as _, - remote_scope_id, - control_data, - control_data_length, - header_include_header: core::ptr::null_mut(), - header_include_header_length: 0, - }; - - // Inject - let status = if inbound { - FwpsInjectTransportReceiveAsync0( - self.transport_inject_handle, - 0, - core::ptr::null_mut(), - 0, - address_family, - UNSPECIFIED_COMPARTMENT_ID, - interface_index, - sub_interface_index, - raw_nbl, - free_transport_packet, - raw_ptr as _, - ) - } else { - FwpsInjectTransportSendAsync1( - self.transport_inject_handle, - 0, - endpoint_handle, - 0, - &mut send_params, - address_family, - UNSPECIFIED_COMPARTMENT_ID, - raw_nbl, - free_transport_packet, - raw_ptr as _, - ) - }; - // Check for success - if let Err(err) = check_ntstatus(status) { - // Injection never started; reclaim and drop the whole context. - _ = Box::from_raw(raw_ptr); - return Err(err); - } - } - - return Ok(()); - } - - pub fn inject_net_buffer_list( - &self, - net_buffer_list: NetBufferList, - inject_info: InjectInfo, - ) -> Result<(), String> { - if self.packet_inject_handle_v4 == INVALID_HANDLE_VALUE { - return Err("failed to inject packet: invalid handle value".to_string()); - } - // Escape the stack, so the data can be freed after inject is complete. - let packet_boxed = Box::new(net_buffer_list); - let nbl = packet_boxed.nbl; - let packet_pointer = Box::into_raw(packet_boxed); - - let inject_handle = if inject_info.ipv6 { - self.packet_inject_handle_v6 - } else { - self.packet_inject_handle_v4 - }; - - let status = if inject_info.inbound && !inject_info.loopback { - // Inject inbound. - unsafe { - FwpsInjectNetworkReceiveAsync0( - inject_handle, - 0, - 0, - inject_info.compartment_id, - inject_info.interface_index, - inject_info.sub_interface_index, - nbl, - free_packet, - (packet_pointer as *mut NetBufferList) as _, - ) - } - } else { - // Inject outbound. - unsafe { - FwpsInjectNetworkSendAsync0( - inject_handle, - 0, - 0, - inject_info.compartment_id, - nbl, - free_packet, - (packet_pointer as *mut NetBufferList) as _, - ) - } - }; - - // Check for error. - if let Err(err) = check_ntstatus(status) { - unsafe { - // Get back ownership for data. - _ = Box::from_raw(packet_pointer); - } - return Err(err); - } - - return Ok(()); - } - - pub fn was_network_packet_injected_by_self( - &self, - nbl: *const NET_BUFFER_LIST, - ipv6: bool, - ) -> bool { - let inject_handle = if ipv6 { - self.packet_inject_handle_v6 - } else { - self.packet_inject_handle_v4 - }; - if inject_handle == INVALID_HANDLE_VALUE || inject_handle == 0 { - return false; - } - - unsafe { - let state = FwpsQueryPacketInjectionState0(inject_handle, nbl, core::ptr::null_mut()); - - match state { - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, - } - } - } - - pub fn was_network_packet_injected_by_self_ale(&self, nbl: *const NET_BUFFER_LIST) -> bool { - unsafe { - let state = FwpsQueryPacketInjectionState0( - self.transport_inject_handle, - nbl, - core::ptr::null_mut(), - ); - - match state { - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, - FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, - } - } - } -} - -impl Drop for Injector { - fn drop(&mut self) { - unsafe { - if self.transport_inject_handle != INVALID_HANDLE_VALUE - && self.transport_inject_handle != 0 - { - FwpsInjectionHandleDestroy0(self.transport_inject_handle); - self.transport_inject_handle = INVALID_HANDLE_VALUE; - } - if self.packet_inject_handle_v4 != INVALID_HANDLE_VALUE - && self.packet_inject_handle_v4 != 0 - { - FwpsInjectionHandleDestroy0(self.packet_inject_handle_v4); - self.packet_inject_handle_v4 = INVALID_HANDLE_VALUE; - } - if self.packet_inject_handle_v6 != INVALID_HANDLE_VALUE - && self.packet_inject_handle_v6 != 0 - { - FwpsInjectionHandleDestroy0(self.packet_inject_handle_v6); - self.packet_inject_handle_v6 = INVALID_HANDLE_VALUE; - } - } - } -} - -unsafe extern "C" fn free_packet( - context: *mut c_void, - net_buffer_list: *mut NET_BUFFER_LIST, - _dispatch_level: bool, -) { - if let Some(nbl) = net_buffer_list.as_ref() { - if let Err(err) = check_ntstatus(nbl.Status) { - crate::err!("inject status: {}", err); - } - } - _ = Box::from_raw(context as *mut NetBufferList); -} - -// Completion routine for transport injections. Reclaims the whole -// `TransportInjectContext` — dropping the NBL together with the control data -// blob and the remote address buffer that the send referenced asynchronously. -unsafe extern "C" fn free_transport_packet( - context: *mut c_void, - net_buffer_list: *mut NET_BUFFER_LIST, - _dispatch_level: bool, -) { - if let Some(nbl) = net_buffer_list.as_ref() { - if let Err(err) = check_ntstatus(nbl.Status) { - crate::err!("inject status: {}", err); - } - } - _ = Box::from_raw(context as *mut TransportInjectContext); -} +use alloc::{ + boxed::Box, + string::{String, ToString}, +}; +use core::{ffi::c_void, mem::MaybeUninit}; +use windows_sys::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SCOPE_ID}, + System::Kernel::{COMPARTMENT_ID, UNSPECIFIED_COMPARTMENT_ID}, +}; + +use crate::{ + ffi::{ + FwpsInjectNetworkReceiveAsync0, FwpsInjectNetworkSendAsync0, + FwpsInjectTransportReceiveAsync0, FwpsInjectTransportSendAsync1, + FwpsInjectionHandleCreate0, FwpsInjectionHandleDestroy0, FwpsQueryPacketInjectionState0, + FWPS_INJECTION_TYPE_NETWORK, FWPS_INJECTION_TYPE_TRANSPORT, FWPS_PACKET_INJECTION_STATE, + FWPS_TRANSPORT_SEND_PARAMS1, NET_BUFFER_LIST, + }, + utils::check_ntstatus, +}; + +use super::{callout_data::CalloutData, net_buffer::NetBufferList}; + +pub struct TransportPacketList { + ipv6: bool, + pub net_buffer_list_queue: NetBufferList, + remote_ip: [u8; 16], + endpoint_handle: u64, + remote_scope_id: SCOPE_ID, + control_data: Option>, + inbound: bool, + interface_index: u32, + sub_interface_index: u32, +} + +/// Owns everything an asynchronous transport injection references. +/// +/// `FwpsInjectTransport*Async*` returns before the packet is actually sent, and +/// the network stack keeps reading the NET_BUFFER_LIST, the control data blob +/// (`FWPS_TRANSPORT_SEND_PARAMS1.control_data`) and the remote address buffer +/// (`remote_address`) after the call returns. All three must therefore stay +/// alive until the injection completion routine (`free_transport_packet`) runs, +/// not just for the duration of the inject call. The `FWPS_TRANSPORT_SEND_PARAMS1` +/// struct itself does not need to outlive the call — WFP copies it synchronously — +/// only the buffers it points into. +struct TransportInjectContext { + nbl: NetBufferList, + control_data: Option>, + remote_ip: [u8; 16], +} + +pub struct InjectInfo { + pub ipv6: bool, + pub inbound: bool, + pub loopback: bool, + pub interface_index: u32, + pub sub_interface_index: u32, + pub compartment_id: COMPARTMENT_ID, +} + +pub struct Injector { + transport_inject_handle: HANDLE, + packet_inject_handle_v4: HANDLE, + packet_inject_handle_v6: HANDLE, +} + +// TODO: Implement custom allocator for the packet buffers for reusing memory and reducing allocations. This should improve latency. +impl Injector { + pub fn new() -> Self { + let mut transport_inject_handle: HANDLE = INVALID_HANDLE_VALUE; + let mut packet_inject_handle_v4: HANDLE = INVALID_HANDLE_VALUE; + let mut packet_inject_handle_v6: HANDLE = INVALID_HANDLE_VALUE; + unsafe { + let status = FwpsInjectionHandleCreate0( + AF_UNSPEC, + FWPS_INJECTION_TYPE_TRANSPORT, + &mut transport_inject_handle, + ); + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating transport inject handle: {}", err); + } + let status = FwpsInjectionHandleCreate0( + AF_INET, + FWPS_INJECTION_TYPE_NETWORK, + &mut packet_inject_handle_v4, + ); + + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating network inject handle: {}", err); + } + let status = FwpsInjectionHandleCreate0( + AF_INET6, + FWPS_INJECTION_TYPE_NETWORK, + &mut packet_inject_handle_v6, + ); + + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating network inject handle: {}", err); + } + } + Self { + transport_inject_handle, + packet_inject_handle_v4, + packet_inject_handle_v6, + } + } + + // TODO: pick a better name + pub fn from_ale_callout( + ipv6: bool, + callout_data: &CalloutData, + net_buffer_list: NetBufferList, + remote_ip_slice: &[u8], + inbound: bool, + interface_index: u32, + sub_interface_index: u32, + ) -> TransportPacketList { + // Copy the transport control data into a buffer we own. The pointer in + // the WFP metadata is only valid for the duration of this classify + // callout, but the packet is injected later, once user space returns a + // verdict. + let control_data = callout_data.get_control_data_copy(); + let mut remote_ip: [u8; 16] = [0; 16]; + if ipv6 { + remote_ip[0..16].copy_from_slice(&remote_ip_slice); + } else { + remote_ip[0..4].copy_from_slice(&remote_ip_slice); + } + + TransportPacketList { + ipv6, + net_buffer_list_queue: net_buffer_list, + remote_ip, + endpoint_handle: callout_data.get_transport_endpoint_handle().unwrap_or(0), + remote_scope_id: callout_data + .get_remote_scope_id() + .unwrap_or(unsafe { MaybeUninit::zeroed().assume_init() }), + control_data, + inbound, + interface_index, + sub_interface_index, + } + } + + // TODO: pick a better name. This is not transport + pub fn inject_packet_list_transport( + &self, + packet_list: TransportPacketList, + ) -> Result<(), String> { + if self.transport_inject_handle == INVALID_HANDLE_VALUE { + return Err("failed to inject packet: invalid handle value".to_string()); + } + unsafe { + let address_family = if packet_list.ipv6 { AF_INET6 } else { AF_INET }; + let inbound = packet_list.inbound; + let endpoint_handle = packet_list.endpoint_handle; + let remote_scope_id = packet_list.remote_scope_id; + let interface_index = packet_list.interface_index; + let sub_interface_index = packet_list.sub_interface_index; + + // Escape the stack. The NBL, the control data blob and the remote + // address are all read by the network stack *after* this asynchronous + // inject call returns, so they must remain valid until the completion + // routine (`free_transport_packet`) runs. Move them onto the heap and + // hand ownership to WFP as the completion context. + let context = Box::new(TransportInjectContext { + nbl: packet_list.net_buffer_list_queue, + control_data: packet_list.control_data, + remote_ip: packet_list.remote_ip, + }); + let raw_ptr = Box::into_raw(context); + let raw_nbl = (*raw_ptr).nbl.nbl; + + // Derive the pointers from the heap-stable context, not the stack. + let (control_data, control_data_length): (*mut c_void, u32) = + match &(*raw_ptr).control_data { + Some(cd) => (cd.as_ptr() as *mut c_void, cd.len() as u32), + None => (core::ptr::null_mut(), 0), + }; + + // `send_params` itself may live on the stack: WFP copies the struct + // synchronously. Only the buffers it points into must outlive the call. + let mut send_params = FWPS_TRANSPORT_SEND_PARAMS1 { + remote_address: &(*raw_ptr).remote_ip as _, + remote_scope_id, + control_data, + control_data_length, + header_include_header: core::ptr::null_mut(), + header_include_header_length: 0, + }; + + // Inject + let status = if inbound { + FwpsInjectTransportReceiveAsync0( + self.transport_inject_handle, + 0, + core::ptr::null_mut(), + 0, + address_family, + UNSPECIFIED_COMPARTMENT_ID, + interface_index, + sub_interface_index, + raw_nbl, + free_transport_packet, + raw_ptr as _, + ) + } else { + FwpsInjectTransportSendAsync1( + self.transport_inject_handle, + 0, + endpoint_handle, + 0, + &mut send_params, + address_family, + UNSPECIFIED_COMPARTMENT_ID, + raw_nbl, + free_transport_packet, + raw_ptr as _, + ) + }; + // Check for success + if let Err(err) = check_ntstatus(status) { + // Injection never started; reclaim and drop the whole context. + _ = Box::from_raw(raw_ptr); + return Err(err); + } + } + + return Ok(()); + } + + pub fn inject_net_buffer_list( + &self, + net_buffer_list: NetBufferList, + inject_info: InjectInfo, + ) -> Result<(), String> { + if self.packet_inject_handle_v4 == INVALID_HANDLE_VALUE { + return Err("failed to inject packet: invalid handle value".to_string()); + } + // Escape the stack, so the data can be freed after inject is complete. + let packet_boxed = Box::new(net_buffer_list); + let nbl = packet_boxed.nbl; + let packet_pointer = Box::into_raw(packet_boxed); + + let inject_handle = if inject_info.ipv6 { + self.packet_inject_handle_v6 + } else { + self.packet_inject_handle_v4 + }; + + let status = if inject_info.inbound && !inject_info.loopback { + // Inject inbound. + unsafe { + FwpsInjectNetworkReceiveAsync0( + inject_handle, + 0, + 0, + inject_info.compartment_id, + inject_info.interface_index, + inject_info.sub_interface_index, + nbl, + free_packet, + (packet_pointer as *mut NetBufferList) as _, + ) + } + } else { + // Inject outbound. + unsafe { + FwpsInjectNetworkSendAsync0( + inject_handle, + 0, + 0, + inject_info.compartment_id, + nbl, + free_packet, + (packet_pointer as *mut NetBufferList) as _, + ) + } + }; + + // Check for error. + if let Err(err) = check_ntstatus(status) { + unsafe { + // Get back ownership for data. + _ = Box::from_raw(packet_pointer); + } + return Err(err); + } + + return Ok(()); + } + + pub fn was_network_packet_injected_by_self( + &self, + nbl: *const NET_BUFFER_LIST, + ipv6: bool, + ) -> bool { + let inject_handle = if ipv6 { + self.packet_inject_handle_v6 + } else { + self.packet_inject_handle_v4 + }; + if inject_handle == INVALID_HANDLE_VALUE || inject_handle == 0 { + return false; + } + + unsafe { + let state = FwpsQueryPacketInjectionState0(inject_handle, nbl, core::ptr::null_mut()); + + match state { + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, + } + } + } + + pub fn was_network_packet_injected_by_self_ale(&self, nbl: *const NET_BUFFER_LIST) -> bool { + unsafe { + let state = FwpsQueryPacketInjectionState0( + self.transport_inject_handle, + nbl, + core::ptr::null_mut(), + ); + + match state { + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, + } + } + } +} + +impl Drop for Injector { + fn drop(&mut self) { + unsafe { + if self.transport_inject_handle != INVALID_HANDLE_VALUE + && self.transport_inject_handle != 0 + { + FwpsInjectionHandleDestroy0(self.transport_inject_handle); + self.transport_inject_handle = INVALID_HANDLE_VALUE; + } + if self.packet_inject_handle_v4 != INVALID_HANDLE_VALUE + && self.packet_inject_handle_v4 != 0 + { + FwpsInjectionHandleDestroy0(self.packet_inject_handle_v4); + self.packet_inject_handle_v4 = INVALID_HANDLE_VALUE; + } + if self.packet_inject_handle_v6 != INVALID_HANDLE_VALUE + && self.packet_inject_handle_v6 != 0 + { + FwpsInjectionHandleDestroy0(self.packet_inject_handle_v6); + self.packet_inject_handle_v6 = INVALID_HANDLE_VALUE; + } + } + } +} + +unsafe extern "C" fn free_packet( + context: *mut c_void, + net_buffer_list: *mut NET_BUFFER_LIST, + _dispatch_level: bool, +) { + if let Some(nbl) = net_buffer_list.as_ref() { + if let Err(err) = check_ntstatus(nbl.Status) { + crate::err!("inject status: {}", err); + } + } + _ = Box::from_raw(context as *mut NetBufferList); +} + +// Completion routine for transport injections. Reclaims the whole +// `TransportInjectContext` — dropping the NBL together with the control data +// blob and the remote address buffer that the send referenced asynchronously. +unsafe extern "C" fn free_transport_packet( + context: *mut c_void, + net_buffer_list: *mut NET_BUFFER_LIST, + _dispatch_level: bool, +) { + if let Some(nbl) = net_buffer_list.as_ref() { + if let Err(err) = check_ntstatus(nbl.Status) { + crate::err!("inject status: {}", err); + } + } + _ = Box::from_raw(context as *mut TransportInjectContext); +} diff --git a/wdk/src/filter_engine/stream_data.rs b/wdk/src/filter_engine/stream_data.rs index 591d87e..7f36ee9 100644 --- a/wdk/src/filter_engine/stream_data.rs +++ b/wdk/src/filter_engine/stream_data.rs @@ -1,67 +1,67 @@ -use crate::ffi::{NET_BUFFER, NET_BUFFER_LIST}; -use windows_sys::Wdk::Foundation::MDL; - -const FWPS_STREAM_FLAG_RECEIVE: u32 = 0x00000001; - -#[repr(C)] -pub enum StreamActionType { - None, - NeedMoreData, - DropConnection, - Defer, - AllowConnection, - TypeMax, -} - -#[repr(C)] -pub struct StreamCalloutIoPacket { - stream_data: *mut StreamData, - missed_bytes: usize, - count_bytes_required: usize, - count_bytes_enforced: usize, - stream_action: StreamActionType, -} - -#[repr(C)] -pub struct StreamDataOffset { - // NET_BUFFER_LIST in which offset lies. - net_buffer_list: *mut NET_BUFFER_LIST, - // NET_BUFFER in which offset lies. - net_buffer: *mut NET_BUFFER, - // MDL in which offset lies. - mdl: *mut MDL, - // Byte offset from the beginning of the MDL in which data lies. - mdl_offset: u32, - // Offset relative to the DataOffset of the NET_BUFFER. - net_buffer_offset: u32, - // Offset from the beginning of the entire stream buffer. - stream_data_offset: usize, -} - -#[repr(C)] -pub struct StreamData { - flags: u32, - data_offset: StreamDataOffset, - data_length: usize, - net_buffer_list_chain: *mut NET_BUFFER_LIST, -} - -impl StreamCalloutIoPacket { - pub fn get_data_len(&self) -> usize { - unsafe { - if let Some(stream_data) = self.stream_data.as_ref() { - return stream_data.data_length; - } - } - return 0; - } - - pub fn is_receive(&self) -> bool { - unsafe { - if let Some(stream_data) = self.stream_data.as_ref() { - return stream_data.flags & FWPS_STREAM_FLAG_RECEIVE > 0; - } - } - return false; - } -} +use crate::ffi::{NET_BUFFER, NET_BUFFER_LIST}; +use windows_sys::Wdk::Foundation::MDL; + +const FWPS_STREAM_FLAG_RECEIVE: u32 = 0x00000001; + +#[repr(C)] +pub enum StreamActionType { + None, + NeedMoreData, + DropConnection, + Defer, + AllowConnection, + TypeMax, +} + +#[repr(C)] +pub struct StreamCalloutIoPacket { + stream_data: *mut StreamData, + missed_bytes: usize, + count_bytes_required: usize, + count_bytes_enforced: usize, + stream_action: StreamActionType, +} + +#[repr(C)] +pub struct StreamDataOffset { + // NET_BUFFER_LIST in which offset lies. + net_buffer_list: *mut NET_BUFFER_LIST, + // NET_BUFFER in which offset lies. + net_buffer: *mut NET_BUFFER, + // MDL in which offset lies. + mdl: *mut MDL, + // Byte offset from the beginning of the MDL in which data lies. + mdl_offset: u32, + // Offset relative to the DataOffset of the NET_BUFFER. + net_buffer_offset: u32, + // Offset from the beginning of the entire stream buffer. + stream_data_offset: usize, +} + +#[repr(C)] +pub struct StreamData { + flags: u32, + data_offset: StreamDataOffset, + data_length: usize, + net_buffer_list_chain: *mut NET_BUFFER_LIST, +} + +impl StreamCalloutIoPacket { + pub fn get_data_len(&self) -> usize { + unsafe { + if let Some(stream_data) = self.stream_data.as_ref() { + return stream_data.data_length; + } + } + return 0; + } + + pub fn is_receive(&self) -> bool { + unsafe { + if let Some(stream_data) = self.stream_data.as_ref() { + return stream_data.flags & FWPS_STREAM_FLAG_RECEIVE > 0; + } + } + return false; + } +} diff --git a/wdk/src/filter_engine/transaction.rs b/wdk/src/filter_engine/transaction.rs index 76c4c3c..c383656 100644 --- a/wdk/src/filter_engine/transaction.rs +++ b/wdk/src/filter_engine/transaction.rs @@ -1,146 +1,146 @@ -use core::ops::{Deref, DerefMut}; - -use super::{ffi, FilterEngine}; -use crate::consts::STATUS_FWP_TXN_IN_PROGRESS; -use crate::utils::{ntstatus_name, sleep_ms}; -use alloc::{format, string::String}; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_TXN_READ_ONLY; - -/// Why beginning a filter engine transaction failed. -pub(super) enum BeginError { - /// Another transaction is already in progress on the filter engine session. Only one can be - /// open at a time. Nothing was changed, so the caller may simply try again later. - InProgress, - /// Anything else. Not worth repeating. - Failed(String), -} - -impl From for String { - fn from(err: BeginError) -> Self { - match err { - BeginError::InProgress => format!( - "filter-engine: failed to begin transaction: {}", - ntstatus_name(STATUS_FWP_TXN_IN_PROGRESS) - ), - BeginError::Failed(message) => message, - } - } -} - -/// Transaction guard for Filter Engine. Internally useses a lock. DO NOT USE WITH OTHER LOCKS. -pub(super) struct Transaction<'a> { - filter_engine: &'a mut FilterEngine, - committed: bool, -} - -impl<'a> Transaction<'a> { - fn begin(filter_engine: &'a mut FilterEngine, flags: u32) -> Result { - if let Err(status) = ffi::filter_engine_transaction_begin(filter_engine.handle, flags) { - if status == STATUS_FWP_TXN_IN_PROGRESS { - return Err(BeginError::InProgress); - } - return Err(BeginError::Failed(format!( - "filter-engine: failed to begin transaction: {}", - ntstatus_name(status) - ))); - } - - Ok(Self { - filter_engine, - committed: false, - }) - } - - /// Creates a read only guard for filter engine transaction. - #[allow(dead_code)] - pub(super) fn begin_read(filter_engine: &'a mut FilterEngine) -> Result { - return Self::begin(filter_engine, FWPM_TXN_READ_ONLY); - } - - /// Creates a read/write guard for filter engine transaction. - pub(super) fn begin_write(filter_engine: &'a mut FilterEngine) -> Result { - return Self::begin(filter_engine, 0); - } - - /// Same as `begin_write`, but waits and tries again while another transaction is in progress. - /// - /// Sleeps between the attempts, so it is only usable at IRQL <= APC_LEVEL. Callers that must - /// not block, or that want to batch the retries with other work (the driver does that for the - /// filter resets), use `begin_write` and handle `BeginError::InProgress` themselves. - pub(super) fn begin_write_retrying( - filter_engine: &'a mut FilterEngine, - ) -> Result { - // The handle is read up front so the mutable borrow of the filter engine is only taken by - // the attempt that actually succeeds. - let handle = filter_engine.handle; - let max_attempts = 20; - for _ in 0..max_attempts { - match ffi::filter_engine_transaction_begin(handle, 0) { - Ok(()) => { - return Ok(Self { - filter_engine, - committed: false, - }) - } - Err(status) if status == STATUS_FWP_TXN_IN_PROGRESS => { - // Nothing was changed by the failed attempt. Give the transaction that holds - // the session a chance to finish and try again. - if let Err(err) = sleep_ms(10) { - return Err(format!( - "filter-engine: cannot wait for the transaction in progress: {}", - err - )); - } - } - Err(status) => { - return Err(format!( - "filter-engine: failed to begin transaction: {}", - ntstatus_name(status) - )) - } - } - } - - return Err(format!( - "filter-engine: failed to begin transaction: {} after {} attempts", - ntstatus_name(STATUS_FWP_TXN_IN_PROGRESS), - max_attempts - )); - } - - /// Applying all the changes and releases the lock. - pub(super) fn commit(&mut self) -> Result<(), String> { - if let Err(code) = ffi::filter_engine_transaction_commit(self.filter_engine.handle) { - return Err(format!( - "filter-engine: failed to commit transaction: {}", - code - )); - } - self.committed = true; - - Ok(()) - } -} - -impl<'a> Deref for Transaction<'a> { - type Target = FilterEngine; - - fn deref(&self) -> &Self::Target { - self.filter_engine - } -} - -impl<'a> DerefMut for Transaction<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.filter_engine - } -} - -impl<'a> Drop for Transaction<'a> { - /// Releases the lock of transaction was not committed. - fn drop(&mut self) { - if !self.committed { - _ = ffi::filter_engine_transaction_abort(self.filter_engine.handle); - } - } -} +use core::ops::{Deref, DerefMut}; + +use super::{ffi, FilterEngine}; +use crate::consts::STATUS_FWP_TXN_IN_PROGRESS; +use crate::utils::{ntstatus_name, sleep_ms}; +use alloc::{format, string::String}; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_TXN_READ_ONLY; + +/// Why beginning a filter engine transaction failed. +pub(super) enum BeginError { + /// Another transaction is already in progress on the filter engine session. Only one can be + /// open at a time. Nothing was changed, so the caller may simply try again later. + InProgress, + /// Anything else. Not worth repeating. + Failed(String), +} + +impl From for String { + fn from(err: BeginError) -> Self { + match err { + BeginError::InProgress => format!( + "filter-engine: failed to begin transaction: {}", + ntstatus_name(STATUS_FWP_TXN_IN_PROGRESS) + ), + BeginError::Failed(message) => message, + } + } +} + +/// Transaction guard for Filter Engine. Internally useses a lock. DO NOT USE WITH OTHER LOCKS. +pub(super) struct Transaction<'a> { + filter_engine: &'a mut FilterEngine, + committed: bool, +} + +impl<'a> Transaction<'a> { + fn begin(filter_engine: &'a mut FilterEngine, flags: u32) -> Result { + if let Err(status) = ffi::filter_engine_transaction_begin(filter_engine.handle, flags) { + if status == STATUS_FWP_TXN_IN_PROGRESS { + return Err(BeginError::InProgress); + } + return Err(BeginError::Failed(format!( + "filter-engine: failed to begin transaction: {}", + ntstatus_name(status) + ))); + } + + Ok(Self { + filter_engine, + committed: false, + }) + } + + /// Creates a read only guard for filter engine transaction. + #[allow(dead_code)] + pub(super) fn begin_read(filter_engine: &'a mut FilterEngine) -> Result { + return Self::begin(filter_engine, FWPM_TXN_READ_ONLY); + } + + /// Creates a read/write guard for filter engine transaction. + pub(super) fn begin_write(filter_engine: &'a mut FilterEngine) -> Result { + return Self::begin(filter_engine, 0); + } + + /// Same as `begin_write`, but waits and tries again while another transaction is in progress. + /// + /// Sleeps between the attempts, so it is only usable at IRQL <= APC_LEVEL. Callers that must + /// not block, or that want to batch the retries with other work (the driver does that for the + /// filter resets), use `begin_write` and handle `BeginError::InProgress` themselves. + pub(super) fn begin_write_retrying( + filter_engine: &'a mut FilterEngine, + ) -> Result { + // The handle is read up front so the mutable borrow of the filter engine is only taken by + // the attempt that actually succeeds. + let handle = filter_engine.handle; + let max_attempts = 20; + for _ in 0..max_attempts { + match ffi::filter_engine_transaction_begin(handle, 0) { + Ok(()) => { + return Ok(Self { + filter_engine, + committed: false, + }) + } + Err(status) if status == STATUS_FWP_TXN_IN_PROGRESS => { + // Nothing was changed by the failed attempt. Give the transaction that holds + // the session a chance to finish and try again. + if let Err(err) = sleep_ms(10) { + return Err(format!( + "filter-engine: cannot wait for the transaction in progress: {}", + err + )); + } + } + Err(status) => { + return Err(format!( + "filter-engine: failed to begin transaction: {}", + ntstatus_name(status) + )) + } + } + } + + return Err(format!( + "filter-engine: failed to begin transaction: {} after {} attempts", + ntstatus_name(STATUS_FWP_TXN_IN_PROGRESS), + max_attempts + )); + } + + /// Applying all the changes and releases the lock. + pub(super) fn commit(&mut self) -> Result<(), String> { + if let Err(code) = ffi::filter_engine_transaction_commit(self.filter_engine.handle) { + return Err(format!( + "filter-engine: failed to commit transaction: {}", + code + )); + } + self.committed = true; + + Ok(()) + } +} + +impl<'a> Deref for Transaction<'a> { + type Target = FilterEngine; + + fn deref(&self) -> &Self::Target { + self.filter_engine + } +} + +impl<'a> DerefMut for Transaction<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.filter_engine + } +} + +impl<'a> Drop for Transaction<'a> { + /// Releases the lock of transaction was not committed. + fn drop(&mut self) { + if !self.committed { + _ = ffi::filter_engine_transaction_abort(self.filter_engine.handle); + } + } +} diff --git a/wdk/src/interface.rs b/wdk/src/interface.rs index 608047f..818afe0 100644 --- a/wdk/src/interface.rs +++ b/wdk/src/interface.rs @@ -1,101 +1,101 @@ -use crate::{ - alloc::borrow::ToOwned, - driver::Driver, - ffi::{ - pm_GetDeviceObject, pm_InitDriverObject, pm_WdfObjectGetTypedContextWorker, - WdfObjectAttributes, WdfObjectContextTypeInfo, - }, - utils::check_ntstatus, -}; -use alloc::ffi::CString; -use alloc::format; -use alloc::string::String; -use widestring::U16CString; -use windows_sys::{ - Wdk::{ - Foundation::{DEVICE_OBJECT, DRIVER_OBJECT}, - System::SystemServices::DbgPrintEx, - }, - Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, UNICODE_STRING}, -}; - -// Debug -pub fn dbg_print(str: String) { - if let Ok(c_str) = CString::new(str) { - const DPFLTR_IHVDRIVER_ID: u32 = 77; - unsafe { - DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, c_str.as_ptr() as _); - } - } -} - -pub fn init_driver_object( - driver_object: *mut DRIVER_OBJECT, - registry_path: *mut UNICODE_STRING, - driver_name: &str, - object_attributes: *mut WdfObjectAttributes, -) -> Result { - let win_driver_path = format!("\\Device\\{}", driver_name); - let dos_driver_path = format!("\\??\\{}", driver_name); - - let mut wdf_driver_handle = INVALID_HANDLE_VALUE; - let mut wdf_device_handle = INVALID_HANDLE_VALUE; - - let Ok(win_driver) = U16CString::from_str(win_driver_path) else { - return Err("Invalid argument win_driver_path".to_owned()); - }; - let Ok(dos_driver) = U16CString::from_str(dos_driver_path) else { - return Err("Invalid argument dos_driver_path".to_owned()); - }; - - unsafe { - let status = pm_InitDriverObject( - driver_object, - registry_path, - &mut wdf_driver_handle, - &mut wdf_device_handle, - win_driver.as_ptr(), - dos_driver.as_ptr(), - object_attributes, - empty_wdf_driver_unload, - ); - - check_ntstatus(status)?; - - return Ok(Driver::new( - driver_object, - wdf_driver_handle, - wdf_device_handle, - )); - } -} - -pub fn get_device_context_from_wdf_device( - wdf_device: HANDLE, - type_info: &'static WdfObjectContextTypeInfo, -) -> *mut T { - unsafe { - return core::mem::transmute(pm_WdfObjectGetTypedContextWorker(wdf_device, type_info)); - } -} - -pub(crate) fn wdf_device_wdm_get_device_object(wdf_device: HANDLE) -> *mut DEVICE_OBJECT { - unsafe { - return pm_GetDeviceObject(wdf_device); - } -} - -pub fn get_device_context_from_device_object<'a, T>( - device_object: &mut DEVICE_OBJECT, -) -> Result<&'a mut T, ()> { - unsafe { - if let Some(context) = device_object.DeviceExtension.as_mut() { - return Ok(core::mem::transmute(context)); - } - } - - return Err(()); -} - -/// Empty unload event -extern "C" fn empty_wdf_driver_unload(_driver: HANDLE) {} +use crate::{ + alloc::borrow::ToOwned, + driver::Driver, + ffi::{ + pm_GetDeviceObject, pm_InitDriverObject, pm_WdfObjectGetTypedContextWorker, + WdfObjectAttributes, WdfObjectContextTypeInfo, + }, + utils::check_ntstatus, +}; +use alloc::ffi::CString; +use alloc::format; +use alloc::string::String; +use widestring::U16CString; +use windows_sys::{ + Wdk::{ + Foundation::{DEVICE_OBJECT, DRIVER_OBJECT}, + System::SystemServices::DbgPrintEx, + }, + Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, UNICODE_STRING}, +}; + +// Debug +pub fn dbg_print(str: String) { + if let Ok(c_str) = CString::new(str) { + const DPFLTR_IHVDRIVER_ID: u32 = 77; + unsafe { + DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, c_str.as_ptr() as _); + } + } +} + +pub fn init_driver_object( + driver_object: *mut DRIVER_OBJECT, + registry_path: *mut UNICODE_STRING, + driver_name: &str, + object_attributes: *mut WdfObjectAttributes, +) -> Result { + let win_driver_path = format!("\\Device\\{}", driver_name); + let dos_driver_path = format!("\\??\\{}", driver_name); + + let mut wdf_driver_handle = INVALID_HANDLE_VALUE; + let mut wdf_device_handle = INVALID_HANDLE_VALUE; + + let Ok(win_driver) = U16CString::from_str(win_driver_path) else { + return Err("Invalid argument win_driver_path".to_owned()); + }; + let Ok(dos_driver) = U16CString::from_str(dos_driver_path) else { + return Err("Invalid argument dos_driver_path".to_owned()); + }; + + unsafe { + let status = pm_InitDriverObject( + driver_object, + registry_path, + &mut wdf_driver_handle, + &mut wdf_device_handle, + win_driver.as_ptr(), + dos_driver.as_ptr(), + object_attributes, + empty_wdf_driver_unload, + ); + + check_ntstatus(status)?; + + return Ok(Driver::new( + driver_object, + wdf_driver_handle, + wdf_device_handle, + )); + } +} + +pub fn get_device_context_from_wdf_device( + wdf_device: HANDLE, + type_info: &'static WdfObjectContextTypeInfo, +) -> *mut T { + unsafe { + return core::mem::transmute(pm_WdfObjectGetTypedContextWorker(wdf_device, type_info)); + } +} + +pub(crate) fn wdf_device_wdm_get_device_object(wdf_device: HANDLE) -> *mut DEVICE_OBJECT { + unsafe { + return pm_GetDeviceObject(wdf_device); + } +} + +pub fn get_device_context_from_device_object<'a, T>( + device_object: &mut DEVICE_OBJECT, +) -> Result<&'a mut T, ()> { + unsafe { + if let Some(context) = device_object.DeviceExtension.as_mut() { + return Ok(core::mem::transmute(context)); + } + } + + return Err(()); +} + +/// Empty unload event +extern "C" fn empty_wdf_driver_unload(_driver: HANDLE) {} diff --git a/wdk/src/ioqueue.rs b/wdk/src/ioqueue.rs index c483619..052f41e 100644 --- a/wdk/src/ioqueue.rs +++ b/wdk/src/ioqueue.rs @@ -1,216 +1,216 @@ -use core::{ - cell::UnsafeCell, - ffi::c_void, - fmt::Display, - marker::PhantomData, - mem::MaybeUninit, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use crate::dbg; -use alloc::boxed::Box; -use ntstatus::ntstatus::NtStatus; -use windows_sys::{Wdk::Foundation::KQUEUE, Win32::System::Kernel::LIST_ENTRY}; - -#[derive(Debug)] -pub enum Status { - Uninitialized, - Timeout, - UserAPC, - Abandoned, -} - -impl Display for Status { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Status::Uninitialized => write!(f, "Uninitialized"), - Status::Timeout => write!(f, "Timeout"), - Status::UserAPC => write!(f, "UserAPC"), - Status::Abandoned => write!(f, "Abandoned"), - } - } -} - -#[repr(i8)] -pub enum KprocessorMode { - KernelMode = 0, - UserMode = 1, -} - -#[link(name = "NtosKrnl", kind = "static")] -extern "C" { - /* - KeInitializeQueue - [out] Queue - Pointer to a KQUEUE structure for which the caller must provide resident storage in nonpaged pool. This structure is defined as follows: - - [in] Count - The maximum number of threads for which the waits on the queue object can be satisfied concurrently. If this parameter is not supplied, the number of processors in the machine is used. - */ - fn KeInitializeQueue(queue: *mut KQUEUE, count: u64); - /* - KeInsertQueue returns the previous signal state of the given Queue. If it was set to zero (that is, not signaled) before KeInsertQueue was called, KeInsertQueue returns zero, meaning that no entries were queued. If it was nonzero (signaled), KeInsertQueue returns the number of entries that were queued before KeInsertQueue was called. - */ - fn KeInsertQueue(queue: *mut KQUEUE, list_entry: *mut c_void) -> i32; - /* - KeRemoveQueue returns one of the following: - A pointer to a dequeued entry from the given queue object, if one is available - STATUS_TIMEOUT, if the given Timeout interval expired before an entry became available - STATUS_USER_APC, if a user-mode APC was delivered in the context of the calling thread - STATUS_ABANDONED, if the queue has been run down - */ - fn KeRemoveQueue( - queue: *mut KQUEUE, - waitmode: KprocessorMode, - timeout: *const i64, - ) -> *mut LIST_ENTRY; - - // If the queue is empty, KeRundownQueue returns NULL; otherwise, it returns the address of the first entry in the queue. - fn KeRundownQueue(queue: *mut KQUEUE) -> *mut LIST_ENTRY; -} - -#[repr(C)] -struct Entry { - list: LIST_ENTRY, // Internal use - entry: T, -} - -pub struct IOQueue { - // The address of the value should not change. - kernel_queue: Pin>>, - initialized: AtomicBool, - _type: PhantomData, // 0 size variable. Required for the generic to work properly. Compiler limitation. -} - -unsafe impl Sync for IOQueue {} - -impl IOQueue { - /// Make sure `rundown` is called on exit, if `drop()` is not called for queue. - pub fn new() -> Self { - unsafe { - let kernel_queue = Box::pin(UnsafeCell::new(MaybeUninit::zeroed().assume_init())); - KeInitializeQueue(kernel_queue.get(), 1); - - Self { - kernel_queue, - initialized: AtomicBool::new(true), - _type: PhantomData, - } - } - } - - /// Pushes new entry of any type. - pub fn push(&self, entry: T) -> Result<(), Status> { - let kqueue = self.kernel_queue.get(); - // Allocate entry. - let list_entry = Box::new(Entry { - list: LIST_ENTRY { - Flink: core::ptr::null_mut(), - Blink: core::ptr::null_mut(), - }, - entry, - }); - let raw_ptr = Box::into_raw(list_entry); - - // Check if initialized. - let result = if self.initialized.load(Ordering::SeqCst) { - unsafe { KeInsertQueue(kqueue, raw_ptr as *mut c_void) } - } else { - -1 - }; - // There is no documentation that rundown queue will return error. This is here just for good measures. - // It is unlikely to happen and not critical. - if result >= 0 { - return Ok(()); - } - - _ = unsafe { Box::from_raw(raw_ptr) }; - return Err(Status::Uninitialized); - } - - /// Returns an Element or a status. - fn pop_internal(&self, timeout: *const i64) -> Result { - unsafe { - let kqueue = self.kernel_queue.get(); - // Check if initialized. - if self.initialized.load(Ordering::SeqCst) { - // Pop and check the return value. - let list_entry = - KeRemoveQueue(kqueue, KprocessorMode::KernelMode, timeout) as *mut Entry; - let error_code = NtStatus::try_from(list_entry as u32); - match error_code { - Ok(NtStatus::STATUS_TIMEOUT) => return Err(Status::Timeout), - Ok(NtStatus::STATUS_USER_APC) => return Err(Status::UserAPC), - Ok(NtStatus::STATUS_ABANDONED) => return Err(Status::Abandoned), - _ => { - // The return value is a pointer. - let list_entry = Box::from_raw(list_entry); - let entry = list_entry.entry; - return Ok(entry); - } - } - } - } - - Err(Status::Uninitialized) - } - - /// Returns element or a status. Waits until element is pushed or the queue is interrupted. - pub fn wait_and_pop(&self) -> Result { - // No timeout. - self.pop_internal(core::ptr::null()) - } - - /// Returns element or a status. Does not wait. - pub fn pop(&self) -> Result { - let timeout: i64 = 0; - self.pop_internal(&timeout) - } - - /// Returns element or a status. Waits the specified timeout. - pub fn pop_timeout(&self, timeout: i64) -> Result { - let timeout_ptr: i64 = timeout * -10000; - self.pop_internal(&timeout_ptr) - } - - /// Removes all elements and frees all the memory. The object can't be used after this function is called. - pub fn rundown(&self) { - unsafe { - let kqueue = self.kernel_queue.get(); - if kqueue.is_null() { - return; - } - - // Check if initialized. - if self.initialized.swap(false, Ordering::SeqCst) { - // Remove and free all elements from the queue. - let list_entries: *mut LIST_ENTRY = KeRundownQueue(kqueue); - if !list_entries.is_null() { - let mut entry = list_entries; - while !core::ptr::eq((*entry).Flink, list_entries) { - let next = (*entry).Flink; - dbg!("discarding entry"); - let _ = Box::from_raw(entry as *mut Entry); - entry = next; - } - dbg!("discarding last entry"); - let _ = Box::from_raw(entry as *mut Entry); - } - } - } - } -} - -impl Drop for IOQueue { - fn drop(&mut self) { - // Reinitialize queue. - self.rundown(); - unsafe { - let ptr = self.kernel_queue.get(); - if !ptr.is_null() { - *ptr = MaybeUninit::zeroed().assume_init(); - } - } - } -} +use core::{ + cell::UnsafeCell, + ffi::c_void, + fmt::Display, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, +}; + +use crate::dbg; +use alloc::boxed::Box; +use ntstatus::ntstatus::NtStatus; +use windows_sys::{Wdk::Foundation::KQUEUE, Win32::System::Kernel::LIST_ENTRY}; + +#[derive(Debug)] +pub enum Status { + Uninitialized, + Timeout, + UserAPC, + Abandoned, +} + +impl Display for Status { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Status::Uninitialized => write!(f, "Uninitialized"), + Status::Timeout => write!(f, "Timeout"), + Status::UserAPC => write!(f, "UserAPC"), + Status::Abandoned => write!(f, "Abandoned"), + } + } +} + +#[repr(i8)] +pub enum KprocessorMode { + KernelMode = 0, + UserMode = 1, +} + +#[link(name = "NtosKrnl", kind = "static")] +extern "C" { + /* + KeInitializeQueue + [out] Queue + Pointer to a KQUEUE structure for which the caller must provide resident storage in nonpaged pool. This structure is defined as follows: + + [in] Count + The maximum number of threads for which the waits on the queue object can be satisfied concurrently. If this parameter is not supplied, the number of processors in the machine is used. + */ + fn KeInitializeQueue(queue: *mut KQUEUE, count: u64); + /* + KeInsertQueue returns the previous signal state of the given Queue. If it was set to zero (that is, not signaled) before KeInsertQueue was called, KeInsertQueue returns zero, meaning that no entries were queued. If it was nonzero (signaled), KeInsertQueue returns the number of entries that were queued before KeInsertQueue was called. + */ + fn KeInsertQueue(queue: *mut KQUEUE, list_entry: *mut c_void) -> i32; + /* + KeRemoveQueue returns one of the following: + A pointer to a dequeued entry from the given queue object, if one is available + STATUS_TIMEOUT, if the given Timeout interval expired before an entry became available + STATUS_USER_APC, if a user-mode APC was delivered in the context of the calling thread + STATUS_ABANDONED, if the queue has been run down + */ + fn KeRemoveQueue( + queue: *mut KQUEUE, + waitmode: KprocessorMode, + timeout: *const i64, + ) -> *mut LIST_ENTRY; + + // If the queue is empty, KeRundownQueue returns NULL; otherwise, it returns the address of the first entry in the queue. + fn KeRundownQueue(queue: *mut KQUEUE) -> *mut LIST_ENTRY; +} + +#[repr(C)] +struct Entry { + list: LIST_ENTRY, // Internal use + entry: T, +} + +pub struct IOQueue { + // The address of the value should not change. + kernel_queue: Pin>>, + initialized: AtomicBool, + _type: PhantomData, // 0 size variable. Required for the generic to work properly. Compiler limitation. +} + +unsafe impl Sync for IOQueue {} + +impl IOQueue { + /// Make sure `rundown` is called on exit, if `drop()` is not called for queue. + pub fn new() -> Self { + unsafe { + let kernel_queue = Box::pin(UnsafeCell::new(MaybeUninit::zeroed().assume_init())); + KeInitializeQueue(kernel_queue.get(), 1); + + Self { + kernel_queue, + initialized: AtomicBool::new(true), + _type: PhantomData, + } + } + } + + /// Pushes new entry of any type. + pub fn push(&self, entry: T) -> Result<(), Status> { + let kqueue = self.kernel_queue.get(); + // Allocate entry. + let list_entry = Box::new(Entry { + list: LIST_ENTRY { + Flink: core::ptr::null_mut(), + Blink: core::ptr::null_mut(), + }, + entry, + }); + let raw_ptr = Box::into_raw(list_entry); + + // Check if initialized. + let result = if self.initialized.load(Ordering::SeqCst) { + unsafe { KeInsertQueue(kqueue, raw_ptr as *mut c_void) } + } else { + -1 + }; + // There is no documentation that rundown queue will return error. This is here just for good measures. + // It is unlikely to happen and not critical. + if result >= 0 { + return Ok(()); + } + + _ = unsafe { Box::from_raw(raw_ptr) }; + return Err(Status::Uninitialized); + } + + /// Returns an Element or a status. + fn pop_internal(&self, timeout: *const i64) -> Result { + unsafe { + let kqueue = self.kernel_queue.get(); + // Check if initialized. + if self.initialized.load(Ordering::SeqCst) { + // Pop and check the return value. + let list_entry = + KeRemoveQueue(kqueue, KprocessorMode::KernelMode, timeout) as *mut Entry; + let error_code = NtStatus::try_from(list_entry as u32); + match error_code { + Ok(NtStatus::STATUS_TIMEOUT) => return Err(Status::Timeout), + Ok(NtStatus::STATUS_USER_APC) => return Err(Status::UserAPC), + Ok(NtStatus::STATUS_ABANDONED) => return Err(Status::Abandoned), + _ => { + // The return value is a pointer. + let list_entry = Box::from_raw(list_entry); + let entry = list_entry.entry; + return Ok(entry); + } + } + } + } + + Err(Status::Uninitialized) + } + + /// Returns element or a status. Waits until element is pushed or the queue is interrupted. + pub fn wait_and_pop(&self) -> Result { + // No timeout. + self.pop_internal(core::ptr::null()) + } + + /// Returns element or a status. Does not wait. + pub fn pop(&self) -> Result { + let timeout: i64 = 0; + self.pop_internal(&timeout) + } + + /// Returns element or a status. Waits the specified timeout. + pub fn pop_timeout(&self, timeout: i64) -> Result { + let timeout_ptr: i64 = timeout * -10000; + self.pop_internal(&timeout_ptr) + } + + /// Removes all elements and frees all the memory. The object can't be used after this function is called. + pub fn rundown(&self) { + unsafe { + let kqueue = self.kernel_queue.get(); + if kqueue.is_null() { + return; + } + + // Check if initialized. + if self.initialized.swap(false, Ordering::SeqCst) { + // Remove and free all elements from the queue. + let list_entries: *mut LIST_ENTRY = KeRundownQueue(kqueue); + if !list_entries.is_null() { + let mut entry = list_entries; + while !core::ptr::eq((*entry).Flink, list_entries) { + let next = (*entry).Flink; + dbg!("discarding entry"); + let _ = Box::from_raw(entry as *mut Entry); + entry = next; + } + dbg!("discarding last entry"); + let _ = Box::from_raw(entry as *mut Entry); + } + } + } + } +} + +impl Drop for IOQueue { + fn drop(&mut self) { + // Reinitialize queue. + self.rundown(); + unsafe { + let ptr = self.kernel_queue.get(); + if !ptr.is_null() { + *ptr = MaybeUninit::zeroed().assume_init(); + } + } + } +} diff --git a/wdk/src/irp_helpers.rs b/wdk/src/irp_helpers.rs index 9eb7e31..2e3d4c5 100644 --- a/wdk/src/irp_helpers.rs +++ b/wdk/src/irp_helpers.rs @@ -1,198 +1,198 @@ -use windows_sys::{ - Wdk::{ - Foundation::{IO_STACK_LOCATION_0_4, IRP}, - Storage::FileSystem::IO_NO_INCREMENT, - System::SystemServices::IofCompleteRequest, - }, - Win32::Foundation::{ - NTSTATUS, STATUS_END_OF_FILE, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS, STATUS_TIMEOUT, - }, -}; - -pub struct ReadRequest<'a> { - irp: &'a mut IRP, - buffer: &'a mut [u8], - fill_index: usize, -} - -impl ReadRequest<'_> { - pub fn new(irp: &'_ mut IRP) -> ReadRequest<'_> { - unsafe { - let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; - let device_io = (*irp_sp).Parameters.Read; - - let system_buffer = irp.AssociatedIrp.SystemBuffer; - let buffer = core::slice::from_raw_parts_mut( - system_buffer as *mut u8, - device_io.Length as usize, - ); - ReadRequest { - irp, - buffer, - fill_index: 0, - } - } - } - - pub fn free_space(&self) -> usize { - self.buffer.len() - self.fill_index - } - - pub fn complete(&mut self) { - self.irp.IoStatus.Information = self.fill_index; - self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; - } - - pub fn end_of_file(&mut self) { - self.irp.IoStatus.Information = self.fill_index; - self.irp.IoStatus.Anonymous.Status = STATUS_END_OF_FILE; - } - - pub fn timeout(&mut self) { - self.irp.IoStatus.Anonymous.Status = STATUS_TIMEOUT; - } - - pub fn get_status(&self) -> NTSTATUS { - unsafe { self.irp.IoStatus.Anonymous.Status } - } - - pub fn write(&mut self, bytes: &[u8]) -> usize { - let mut bytes_to_write: usize = bytes.len(); - - // Check if we have enough space - if bytes_to_write > self.free_space() { - bytes_to_write = self.free_space(); - } - - for i in 0..bytes_to_write { - self.buffer[self.fill_index + i] = bytes[i]; - } - self.fill_index = self.fill_index + bytes_to_write; - - bytes_to_write - } -} - -pub struct WriteRequest<'a> { - irp: &'a mut IRP, - buffer: &'a mut [u8], -} - -impl WriteRequest<'_> { - pub fn new(irp: &'_ mut IRP) -> WriteRequest<'_> { - unsafe { - let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; - let device_io = (*irp_sp).Parameters.Write; - - let system_buffer = irp.AssociatedIrp.SystemBuffer; - let buffer = core::slice::from_raw_parts_mut( - system_buffer as *mut u8, - device_io.Length as usize, - ); - WriteRequest { irp, buffer } - } - } - - pub fn get_buffer(&self) -> &[u8] { - &self.buffer - } - - pub fn mark_all_as_read(&mut self) { - self.irp.IoStatus.Information = self.buffer.len(); - } - - pub fn complete(&mut self) { - self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; - } - - pub fn get_status(&self) -> NTSTATUS { - unsafe { self.irp.IoStatus.Anonymous.Status } - } -} - -pub struct DeviceControlRequest<'a> { - irp: &'a mut IRP, - buffer: &'a mut [u8], - fill_index: usize, - control_code: u32, -} - -// Windows-rs version of the struct is incorrect (18.01.2024). -// TODO: Use the official version when fixed. -// For future reference: https://github.com/microsoft/windows-rs/issues/2805 -#[repr(C)] -struct DeviceIOControlParams { - output_buffer_length: u32, - _padding1: u32, - input_buffer_length: u32, - _padding2: u32, - io_control_code: u32, - _padding3: u32, -} - -impl DeviceControlRequest<'_> { - pub fn new(irp: &'_ mut IRP) -> DeviceControlRequest<'_> { - unsafe { - let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; - // Use the struct directly when replaced with proper version. - let device_io: DeviceIOControlParams = - core::mem::transmute::( - (*irp_sp).Parameters.DeviceIoControl, - ); - - let system_buffer = irp.AssociatedIrp.SystemBuffer; - let buffer = core::slice::from_raw_parts_mut( - system_buffer as *mut u8, - device_io.output_buffer_length as usize, - ); - DeviceControlRequest { - irp, - buffer, - fill_index: 0, - control_code: device_io.io_control_code, - } - } - } - - pub fn get_buffer(&self) -> &[u8] { - &self.buffer - } - pub fn write(&mut self, bytes: &[u8]) -> usize { - let mut bytes_to_write: usize = bytes.len(); - - // Check if we have enough space - if bytes_to_write > self.free_space() { - bytes_to_write = self.free_space(); - } - - for i in 0..bytes_to_write { - self.buffer[self.fill_index + i] = bytes[i]; - } - self.fill_index = self.fill_index + bytes_to_write; - - bytes_to_write - } - - pub fn complete(&mut self) { - self.irp.IoStatus.Information = self.buffer.len(); - self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; - unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; - } - - pub fn not_implemented(&mut self) { - self.irp.IoStatus.Anonymous.Status = STATUS_NOT_IMPLEMENTED; - unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; - } - - pub fn get_status(&self) -> NTSTATUS { - unsafe { self.irp.IoStatus.Anonymous.Status } - } - - pub fn get_control_code(&self) -> u32 { - self.control_code - } - - pub fn free_space(&self) -> usize { - self.buffer.len() - self.fill_index - } -} +use windows_sys::{ + Wdk::{ + Foundation::{IO_STACK_LOCATION_0_4, IRP}, + Storage::FileSystem::IO_NO_INCREMENT, + System::SystemServices::IofCompleteRequest, + }, + Win32::Foundation::{ + NTSTATUS, STATUS_END_OF_FILE, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS, STATUS_TIMEOUT, + }, +}; + +pub struct ReadRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], + fill_index: usize, +} + +impl ReadRequest<'_> { + pub fn new(irp: &'_ mut IRP) -> ReadRequest<'_> { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + let device_io = (*irp_sp).Parameters.Read; + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.Length as usize, + ); + ReadRequest { + irp, + buffer, + fill_index: 0, + } + } + } + + pub fn free_space(&self) -> usize { + self.buffer.len() - self.fill_index + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Information = self.fill_index; + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + } + + pub fn end_of_file(&mut self) { + self.irp.IoStatus.Information = self.fill_index; + self.irp.IoStatus.Anonymous.Status = STATUS_END_OF_FILE; + } + + pub fn timeout(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_TIMEOUT; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } + + pub fn write(&mut self, bytes: &[u8]) -> usize { + let mut bytes_to_write: usize = bytes.len(); + + // Check if we have enough space + if bytes_to_write > self.free_space() { + bytes_to_write = self.free_space(); + } + + for i in 0..bytes_to_write { + self.buffer[self.fill_index + i] = bytes[i]; + } + self.fill_index = self.fill_index + bytes_to_write; + + bytes_to_write + } +} + +pub struct WriteRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], +} + +impl WriteRequest<'_> { + pub fn new(irp: &'_ mut IRP) -> WriteRequest<'_> { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + let device_io = (*irp_sp).Parameters.Write; + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.Length as usize, + ); + WriteRequest { irp, buffer } + } + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + + pub fn mark_all_as_read(&mut self) { + self.irp.IoStatus.Information = self.buffer.len(); + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } +} + +pub struct DeviceControlRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], + fill_index: usize, + control_code: u32, +} + +// Windows-rs version of the struct is incorrect (18.01.2024). +// TODO: Use the official version when fixed. +// For future reference: https://github.com/microsoft/windows-rs/issues/2805 +#[repr(C)] +struct DeviceIOControlParams { + output_buffer_length: u32, + _padding1: u32, + input_buffer_length: u32, + _padding2: u32, + io_control_code: u32, + _padding3: u32, +} + +impl DeviceControlRequest<'_> { + pub fn new(irp: &'_ mut IRP) -> DeviceControlRequest<'_> { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + // Use the struct directly when replaced with proper version. + let device_io: DeviceIOControlParams = + core::mem::transmute::( + (*irp_sp).Parameters.DeviceIoControl, + ); + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.output_buffer_length as usize, + ); + DeviceControlRequest { + irp, + buffer, + fill_index: 0, + control_code: device_io.io_control_code, + } + } + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + pub fn write(&mut self, bytes: &[u8]) -> usize { + let mut bytes_to_write: usize = bytes.len(); + + // Check if we have enough space + if bytes_to_write > self.free_space() { + bytes_to_write = self.free_space(); + } + + for i in 0..bytes_to_write { + self.buffer[self.fill_index + i] = bytes[i]; + } + self.fill_index = self.fill_index + bytes_to_write; + + bytes_to_write + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Information = self.buffer.len(); + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; + } + + pub fn not_implemented(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_NOT_IMPLEMENTED; + unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } + + pub fn get_control_code(&self) -> u32 { + self.control_code + } + + pub fn free_space(&self) -> usize { + self.buffer.len() - self.fill_index + } +} diff --git a/wdk/src/lib.rs b/wdk/src/lib.rs index 1abdbe4..ba1daf7 100644 --- a/wdk/src/lib.rs +++ b/wdk/src/lib.rs @@ -1,32 +1,32 @@ -#![cfg_attr(not(test), no_std)] -#![allow(clippy::needless_return)] - -extern crate alloc; - -pub mod allocator; -pub mod consts; -pub mod debug; -pub mod driver; -pub mod error; -pub mod filter_engine; -pub mod interface; -pub mod ioqueue; -pub mod irp_helpers; -pub mod rw_spin_lock; -pub mod spin_lock; -pub mod utils; - -#[allow(dead_code)] -pub mod ffi; - -// Needed by the linker for legacy reasons. Not important for rust. -#[cfg(not(test))] -#[export_name = "_fltused"] -static _FLTUSED: i32 = 0; - -// Needed by the compiler but not used. -#[cfg(not(test))] -#[no_mangle] -pub extern "system" fn __CxxFrameHandler3(_: *mut u8, _: *mut u8, _: *mut u8, _: *mut u8) -> i32 { - 0 -} +#![cfg_attr(not(test), no_std)] +#![allow(clippy::needless_return)] + +extern crate alloc; + +pub mod allocator; +pub mod consts; +pub mod debug; +pub mod driver; +pub mod error; +pub mod filter_engine; +pub mod interface; +pub mod ioqueue; +pub mod irp_helpers; +pub mod rw_spin_lock; +pub mod spin_lock; +pub mod utils; + +#[allow(dead_code)] +pub mod ffi; + +// Needed by the linker for legacy reasons. Not important for rust. +#[cfg(not(test))] +#[export_name = "_fltused"] +static _FLTUSED: i32 = 0; + +// Needed by the compiler but not used. +#[cfg(not(test))] +#[no_mangle] +pub extern "system" fn __CxxFrameHandler3(_: *mut u8, _: *mut u8, _: *mut u8, _: *mut u8) -> i32 { + 0 +} diff --git a/wdk/src/rw_spin_lock.rs b/wdk/src/rw_spin_lock.rs index 0b46f6b..44dd483 100644 --- a/wdk/src/rw_spin_lock.rs +++ b/wdk/src/rw_spin_lock.rs @@ -1,122 +1,122 @@ -use core::{ - cell::UnsafeCell, - ops::{Deref, DerefMut}, -}; - -use windows_sys::Wdk::System::SystemServices::{ - ExAcquireSpinLockExclusive, ExAcquireSpinLockShared, ExReleaseSpinLockExclusive, - ExReleaseSpinLockShared, -}; - -/// A reader-writer spin lock implementation. -/// -/// This lock allows multiple readers to access the data simultaneously, -/// but only one writer can access the data at a time. It uses a spin loop -/// to wait for the lock to become available. -pub struct RwSpinLock { - data: UnsafeCell, -} - -impl RwSpinLock { - /// Creates a new `RwSpinLock` with the default initial value. - pub const fn default() -> Self { - Self { - data: UnsafeCell::new(0), - } - } - - pub fn read_lock(&self) -> u8 { - return unsafe { ExAcquireSpinLockShared(self.data.get()) }; - } - - pub fn write_lock(&self) -> u8 { - return unsafe { ExAcquireSpinLockExclusive(self.data.get()) }; - } - - pub fn read_unlock(&self, old_irq: u8) { - unsafe { ExReleaseSpinLockShared(self.data.get(), old_irq) }; - } - pub fn write_unlock(&self, old_irq: u8) { - unsafe { ExReleaseSpinLockExclusive(self.data.get(), old_irq) }; - } -} - -// High level interface for RWSpinLock -pub struct Mutex { - lock: RwSpinLock, - data: UnsafeCell, -} - -unsafe impl Sync for Mutex {} -unsafe impl Send for Mutex {} - -impl Mutex { - pub const fn new(data: T) -> Self { - Self { - lock: RwSpinLock::default(), - data: UnsafeCell::new(data), - } - } - - // Locks the resource for reading - pub fn read_lock(&self) -> MutexReadGuard<'_, T> { - let old_irq = self.lock.read_lock(); - MutexReadGuard { - lock: self, - old_irq, - } - } - - // Locks the resource for writing - pub fn write_lock(&self) -> MutexWriteGuard<'_, T> { - let old_irq = self.lock.write_lock(); - MutexWriteGuard { - lock: self, - old_irq, - } - } -} - -// MutexWriteGuard is a mutex write guard lock -pub struct MutexWriteGuard<'a, T> { - lock: &'a Mutex, - old_irq: u8, -} - -impl<'a, T> Deref for MutexWriteGuard<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.data.get() } - } -} - -impl<'a, T> Drop for MutexWriteGuard<'a, T> { - fn drop(&mut self) { - self.lock.lock.write_unlock(self.old_irq); - } -} - -impl<'a, T> DerefMut for MutexWriteGuard<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.lock.data.get() } - } -} - -// MutexReadGuard is a mutex read guard lock -pub struct MutexReadGuard<'a, T> { - lock: &'a Mutex, - old_irq: u8, -} - -impl<'a, T> Deref for MutexReadGuard<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.data.get() } - } -} - -impl<'a, T> Drop for MutexReadGuard<'a, T> { - fn drop(&mut self) { - self.lock.lock.read_unlock(self.old_irq); - } -} +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, +}; + +use windows_sys::Wdk::System::SystemServices::{ + ExAcquireSpinLockExclusive, ExAcquireSpinLockShared, ExReleaseSpinLockExclusive, + ExReleaseSpinLockShared, +}; + +/// A reader-writer spin lock implementation. +/// +/// This lock allows multiple readers to access the data simultaneously, +/// but only one writer can access the data at a time. It uses a spin loop +/// to wait for the lock to become available. +pub struct RwSpinLock { + data: UnsafeCell, +} + +impl RwSpinLock { + /// Creates a new `RwSpinLock` with the default initial value. + pub const fn default() -> Self { + Self { + data: UnsafeCell::new(0), + } + } + + pub fn read_lock(&self) -> u8 { + return unsafe { ExAcquireSpinLockShared(self.data.get()) }; + } + + pub fn write_lock(&self) -> u8 { + return unsafe { ExAcquireSpinLockExclusive(self.data.get()) }; + } + + pub fn read_unlock(&self, old_irq: u8) { + unsafe { ExReleaseSpinLockShared(self.data.get(), old_irq) }; + } + pub fn write_unlock(&self, old_irq: u8) { + unsafe { ExReleaseSpinLockExclusive(self.data.get(), old_irq) }; + } +} + +// High level interface for RWSpinLock +pub struct Mutex { + lock: RwSpinLock, + data: UnsafeCell, +} + +unsafe impl Sync for Mutex {} +unsafe impl Send for Mutex {} + +impl Mutex { + pub const fn new(data: T) -> Self { + Self { + lock: RwSpinLock::default(), + data: UnsafeCell::new(data), + } + } + + // Locks the resource for reading + pub fn read_lock(&self) -> MutexReadGuard<'_, T> { + let old_irq = self.lock.read_lock(); + MutexReadGuard { + lock: self, + old_irq, + } + } + + // Locks the resource for writing + pub fn write_lock(&self) -> MutexWriteGuard<'_, T> { + let old_irq = self.lock.write_lock(); + MutexWriteGuard { + lock: self, + old_irq, + } + } +} + +// MutexWriteGuard is a mutex write guard lock +pub struct MutexWriteGuard<'a, T> { + lock: &'a Mutex, + old_irq: u8, +} + +impl<'a, T> Deref for MutexWriteGuard<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { &*self.lock.data.get() } + } +} + +impl<'a, T> Drop for MutexWriteGuard<'a, T> { + fn drop(&mut self) { + self.lock.lock.write_unlock(self.old_irq); + } +} + +impl<'a, T> DerefMut for MutexWriteGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.lock.data.get() } + } +} + +// MutexReadGuard is a mutex read guard lock +pub struct MutexReadGuard<'a, T> { + lock: &'a Mutex, + old_irq: u8, +} + +impl<'a, T> Deref for MutexReadGuard<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { &*self.lock.data.get() } + } +} + +impl<'a, T> Drop for MutexReadGuard<'a, T> { + fn drop(&mut self) { + self.lock.lock.read_unlock(self.old_irq); + } +} diff --git a/wdk/src/spin_lock.rs b/wdk/src/spin_lock.rs index b3f07ed..fd6802f 100644 --- a/wdk/src/spin_lock.rs +++ b/wdk/src/spin_lock.rs @@ -1,53 +1,53 @@ -use core::{ffi::c_void, mem::MaybeUninit, ptr}; - -use windows_sys::Wdk::System::SystemServices::{ - KeAcquireInStackQueuedSpinLock, KeInitializeSpinLock, KeReleaseInStackQueuedSpinLock, - KLOCK_QUEUE_HANDLE, -}; - -// Copy of KSPIN_LOCK_QUEUE WDK C struct -#[repr(C)] -#[allow(dead_code)] -struct KSpinLockQueue { - next: *mut c_void, // struct _KSPIN_LOCK_QUEUE * volatile Next; - lock: *mut c_void, // PKSPIN_LOCK volatile Lock; -} - -// Copy of KLOCK_QUEUE_HANDLE WDK C struct -pub struct KLockQueueHandle { - lock: KLOCK_QUEUE_HANDLE, -} - -// Copy of KSpinLock WDK C struct -#[repr(C)] -pub struct KSpinLock { - ptr: *mut usize, -} - -impl KSpinLock { - pub fn create() -> Self { - unsafe { - let p: KSpinLock = KSpinLock { - ptr: ptr::null_mut(), - }; - KeInitializeSpinLock(p.ptr); - return p; - } - } - - pub fn lock(&mut self) -> KLockQueueHandle { - unsafe { - let mut handle = MaybeUninit::zeroed().assume_init(); - KeAcquireInStackQueuedSpinLock(self.ptr, &mut handle); - KLockQueueHandle { lock: handle } - } - } -} - -impl Drop for KLockQueueHandle { - fn drop(&mut self) { - unsafe { - KeReleaseInStackQueuedSpinLock(&mut self.lock); - } - } -} +use core::{ffi::c_void, mem::MaybeUninit, ptr}; + +use windows_sys::Wdk::System::SystemServices::{ + KeAcquireInStackQueuedSpinLock, KeInitializeSpinLock, KeReleaseInStackQueuedSpinLock, + KLOCK_QUEUE_HANDLE, +}; + +// Copy of KSPIN_LOCK_QUEUE WDK C struct +#[repr(C)] +#[allow(dead_code)] +struct KSpinLockQueue { + next: *mut c_void, // struct _KSPIN_LOCK_QUEUE * volatile Next; + lock: *mut c_void, // PKSPIN_LOCK volatile Lock; +} + +// Copy of KLOCK_QUEUE_HANDLE WDK C struct +pub struct KLockQueueHandle { + lock: KLOCK_QUEUE_HANDLE, +} + +// Copy of KSpinLock WDK C struct +#[repr(C)] +pub struct KSpinLock { + ptr: *mut usize, +} + +impl KSpinLock { + pub fn create() -> Self { + unsafe { + let p: KSpinLock = KSpinLock { + ptr: ptr::null_mut(), + }; + KeInitializeSpinLock(p.ptr); + return p; + } + } + + pub fn lock(&mut self) -> KLockQueueHandle { + unsafe { + let mut handle = MaybeUninit::zeroed().assume_init(); + KeAcquireInStackQueuedSpinLock(self.ptr, &mut handle); + KLockQueueHandle { lock: handle } + } + } +} + +impl Drop for KLockQueueHandle { + fn drop(&mut self) { + unsafe { + KeReleaseInStackQueuedSpinLock(&mut self.lock); + } + } +} diff --git a/wdk/src/utils.rs b/wdk/src/utils.rs index 444462c..2f33a8a 100644 --- a/wdk/src/utils.rs +++ b/wdk/src/utils.rs @@ -1,59 +1,59 @@ -use alloc::format; -use alloc::string::{String, ToString}; -use ntstatus::ntstatus::NtStatus; -use windows_sys::Win32::Foundation::STATUS_SUCCESS; - -use crate::consts::{APC_LEVEL, KERNEL_MODE}; -use crate::ffi; - -/// Kernel wait intervals are counted in 100-nanosecond units. -const HUNDRED_NANOS_PER_MILLISECOND: i64 = 10_000; - -/// Returns the symbolic name of an NTSTATUS value, or a placeholder if it is not a known code. -pub fn ntstatus_name(status: i32) -> String { - let Some(name) = NtStatus::from_u32(status as u32) else { - return "UNKNOWN_ERROR_CODE".to_string(); - }; - - return name.to_string(); -} - -pub fn check_ntstatus(status: i32) -> Result<(), String> { - if status == STATUS_SUCCESS { - return Ok(()); - } - - return Err(ntstatus_name(status)); -} - -pub fn get_system_timestamp_ms() -> u64 { - // 100 nano seconds units -> device by 10 -> micro seconds -> divide by 1000 -> milliseconds - unsafe { ffi::pm_QuerySystemTime() / 10_000 } -} - -/// Blocks the calling thread for at least `milliseconds`, giving the rest of the system a chance -/// to make progress. The wait is non-alertable, so an APC does not cut it short. -/// -/// Only usable at IRQL <= APC_LEVEL. Waiting above that would bugcheck, so the level is checked -/// and reported as an error instead. -pub fn sleep_ms(milliseconds: u32) -> Result<(), String> { - let irql = unsafe { ffi::KeGetCurrentIrql() }; - if irql > APC_LEVEL { - return Err(format!("cannot wait at IRQL {}", irql)); - } - - // A negative interval is relative to now; a positive one would be an absolute system time. - // The multiplication cannot overflow: u32 milliseconds times 10_000 stays well inside an i64. - let interval = -(i64::from(milliseconds) * HUNDRED_NANOS_PER_MILLISECOND); - let status = unsafe { ffi::KeDelayExecutionThread(KERNEL_MODE, 0, &interval) }; - - return check_ntstatus(status); -} - -// get_system_timestamp_ns return the number of nanoseconds since the start of the system. SHould be used only for performance measurements. -pub fn get_startup_time_ns() -> u64 { - let mut freq: i64 = 0; - let counter = unsafe { ffi::KeQueryPerformanceCounter(&mut freq as *mut i64) }; - // Convert counter ticks to nanoseconds - ((counter as u128) * 1_000_000_000u128 / freq as u128) as u64 -} +use alloc::format; +use alloc::string::{String, ToString}; +use ntstatus::ntstatus::NtStatus; +use windows_sys::Win32::Foundation::STATUS_SUCCESS; + +use crate::consts::{APC_LEVEL, KERNEL_MODE}; +use crate::ffi; + +/// Kernel wait intervals are counted in 100-nanosecond units. +const HUNDRED_NANOS_PER_MILLISECOND: i64 = 10_000; + +/// Returns the symbolic name of an NTSTATUS value, or a placeholder if it is not a known code. +pub fn ntstatus_name(status: i32) -> String { + let Some(name) = NtStatus::from_u32(status as u32) else { + return "UNKNOWN_ERROR_CODE".to_string(); + }; + + return name.to_string(); +} + +pub fn check_ntstatus(status: i32) -> Result<(), String> { + if status == STATUS_SUCCESS { + return Ok(()); + } + + return Err(ntstatus_name(status)); +} + +pub fn get_system_timestamp_ms() -> u64 { + // 100 nano seconds units -> device by 10 -> micro seconds -> divide by 1000 -> milliseconds + unsafe { ffi::pm_QuerySystemTime() / 10_000 } +} + +/// Blocks the calling thread for at least `milliseconds`, giving the rest of the system a chance +/// to make progress. The wait is non-alertable, so an APC does not cut it short. +/// +/// Only usable at IRQL <= APC_LEVEL. Waiting above that would bugcheck, so the level is checked +/// and reported as an error instead. +pub fn sleep_ms(milliseconds: u32) -> Result<(), String> { + let irql = unsafe { ffi::KeGetCurrentIrql() }; + if irql > APC_LEVEL { + return Err(format!("cannot wait at IRQL {}", irql)); + } + + // A negative interval is relative to now; a positive one would be an absolute system time. + // The multiplication cannot overflow: u32 milliseconds times 10_000 stays well inside an i64. + let interval = -(i64::from(milliseconds) * HUNDRED_NANOS_PER_MILLISECOND); + let status = unsafe { ffi::KeDelayExecutionThread(KERNEL_MODE, 0, &interval) }; + + return check_ntstatus(status); +} + +// get_system_timestamp_ns return the number of nanoseconds since the start of the system. SHould be used only for performance measurements. +pub fn get_startup_time_ns() -> u64 { + let mut freq: i64 = 0; + let counter = unsafe { ffi::KeQueryPerformanceCounter(&mut freq as *mut i64) }; + // Convert counter ticks to nanoseconds + ((counter as u128) * 1_000_000_000u128 / freq as u128) as u64 +}