ss_rs/acl/
cidr.rs

1//! CIDR parser.
2
3use std::{
4    fmt::{self, Display, Formatter},
5    net::{IpAddr, Ipv4Addr, Ipv6Addr},
6    str::FromStr,
7};
8
9mod constants {
10    pub const MAXIMUM_IPV4_MASK: u8 = 32;
11    pub const MAXIMUM_IPV6_MASK: u8 = 128;
12}
13
14/// Represents a CIDR network.
15pub struct Cidr {
16    /// The network.
17    pub addr: IpAddr,
18
19    /// The subnet mask.
20    pub mask: u8,
21}
22
23impl FromStr for Cidr {
24    type Err = Error;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        let (addr, mask) = match s.split_once("/") {
28            Some(res) => res,
29            None => return Err(Error::NoSlash),
30        };
31
32        let is_ipv4 = Ipv4Addr::from_str(addr);
33        let is_ipv6 = Ipv6Addr::from_str(addr);
34
35        if is_ipv4.is_err() && is_ipv6.is_err() {
36            return Err(Error::NotAddr(addr.to_owned()));
37        }
38
39        if let Ok(v4) = is_ipv4 {
40            let mask = match mask.parse::<u8>() {
41                Ok(res) => res,
42                Err(_) => return Err(Error::NotMask(mask.to_string())),
43            };
44
45            if mask > constants::MAXIMUM_IPV4_MASK {
46                return Err(Error::NotMask(mask.to_string()));
47            }
48
49            return Ok(Cidr {
50                addr: IpAddr::V4(v4),
51                mask,
52            });
53        }
54
55        let v6 = unsafe { is_ipv6.unwrap_unchecked() };
56
57        let mask = match mask.parse::<u8>() {
58            Ok(res) => res,
59            Err(_) => return Err(Error::NotMask(mask.to_owned())),
60        };
61
62        if mask > constants::MAXIMUM_IPV6_MASK {
63            return Err(Error::NotMask(mask.to_string()));
64        }
65
66        Ok(Cidr {
67            addr: IpAddr::V6(v6),
68            mask,
69        })
70    }
71}
72
73/// Errors when parse a CIDR.
74#[derive(Debug)]
75pub enum Error {
76    /// No forward slash found.
77    NoSlash,
78
79    /// Not a ip address.
80    NotAddr(String),
81
82    /// Not a subnet mask.
83    NotMask(String),
84}
85
86impl Display for Error {
87    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
88        match self {
89            Error::NoSlash => write!(f, "no forward slash found"),
90            Error::NotAddr(addr) => write!(f, "{} is not a valid ip address", addr),
91            Error::NotMask(mask) => write!(f, "{} is not a valid subnet mask", mask),
92        }
93    }
94}
95
96impl std::error::Error for Error {}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_cidr() {
104        let iplist = [
105            "0.0.0.0/8",
106            "10.0.0.0/8",
107            "100.64.0.0/10",
108            "127.0.0.0/8",
109            "169.254.0.0/16",
110            "172.16.0.0/12",
111            "192.0.0.0/24",
112            "192.0.2.0/24",
113            "192.88.99.0/24",
114            "192.168.0.0/16",
115            "198.18.0.0/15",
116            "198.51.100.0/24",
117            "203.0.113.0/24",
118            "220.160.0.0/11",
119            "224.0.0.0/4",
120            "240.0.0.0/4",
121            "255.255.255.255/32",
122            "::1/128",
123            "::ffff:127.0.0.1/104",
124            "fc00::/7",
125            "fe80::/10",
126            "2001:b28:f23d:f001::e/128",
127        ];
128
129        for ip in iplist {
130            let _: Cidr = ip.parse().unwrap();
131        }
132    }
133
134    #[test]
135    fn test_error() {
136        assert!("127.0.0.1".parse::<Cidr>().is_err());
137        assert!("127.0.0./12".parse::<Cidr>().is_err());
138        assert!("127.0.0/12".parse::<Cidr>().is_err());
139
140        assert!(":1:/12".parse::<Cidr>().is_err());
141        assert!("122ff:/12".parse::<Cidr>().is_err());
142        assert!("122z:/12".parse::<Cidr>().is_err());
143
144        assert!("127.0.0.1/33".parse::<Cidr>().is_err());
145        assert!("127.0.0.1/99999999".parse::<Cidr>().is_err());
146
147        assert!("::1/129".parse::<Cidr>().is_err());
148        assert!("1222::1/999999999999".parse::<Cidr>().is_err());
149    }
150}