use std::{
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
num::ParseIntError,
};
#[derive(Debug, Clone)]
pub struct IpNetPrefixError(u8);
impl std::fmt::Display for IpNetPrefixError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid network prefix {}", self.0)
}
}
impl std::error::Error for IpNetPrefixError {}
#[derive(Debug, Clone)]
pub enum IpNetParseError {
InvalidAddr(AddrParseError),
PrefixValue(IpNetPrefixError),
NoPrefix,
InvalidPrefix(ParseIntError),
}
impl std::fmt::Display for IpNetParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpNetParseError::InvalidAddr(e) => e.fmt(f),
IpNetParseError::PrefixValue(e) => {
write!(f, "invalid prefix value: {e}")
}
IpNetParseError::NoPrefix => write!(f, "missing '/' character"),
IpNetParseError::InvalidPrefix(e) => e.fmt(f),
}
}
}
impl std::error::Error for IpNetParseError {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum IpNet {
V4(Ipv4Net),
V6(Ipv6Net),
}
impl IpNet {
pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, IpNetPrefixError> {
match addr {
IpAddr::V4(addr) => Ok(Self::V4(Ipv4Net::new(addr, prefix)?)),
IpAddr::V6(addr) => Ok(Self::V6(Ipv6Net::new(addr, prefix)?)),
}
}
pub const fn new_unchecked(addr: IpAddr, prefix: u8) -> Self {
match addr {
IpAddr::V4(addr) => Self::V4(Ipv4Net::new_unchecked(addr, prefix)),
IpAddr::V6(addr) => Self::V6(Ipv6Net::new_unchecked(addr, prefix)),
}
}
pub fn host_net(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(addr) => Self::V4(Ipv4Net::host_net(addr)),
IpAddr::V6(addr) => Self::V6(Ipv6Net::host_net(addr)),
}
}
pub const fn addr(&self) -> IpAddr {
match self {
IpNet::V4(inner) => IpAddr::V4(inner.addr()),
IpNet::V6(inner) => IpAddr::V6(inner.addr()),
}
}
pub fn prefix(&self) -> IpAddr {
match self {
IpNet::V4(inner) => inner.prefix().into(),
IpNet::V6(inner) => inner.prefix().into(),
}
}
pub const fn width(&self) -> u8 {
match self {
IpNet::V4(inner) => inner.width(),
IpNet::V6(inner) => inner.width(),
}
}
pub fn mask_addr(&self) -> IpAddr {
match self {
IpNet::V4(inner) => inner.mask_addr().into(),
IpNet::V6(inner) => inner.mask_addr().into(),
}
}
pub const fn is_host_net(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_host_net(),
IpNet::V6(inner) => inner.is_host_net(),
}
}
pub fn is_network_address(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_network_address(),
IpNet::V6(inner) => inner.is_network_address(),
}
}
pub const fn is_multicast(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_multicast(),
IpNet::V6(inner) => inner.is_multicast(),
}
}
pub const fn is_admin_scoped_multicast(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_admin_scoped_multicast(),
IpNet::V6(inner) => inner.is_admin_scoped_multicast(),
}
}
pub const fn is_admin_local_multicast(&self) -> bool {
match self {
IpNet::V4(_inner) => false,
IpNet::V6(inner) => inner.is_admin_local_multicast(),
}
}
pub const fn is_local_multicast(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_local_multicast(),
IpNet::V6(_inner) => false,
}
}
pub const fn is_site_local_multicast(&self) -> bool {
match self {
IpNet::V4(_inner) => false,
IpNet::V6(inner) => inner.is_site_local_multicast(),
}
}
pub const fn is_org_local_multicast(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_org_local_multicast(),
IpNet::V6(inner) => inner.is_org_local_multicast(),
}
}
pub const fn is_unique_local(&self) -> bool {
match self {
IpNet::V4(_inner) => false, IpNet::V6(inner) => inner.is_unique_local(),
}
}
pub const fn is_loopback(&self) -> bool {
match self {
IpNet::V4(inner) => inner.is_loopback(),
IpNet::V6(inner) => inner.is_loopback(),
}
}
pub fn contains(&self, addr: IpAddr) -> bool {
match (self, addr) {
(IpNet::V4(net), IpAddr::V4(ip)) => net.contains(ip),
(IpNet::V6(net), IpAddr::V6(ip)) => net.contains(ip),
(_, _) => false,
}
}
pub fn is_subnet_of(&self, other: &Self) -> bool {
match (self, other) {
(IpNet::V4(net), IpNet::V4(other)) => net.is_subnet_of(other),
(IpNet::V6(net), IpNet::V6(other)) => net.is_subnet_of(other),
(_, _) => false,
}
}
pub fn is_supernet_of(&self, other: &Self) -> bool {
other.is_subnet_of(self)
}
pub fn overlaps(&self, other: &Self) -> bool {
match (self, other) {
(IpNet::V4(net), IpNet::V4(other)) => net.overlaps(other),
(IpNet::V6(net), IpNet::V6(other)) => net.overlaps(other),
(_, _) => false,
}
}
pub const fn is_ipv4(&self) -> bool {
matches!(self, IpNet::V4(_))
}
pub const fn is_ipv6(&self) -> bool {
matches!(self, IpNet::V6(_))
}
}
impl From<Ipv4Net> for IpNet {
fn from(n: Ipv4Net) -> IpNet {
IpNet::V4(n)
}
}
impl From<Ipv6Net> for IpNet {
fn from(n: Ipv6Net) -> IpNet {
IpNet::V6(n)
}
}
impl std::fmt::Display for IpNet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpNet::V4(inner) => write!(f, "{inner}"),
IpNet::V6(inner) => write!(f, "{inner}"),
}
}
}
impl std::str::FromStr for IpNet {
type Err = IpNetParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((addr_str, prefix_str)) = s.split_once('/') else {
return Err(IpNetParseError::NoPrefix);
};
let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
IpNet::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for IpNet {
fn schema_name() -> String {
"IpNet".to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
use crate::schema_util::label_schema;
schemars::schema::SchemaObject {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
one_of: Some(vec![
label_schema("v4", gen.subschema_for::<Ipv4Net>()),
label_schema("v6", gen.subschema_for::<Ipv6Net>()),
]),
..Default::default()
})),
extensions: crate::schema_util::extension("IpNet", "0.1.0"),
..Default::default()
}
.into()
}
}
pub const IPV4_NET_WIDTH_MAX: u8 = 32;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ipv4Net {
addr: Ipv4Addr,
width: u8,
}
impl Ipv4Net {
pub fn new(addr: Ipv4Addr, width: u8) -> Result<Self, IpNetPrefixError> {
if width > IPV4_NET_WIDTH_MAX {
Err(IpNetPrefixError(width))
} else {
Ok(Self { addr, width })
}
}
pub const fn new_unchecked(addr: Ipv4Addr, width: u8) -> Self {
Self { addr, width }
}
pub const fn host_net(addr: Ipv4Addr) -> Self {
Self {
addr,
width: IPV4_NET_WIDTH_MAX,
}
}
pub const fn addr(&self) -> Ipv4Addr {
self.addr
}
pub const fn width(&self) -> u8 {
self.width
}
pub(crate) fn mask(&self) -> u32 {
u32::MAX
.checked_shl((IPV4_NET_WIDTH_MAX - self.width) as u32)
.unwrap_or(0)
}
pub fn mask_addr(&self) -> Ipv4Addr {
Ipv4Addr::from(self.mask())
}
pub const fn is_host_net(&self) -> bool {
self.width == IPV4_NET_WIDTH_MAX
}
pub fn is_network_address(&self) -> bool {
self.addr == self.prefix()
}
pub const fn is_multicast(&self) -> bool {
self.addr.is_multicast()
}
pub const fn is_admin_scoped_multicast(&self) -> bool {
self.addr.octets()[0] == 239
}
pub const fn is_local_multicast(&self) -> bool {
let octets = self.addr.octets();
octets[0] == 239 && octets[1] == 255
}
pub const fn is_org_local_multicast(&self) -> bool {
let octets = self.addr.octets();
octets[0] == 239 && (octets[1] >= 192 && octets[1] <= 195)
}
pub const fn is_loopback(&self) -> bool {
self.addr.is_loopback()
}
pub const fn size(&self) -> Option<u32> {
1u32.checked_shl((IPV4_NET_WIDTH_MAX - self.width) as u32)
}
pub fn prefix(&self) -> Ipv4Addr {
self.first_addr()
}
pub fn network(&self) -> Option<Ipv4Addr> {
(self.width < 31).then(|| self.first_addr())
}
pub fn broadcast(&self) -> Option<Ipv4Addr> {
(self.width < 31).then(|| self.last_addr())
}
pub fn first_addr(&self) -> Ipv4Addr {
let addr: u32 = self.addr.into();
Ipv4Addr::from(addr & self.mask())
}
pub fn last_addr(&self) -> Ipv4Addr {
let addr: u32 = self.addr.into();
Ipv4Addr::from(addr | !self.mask())
}
pub fn first_host(&self) -> Ipv4Addr {
let mask = self.mask();
let addr: u32 = self.addr.into();
let first = addr & mask;
if self.width == 31 || self.width == 32 {
Ipv4Addr::from(first)
} else {
Ipv4Addr::from(first + 1)
}
}
pub fn last_host(&self) -> Ipv4Addr {
let mask = self.mask();
let addr: u32 = self.addr.into();
let last = addr | !mask;
if self.width == 31 || self.width == 32 {
Ipv4Addr::from(last)
} else {
Ipv4Addr::from(last - 1)
}
}
pub fn contains(&self, other: Ipv4Addr) -> bool {
let mask = self.mask();
let addr: u32 = self.addr.into();
let other: u32 = other.into();
(addr & mask) == (other & mask)
}
pub fn nth(&self, n: usize) -> Option<Ipv4Addr> {
let addr: u32 = self.addr.into();
let nth = addr.checked_add(n.try_into().ok()?)?;
(nth <= self.last_addr().into()).then_some(nth.into())
}
pub fn addr_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
Ipv4NetIter {
next: Some(self.first_addr().into()),
last: self.last_addr().into(),
}
}
pub fn host_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
Ipv4NetIter {
next: Some(self.first_host().into()),
last: self.last_host().into(),
}
}
pub fn is_subnet_of(&self, other: &Self) -> bool {
other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
}
pub fn is_supernet_of(&self, other: &Self) -> bool {
other.is_subnet_of(self)
}
pub fn overlaps(&self, other: &Self) -> bool {
let (parent, child) = if self.width <= other.width {
(self, other)
} else {
(other, self)
};
child.is_subnet_of(parent)
}
}
impl std::fmt::Display for Ipv4Net {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", &self.addr, self.width)
}
}
impl std::str::FromStr for Ipv4Net {
type Err = IpNetParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((addr_str, prefix_str)) = s.split_once('/') else {
return Err(IpNetParseError::NoPrefix);
};
let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
Ipv4Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv4Net {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(<D::Error as serde::de::Error>::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv4Net {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
#[cfg(feature = "schemars")]
const IPV4_NET_REGEX: &str = concat!(
r#"^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}"#,
r#"([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"#,
r#"/([0-9]|1[0-9]|2[0-9]|3[0-2])$"#,
);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Ipv4Net {
fn schema_name() -> String {
"Ipv4Net".to_string()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(Box::new(schemars::schema::Metadata {
title: Some("An IPv4 subnet".to_string()),
description: Some("An IPv4 subnet, including prefix and prefix length".to_string()),
examples: vec!["192.168.1.0/24".into()],
..Default::default()
})),
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(IPV4_NET_REGEX.to_string()),
..Default::default()
})),
extensions: crate::schema_util::extension("Ipv4Net", "0.1.0"),
..Default::default()
}
.into()
}
}
pub const IPV6_NET_WIDTH_MAX: u8 = 128;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum MulticastScopeV6 {
InterfaceLocal = 0x1,
LinkLocal = 0x2,
AdminLocal = 0x4,
SiteLocal = 0x5,
OrganizationLocal = 0x8,
Global = 0xE,
}
impl MulticastScopeV6 {
pub const fn is_admin_scoped_multicast(&self) -> bool {
matches!(
self,
MulticastScopeV6::AdminLocal
| MulticastScopeV6::SiteLocal
| MulticastScopeV6::OrganizationLocal
)
}
pub const fn from_u8(scope: u8) -> Option<Self> {
match scope {
0x1 => Some(MulticastScopeV6::InterfaceLocal),
0x2 => Some(MulticastScopeV6::LinkLocal),
0x4 => Some(MulticastScopeV6::AdminLocal),
0x5 => Some(MulticastScopeV6::SiteLocal),
0x8 => Some(MulticastScopeV6::OrganizationLocal),
0xE => Some(MulticastScopeV6::Global),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ipv6Net {
addr: Ipv6Addr,
width: u8,
}
impl Ipv6Net {
pub fn new(addr: Ipv6Addr, width: u8) -> Result<Self, IpNetPrefixError> {
if width > IPV6_NET_WIDTH_MAX {
Err(IpNetPrefixError(width))
} else {
Ok(Self { addr, width })
}
}
pub const fn new_unchecked(addr: Ipv6Addr, width: u8) -> Self {
Self { addr, width }
}
pub const fn host_net(addr: Ipv6Addr) -> Self {
Self {
addr,
width: IPV6_NET_WIDTH_MAX,
}
}
pub const fn addr(&self) -> Ipv6Addr {
self.addr
}
pub const fn width(&self) -> u8 {
self.width
}
pub(crate) fn mask(&self) -> u128 {
u128::MAX
.checked_shl((IPV6_NET_WIDTH_MAX - self.width) as u32)
.unwrap_or(0)
}
pub fn mask_addr(&self) -> Ipv6Addr {
Ipv6Addr::from(self.mask())
}
pub const fn is_host_net(&self) -> bool {
self.width == IPV6_NET_WIDTH_MAX
}
pub fn is_network_address(&self) -> bool {
self.addr == self.prefix()
}
pub const fn is_multicast(&self) -> bool {
self.addr.is_multicast()
}
pub const fn multicast_scope(&self) -> Option<MulticastScopeV6> {
if !self.addr.is_multicast() {
return None;
}
let segments = self.addr.segments();
let scope = (segments[0] & 0x000F) as u8;
MulticastScopeV6::from_u8(scope)
}
pub const fn is_admin_scoped_multicast(&self) -> bool {
match self.multicast_scope() {
Some(scope) => scope.is_admin_scoped_multicast(),
None => false,
}
}
pub const fn is_admin_local_multicast(&self) -> bool {
matches!(self.multicast_scope(), Some(MulticastScopeV6::AdminLocal))
}
pub const fn is_site_local_multicast(&self) -> bool {
matches!(self.multicast_scope(), Some(MulticastScopeV6::SiteLocal))
}
pub const fn is_org_local_multicast(&self) -> bool {
matches!(
self.multicast_scope(),
Some(MulticastScopeV6::OrganizationLocal)
)
}
pub const fn is_loopback(&self) -> bool {
self.addr.is_loopback()
}
pub const fn size(&self) -> Option<u128> {
1u128.checked_shl((IPV6_NET_WIDTH_MAX - self.width) as u32)
}
pub fn prefix(&self) -> Ipv6Addr {
self.first_addr()
}
pub const fn is_unique_local(&self) -> bool {
self.addr.is_unique_local()
}
pub fn first_addr(&self) -> Ipv6Addr {
let addr: u128 = self.addr.into();
Ipv6Addr::from(addr & self.mask())
}
pub fn last_addr(&self) -> Ipv6Addr {
let addr: u128 = self.addr.into();
Ipv6Addr::from(addr | !self.mask())
}
pub fn iter(&self) -> impl Iterator<Item = Ipv6Addr> {
Ipv6NetIter {
next: Some(self.first_addr().into()),
last: self.last_addr().into(),
}
}
pub fn contains(&self, other: Ipv6Addr) -> bool {
let mask = self.mask();
let addr: u128 = self.addr.into();
let other: u128 = other.into();
(addr & mask) == (other & mask)
}
pub fn nth(&self, n: u128) -> Option<Ipv6Addr> {
let addr: u128 = self.addr.into();
let nth = addr.checked_add(n)?;
(nth <= self.last_addr().into()).then_some(nth.into())
}
pub fn is_subnet_of(&self, other: &Self) -> bool {
other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
}
pub fn is_supernet_of(&self, other: &Self) -> bool {
other.is_subnet_of(self)
}
pub fn overlaps(&self, other: &Self) -> bool {
let (parent, child) = if self.width <= other.width {
(self, other)
} else {
(other, self)
};
child.is_subnet_of(parent)
}
}
impl std::fmt::Display for Ipv6Net {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", &self.addr, self.width)
}
}
impl std::str::FromStr for Ipv6Net {
type Err = IpNetParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((addr_str, prefix_str)) = s.split_once('/') else {
return Err(IpNetParseError::NoPrefix);
};
let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
Ipv6Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv6Net {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(<D::Error as serde::de::Error>::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv6Net {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
#[cfg(feature = "schemars")]
const IPV6_NET_REGEX: &str = concat!(
r#"^("#,
r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"#,
r#"([0-9a-fA-F]{1,4}:){1,7}:|"#,
r#"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"#,
r#"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"#,
r#"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"#,
r#"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"#,
r#"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"#,
r#"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"#,
r#":((:[0-9a-fA-F]{1,4}){1,7}|:)|"#,
r#"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"#,
r#"::(ffff(:0{1,4}){0,1}:){0,1}"#,
r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"#,
r#"([0-9a-fA-F]{1,4}:){1,4}:"#,
r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#,
r#")\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"#,
);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Ipv6Net {
fn schema_name() -> String {
"Ipv6Net".to_string()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(Box::new(schemars::schema::Metadata {
title: Some("An IPv6 subnet".to_string()),
description: Some("An IPv6 subnet, including prefix and subnet mask".to_string()),
examples: vec!["fd12:3456::/64".into()],
..Default::default()
})),
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(IPV6_NET_REGEX.to_string()),
..Default::default()
})),
extensions: crate::schema_util::extension("Ipv6Net", "0.1.0"),
..Default::default()
}
.into()
}
}
pub struct Ipv4NetIter {
next: Option<u32>,
last: u32,
}
impl Iterator for Ipv4NetIter {
type Item = Ipv4Addr;
fn next(&mut self) -> Option<Self::Item> {
let next = self.next?;
if next == self.last {
self.next = None;
} else {
self.next = Some(next + 1)
}
Some(next.into())
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let next = self.next?;
let nth = next.checked_add(n as u32)?;
self.next = (nth <= self.last).then_some(nth);
self.next()
}
}
pub struct Ipv6NetIter {
next: Option<u128>,
last: u128,
}
impl Iterator for Ipv6NetIter {
type Item = Ipv6Addr;
fn next(&mut self) -> Option<Self::Item> {
let next = self.next?;
if next == self.last {
self.next = None;
} else {
self.next = Some(next + 1)
}
Some(next.into())
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let next = self.next?;
let nth = next.checked_add(n as u128)?;
self.next = (nth <= self.last).then_some(nth);
self.next()
}
}
#[cfg(feature = "ipnetwork")]
mod ipnetwork_feature {
use super::*;
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
impl From<IpNetwork> for IpNet {
fn from(value: IpNetwork) -> Self {
match value {
IpNetwork::V4(net) => Self::V4(net.into()),
IpNetwork::V6(net) => Self::V6(net.into()),
}
}
}
impl From<IpNet> for IpNetwork {
fn from(value: IpNet) -> Self {
match value {
IpNet::V4(net) => Self::V4(net.into()),
IpNet::V6(net) => Self::V6(net.into()),
}
}
}
impl From<Ipv4Network> for Ipv4Net {
fn from(value: Ipv4Network) -> Self {
Self {
addr: value.ip(),
width: value.prefix(),
}
}
}
impl From<Ipv4Net> for Ipv4Network {
fn from(value: Ipv4Net) -> Self {
Self::new(value.addr, value.width).unwrap()
}
}
impl From<Ipv6Network> for Ipv6Net {
fn from(value: Ipv6Network) -> Self {
Self {
addr: value.ip(),
width: value.prefix(),
}
}
}
impl From<Ipv6Net> for Ipv6Network {
fn from(value: Ipv6Net) -> Self {
Self::new(value.addr, value.width).unwrap()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ipv6_regex() {
let re = regress::Regex::new(IPV6_NET_REGEX).unwrap();
for case in [
"1:2:3:4:5:6:7:8",
"1:a:2:b:3:c:4:d",
"1::",
"::1",
"::",
"1::3:4:5:6:7:8",
"1:2::4:5:6:7:8",
"1:2:3::5:6:7:8",
"1:2:3:4::6:7:8",
"1:2:3:4:5::7:8",
"1:2:3:4:5:6::8",
"1:2:3:4:5:6:7::",
"2001::",
"fd00::",
"::100:1",
"fd12:3456::",
] {
for prefix in 0..=128 {
let net = format!("{case}/{prefix}");
assert!(
re.find(&net).is_some(),
"Expected to match IPv6 case: {}",
prefix,
);
}
}
}
#[test]
fn test_ipv4_net_operations() {
let x: IpNet = "0.0.0.0/0".parse().unwrap();
assert_eq!(x, IpNet::V4("0.0.0.0/0".parse().unwrap()));
}
#[test]
fn test_ipnet_serde() {
let net_str = "fd00:2::/32";
let net: IpNet = net_str.parse().unwrap();
let ser = serde_json::to_string(&net).unwrap();
assert_eq!(format!(r#""{}""#, net_str), ser);
let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
assert_eq!(net, net_des);
let net_str = "fd00:47::1/64";
let net: IpNet = net_str.parse().unwrap();
let ser = serde_json::to_string(&net).unwrap();
assert_eq!(format!(r#""{}""#, net_str), ser);
let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
assert_eq!(net, net_des);
let net_str = "192.168.1.1/16";
let net: IpNet = net_str.parse().unwrap();
let ser = serde_json::to_string(&net).unwrap();
assert_eq!(format!(r#""{}""#, net_str), ser);
let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
assert_eq!(net, net_des);
let net_str = "0.0.0.0/0";
let net: IpNet = net_str.parse().unwrap();
let ser = serde_json::to_string(&net).unwrap();
assert_eq!(format!(r#""{}""#, net_str), ser);
let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
assert_eq!(net, net_des);
}
#[test]
fn test_ipnet_size() {
let net = Ipv4Net::host_net("1.2.3.4".parse().unwrap());
assert_eq!(net.size(), Some(1));
assert_eq!(net.width(), 32);
assert_eq!(net.mask(), 0xffff_ffff);
assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0xff));
let net = Ipv4Net::new("1.2.3.4".parse().unwrap(), 24).unwrap();
assert_eq!(net.size(), Some(256));
assert_eq!(net.width(), 24);
assert_eq!(net.mask(), 0xffff_ff00);
assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0));
let net = Ipv4Net::new("0.0.0.0".parse().unwrap(), 0).unwrap();
assert_eq!(net.size(), None);
assert_eq!(net.width(), 0);
assert_eq!(net.mask(), 0);
assert_eq!(net.mask_addr(), Ipv4Addr::new(0, 0, 0, 0));
let net = Ipv6Net::host_net("fd00:47::1".parse().unwrap());
assert_eq!(net.size(), Some(1));
assert_eq!(net.width(), 128);
assert_eq!(net.mask(), 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff);
assert_eq!(
net.mask_addr(),
Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)
);
let net = Ipv6Net::new("fd00:47::1".parse().unwrap(), 56).unwrap();
assert_eq!(net.size(), Some(0x0000_0000_0000_0100_0000_0000_0000_0000));
assert_eq!(net.width(), 56);
assert_eq!(net.mask(), 0xffff_ffff_ffff_ff00_0000_0000_0000_0000);
assert_eq!(
net.mask_addr(),
Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xff00, 0, 0, 0, 0)
);
}
#[test]
fn test_iter() {
let ipnet = Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0).unwrap();
let actual = ipnet.addr_iter().take(5).collect::<Vec<_>>();
let expected = (0..5).map(Ipv4Addr::from).collect::<Vec<_>>();
assert_eq!(actual, expected);
let actual = ipnet.addr_iter().skip(5).take(10).collect::<Vec<_>>();
let expected = (5..15).map(Ipv4Addr::from).collect::<Vec<_>>();
assert_eq!(actual, expected);
let ipnet = Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0).unwrap();
let actual = ipnet.iter().take(5).collect::<Vec<_>>();
let expected = (0..5).map(Ipv6Addr::from).collect::<Vec<_>>();
assert_eq!(actual, expected);
let actual = ipnet.iter().skip(5).take(10).collect::<Vec<_>>();
let expected = (5..15).map(Ipv6Addr::from).collect::<Vec<_>>();
assert_eq!(actual, expected);
}
#[test]
fn test_contains() {
let default_v4: IpNet = "0.0.0.0/0".parse().unwrap();
let private_v4: IpNet = "10.0.0.0/8".parse().unwrap();
let privater_v4_c0: IpNet = "10.0.0.0/9".parse().unwrap();
let privater_v4_c1: IpNet = "10.128.0.0/9".parse().unwrap();
assert!(private_v4.is_subnet_of(&default_v4));
assert!(privater_v4_c0.is_subnet_of(&default_v4));
assert!(privater_v4_c0.is_subnet_of(&private_v4));
assert!(privater_v4_c1.is_subnet_of(&default_v4));
assert!(privater_v4_c1.is_subnet_of(&private_v4));
assert!(private_v4.is_supernet_of(&privater_v4_c0));
assert!(private_v4.is_supernet_of(&privater_v4_c1));
assert!(!privater_v4_c0.overlaps(&privater_v4_c1));
assert!(!privater_v4_c1.overlaps(&privater_v4_c0));
assert!(privater_v4_c0.overlaps(&privater_v4_c0));
assert!(privater_v4_c0.overlaps(&private_v4));
assert!(private_v4.overlaps(&privater_v4_c0));
let child_ip: IpNet = "10.128.20.20/16".parse().unwrap();
assert!(child_ip.is_subnet_of(&privater_v4_c1));
assert!(!child_ip.is_subnet_of(&privater_v4_c0));
}
#[test]
fn test_is_network_addr() {
let v4_net: IpNet = "127.0.0.0/8".parse().unwrap();
let v4_host: IpNet = "127.0.0.1/8".parse().unwrap();
let v6_net: IpNet = "fd00:1234:5678::/48".parse().unwrap();
let v6_host: IpNet = "fd00:1234:5678::7777/48".parse().unwrap();
assert!(v4_net.is_network_address());
assert!(!v4_host.is_network_address());
assert!(v6_net.is_network_address());
assert!(!v6_host.is_network_address());
let two_addr: IpNet = "10.7.7.64/31".parse().unwrap();
let one_addr: IpNet = "10.7.7.64/32".parse().unwrap();
assert!(two_addr.is_network_address());
assert!(one_addr.is_network_address());
let unspec: IpNet = "0.0.0.0/0".parse().unwrap();
assert!(unspec.is_network_address());
}
#[test]
fn test_is_multicast_with_scopes() {
let v4_mcast: IpNet = "224.0.0.1/32".parse().unwrap();
let v4_not_mcast: IpNet = "192.168.1.1/24".parse().unwrap();
assert!(v4_mcast.is_multicast());
assert!(!v4_not_mcast.is_multicast());
let v6_mcast: IpNet = "ff02::1/128".parse().unwrap();
let v6_not_mcast: IpNet = "2001:db8::1/64".parse().unwrap();
assert!(v6_mcast.is_multicast());
assert!(!v6_not_mcast.is_multicast());
let v6_site_local_mcast: IpNet = "ff05::1/128".parse().unwrap();
let v6_org_local_mcast: IpNet = "ff08::1/128".parse().unwrap();
let v6_admin_local_mcast: IpNet = "ff04::1/128".parse().unwrap();
let v6_link_local_mcast: IpNet = "ff02::1/128".parse().unwrap();
assert!(v6_admin_local_mcast.is_admin_scoped_multicast());
assert!(v6_site_local_mcast.is_admin_scoped_multicast());
assert!(v6_org_local_mcast.is_admin_scoped_multicast());
assert!(!v6_link_local_mcast.is_admin_scoped_multicast()); assert!(!v6_not_mcast.is_admin_scoped_multicast());
let v4_admin_scoped: IpNet = "239.0.0.1/32".parse().unwrap();
let v4_admin_scoped_range: IpNet = "239.192.0.0/16".parse().unwrap();
assert!(v4_admin_scoped.is_admin_scoped_multicast());
assert!(v4_admin_scoped_range.is_admin_scoped_multicast());
assert!(!v4_mcast.is_admin_scoped_multicast());
assert!(!v6_site_local_mcast.is_admin_local_multicast());
assert!(!v6_org_local_mcast.is_admin_local_multicast());
assert!(v6_admin_local_mcast.is_admin_local_multicast());
assert!(!v6_link_local_mcast.is_admin_local_multicast());
assert!(!v6_not_mcast.is_admin_local_multicast());
assert!(!v4_mcast.is_admin_local_multicast()); assert!(!v4_admin_scoped.is_admin_local_multicast());
let v4_local_mcast: IpNet = "239.255.0.1/32".parse().unwrap();
let v4_local_mcast_range: IpNet = "239.255.128.0/24".parse().unwrap();
let v4_not_local: IpNet = "239.254.255.255/32".parse().unwrap();
assert!(v4_local_mcast.is_local_multicast());
assert!(v4_local_mcast_range.is_local_multicast());
assert!(!v4_not_local.is_local_multicast());
assert!(!v4_mcast.is_local_multicast()); assert!(!v6_admin_local_mcast.is_local_multicast());
assert!(v6_site_local_mcast.is_site_local_multicast());
assert!(!v6_org_local_mcast.is_site_local_multicast());
assert!(!v6_admin_local_mcast.is_site_local_multicast());
assert!(!v6_link_local_mcast.is_site_local_multicast());
assert!(!v6_not_mcast.is_site_local_multicast());
assert!(!v4_mcast.is_site_local_multicast());
assert!(!v6_site_local_mcast.is_org_local_multicast());
assert!(v6_org_local_mcast.is_org_local_multicast());
assert!(!v6_admin_local_mcast.is_org_local_multicast());
assert!(!v6_link_local_mcast.is_org_local_multicast());
assert!(!v6_not_mcast.is_org_local_multicast());
let v4_org_local_mcast: IpNet = "239.192.0.1/32".parse().unwrap();
let v4_org_local_mcast_end: IpNet = "239.195.255.255/32".parse().unwrap();
let v4_not_org_local: IpNet = "239.196.0.0/32".parse().unwrap();
assert!(v4_org_local_mcast.is_org_local_multicast());
assert!(v4_org_local_mcast_end.is_org_local_multicast());
assert!(!v4_not_org_local.is_org_local_multicast());
assert!(!v4_mcast.is_org_local_multicast()); }
#[test]
fn test_ipv6_multicast_scope() {
use MulticastScopeV6::*;
let link_local: Ipv6Net = "ff02::1/128".parse().unwrap();
let admin_local: Ipv6Net = "ff04::1/128".parse().unwrap();
let site_local: Ipv6Net = "ff05::1/128".parse().unwrap();
let org_local: Ipv6Net = "ff08::1/128".parse().unwrap();
let global: Ipv6Net = "ff0e::1/128".parse().unwrap();
let not_mcast: Ipv6Net = "2001:db8::1/64".parse().unwrap();
assert_eq!(link_local.multicast_scope(), Some(LinkLocal));
assert_eq!(admin_local.multicast_scope(), Some(AdminLocal));
assert_eq!(site_local.multicast_scope(), Some(SiteLocal));
assert_eq!(org_local.multicast_scope(), Some(OrganizationLocal));
assert_eq!(global.multicast_scope(), Some(Global));
assert_eq!(not_mcast.multicast_scope(), None);
assert!(!LinkLocal.is_admin_scoped_multicast());
assert!(AdminLocal.is_admin_scoped_multicast());
assert!(SiteLocal.is_admin_scoped_multicast());
assert!(OrganizationLocal.is_admin_scoped_multicast());
assert!(!Global.is_admin_scoped_multicast());
}
}