use crate::engine::WfpEngine;
use crate::errors::{WfpError, WfpResult};
use std::path::PathBuf;
use std::ptr;
use windows::core::GUID;
use windows::Win32::Foundation::{ERROR_SUCCESS, HANDLE};
use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
FwpmFilterCreateEnumHandle0, FwpmFilterDestroyEnumHandle0, FwpmFilterEnum0, FwpmFreeMemory0,
FWPM_FILTER0, FWP_ACTION_BLOCK, FWP_ACTION_CALLOUT_INSPECTION, FWP_ACTION_CALLOUT_TERMINATING,
FWP_ACTION_CALLOUT_UNKNOWN, FWP_ACTION_PERMIT, FWP_BYTE_BLOB_TYPE, FWP_EMPTY, FWP_UINT16,
FWP_UINT32, FWP_UINT64, FWP_UINT8,
};
use crate::constants::CONDITION_ALE_APP_ID;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterAction {
Block,
Permit,
CalloutTerminating,
CalloutInspection,
CalloutUnknown,
Other(u32),
}
#[derive(Debug, Clone)]
pub struct FilterInfo {
pub id: u64,
pub name: String,
pub description: String,
pub action: FilterAction,
pub provider_key: Option<GUID>,
pub weight: u64,
pub layer_key: GUID,
pub sublayer_key: GUID,
pub app_path: Option<PathBuf>,
pub num_conditions: u32,
}
pub struct FilterEnumerator;
impl FilterEnumerator {
pub fn all(engine: &WfpEngine) -> WfpResult<Vec<FilterInfo>> {
Self::enumerate_raw(engine, |filter_array, num_returned, acc: &mut Vec<FilterInfo>| {
for i in 0..num_returned {
unsafe {
let filter = &**filter_array.offset(i as isize);
acc.push(parse_filter(filter));
}
}
})
}
pub fn count(engine: &WfpEngine) -> WfpResult<usize> {
Self::enumerate_raw(engine, |_filter_array, num_returned, acc: &mut usize| {
*acc += num_returned as usize;
})
}
fn enumerate_raw<T, F>(engine: &WfpEngine, mut visitor: F) -> WfpResult<T>
where
T: Default,
F: FnMut(*mut *mut FWPM_FILTER0, u32, &mut T),
{
let mut enum_handle = HANDLE::default();
unsafe {
let result = FwpmFilterCreateEnumHandle0(engine.handle(), None, &mut enum_handle);
if result != ERROR_SUCCESS.0 {
return Err(WfpError::Other(format!(
"Failed to create filter enum handle: error code {}",
result
)));
}
}
let mut accumulator = T::default();
let batch_size = 100;
loop {
let mut filter_array: *mut *mut FWPM_FILTER0 = ptr::null_mut();
let mut num_returned: u32 = 0;
let result = unsafe {
FwpmFilterEnum0(
engine.handle(),
enum_handle,
batch_size,
&mut filter_array,
&mut num_returned,
)
};
if result != ERROR_SUCCESS.0 {
unsafe {
let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
}
return Err(WfpError::Other(format!(
"Failed to enumerate filters: error code {}",
result
)));
}
if num_returned == 0 {
unsafe {
if !filter_array.is_null() {
FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
}
}
break;
}
visitor(filter_array, num_returned, &mut accumulator);
unsafe {
if !filter_array.is_null() {
FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
}
}
}
unsafe {
let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
}
Ok(accumulator)
}
}
unsafe fn parse_filter(filter: &FWPM_FILTER0) -> FilterInfo {
let name = if !filter.displayData.name.is_null() {
filter
.displayData
.name
.to_string()
.unwrap_or_else(|_| String::new())
} else {
String::new()
};
let description = if !filter.displayData.description.is_null() {
filter
.displayData
.description
.to_string()
.unwrap_or_else(|_| String::new())
} else {
String::new()
};
let action = match filter.action.r#type {
FWP_ACTION_BLOCK => FilterAction::Block,
FWP_ACTION_PERMIT => FilterAction::Permit,
FWP_ACTION_CALLOUT_TERMINATING => FilterAction::CalloutTerminating,
FWP_ACTION_CALLOUT_INSPECTION => FilterAction::CalloutInspection,
FWP_ACTION_CALLOUT_UNKNOWN => FilterAction::CalloutUnknown,
other => FilterAction::Other(other.0),
};
let provider_key = if !filter.providerKey.is_null() {
Some(*filter.providerKey)
} else {
None
};
let weight = match filter.weight.r#type {
FWP_UINT8 => filter.weight.Anonymous.uint8 as u64,
FWP_UINT16 => filter.weight.Anonymous.uint16 as u64,
FWP_UINT32 => filter.weight.Anonymous.uint32 as u64,
FWP_UINT64 => {
let ptr = filter.weight.Anonymous.uint64;
if ptr.is_null() {
0
} else {
*ptr
}
}
FWP_EMPTY => 0,
_ => 0,
};
let app_path = extract_app_path(filter);
FilterInfo {
id: filter.filterId,
name,
description,
action,
provider_key,
weight,
layer_key: filter.layerKey,
sublayer_key: filter.subLayerKey,
app_path,
num_conditions: filter.numFilterConditions,
}
}
unsafe fn extract_app_path(filter: &FWPM_FILTER0) -> Option<PathBuf> {
if filter.numFilterConditions == 0 || filter.filterCondition.is_null() {
return None;
}
let conditions =
std::slice::from_raw_parts(filter.filterCondition, filter.numFilterConditions as usize);
let condition = conditions
.iter()
.find(|c| c.fieldKey == CONDITION_ALE_APP_ID)?;
if condition.conditionValue.r#type != FWP_BYTE_BLOB_TYPE {
return None;
}
let blob_ptr = condition.conditionValue.Anonymous.byteBlob;
if blob_ptr.is_null() {
return None;
}
let blob = &*blob_ptr;
if blob.data.is_null() || blob.size == 0 || (blob.size % 2) != 0 {
return None;
}
let wide_slice = std::slice::from_raw_parts(blob.data as *const u16, (blob.size / 2) as usize);
let null_pos = wide_slice
.iter()
.position(|&c| c == 0)
.unwrap_or(wide_slice.len());
String::from_utf16(&wide_slice[..null_pos])
.ok()
.map(PathBuf::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_action_eq() {
assert_eq!(FilterAction::Block, FilterAction::Block);
assert_eq!(FilterAction::Permit, FilterAction::Permit);
assert_ne!(FilterAction::Block, FilterAction::Permit);
assert_eq!(FilterAction::Other(42), FilterAction::Other(42));
assert_ne!(FilterAction::Other(1), FilterAction::Other(2));
}
#[test]
fn test_filter_action_copy() {
let action = FilterAction::Block;
let copy = action;
assert_eq!(action, copy);
}
#[test]
fn test_filter_info_clone() {
let info = FilterInfo {
id: 42,
name: "Test filter".to_string(),
description: "A test".to_string(),
action: FilterAction::Block,
provider_key: None,
weight: 1000,
layer_key: GUID::zeroed(),
sublayer_key: GUID::zeroed(),
app_path: Some(PathBuf::from(r"C:\test.exe")),
num_conditions: 1,
};
let cloned = info.clone();
assert_eq!(cloned.id, 42);
assert_eq!(cloned.name, "Test filter");
assert_eq!(cloned.action, FilterAction::Block);
assert!(cloned.app_path.is_some());
}
#[test]
fn test_filter_info_default_values() {
let info = FilterInfo {
id: 0,
name: String::new(),
description: String::new(),
action: FilterAction::Permit,
provider_key: None,
weight: 0,
layer_key: GUID::zeroed(),
sublayer_key: GUID::zeroed(),
app_path: None,
num_conditions: 0,
};
assert_eq!(info.id, 0);
assert!(info.name.is_empty());
assert!(info.provider_key.is_none());
assert!(info.app_path.is_none());
}
#[test]
fn test_filter_info_with_provider() {
let guid = GUID::from_u128(0x12345678_1234_5678_1234_567812345678);
let info = FilterInfo {
id: 100,
name: "Provider filter".to_string(),
description: String::new(),
action: FilterAction::Block,
provider_key: Some(guid),
weight: 5000,
layer_key: GUID::zeroed(),
sublayer_key: GUID::zeroed(),
app_path: None,
num_conditions: 3,
};
assert_eq!(info.provider_key, Some(guid));
assert_eq!(info.num_conditions, 3);
}
#[test]
#[ignore] fn test_enumerate_all_filters() {
let engine = WfpEngine::new().expect("Failed to open WFP engine");
let filters = FilterEnumerator::all(&engine).expect("Failed to enumerate");
assert!(!filters.is_empty(), "Expected at least one WFP filter");
}
#[test]
#[ignore] fn test_count_filters() {
let engine = WfpEngine::new().expect("Failed to open WFP engine");
let count = FilterEnumerator::count(&engine).expect("Failed to count");
let all = FilterEnumerator::all(&engine).expect("Failed to enumerate");
assert_eq!(count, all.len());
}
}