Skip to main content

windows_wfp/
condition.rs

1//! WFP filter condition types
2//!
3//! Types used as filter conditions in WFP rules: IP addresses with masks,
4//! and IP protocol numbers.
5
6use std::net::IpAddr;
7
8/// IP address with CIDR prefix length
9///
10/// Used for local and remote IP address conditions in WFP filters.
11///
12/// # Examples
13///
14/// ```
15/// use windows_wfp::IpAddrMask;
16/// use std::net::IpAddr;
17///
18/// // Match a single host
19/// let host = IpAddrMask::new("192.168.1.1".parse().unwrap(), 32);
20///
21/// // Match a /24 subnet
22/// let subnet = IpAddrMask::from_cidr("192.168.1.0/24").unwrap();
23///
24/// // Match an IPv6 address
25/// let ipv6 = IpAddrMask::new("::1".parse().unwrap(), 128);
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct IpAddrMask {
29    /// IP address (IPv4 or IPv6)
30    pub addr: IpAddr,
31    /// CIDR prefix length (0-32 for IPv4, 0-128 for IPv6)
32    pub prefix_len: u8,
33}
34
35impl IpAddrMask {
36    /// Create a new IP address with mask
37    pub fn new(addr: IpAddr, prefix_len: u8) -> Self {
38        Self { addr, prefix_len }
39    }
40
41    /// Parse from CIDR notation (e.g., "192.168.1.0/24" or "::1/128")
42    ///
43    /// # Errors
44    ///
45    /// Returns an error string if the format is invalid.
46    pub fn from_cidr(s: &str) -> Result<Self, String> {
47        let parts: Vec<&str> = s.split('/').collect();
48        if parts.len() != 2 {
49            return Err(format!("Invalid CIDR notation: {}", s));
50        }
51
52        let addr: IpAddr = parts[0]
53            .parse()
54            .map_err(|e| format!("Invalid IP address: {}", e))?;
55
56        let prefix_len: u8 = parts[1]
57            .parse()
58            .map_err(|e| format!("Invalid prefix length: {}", e))?;
59
60        let max_prefix = match addr {
61            IpAddr::V4(_) => 32,
62            IpAddr::V6(_) => 128,
63        };
64
65        if prefix_len > max_prefix {
66            return Err(format!(
67                "Prefix length {} exceeds maximum {} for {:?}",
68                prefix_len, max_prefix, addr
69            ));
70        }
71
72        Ok(Self { addr, prefix_len })
73    }
74
75    /// Returns true if this is an IPv6 address
76    pub fn is_ipv6(&self) -> bool {
77        matches!(self.addr, IpAddr::V6(_))
78    }
79}
80
81/// IP protocol numbers (IANA assigned)
82///
83/// Standard protocol numbers used in WFP filter conditions.
84/// Values match the IANA protocol number assignments.
85///
86/// # Examples
87///
88/// ```
89/// use windows_wfp::Protocol;
90///
91/// let tcp = Protocol::Tcp;
92/// assert_eq!(tcp as u8, 6);
93///
94/// let udp = Protocol::Udp;
95/// assert_eq!(udp as u8, 17);
96/// ```
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98#[repr(u8)]
99pub enum Protocol {
100    /// IPv6 Hop-by-Hop Option (protocol 0)
101    Hopopt = 0,
102    /// Internet Control Message Protocol v4 (protocol 1)
103    Icmp = 1,
104    /// Internet Group Management Protocol (protocol 2)
105    Igmp = 2,
106    /// Transmission Control Protocol (protocol 6)
107    Tcp = 6,
108    /// User Datagram Protocol (protocol 17)
109    Udp = 17,
110    /// Generic Routing Encapsulation (protocol 47)
111    Gre = 47,
112    /// Encapsulating Security Payload / IPsec (protocol 50)
113    Esp = 50,
114    /// Authentication Header / IPsec (protocol 51)
115    Ah = 51,
116    /// Internet Control Message Protocol v6 (protocol 58)
117    Icmpv6 = 58,
118}
119
120impl Protocol {
121    /// Get the IANA protocol number
122    pub fn as_u8(self) -> u8 {
123        self as u8
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_ip_addr_mask_new() {
133        let mask = IpAddrMask::new("10.0.0.1".parse().unwrap(), 24);
134        assert_eq!(mask.prefix_len, 24);
135        assert!(!mask.is_ipv6());
136    }
137
138    #[test]
139    fn test_ip_addr_mask_from_cidr_v4() {
140        let mask = IpAddrMask::from_cidr("192.168.1.0/24").unwrap();
141        assert_eq!(mask.addr, "192.168.1.0".parse::<IpAddr>().unwrap());
142        assert_eq!(mask.prefix_len, 24);
143    }
144
145    #[test]
146    fn test_ip_addr_mask_from_cidr_v6() {
147        let mask = IpAddrMask::from_cidr("fe80::1/64").unwrap();
148        assert!(mask.is_ipv6());
149        assert_eq!(mask.prefix_len, 64);
150    }
151
152    #[test]
153    fn test_ip_addr_mask_invalid_cidr() {
154        assert!(IpAddrMask::from_cidr("not-an-ip/24").is_err());
155        assert!(IpAddrMask::from_cidr("192.168.1.0").is_err());
156        assert!(IpAddrMask::from_cidr("192.168.1.0/33").is_err());
157        assert!(IpAddrMask::from_cidr("::1/129").is_err());
158    }
159
160    #[test]
161    fn test_protocol_values() {
162        assert_eq!(Protocol::Hopopt.as_u8(), 0);
163        assert_eq!(Protocol::Icmp.as_u8(), 1);
164        assert_eq!(Protocol::Igmp.as_u8(), 2);
165        assert_eq!(Protocol::Tcp.as_u8(), 6);
166        assert_eq!(Protocol::Udp.as_u8(), 17);
167        assert_eq!(Protocol::Gre.as_u8(), 47);
168        assert_eq!(Protocol::Esp.as_u8(), 50);
169        assert_eq!(Protocol::Ah.as_u8(), 51);
170        assert_eq!(Protocol::Icmpv6.as_u8(), 58);
171    }
172
173    #[test]
174    fn test_ip_addr_mask_boundary_prefixes_v4() {
175        let zero = IpAddrMask::from_cidr("0.0.0.0/0").unwrap();
176        assert_eq!(zero.prefix_len, 0);
177
178        let host = IpAddrMask::from_cidr("10.0.0.1/32").unwrap();
179        assert_eq!(host.prefix_len, 32);
180    }
181
182    #[test]
183    fn test_ip_addr_mask_boundary_prefixes_v6() {
184        let zero = IpAddrMask::from_cidr("::/0").unwrap();
185        assert_eq!(zero.prefix_len, 0);
186        assert!(zero.is_ipv6());
187
188        let host = IpAddrMask::from_cidr("::1/128").unwrap();
189        assert_eq!(host.prefix_len, 128);
190    }
191
192    #[test]
193    fn test_ip_addr_mask_equality() {
194        let a = IpAddrMask::new("10.0.0.1".parse().unwrap(), 24);
195        let b = IpAddrMask::new("10.0.0.1".parse().unwrap(), 24);
196        assert_eq!(a, b);
197
198        let c = IpAddrMask::new("10.0.0.1".parse().unwrap(), 16);
199        assert_ne!(a, c);
200    }
201
202    #[test]
203    fn test_ip_addr_mask_multiple_slashes() {
204        assert!(IpAddrMask::from_cidr("10.0.0.1/24/8").is_err());
205    }
206
207    #[test]
208    fn test_ip_addr_mask_empty_string() {
209        assert!(IpAddrMask::from_cidr("").is_err());
210    }
211
212    #[test]
213    fn test_ip_addr_mask_v4_is_not_ipv6() {
214        let mask = IpAddrMask::new("192.168.0.1".parse().unwrap(), 24);
215        assert!(!mask.is_ipv6());
216    }
217
218    #[test]
219    fn test_ip_addr_mask_v6_is_ipv6() {
220        let mask = IpAddrMask::new("::1".parse().unwrap(), 128);
221        assert!(mask.is_ipv6());
222    }
223
224    #[test]
225    fn test_protocol_copy() {
226        let p = Protocol::Tcp;
227        let p2 = p; // Copy
228        assert_eq!(p, p2);
229    }
230}