Skip to main content

windows_wfp/
filter_enum.rs

1//! WFP Filter Enumeration
2//!
3//! Enumerate active WFP filters in the system. Useful for debugging,
4//! auditing, and discovering filters from other firewall software.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use windows_wfp::{WfpEngine, FilterEnumerator, FilterInfo};
10//!
11//! let engine = WfpEngine::new()?;
12//! let filters: Vec<FilterInfo> = FilterEnumerator::all(&engine)?;
13//!
14//! for filter in &filters {
15//!     println!("{}: {} ({:?})", filter.id, filter.name, filter.action);
16//! }
17//! # Ok::<(), windows_wfp::WfpError>(())
18//! ```
19
20use crate::engine::WfpEngine;
21use crate::errors::{WfpError, WfpResult};
22use std::path::PathBuf;
23use std::ptr;
24use windows::core::GUID;
25use windows::Win32::Foundation::{ERROR_SUCCESS, HANDLE};
26use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
27    FwpmFilterCreateEnumHandle0, FwpmFilterDestroyEnumHandle0, FwpmFilterEnum0, FwpmFreeMemory0,
28    FWPM_FILTER0, FWP_ACTION_BLOCK, FWP_ACTION_CALLOUT_INSPECTION, FWP_ACTION_CALLOUT_TERMINATING,
29    FWP_ACTION_CALLOUT_UNKNOWN, FWP_ACTION_PERMIT, FWP_BYTE_BLOB_TYPE, FWP_EMPTY, FWP_UINT16,
30    FWP_UINT32, FWP_UINT64, FWP_UINT8,
31};
32
33use crate::constants::CONDITION_ALE_APP_ID;
34
35/// Action type of a WFP filter
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum FilterAction {
38    /// Block traffic
39    Block,
40    /// Permit traffic
41    Permit,
42    /// Callout (terminating)
43    CalloutTerminating,
44    /// Callout (inspection only)
45    CalloutInspection,
46    /// Callout (unknown)
47    CalloutUnknown,
48    /// Other action type
49    Other(u32),
50}
51
52/// Information about an active WFP filter
53#[derive(Debug, Clone)]
54pub struct FilterInfo {
55    /// WFP filter ID
56    pub id: u64,
57    /// Display name
58    pub name: String,
59    /// Description
60    pub description: String,
61    /// Filter action (Block, Permit, Callout)
62    pub action: FilterAction,
63    /// Provider GUID (if set)
64    pub provider_key: Option<GUID>,
65    /// Filter weight/priority
66    pub weight: u64,
67    /// Layer GUID where the filter is installed
68    pub layer_key: GUID,
69    /// Sublayer GUID
70    pub sublayer_key: GUID,
71    /// Application path extracted from conditions (if present)
72    pub app_path: Option<PathBuf>,
73    /// Number of conditions on this filter
74    pub num_conditions: u32,
75}
76
77/// Enumerates WFP filters from the system
78///
79/// Provides methods to list all active filters; callers can then filter by provider if desired.
80///
81/// # Example
82///
83/// ```no_run
84/// use windows_wfp::{WfpEngine, FilterEnumerator};
85///
86/// let engine = WfpEngine::new()?;
87///
88/// // List all filters
89/// let all = FilterEnumerator::all(&engine)?;
90/// println!("Total filters: {}", all.len());
91///
92/// // Filter by provider GUID
93/// use windows::core::GUID;
94/// let my_guid = GUID::from_u128(0x12345678_1234_5678_1234_567812345678);
95/// let mine: Vec<_> = all.iter().filter(|f| f.provider_key == Some(my_guid)).collect();
96/// # Ok::<(), windows_wfp::WfpError>(())
97/// ```
98pub struct FilterEnumerator;
99
100impl FilterEnumerator {
101    /// Enumerate all active WFP filters
102    ///
103    /// Returns a vector of [`FilterInfo`] for every filter currently registered in WFP.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the enumeration handle cannot be created or enumeration fails.
108    /// Requires administrator privileges.
109    pub fn all(engine: &WfpEngine) -> WfpResult<Vec<FilterInfo>> {
110        let mut enum_handle = HANDLE::default();
111
112        unsafe {
113            let result = FwpmFilterCreateEnumHandle0(engine.handle(), None, &mut enum_handle);
114
115            if result != ERROR_SUCCESS.0 {
116                return Err(WfpError::Other(format!(
117                    "Failed to create filter enum handle: error code {}",
118                    result
119                )));
120            }
121        }
122
123        let mut filters = Vec::new();
124        let batch_size = 100;
125
126        loop {
127            let mut filter_array: *mut *mut FWPM_FILTER0 = ptr::null_mut();
128            let mut num_returned: u32 = 0;
129
130            unsafe {
131                let result = FwpmFilterEnum0(
132                    engine.handle(),
133                    enum_handle,
134                    batch_size,
135                    &mut filter_array,
136                    &mut num_returned,
137                );
138
139                if result != ERROR_SUCCESS.0 {
140                    let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
141                    return Err(WfpError::Other(format!(
142                        "Failed to enumerate filters: error code {}",
143                        result
144                    )));
145                }
146
147                if num_returned == 0 {
148                    if !filter_array.is_null() {
149                        FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
150                    }
151                    break;
152                }
153
154                for i in 0..num_returned {
155                    let filter = &**filter_array.offset(i as isize);
156                    filters.push(parse_filter(filter));
157                }
158
159                if !filter_array.is_null() {
160                    FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
161                }
162            }
163        }
164
165        unsafe {
166            let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
167        }
168
169        Ok(filters)
170    }
171
172    /// Count all active WFP filters without collecting details
173    ///
174    /// Only counts filters without parsing their contents, avoiding
175    /// string allocations and condition extraction.
176    pub fn count(engine: &WfpEngine) -> WfpResult<usize> {
177        let mut enum_handle = HANDLE::default();
178
179        unsafe {
180            let result = FwpmFilterCreateEnumHandle0(engine.handle(), None, &mut enum_handle);
181
182            if result != ERROR_SUCCESS.0 {
183                return Err(WfpError::Other(format!(
184                    "Failed to create filter enum handle: error code {}",
185                    result
186                )));
187            }
188        }
189
190        let mut count: usize = 0;
191        let batch_size = 100;
192
193        loop {
194            let mut filter_array: *mut *mut FWPM_FILTER0 = ptr::null_mut();
195            let mut num_returned: u32 = 0;
196
197            unsafe {
198                let result = FwpmFilterEnum0(
199                    engine.handle(),
200                    enum_handle,
201                    batch_size,
202                    &mut filter_array,
203                    &mut num_returned,
204                );
205
206                if result != ERROR_SUCCESS.0 {
207                    let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
208                    return Err(WfpError::Other(format!(
209                        "Failed to enumerate filters: error code {}",
210                        result
211                    )));
212                }
213
214                if num_returned == 0 {
215                    if !filter_array.is_null() {
216                        FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
217                    }
218                    break;
219                }
220
221                count += num_returned as usize;
222
223                if !filter_array.is_null() {
224                    FwpmFreeMemory0(&mut filter_array as *mut _ as *mut *mut _);
225                }
226            }
227        }
228
229        unsafe {
230            let _ = FwpmFilterDestroyEnumHandle0(engine.handle(), enum_handle);
231        }
232
233        Ok(count)
234    }
235}
236
237/// Parse a raw FWPM_FILTER0 into FilterInfo
238///
239/// # Safety
240///
241/// The filter pointer must be valid.
242unsafe fn parse_filter(filter: &FWPM_FILTER0) -> FilterInfo {
243    let name = if !filter.displayData.name.is_null() {
244        filter
245            .displayData
246            .name
247            .to_string()
248            .unwrap_or_else(|_| String::new())
249    } else {
250        String::new()
251    };
252
253    let description = if !filter.displayData.description.is_null() {
254        filter
255            .displayData
256            .description
257            .to_string()
258            .unwrap_or_else(|_| String::new())
259    } else {
260        String::new()
261    };
262
263    let action = match filter.action.r#type {
264        FWP_ACTION_BLOCK => FilterAction::Block,
265        FWP_ACTION_PERMIT => FilterAction::Permit,
266        FWP_ACTION_CALLOUT_TERMINATING => FilterAction::CalloutTerminating,
267        FWP_ACTION_CALLOUT_INSPECTION => FilterAction::CalloutInspection,
268        FWP_ACTION_CALLOUT_UNKNOWN => FilterAction::CalloutUnknown,
269        other => FilterAction::Other(other.0),
270    };
271
272    let provider_key = if !filter.providerKey.is_null() {
273        Some(*filter.providerKey)
274    } else {
275        None
276    };
277
278    let weight = match filter.weight.r#type {
279        FWP_UINT8 => filter.weight.Anonymous.uint8 as u64,
280        FWP_UINT16 => filter.weight.Anonymous.uint16 as u64,
281        FWP_UINT32 => filter.weight.Anonymous.uint32 as u64,
282        FWP_UINT64 => {
283            let ptr = filter.weight.Anonymous.uint64;
284            if ptr.is_null() {
285                0
286            } else {
287                *ptr
288            }
289        }
290        FWP_EMPTY => 0,
291        _ => 0,
292    };
293
294    let app_path = extract_app_path(filter);
295
296    FilterInfo {
297        id: filter.filterId,
298        name,
299        description,
300        action,
301        provider_key,
302        weight,
303        layer_key: filter.layerKey,
304        sublayer_key: filter.subLayerKey,
305        app_path,
306        num_conditions: filter.numFilterConditions,
307    }
308}
309
310/// Extract application path from filter conditions
311///
312/// Looks for FWPM_CONDITION_ALE_APP_ID in the filter's conditions
313/// and decodes the wide-string blob.
314unsafe fn extract_app_path(filter: &FWPM_FILTER0) -> Option<PathBuf> {
315    if filter.numFilterConditions == 0 || filter.filterCondition.is_null() {
316        return None;
317    }
318
319    let conditions =
320        std::slice::from_raw_parts(filter.filterCondition, filter.numFilterConditions as usize);
321
322    let condition = conditions
323        .iter()
324        .find(|c| c.fieldKey == CONDITION_ALE_APP_ID)?;
325
326    if condition.conditionValue.r#type != FWP_BYTE_BLOB_TYPE {
327        return None;
328    }
329
330    let blob_ptr = condition.conditionValue.Anonymous.byteBlob;
331    if blob_ptr.is_null() {
332        return None;
333    }
334
335    let blob = &*blob_ptr;
336    if blob.data.is_null() || blob.size == 0 || (blob.size % 2) != 0 {
337        return None;
338    }
339
340    let wide_slice = std::slice::from_raw_parts(blob.data as *const u16, (blob.size / 2) as usize);
341    let null_pos = wide_slice
342        .iter()
343        .position(|&c| c == 0)
344        .unwrap_or(wide_slice.len());
345
346    String::from_utf16(&wide_slice[..null_pos])
347        .ok()
348        .map(PathBuf::from)
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    #[test]
356    fn test_filter_action_eq() {
357        assert_eq!(FilterAction::Block, FilterAction::Block);
358        assert_eq!(FilterAction::Permit, FilterAction::Permit);
359        assert_ne!(FilterAction::Block, FilterAction::Permit);
360        assert_eq!(FilterAction::Other(42), FilterAction::Other(42));
361        assert_ne!(FilterAction::Other(1), FilterAction::Other(2));
362    }
363
364    #[test]
365    fn test_filter_action_copy() {
366        let action = FilterAction::Block;
367        let copy = action;
368        assert_eq!(action, copy);
369    }
370
371    #[test]
372    fn test_filter_info_clone() {
373        let info = FilterInfo {
374            id: 42,
375            name: "Test filter".to_string(),
376            description: "A test".to_string(),
377            action: FilterAction::Block,
378            provider_key: None,
379            weight: 1000,
380            layer_key: GUID::zeroed(),
381            sublayer_key: GUID::zeroed(),
382            app_path: Some(PathBuf::from(r"C:\test.exe")),
383            num_conditions: 1,
384        };
385
386        let cloned = info.clone();
387        assert_eq!(cloned.id, 42);
388        assert_eq!(cloned.name, "Test filter");
389        assert_eq!(cloned.action, FilterAction::Block);
390        assert!(cloned.app_path.is_some());
391    }
392
393    #[test]
394    fn test_filter_info_default_values() {
395        let info = FilterInfo {
396            id: 0,
397            name: String::new(),
398            description: String::new(),
399            action: FilterAction::Permit,
400            provider_key: None,
401            weight: 0,
402            layer_key: GUID::zeroed(),
403            sublayer_key: GUID::zeroed(),
404            app_path: None,
405            num_conditions: 0,
406        };
407
408        assert_eq!(info.id, 0);
409        assert!(info.name.is_empty());
410        assert!(info.provider_key.is_none());
411        assert!(info.app_path.is_none());
412    }
413
414    #[test]
415    fn test_filter_info_with_provider() {
416        let guid = GUID::from_u128(0x12345678_1234_5678_1234_567812345678);
417        let info = FilterInfo {
418            id: 100,
419            name: "Provider filter".to_string(),
420            description: String::new(),
421            action: FilterAction::Block,
422            provider_key: Some(guid),
423            weight: 5000,
424            layer_key: GUID::zeroed(),
425            sublayer_key: GUID::zeroed(),
426            app_path: None,
427            num_conditions: 3,
428        };
429
430        assert_eq!(info.provider_key, Some(guid));
431        assert_eq!(info.num_conditions, 3);
432    }
433
434    #[test]
435    #[ignore] // Requires administrator privileges
436    fn test_enumerate_all_filters() {
437        let engine = WfpEngine::new().expect("Failed to open WFP engine");
438        let filters = FilterEnumerator::all(&engine).expect("Failed to enumerate");
439        // Any Windows system should have at least some WFP filters
440        assert!(!filters.is_empty(), "Expected at least one WFP filter");
441    }
442
443    #[test]
444    #[ignore] // Requires administrator privileges
445    fn test_count_filters() {
446        let engine = WfpEngine::new().expect("Failed to open WFP engine");
447        let count = FilterEnumerator::count(&engine).expect("Failed to count");
448        let all = FilterEnumerator::all(&engine).expect("Failed to enumerate");
449        assert_eq!(count, all.len());
450    }
451}