1use std::net::IpAddr;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct IpAddrMask {
29 pub addr: IpAddr,
31 pub prefix_len: u8,
33}
34
35impl IpAddrMask {
36 pub fn new(addr: IpAddr, prefix_len: u8) -> Self {
38 Self { addr, prefix_len }
39 }
40
41 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 pub fn is_ipv6(&self) -> bool {
77 matches!(self.addr, IpAddr::V6(_))
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98#[repr(u8)]
99pub enum Protocol {
100 Hopopt = 0,
102 Icmp = 1,
104 Igmp = 2,
106 Tcp = 6,
108 Udp = 17,
110 Gre = 47,
112 Esp = 50,
114 Ah = 51,
116 Icmpv6 = 58,
118}
119
120impl Protocol {
121 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; assert_eq!(p, p2);
229 }
230}