use crate::error::NetSemError;
use std::net::IpAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IpClass {
Loopback,
Private,
LinkLocal,
Global,
Multicast,
Unspecified,
Broadcast,
Documentation,
SharedAddress,
Benchmarking,
}
pub fn parse_ip(s: &str) -> Result<IpAddr, NetSemError> {
s.parse::<IpAddr>()
.map_err(|_| NetSemError::InvalidIp(s.to_owned()))
}
#[must_use]
pub fn classify_ip(ip: IpAddr) -> IpClass {
if ip.is_loopback() {
return IpClass::Loopback;
}
if ip.is_unspecified() {
return IpClass::Unspecified;
}
if ip.is_multicast() {
return IpClass::Multicast;
}
match ip {
IpAddr::V4(ipv4) => {
if ipv4.is_broadcast() {
return IpClass::Broadcast;
}
if ipv4.is_link_local() {
return IpClass::LinkLocal;
}
if ipv4.is_documentation() {
return IpClass::Documentation;
}
{
let octets = ipv4.octets();
if octets[0] == 100 && (octets[1] & 0xc0) == 64 {
return IpClass::SharedAddress;
}
}
{
let octets = ipv4.octets();
if octets[0] == 198 && (octets[1] & 0xfe) == 18 {
return IpClass::Benchmarking;
}
}
if ipv4.is_private() {
return IpClass::Private;
}
}
IpAddr::V6(ipv6) => {
if (ipv6.segments()[0] & 0xffc0) == 0xfe80 {
return IpClass::LinkLocal;
}
if ipv6.segments()[0] == 0x2001 && ipv6.segments()[1] == 0x0db8 {
return IpClass::Documentation;
}
if (ipv6.segments()[0] & 0xfe00) == 0xfc00 {
return IpClass::Private;
}
}
}
IpClass::Global
}
#[must_use]
pub fn is_valid_ip_literal(s: &str) -> bool {
s.parse::<IpAddr>().is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn test_parse_ip() {
assert!(parse_ip("127.0.0.1").is_ok());
assert!(parse_ip("::1").is_ok());
assert!(parse_ip("invalid").is_err());
}
#[test]
fn test_classify_ip() {
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
IpClass::Loopback
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
IpClass::Loopback
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
IpClass::Unspecified
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1))),
IpClass::Multicast
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1))),
IpClass::Multicast
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255))),
IpClass::Broadcast
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 1, 1))),
IpClass::LinkLocal
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))),
IpClass::LinkLocal
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1))),
IpClass::Documentation
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1))),
IpClass::Documentation
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1))),
IpClass::Documentation
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1))),
IpClass::Documentation
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
IpClass::Private
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
IpClass::Private
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1))),
IpClass::Private
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(100, 64, 0, 1))),
IpClass::SharedAddress
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(100, 127, 255, 255))),
IpClass::SharedAddress
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(198, 18, 0, 1))),
IpClass::Benchmarking
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(198, 19, 255, 255))),
IpClass::Benchmarking
);
assert_eq!(
classify_ip(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))),
IpClass::Global
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::new(
0x2606, 0x4700, 0, 0, 0, 0, 0, 0x1111
))),
IpClass::Global
);
assert_eq!(
classify_ip(IpAddr::V6(Ipv6Addr::UNSPECIFIED)),
IpClass::Unspecified
);
}
#[test]
fn test_is_valid_ip_literal() {
assert!(is_valid_ip_literal("127.0.0.1"));
assert!(is_valid_ip_literal("::1"));
assert!(is_valid_ip_literal("192.168.0.1"));
assert!(!is_valid_ip_literal("invalid"));
assert!(!is_valid_ip_literal("256.0.0.1"));
assert!(!is_valid_ip_literal(""));
}
}