Skip to main content

relintio_agent/
utils.rs

1use std::net::IpAddr;
2
3/// Normalize IP address string (removes port numbers or brackets).
4pub fn normalize_ip(ip_str: &str) -> String {
5    let cleaned = ip_str.trim();
6    if let Ok(ip) = cleaned.parse::<IpAddr>() {
7        return ip.to_string();
8    }
9    // Try to remove port
10    if let Some(pos) = cleaned.rfind(':') {
11        let host = &cleaned[..pos];
12        let host_clean = host.trim_matches(|c| c == '[' || c == ']');
13        if let Ok(ip) = host_clean.parse::<IpAddr>() {
14            return ip.to_string();
15        }
16    }
17    cleaned.to_string()
18}
19
20/// Helper to check if an IP matches a CIDR range string (e.g., "192.168.1.0/24")
21pub fn ip_matches_cidr(ip_str: &str, cidr: &str) -> bool {
22    let ip = match ip_str.parse::<IpAddr>() {
23        Ok(addr) => addr,
24        Err(_) => return false,
25    };
26
27    let parts: Vec<&str> = cidr.split('/').collect();
28    if parts.is_empty() {
29        return false;
30    }
31
32    let subnet_ip = match parts[0].parse::<IpAddr>() {
33        Ok(addr) => addr,
34        Err(_) => return false,
35    };
36
37    let prefix_len = if parts.len() > 1 {
38        match parts[1].parse::<u8>() {
39            Ok(len) => len,
40            Err(_) => return false,
41        }
42    } else {
43        match ip {
44            IpAddr::V4(_) => 32,
45            IpAddr::V6(_) => 128,
46        }
47    };
48
49    match (ip, subnet_ip) {
50        (IpAddr::V4(ip_v4), IpAddr::V4(sub_v4)) => {
51            if prefix_len > 32 {
52                return false;
53            }
54            let ip_num = u32::from(ip_v4);
55            let sub_num = u32::from(sub_v4);
56            let mask = if prefix_len == 0 {
57                0
58            } else {
59                !0u32 << (32 - prefix_len)
60            };
61            (ip_num & mask) == (sub_num & mask)
62        }
63        (IpAddr::V6(ip_v6), IpAddr::V6(sub_v6)) => {
64            if prefix_len > 128 {
65                return false;
66            }
67            let ip_num = u128::from(ip_v6);
68            let sub_num = u128::from(sub_v6);
69            let mask = if prefix_len == 0 {
70                0
71            } else {
72                !0u128 << (128 - prefix_len)
73            };
74            (ip_num & mask) == (sub_num & mask)
75        }
76        _ => false,
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_normalize_ip() {
86        assert_eq!(normalize_ip("127.0.0.1:8080"), "127.0.0.1");
87        assert_eq!(normalize_ip("[::1]:80"), "::1");
88        assert_eq!(normalize_ip("192.168.1.1"), "192.168.1.1");
89    }
90
91    #[test]
92    fn test_ip_matches_cidr() {
93        assert!(ip_matches_cidr("192.168.1.50", "192.168.1.0/24"));
94        assert!(!ip_matches_cidr("192.168.2.50", "192.168.1.0/24"));
95        assert!(ip_matches_cidr("::1", "::1/128"));
96    }
97}