use std::net::IpAddr; use std::str::FromStr; use ipnet::IpNet; /// Match an IP address against a pattern. /// /// Supported patterns: /// - `*` matches any IP /// - `192.168.1.0/24` CIDR range /// - `192.168.1.100` exact match /// - `192.168.1.*` wildcard (converted to CIDR) /// - `::ffff:192.168.1.100` IPv6-mapped IPv4 pub fn ip_matches(pattern: &str, ip: &str) -> bool { let pattern = pattern.trim(); if pattern == "*" { return true; } // Normalize IPv4-mapped IPv6 let normalized_ip = normalize_ip_str(ip); // Try CIDR match if pattern.contains('/') { if let Ok(net) = IpNet::from_str(pattern) { if let Ok(addr) = IpAddr::from_str(&normalized_ip) { return net.contains(&addr); } } return false; } // Handle wildcard patterns like 192.168.1.* if pattern.contains('*') { let pattern_cidr = wildcard_to_cidr(pattern); if let Some(cidr) = pattern_cidr { if let Ok(net) = IpNet::from_str(&cidr) { if let Ok(addr) = IpAddr::from_str(&normalized_ip) { return net.contains(&addr); } } } return false; } // Exact match let normalized_pattern = normalize_ip_str(pattern); normalized_ip == normalized_pattern } /// Check if an IP matches any of the given patterns. pub fn ip_matches_any(patterns: &[String], ip: &str) -> bool { patterns.iter().any(|p| ip_matches(p, ip)) } /// Normalize IPv4-mapped IPv6 addresses. fn normalize_ip_str(ip: &str) -> String { let ip = ip.trim(); if ip.starts_with("::ffff:") { return ip[7..].to_string(); } ip.to_string() } /// Convert a wildcard IP pattern to CIDR notation. /// e.g., "192.168.1.*" -> "192.168.1.0/24" fn wildcard_to_cidr(pattern: &str) -> Option { let parts: Vec<&str> = pattern.split('.').collect(); if parts.len() != 4 { return None; } let mut octets = [0u8; 4]; let mut prefix_len = 0; for (i, part) in parts.iter().enumerate() { if *part == "*" { break; } if let Ok(n) = part.parse::() { octets[i] = n; prefix_len += 8; } else { return None; } } Some(format!("{}.{}.{}.{}/{}", octets[0], octets[1], octets[2], octets[3], prefix_len)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_wildcard_all() { assert!(ip_matches("*", "192.168.1.100")); assert!(ip_matches("*", "::1")); } #[test] fn test_exact_match() { assert!(ip_matches("192.168.1.100", "192.168.1.100")); assert!(!ip_matches("192.168.1.100", "192.168.1.101")); } #[test] fn test_cidr() { assert!(ip_matches("192.168.1.0/24", "192.168.1.100")); assert!(ip_matches("192.168.1.0/24", "192.168.1.1")); assert!(!ip_matches("192.168.1.0/24", "192.168.2.1")); } #[test] fn test_wildcard_pattern() { assert!(ip_matches("192.168.1.*", "192.168.1.100")); assert!(ip_matches("192.168.1.*", "192.168.1.1")); assert!(!ip_matches("192.168.1.*", "192.168.2.1")); } #[test] fn test_ipv6_mapped() { assert!(ip_matches("192.168.1.100", "::ffff:192.168.1.100")); assert!(ip_matches("192.168.1.0/24", "::ffff:192.168.1.50")); } }