1use crate::condition::Protocol;
31use crate::constants::*;
32use crate::engine::WfpEngine;
33use crate::errors::{WfpError, WfpResult};
34use crate::filter::{Action, FilterRule};
35use crate::layer;
36use std::net::IpAddr;
37use std::ptr;
38use windows::core::{GUID, PWSTR};
39use windows::Win32::Foundation::ERROR_SUCCESS;
40use windows::Win32::NetworkManagement::WindowsFilteringPlatform::{
41 FwpmFilterAdd0, FwpmFilterDeleteById0, FwpmFreeMemory0, FwpmGetAppIdFromFileName0,
42 FWPM_FILTER0, FWPM_FILTER_CONDITION0, FWPM_FILTER_FLAGS, FWP_ACTION_BLOCK, FWP_ACTION_PERMIT,
43 FWP_ACTION_TYPE, FWP_BYTE_BLOB, FWP_BYTE_BLOB_TYPE, FWP_CONDITION_VALUE0, FWP_MATCH_EQUAL,
44 FWP_UINT16, FWP_UINT64, FWP_UINT8, FWP_V4_ADDR_AND_MASK, FWP_V4_ADDR_MASK,
45 FWP_V6_ADDR_AND_MASK, FWP_V6_ADDR_MASK,
46};
47
48pub struct FilterBuilder;
70
71impl FilterBuilder {
72 fn translate_action(action: Action) -> FWP_ACTION_TYPE {
74 match action {
75 Action::Permit => FWP_ACTION_PERMIT,
76 Action::Block => FWP_ACTION_BLOCK,
77 }
78 }
79
80 fn translate_protocol(protocol: Protocol) -> u8 {
82 protocol.as_u8()
83 }
84
85 fn prefix_to_v4_mask(prefix_len: u8) -> u32 {
89 if prefix_len == 0 {
90 0
91 } else if prefix_len >= 32 {
92 0xFFFFFFFF
93 } else {
94 u32::MAX << (32 - prefix_len)
95 }
96 }
97
98 pub fn add_filter(engine: &WfpEngine, rule: &FilterRule) -> WfpResult<u64> {
120 let is_ipv6 = rule
122 .remote_ip
123 .as_ref()
124 .map(|ip| ip.is_ipv6())
125 .or_else(|| rule.local_ip.as_ref().map(|ip| ip.is_ipv6()))
126 .unwrap_or(false);
127
128 let layer_key = layer::select_layer(rule.direction, is_ipv6);
129 let action = Self::translate_action(rule.action);
130
131 let name_wide: Vec<u16> = rule.name.encode_utf16().chain(std::iter::once(0)).collect();
133
134 let weight_value: u64 = rule.weight;
136
137 let app_id_blob: Option<*mut FWP_BYTE_BLOB> = if let Some(app_path) = &rule.app_path {
141 let path_str = app_path.to_string_lossy().to_string();
142 let path_wide: Vec<u16> = path_str.encode_utf16().chain(std::iter::once(0)).collect();
144
145 let mut blob_ptr: *mut FWP_BYTE_BLOB = ptr::null_mut();
146 let result = unsafe {
147 FwpmGetAppIdFromFileName0(PWSTR(path_wide.as_ptr() as *mut u16), &mut blob_ptr)
148 };
149
150 if result != ERROR_SUCCESS.0 {
151 return Err(WfpError::AppPathNotFound(path_str));
152 }
153
154 Some(blob_ptr)
155 } else {
156 None
157 };
158
159 let remote_v4_mask: Option<FWP_V4_ADDR_AND_MASK> =
160 rule.remote_ip.as_ref().and_then(|remote_ip| {
161 if let IpAddr::V4(ipv4) = remote_ip.addr {
162 Some(FWP_V4_ADDR_AND_MASK {
163 addr: u32::from_be_bytes(ipv4.octets()),
164 mask: Self::prefix_to_v4_mask(remote_ip.prefix_len),
165 })
166 } else {
167 None
168 }
169 });
170
171 let remote_v6_mask: Option<FWP_V6_ADDR_AND_MASK> =
172 rule.remote_ip.as_ref().and_then(|remote_ip| {
173 if let IpAddr::V6(ipv6) = remote_ip.addr {
174 Some(FWP_V6_ADDR_AND_MASK {
175 addr: ipv6.octets(),
176 prefixLength: remote_ip.prefix_len,
177 })
178 } else {
179 None
180 }
181 });
182
183 let local_v4_mask: Option<FWP_V4_ADDR_AND_MASK> =
184 rule.local_ip.as_ref().and_then(|local_ip| {
185 if let IpAddr::V4(ipv4) = local_ip.addr {
186 Some(FWP_V4_ADDR_AND_MASK {
187 addr: u32::from_be_bytes(ipv4.octets()),
188 mask: Self::prefix_to_v4_mask(local_ip.prefix_len),
189 })
190 } else {
191 None
192 }
193 });
194
195 let local_v6_mask: Option<FWP_V6_ADDR_AND_MASK> =
196 rule.local_ip.as_ref().and_then(|local_ip| {
197 if let IpAddr::V6(ipv6) = local_ip.addr {
198 Some(FWP_V6_ADDR_AND_MASK {
199 addr: ipv6.octets(),
200 prefixLength: local_ip.prefix_len,
201 })
202 } else {
203 None
204 }
205 });
206
207 let mut conditions = Vec::new();
209
210 if let Some(blob_ptr) = app_id_blob {
212 conditions.push(FWPM_FILTER_CONDITION0 {
213 fieldKey: CONDITION_ALE_APP_ID,
214 matchType: FWP_MATCH_EQUAL,
215 conditionValue: FWP_CONDITION_VALUE0 {
216 r#type: FWP_BYTE_BLOB_TYPE,
217 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
218 byteBlob: blob_ptr,
219 },
220 },
221 });
222 }
223
224 if let Some(remote_port) = rule.remote_port {
226 conditions.push(FWPM_FILTER_CONDITION0 {
227 fieldKey: CONDITION_IP_REMOTE_PORT,
228 matchType: FWP_MATCH_EQUAL,
229 conditionValue: FWP_CONDITION_VALUE0 {
230 r#type: FWP_UINT16,
231 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
232 uint16: remote_port,
233 },
234 },
235 });
236 }
237
238 if let Some(local_port) = rule.local_port {
240 conditions.push(FWPM_FILTER_CONDITION0 {
241 fieldKey: CONDITION_IP_LOCAL_PORT,
242 matchType: FWP_MATCH_EQUAL,
243 conditionValue: FWP_CONDITION_VALUE0 {
244 r#type: FWP_UINT16,
245 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
246 uint16: local_port,
247 },
248 },
249 });
250 }
251
252 if let Some(protocol) = rule.protocol {
254 conditions.push(FWPM_FILTER_CONDITION0 {
255 fieldKey: CONDITION_IP_PROTOCOL,
256 matchType: FWP_MATCH_EQUAL,
257 conditionValue: FWP_CONDITION_VALUE0 {
258 r#type: FWP_UINT8,
259 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
260 uint8: Self::translate_protocol(protocol),
261 },
262 },
263 });
264 }
265
266 if let Some(ref mask) = remote_v4_mask {
268 conditions.push(FWPM_FILTER_CONDITION0 {
269 fieldKey: CONDITION_IP_REMOTE_ADDRESS,
270 matchType: FWP_MATCH_EQUAL,
271 conditionValue: FWP_CONDITION_VALUE0 {
272 r#type: FWP_V4_ADDR_MASK,
273 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
274 v4AddrMask: mask as *const _ as *mut _,
275 },
276 },
277 });
278 }
279
280 if let Some(ref mask) = remote_v6_mask {
282 conditions.push(FWPM_FILTER_CONDITION0 {
283 fieldKey: CONDITION_IP_REMOTE_ADDRESS,
284 matchType: FWP_MATCH_EQUAL,
285 conditionValue: FWP_CONDITION_VALUE0 {
286 r#type: FWP_V6_ADDR_MASK,
287 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
288 v6AddrMask: mask as *const _ as *mut _,
289 },
290 },
291 });
292 }
293
294 if let Some(ref mask) = local_v4_mask {
296 conditions.push(FWPM_FILTER_CONDITION0 {
297 fieldKey: CONDITION_IP_LOCAL_ADDRESS,
298 matchType: FWP_MATCH_EQUAL,
299 conditionValue: FWP_CONDITION_VALUE0 {
300 r#type: FWP_V4_ADDR_MASK,
301 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
302 v4AddrMask: mask as *const _ as *mut _,
303 },
304 },
305 });
306 }
307
308 if let Some(ref mask) = local_v6_mask {
310 conditions.push(FWPM_FILTER_CONDITION0 {
311 fieldKey: CONDITION_IP_LOCAL_ADDRESS,
312 matchType: FWP_MATCH_EQUAL,
313 conditionValue: FWP_CONDITION_VALUE0 {
314 r#type: FWP_V6_ADDR_MASK,
315 Anonymous: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0 {
316 v6AddrMask: mask as *const _ as *mut _,
317 },
318 },
319 });
320 }
321
322 let filter = FWPM_FILTER0 {
324 filterKey: GUID::zeroed(),
325 displayData:
326 windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_DISPLAY_DATA0 {
327 name: PWSTR(name_wide.as_ptr() as *mut u16),
328 description: PWSTR::null(),
329 },
330 flags: FWPM_FILTER_FLAGS(0),
331 providerKey: &WFP_PROVIDER_GUID as *const _ as *mut _,
332 providerData:
333 windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_BYTE_BLOB {
334 size: 0,
335 data: ptr::null_mut(),
336 },
337 layerKey: layer_key,
338 subLayerKey: WFP_SUBLAYER_GUID,
339 weight: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_VALUE0 {
340 r#type: FWP_UINT64,
341 Anonymous:
342 windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_VALUE0_0 {
343 uint64: &weight_value as *const u64 as *mut u64,
344 },
345 },
346 numFilterConditions: conditions.len() as u32,
347 filterCondition: if conditions.is_empty() {
348 ptr::null_mut()
349 } else {
350 conditions.as_ptr() as *mut _
351 },
352 action: windows::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_ACTION0 {
353 r#type: action,
354 Anonymous: Default::default(),
355 },
356 Anonymous: Default::default(),
357 reserved: ptr::null_mut(),
358 filterId: 0,
359 effectiveWeight: Default::default(),
360 };
361
362 let mut filter_id: u64 = 0;
363
364 unsafe {
365 let result = FwpmFilterAdd0(engine.handle(), &filter, None, Some(&mut filter_id));
366
367 if let Some(mut blob_ptr) = app_id_blob {
369 if !blob_ptr.is_null() {
370 FwpmFreeMemory0(&mut blob_ptr as *mut _ as *mut *mut _);
371 }
372 }
373
374 if result != ERROR_SUCCESS.0 {
375 return Err(WfpError::FilterAddFailed(format!(
376 "Failed to add filter '{}': error code {}",
377 rule.name, result
378 )));
379 }
380 }
381
382 Ok(filter_id)
383 }
384
385 pub fn delete_filter(engine: &WfpEngine, filter_id: u64) -> WfpResult<()> {
410 unsafe {
411 let result = FwpmFilterDeleteById0(engine.handle(), filter_id);
412
413 if result != ERROR_SUCCESS.0 {
414 return Err(WfpError::FilterDeleteFailed(format!(
415 "Failed to delete filter ID {}: error code {}",
416 filter_id, result
417 )));
418 }
419 }
420
421 Ok(())
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use crate::condition::Protocol;
429 use crate::filter::{Action, FilterRule};
430
431 #[test]
432 fn test_action_translation() {
433 assert_eq!(
434 FilterBuilder::translate_action(Action::Permit),
435 FWP_ACTION_PERMIT
436 );
437 assert_eq!(
438 FilterBuilder::translate_action(Action::Block),
439 FWP_ACTION_BLOCK
440 );
441 }
442
443 #[test]
444 fn test_protocol_translation() {
445 assert_eq!(FilterBuilder::translate_protocol(Protocol::Hopopt), 0);
446 assert_eq!(FilterBuilder::translate_protocol(Protocol::Icmp), 1);
447 assert_eq!(FilterBuilder::translate_protocol(Protocol::Igmp), 2);
448 assert_eq!(FilterBuilder::translate_protocol(Protocol::Tcp), 6);
449 assert_eq!(FilterBuilder::translate_protocol(Protocol::Udp), 17);
450 assert_eq!(FilterBuilder::translate_protocol(Protocol::Gre), 47);
451 assert_eq!(FilterBuilder::translate_protocol(Protocol::Esp), 50);
452 assert_eq!(FilterBuilder::translate_protocol(Protocol::Ah), 51);
453 assert_eq!(FilterBuilder::translate_protocol(Protocol::Icmpv6), 58);
454 }
455
456 #[test]
457 fn test_prefix_to_v4_mask() {
458 assert_eq!(FilterBuilder::prefix_to_v4_mask(0), 0x00000000);
459 assert_eq!(FilterBuilder::prefix_to_v4_mask(8), 0xFF000000);
460 assert_eq!(FilterBuilder::prefix_to_v4_mask(16), 0xFFFF0000);
461 assert_eq!(FilterBuilder::prefix_to_v4_mask(24), 0xFFFFFF00);
462 assert_eq!(FilterBuilder::prefix_to_v4_mask(32), 0xFFFFFFFF);
463 }
464
465 #[test]
466 fn test_prefix_to_v4_mask_all_values() {
467 assert_eq!(FilterBuilder::prefix_to_v4_mask(1), 0x80000000);
468 assert_eq!(FilterBuilder::prefix_to_v4_mask(4), 0xF0000000);
469 assert_eq!(FilterBuilder::prefix_to_v4_mask(12), 0xFFF00000);
470 assert_eq!(FilterBuilder::prefix_to_v4_mask(20), 0xFFFFF000);
471 assert_eq!(FilterBuilder::prefix_to_v4_mask(28), 0xFFFFFFF0);
472 assert_eq!(FilterBuilder::prefix_to_v4_mask(31), 0xFFFFFFFE);
473 }
474
475 #[test]
476 fn test_prefix_to_v4_mask_overflow() {
477 assert_eq!(FilterBuilder::prefix_to_v4_mask(33), 0xFFFFFFFF);
479 assert_eq!(FilterBuilder::prefix_to_v4_mask(255), 0xFFFFFFFF);
480 }
481
482 #[test]
483 #[ignore] fn test_add_simple_filter() {
485 let engine = WfpEngine::new().expect("Failed to create engine");
486 crate::initialize_wfp(&engine).expect("Failed to initialize WFP");
487
488 let rule = FilterRule::allow_all_outbound();
489 let result = FilterBuilder::add_filter(&engine, &rule);
490
491 assert!(result.is_ok(), "Failed to add filter: {:?}", result);
492 }
493
494 #[test]
495 #[ignore] fn test_add_and_delete_filter() {
497 let engine = WfpEngine::new().expect("Failed to create engine");
498 crate::initialize_wfp(&engine).expect("Failed to initialize WFP");
499
500 let rule = FilterRule::allow_all_outbound();
501
502 let filter_id = FilterBuilder::add_filter(&engine, &rule).expect("Failed to add filter");
503 let result = FilterBuilder::delete_filter(&engine, filter_id);
504
505 assert!(result.is_ok(), "Failed to delete filter: {:?}", result);
506 }
507
508 #[test]
509 #[ignore] fn test_add_filter_with_nonexistent_app_path_returns_error() {
511 let engine = WfpEngine::new().expect("Failed to create engine");
512 crate::initialize_wfp(&engine).expect("Failed to initialize WFP");
513
514 let rule = FilterRule::new("Test", crate::Direction::Outbound, crate::Action::Block)
515 .with_app_path(r"C:\this\path\does\not\exist.exe");
516
517 let result = FilterBuilder::add_filter(&engine, &rule);
518 assert!(
519 matches!(result, Err(WfpError::AppPathNotFound(_))),
520 "Expected AppPathNotFound, got: {:?}",
521 result
522 );
523 }
524}