* [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit @ 2025-04-01 14:52 Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type Stefan Hanreich ` (3 more replies) 0 siblings, 4 replies; 11+ messages in thread From: Stefan Hanreich @ 2025-04-01 14:52 UTC (permalink / raw) To: pve-devel This commit moves some IP address and MAC address types from proxmox-ve-config to proxmox, so they can be used re-used across our code base. The code in this commit is mostly the same as in proxmox-ve-config ('bc9253d8'), but I have made a few changes: * Added additional documentation to some of the structs and their methods * Converted all error types to thiserror Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> --- Cargo.toml | 1 + proxmox-network-types/Cargo.toml | 18 + proxmox-network-types/debian/changelog | 5 + proxmox-network-types/debian/control | 39 + proxmox-network-types/debian/copyright | 18 + proxmox-network-types/debian/debcargo.toml | 7 + proxmox-network-types/src/ip_address.rs | 1404 ++++++++++++++++++++ proxmox-network-types/src/lib.rs | 2 + proxmox-network-types/src/mac_address.rs | 120 ++ 9 files changed, 1614 insertions(+) create mode 100644 proxmox-network-types/Cargo.toml create mode 100644 proxmox-network-types/debian/changelog create mode 100644 proxmox-network-types/debian/control create mode 100644 proxmox-network-types/debian/copyright create mode 100644 proxmox-network-types/debian/debcargo.toml create mode 100644 proxmox-network-types/src/ip_address.rs create mode 100644 proxmox-network-types/src/lib.rs create mode 100644 proxmox-network-types/src/mac_address.rs diff --git a/Cargo.toml b/Cargo.toml index 268b39eb..2ca0ea61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "proxmox-login", "proxmox-metrics", "proxmox-network-api", + "proxmox-network-types", "proxmox-notify", "proxmox-openid", "proxmox-product-config", diff --git a/proxmox-network-types/Cargo.toml b/proxmox-network-types/Cargo.toml new file mode 100644 index 00000000..2ac0e96a --- /dev/null +++ b/proxmox-network-types/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "proxmox-network-types" +description = "Rust types for common networking entities" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +exclude.workspace = true +rust-version.workspace = true + +[dependencies] +serde = { workspace = true, features = [ "derive" ] } +serde_with = "3.8.1" +thiserror = "2" + +[features] +default = [] diff --git a/proxmox-network-types/debian/changelog b/proxmox-network-types/debian/changelog new file mode 100644 index 00000000..78cb0ab0 --- /dev/null +++ b/proxmox-network-types/debian/changelog @@ -0,0 +1,5 @@ +rust-proxmox-network-types (0.1.0-1) unstable; urgency=medium + + * Initial release. + + -- Proxmox Support Team <support@proxmox.com> Mon, 03 Jun 2024 10:51:11 +0200 diff --git a/proxmox-network-types/debian/control b/proxmox-network-types/debian/control new file mode 100644 index 00000000..bc0b2aa4 --- /dev/null +++ b/proxmox-network-types/debian/control @@ -0,0 +1,39 @@ +Source: rust-proxmox-network-types +Section: rust +Priority: optional +Build-Depends: debhelper-compat (= 13), + dh-sequence-cargo +Build-Depends-Arch: cargo:native <!nocheck>, + rustc:native (>= 1.82) <!nocheck>, + libstd-rust-dev <!nocheck>, + librust-serde-1+default-dev <!nocheck>, + librust-serde-1+derive-dev <!nocheck>, + librust-serde-with-3+default-dev (>= 3.8.1-~~) <!nocheck>, + librust-thiserror-2+default-dev <!nocheck> +Maintainer: Proxmox Support Team <support@proxmox.com> +Standards-Version: 4.7.0 +Vcs-Git: git://git.proxmox.com/git/proxmox.git +Vcs-Browser: https://git.proxmox.com/?p=proxmox.git +Homepage: https://proxmox.com +X-Cargo-Crate: proxmox-network-types +Rules-Requires-Root: no + +Package: librust-proxmox-network-types-dev +Architecture: any +Multi-Arch: same +Depends: + ${misc:Depends}, + librust-serde-1+default-dev, + librust-serde-1+derive-dev, + librust-serde-with-3+default-dev (>= 3.8.1-~~), + librust-thiserror-2+default-dev +Provides: + librust-proxmox-network-types+default-dev (= ${binary:Version}), + librust-proxmox-network-types-0-dev (= ${binary:Version}), + librust-proxmox-network-types-0+default-dev (= ${binary:Version}), + librust-proxmox-network-types-0.1-dev (= ${binary:Version}), + librust-proxmox-network-types-0.1+default-dev (= ${binary:Version}), + librust-proxmox-network-types-0.1.0-dev (= ${binary:Version}), + librust-proxmox-network-types-0.1.0+default-dev (= ${binary:Version}) +Description: Rust types for common networking entities - Rust source code + Source code for Debianized Rust crate "proxmox-network-types" diff --git a/proxmox-network-types/debian/copyright b/proxmox-network-types/debian/copyright new file mode 100644 index 00000000..1ea8a56b --- /dev/null +++ b/proxmox-network-types/debian/copyright @@ -0,0 +1,18 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: + * +Copyright: 2019 - 2025 Proxmox Server Solutions GmbH <support@proxmox.com> +License: AGPL-3.0-or-later + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) any + later version. + . + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + details. + . + You should have received a copy of the GNU Affero General Public License along + with this program. If not, see <https://www.gnu.org/licenses/>. diff --git a/proxmox-network-types/debian/debcargo.toml b/proxmox-network-types/debian/debcargo.toml new file mode 100644 index 00000000..b7864cdb --- /dev/null +++ b/proxmox-network-types/debian/debcargo.toml @@ -0,0 +1,7 @@ +overlay = "." +crate_src_path = ".." +maintainer = "Proxmox Support Team <support@proxmox.com>" + +[source] +vcs_git = "git://git.proxmox.com/git/proxmox.git" +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" diff --git a/proxmox-network-types/src/ip_address.rs b/proxmox-network-types/src/ip_address.rs new file mode 100644 index 00000000..3d27d38f --- /dev/null +++ b/proxmox-network-types/src/ip_address.rs @@ -0,0 +1,1404 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, AddrParseError}; + +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use thiserror::Error; + +/// The family (v4 or v6) of an IP address or CIDR prefix +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Family { + V4, + V6, +} + +impl Family { + pub fn is_ipv4(&self) -> bool { + *self == Self::V4 + } + + pub fn is_ipv6(&self) -> bool { + *self == Self::V6 + } +} + +impl std::fmt::Display for Family { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Family::V4 => f.write_str("Ipv4"), + Family::V6 => f.write_str("Ipv6"), + } + } +} + +#[derive(Error, Debug)] +pub enum CidrError { + #[error("invalid netmask")] + InvalidNetmask, + #[error("invalid IP address")] + InvalidAddress(#[from] AddrParseError), +} + +/// Represents either an [`Ipv4Cidr`] or [`Ipv6Cidr`] CIDR prefix +#[derive( + Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, +)] +pub enum Cidr { + Ipv4(Ipv4Cidr), + Ipv6(Ipv6Cidr), +} + +impl Cidr { + pub fn new_v4(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, CidrError> { + Ok(Cidr::Ipv4(Ipv4Cidr::new(addr, mask)?)) + } + + pub fn new_v6(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, CidrError> { + Ok(Cidr::Ipv6(Ipv6Cidr::new(addr, mask)?)) + } + + /// which [`Family`] this CIDr belongs to + pub const fn family(&self) -> Family { + match self { + Cidr::Ipv4(_) => Family::V4, + Cidr::Ipv6(_) => Family::V6, + } + } + + pub fn is_ipv4(&self) -> bool { + matches!(self, Cidr::Ipv4(_)) + } + + pub fn is_ipv6(&self) -> bool { + matches!(self, Cidr::Ipv6(_)) + } + + /// Whether a given IP address is contained in this [`Cidr`] + /// + /// This only works if both [`IpAddr`] are in the same family, otherwise the function returns + /// false. + pub fn contains_address(&self, ip: &IpAddr) -> bool { + match (self, ip) { + (Cidr::Ipv4(cidr), IpAddr::V4(ip)) => cidr.contains_address(ip), + (Cidr::Ipv6(cidr), IpAddr::V6(ip)) => cidr.contains_address(ip), + _ => false, + } + } +} + +impl std::fmt::Display for Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Ipv4(ip) => f.write_str(ip.to_string().as_str()), + Self::Ipv6(ip) => f.write_str(ip.to_string().as_str()), + } + } +} + +impl std::str::FromStr for Cidr { + type Err = CidrError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if let Ok(ip) = s.parse::<Ipv4Cidr>() { + return Ok(Cidr::Ipv4(ip)); + } + + Ok(Cidr::Ipv6(s.parse()?)) + } +} + +impl From<Ipv4Cidr> for Cidr { + fn from(cidr: Ipv4Cidr) -> Self { + Cidr::Ipv4(cidr) + } +} + +impl From<Ipv6Cidr> for Cidr { + fn from(cidr: Ipv6Cidr) -> Self { + Cidr::Ipv6(cidr) + } +} + +impl From<IpAddr> for Cidr { + fn from(value: IpAddr) -> Self { + match value { + IpAddr::V4(addr) => Ipv4Cidr::from(addr).into(), + IpAddr::V6(addr) => Ipv6Cidr::from(addr).into(), + } + } +} + +const IPV4_LENGTH: u8 = 32; + +#[derive( + SerializeDisplay, DeserializeFromStr, Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, +)] +pub struct Ipv4Cidr { + addr: Ipv4Addr, + mask: u8, +} + +impl Ipv4Cidr { + pub fn new(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, CidrError> { + if mask > IPV4_LENGTH { + return Err(CidrError::InvalidNetmask); + } + + Ok(Self { + addr: addr.into(), + mask, + }) + } + + pub fn contains_address(&self, other: &Ipv4Addr) -> bool { + let bits = u32::from_be_bytes(self.addr.octets()); + let other_bits = u32::from_be_bytes(other.octets()); + + let shift_amount: u32 = IPV4_LENGTH.saturating_sub(self.mask).into(); + + bits.checked_shr(shift_amount).unwrap_or(0) + == other_bits.checked_shr(shift_amount).unwrap_or(0) + } + + pub fn address(&self) -> &Ipv4Addr { + &self.addr + } + + pub fn mask(&self) -> u8 { + self.mask + } +} + +impl<T: Into<Ipv4Addr>> From<T> for Ipv4Cidr { + fn from(value: T) -> Self { + Self { + addr: value.into(), + mask: 32, + } + } +} + +impl std::str::FromStr for Ipv4Cidr { + type Err = CidrError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s.find('/') { + None => Self { + addr: s.parse()?, + mask: 32, + }, + Some(pos) => { + let mask: u8 = s[(pos + 1)..] + .parse() + .map_err(|_| CidrError::InvalidNetmask)?; + + Self::new(s[..pos].parse::<Ipv4Addr>()?, mask)? + } + }) + } +} + +impl std::fmt::Display for Ipv4Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}/{}", &self.addr, self.mask) + } +} + +const IPV6_LENGTH: u8 = 128; + +#[derive( + SerializeDisplay, DeserializeFromStr, Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, +)] +pub struct Ipv6Cidr { + addr: Ipv6Addr, + mask: u8, +} + +impl Ipv6Cidr { + pub fn new(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, CidrError> { + if mask > IPV6_LENGTH { + return Err(CidrError::InvalidNetmask); + } + + Ok(Self { + addr: addr.into(), + mask, + }) + } + + pub fn contains_address(&self, other: &Ipv6Addr) -> bool { + let bits = u128::from_be_bytes(self.addr.octets()); + let other_bits = u128::from_be_bytes(other.octets()); + + let shift_amount: u32 = IPV6_LENGTH.saturating_sub(self.mask).into(); + + bits.checked_shr(shift_amount).unwrap_or(0) + == other_bits.checked_shr(shift_amount).unwrap_or(0) + } + + pub fn address(&self) -> &Ipv6Addr { + &self.addr + } + + pub fn mask(&self) -> u8 { + self.mask + } +} + +impl std::str::FromStr for Ipv6Cidr { + type Err = CidrError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s.find('/') { + None => Self { + addr: s.parse()?, + mask: 128, + }, + Some(pos) => { + let mask: u8 = s[(pos + 1)..] + .parse() + .map_err(|_| CidrError::InvalidNetmask)?; + + Self::new(s[..pos].parse::<Ipv6Addr>()?, mask)? + } + }) + } +} + +impl std::fmt::Display for Ipv6Cidr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}/{}", &self.addr, self.mask) + } +} + +impl<T: Into<Ipv6Addr>> From<T> for Ipv6Cidr { + fn from(addr: T) -> Self { + Self { + addr: addr.into(), + mask: 128, + } + } +} + +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Error)] +pub enum IpRangeError { + #[error("mismatched ip address families")] + MismatchedFamilies, + #[error("start is greater than last")] + StartGreaterThanLast, + #[error("invalid ip range format")] + InvalidFormat, +} + +/// Represents a range of IPv4 or IPv6 addresses. +/// +/// For more information see [`AddressRange`] +#[derive( + Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, +)] +pub enum IpRange { + V4(AddressRange<Ipv4Addr>), + V6(AddressRange<Ipv6Addr>), +} + +impl IpRange { + /// Returns the family of the IpRange. + pub fn family(&self) -> Family { + match self { + IpRange::V4(_) => Family::V4, + IpRange::V6(_) => Family::V6, + } + } + + /// Creates a new [`IpRange`] from two [`IpAddr`]. + /// + /// # Errors + /// + /// This function will return an error if start and last IP address are not from the same family. + pub fn new(start: impl Into<IpAddr>, last: impl Into<IpAddr>) -> Result<Self, IpRangeError> { + match (start.into(), last.into()) { + (IpAddr::V4(start), IpAddr::V4(last)) => Self::new_v4(start, last), + (IpAddr::V6(start), IpAddr::V6(last)) => Self::new_v6(start, last), + _ => Err(IpRangeError::MismatchedFamilies), + } + } + + /// construct a new IPv4 Range + pub fn new_v4( + start: impl Into<Ipv4Addr>, + last: impl Into<Ipv4Addr>, + ) -> Result<Self, IpRangeError> { + Ok(IpRange::V4(AddressRange::new_v4(start, last)?)) + } + + /// construct a new IPv6 Range + pub fn new_v6( + start: impl Into<Ipv6Addr>, + last: impl Into<Ipv6Addr>, + ) -> Result<Self, IpRangeError> { + Ok(IpRange::V6(AddressRange::new_v6(start, last)?)) + } + + /// Converts an IpRange into the minimal amount of CIDRs. + /// + /// see the concrete implementations of [`AddressRange<Ipv4Addr>`] or [`AddressRange<Ipv6Addr>`] + /// respectively + pub fn to_cidrs(&self) -> Vec<Cidr> { + match self { + IpRange::V4(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), + IpRange::V6(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), + } + } +} + +impl std::str::FromStr for IpRange { + type Err = IpRangeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if let Ok(range) = s.parse() { + return Ok(IpRange::V4(range)); + } + + if let Ok(range) = s.parse() { + return Ok(IpRange::V6(range)); + } + + Err(IpRangeError::InvalidFormat) + } +} + +impl std::fmt::Display for IpRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IpRange::V4(range) => range.fmt(f), + IpRange::V6(range) => range.fmt(f), + } + } +} + +/// Represents a range of IP addresses from start to last. +/// +/// This type is for encapsulation purposes for the [`IpRange`] enum and should be instantiated via +/// that enum. +/// +/// # Invariants +/// +/// * start and last have the same IP address family +/// * start is less than or equal to last +/// +/// # Textual representation +/// +/// Two IP addresses separated by a hyphen, e.g.: `127.0.0.1-127.0.0.255` +#[derive( + Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, +)] +pub struct AddressRange<T> { + start: T, + last: T, +} + +impl AddressRange<Ipv4Addr> { + pub(crate) fn new_v4( + start: impl Into<Ipv4Addr>, + last: impl Into<Ipv4Addr>, + ) -> Result<AddressRange<Ipv4Addr>, IpRangeError> { + let (start, last) = (start.into(), last.into()); + + if start > last { + return Err(IpRangeError::StartGreaterThanLast); + } + + Ok(Self { start, last }) + } + + /// Returns the minimum amount of CIDRs that exactly represent the range + /// + /// The idea behind this algorithm is as follows: + /// + /// Start iterating with current = start of the IP range + /// + /// Find two netmasks + /// * The largest CIDR that the current IP can be the first of + /// * The largest CIDR that *only* contains IPs from current - last + /// + /// Add the smaller of the two CIDRs to our result and current to the first IP that is in + /// the range but not in the CIDR we just added. Proceed until we reached the last of the IP + /// range. + /// + pub fn to_cidrs(&self) -> Vec<Ipv4Cidr> { + let mut cidrs = Vec::new(); + + let mut current = u32::from_be_bytes(self.start.octets()); + let last = u32::from_be_bytes(self.last.octets()); + + if current == last { + // valid Ipv4 since netmask is 32 + cidrs.push(Ipv4Cidr::new(current, 32).unwrap()); + return cidrs; + } + + // special case this, since this is the only possibility of overflow + // when calculating delta_min_mask - makes everything a lot easier + if current == u32::MIN && last == u32::MAX { + // valid Ipv4 since it is `0.0.0.0/0` + cidrs.push(Ipv4Cidr::new(current, 0).unwrap()); + return cidrs; + } + + while current <= last { + // netmask of largest CIDR that current IP can be the first of + // cast is safe, because trailing zeroes can at most be 32 + let current_max_mask = IPV4_LENGTH - (current.trailing_zeros() as u8); + + // netmask of largest CIDR that *only* contains IPs of the remaining range + // is at most 32 due to unwrap_or returning 32 and ilog2 being at most 31 + let delta_min_mask = ((last - current) + 1) // safe due to special case above + .checked_ilog2() // should never occur due to special case, but for good measure + .map(|mask| IPV4_LENGTH - mask as u8) + .unwrap_or(IPV4_LENGTH); + + // at most 32, due to current/delta being at most 32 + let netmask = u8::max(current_max_mask, delta_min_mask); + + // netmask is at most 32, therefore safe to unwrap + cidrs.push(Ipv4Cidr::new(current, netmask).unwrap()); + + let delta = 2u32.saturating_pow((IPV4_LENGTH - netmask).into()); + + if let Some(result) = current.checked_add(delta) { + current = result + } else { + // we reached the end of IP address space + break; + } + } + + cidrs + } +} + +impl AddressRange<Ipv6Addr> { + pub(crate) fn new_v6( + start: impl Into<Ipv6Addr>, + last: impl Into<Ipv6Addr>, + ) -> Result<AddressRange<Ipv6Addr>, IpRangeError> { + let (start, last) = (start.into(), last.into()); + + if start > last { + return Err(IpRangeError::StartGreaterThanLast); + } + + Ok(Self { start, last }) + } + + /// Returns the minimum amount of CIDRs that exactly represent the [`AddressRange`]. + /// + /// This function works analogous to the IPv4 version, please refer to the respective + /// documentation of [`AddressRange<Ipv4Addr>`] + pub fn to_cidrs(&self) -> Vec<Ipv6Cidr> { + let mut cidrs = Vec::new(); + + let mut current = u128::from_be_bytes(self.start.octets()); + let last = u128::from_be_bytes(self.last.octets()); + + if current == last { + // valid Ipv6 since netmask is 128 + cidrs.push(Ipv6Cidr::new(current, 128).unwrap()); + return cidrs; + } + + // special case this, since this is the only possibility of overflow + // when calculating delta_min_mask - makes everything a lot easier + if current == u128::MIN && last == u128::MAX { + // valid Ipv6 since it is `::/0` + cidrs.push(Ipv6Cidr::new(current, 0).unwrap()); + return cidrs; + } + + while current <= last { + // netmask of largest CIDR that current IP can be the first of + // cast is safe, because trailing zeroes can at most be 128 + let current_max_mask = IPV6_LENGTH - (current.trailing_zeros() as u8); + + // netmask of largest CIDR that *only* contains IPs of the remaining range + // is at most 128 due to unwrap_or returning 128 and ilog2 being at most 31 + let delta_min_mask = ((last - current) + 1) // safe due to special case above + .checked_ilog2() // should never occur due to special case, but for good measure + .map(|mask| IPV6_LENGTH - mask as u8) + .unwrap_or(IPV6_LENGTH); + + // at most 128, due to current/delta being at most 128 + let netmask = u8::max(current_max_mask, delta_min_mask); + + // netmask is at most 128, therefore safe to unwrap + cidrs.push(Ipv6Cidr::new(current, netmask).unwrap()); + + let delta = 2u128.saturating_pow((IPV6_LENGTH - netmask).into()); + + if let Some(result) = current.checked_add(delta) { + current = result + } else { + // we reached the end of IP address space + break; + } + } + + cidrs + } +} + +impl<T> AddressRange<T> { + /// the first IP address contained in this [`AddressRange`] + pub fn start(&self) -> &T { + &self.start + } + + /// the last IP address contained in this [`AddressRange`] + pub fn last(&self) -> &T { + &self.last + } +} + +impl std::str::FromStr for AddressRange<Ipv4Addr> { + type Err = IpRangeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if let Some((start, last)) = s.split_once('-') { + let start_address = start + .parse::<Ipv4Addr>() + .map_err(|_| IpRangeError::InvalidFormat)?; + + let last_address = last + .parse::<Ipv4Addr>() + .map_err(|_| IpRangeError::InvalidFormat)?; + + return Self::new_v4(start_address, last_address); + } + + Err(IpRangeError::InvalidFormat) + } +} + +impl std::str::FromStr for AddressRange<Ipv6Addr> { + type Err = IpRangeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if let Some((start, last)) = s.split_once('-') { + let start_address = start + .parse::<Ipv6Addr>() + .map_err(|_| IpRangeError::InvalidFormat)?; + + let last_address = last + .parse::<Ipv6Addr>() + .map_err(|_| IpRangeError::InvalidFormat)?; + + return Self::new_v6(start_address, last_address); + } + + Err(IpRangeError::InvalidFormat) + } +} + +impl<T: std::fmt::Display> std::fmt::Display for AddressRange<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.start, self.last) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{Ipv4Addr, Ipv6Addr}; + + #[test] + fn test_v4_cidr() { + let mut cidr: Ipv4Cidr = "0.0.0.0/0".parse().expect("valid IPv4 CIDR"); + + assert_eq!(cidr.addr, Ipv4Addr::new(0, 0, 0, 0)); + assert_eq!(cidr.mask, 0); + + assert!(cidr.contains_address(&Ipv4Addr::new(0, 0, 0, 0))); + assert!(cidr.contains_address(&Ipv4Addr::new(255, 255, 255, 255))); + + cidr = "192.168.100.1".parse().expect("valid IPv4 CIDR"); + + assert_eq!(cidr.addr, Ipv4Addr::new(192, 168, 100, 1)); + assert_eq!(cidr.mask, 32); + + assert!(cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 1))); + assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 2))); + assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 0))); + + cidr = "10.100.5.0/24".parse().expect("valid IPv4 CIDR"); + + assert_eq!(cidr.mask, 24); + + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 0))); + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 1))); + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 100))); + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 255))); + assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 4, 255))); + assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 6, 0))); + + "0.0.0.0/-1".parse::<Ipv4Cidr>().unwrap_err(); + "0.0.0.0/33".parse::<Ipv4Cidr>().unwrap_err(); + "256.256.256.256/10".parse::<Ipv4Cidr>().unwrap_err(); + + "fe80::1/64".parse::<Ipv4Cidr>().unwrap_err(); + "qweasd".parse::<Ipv4Cidr>().unwrap_err(); + "".parse::<Ipv4Cidr>().unwrap_err(); + } + + #[test] + fn test_v6_cidr() { + let mut cidr: Ipv6Cidr = "abab::1/64".parse().expect("valid IPv6 CIDR"); + + assert_eq!(cidr.addr, Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 1)); + assert_eq!(cidr.mask, 64); + + assert!(cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 0))); + assert!(cidr.contains_address(&Ipv6Addr::new( + 0xABAB, 0, 0, 0, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA + ))); + assert!(cidr.contains_address(&Ipv6Addr::new( + 0xABAB, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF + ))); + assert!(!cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 1, 0, 0, 0, 0))); + assert!(!cidr.contains_address(&Ipv6Addr::new( + 0xABAA, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF + ))); + + cidr = "eeee::1".parse().expect("valid IPv6 CIDR"); + + assert_eq!(cidr.mask, 128); + + assert!(cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 1))); + assert!(!cidr.contains_address(&Ipv6Addr::new( + 0xEEED, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF + ))); + assert!(!cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 0))); + + "eeee::1/-1".parse::<Ipv6Cidr>().unwrap_err(); + "eeee::1/129".parse::<Ipv6Cidr>().unwrap_err(); + "gggg::1/64".parse::<Ipv6Cidr>().unwrap_err(); + + "192.168.0.1".parse::<Ipv6Cidr>().unwrap_err(); + "qweasd".parse::<Ipv6Cidr>().unwrap_err(); + "".parse::<Ipv6Cidr>().unwrap_err(); + } + + #[test] + fn test_ip_range() { + IpRange::new([10, 0, 0, 2], [10, 0, 0, 1]).unwrap_err(); + + IpRange::new( + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], + ) + .unwrap_err(); + + let v4_range = IpRange::new([10, 0, 0, 0], [10, 0, 0, 100]).unwrap(); + assert_eq!(v4_range.family(), Family::V4); + + let v6_range = IpRange::new( + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], + ) + .unwrap(); + assert_eq!(v6_range.family(), Family::V6); + + "10.0.0.1-10.0.0.100".parse::<IpRange>().unwrap(); + "2001:db8::1-2001:db8::f".parse::<IpRange>().unwrap(); + + "10.0.0.1-2001:db8::1000".parse::<IpRange>().unwrap_err(); + "2001:db8::1-192.168.0.2".parse::<IpRange>().unwrap_err(); + + "10.0.0.1-10.0.0.0".parse::<IpRange>().unwrap_err(); + "2001:db8::1-2001:db8::0".parse::<IpRange>().unwrap_err(); + } + + #[test] + fn test_ipv4_to_cidrs() { + let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 100]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([192, 168, 0, 100], 32).unwrap()], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 200]).unwrap(); + + assert_eq!( + [ + Ipv4Cidr::new([192, 168, 0, 100], 30).unwrap(), + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 200]).unwrap(); + + assert_eq!( + [ + Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), + Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 101]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap()], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 201]).unwrap(); + + assert_eq!( + [ + Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), + Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), + Ipv4Cidr::new([192, 168, 0, 200], 31).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([192, 168, 0, 0], [192, 168, 0, 255]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([192, 168, 0, 0], 24).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 255]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([0, 0, 0, 0], 0).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([0, 0, 0, 1], [255, 255, 255, 255]).unwrap(); + + assert_eq!( + [ + Ipv4Cidr::new([0, 0, 0, 1], 32).unwrap(), + Ipv4Cidr::new([0, 0, 0, 2], 31).unwrap(), + Ipv4Cidr::new([0, 0, 0, 4], 30).unwrap(), + Ipv4Cidr::new([0, 0, 0, 8], 29).unwrap(), + Ipv4Cidr::new([0, 0, 0, 16], 28).unwrap(), + Ipv4Cidr::new([0, 0, 0, 32], 27).unwrap(), + Ipv4Cidr::new([0, 0, 0, 64], 26).unwrap(), + Ipv4Cidr::new([0, 0, 0, 128], 25).unwrap(), + Ipv4Cidr::new([0, 0, 1, 0], 24).unwrap(), + Ipv4Cidr::new([0, 0, 2, 0], 23).unwrap(), + Ipv4Cidr::new([0, 0, 4, 0], 22).unwrap(), + Ipv4Cidr::new([0, 0, 8, 0], 21).unwrap(), + Ipv4Cidr::new([0, 0, 16, 0], 20).unwrap(), + Ipv4Cidr::new([0, 0, 32, 0], 19).unwrap(), + Ipv4Cidr::new([0, 0, 64, 0], 18).unwrap(), + Ipv4Cidr::new([0, 0, 128, 0], 17).unwrap(), + Ipv4Cidr::new([0, 1, 0, 0], 16).unwrap(), + Ipv4Cidr::new([0, 2, 0, 0], 15).unwrap(), + Ipv4Cidr::new([0, 4, 0, 0], 14).unwrap(), + Ipv4Cidr::new([0, 8, 0, 0], 13).unwrap(), + Ipv4Cidr::new([0, 16, 0, 0], 12).unwrap(), + Ipv4Cidr::new([0, 32, 0, 0], 11).unwrap(), + Ipv4Cidr::new([0, 64, 0, 0], 10).unwrap(), + Ipv4Cidr::new([0, 128, 0, 0], 9).unwrap(), + Ipv4Cidr::new([1, 0, 0, 0], 8).unwrap(), + Ipv4Cidr::new([2, 0, 0, 0], 7).unwrap(), + Ipv4Cidr::new([4, 0, 0, 0], 6).unwrap(), + Ipv4Cidr::new([8, 0, 0, 0], 5).unwrap(), + Ipv4Cidr::new([16, 0, 0, 0], 4).unwrap(), + Ipv4Cidr::new([32, 0, 0, 0], 3).unwrap(), + Ipv4Cidr::new([64, 0, 0, 0], 2).unwrap(), + Ipv4Cidr::new([128, 0, 0, 0], 1).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 254]).unwrap(); + + assert_eq!( + [ + Ipv4Cidr::new([0, 0, 0, 0], 1).unwrap(), + Ipv4Cidr::new([128, 0, 0, 0], 2).unwrap(), + Ipv4Cidr::new([192, 0, 0, 0], 3).unwrap(), + Ipv4Cidr::new([224, 0, 0, 0], 4).unwrap(), + Ipv4Cidr::new([240, 0, 0, 0], 5).unwrap(), + Ipv4Cidr::new([248, 0, 0, 0], 6).unwrap(), + Ipv4Cidr::new([252, 0, 0, 0], 7).unwrap(), + Ipv4Cidr::new([254, 0, 0, 0], 8).unwrap(), + Ipv4Cidr::new([255, 0, 0, 0], 9).unwrap(), + Ipv4Cidr::new([255, 128, 0, 0], 10).unwrap(), + Ipv4Cidr::new([255, 192, 0, 0], 11).unwrap(), + Ipv4Cidr::new([255, 224, 0, 0], 12).unwrap(), + Ipv4Cidr::new([255, 240, 0, 0], 13).unwrap(), + Ipv4Cidr::new([255, 248, 0, 0], 14).unwrap(), + Ipv4Cidr::new([255, 252, 0, 0], 15).unwrap(), + Ipv4Cidr::new([255, 254, 0, 0], 16).unwrap(), + Ipv4Cidr::new([255, 255, 0, 0], 17).unwrap(), + Ipv4Cidr::new([255, 255, 128, 0], 18).unwrap(), + Ipv4Cidr::new([255, 255, 192, 0], 19).unwrap(), + Ipv4Cidr::new([255, 255, 224, 0], 20).unwrap(), + Ipv4Cidr::new([255, 255, 240, 0], 21).unwrap(), + Ipv4Cidr::new([255, 255, 248, 0], 22).unwrap(), + Ipv4Cidr::new([255, 255, 252, 0], 23).unwrap(), + Ipv4Cidr::new([255, 255, 254, 0], 24).unwrap(), + Ipv4Cidr::new([255, 255, 255, 0], 25).unwrap(), + Ipv4Cidr::new([255, 255, 255, 128], 26).unwrap(), + Ipv4Cidr::new([255, 255, 255, 192], 27).unwrap(), + Ipv4Cidr::new([255, 255, 255, 224], 28).unwrap(), + Ipv4Cidr::new([255, 255, 255, 240], 29).unwrap(), + Ipv4Cidr::new([255, 255, 255, 248], 30).unwrap(), + Ipv4Cidr::new([255, 255, 255, 252], 31).unwrap(), + Ipv4Cidr::new([255, 255, 255, 254], 32).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([0, 0, 0, 0], [0, 0, 0, 0]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([0, 0, 0, 0], 32).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v4([255, 255, 255, 255], [255, 255, 255, 255]).unwrap(); + + assert_eq!( + [Ipv4Cidr::new([255, 255, 255, 255], 32).unwrap(),], + range.to_cidrs().as_slice() + ); + } + + #[test] + fn test_ipv6_to_cidrs() { + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], + ) + .unwrap(); + + assert_eq!( + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 128).unwrap()], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], + ) + .unwrap(); + + assert_eq!( + [ + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 116).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], + ) + .unwrap(); + + assert_eq!( + [ + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], + ) + .unwrap(); + + assert_eq!( + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2001], + ) + .unwrap(); + + assert_eq!( + [ + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 127).unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], + [0x2001, 0x0DB8, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], + ) + .unwrap(); + + assert_eq!( + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], 64).unwrap()], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0, 0, 0, 0, 0, 0, 0, 0], + [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + ], + ) + .unwrap(); + + assert_eq!( + [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 0).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0, 0, 0, 0, 0, 0, 0, 0x0001], + [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + ], + ) + .unwrap(); + + assert_eq!( + [ + "::1/128".parse::<Ipv6Cidr>().unwrap(), + "::2/127".parse::<Ipv6Cidr>().unwrap(), + "::4/126".parse::<Ipv6Cidr>().unwrap(), + "::8/125".parse::<Ipv6Cidr>().unwrap(), + "::10/124".parse::<Ipv6Cidr>().unwrap(), + "::20/123".parse::<Ipv6Cidr>().unwrap(), + "::40/122".parse::<Ipv6Cidr>().unwrap(), + "::80/121".parse::<Ipv6Cidr>().unwrap(), + "::100/120".parse::<Ipv6Cidr>().unwrap(), + "::200/119".parse::<Ipv6Cidr>().unwrap(), + "::400/118".parse::<Ipv6Cidr>().unwrap(), + "::800/117".parse::<Ipv6Cidr>().unwrap(), + "::1000/116".parse::<Ipv6Cidr>().unwrap(), + "::2000/115".parse::<Ipv6Cidr>().unwrap(), + "::4000/114".parse::<Ipv6Cidr>().unwrap(), + "::8000/113".parse::<Ipv6Cidr>().unwrap(), + "::1:0/112".parse::<Ipv6Cidr>().unwrap(), + "::2:0/111".parse::<Ipv6Cidr>().unwrap(), + "::4:0/110".parse::<Ipv6Cidr>().unwrap(), + "::8:0/109".parse::<Ipv6Cidr>().unwrap(), + "::10:0/108".parse::<Ipv6Cidr>().unwrap(), + "::20:0/107".parse::<Ipv6Cidr>().unwrap(), + "::40:0/106".parse::<Ipv6Cidr>().unwrap(), + "::80:0/105".parse::<Ipv6Cidr>().unwrap(), + "::100:0/104".parse::<Ipv6Cidr>().unwrap(), + "::200:0/103".parse::<Ipv6Cidr>().unwrap(), + "::400:0/102".parse::<Ipv6Cidr>().unwrap(), + "::800:0/101".parse::<Ipv6Cidr>().unwrap(), + "::1000:0/100".parse::<Ipv6Cidr>().unwrap(), + "::2000:0/99".parse::<Ipv6Cidr>().unwrap(), + "::4000:0/98".parse::<Ipv6Cidr>().unwrap(), + "::8000:0/97".parse::<Ipv6Cidr>().unwrap(), + "::1:0:0/96".parse::<Ipv6Cidr>().unwrap(), + "::2:0:0/95".parse::<Ipv6Cidr>().unwrap(), + "::4:0:0/94".parse::<Ipv6Cidr>().unwrap(), + "::8:0:0/93".parse::<Ipv6Cidr>().unwrap(), + "::10:0:0/92".parse::<Ipv6Cidr>().unwrap(), + "::20:0:0/91".parse::<Ipv6Cidr>().unwrap(), + "::40:0:0/90".parse::<Ipv6Cidr>().unwrap(), + "::80:0:0/89".parse::<Ipv6Cidr>().unwrap(), + "::100:0:0/88".parse::<Ipv6Cidr>().unwrap(), + "::200:0:0/87".parse::<Ipv6Cidr>().unwrap(), + "::400:0:0/86".parse::<Ipv6Cidr>().unwrap(), + "::800:0:0/85".parse::<Ipv6Cidr>().unwrap(), + "::1000:0:0/84".parse::<Ipv6Cidr>().unwrap(), + "::2000:0:0/83".parse::<Ipv6Cidr>().unwrap(), + "::4000:0:0/82".parse::<Ipv6Cidr>().unwrap(), + "::8000:0:0/81".parse::<Ipv6Cidr>().unwrap(), + "::1:0:0:0/80".parse::<Ipv6Cidr>().unwrap(), + "::2:0:0:0/79".parse::<Ipv6Cidr>().unwrap(), + "::4:0:0:0/78".parse::<Ipv6Cidr>().unwrap(), + "::8:0:0:0/77".parse::<Ipv6Cidr>().unwrap(), + "::10:0:0:0/76".parse::<Ipv6Cidr>().unwrap(), + "::20:0:0:0/75".parse::<Ipv6Cidr>().unwrap(), + "::40:0:0:0/74".parse::<Ipv6Cidr>().unwrap(), + "::80:0:0:0/73".parse::<Ipv6Cidr>().unwrap(), + "::100:0:0:0/72".parse::<Ipv6Cidr>().unwrap(), + "::200:0:0:0/71".parse::<Ipv6Cidr>().unwrap(), + "::400:0:0:0/70".parse::<Ipv6Cidr>().unwrap(), + "::800:0:0:0/69".parse::<Ipv6Cidr>().unwrap(), + "::1000:0:0:0/68".parse::<Ipv6Cidr>().unwrap(), + "::2000:0:0:0/67".parse::<Ipv6Cidr>().unwrap(), + "::4000:0:0:0/66".parse::<Ipv6Cidr>().unwrap(), + "::8000:0:0:0/65".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:1::/64".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:2::/63".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:4::/62".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:8::/61".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:10::/60".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:20::/59".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:40::/58".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:80::/57".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:100::/56".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:200::/55".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:400::/54".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:800::/53".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:1000::/52".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:2000::/51".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:4000::/50".parse::<Ipv6Cidr>().unwrap(), + "0:0:0:8000::/49".parse::<Ipv6Cidr>().unwrap(), + "0:0:1::/48".parse::<Ipv6Cidr>().unwrap(), + "0:0:2::/47".parse::<Ipv6Cidr>().unwrap(), + "0:0:4::/46".parse::<Ipv6Cidr>().unwrap(), + "0:0:8::/45".parse::<Ipv6Cidr>().unwrap(), + "0:0:10::/44".parse::<Ipv6Cidr>().unwrap(), + "0:0:20::/43".parse::<Ipv6Cidr>().unwrap(), + "0:0:40::/42".parse::<Ipv6Cidr>().unwrap(), + "0:0:80::/41".parse::<Ipv6Cidr>().unwrap(), + "0:0:100::/40".parse::<Ipv6Cidr>().unwrap(), + "0:0:200::/39".parse::<Ipv6Cidr>().unwrap(), + "0:0:400::/38".parse::<Ipv6Cidr>().unwrap(), + "0:0:800::/37".parse::<Ipv6Cidr>().unwrap(), + "0:0:1000::/36".parse::<Ipv6Cidr>().unwrap(), + "0:0:2000::/35".parse::<Ipv6Cidr>().unwrap(), + "0:0:4000::/34".parse::<Ipv6Cidr>().unwrap(), + "0:0:8000::/33".parse::<Ipv6Cidr>().unwrap(), + "0:1::/32".parse::<Ipv6Cidr>().unwrap(), + "0:2::/31".parse::<Ipv6Cidr>().unwrap(), + "0:4::/30".parse::<Ipv6Cidr>().unwrap(), + "0:8::/29".parse::<Ipv6Cidr>().unwrap(), + "0:10::/28".parse::<Ipv6Cidr>().unwrap(), + "0:20::/27".parse::<Ipv6Cidr>().unwrap(), + "0:40::/26".parse::<Ipv6Cidr>().unwrap(), + "0:80::/25".parse::<Ipv6Cidr>().unwrap(), + "0:100::/24".parse::<Ipv6Cidr>().unwrap(), + "0:200::/23".parse::<Ipv6Cidr>().unwrap(), + "0:400::/22".parse::<Ipv6Cidr>().unwrap(), + "0:800::/21".parse::<Ipv6Cidr>().unwrap(), + "0:1000::/20".parse::<Ipv6Cidr>().unwrap(), + "0:2000::/19".parse::<Ipv6Cidr>().unwrap(), + "0:4000::/18".parse::<Ipv6Cidr>().unwrap(), + "0:8000::/17".parse::<Ipv6Cidr>().unwrap(), + "1::/16".parse::<Ipv6Cidr>().unwrap(), + "2::/15".parse::<Ipv6Cidr>().unwrap(), + "4::/14".parse::<Ipv6Cidr>().unwrap(), + "8::/13".parse::<Ipv6Cidr>().unwrap(), + "10::/12".parse::<Ipv6Cidr>().unwrap(), + "20::/11".parse::<Ipv6Cidr>().unwrap(), + "40::/10".parse::<Ipv6Cidr>().unwrap(), + "80::/9".parse::<Ipv6Cidr>().unwrap(), + "100::/8".parse::<Ipv6Cidr>().unwrap(), + "200::/7".parse::<Ipv6Cidr>().unwrap(), + "400::/6".parse::<Ipv6Cidr>().unwrap(), + "800::/5".parse::<Ipv6Cidr>().unwrap(), + "1000::/4".parse::<Ipv6Cidr>().unwrap(), + "2000::/3".parse::<Ipv6Cidr>().unwrap(), + "4000::/2".parse::<Ipv6Cidr>().unwrap(), + "8000::/1".parse::<Ipv6Cidr>().unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [0, 0, 0, 0, 0, 0, 0, 0], + [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, + ], + ) + .unwrap(); + + assert_eq!( + [ + "::/1".parse::<Ipv6Cidr>().unwrap(), + "8000::/2".parse::<Ipv6Cidr>().unwrap(), + "c000::/3".parse::<Ipv6Cidr>().unwrap(), + "e000::/4".parse::<Ipv6Cidr>().unwrap(), + "f000::/5".parse::<Ipv6Cidr>().unwrap(), + "f800::/6".parse::<Ipv6Cidr>().unwrap(), + "fc00::/7".parse::<Ipv6Cidr>().unwrap(), + "fe00::/8".parse::<Ipv6Cidr>().unwrap(), + "ff00::/9".parse::<Ipv6Cidr>().unwrap(), + "ff80::/10".parse::<Ipv6Cidr>().unwrap(), + "ffc0::/11".parse::<Ipv6Cidr>().unwrap(), + "ffe0::/12".parse::<Ipv6Cidr>().unwrap(), + "fff0::/13".parse::<Ipv6Cidr>().unwrap(), + "fff8::/14".parse::<Ipv6Cidr>().unwrap(), + "fffc::/15".parse::<Ipv6Cidr>().unwrap(), + "fffe::/16".parse::<Ipv6Cidr>().unwrap(), + "ffff::/17".parse::<Ipv6Cidr>().unwrap(), + "ffff:8000::/18".parse::<Ipv6Cidr>().unwrap(), + "ffff:c000::/19".parse::<Ipv6Cidr>().unwrap(), + "ffff:e000::/20".parse::<Ipv6Cidr>().unwrap(), + "ffff:f000::/21".parse::<Ipv6Cidr>().unwrap(), + "ffff:f800::/22".parse::<Ipv6Cidr>().unwrap(), + "ffff:fc00::/23".parse::<Ipv6Cidr>().unwrap(), + "ffff:fe00::/24".parse::<Ipv6Cidr>().unwrap(), + "ffff:ff00::/25".parse::<Ipv6Cidr>().unwrap(), + "ffff:ff80::/26".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffc0::/27".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffe0::/28".parse::<Ipv6Cidr>().unwrap(), + "ffff:fff0::/29".parse::<Ipv6Cidr>().unwrap(), + "ffff:fff8::/30".parse::<Ipv6Cidr>().unwrap(), + "ffff:fffc::/31".parse::<Ipv6Cidr>().unwrap(), + "ffff:fffe::/32".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff::/33".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:8000::/34".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:c000::/35".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:e000::/36".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:f000::/37".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:f800::/38".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fc00::/39".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fe00::/40".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ff00::/41".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ff80::/42".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffc0::/43".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffe0::/44".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fff0::/45".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fff8::/46".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fffc::/47".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:fffe::/48".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff::/49".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:8000::/50".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:c000::/51".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:e000::/52".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:f000::/53".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:f800::/54".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fc00::/55".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fe00::/56".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ff00::/57".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ff80::/58".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffc0::/59".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffe0::/60".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fff0::/61".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fff8::/62".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fffc::/63".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:fffe::/64".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff::/65".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:8000::/66".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:c000::/67".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:e000::/68".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:f000::/69".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:f800::/70".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fc00::/71".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fe00::/72".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ff00::/73".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ff80::/74".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ffc0::/75".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ffe0::/76".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fff0::/77".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fff8::/78".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fffc::/79".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:fffe::/80".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ffff::/81".parse::<Ipv6Cidr>().unwrap(), + "ffff:ffff:ffff:ffff:ffff:8000::/82" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:c000::/83" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:e000::/84" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:f000::/85" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:f800::/86" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fc00::/87" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fe00::/88" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ff00::/89" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ff80::/90" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffc0::/91" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffe0::/92" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fff0::/93" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fff8::/94" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fffc::/95" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:fffe::/96" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff::/97" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:8000:0/98" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:c000:0/99" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:e000:0/100" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:f000:0/101" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:f800:0/102" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/103" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/104" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/105" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/106" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/108" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/109" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/110" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/111" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/113" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/114" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/115" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/116" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/117" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/118" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/119" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/120" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/121" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/122" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/123" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/124" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/125" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/126" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/127" + .parse::<Ipv6Cidr>() + .unwrap(), + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/128" + .parse::<Ipv6Cidr>() + .unwrap(), + ], + range.to_cidrs().as_slice() + ); + + let range = + AddressRange::new_v6([0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); + + assert_eq!( + [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 128).unwrap(),], + range.to_cidrs().as_slice() + ); + + let range = AddressRange::new_v6( + [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + ], + [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + ], + ) + .unwrap(); + + assert_eq!( + [Ipv6Cidr::new( + [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], + 128 + ) + .unwrap(),], + range.to_cidrs().as_slice() + ); + } +} diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs new file mode 100644 index 00000000..b952d71c --- /dev/null +++ b/proxmox-network-types/src/lib.rs @@ -0,0 +1,2 @@ +pub mod ip_address; +pub mod mac_address; diff --git a/proxmox-network-types/src/mac_address.rs b/proxmox-network-types/src/mac_address.rs new file mode 100644 index 00000000..2c3aad29 --- /dev/null +++ b/proxmox-network-types/src/mac_address.rs @@ -0,0 +1,120 @@ +use std::fmt::Display; +use std::net::Ipv6Addr; + +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum MacAddressError { + #[error("the hostname must be from 1 to 63 characters long")] + InvalidLength, + #[error("the hostname contains invalid symbols")] + InvalidSymbols, +} + +/// EUI-48 MAC Address +#[derive(Clone, Debug, DeserializeFromStr, SerializeDisplay, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct MacAddress([u8; 6]); + +static LOCAL_PART: [u8; 8] = [0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; +static EUI64_MIDDLE_PART: [u8; 2] = [0xFF, 0xFE]; + +impl MacAddress { + pub fn new(address: [u8; 6]) -> Self { + Self(address) + } + + /// generates a link local IPv6-address according to RFC 4291 (Appendix A) + pub fn eui64_link_local_address(&self) -> Ipv6Addr { + let head = &self.0[..3]; + let tail = &self.0[3..]; + + let mut eui64_address: Vec<u8> = LOCAL_PART + .iter() + .chain(head.iter()) + .chain(EUI64_MIDDLE_PART.iter()) + .chain(tail.iter()) + .copied() + .collect(); + + // we need to flip the 7th bit of the first eui64 byte + eui64_address[8] ^= 0x02; + + Ipv6Addr::from( + TryInto::<[u8; 16]>::try_into(eui64_address).expect("is an u8 array with 16 entries"), + ) + } +} + +impl std::str::FromStr for MacAddress { + type Err = MacAddressError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let split = s.split(':'); + + let parsed = split + .into_iter() + .map(|elem| u8::from_str_radix(elem, 16)) + .collect::<Result<Vec<u8>, _>>() + .map_err(|_| MacAddressError::InvalidSymbols)?; + + if parsed.len() != 6 { + return Err(MacAddressError::InvalidLength); + } + + // SAFETY: ok because of length check + Ok(Self(parsed.try_into().unwrap())) + } +} + +impl Display for MacAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] + ) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_parse_mac_address() { + for input in [ + "aa:aa:aa:11:22:33", + "AA:BB:FF:11:22:33", + "bc:24:11:AA:bb:Ef", + ] { + let mac_address = input.parse::<MacAddress>().expect("valid mac address"); + + assert_eq!(input.to_uppercase(), mac_address.to_string()); + } + + for input in [ + "aa:aa:aa:11:22:33:aa", + "AA:BB:FF:11:22", + "AA:BB:GG:11:22:33", + "AABBGG112233", + "", + ] { + input + .parse::<MacAddress>() + .expect_err("invalid mac address"); + } + } + + #[test] + fn test_eui64_link_local_address() { + let mac_address: MacAddress = "BC:24:11:49:8D:75".parse().expect("valid MAC address"); + + let link_local_address = + Ipv6Addr::from_str("fe80::be24:11ff:fe49:8d75").expect("valid IPv6 address"); + + assert_eq!(link_local_address, mac_address.eui64_link_local_address()); + } +} -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-01 14:52 [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Stefan Hanreich @ 2025-04-01 14:52 ` Stefan Hanreich 2025-04-04 7:31 ` Wolfgang Bumiller 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-ve-rs v3 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich ` (2 subsequent siblings) 3 siblings, 1 reply; 11+ messages in thread From: Stefan Hanreich @ 2025-04-01 14:52 UTC (permalink / raw) To: pve-devel Add a type for representing Linux hostnames. These are the same constraints as the installer enforces [1]. Lowercasing is fine as well, since practically everything treats hostnames case-insensitively as RFC 952 stipulates: > No distinction is made between upper and lower case. [1] https://git.proxmox.com/?p=pve-installer.git;a=blob;f=Proxmox/Sys/Net.pm;h=81cb15f0042b195461324fffeca53d732133629e;hb=HEAD#l11 [2] https://www.rfc-editor.org/rfc/rfc952.txt Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> --- Notes: sending this separately because this contains the new types, that haven't been a part of proxmox-ve-rs before. Changes from v2: * improved hostname validation (thanks @Maximiliano @Christoph) * added additional unit tests Changes from v1: * added unit tests proxmox-network-types/src/hostname.rs | 103 ++++++++++++++++++++++++++ proxmox-network-types/src/lib.rs | 1 + 2 files changed, 104 insertions(+) create mode 100644 proxmox-network-types/src/hostname.rs diff --git a/proxmox-network-types/src/hostname.rs b/proxmox-network-types/src/hostname.rs new file mode 100644 index 00000000..4b2f7ede --- /dev/null +++ b/proxmox-network-types/src/hostname.rs @@ -0,0 +1,103 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum HostnameError { + #[error("the hostname must be from 1 to 63 characters long")] + InvalidLength, + #[error("the hostname has an invalid format")] + InvalidFormat, +} + +/// Hostname of a Debian system +/// +/// It checks for the following conditions: +/// * At most 63 characters long. +/// * It must not start or end with a hyphen. +/// * Must only contain ASCII alphanumeric characters as well as hyphens. +/// * It must not be purely numerical. +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] +pub struct Hostname(String); + +impl std::str::FromStr for Hostname { + type Err = HostnameError; + + fn from_str(hostname: &str) -> Result<Self, Self::Err> { + Self::new(hostname) + } +} + +impl AsRef<str> for Hostname { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Display for Hostname { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Hostname { + /// Constructs a new hostname from a string + /// + /// This function accepts characters in any case, but the resulting hostname will be + /// lowercased. + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { + let name: &str = name_ref.as_ref(); + + if name.is_empty() || name.len() > 63 { + return Err(HostnameError::InvalidLength); + } + + if !(name.starts_with(|c: char| c.is_ascii_alphanumeric()) + && name.ends_with(|c: char| c.is_ascii_alphanumeric())) { + return Err(HostnameError::InvalidFormat); + } + + if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { + return Err(HostnameError::InvalidFormat); + } + + if name.chars().all(|c| c.is_ascii_digit()) { + return Err(HostnameError::InvalidFormat); + } + + Ok(Self(name.to_lowercase())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_hostname() { + for valid_hostname in [ + "debian", + "0host", + "some-host-123", + "63characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ] { + Hostname::new(valid_hostname).expect("valid hostname"); + } + + for invalid_hostname in [ + "-debian", + "0host-", + "some/host", + "", + "123", + "64characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "🆒" + ] { + Hostname::new(invalid_hostname).expect_err("invalid hostname"); + } + + let uppercased_hostname = Hostname::new("UPPERCASE").expect("valid hostname"); + assert_eq!(uppercased_hostname.as_ref(), "uppercase"); + } +} diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs index b952d71c..f4812146 100644 --- a/proxmox-network-types/src/lib.rs +++ b/proxmox-network-types/src/lib.rs @@ -1,2 +1,3 @@ +pub mod hostname; pub mod ip_address; pub mod mac_address; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type Stefan Hanreich @ 2025-04-04 7:31 ` Wolfgang Bumiller 2025-04-04 7:51 ` Stefan Hanreich 0 siblings, 1 reply; 11+ messages in thread From: Wolfgang Bumiller @ 2025-04-04 7:31 UTC (permalink / raw) To: Stefan Hanreich; +Cc: pve-devel On Tue, Apr 01, 2025 at 04:52:44PM +0200, Stefan Hanreich wrote: > Add a type for representing Linux hostnames. These are the same > constraints as the installer enforces [1]. Lowercasing is fine as > well, since practically everything treats hostnames case-insensitively > as RFC 952 stipulates: > > > No distinction is made between upper and lower case. > > [1] https://git.proxmox.com/?p=pve-installer.git;a=blob;f=Proxmox/Sys/Net.pm;h=81cb15f0042b195461324fffeca53d732133629e;hb=HEAD#l11 > [2] https://www.rfc-editor.org/rfc/rfc952.txt > > Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> > --- > > Notes: > sending this separately because this contains the new types, that > haven't been a part of proxmox-ve-rs before. > > Changes from v2: > * improved hostname validation (thanks @Maximiliano @Christoph) > * added additional unit tests > > Changes from v1: > * added unit tests > > proxmox-network-types/src/hostname.rs | 103 ++++++++++++++++++++++++++ > proxmox-network-types/src/lib.rs | 1 + > 2 files changed, 104 insertions(+) > create mode 100644 proxmox-network-types/src/hostname.rs > > diff --git a/proxmox-network-types/src/hostname.rs b/proxmox-network-types/src/hostname.rs > new file mode 100644 > index 00000000..4b2f7ede > --- /dev/null > +++ b/proxmox-network-types/src/hostname.rs > @@ -0,0 +1,103 @@ > +use std::fmt::Display; > + > +use serde::{Deserialize, Serialize}; > +use thiserror::Error; > + > +#[derive(Error, Debug)] > +pub enum HostnameError { > + #[error("the hostname must be from 1 to 63 characters long")] > + InvalidLength, > + #[error("the hostname has an invalid format")] > + InvalidFormat, > +} > + > +/// Hostname of a Debian system ^ Why debian specific? Should this then not be in a different namespace or have a different name? > +/// > +/// It checks for the following conditions: > +/// * At most 63 characters long. > +/// * It must not start or end with a hyphen. > +/// * Must only contain ASCII alphanumeric characters as well as hyphens. > +/// * It must not be purely numerical. > +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] > +pub struct Hostname(String); > + > +impl std::str::FromStr for Hostname { > + type Err = HostnameError; > + > + fn from_str(hostname: &str) -> Result<Self, Self::Err> { > + Self::new(hostname) > + } > +} > + > +impl AsRef<str> for Hostname { > + fn as_ref(&self) -> &str { > + &self.0 > + } > +} > + > +impl Display for Hostname { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + self.0.fmt(f) > + } > +} > + > +impl Hostname { > + /// Constructs a new hostname from a string > + /// > + /// This function accepts characters in any case, but the resulting hostname will be > + /// lowercased. > + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { Nit: I'd recommend using a `check()` function which does not create the `Hostname` itself, because then: - in `FromStr` we know we have a reference (&str) and need to clone. - We could add a `TryFrom<&str>` wich just uses `.parse()` - We could add a `TryFrom<String>` which avoids the clone. But... > + let name: &str = name_ref.as_ref(); > + > + if name.is_empty() || name.len() > 63 { > + return Err(HostnameError::InvalidLength); > + } > + > + if !(name.starts_with(|c: char| c.is_ascii_alphanumeric()) > + && name.ends_with(|c: char| c.is_ascii_alphanumeric())) { > + return Err(HostnameError::InvalidFormat); > + } > + > + if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { > + return Err(HostnameError::InvalidFormat); > + } > + > + if name.chars().all(|c| c.is_ascii_digit()) { > + return Err(HostnameError::InvalidFormat); > + } > + > + Ok(Self(name.to_lowercase())) ...do we really want/need to do this? (Note that if we really do this, it should IMO be documented on the *type*, too, not just this method.) I mean, I'm not completely against it, but if we "normalize", would we not technically also have to punycode non-ascii hostnames? (But at the very least it seems that punycode does case-folding... at least a quick online-punycode conversion tool seems to convert both 'Ө' and 'ө' to 'xn--j6a') > + } > +} > + > +#[cfg(test)] > +mod tests { > + use super::*; > + > + #[test] > + fn test_parse_hostname() { > + for valid_hostname in [ > + "debian", > + "0host", > + "some-host-123", > + "63characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > + ] { > + Hostname::new(valid_hostname).expect("valid hostname"); > + } > + > + for invalid_hostname in [ > + "-debian", > + "0host-", > + "some/host", > + "", > + "123", > + "64characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", > + "🆒" > + ] { > + Hostname::new(invalid_hostname).expect_err("invalid hostname"); > + } > + > + let uppercased_hostname = Hostname::new("UPPERCASE").expect("valid hostname"); > + assert_eq!(uppercased_hostname.as_ref(), "uppercase"); > + } > +} > diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs > index b952d71c..f4812146 100644 > --- a/proxmox-network-types/src/lib.rs > +++ b/proxmox-network-types/src/lib.rs > @@ -1,2 +1,3 @@ > +pub mod hostname; > pub mod ip_address; > pub mod mac_address; > -- > 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-04 7:31 ` Wolfgang Bumiller @ 2025-04-04 7:51 ` Stefan Hanreich 2025-04-04 9:22 ` Wolfgang Bumiller 2025-04-04 11:26 ` Stefan Hanreich 0 siblings, 2 replies; 11+ messages in thread From: Stefan Hanreich @ 2025-04-04 7:51 UTC (permalink / raw) To: Wolfgang Bumiller; +Cc: pve-devel Thanks for your review - comments inline On 4/4/25 09:31, Wolfgang Bumiller wrote: > On Tue, Apr 01, 2025 at 04:52:44PM +0200, Stefan Hanreich wrote: >> Add a type for representing Linux hostnames. These are the same >> constraints as the installer enforces [1]. Lowercasing is fine as >> well, since practically everything treats hostnames case-insensitively >> as RFC 952 stipulates: >> >>> No distinction is made between upper and lower case. >> >> [1] https://git.proxmox.com/?p=pve-installer.git;a=blob;f=Proxmox/Sys/Net.pm;h=81cb15f0042b195461324fffeca53d732133629e;hb=HEAD#l11 >> [2] https://www.rfc-editor.org/rfc/rfc952.txt >> >> Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> >> --- >> >> Notes: >> sending this separately because this contains the new types, that >> haven't been a part of proxmox-ve-rs before. >> >> Changes from v2: >> * improved hostname validation (thanks @Maximiliano @Christoph) >> * added additional unit tests >> >> Changes from v1: >> * added unit tests >> >> proxmox-network-types/src/hostname.rs | 103 ++++++++++++++++++++++++++ >> proxmox-network-types/src/lib.rs | 1 + >> 2 files changed, 104 insertions(+) >> create mode 100644 proxmox-network-types/src/hostname.rs >> >> diff --git a/proxmox-network-types/src/hostname.rs b/proxmox-network-types/src/hostname.rs >> new file mode 100644 >> index 00000000..4b2f7ede >> --- /dev/null >> +++ b/proxmox-network-types/src/hostname.rs >> @@ -0,0 +1,103 @@ >> +use std::fmt::Display; >> + >> +use serde::{Deserialize, Serialize}; >> +use thiserror::Error; >> + >> +#[derive(Error, Debug)] >> +pub enum HostnameError { >> + #[error("the hostname must be from 1 to 63 characters long")] >> + InvalidLength, >> + #[error("the hostname has an invalid format")] >> + InvalidFormat, >> +} >> + >> +/// Hostname of a Debian system > > ^ Why debian specific? Should this then not be in a different namespace > or have a different name? As Christoph mentioned, Debian explicitly forbids numeric-only hostnames. They are technically valid, just *strongly* discouraged since depending on the implementation they are interpreted as IPs rather than hostnames. 0 or 1 or 234728934 for instance is a valid IPv4 after all (ping accepts them as input for instance). Since we're debian-based I figured I'd add it to the validation since it's very much relevant for our use-case here. I have nothing against changing the name though to DebianHostname. >> +/// >> +/// It checks for the following conditions: >> +/// * At most 63 characters long. >> +/// * It must not start or end with a hyphen. >> +/// * Must only contain ASCII alphanumeric characters as well as hyphens. >> +/// * It must not be purely numerical. >> +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] >> +pub struct Hostname(String); >> + >> +impl std::str::FromStr for Hostname { >> + type Err = HostnameError; >> + >> + fn from_str(hostname: &str) -> Result<Self, Self::Err> { >> + Self::new(hostname) >> + } >> +} >> + >> +impl AsRef<str> for Hostname { >> + fn as_ref(&self) -> &str { >> + &self.0 >> + } >> +} >> + >> +impl Display for Hostname { >> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { >> + self.0.fmt(f) >> + } >> +} >> + >> +impl Hostname { >> + /// Constructs a new hostname from a string >> + /// >> + /// This function accepts characters in any case, but the resulting hostname will be >> + /// lowercased. >> + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { > > Nit: I'd recommend using a `check()` function which does not create the > `Hostname` itself, because then: > > - in `FromStr` we know we have a reference (&str) and need to clone. > - We could add a `TryFrom<&str>` wich just uses `.parse()` > - We could add a `TryFrom<String>` which avoids the clone. > > But... > >> + let name: &str = name_ref.as_ref(); >> + >> + if name.is_empty() || name.len() > 63 { >> + return Err(HostnameError::InvalidLength); >> + } >> + >> + if !(name.starts_with(|c: char| c.is_ascii_alphanumeric()) >> + && name.ends_with(|c: char| c.is_ascii_alphanumeric())) { >> + return Err(HostnameError::InvalidFormat); >> + } >> + >> + if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { >> + return Err(HostnameError::InvalidFormat); >> + } >> + >> + if name.chars().all(|c| c.is_ascii_digit()) { >> + return Err(HostnameError::InvalidFormat); >> + } >> + >> + Ok(Self(name.to_lowercase())) > > ...do we really want/need to do this? (Note that if we really do this, > it should IMO be documented on the *type*, too, not just this method.) The idea was that, since hostnames are treated case-insensitively by basically every application, it would make no sense to create two *different* hostnames, that would be the same in practice. It's something we could maybe move to a (Partial)Eq implementation and remove the lowercasing here? > I mean, I'm not completely against it, but if we "normalize", would we > not technically also have to punycode non-ascii hostnames? > > (But at the very least it seems that punycode does case-folding... at > least a quick online-punycode conversion tool seems to convert both 'Ө' > and 'ө' to 'xn--j6a') I see your point, but I decided to stick to what the installer allows for hostnames, which is ASCII-only characters without doing any punycode conversion (correct me if I'm wrong there, but I don't remember this being the case). >> + } >> +} >> + >> +#[cfg(test)] >> +mod tests { >> + use super::*; >> + >> + #[test] >> + fn test_parse_hostname() { >> + for valid_hostname in [ >> + "debian", >> + "0host", >> + "some-host-123", >> + "63characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" >> + ] { >> + Hostname::new(valid_hostname).expect("valid hostname"); >> + } >> + >> + for invalid_hostname in [ >> + "-debian", >> + "0host-", >> + "some/host", >> + "", >> + "123", >> + "64characterlonghostnamexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", >> + "🆒" >> + ] { >> + Hostname::new(invalid_hostname).expect_err("invalid hostname"); >> + } >> + >> + let uppercased_hostname = Hostname::new("UPPERCASE").expect("valid hostname"); >> + assert_eq!(uppercased_hostname.as_ref(), "uppercase"); >> + } >> +} >> diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs >> index b952d71c..f4812146 100644 >> --- a/proxmox-network-types/src/lib.rs >> +++ b/proxmox-network-types/src/lib.rs >> @@ -1,2 +1,3 @@ >> +pub mod hostname; >> pub mod ip_address; >> pub mod mac_address; >> -- >> 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-04 7:51 ` Stefan Hanreich @ 2025-04-04 9:22 ` Wolfgang Bumiller 2025-04-04 11:26 ` Stefan Hanreich 1 sibling, 0 replies; 11+ messages in thread From: Wolfgang Bumiller @ 2025-04-04 9:22 UTC (permalink / raw) To: Stefan Hanreich; +Cc: pve-devel On Fri, Apr 04, 2025 at 09:51:05AM +0200, Stefan Hanreich wrote: > Thanks for your review - comments inline > > On 4/4/25 09:31, Wolfgang Bumiller wrote: > > On Tue, Apr 01, 2025 at 04:52:44PM +0200, Stefan Hanreich wrote: > >> Add a type for representing Linux hostnames. These are the same > >> constraints as the installer enforces [1]. Lowercasing is fine as > >> well, since practically everything treats hostnames case-insensitively > >> as RFC 952 stipulates: > >> > >>> No distinction is made between upper and lower case. > >> > >> [1] https://git.proxmox.com/?p=pve-installer.git;a=blob;f=Proxmox/Sys/Net.pm;h=81cb15f0042b195461324fffeca53d732133629e;hb=HEAD#l11 > >> [2] https://www.rfc-editor.org/rfc/rfc952.txt > >> > >> Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> > >> --- > >> > >> Notes: > >> sending this separately because this contains the new types, that > >> haven't been a part of proxmox-ve-rs before. > >> > >> Changes from v2: > >> * improved hostname validation (thanks @Maximiliano @Christoph) > >> * added additional unit tests > >> > >> Changes from v1: > >> * added unit tests > >> > >> proxmox-network-types/src/hostname.rs | 103 ++++++++++++++++++++++++++ > >> proxmox-network-types/src/lib.rs | 1 + > >> 2 files changed, 104 insertions(+) > >> create mode 100644 proxmox-network-types/src/hostname.rs > >> > >> diff --git a/proxmox-network-types/src/hostname.rs b/proxmox-network-types/src/hostname.rs > >> new file mode 100644 > >> index 00000000..4b2f7ede > >> --- /dev/null > >> +++ b/proxmox-network-types/src/hostname.rs > >> @@ -0,0 +1,103 @@ > >> +use std::fmt::Display; > >> + > >> +use serde::{Deserialize, Serialize}; > >> +use thiserror::Error; > >> + > >> +#[derive(Error, Debug)] > >> +pub enum HostnameError { > >> + #[error("the hostname must be from 1 to 63 characters long")] > >> + InvalidLength, > >> + #[error("the hostname has an invalid format")] > >> + InvalidFormat, > >> +} > >> + > >> +/// Hostname of a Debian system > > > > ^ Why debian specific? Should this then not be in a different namespace > > or have a different name? > > As Christoph mentioned, Debian explicitly forbids numeric-only > hostnames. They are technically valid, just *strongly* discouraged since > depending on the implementation they are interpreted as IPs rather than > hostnames. 0 or 1 or 234728934 for instance is a valid IPv4 after all > (ping accepts them as input for instance). > > Since we're debian-based I figured I'd add it to the validation since > it's very much relevant for our use-case here. I have nothing against > changing the name though to DebianHostname. > > >> +/// > >> +/// It checks for the following conditions: > >> +/// * At most 63 characters long. > >> +/// * It must not start or end with a hyphen. > >> +/// * Must only contain ASCII alphanumeric characters as well as hyphens. > >> +/// * It must not be purely numerical. > >> +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] > >> +pub struct Hostname(String); > >> + > >> +impl std::str::FromStr for Hostname { > >> + type Err = HostnameError; > >> + > >> + fn from_str(hostname: &str) -> Result<Self, Self::Err> { > >> + Self::new(hostname) > >> + } > >> +} > >> + > >> +impl AsRef<str> for Hostname { > >> + fn as_ref(&self) -> &str { > >> + &self.0 > >> + } > >> +} > >> + > >> +impl Display for Hostname { > >> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > >> + self.0.fmt(f) > >> + } > >> +} > >> + > >> +impl Hostname { > >> + /// Constructs a new hostname from a string > >> + /// > >> + /// This function accepts characters in any case, but the resulting hostname will be > >> + /// lowercased. > >> + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { > > > > Nit: I'd recommend using a `check()` function which does not create the > > `Hostname` itself, because then: > > > > - in `FromStr` we know we have a reference (&str) and need to clone. > > - We could add a `TryFrom<&str>` wich just uses `.parse()` > > - We could add a `TryFrom<String>` which avoids the clone. > > > > But... > > > >> + let name: &str = name_ref.as_ref(); > >> + > >> + if name.is_empty() || name.len() > 63 { > >> + return Err(HostnameError::InvalidLength); > >> + } > >> + > >> + if !(name.starts_with(|c: char| c.is_ascii_alphanumeric()) > >> + && name.ends_with(|c: char| c.is_ascii_alphanumeric())) { > >> + return Err(HostnameError::InvalidFormat); > >> + } > >> + > >> + if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { > >> + return Err(HostnameError::InvalidFormat); > >> + } (Oh btw. if we move this up above the upper check, that one could be shortened to `.{starts,ends}_with('-')`, no?) > >> + > >> + if name.chars().all(|c| c.is_ascii_digit()) { > >> + return Err(HostnameError::InvalidFormat); > >> + } > >> + > >> + Ok(Self(name.to_lowercase())) > > > > ...do we really want/need to do this? (Note that if we really do this, > > it should IMO be documented on the *type*, too, not just this method.) > > The idea was that, since hostnames are treated case-insensitively by > basically every application, it would make no sense to create two > *different* hostnames, that would be the same in practice. It's > something we could maybe move to a (Partial)Eq implementation and remove > the lowercasing here? > > > I mean, I'm not completely against it, but if we "normalize", would we > > not technically also have to punycode non-ascii hostnames? > > > > (But at the very least it seems that punycode does case-folding... at > > least a quick online-punycode conversion tool seems to convert both 'Ө' > > and 'ө' to 'xn--j6a') > > I see your point, but I decided to stick to what the installer allows > for hostnames, which is ASCII-only characters without doing any punycode > conversion (correct me if I'm wrong there, but I don't remember this > being the case). My main point is actually that this crate has a generic "network-types" name, which means someone may end up using it for *generic* code and suddenly things will fail. So I'd strongly suggest a `debian` submodule or something. _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-04 7:51 ` Stefan Hanreich 2025-04-04 9:22 ` Wolfgang Bumiller @ 2025-04-04 11:26 ` Stefan Hanreich 2025-04-04 11:41 ` Wolfgang Bumiller 1 sibling, 1 reply; 11+ messages in thread From: Stefan Hanreich @ 2025-04-04 11:26 UTC (permalink / raw) To: Wolfgang Bumiller; +Cc: pve-devel On 4/4/25 09:51, Stefan Hanreich wrote: >>> +/// >>> +/// It checks for the following conditions: >>> +/// * At most 63 characters long. >>> +/// * It must not start or end with a hyphen. >>> +/// * Must only contain ASCII alphanumeric characters as well as hyphens. >>> +/// * It must not be purely numerical. >>> +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] >>> +pub struct Hostname(String); >>> + >>> +impl std::str::FromStr for Hostname { >>> + type Err = HostnameError; >>> + >>> + fn from_str(hostname: &str) -> Result<Self, Self::Err> { >>> + Self::new(hostname) >>> + } >>> +} >>> + >>> +impl AsRef<str> for Hostname { >>> + fn as_ref(&self) -> &str { >>> + &self.0 >>> + } >>> +} >>> + >>> +impl Display for Hostname { >>> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { >>> + self.0.fmt(f) >>> + } >>> +} >>> + >>> +impl Hostname { >>> + /// Constructs a new hostname from a string >>> + /// >>> + /// This function accepts characters in any case, but the resulting hostname will be >>> + /// lowercased. >>> + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { >> >> Nit: I'd recommend using a `check()` function which does not create the >> `Hostname` itself, because then: >> >> - in `FromStr` we know we have a reference (&str) and need to clone. >> - We could add a `TryFrom<&str>` wich just uses `.parse()` >> - We could add a `TryFrom<String>` which avoids the clone. What about a constructor that just takes String (if a function needs to own the value it should demand a String anyway) and then calling that constructor from the proposed trait implementations? Maybe something more generic than String as a param, but that could usually be tacked on later without breaking the API. _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type 2025-04-04 11:26 ` Stefan Hanreich @ 2025-04-04 11:41 ` Wolfgang Bumiller 0 siblings, 0 replies; 11+ messages in thread From: Wolfgang Bumiller @ 2025-04-04 11:41 UTC (permalink / raw) To: Stefan Hanreich; +Cc: pve-devel On Fri, Apr 04, 2025 at 01:26:46PM +0200, Stefan Hanreich wrote: > > > On 4/4/25 09:51, Stefan Hanreich wrote: > >>> +/// > >>> +/// It checks for the following conditions: > >>> +/// * At most 63 characters long. > >>> +/// * It must not start or end with a hyphen. > >>> +/// * Must only contain ASCII alphanumeric characters as well as hyphens. > >>> +/// * It must not be purely numerical. > >>> +#[derive(Debug, Deserialize, Serialize, Clone, Eq, Hash, PartialOrd, Ord, PartialEq)] > >>> +pub struct Hostname(String); > >>> + > >>> +impl std::str::FromStr for Hostname { > >>> + type Err = HostnameError; > >>> + > >>> + fn from_str(hostname: &str) -> Result<Self, Self::Err> { > >>> + Self::new(hostname) > >>> + } > >>> +} > >>> + > >>> +impl AsRef<str> for Hostname { > >>> + fn as_ref(&self) -> &str { > >>> + &self.0 > >>> + } > >>> +} > >>> + > >>> +impl Display for Hostname { > >>> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > >>> + self.0.fmt(f) > >>> + } > >>> +} > >>> + > >>> +impl Hostname { > >>> + /// Constructs a new hostname from a string > >>> + /// > >>> + /// This function accepts characters in any case, but the resulting hostname will be > >>> + /// lowercased. > >>> + pub fn new(name_ref: impl AsRef<str>) -> Result<Self, HostnameError> { > >> > >> Nit: I'd recommend using a `check()` function which does not create the > >> `Hostname` itself, because then: > >> > >> - in `FromStr` we know we have a reference (&str) and need to clone. > >> - We could add a `TryFrom<&str>` wich just uses `.parse()` > >> - We could add a `TryFrom<String>` which avoids the clone. > > What about a constructor that just takes String (if a function needs to > own the value it should demand a String anyway) and then calling that > constructor from the proposed trait implementations? Maybe something > more generic than String as a param, but that could usually be tacked on > later without breaking the API. I suppose that's fine. We'd clone in the error case, but that's not the expected case anyway. _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [pve-devel] [PATCH proxmox-ve-rs v3 1/1] ve-config: move types to proxmox-network-types 2025-04-01 14:52 [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type Stefan Hanreich @ 2025-04-01 14:52 ` Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate " Stefan Hanreich 2025-04-02 9:06 ` [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Christoph Heiss 3 siblings, 0 replies; 11+ messages in thread From: Stefan Hanreich @ 2025-04-01 14:52 UTC (permalink / raw) To: pve-devel Some of the types defined in this crate have been moved to proxmox-network-types so they can be re-used across crates. This is a preparation for the fabrics patch series, where those types will get used by addtional, new, crates. Remove the types that have been moved and adjust the imports for them accordingly. This patch has no functional changes, except for one in the firewall ipset type. There we had a blanket From implementation: impl<T: Into<Cidr>> From<T> for IpsetAddress This is no longer possible due to the orphan rule, so it has been replaced with a simple From<Cidr> implementation. All call sites that used the blanket implementation for the trait have been adjusted as well. Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> --- Notes: This depends on the changes in the proxmox repository. Cargo.toml | 2 + proxmox-ve-config/Cargo.toml | 1 + proxmox-ve-config/debian/control | 6 +- proxmox-ve-config/src/firewall/cluster.rs | 3 +- proxmox-ve-config/src/firewall/ct_helper.rs | 2 +- proxmox-ve-config/src/firewall/host.rs | 4 +- .../src/firewall/types/address.rs | 1395 +---------------- proxmox-ve-config/src/firewall/types/alias.rs | 2 +- proxmox-ve-config/src/firewall/types/ipset.rs | 8 +- proxmox-ve-config/src/firewall/types/mod.rs | 1 - proxmox-ve-config/src/firewall/types/rule.rs | 5 +- .../src/firewall/types/rule_match.rs | 6 +- proxmox-ve-config/src/guest/vm.rs | 87 +- proxmox-ve-config/src/host/utils.rs | 2 +- proxmox-ve-config/src/sdn/config.rs | 9 +- proxmox-ve-config/src/sdn/ipam.rs | 11 +- proxmox-ve-config/src/sdn/mod.rs | 3 +- proxmox-ve-config/tests/sdn/main.rs | 11 +- 18 files changed, 60 insertions(+), 1498 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dc7f312..b6e6df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ homepage = "https://proxmox.com" exclude = [ "debian" ] rust-version = "1.82" +[workspace.dependencies] +proxmox-network-types = { version = "0.1" } diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml index b20ced1..2f3544b 100644 --- a/proxmox-ve-config/Cargo.toml +++ b/proxmox-ve-config/Cargo.toml @@ -17,6 +17,7 @@ serde_json = "1" serde_plain = "1" serde_with = "3" +proxmox-network-types = { workspace = true } proxmox-schema = "4" proxmox-sys = "0.6.4" proxmox-sortable-macro = "0.1.3" diff --git a/proxmox-ve-config/debian/control b/proxmox-ve-config/debian/control index 60ebcbc..d498520 100644 --- a/proxmox-ve-config/debian/control +++ b/proxmox-ve-config/debian/control @@ -2,13 +2,14 @@ Source: rust-proxmox-ve-config Section: rust Priority: optional Build-Depends: debhelper-compat (= 13), - dh-sequence-cargo, - cargo:native <!nocheck>, + dh-sequence-cargo +Build-Depends-Arch: cargo:native <!nocheck>, rustc:native <!nocheck>, libstd-rust-dev <!nocheck>, librust-anyhow-1+default-dev <!nocheck>, librust-log-0.4+default-dev <!nocheck>, librust-nix-0.26+default-dev <!nocheck>, + librust-proxmox-network-types-0.1+default-dev <!nocheck>, librust-proxmox-schema-4+default-dev <!nocheck>, librust-proxmox-sortable-macro-0.1+default-dev (>= 0.1.3-~~) <!nocheck>, librust-proxmox-sys-0.6+default-dev (>= 0.6.4-~~) <!nocheck>, @@ -33,6 +34,7 @@ Depends: librust-anyhow-1+default-dev, librust-log-0.4+default-dev, librust-nix-0.26+default-dev, + librust-proxmox-network-types-0.1+default-dev, librust-proxmox-schema-4+default-dev, librust-proxmox-sortable-macro-0.1+default-dev (>= 0.1.3-~~), librust-proxmox-sys-0.6+default-dev (>= 0.6.4-~~), diff --git a/proxmox-ve-config/src/firewall/cluster.rs b/proxmox-ve-config/src/firewall/cluster.rs index ce3dd53..8879a69 100644 --- a/proxmox-ve-config/src/firewall/cluster.rs +++ b/proxmox-ve-config/src/firewall/cluster.rs @@ -134,6 +134,8 @@ pub struct Options { #[cfg(test)] mod tests { + use proxmox_network_types::ip_address::Cidr; + use crate::firewall::types::{ address::IpList, alias::{AliasName, AliasScope}, @@ -143,7 +145,6 @@ mod tests { rule_match::{ Icmpv6, Icmpv6Code, IpAddrMatch, IpMatch, Ports, Protocol, RuleMatch, Tcp, Udp, }, - Cidr, }; use super::*; diff --git a/proxmox-ve-config/src/firewall/ct_helper.rs b/proxmox-ve-config/src/firewall/ct_helper.rs index 40e4fee..57fe9aa 100644 --- a/proxmox-ve-config/src/firewall/ct_helper.rs +++ b/proxmox-ve-config/src/firewall/ct_helper.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::sync::OnceLock; -use crate::firewall::types::address::Family; +use proxmox_network_types::ip_address::Family; use crate::firewall::types::rule_match::{Ports, Protocol, Tcp, Udp}; #[derive(Clone, Debug, Deserialize)] diff --git a/proxmox-ve-config/src/firewall/host.rs b/proxmox-ve-config/src/firewall/host.rs index 394896c..dcb49bb 100644 --- a/proxmox-ve-config/src/firewall/host.rs +++ b/proxmox-ve-config/src/firewall/host.rs @@ -4,13 +4,15 @@ use std::net::IpAddr; use anyhow::{bail, Error}; use serde::Deserialize; +use proxmox_network_types::ip_address::Cidr; + use crate::host::utils::{host_ips, network_interface_cidrs}; use proxmox_sys::nodename; use crate::firewall::parse; use crate::firewall::types::log::LogLevel; use crate::firewall::types::rule::Direction; -use crate::firewall::types::{Alias, Cidr, Rule}; +use crate::firewall::types::{Alias, Rule}; /// default setting for the enabled key pub const HOST_ENABLED_DEFAULT: bool = true; diff --git a/proxmox-ve-config/src/firewall/types/address.rs b/proxmox-ve-config/src/firewall/types/address.rs index 9b73d3d..548b813 100644 --- a/proxmox-ve-config/src/firewall/types/address.rs +++ b/proxmox-ve-config/src/firewall/types/address.rs @@ -1,600 +1,9 @@ -use std::fmt::{self, Display}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::fmt; use std::ops::Deref; -use anyhow::{bail, format_err, Error}; -use serde_with::{DeserializeFromStr, SerializeDisplay}; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Family { - V4, - V6, -} - -impl Family { - pub fn is_ipv4(&self) -> bool { - *self == Self::V4 - } - - pub fn is_ipv6(&self) -> bool { - *self == Self::V6 - } -} - -impl fmt::Display for Family { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Family::V4 => f.write_str("Ipv4"), - Family::V6 => f.write_str("Ipv6"), - } - } -} - -#[derive( - Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, -)] -pub enum Cidr { - Ipv4(Ipv4Cidr), - Ipv6(Ipv6Cidr), -} - -impl Cidr { - pub fn new_v4(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, Error> { - Ok(Cidr::Ipv4(Ipv4Cidr::new(addr, mask)?)) - } - - pub fn new_v6(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, Error> { - Ok(Cidr::Ipv6(Ipv6Cidr::new(addr, mask)?)) - } - - pub const fn family(&self) -> Family { - match self { - Cidr::Ipv4(_) => Family::V4, - Cidr::Ipv6(_) => Family::V6, - } - } - - pub fn is_ipv4(&self) -> bool { - matches!(self, Cidr::Ipv4(_)) - } - - pub fn is_ipv6(&self) -> bool { - matches!(self, Cidr::Ipv6(_)) - } - - pub fn contains_address(&self, ip: &IpAddr) -> bool { - match (self, ip) { - (Cidr::Ipv4(cidr), IpAddr::V4(ip)) => cidr.contains_address(ip), - (Cidr::Ipv6(cidr), IpAddr::V6(ip)) => cidr.contains_address(ip), - _ => false, - } - } -} - -impl fmt::Display for Cidr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Ipv4(ip) => f.write_str(ip.to_string().as_str()), - Self::Ipv6(ip) => f.write_str(ip.to_string().as_str()), - } - } -} - -impl std::str::FromStr for Cidr { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Error> { - if let Ok(ip) = s.parse::<Ipv4Cidr>() { - return Ok(Cidr::Ipv4(ip)); - } - - if let Ok(ip) = s.parse::<Ipv6Cidr>() { - return Ok(Cidr::Ipv6(ip)); - } - - bail!("invalid ip address or CIDR: {s:?}"); - } -} - -impl From<Ipv4Cidr> for Cidr { - fn from(cidr: Ipv4Cidr) -> Self { - Cidr::Ipv4(cidr) - } -} - -impl From<Ipv6Cidr> for Cidr { - fn from(cidr: Ipv6Cidr) -> Self { - Cidr::Ipv6(cidr) - } -} - -impl From<IpAddr> for Cidr { - fn from(value: IpAddr) -> Self { - match value { - IpAddr::V4(addr) => Ipv4Cidr::from(addr).into(), - IpAddr::V6(addr) => Ipv6Cidr::from(addr).into(), - } - } -} - -const IPV4_LENGTH: u8 = 32; - -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct Ipv4Cidr { - addr: Ipv4Addr, - mask: u8, -} - -impl Ipv4Cidr { - pub fn new(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, Error> { - if mask > 32 { - bail!("mask out of range for ipv4 cidr ({mask})"); - } - - Ok(Self { - addr: addr.into(), - mask, - }) - } - - pub fn contains_address(&self, other: &Ipv4Addr) -> bool { - let bits = u32::from_be_bytes(self.addr.octets()); - let other_bits = u32::from_be_bytes(other.octets()); - - let shift_amount: u32 = IPV4_LENGTH.saturating_sub(self.mask).into(); - - bits.checked_shr(shift_amount).unwrap_or(0) - == other_bits.checked_shr(shift_amount).unwrap_or(0) - } - - pub fn address(&self) -> &Ipv4Addr { - &self.addr - } - - pub fn mask(&self) -> u8 { - self.mask - } -} - -impl<T: Into<Ipv4Addr>> From<T> for Ipv4Cidr { - fn from(value: T) -> Self { - Self { - addr: value.into(), - mask: 32, - } - } -} - -impl std::str::FromStr for Ipv4Cidr { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Error> { - Ok(match s.find('/') { - None => Self { - addr: s.parse()?, - mask: 32, - }, - Some(pos) => { - let mask: u8 = s[(pos + 1)..] - .parse() - .map_err(|_| format_err!("invalid mask in ipv4 cidr: {s:?}"))?; - - Self::new(s[..pos].parse::<Ipv4Addr>()?, mask)? - } - }) - } -} - -impl fmt::Display for Ipv4Cidr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}/{}", &self.addr, self.mask) - } -} - -const IPV6_LENGTH: u8 = 128; - -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct Ipv6Cidr { - addr: Ipv6Addr, - mask: u8, -} - -impl Ipv6Cidr { - pub fn new(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, Error> { - if mask > IPV6_LENGTH { - bail!("mask out of range for ipv6 cidr"); - } - - Ok(Self { - addr: addr.into(), - mask, - }) - } - - pub fn contains_address(&self, other: &Ipv6Addr) -> bool { - let bits = u128::from_be_bytes(self.addr.octets()); - let other_bits = u128::from_be_bytes(other.octets()); - - let shift_amount: u32 = IPV6_LENGTH.saturating_sub(self.mask).into(); - - bits.checked_shr(shift_amount).unwrap_or(0) - == other_bits.checked_shr(shift_amount).unwrap_or(0) - } - - pub fn address(&self) -> &Ipv6Addr { - &self.addr - } - - pub fn mask(&self) -> u8 { - self.mask - } -} - -impl std::str::FromStr for Ipv6Cidr { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Error> { - Ok(match s.find('/') { - None => Self { - addr: s.parse()?, - mask: 128, - }, - Some(pos) => { - let mask: u8 = s[(pos + 1)..] - .parse() - .map_err(|_| format_err!("invalid mask in ipv6 cidr: {s:?}"))?; - - Self::new(s[..pos].parse::<Ipv6Addr>()?, mask)? - } - }) - } -} - -impl fmt::Display for Ipv6Cidr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}/{}", &self.addr, self.mask) - } -} - -impl<T: Into<Ipv6Addr>> From<T> for Ipv6Cidr { - fn from(addr: T) -> Self { - Self { - addr: addr.into(), - mask: 128, - } - } -} - -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub enum IpRangeError { - MismatchedFamilies, - StartGreaterThanLast, - InvalidFormat, -} - -impl std::error::Error for IpRangeError {} - -impl Display for IpRangeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - IpRangeError::MismatchedFamilies => "mismatched ip address families", - IpRangeError::StartGreaterThanLast => "start is greater than last", - IpRangeError::InvalidFormat => "invalid ip range format", - }) - } -} - -/// Represents a range of IPv4 or IPv6 addresses. -/// -/// For more information see [`AddressRange`] -#[derive( - Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, -)] -pub enum IpRange { - V4(AddressRange<Ipv4Addr>), - V6(AddressRange<Ipv6Addr>), -} - -impl IpRange { - /// Returns the family of the IpRange. - pub fn family(&self) -> Family { - match self { - IpRange::V4(_) => Family::V4, - IpRange::V6(_) => Family::V6, - } - } - - /// Creates a new [`IpRange`] from two [`IpAddr`]. - /// - /// # Errors - /// - /// This function will return an error if start and last IP address are not from the same family. - pub fn new(start: impl Into<IpAddr>, last: impl Into<IpAddr>) -> Result<Self, IpRangeError> { - match (start.into(), last.into()) { - (IpAddr::V4(start), IpAddr::V4(last)) => Self::new_v4(start, last), - (IpAddr::V6(start), IpAddr::V6(last)) => Self::new_v6(start, last), - _ => Err(IpRangeError::MismatchedFamilies), - } - } - - /// construct a new Ipv4 Range - pub fn new_v4( - start: impl Into<Ipv4Addr>, - last: impl Into<Ipv4Addr>, - ) -> Result<Self, IpRangeError> { - Ok(IpRange::V4(AddressRange::new_v4(start, last)?)) - } - - pub fn new_v6( - start: impl Into<Ipv6Addr>, - last: impl Into<Ipv6Addr>, - ) -> Result<Self, IpRangeError> { - Ok(IpRange::V6(AddressRange::new_v6(start, last)?)) - } - - /// Converts an IpRange into the minimal amount of CIDRs. - /// - /// see the concrete implementations of [`AddressRange<Ipv4Addr>`] or [`AddressRange<Ipv6Addr>`] - /// respectively - pub fn to_cidrs(&self) -> Vec<Cidr> { - match self { - IpRange::V4(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), - IpRange::V6(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), - } - } -} - -impl std::str::FromStr for IpRange { - type Err = IpRangeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - if let Ok(range) = s.parse() { - return Ok(IpRange::V4(range)); - } - - if let Ok(range) = s.parse() { - return Ok(IpRange::V6(range)); - } - - Err(IpRangeError::InvalidFormat) - } -} - -impl fmt::Display for IpRange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IpRange::V4(range) => range.fmt(f), - IpRange::V6(range) => range.fmt(f), - } - } -} - -/// Represents a range of IP addresses from start to last. -/// -/// This type is for encapsulation purposes for the [`IpRange`] enum and should be instantiated via -/// that enum. -/// -/// # Invariants -/// -/// * start and last have the same IP address family -/// * start is less than or equal to last -/// -/// # Textual representation -/// -/// Two IP addresses separated by a hyphen, e.g.: `127.0.0.1-127.0.0.255` -#[derive( - Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, -)] -pub struct AddressRange<T> { - start: T, - last: T, -} - -impl AddressRange<Ipv4Addr> { - pub(crate) fn new_v4( - start: impl Into<Ipv4Addr>, - last: impl Into<Ipv4Addr>, - ) -> Result<AddressRange<Ipv4Addr>, IpRangeError> { - let (start, last) = (start.into(), last.into()); - - if start > last { - return Err(IpRangeError::StartGreaterThanLast); - } - - Ok(Self { start, last }) - } - - /// Returns the minimum amount of CIDRs that exactly represent the range - /// - /// The idea behind this algorithm is as follows: - /// - /// Start iterating with current = start of the IP range - /// - /// Find two netmasks - /// * The largest CIDR that the current IP can be the first of - /// * The largest CIDR that *only* contains IPs from current - last - /// - /// Add the smaller of the two CIDRs to our result and current to the first IP that is in - /// the range but not in the CIDR we just added. Proceed until we reached the last of the IP - /// range. - /// - pub fn to_cidrs(&self) -> Vec<Ipv4Cidr> { - let mut cidrs = Vec::new(); - - let mut current = u32::from_be_bytes(self.start.octets()); - let last = u32::from_be_bytes(self.last.octets()); - - if current == last { - // valid Ipv4 since netmask is 32 - cidrs.push(Ipv4Cidr::new(current, 32).unwrap()); - return cidrs; - } - - // special case this, since this is the only possibility of overflow - // when calculating delta_min_mask - makes everything a lot easier - if current == u32::MIN && last == u32::MAX { - // valid Ipv4 since it is `0.0.0.0/0` - cidrs.push(Ipv4Cidr::new(current, 0).unwrap()); - return cidrs; - } - - while current <= last { - // netmask of largest CIDR that current IP can be the first of - // cast is safe, because trailing zeroes can at most be 32 - let current_max_mask = IPV4_LENGTH - (current.trailing_zeros() as u8); - - // netmask of largest CIDR that *only* contains IPs of the remaining range - // is at most 32 due to unwrap_or returning 32 and ilog2 being at most 31 - let delta_min_mask = ((last - current) + 1) // safe due to special case above - .checked_ilog2() // should never occur due to special case, but for good measure - .map(|mask| IPV4_LENGTH - mask as u8) - .unwrap_or(IPV4_LENGTH); - - // at most 32, due to current/delta being at most 32 - let netmask = u8::max(current_max_mask, delta_min_mask); - - // netmask is at most 32, therefore safe to unwrap - cidrs.push(Ipv4Cidr::new(current, netmask).unwrap()); - - let delta = 2u32.saturating_pow((IPV4_LENGTH - netmask).into()); - - if let Some(result) = current.checked_add(delta) { - current = result - } else { - // we reached the end of IP address space - break; - } - } - - cidrs - } -} - -impl AddressRange<Ipv6Addr> { - pub(crate) fn new_v6( - start: impl Into<Ipv6Addr>, - last: impl Into<Ipv6Addr>, - ) -> Result<AddressRange<Ipv6Addr>, IpRangeError> { - let (start, last) = (start.into(), last.into()); - - if start > last { - return Err(IpRangeError::StartGreaterThanLast); - } - - Ok(Self { start, last }) - } - - /// Returns the minimum amount of CIDRs that exactly represent the [`AddressRange`]. - /// - /// This function works analogous to the IPv4 version, please refer to the respective - /// documentation of [`AddressRange<Ipv4Addr>`] - pub fn to_cidrs(&self) -> Vec<Ipv6Cidr> { - let mut cidrs = Vec::new(); - - let mut current = u128::from_be_bytes(self.start.octets()); - let last = u128::from_be_bytes(self.last.octets()); - - if current == last { - // valid Ipv6 since netmask is 128 - cidrs.push(Ipv6Cidr::new(current, 128).unwrap()); - return cidrs; - } - - // special case this, since this is the only possibility of overflow - // when calculating delta_min_mask - makes everything a lot easier - if current == u128::MIN && last == u128::MAX { - // valid Ipv6 since it is `::/0` - cidrs.push(Ipv6Cidr::new(current, 0).unwrap()); - return cidrs; - } - - while current <= last { - // netmask of largest CIDR that current IP can be the first of - // cast is safe, because trailing zeroes can at most be 128 - let current_max_mask = IPV6_LENGTH - (current.trailing_zeros() as u8); - - // netmask of largest CIDR that *only* contains IPs of the remaining range - // is at most 128 due to unwrap_or returning 128 and ilog2 being at most 31 - let delta_min_mask = ((last - current) + 1) // safe due to special case above - .checked_ilog2() // should never occur due to special case, but for good measure - .map(|mask| IPV6_LENGTH - mask as u8) - .unwrap_or(IPV6_LENGTH); - - // at most 128, due to current/delta being at most 128 - let netmask = u8::max(current_max_mask, delta_min_mask); - - // netmask is at most 128, therefore safe to unwrap - cidrs.push(Ipv6Cidr::new(current, netmask).unwrap()); - - let delta = 2u128.saturating_pow((IPV6_LENGTH - netmask).into()); - - if let Some(result) = current.checked_add(delta) { - current = result - } else { - // we reached the end of IP address space - break; - } - } - - cidrs - } -} - -impl<T> AddressRange<T> { - pub fn start(&self) -> &T { - &self.start - } - - pub fn last(&self) -> &T { - &self.last - } -} - -impl std::str::FromStr for AddressRange<Ipv4Addr> { - type Err = IpRangeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - if let Some((start, last)) = s.split_once('-') { - let start_address = start - .parse::<Ipv4Addr>() - .map_err(|_| IpRangeError::InvalidFormat)?; - - let last_address = last - .parse::<Ipv4Addr>() - .map_err(|_| IpRangeError::InvalidFormat)?; - - return Self::new_v4(start_address, last_address); - } - - Err(IpRangeError::InvalidFormat) - } -} - -impl std::str::FromStr for AddressRange<Ipv6Addr> { - type Err = IpRangeError; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - if let Some((start, last)) = s.split_once('-') { - let start_address = start - .parse::<Ipv6Addr>() - .map_err(|_| IpRangeError::InvalidFormat)?; - - let last_address = last - .parse::<Ipv6Addr>() - .map_err(|_| IpRangeError::InvalidFormat)?; - - return Self::new_v6(start_address, last_address); - } - - Err(IpRangeError::InvalidFormat) - } -} - -impl<T: fmt::Display> fmt::Display for AddressRange<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}-{}", self.start, self.last) - } -} +use anyhow::{bail, Error}; +use proxmox_network_types::ip_address::{Cidr, Family, IpRange}; +use serde_with::DeserializeFromStr; #[derive(Clone, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] @@ -741,84 +150,6 @@ impl IpList { #[cfg(test)] mod tests { use super::*; - use std::net::{Ipv4Addr, Ipv6Addr}; - - #[test] - fn test_v4_cidr() { - let mut cidr: Ipv4Cidr = "0.0.0.0/0".parse().expect("valid IPv4 CIDR"); - - assert_eq!(cidr.addr, Ipv4Addr::new(0, 0, 0, 0)); - assert_eq!(cidr.mask, 0); - - assert!(cidr.contains_address(&Ipv4Addr::new(0, 0, 0, 0))); - assert!(cidr.contains_address(&Ipv4Addr::new(255, 255, 255, 255))); - - cidr = "192.168.100.1".parse().expect("valid IPv4 CIDR"); - - assert_eq!(cidr.addr, Ipv4Addr::new(192, 168, 100, 1)); - assert_eq!(cidr.mask, 32); - - assert!(cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 1))); - assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 2))); - assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 0))); - - cidr = "10.100.5.0/24".parse().expect("valid IPv4 CIDR"); - - assert_eq!(cidr.mask, 24); - - assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 0))); - assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 1))); - assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 100))); - assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 255))); - assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 4, 255))); - assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 6, 0))); - - "0.0.0.0/-1".parse::<Ipv4Cidr>().unwrap_err(); - "0.0.0.0/33".parse::<Ipv4Cidr>().unwrap_err(); - "256.256.256.256/10".parse::<Ipv4Cidr>().unwrap_err(); - - "fe80::1/64".parse::<Ipv4Cidr>().unwrap_err(); - "qweasd".parse::<Ipv4Cidr>().unwrap_err(); - "".parse::<Ipv4Cidr>().unwrap_err(); - } - - #[test] - fn test_v6_cidr() { - let mut cidr: Ipv6Cidr = "abab::1/64".parse().expect("valid IPv6 CIDR"); - - assert_eq!(cidr.addr, Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 1)); - assert_eq!(cidr.mask, 64); - - assert!(cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 0))); - assert!(cidr.contains_address(&Ipv6Addr::new( - 0xABAB, 0, 0, 0, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA - ))); - assert!(cidr.contains_address(&Ipv6Addr::new( - 0xABAB, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF - ))); - assert!(!cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 1, 0, 0, 0, 0))); - assert!(!cidr.contains_address(&Ipv6Addr::new( - 0xABAA, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF - ))); - - cidr = "eeee::1".parse().expect("valid IPv6 CIDR"); - - assert_eq!(cidr.mask, 128); - - assert!(cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 1))); - assert!(!cidr.contains_address(&Ipv6Addr::new( - 0xEEED, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF - ))); - assert!(!cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 0))); - - "eeee::1/-1".parse::<Ipv6Cidr>().unwrap_err(); - "eeee::1/129".parse::<Ipv6Cidr>().unwrap_err(); - "gggg::1/64".parse::<Ipv6Cidr>().unwrap_err(); - - "192.168.0.1".parse::<Ipv6Cidr>().unwrap_err(); - "qweasd".parse::<Ipv6Cidr>().unwrap_err(); - "".parse::<Ipv6Cidr>().unwrap_err(); - } #[test] fn test_parse_ip_entry() { @@ -942,721 +273,5 @@ mod tests { ]) .expect_err("cannot mix ip families in ip list"); } - - #[test] - fn test_ip_range() { - IpRange::new([10, 0, 0, 2], [10, 0, 0, 1]).unwrap_err(); - - IpRange::new( - [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], - [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], - ) - .unwrap_err(); - - let v4_range = IpRange::new([10, 0, 0, 0], [10, 0, 0, 100]).unwrap(); - assert_eq!(v4_range.family(), Family::V4); - - let v6_range = IpRange::new( - [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], - [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], - ) - .unwrap(); - assert_eq!(v6_range.family(), Family::V6); - - "10.0.0.1-10.0.0.100".parse::<IpRange>().unwrap(); - "2001:db8::1-2001:db8::f".parse::<IpRange>().unwrap(); - - "10.0.0.1-2001:db8::1000".parse::<IpRange>().unwrap_err(); - "2001:db8::1-192.168.0.2".parse::<IpRange>().unwrap_err(); - - "10.0.0.1-10.0.0.0".parse::<IpRange>().unwrap_err(); - "2001:db8::1-2001:db8::0".parse::<IpRange>().unwrap_err(); - } - - #[test] - fn test_ipv4_to_cidrs() { - let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 100]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([192, 168, 0, 100], 32).unwrap()], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 200]).unwrap(); - - assert_eq!( - [ - Ipv4Cidr::new([192, 168, 0, 100], 30).unwrap(), - Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), - Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), - Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 200]).unwrap(); - - assert_eq!( - [ - Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), - Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), - Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), - Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), - Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 101]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap()], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 201]).unwrap(); - - assert_eq!( - [ - Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), - Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), - Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), - Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), - Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), - Ipv4Cidr::new([192, 168, 0, 200], 31).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([192, 168, 0, 0], [192, 168, 0, 255]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([192, 168, 0, 0], 24).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 255]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([0, 0, 0, 0], 0).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([0, 0, 0, 1], [255, 255, 255, 255]).unwrap(); - - assert_eq!( - [ - Ipv4Cidr::new([0, 0, 0, 1], 32).unwrap(), - Ipv4Cidr::new([0, 0, 0, 2], 31).unwrap(), - Ipv4Cidr::new([0, 0, 0, 4], 30).unwrap(), - Ipv4Cidr::new([0, 0, 0, 8], 29).unwrap(), - Ipv4Cidr::new([0, 0, 0, 16], 28).unwrap(), - Ipv4Cidr::new([0, 0, 0, 32], 27).unwrap(), - Ipv4Cidr::new([0, 0, 0, 64], 26).unwrap(), - Ipv4Cidr::new([0, 0, 0, 128], 25).unwrap(), - Ipv4Cidr::new([0, 0, 1, 0], 24).unwrap(), - Ipv4Cidr::new([0, 0, 2, 0], 23).unwrap(), - Ipv4Cidr::new([0, 0, 4, 0], 22).unwrap(), - Ipv4Cidr::new([0, 0, 8, 0], 21).unwrap(), - Ipv4Cidr::new([0, 0, 16, 0], 20).unwrap(), - Ipv4Cidr::new([0, 0, 32, 0], 19).unwrap(), - Ipv4Cidr::new([0, 0, 64, 0], 18).unwrap(), - Ipv4Cidr::new([0, 0, 128, 0], 17).unwrap(), - Ipv4Cidr::new([0, 1, 0, 0], 16).unwrap(), - Ipv4Cidr::new([0, 2, 0, 0], 15).unwrap(), - Ipv4Cidr::new([0, 4, 0, 0], 14).unwrap(), - Ipv4Cidr::new([0, 8, 0, 0], 13).unwrap(), - Ipv4Cidr::new([0, 16, 0, 0], 12).unwrap(), - Ipv4Cidr::new([0, 32, 0, 0], 11).unwrap(), - Ipv4Cidr::new([0, 64, 0, 0], 10).unwrap(), - Ipv4Cidr::new([0, 128, 0, 0], 9).unwrap(), - Ipv4Cidr::new([1, 0, 0, 0], 8).unwrap(), - Ipv4Cidr::new([2, 0, 0, 0], 7).unwrap(), - Ipv4Cidr::new([4, 0, 0, 0], 6).unwrap(), - Ipv4Cidr::new([8, 0, 0, 0], 5).unwrap(), - Ipv4Cidr::new([16, 0, 0, 0], 4).unwrap(), - Ipv4Cidr::new([32, 0, 0, 0], 3).unwrap(), - Ipv4Cidr::new([64, 0, 0, 0], 2).unwrap(), - Ipv4Cidr::new([128, 0, 0, 0], 1).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 254]).unwrap(); - - assert_eq!( - [ - Ipv4Cidr::new([0, 0, 0, 0], 1).unwrap(), - Ipv4Cidr::new([128, 0, 0, 0], 2).unwrap(), - Ipv4Cidr::new([192, 0, 0, 0], 3).unwrap(), - Ipv4Cidr::new([224, 0, 0, 0], 4).unwrap(), - Ipv4Cidr::new([240, 0, 0, 0], 5).unwrap(), - Ipv4Cidr::new([248, 0, 0, 0], 6).unwrap(), - Ipv4Cidr::new([252, 0, 0, 0], 7).unwrap(), - Ipv4Cidr::new([254, 0, 0, 0], 8).unwrap(), - Ipv4Cidr::new([255, 0, 0, 0], 9).unwrap(), - Ipv4Cidr::new([255, 128, 0, 0], 10).unwrap(), - Ipv4Cidr::new([255, 192, 0, 0], 11).unwrap(), - Ipv4Cidr::new([255, 224, 0, 0], 12).unwrap(), - Ipv4Cidr::new([255, 240, 0, 0], 13).unwrap(), - Ipv4Cidr::new([255, 248, 0, 0], 14).unwrap(), - Ipv4Cidr::new([255, 252, 0, 0], 15).unwrap(), - Ipv4Cidr::new([255, 254, 0, 0], 16).unwrap(), - Ipv4Cidr::new([255, 255, 0, 0], 17).unwrap(), - Ipv4Cidr::new([255, 255, 128, 0], 18).unwrap(), - Ipv4Cidr::new([255, 255, 192, 0], 19).unwrap(), - Ipv4Cidr::new([255, 255, 224, 0], 20).unwrap(), - Ipv4Cidr::new([255, 255, 240, 0], 21).unwrap(), - Ipv4Cidr::new([255, 255, 248, 0], 22).unwrap(), - Ipv4Cidr::new([255, 255, 252, 0], 23).unwrap(), - Ipv4Cidr::new([255, 255, 254, 0], 24).unwrap(), - Ipv4Cidr::new([255, 255, 255, 0], 25).unwrap(), - Ipv4Cidr::new([255, 255, 255, 128], 26).unwrap(), - Ipv4Cidr::new([255, 255, 255, 192], 27).unwrap(), - Ipv4Cidr::new([255, 255, 255, 224], 28).unwrap(), - Ipv4Cidr::new([255, 255, 255, 240], 29).unwrap(), - Ipv4Cidr::new([255, 255, 255, 248], 30).unwrap(), - Ipv4Cidr::new([255, 255, 255, 252], 31).unwrap(), - Ipv4Cidr::new([255, 255, 255, 254], 32).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([0, 0, 0, 0], [0, 0, 0, 0]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([0, 0, 0, 0], 32).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v4([255, 255, 255, 255], [255, 255, 255, 255]).unwrap(); - - assert_eq!( - [Ipv4Cidr::new([255, 255, 255, 255], 32).unwrap(),], - range.to_cidrs().as_slice() - ); - } - - #[test] - fn test_ipv6_to_cidrs() { - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], - ) - .unwrap(); - - assert_eq!( - [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 128).unwrap()], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], - ) - .unwrap(); - - assert_eq!( - [ - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 116).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], - ) - .unwrap(); - - assert_eq!( - [ - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], - ) - .unwrap(); - - assert_eq!( - [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2001], - ) - .unwrap(); - - assert_eq!( - [ - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), - Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 127).unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], - [0x2001, 0x0DB8, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], - ) - .unwrap(); - - assert_eq!( - [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], 64).unwrap()], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0, 0, 0, 0, 0, 0, 0, 0], - [ - 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, - ], - ) - .unwrap(); - - assert_eq!( - [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 0).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0, 0, 0, 0, 0, 0, 0, 0x0001], - [ - 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, - ], - ) - .unwrap(); - - assert_eq!( - [ - "::1/128".parse::<Ipv6Cidr>().unwrap(), - "::2/127".parse::<Ipv6Cidr>().unwrap(), - "::4/126".parse::<Ipv6Cidr>().unwrap(), - "::8/125".parse::<Ipv6Cidr>().unwrap(), - "::10/124".parse::<Ipv6Cidr>().unwrap(), - "::20/123".parse::<Ipv6Cidr>().unwrap(), - "::40/122".parse::<Ipv6Cidr>().unwrap(), - "::80/121".parse::<Ipv6Cidr>().unwrap(), - "::100/120".parse::<Ipv6Cidr>().unwrap(), - "::200/119".parse::<Ipv6Cidr>().unwrap(), - "::400/118".parse::<Ipv6Cidr>().unwrap(), - "::800/117".parse::<Ipv6Cidr>().unwrap(), - "::1000/116".parse::<Ipv6Cidr>().unwrap(), - "::2000/115".parse::<Ipv6Cidr>().unwrap(), - "::4000/114".parse::<Ipv6Cidr>().unwrap(), - "::8000/113".parse::<Ipv6Cidr>().unwrap(), - "::1:0/112".parse::<Ipv6Cidr>().unwrap(), - "::2:0/111".parse::<Ipv6Cidr>().unwrap(), - "::4:0/110".parse::<Ipv6Cidr>().unwrap(), - "::8:0/109".parse::<Ipv6Cidr>().unwrap(), - "::10:0/108".parse::<Ipv6Cidr>().unwrap(), - "::20:0/107".parse::<Ipv6Cidr>().unwrap(), - "::40:0/106".parse::<Ipv6Cidr>().unwrap(), - "::80:0/105".parse::<Ipv6Cidr>().unwrap(), - "::100:0/104".parse::<Ipv6Cidr>().unwrap(), - "::200:0/103".parse::<Ipv6Cidr>().unwrap(), - "::400:0/102".parse::<Ipv6Cidr>().unwrap(), - "::800:0/101".parse::<Ipv6Cidr>().unwrap(), - "::1000:0/100".parse::<Ipv6Cidr>().unwrap(), - "::2000:0/99".parse::<Ipv6Cidr>().unwrap(), - "::4000:0/98".parse::<Ipv6Cidr>().unwrap(), - "::8000:0/97".parse::<Ipv6Cidr>().unwrap(), - "::1:0:0/96".parse::<Ipv6Cidr>().unwrap(), - "::2:0:0/95".parse::<Ipv6Cidr>().unwrap(), - "::4:0:0/94".parse::<Ipv6Cidr>().unwrap(), - "::8:0:0/93".parse::<Ipv6Cidr>().unwrap(), - "::10:0:0/92".parse::<Ipv6Cidr>().unwrap(), - "::20:0:0/91".parse::<Ipv6Cidr>().unwrap(), - "::40:0:0/90".parse::<Ipv6Cidr>().unwrap(), - "::80:0:0/89".parse::<Ipv6Cidr>().unwrap(), - "::100:0:0/88".parse::<Ipv6Cidr>().unwrap(), - "::200:0:0/87".parse::<Ipv6Cidr>().unwrap(), - "::400:0:0/86".parse::<Ipv6Cidr>().unwrap(), - "::800:0:0/85".parse::<Ipv6Cidr>().unwrap(), - "::1000:0:0/84".parse::<Ipv6Cidr>().unwrap(), - "::2000:0:0/83".parse::<Ipv6Cidr>().unwrap(), - "::4000:0:0/82".parse::<Ipv6Cidr>().unwrap(), - "::8000:0:0/81".parse::<Ipv6Cidr>().unwrap(), - "::1:0:0:0/80".parse::<Ipv6Cidr>().unwrap(), - "::2:0:0:0/79".parse::<Ipv6Cidr>().unwrap(), - "::4:0:0:0/78".parse::<Ipv6Cidr>().unwrap(), - "::8:0:0:0/77".parse::<Ipv6Cidr>().unwrap(), - "::10:0:0:0/76".parse::<Ipv6Cidr>().unwrap(), - "::20:0:0:0/75".parse::<Ipv6Cidr>().unwrap(), - "::40:0:0:0/74".parse::<Ipv6Cidr>().unwrap(), - "::80:0:0:0/73".parse::<Ipv6Cidr>().unwrap(), - "::100:0:0:0/72".parse::<Ipv6Cidr>().unwrap(), - "::200:0:0:0/71".parse::<Ipv6Cidr>().unwrap(), - "::400:0:0:0/70".parse::<Ipv6Cidr>().unwrap(), - "::800:0:0:0/69".parse::<Ipv6Cidr>().unwrap(), - "::1000:0:0:0/68".parse::<Ipv6Cidr>().unwrap(), - "::2000:0:0:0/67".parse::<Ipv6Cidr>().unwrap(), - "::4000:0:0:0/66".parse::<Ipv6Cidr>().unwrap(), - "::8000:0:0:0/65".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:1::/64".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:2::/63".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:4::/62".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:8::/61".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:10::/60".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:20::/59".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:40::/58".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:80::/57".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:100::/56".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:200::/55".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:400::/54".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:800::/53".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:1000::/52".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:2000::/51".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:4000::/50".parse::<Ipv6Cidr>().unwrap(), - "0:0:0:8000::/49".parse::<Ipv6Cidr>().unwrap(), - "0:0:1::/48".parse::<Ipv6Cidr>().unwrap(), - "0:0:2::/47".parse::<Ipv6Cidr>().unwrap(), - "0:0:4::/46".parse::<Ipv6Cidr>().unwrap(), - "0:0:8::/45".parse::<Ipv6Cidr>().unwrap(), - "0:0:10::/44".parse::<Ipv6Cidr>().unwrap(), - "0:0:20::/43".parse::<Ipv6Cidr>().unwrap(), - "0:0:40::/42".parse::<Ipv6Cidr>().unwrap(), - "0:0:80::/41".parse::<Ipv6Cidr>().unwrap(), - "0:0:100::/40".parse::<Ipv6Cidr>().unwrap(), - "0:0:200::/39".parse::<Ipv6Cidr>().unwrap(), - "0:0:400::/38".parse::<Ipv6Cidr>().unwrap(), - "0:0:800::/37".parse::<Ipv6Cidr>().unwrap(), - "0:0:1000::/36".parse::<Ipv6Cidr>().unwrap(), - "0:0:2000::/35".parse::<Ipv6Cidr>().unwrap(), - "0:0:4000::/34".parse::<Ipv6Cidr>().unwrap(), - "0:0:8000::/33".parse::<Ipv6Cidr>().unwrap(), - "0:1::/32".parse::<Ipv6Cidr>().unwrap(), - "0:2::/31".parse::<Ipv6Cidr>().unwrap(), - "0:4::/30".parse::<Ipv6Cidr>().unwrap(), - "0:8::/29".parse::<Ipv6Cidr>().unwrap(), - "0:10::/28".parse::<Ipv6Cidr>().unwrap(), - "0:20::/27".parse::<Ipv6Cidr>().unwrap(), - "0:40::/26".parse::<Ipv6Cidr>().unwrap(), - "0:80::/25".parse::<Ipv6Cidr>().unwrap(), - "0:100::/24".parse::<Ipv6Cidr>().unwrap(), - "0:200::/23".parse::<Ipv6Cidr>().unwrap(), - "0:400::/22".parse::<Ipv6Cidr>().unwrap(), - "0:800::/21".parse::<Ipv6Cidr>().unwrap(), - "0:1000::/20".parse::<Ipv6Cidr>().unwrap(), - "0:2000::/19".parse::<Ipv6Cidr>().unwrap(), - "0:4000::/18".parse::<Ipv6Cidr>().unwrap(), - "0:8000::/17".parse::<Ipv6Cidr>().unwrap(), - "1::/16".parse::<Ipv6Cidr>().unwrap(), - "2::/15".parse::<Ipv6Cidr>().unwrap(), - "4::/14".parse::<Ipv6Cidr>().unwrap(), - "8::/13".parse::<Ipv6Cidr>().unwrap(), - "10::/12".parse::<Ipv6Cidr>().unwrap(), - "20::/11".parse::<Ipv6Cidr>().unwrap(), - "40::/10".parse::<Ipv6Cidr>().unwrap(), - "80::/9".parse::<Ipv6Cidr>().unwrap(), - "100::/8".parse::<Ipv6Cidr>().unwrap(), - "200::/7".parse::<Ipv6Cidr>().unwrap(), - "400::/6".parse::<Ipv6Cidr>().unwrap(), - "800::/5".parse::<Ipv6Cidr>().unwrap(), - "1000::/4".parse::<Ipv6Cidr>().unwrap(), - "2000::/3".parse::<Ipv6Cidr>().unwrap(), - "4000::/2".parse::<Ipv6Cidr>().unwrap(), - "8000::/1".parse::<Ipv6Cidr>().unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [0, 0, 0, 0, 0, 0, 0, 0], - [ - 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, - ], - ) - .unwrap(); - - assert_eq!( - [ - "::/1".parse::<Ipv6Cidr>().unwrap(), - "8000::/2".parse::<Ipv6Cidr>().unwrap(), - "c000::/3".parse::<Ipv6Cidr>().unwrap(), - "e000::/4".parse::<Ipv6Cidr>().unwrap(), - "f000::/5".parse::<Ipv6Cidr>().unwrap(), - "f800::/6".parse::<Ipv6Cidr>().unwrap(), - "fc00::/7".parse::<Ipv6Cidr>().unwrap(), - "fe00::/8".parse::<Ipv6Cidr>().unwrap(), - "ff00::/9".parse::<Ipv6Cidr>().unwrap(), - "ff80::/10".parse::<Ipv6Cidr>().unwrap(), - "ffc0::/11".parse::<Ipv6Cidr>().unwrap(), - "ffe0::/12".parse::<Ipv6Cidr>().unwrap(), - "fff0::/13".parse::<Ipv6Cidr>().unwrap(), - "fff8::/14".parse::<Ipv6Cidr>().unwrap(), - "fffc::/15".parse::<Ipv6Cidr>().unwrap(), - "fffe::/16".parse::<Ipv6Cidr>().unwrap(), - "ffff::/17".parse::<Ipv6Cidr>().unwrap(), - "ffff:8000::/18".parse::<Ipv6Cidr>().unwrap(), - "ffff:c000::/19".parse::<Ipv6Cidr>().unwrap(), - "ffff:e000::/20".parse::<Ipv6Cidr>().unwrap(), - "ffff:f000::/21".parse::<Ipv6Cidr>().unwrap(), - "ffff:f800::/22".parse::<Ipv6Cidr>().unwrap(), - "ffff:fc00::/23".parse::<Ipv6Cidr>().unwrap(), - "ffff:fe00::/24".parse::<Ipv6Cidr>().unwrap(), - "ffff:ff00::/25".parse::<Ipv6Cidr>().unwrap(), - "ffff:ff80::/26".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffc0::/27".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffe0::/28".parse::<Ipv6Cidr>().unwrap(), - "ffff:fff0::/29".parse::<Ipv6Cidr>().unwrap(), - "ffff:fff8::/30".parse::<Ipv6Cidr>().unwrap(), - "ffff:fffc::/31".parse::<Ipv6Cidr>().unwrap(), - "ffff:fffe::/32".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff::/33".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:8000::/34".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:c000::/35".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:e000::/36".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:f000::/37".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:f800::/38".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fc00::/39".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fe00::/40".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ff00::/41".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ff80::/42".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffc0::/43".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffe0::/44".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fff0::/45".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fff8::/46".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fffc::/47".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:fffe::/48".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff::/49".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:8000::/50".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:c000::/51".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:e000::/52".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:f000::/53".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:f800::/54".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fc00::/55".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fe00::/56".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ff00::/57".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ff80::/58".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffc0::/59".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffe0::/60".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fff0::/61".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fff8::/62".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fffc::/63".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:fffe::/64".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff::/65".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:8000::/66".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:c000::/67".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:e000::/68".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:f000::/69".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:f800::/70".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fc00::/71".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fe00::/72".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ff00::/73".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ff80::/74".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ffc0::/75".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ffe0::/76".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fff0::/77".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fff8::/78".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fffc::/79".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:fffe::/80".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ffff::/81".parse::<Ipv6Cidr>().unwrap(), - "ffff:ffff:ffff:ffff:ffff:8000::/82" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:c000::/83" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:e000::/84" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:f000::/85" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:f800::/86" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fc00::/87" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fe00::/88" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ff00::/89" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ff80::/90" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffc0::/91" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffe0::/92" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fff0::/93" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fff8::/94" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fffc::/95" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:fffe::/96" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff::/97" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:8000:0/98" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:c000:0/99" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:e000:0/100" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:f000:0/101" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:f800:0/102" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/103" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/104" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/105" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/106" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/108" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/109" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/110" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/111" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/113" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/114" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/115" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/116" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/117" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/118" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/119" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/120" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/121" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/122" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/123" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/124" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/125" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/126" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/127" - .parse::<Ipv6Cidr>() - .unwrap(), - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/128" - .parse::<Ipv6Cidr>() - .unwrap(), - ], - range.to_cidrs().as_slice() - ); - - let range = - AddressRange::new_v6([0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); - - assert_eq!( - [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 128).unwrap(),], - range.to_cidrs().as_slice() - ); - - let range = AddressRange::new_v6( - [ - 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, - ], - [ - 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, - ], - ) - .unwrap(); - - assert_eq!( - [Ipv6Cidr::new( - [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], - 128 - ) - .unwrap(),], - range.to_cidrs().as_slice() - ); - } } + diff --git a/proxmox-ve-config/src/firewall/types/alias.rs b/proxmox-ve-config/src/firewall/types/alias.rs index 7bc2fb8..a463e52 100644 --- a/proxmox-ve-config/src/firewall/types/alias.rs +++ b/proxmox-ve-config/src/firewall/types/alias.rs @@ -2,10 +2,10 @@ use std::fmt::Display; use std::str::FromStr; use anyhow::{bail, format_err, Error}; +use proxmox_network_types::ip_address::Cidr; use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate::firewall::parse::{match_name, match_non_whitespace}; -use crate::firewall::types::address::Cidr; #[derive(Debug, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] diff --git a/proxmox-ve-config/src/firewall/types/ipset.rs b/proxmox-ve-config/src/firewall/types/ipset.rs index fe5a930..2aaf261 100644 --- a/proxmox-ve-config/src/firewall/types/ipset.rs +++ b/proxmox-ve-config/src/firewall/types/ipset.rs @@ -3,10 +3,10 @@ use std::ops::{Deref, DerefMut}; use std::str::FromStr; use anyhow::{bail, format_err, Error}; +use proxmox_network_types::ip_address::{Cidr, IpRange}; use serde_with::DeserializeFromStr; use crate::firewall::parse::match_non_whitespace; -use crate::firewall::types::address::{Cidr, IpRange}; use crate::firewall::types::alias::AliasName; use crate::guest::vm::NetworkConfig; @@ -112,9 +112,9 @@ impl FromStr for IpsetAddress { } } -impl<T: Into<Cidr>> From<T> for IpsetAddress { - fn from(cidr: T) -> Self { - IpsetAddress::Cidr(cidr.into()) +impl From<Cidr> for IpsetAddress { + fn from(cidr: Cidr) -> Self { + IpsetAddress::Cidr(cidr) } } diff --git a/proxmox-ve-config/src/firewall/types/mod.rs b/proxmox-ve-config/src/firewall/types/mod.rs index 8fd551e..567b302 100644 --- a/proxmox-ve-config/src/firewall/types/mod.rs +++ b/proxmox-ve-config/src/firewall/types/mod.rs @@ -7,7 +7,6 @@ pub mod port; pub mod rule; pub mod rule_match; -pub use address::Cidr; pub use alias::Alias; pub use group::Group; pub use ipset::Ipset; diff --git a/proxmox-ve-config/src/firewall/types/rule.rs b/proxmox-ve-config/src/firewall/types/rule.rs index 2c8f49c..b6b83d2 100644 --- a/proxmox-ve-config/src/firewall/types/rule.rs +++ b/proxmox-ve-config/src/firewall/types/rule.rs @@ -247,13 +247,14 @@ impl FromStr for RuleGroup { #[cfg(test)] mod tests { + use proxmox_network_types::ip_address::{Cidr, IpRange}; + use crate::firewall::types::{ - address::{IpEntry, IpList, IpRange}, + address::{IpEntry, IpList}, alias::{AliasName, AliasScope}, ipset::{IpsetName, IpsetScope}, log::LogLevel, rule_match::{Icmp, IcmpCode, IpAddrMatch, IpMatch, Ports, Protocol, Udp}, - Cidr, }; use super::*; diff --git a/proxmox-ve-config/src/firewall/types/rule_match.rs b/proxmox-ve-config/src/firewall/types/rule_match.rs index 94d8624..3256497 100644 --- a/proxmox-ve-config/src/firewall/types/rule_match.rs +++ b/proxmox-ve-config/src/firewall/types/rule_match.rs @@ -7,10 +7,11 @@ use serde::Deserialize; use anyhow::{bail, format_err, Error}; use serde::de::IntoDeserializer; +use proxmox_network_types::ip_address::Family; use proxmox_sortable_macro::sortable; use crate::firewall::parse::{match_name, match_non_whitespace, SomeStr}; -use crate::firewall::types::address::{Family, IpList}; +use crate::firewall::types::address::IpList; use crate::firewall::types::alias::AliasName; use crate::firewall::types::ipset::IpsetName; use crate::firewall::types::log::LogLevel; @@ -770,7 +771,8 @@ impl fmt::Display for Icmpv6Code { #[cfg(test)] mod tests { - use crate::firewall::types::{alias::AliasScope::Guest, Cidr}; + use proxmox_network_types::ip_address::Cidr; + use crate::firewall::types::alias::AliasScope::Guest; use super::*; diff --git a/proxmox-ve-config/src/guest/vm.rs b/proxmox-ve-config/src/guest/vm.rs index 3476b93..aa47e26 100644 --- a/proxmox-ve-config/src/guest/vm.rs +++ b/proxmox-ve-config/src/guest/vm.rs @@ -1,79 +1,14 @@ -use core::fmt::Display; use std::io; use std::str::FromStr; -use std::{collections::HashMap, net::Ipv6Addr}; +use std::collections::HashMap; use proxmox_schema::property_string::PropertyIterator; use anyhow::{bail, Error}; -use serde_with::DeserializeFromStr; +use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr}; +use proxmox_network_types::mac_address::MacAddress; use crate::firewall::parse::{match_digits, parse_bool}; -use crate::firewall::types::address::{Ipv4Cidr, Ipv6Cidr}; - -#[derive(Clone, Debug, DeserializeFromStr, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct MacAddress([u8; 6]); - -static LOCAL_PART: [u8; 8] = [0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; -static EUI64_MIDDLE_PART: [u8; 2] = [0xFF, 0xFE]; - -impl MacAddress { - pub fn new(address: [u8; 6]) -> Self { - Self(address) - } - - /// generates a link local IPv6-address according to RFC 4291 (Appendix A) - pub fn eui64_link_local_address(&self) -> Ipv6Addr { - let head = &self.0[..3]; - let tail = &self.0[3..]; - - let mut eui64_address: Vec<u8> = LOCAL_PART - .iter() - .chain(head.iter()) - .chain(EUI64_MIDDLE_PART.iter()) - .chain(tail.iter()) - .copied() - .collect(); - - // we need to flip the 7th bit of the first eui64 byte - eui64_address[8] ^= 0x02; - - Ipv6Addr::from( - TryInto::<[u8; 16]>::try_into(eui64_address).expect("is an u8 array with 16 entries"), - ) - } -} - -impl FromStr for MacAddress { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - let split = s.split(':'); - - let parsed = split - .into_iter() - .map(|elem| u8::from_str_radix(elem, 16)) - .collect::<Result<Vec<u8>, _>>() - .map_err(Error::msg)?; - - if parsed.len() != 6 { - bail!("Invalid amount of elements in MAC address!"); - } - - let address = &parsed.as_slice()[0..6]; - Ok(Self(address.try_into().unwrap())) - } -} - -impl Display for MacAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}", - self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] - ) - } -} #[derive(Debug, Clone, Copy)] #[cfg_attr(test, derive(Eq, PartialEq))] @@ -266,6 +201,8 @@ impl NetworkConfig { #[cfg(test)] mod tests { + use std::net::Ipv6Addr; + use super::*; #[test] @@ -314,7 +251,7 @@ mod tests { network_device, NetworkDevice { model: NetworkDeviceModel::VirtIO, - mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]), + mac_address: MacAddress::new([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]), firewall: true, ip: None, ip6: None, @@ -329,7 +266,7 @@ mod tests { network_device, NetworkDevice { model: NetworkDeviceModel::VirtIO, - mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]), + mac_address: MacAddress::new([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]), firewall: true, ip: None, ip6: None, @@ -345,7 +282,7 @@ mod tests { network_device, NetworkDevice { model: NetworkDeviceModel::Veth, - mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0xE2, 0x3E, 0x24]), + mac_address: MacAddress::new([0xAA, 0xAA, 0xAA, 0xE2, 0x3E, 0x24]), firewall: false, ip: None, ip6: None, @@ -435,7 +372,7 @@ vmgenid: 706fbe99-d28b-4047-a9cd-3677c859ca8a" network_config.network_devices()[&0], NetworkDevice { model: NetworkDeviceModel::VirtIO, - mac_address: MacAddress([0xAA, 0xBB, 0xCC, 0xF2, 0xFE, 0x75]), + mac_address: MacAddress::new([0xAA, 0xBB, 0xCC, 0xF2, 0xFE, 0x75]), firewall: true, ip: None, ip6: None, @@ -465,7 +402,7 @@ unprivileged: 1" network_config.network_devices()[&0], NetworkDevice { model: NetworkDeviceModel::Veth, - mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x11]), + mac_address: MacAddress::new([0xBC, 0x24, 0x11, 0x47, 0x83, 0x11]), firewall: true, ip: None, ip6: None, @@ -476,7 +413,7 @@ unprivileged: 1" network_config.network_devices()[&2], NetworkDevice { model: NetworkDeviceModel::Veth, - mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x12]), + mac_address: MacAddress::new([0xBC, 0x24, 0x11, 0x47, 0x83, 0x12]), firewall: false, ip: Some(Ipv4Cidr::from_str("123.123.123.123/24").expect("valid ipv4")), ip6: None, @@ -487,7 +424,7 @@ unprivileged: 1" network_config.network_devices()[&5], NetworkDevice { model: NetworkDeviceModel::Veth, - mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x13]), + mac_address: MacAddress::new([0xBC, 0x24, 0x11, 0x47, 0x83, 0x13]), firewall: true, ip: None, ip6: Some(Ipv6Cidr::from_str("fd80::1/64").expect("valid ipv6")), diff --git a/proxmox-ve-config/src/host/utils.rs b/proxmox-ve-config/src/host/utils.rs index b1dc8e9..3dee72b 100644 --- a/proxmox-ve-config/src/host/utils.rs +++ b/proxmox-ve-config/src/host/utils.rs @@ -1,6 +1,6 @@ use std::net::{IpAddr, ToSocketAddrs}; -use crate::firewall::types::Cidr; +use proxmox_network_types::ip_address::Cidr; use nix::sys::socket::{AddressFamily, SockaddrLike}; use proxmox_sys::nodename; diff --git a/proxmox-ve-config/src/sdn/config.rs b/proxmox-ve-config/src/sdn/config.rs index 7ee1101..9949be0 100644 --- a/proxmox-ve-config/src/sdn/config.rs +++ b/proxmox-ve-config/src/sdn/config.rs @@ -6,17 +6,16 @@ use std::{ str::FromStr, }; +use proxmox_network_types::ip_address::{Cidr, IpRange, IpRangeError}; use proxmox_schema::{property_string::PropertyString, ApiType, ObjectSchema, StringSchema}; - use serde::Deserialize; use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate::{ common::Allowlist, firewall::types::{ - address::{IpRange, IpRangeError}, ipset::{IpsetEntry, IpsetName, IpsetScope}, - Cidr, Ipset, + Ipset, }, sdn::{SdnNameError, SubnetName, VnetName, ZoneName}, }; @@ -587,10 +586,10 @@ impl SdnConfig { ipset_all_wo_gateway.push((*subnet.cidr()).into()); if let Some(gateway) = subnet.gateway { - let gateway_nomatch = IpsetEntry::new(gateway, true, None); + let gateway_nomatch = IpsetEntry::new(Cidr::from(gateway), true, None); ipset_all_wo_gateway.push(gateway_nomatch); - ipset_gateway.push(gateway.into()); + ipset_gateway.push(Cidr::from(gateway).into()); } ipset_dhcp.extend(subnet.dhcp_range.iter().cloned().map(IpsetEntry::from)); diff --git a/proxmox-ve-config/src/sdn/ipam.rs b/proxmox-ve-config/src/sdn/ipam.rs index 598b835..9c6985b 100644 --- a/proxmox-ve-config/src/sdn/ipam.rs +++ b/proxmox-ve-config/src/sdn/ipam.rs @@ -7,13 +7,16 @@ use std::{ use serde::Deserialize; +use proxmox_network_types::ip_address::Cidr; +use proxmox_network_types::mac_address::MacAddress; + use crate::{ common::Allowlist, firewall::types::{ - ipset::{IpsetEntry, IpsetScope}, - Cidr, Ipset, + ipset::IpsetScope, + Ipset, }, - guest::{types::Vmid, vm::MacAddress}, + guest::types::Vmid, sdn::{SdnNameError, SubnetName, ZoneName}, }; @@ -339,7 +342,7 @@ impl Ipam { .or_insert_with(|| { Ipset::from_parts(IpsetScope::Sdn, format!("guest-ipam-{}", entry.vmid)) }) - .push(IpsetEntry::from(entry.ip)); + .push(Cidr::from(entry.ip).into()); acc }) diff --git a/proxmox-ve-config/src/sdn/mod.rs b/proxmox-ve-config/src/sdn/mod.rs index c8dc724..cde6fed 100644 --- a/proxmox-ve-config/src/sdn/mod.rs +++ b/proxmox-ve-config/src/sdn/mod.rs @@ -3,10 +3,9 @@ pub mod ipam; use std::{error::Error, fmt::Display, str::FromStr}; +use proxmox_network_types::ip_address::Cidr; use serde_with::DeserializeFromStr; -use crate::firewall::types::Cidr; - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum SdnNameError { Empty, diff --git a/proxmox-ve-config/tests/sdn/main.rs b/proxmox-ve-config/tests/sdn/main.rs index 1815bec..2ab0e5c 100644 --- a/proxmox-ve-config/tests/sdn/main.rs +++ b/proxmox-ve-config/tests/sdn/main.rs @@ -3,18 +3,17 @@ use std::{ str::FromStr, }; -use proxmox_ve_config::{ - firewall::types::{address::IpRange, Cidr}, - guest::vm::MacAddress, - sdn::{ +use proxmox_network_types::ip_address::{Cidr, IpRange}; +use proxmox_network_types::mac_address::MacAddress; + +use proxmox_ve_config::sdn::{ config::{ RunningConfig, SdnConfig, SdnConfigError, SubnetConfig, VnetConfig, ZoneConfig, ZoneType, }, ipam::{Ipam, IpamDataVm, IpamEntry, IpamJson}, SubnetName, VnetName, ZoneName, - }, -}; + }; #[test] fn parse_running_config() { -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate to proxmox-network-types 2025-04-01 14:52 [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-ve-rs v3 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich @ 2025-04-01 14:52 ` Stefan Hanreich 2025-04-02 9:06 ` [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Christoph Heiss 3 siblings, 0 replies; 11+ messages in thread From: Stefan Hanreich @ 2025-04-01 14:52 UTC (permalink / raw) To: pve-devel The fabrics patch series moved some generic network types into its own crate, so they can be reused across crates. Migrate proxmox-firewall to use the new proxmox-network-types crate instead of proxmox_ve_config. Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> --- Notes: This depends on the changes in the proxmox-ve-rs repository. Cargo.toml | 1 + proxmox-firewall/Cargo.toml | 1 + proxmox-firewall/src/firewall.rs | 13 +++++++------ proxmox-firewall/src/object.rs | 4 +++- proxmox-firewall/src/rule.rs | 3 ++- proxmox-nftables/Cargo.toml | 3 ++- proxmox-nftables/src/expression.rs | 5 ++--- proxmox-nftables/src/types.rs | 2 +- 8 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 079fb79..7e1ebb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ resolver = "2" [workspace.dependencies] proxmox-ve-config = { version = "0.2.2" } +proxmox-network-types = { version = "0.1" } diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml index 09ea3fe..67622d2 100644 --- a/proxmox-firewall/Cargo.toml +++ b/proxmox-firewall/Cargo.toml @@ -22,6 +22,7 @@ signal-hook = "0.3" proxmox-nftables = { path = "../proxmox-nftables", features = ["config-ext"] } proxmox-ve-config = { workspace = true } +proxmox-network-types = { workspace = true } [dev-dependencies] insta = { version = "1.21", features = ["json"] } diff --git a/proxmox-firewall/src/firewall.rs b/proxmox-firewall/src/firewall.rs index 607fc75..950c401 100644 --- a/proxmox-firewall/src/firewall.rs +++ b/proxmox-firewall/src/firewall.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; use std::fs; +use std::net::IpAddr; use anyhow::{bail, Error}; @@ -20,7 +21,7 @@ use proxmox_ve_config::firewall::ct_helper::get_cthelper; use proxmox_ve_config::firewall::guest::Config as GuestConfig; use proxmox_ve_config::firewall::host::Config as HostConfig; -use proxmox_ve_config::firewall::types::address::Ipv6Cidr; +use proxmox_network_types::ip_address::Cidr; use proxmox_ve_config::firewall::types::ipset::{ Ipfilter, Ipset, IpsetEntry, IpsetName, IpsetScope, }; @@ -800,17 +801,17 @@ impl Firewall { let ipset_name = IpsetName::new(IpsetScope::Guest, ipfilter_name); let mut ipset = Ipset::new(ipset_name); - let cidr = - Ipv6Cidr::from(network_device.mac_address().eui64_link_local_address()); + let link_local_address = network_device.mac_address().eui64_link_local_address(); + let cidr = Cidr::from(IpAddr::from(link_local_address)); - ipset.push(cidr.into()); + ipset.push(IpsetEntry::from(cidr)); if let Some(ip_address) = network_device.ip() { - ipset.push(IpsetEntry::from(*ip_address)); + ipset.push(IpsetEntry::from(Cidr::from(*ip_address))); } if let Some(ip6_address) = network_device.ip6() { - ipset.push(IpsetEntry::from(*ip6_address)); + ipset.push(IpsetEntry::from(Cidr::from(*ip6_address))); } commands.append(&mut ipset.to_nft_objects(&env)?); diff --git a/proxmox-firewall/src/object.rs b/proxmox-firewall/src/object.rs index cf7e773..cbfadba 100644 --- a/proxmox-firewall/src/object.rs +++ b/proxmox-firewall/src/object.rs @@ -11,11 +11,13 @@ use proxmox_nftables::{ use proxmox_ve_config::{ firewall::{ ct_helper::CtHelperMacro, - types::{address::Family, alias::AliasName, ipset::IpsetAddress, Alias, Ipset}, + types::{alias::AliasName, ipset::IpsetAddress, Alias, Ipset}, }, guest::types::Vmid, }; +use proxmox_network_types::ip_address::Family; + use crate::config::FirewallConfig; pub(crate) struct NftObjectEnv<'a, 'b> { diff --git a/proxmox-firewall/src/rule.rs b/proxmox-firewall/src/rule.rs index 14ee544..16a0b5a 100644 --- a/proxmox-firewall/src/rule.rs +++ b/proxmox-firewall/src/rule.rs @@ -12,7 +12,6 @@ use proxmox_ve_config::{ ct_helper::CtHelperMacro, fw_macros::{get_macro, FwMacro}, types::{ - address::Family, alias::AliasName, ipset::{Ipfilter, IpsetName}, log::LogRateLimit, @@ -26,6 +25,8 @@ use proxmox_ve_config::{ guest::types::Vmid, }; +use proxmox_network_types::ip_address::Family; + use crate::config::FirewallConfig; #[derive(Debug, Clone)] diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml index 4ff6f41..85f07f0 100644 --- a/proxmox-nftables/Cargo.toml +++ b/proxmox-nftables/Cargo.toml @@ -11,7 +11,7 @@ description = "Proxmox VE nftables" license = "AGPL-3" [features] -config-ext = ["dep:proxmox-ve-config"] +config-ext = ["dep:proxmox-ve-config", "dep:proxmox-network-types"] [dependencies] log = "0.4" @@ -23,3 +23,4 @@ serde_json = "1" serde_plain = "1" proxmox-ve-config = { workspace = true, optional = true } +proxmox-network-types = { workspace = true, optional = true } diff --git a/proxmox-nftables/src/expression.rs b/proxmox-nftables/src/expression.rs index e9ef94f..4794ac1 100644 --- a/proxmox-nftables/src/expression.rs +++ b/proxmox-nftables/src/expression.rs @@ -1,17 +1,16 @@ use crate::types::{ElemConfig, Verdict}; -use proxmox_ve_config::firewall::types::address::IpRange; use proxmox_ve_config::host::types::BridgeName; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::address::{Family, IpEntry, IpList}; +use proxmox_ve_config::firewall::types::address::{IpEntry, IpList}; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::port::{PortEntry, PortList}; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::rule_match::{IcmpCode, IcmpType, Icmpv6Code, Icmpv6Type}; #[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::Cidr; +use proxmox_network_types::ip_address::{Cidr, IpRange, Family}; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] diff --git a/proxmox-nftables/src/types.rs b/proxmox-nftables/src/types.rs index 320c757..c613e64 100644 --- a/proxmox-nftables/src/types.rs +++ b/proxmox-nftables/src/types.rs @@ -8,7 +8,7 @@ use crate::{Expression, Statement}; use serde::{Deserialize, Serialize}; #[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::address::Family; +use proxmox_network_types::ip_address::Family; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::ipset::IpsetName; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit 2025-04-01 14:52 [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Stefan Hanreich ` (2 preceding siblings ...) 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate " Stefan Hanreich @ 2025-04-02 9:06 ` Christoph Heiss 3 siblings, 0 replies; 11+ messages in thread From: Christoph Heiss @ 2025-04-02 9:06 UTC (permalink / raw) To: Stefan Hanreich; +Cc: Proxmox VE development discussion Applied all the patches and built all the crates. Unit tests and clippy look good. Only really looked at patch #2 ("network-types: add hostname type") in the series, as all others are just code moves. Please consider the entire series: Reviewed-by: Christoph Heiss <c.heiss@proxmox.com> On Tue Apr 1, 2025 at 4:52 PM CEST, Stefan Hanreich wrote: > This commit moves some IP address and MAC address types from > proxmox-ve-config to proxmox, so they can be used re-used across our > code base. > > The code in this commit is mostly the same as in proxmox-ve-config > ('bc9253d8'), but I have made a few changes: > > * Added additional documentation to some of the structs and their > methods > * Converted all error types to thiserror > > Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> > --- > Cargo.toml | 1 + > proxmox-network-types/Cargo.toml | 18 + > proxmox-network-types/debian/changelog | 5 + > proxmox-network-types/debian/control | 39 + > proxmox-network-types/debian/copyright | 18 + > proxmox-network-types/debian/debcargo.toml | 7 + > proxmox-network-types/src/ip_address.rs | 1404 ++++++++++++++++++++ > proxmox-network-types/src/lib.rs | 2 + > proxmox-network-types/src/mac_address.rs | 120 ++ > 9 files changed, 1614 insertions(+) > create mode 100644 proxmox-network-types/Cargo.toml > create mode 100644 proxmox-network-types/debian/changelog > create mode 100644 proxmox-network-types/debian/control > create mode 100644 proxmox-network-types/debian/copyright > create mode 100644 proxmox-network-types/debian/debcargo.toml > create mode 100644 proxmox-network-types/src/ip_address.rs > create mode 100644 proxmox-network-types/src/lib.rs > create mode 100644 proxmox-network-types/src/mac_address.rs > > diff --git a/Cargo.toml b/Cargo.toml > index 268b39eb..2ca0ea61 100644 > --- a/Cargo.toml > +++ b/Cargo.toml > @@ -24,6 +24,7 @@ members = [ > "proxmox-login", > "proxmox-metrics", > "proxmox-network-api", > + "proxmox-network-types", > "proxmox-notify", > "proxmox-openid", > "proxmox-product-config", > diff --git a/proxmox-network-types/Cargo.toml b/proxmox-network-types/Cargo.toml > new file mode 100644 > index 00000000..2ac0e96a > --- /dev/null > +++ b/proxmox-network-types/Cargo.toml > @@ -0,0 +1,18 @@ > +[package] > +name = "proxmox-network-types" > +description = "Rust types for common networking entities" > +version = "0.1.0" > +authors.workspace = true > +edition.workspace = true > +license.workspace = true > +homepage.workspace = true > +exclude.workspace = true > +rust-version.workspace = true > + > +[dependencies] > +serde = { workspace = true, features = [ "derive" ] } > +serde_with = "3.8.1" > +thiserror = "2" > + > +[features] > +default = [] > diff --git a/proxmox-network-types/debian/changelog b/proxmox-network-types/debian/changelog > new file mode 100644 > index 00000000..78cb0ab0 > --- /dev/null > +++ b/proxmox-network-types/debian/changelog > @@ -0,0 +1,5 @@ > +rust-proxmox-network-types (0.1.0-1) unstable; urgency=medium > + > + * Initial release. > + > + -- Proxmox Support Team <support@proxmox.com> Mon, 03 Jun 2024 10:51:11 +0200 > diff --git a/proxmox-network-types/debian/control b/proxmox-network-types/debian/control > new file mode 100644 > index 00000000..bc0b2aa4 > --- /dev/null > +++ b/proxmox-network-types/debian/control > @@ -0,0 +1,39 @@ > +Source: rust-proxmox-network-types > +Section: rust > +Priority: optional > +Build-Depends: debhelper-compat (= 13), > + dh-sequence-cargo > +Build-Depends-Arch: cargo:native <!nocheck>, > + rustc:native (>= 1.82) <!nocheck>, > + libstd-rust-dev <!nocheck>, > + librust-serde-1+default-dev <!nocheck>, > + librust-serde-1+derive-dev <!nocheck>, > + librust-serde-with-3+default-dev (>= 3.8.1-~~) <!nocheck>, > + librust-thiserror-2+default-dev <!nocheck> > +Maintainer: Proxmox Support Team <support@proxmox.com> > +Standards-Version: 4.7.0 > +Vcs-Git: git://git.proxmox.com/git/proxmox.git > +Vcs-Browser: https://git.proxmox.com/?p=proxmox.git > +Homepage: https://proxmox.com > +X-Cargo-Crate: proxmox-network-types > +Rules-Requires-Root: no > + > +Package: librust-proxmox-network-types-dev > +Architecture: any > +Multi-Arch: same > +Depends: > + ${misc:Depends}, > + librust-serde-1+default-dev, > + librust-serde-1+derive-dev, > + librust-serde-with-3+default-dev (>= 3.8.1-~~), > + librust-thiserror-2+default-dev > +Provides: > + librust-proxmox-network-types+default-dev (= ${binary:Version}), > + librust-proxmox-network-types-0-dev (= ${binary:Version}), > + librust-proxmox-network-types-0+default-dev (= ${binary:Version}), > + librust-proxmox-network-types-0.1-dev (= ${binary:Version}), > + librust-proxmox-network-types-0.1+default-dev (= ${binary:Version}), > + librust-proxmox-network-types-0.1.0-dev (= ${binary:Version}), > + librust-proxmox-network-types-0.1.0+default-dev (= ${binary:Version}) > +Description: Rust types for common networking entities - Rust source code > + Source code for Debianized Rust crate "proxmox-network-types" > diff --git a/proxmox-network-types/debian/copyright b/proxmox-network-types/debian/copyright > new file mode 100644 > index 00000000..1ea8a56b > --- /dev/null > +++ b/proxmox-network-types/debian/copyright > @@ -0,0 +1,18 @@ > +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ > + > +Files: > + * > +Copyright: 2019 - 2025 Proxmox Server Solutions GmbH <support@proxmox.com> > +License: AGPL-3.0-or-later > + This program is free software: you can redistribute it and/or modify it under > + the terms of the GNU Affero General Public License as published by the Free > + Software Foundation, either version 3 of the License, or (at your option) any > + later version. > + . > + This program is distributed in the hope that it will be useful, but WITHOUT > + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS > + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more > + details. > + . > + You should have received a copy of the GNU Affero General Public License along > + with this program. If not, see <https://www.gnu.org/licenses/>. > diff --git a/proxmox-network-types/debian/debcargo.toml b/proxmox-network-types/debian/debcargo.toml > new file mode 100644 > index 00000000..b7864cdb > --- /dev/null > +++ b/proxmox-network-types/debian/debcargo.toml > @@ -0,0 +1,7 @@ > +overlay = "." > +crate_src_path = ".." > +maintainer = "Proxmox Support Team <support@proxmox.com>" > + > +[source] > +vcs_git = "git://git.proxmox.com/git/proxmox.git" > +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" > diff --git a/proxmox-network-types/src/ip_address.rs b/proxmox-network-types/src/ip_address.rs > new file mode 100644 > index 00000000..3d27d38f > --- /dev/null > +++ b/proxmox-network-types/src/ip_address.rs > @@ -0,0 +1,1404 @@ > +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, AddrParseError}; > + > +use serde_with::{DeserializeFromStr, SerializeDisplay}; > +use thiserror::Error; > + > +/// The family (v4 or v6) of an IP address or CIDR prefix > +#[derive(Clone, Copy, Debug, Eq, PartialEq)] > +pub enum Family { > + V4, > + V6, > +} > + > +impl Family { > + pub fn is_ipv4(&self) -> bool { > + *self == Self::V4 > + } > + > + pub fn is_ipv6(&self) -> bool { > + *self == Self::V6 > + } > +} > + > +impl std::fmt::Display for Family { > + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { > + match self { > + Family::V4 => f.write_str("Ipv4"), > + Family::V6 => f.write_str("Ipv6"), > + } > + } > +} > + > +#[derive(Error, Debug)] > +pub enum CidrError { > + #[error("invalid netmask")] > + InvalidNetmask, > + #[error("invalid IP address")] > + InvalidAddress(#[from] AddrParseError), > +} > + > +/// Represents either an [`Ipv4Cidr`] or [`Ipv6Cidr`] CIDR prefix > +#[derive( > + Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr, > +)] > +pub enum Cidr { > + Ipv4(Ipv4Cidr), > + Ipv6(Ipv6Cidr), > +} > + > +impl Cidr { > + pub fn new_v4(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, CidrError> { > + Ok(Cidr::Ipv4(Ipv4Cidr::new(addr, mask)?)) > + } > + > + pub fn new_v6(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, CidrError> { > + Ok(Cidr::Ipv6(Ipv6Cidr::new(addr, mask)?)) > + } > + > + /// which [`Family`] this CIDr belongs to > + pub const fn family(&self) -> Family { > + match self { > + Cidr::Ipv4(_) => Family::V4, > + Cidr::Ipv6(_) => Family::V6, > + } > + } > + > + pub fn is_ipv4(&self) -> bool { > + matches!(self, Cidr::Ipv4(_)) > + } > + > + pub fn is_ipv6(&self) -> bool { > + matches!(self, Cidr::Ipv6(_)) > + } > + > + /// Whether a given IP address is contained in this [`Cidr`] > + /// > + /// This only works if both [`IpAddr`] are in the same family, otherwise the function returns > + /// false. > + pub fn contains_address(&self, ip: &IpAddr) -> bool { > + match (self, ip) { > + (Cidr::Ipv4(cidr), IpAddr::V4(ip)) => cidr.contains_address(ip), > + (Cidr::Ipv6(cidr), IpAddr::V6(ip)) => cidr.contains_address(ip), > + _ => false, > + } > + } > +} > + > +impl std::fmt::Display for Cidr { > + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { > + match self { > + Self::Ipv4(ip) => f.write_str(ip.to_string().as_str()), > + Self::Ipv6(ip) => f.write_str(ip.to_string().as_str()), > + } > + } > +} > + > +impl std::str::FromStr for Cidr { > + type Err = CidrError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + if let Ok(ip) = s.parse::<Ipv4Cidr>() { > + return Ok(Cidr::Ipv4(ip)); > + } > + > + Ok(Cidr::Ipv6(s.parse()?)) > + } > +} > + > +impl From<Ipv4Cidr> for Cidr { > + fn from(cidr: Ipv4Cidr) -> Self { > + Cidr::Ipv4(cidr) > + } > +} > + > +impl From<Ipv6Cidr> for Cidr { > + fn from(cidr: Ipv6Cidr) -> Self { > + Cidr::Ipv6(cidr) > + } > +} > + > +impl From<IpAddr> for Cidr { > + fn from(value: IpAddr) -> Self { > + match value { > + IpAddr::V4(addr) => Ipv4Cidr::from(addr).into(), > + IpAddr::V6(addr) => Ipv6Cidr::from(addr).into(), > + } > + } > +} > + > +const IPV4_LENGTH: u8 = 32; > + > +#[derive( > + SerializeDisplay, DeserializeFromStr, Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, > +)] > +pub struct Ipv4Cidr { > + addr: Ipv4Addr, > + mask: u8, > +} > + > +impl Ipv4Cidr { > + pub fn new(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, CidrError> { > + if mask > IPV4_LENGTH { > + return Err(CidrError::InvalidNetmask); > + } > + > + Ok(Self { > + addr: addr.into(), > + mask, > + }) > + } > + > + pub fn contains_address(&self, other: &Ipv4Addr) -> bool { > + let bits = u32::from_be_bytes(self.addr.octets()); > + let other_bits = u32::from_be_bytes(other.octets()); > + > + let shift_amount: u32 = IPV4_LENGTH.saturating_sub(self.mask).into(); > + > + bits.checked_shr(shift_amount).unwrap_or(0) > + == other_bits.checked_shr(shift_amount).unwrap_or(0) > + } > + > + pub fn address(&self) -> &Ipv4Addr { > + &self.addr > + } > + > + pub fn mask(&self) -> u8 { > + self.mask > + } > +} > + > +impl<T: Into<Ipv4Addr>> From<T> for Ipv4Cidr { > + fn from(value: T) -> Self { > + Self { > + addr: value.into(), > + mask: 32, > + } > + } > +} > + > +impl std::str::FromStr for Ipv4Cidr { > + type Err = CidrError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + Ok(match s.find('/') { > + None => Self { > + addr: s.parse()?, > + mask: 32, > + }, > + Some(pos) => { > + let mask: u8 = s[(pos + 1)..] > + .parse() > + .map_err(|_| CidrError::InvalidNetmask)?; > + > + Self::new(s[..pos].parse::<Ipv4Addr>()?, mask)? > + } > + }) > + } > +} > + > +impl std::fmt::Display for Ipv4Cidr { > + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { > + write!(f, "{}/{}", &self.addr, self.mask) > + } > +} > + > +const IPV6_LENGTH: u8 = 128; > + > +#[derive( > + SerializeDisplay, DeserializeFromStr, Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, > +)] > +pub struct Ipv6Cidr { > + addr: Ipv6Addr, > + mask: u8, > +} > + > +impl Ipv6Cidr { > + pub fn new(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, CidrError> { > + if mask > IPV6_LENGTH { > + return Err(CidrError::InvalidNetmask); > + } > + > + Ok(Self { > + addr: addr.into(), > + mask, > + }) > + } > + > + pub fn contains_address(&self, other: &Ipv6Addr) -> bool { > + let bits = u128::from_be_bytes(self.addr.octets()); > + let other_bits = u128::from_be_bytes(other.octets()); > + > + let shift_amount: u32 = IPV6_LENGTH.saturating_sub(self.mask).into(); > + > + bits.checked_shr(shift_amount).unwrap_or(0) > + == other_bits.checked_shr(shift_amount).unwrap_or(0) > + } > + > + pub fn address(&self) -> &Ipv6Addr { > + &self.addr > + } > + > + pub fn mask(&self) -> u8 { > + self.mask > + } > +} > + > +impl std::str::FromStr for Ipv6Cidr { > + type Err = CidrError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + Ok(match s.find('/') { > + None => Self { > + addr: s.parse()?, > + mask: 128, > + }, > + Some(pos) => { > + let mask: u8 = s[(pos + 1)..] > + .parse() > + .map_err(|_| CidrError::InvalidNetmask)?; > + > + Self::new(s[..pos].parse::<Ipv6Addr>()?, mask)? > + } > + }) > + } > +} > + > +impl std::fmt::Display for Ipv6Cidr { > + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { > + write!(f, "{}/{}", &self.addr, self.mask) > + } > +} > + > +impl<T: Into<Ipv6Addr>> From<T> for Ipv6Cidr { > + fn from(addr: T) -> Self { > + Self { > + addr: addr.into(), > + mask: 128, > + } > + } > +} > + > +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Error)] > +pub enum IpRangeError { > + #[error("mismatched ip address families")] > + MismatchedFamilies, > + #[error("start is greater than last")] > + StartGreaterThanLast, > + #[error("invalid ip range format")] > + InvalidFormat, > +} > + > +/// Represents a range of IPv4 or IPv6 addresses. > +/// > +/// For more information see [`AddressRange`] > +#[derive( > + Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, > +)] > +pub enum IpRange { > + V4(AddressRange<Ipv4Addr>), > + V6(AddressRange<Ipv6Addr>), > +} > + > +impl IpRange { > + /// Returns the family of the IpRange. > + pub fn family(&self) -> Family { > + match self { > + IpRange::V4(_) => Family::V4, > + IpRange::V6(_) => Family::V6, > + } > + } > + > + /// Creates a new [`IpRange`] from two [`IpAddr`]. > + /// > + /// # Errors > + /// > + /// This function will return an error if start and last IP address are not from the same family. > + pub fn new(start: impl Into<IpAddr>, last: impl Into<IpAddr>) -> Result<Self, IpRangeError> { > + match (start.into(), last.into()) { > + (IpAddr::V4(start), IpAddr::V4(last)) => Self::new_v4(start, last), > + (IpAddr::V6(start), IpAddr::V6(last)) => Self::new_v6(start, last), > + _ => Err(IpRangeError::MismatchedFamilies), > + } > + } > + > + /// construct a new IPv4 Range > + pub fn new_v4( > + start: impl Into<Ipv4Addr>, > + last: impl Into<Ipv4Addr>, > + ) -> Result<Self, IpRangeError> { > + Ok(IpRange::V4(AddressRange::new_v4(start, last)?)) > + } > + > + /// construct a new IPv6 Range > + pub fn new_v6( > + start: impl Into<Ipv6Addr>, > + last: impl Into<Ipv6Addr>, > + ) -> Result<Self, IpRangeError> { > + Ok(IpRange::V6(AddressRange::new_v6(start, last)?)) > + } > + > + /// Converts an IpRange into the minimal amount of CIDRs. > + /// > + /// see the concrete implementations of [`AddressRange<Ipv4Addr>`] or [`AddressRange<Ipv6Addr>`] > + /// respectively > + pub fn to_cidrs(&self) -> Vec<Cidr> { > + match self { > + IpRange::V4(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), > + IpRange::V6(range) => range.to_cidrs().into_iter().map(Cidr::from).collect(), > + } > + } > +} > + > +impl std::str::FromStr for IpRange { > + type Err = IpRangeError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + if let Ok(range) = s.parse() { > + return Ok(IpRange::V4(range)); > + } > + > + if let Ok(range) = s.parse() { > + return Ok(IpRange::V6(range)); > + } > + > + Err(IpRangeError::InvalidFormat) > + } > +} > + > +impl std::fmt::Display for IpRange { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + match self { > + IpRange::V4(range) => range.fmt(f), > + IpRange::V6(range) => range.fmt(f), > + } > + } > +} > + > +/// Represents a range of IP addresses from start to last. > +/// > +/// This type is for encapsulation purposes for the [`IpRange`] enum and should be instantiated via > +/// that enum. > +/// > +/// # Invariants > +/// > +/// * start and last have the same IP address family > +/// * start is less than or equal to last > +/// > +/// # Textual representation > +/// > +/// Two IP addresses separated by a hyphen, e.g.: `127.0.0.1-127.0.0.255` > +#[derive( > + Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr, > +)] > +pub struct AddressRange<T> { > + start: T, > + last: T, > +} > + > +impl AddressRange<Ipv4Addr> { > + pub(crate) fn new_v4( > + start: impl Into<Ipv4Addr>, > + last: impl Into<Ipv4Addr>, > + ) -> Result<AddressRange<Ipv4Addr>, IpRangeError> { > + let (start, last) = (start.into(), last.into()); > + > + if start > last { > + return Err(IpRangeError::StartGreaterThanLast); > + } > + > + Ok(Self { start, last }) > + } > + > + /// Returns the minimum amount of CIDRs that exactly represent the range > + /// > + /// The idea behind this algorithm is as follows: > + /// > + /// Start iterating with current = start of the IP range > + /// > + /// Find two netmasks > + /// * The largest CIDR that the current IP can be the first of > + /// * The largest CIDR that *only* contains IPs from current - last > + /// > + /// Add the smaller of the two CIDRs to our result and current to the first IP that is in > + /// the range but not in the CIDR we just added. Proceed until we reached the last of the IP > + /// range. > + /// > + pub fn to_cidrs(&self) -> Vec<Ipv4Cidr> { > + let mut cidrs = Vec::new(); > + > + let mut current = u32::from_be_bytes(self.start.octets()); > + let last = u32::from_be_bytes(self.last.octets()); > + > + if current == last { > + // valid Ipv4 since netmask is 32 > + cidrs.push(Ipv4Cidr::new(current, 32).unwrap()); > + return cidrs; > + } > + > + // special case this, since this is the only possibility of overflow > + // when calculating delta_min_mask - makes everything a lot easier > + if current == u32::MIN && last == u32::MAX { > + // valid Ipv4 since it is `0.0.0.0/0` > + cidrs.push(Ipv4Cidr::new(current, 0).unwrap()); > + return cidrs; > + } > + > + while current <= last { > + // netmask of largest CIDR that current IP can be the first of > + // cast is safe, because trailing zeroes can at most be 32 > + let current_max_mask = IPV4_LENGTH - (current.trailing_zeros() as u8); > + > + // netmask of largest CIDR that *only* contains IPs of the remaining range > + // is at most 32 due to unwrap_or returning 32 and ilog2 being at most 31 > + let delta_min_mask = ((last - current) + 1) // safe due to special case above > + .checked_ilog2() // should never occur due to special case, but for good measure > + .map(|mask| IPV4_LENGTH - mask as u8) > + .unwrap_or(IPV4_LENGTH); > + > + // at most 32, due to current/delta being at most 32 > + let netmask = u8::max(current_max_mask, delta_min_mask); > + > + // netmask is at most 32, therefore safe to unwrap > + cidrs.push(Ipv4Cidr::new(current, netmask).unwrap()); > + > + let delta = 2u32.saturating_pow((IPV4_LENGTH - netmask).into()); > + > + if let Some(result) = current.checked_add(delta) { > + current = result > + } else { > + // we reached the end of IP address space > + break; > + } > + } > + > + cidrs > + } > +} > + > +impl AddressRange<Ipv6Addr> { > + pub(crate) fn new_v6( > + start: impl Into<Ipv6Addr>, > + last: impl Into<Ipv6Addr>, > + ) -> Result<AddressRange<Ipv6Addr>, IpRangeError> { > + let (start, last) = (start.into(), last.into()); > + > + if start > last { > + return Err(IpRangeError::StartGreaterThanLast); > + } > + > + Ok(Self { start, last }) > + } > + > + /// Returns the minimum amount of CIDRs that exactly represent the [`AddressRange`]. > + /// > + /// This function works analogous to the IPv4 version, please refer to the respective > + /// documentation of [`AddressRange<Ipv4Addr>`] > + pub fn to_cidrs(&self) -> Vec<Ipv6Cidr> { > + let mut cidrs = Vec::new(); > + > + let mut current = u128::from_be_bytes(self.start.octets()); > + let last = u128::from_be_bytes(self.last.octets()); > + > + if current == last { > + // valid Ipv6 since netmask is 128 > + cidrs.push(Ipv6Cidr::new(current, 128).unwrap()); > + return cidrs; > + } > + > + // special case this, since this is the only possibility of overflow > + // when calculating delta_min_mask - makes everything a lot easier > + if current == u128::MIN && last == u128::MAX { > + // valid Ipv6 since it is `::/0` > + cidrs.push(Ipv6Cidr::new(current, 0).unwrap()); > + return cidrs; > + } > + > + while current <= last { > + // netmask of largest CIDR that current IP can be the first of > + // cast is safe, because trailing zeroes can at most be 128 > + let current_max_mask = IPV6_LENGTH - (current.trailing_zeros() as u8); > + > + // netmask of largest CIDR that *only* contains IPs of the remaining range > + // is at most 128 due to unwrap_or returning 128 and ilog2 being at most 31 > + let delta_min_mask = ((last - current) + 1) // safe due to special case above > + .checked_ilog2() // should never occur due to special case, but for good measure > + .map(|mask| IPV6_LENGTH - mask as u8) > + .unwrap_or(IPV6_LENGTH); > + > + // at most 128, due to current/delta being at most 128 > + let netmask = u8::max(current_max_mask, delta_min_mask); > + > + // netmask is at most 128, therefore safe to unwrap > + cidrs.push(Ipv6Cidr::new(current, netmask).unwrap()); > + > + let delta = 2u128.saturating_pow((IPV6_LENGTH - netmask).into()); > + > + if let Some(result) = current.checked_add(delta) { > + current = result > + } else { > + // we reached the end of IP address space > + break; > + } > + } > + > + cidrs > + } > +} > + > +impl<T> AddressRange<T> { > + /// the first IP address contained in this [`AddressRange`] > + pub fn start(&self) -> &T { > + &self.start > + } > + > + /// the last IP address contained in this [`AddressRange`] > + pub fn last(&self) -> &T { > + &self.last > + } > +} > + > +impl std::str::FromStr for AddressRange<Ipv4Addr> { > + type Err = IpRangeError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + if let Some((start, last)) = s.split_once('-') { > + let start_address = start > + .parse::<Ipv4Addr>() > + .map_err(|_| IpRangeError::InvalidFormat)?; > + > + let last_address = last > + .parse::<Ipv4Addr>() > + .map_err(|_| IpRangeError::InvalidFormat)?; > + > + return Self::new_v4(start_address, last_address); > + } > + > + Err(IpRangeError::InvalidFormat) > + } > +} > + > +impl std::str::FromStr for AddressRange<Ipv6Addr> { > + type Err = IpRangeError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + if let Some((start, last)) = s.split_once('-') { > + let start_address = start > + .parse::<Ipv6Addr>() > + .map_err(|_| IpRangeError::InvalidFormat)?; > + > + let last_address = last > + .parse::<Ipv6Addr>() > + .map_err(|_| IpRangeError::InvalidFormat)?; > + > + return Self::new_v6(start_address, last_address); > + } > + > + Err(IpRangeError::InvalidFormat) > + } > +} > + > +impl<T: std::fmt::Display> std::fmt::Display for AddressRange<T> { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + write!(f, "{}-{}", self.start, self.last) > + } > +} > + > +#[cfg(test)] > +mod tests { > + use super::*; > + use std::net::{Ipv4Addr, Ipv6Addr}; > + > + #[test] > + fn test_v4_cidr() { > + let mut cidr: Ipv4Cidr = "0.0.0.0/0".parse().expect("valid IPv4 CIDR"); > + > + assert_eq!(cidr.addr, Ipv4Addr::new(0, 0, 0, 0)); > + assert_eq!(cidr.mask, 0); > + > + assert!(cidr.contains_address(&Ipv4Addr::new(0, 0, 0, 0))); > + assert!(cidr.contains_address(&Ipv4Addr::new(255, 255, 255, 255))); > + > + cidr = "192.168.100.1".parse().expect("valid IPv4 CIDR"); > + > + assert_eq!(cidr.addr, Ipv4Addr::new(192, 168, 100, 1)); > + assert_eq!(cidr.mask, 32); > + > + assert!(cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 1))); > + assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 2))); > + assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 0))); > + > + cidr = "10.100.5.0/24".parse().expect("valid IPv4 CIDR"); > + > + assert_eq!(cidr.mask, 24); > + > + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 0))); > + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 1))); > + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 100))); > + assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 255))); > + assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 4, 255))); > + assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 6, 0))); > + > + "0.0.0.0/-1".parse::<Ipv4Cidr>().unwrap_err(); > + "0.0.0.0/33".parse::<Ipv4Cidr>().unwrap_err(); > + "256.256.256.256/10".parse::<Ipv4Cidr>().unwrap_err(); > + > + "fe80::1/64".parse::<Ipv4Cidr>().unwrap_err(); > + "qweasd".parse::<Ipv4Cidr>().unwrap_err(); > + "".parse::<Ipv4Cidr>().unwrap_err(); > + } > + > + #[test] > + fn test_v6_cidr() { > + let mut cidr: Ipv6Cidr = "abab::1/64".parse().expect("valid IPv6 CIDR"); > + > + assert_eq!(cidr.addr, Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 1)); > + assert_eq!(cidr.mask, 64); > + > + assert!(cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 0))); > + assert!(cidr.contains_address(&Ipv6Addr::new( > + 0xABAB, 0, 0, 0, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA > + ))); > + assert!(cidr.contains_address(&Ipv6Addr::new( > + 0xABAB, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF > + ))); > + assert!(!cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 1, 0, 0, 0, 0))); > + assert!(!cidr.contains_address(&Ipv6Addr::new( > + 0xABAA, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF > + ))); > + > + cidr = "eeee::1".parse().expect("valid IPv6 CIDR"); > + > + assert_eq!(cidr.mask, 128); > + > + assert!(cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 1))); > + assert!(!cidr.contains_address(&Ipv6Addr::new( > + 0xEEED, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF > + ))); > + assert!(!cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 0))); > + > + "eeee::1/-1".parse::<Ipv6Cidr>().unwrap_err(); > + "eeee::1/129".parse::<Ipv6Cidr>().unwrap_err(); > + "gggg::1/64".parse::<Ipv6Cidr>().unwrap_err(); > + > + "192.168.0.1".parse::<Ipv6Cidr>().unwrap_err(); > + "qweasd".parse::<Ipv6Cidr>().unwrap_err(); > + "".parse::<Ipv6Cidr>().unwrap_err(); > + } > + > + #[test] > + fn test_ip_range() { > + IpRange::new([10, 0, 0, 2], [10, 0, 0, 1]).unwrap_err(); > + > + IpRange::new( > + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], > + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], > + ) > + .unwrap_err(); > + > + let v4_range = IpRange::new([10, 0, 0, 0], [10, 0, 0, 100]).unwrap(); > + assert_eq!(v4_range.family(), Family::V4); > + > + let v6_range = IpRange::new( > + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0], > + [0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1000], > + ) > + .unwrap(); > + assert_eq!(v6_range.family(), Family::V6); > + > + "10.0.0.1-10.0.0.100".parse::<IpRange>().unwrap(); > + "2001:db8::1-2001:db8::f".parse::<IpRange>().unwrap(); > + > + "10.0.0.1-2001:db8::1000".parse::<IpRange>().unwrap_err(); > + "2001:db8::1-192.168.0.2".parse::<IpRange>().unwrap_err(); > + > + "10.0.0.1-10.0.0.0".parse::<IpRange>().unwrap_err(); > + "2001:db8::1-2001:db8::0".parse::<IpRange>().unwrap_err(); > + } > + > + #[test] > + fn test_ipv4_to_cidrs() { > + let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 100]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([192, 168, 0, 100], 32).unwrap()], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([192, 168, 0, 100], [192, 168, 0, 200]).unwrap(); > + > + assert_eq!( > + [ > + Ipv4Cidr::new([192, 168, 0, 100], 30).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 200]).unwrap(); > + > + assert_eq!( > + [ > + Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 200], 32).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 101]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap()], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([192, 168, 0, 101], [192, 168, 0, 201]).unwrap(); > + > + assert_eq!( > + [ > + Ipv4Cidr::new([192, 168, 0, 101], 32).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 102], 31).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 104], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 112], 28).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 128], 26).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 192], 29).unwrap(), > + Ipv4Cidr::new([192, 168, 0, 200], 31).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([192, 168, 0, 0], [192, 168, 0, 255]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([192, 168, 0, 0], 24).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 255]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([0, 0, 0, 0], 0).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([0, 0, 0, 1], [255, 255, 255, 255]).unwrap(); > + > + assert_eq!( > + [ > + Ipv4Cidr::new([0, 0, 0, 1], 32).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 2], 31).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 4], 30).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 8], 29).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 16], 28).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 32], 27).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 64], 26).unwrap(), > + Ipv4Cidr::new([0, 0, 0, 128], 25).unwrap(), > + Ipv4Cidr::new([0, 0, 1, 0], 24).unwrap(), > + Ipv4Cidr::new([0, 0, 2, 0], 23).unwrap(), > + Ipv4Cidr::new([0, 0, 4, 0], 22).unwrap(), > + Ipv4Cidr::new([0, 0, 8, 0], 21).unwrap(), > + Ipv4Cidr::new([0, 0, 16, 0], 20).unwrap(), > + Ipv4Cidr::new([0, 0, 32, 0], 19).unwrap(), > + Ipv4Cidr::new([0, 0, 64, 0], 18).unwrap(), > + Ipv4Cidr::new([0, 0, 128, 0], 17).unwrap(), > + Ipv4Cidr::new([0, 1, 0, 0], 16).unwrap(), > + Ipv4Cidr::new([0, 2, 0, 0], 15).unwrap(), > + Ipv4Cidr::new([0, 4, 0, 0], 14).unwrap(), > + Ipv4Cidr::new([0, 8, 0, 0], 13).unwrap(), > + Ipv4Cidr::new([0, 16, 0, 0], 12).unwrap(), > + Ipv4Cidr::new([0, 32, 0, 0], 11).unwrap(), > + Ipv4Cidr::new([0, 64, 0, 0], 10).unwrap(), > + Ipv4Cidr::new([0, 128, 0, 0], 9).unwrap(), > + Ipv4Cidr::new([1, 0, 0, 0], 8).unwrap(), > + Ipv4Cidr::new([2, 0, 0, 0], 7).unwrap(), > + Ipv4Cidr::new([4, 0, 0, 0], 6).unwrap(), > + Ipv4Cidr::new([8, 0, 0, 0], 5).unwrap(), > + Ipv4Cidr::new([16, 0, 0, 0], 4).unwrap(), > + Ipv4Cidr::new([32, 0, 0, 0], 3).unwrap(), > + Ipv4Cidr::new([64, 0, 0, 0], 2).unwrap(), > + Ipv4Cidr::new([128, 0, 0, 0], 1).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([0, 0, 0, 0], [255, 255, 255, 254]).unwrap(); > + > + assert_eq!( > + [ > + Ipv4Cidr::new([0, 0, 0, 0], 1).unwrap(), > + Ipv4Cidr::new([128, 0, 0, 0], 2).unwrap(), > + Ipv4Cidr::new([192, 0, 0, 0], 3).unwrap(), > + Ipv4Cidr::new([224, 0, 0, 0], 4).unwrap(), > + Ipv4Cidr::new([240, 0, 0, 0], 5).unwrap(), > + Ipv4Cidr::new([248, 0, 0, 0], 6).unwrap(), > + Ipv4Cidr::new([252, 0, 0, 0], 7).unwrap(), > + Ipv4Cidr::new([254, 0, 0, 0], 8).unwrap(), > + Ipv4Cidr::new([255, 0, 0, 0], 9).unwrap(), > + Ipv4Cidr::new([255, 128, 0, 0], 10).unwrap(), > + Ipv4Cidr::new([255, 192, 0, 0], 11).unwrap(), > + Ipv4Cidr::new([255, 224, 0, 0], 12).unwrap(), > + Ipv4Cidr::new([255, 240, 0, 0], 13).unwrap(), > + Ipv4Cidr::new([255, 248, 0, 0], 14).unwrap(), > + Ipv4Cidr::new([255, 252, 0, 0], 15).unwrap(), > + Ipv4Cidr::new([255, 254, 0, 0], 16).unwrap(), > + Ipv4Cidr::new([255, 255, 0, 0], 17).unwrap(), > + Ipv4Cidr::new([255, 255, 128, 0], 18).unwrap(), > + Ipv4Cidr::new([255, 255, 192, 0], 19).unwrap(), > + Ipv4Cidr::new([255, 255, 224, 0], 20).unwrap(), > + Ipv4Cidr::new([255, 255, 240, 0], 21).unwrap(), > + Ipv4Cidr::new([255, 255, 248, 0], 22).unwrap(), > + Ipv4Cidr::new([255, 255, 252, 0], 23).unwrap(), > + Ipv4Cidr::new([255, 255, 254, 0], 24).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 0], 25).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 128], 26).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 192], 27).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 224], 28).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 240], 29).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 248], 30).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 252], 31).unwrap(), > + Ipv4Cidr::new([255, 255, 255, 254], 32).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([0, 0, 0, 0], [0, 0, 0, 0]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([0, 0, 0, 0], 32).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v4([255, 255, 255, 255], [255, 255, 255, 255]).unwrap(); > + > + assert_eq!( > + [Ipv4Cidr::new([255, 255, 255, 255], 32).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + } > + > + #[test] > + fn test_ipv6_to_cidrs() { > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], > + ) > + .unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 128).unwrap()], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], > + ) > + .unwrap(); > + > + assert_eq!( > + [ > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1000], 116).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], > + ) > + .unwrap(); > + > + assert_eq!( > + [ > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 128).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], > + ) > + .unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2001], > + ) > + .unwrap(); > + > + assert_eq!( > + [ > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1001], 128).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1002], 127).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1004], 126).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1008], 125).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1010], 124).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1020], 123).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1040], 122).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1080], 121).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1100], 120).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1200], 119).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1400], 118).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x1800], 117).unwrap(), > + Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0x2000], 127).unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], > + [0x2001, 0x0DB8, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], > + ) > + .unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new([0x2001, 0x0DB8, 0, 0, 0, 0, 0, 0], 64).unwrap()], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0, 0, 0, 0, 0, 0, 0, 0], > + [ > + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, > + ], > + ) > + .unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 0).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0, 0, 0, 0, 0, 0, 0, 0x0001], > + [ > + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, > + ], > + ) > + .unwrap(); > + > + assert_eq!( > + [ > + "::1/128".parse::<Ipv6Cidr>().unwrap(), > + "::2/127".parse::<Ipv6Cidr>().unwrap(), > + "::4/126".parse::<Ipv6Cidr>().unwrap(), > + "::8/125".parse::<Ipv6Cidr>().unwrap(), > + "::10/124".parse::<Ipv6Cidr>().unwrap(), > + "::20/123".parse::<Ipv6Cidr>().unwrap(), > + "::40/122".parse::<Ipv6Cidr>().unwrap(), > + "::80/121".parse::<Ipv6Cidr>().unwrap(), > + "::100/120".parse::<Ipv6Cidr>().unwrap(), > + "::200/119".parse::<Ipv6Cidr>().unwrap(), > + "::400/118".parse::<Ipv6Cidr>().unwrap(), > + "::800/117".parse::<Ipv6Cidr>().unwrap(), > + "::1000/116".parse::<Ipv6Cidr>().unwrap(), > + "::2000/115".parse::<Ipv6Cidr>().unwrap(), > + "::4000/114".parse::<Ipv6Cidr>().unwrap(), > + "::8000/113".parse::<Ipv6Cidr>().unwrap(), > + "::1:0/112".parse::<Ipv6Cidr>().unwrap(), > + "::2:0/111".parse::<Ipv6Cidr>().unwrap(), > + "::4:0/110".parse::<Ipv6Cidr>().unwrap(), > + "::8:0/109".parse::<Ipv6Cidr>().unwrap(), > + "::10:0/108".parse::<Ipv6Cidr>().unwrap(), > + "::20:0/107".parse::<Ipv6Cidr>().unwrap(), > + "::40:0/106".parse::<Ipv6Cidr>().unwrap(), > + "::80:0/105".parse::<Ipv6Cidr>().unwrap(), > + "::100:0/104".parse::<Ipv6Cidr>().unwrap(), > + "::200:0/103".parse::<Ipv6Cidr>().unwrap(), > + "::400:0/102".parse::<Ipv6Cidr>().unwrap(), > + "::800:0/101".parse::<Ipv6Cidr>().unwrap(), > + "::1000:0/100".parse::<Ipv6Cidr>().unwrap(), > + "::2000:0/99".parse::<Ipv6Cidr>().unwrap(), > + "::4000:0/98".parse::<Ipv6Cidr>().unwrap(), > + "::8000:0/97".parse::<Ipv6Cidr>().unwrap(), > + "::1:0:0/96".parse::<Ipv6Cidr>().unwrap(), > + "::2:0:0/95".parse::<Ipv6Cidr>().unwrap(), > + "::4:0:0/94".parse::<Ipv6Cidr>().unwrap(), > + "::8:0:0/93".parse::<Ipv6Cidr>().unwrap(), > + "::10:0:0/92".parse::<Ipv6Cidr>().unwrap(), > + "::20:0:0/91".parse::<Ipv6Cidr>().unwrap(), > + "::40:0:0/90".parse::<Ipv6Cidr>().unwrap(), > + "::80:0:0/89".parse::<Ipv6Cidr>().unwrap(), > + "::100:0:0/88".parse::<Ipv6Cidr>().unwrap(), > + "::200:0:0/87".parse::<Ipv6Cidr>().unwrap(), > + "::400:0:0/86".parse::<Ipv6Cidr>().unwrap(), > + "::800:0:0/85".parse::<Ipv6Cidr>().unwrap(), > + "::1000:0:0/84".parse::<Ipv6Cidr>().unwrap(), > + "::2000:0:0/83".parse::<Ipv6Cidr>().unwrap(), > + "::4000:0:0/82".parse::<Ipv6Cidr>().unwrap(), > + "::8000:0:0/81".parse::<Ipv6Cidr>().unwrap(), > + "::1:0:0:0/80".parse::<Ipv6Cidr>().unwrap(), > + "::2:0:0:0/79".parse::<Ipv6Cidr>().unwrap(), > + "::4:0:0:0/78".parse::<Ipv6Cidr>().unwrap(), > + "::8:0:0:0/77".parse::<Ipv6Cidr>().unwrap(), > + "::10:0:0:0/76".parse::<Ipv6Cidr>().unwrap(), > + "::20:0:0:0/75".parse::<Ipv6Cidr>().unwrap(), > + "::40:0:0:0/74".parse::<Ipv6Cidr>().unwrap(), > + "::80:0:0:0/73".parse::<Ipv6Cidr>().unwrap(), > + "::100:0:0:0/72".parse::<Ipv6Cidr>().unwrap(), > + "::200:0:0:0/71".parse::<Ipv6Cidr>().unwrap(), > + "::400:0:0:0/70".parse::<Ipv6Cidr>().unwrap(), > + "::800:0:0:0/69".parse::<Ipv6Cidr>().unwrap(), > + "::1000:0:0:0/68".parse::<Ipv6Cidr>().unwrap(), > + "::2000:0:0:0/67".parse::<Ipv6Cidr>().unwrap(), > + "::4000:0:0:0/66".parse::<Ipv6Cidr>().unwrap(), > + "::8000:0:0:0/65".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:1::/64".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:2::/63".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:4::/62".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:8::/61".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:10::/60".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:20::/59".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:40::/58".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:80::/57".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:100::/56".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:200::/55".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:400::/54".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:800::/53".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:1000::/52".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:2000::/51".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:4000::/50".parse::<Ipv6Cidr>().unwrap(), > + "0:0:0:8000::/49".parse::<Ipv6Cidr>().unwrap(), > + "0:0:1::/48".parse::<Ipv6Cidr>().unwrap(), > + "0:0:2::/47".parse::<Ipv6Cidr>().unwrap(), > + "0:0:4::/46".parse::<Ipv6Cidr>().unwrap(), > + "0:0:8::/45".parse::<Ipv6Cidr>().unwrap(), > + "0:0:10::/44".parse::<Ipv6Cidr>().unwrap(), > + "0:0:20::/43".parse::<Ipv6Cidr>().unwrap(), > + "0:0:40::/42".parse::<Ipv6Cidr>().unwrap(), > + "0:0:80::/41".parse::<Ipv6Cidr>().unwrap(), > + "0:0:100::/40".parse::<Ipv6Cidr>().unwrap(), > + "0:0:200::/39".parse::<Ipv6Cidr>().unwrap(), > + "0:0:400::/38".parse::<Ipv6Cidr>().unwrap(), > + "0:0:800::/37".parse::<Ipv6Cidr>().unwrap(), > + "0:0:1000::/36".parse::<Ipv6Cidr>().unwrap(), > + "0:0:2000::/35".parse::<Ipv6Cidr>().unwrap(), > + "0:0:4000::/34".parse::<Ipv6Cidr>().unwrap(), > + "0:0:8000::/33".parse::<Ipv6Cidr>().unwrap(), > + "0:1::/32".parse::<Ipv6Cidr>().unwrap(), > + "0:2::/31".parse::<Ipv6Cidr>().unwrap(), > + "0:4::/30".parse::<Ipv6Cidr>().unwrap(), > + "0:8::/29".parse::<Ipv6Cidr>().unwrap(), > + "0:10::/28".parse::<Ipv6Cidr>().unwrap(), > + "0:20::/27".parse::<Ipv6Cidr>().unwrap(), > + "0:40::/26".parse::<Ipv6Cidr>().unwrap(), > + "0:80::/25".parse::<Ipv6Cidr>().unwrap(), > + "0:100::/24".parse::<Ipv6Cidr>().unwrap(), > + "0:200::/23".parse::<Ipv6Cidr>().unwrap(), > + "0:400::/22".parse::<Ipv6Cidr>().unwrap(), > + "0:800::/21".parse::<Ipv6Cidr>().unwrap(), > + "0:1000::/20".parse::<Ipv6Cidr>().unwrap(), > + "0:2000::/19".parse::<Ipv6Cidr>().unwrap(), > + "0:4000::/18".parse::<Ipv6Cidr>().unwrap(), > + "0:8000::/17".parse::<Ipv6Cidr>().unwrap(), > + "1::/16".parse::<Ipv6Cidr>().unwrap(), > + "2::/15".parse::<Ipv6Cidr>().unwrap(), > + "4::/14".parse::<Ipv6Cidr>().unwrap(), > + "8::/13".parse::<Ipv6Cidr>().unwrap(), > + "10::/12".parse::<Ipv6Cidr>().unwrap(), > + "20::/11".parse::<Ipv6Cidr>().unwrap(), > + "40::/10".parse::<Ipv6Cidr>().unwrap(), > + "80::/9".parse::<Ipv6Cidr>().unwrap(), > + "100::/8".parse::<Ipv6Cidr>().unwrap(), > + "200::/7".parse::<Ipv6Cidr>().unwrap(), > + "400::/6".parse::<Ipv6Cidr>().unwrap(), > + "800::/5".parse::<Ipv6Cidr>().unwrap(), > + "1000::/4".parse::<Ipv6Cidr>().unwrap(), > + "2000::/3".parse::<Ipv6Cidr>().unwrap(), > + "4000::/2".parse::<Ipv6Cidr>().unwrap(), > + "8000::/1".parse::<Ipv6Cidr>().unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [0, 0, 0, 0, 0, 0, 0, 0], > + [ > + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, > + ], > + ) > + .unwrap(); > + > + assert_eq!( > + [ > + "::/1".parse::<Ipv6Cidr>().unwrap(), > + "8000::/2".parse::<Ipv6Cidr>().unwrap(), > + "c000::/3".parse::<Ipv6Cidr>().unwrap(), > + "e000::/4".parse::<Ipv6Cidr>().unwrap(), > + "f000::/5".parse::<Ipv6Cidr>().unwrap(), > + "f800::/6".parse::<Ipv6Cidr>().unwrap(), > + "fc00::/7".parse::<Ipv6Cidr>().unwrap(), > + "fe00::/8".parse::<Ipv6Cidr>().unwrap(), > + "ff00::/9".parse::<Ipv6Cidr>().unwrap(), > + "ff80::/10".parse::<Ipv6Cidr>().unwrap(), > + "ffc0::/11".parse::<Ipv6Cidr>().unwrap(), > + "ffe0::/12".parse::<Ipv6Cidr>().unwrap(), > + "fff0::/13".parse::<Ipv6Cidr>().unwrap(), > + "fff8::/14".parse::<Ipv6Cidr>().unwrap(), > + "fffc::/15".parse::<Ipv6Cidr>().unwrap(), > + "fffe::/16".parse::<Ipv6Cidr>().unwrap(), > + "ffff::/17".parse::<Ipv6Cidr>().unwrap(), > + "ffff:8000::/18".parse::<Ipv6Cidr>().unwrap(), > + "ffff:c000::/19".parse::<Ipv6Cidr>().unwrap(), > + "ffff:e000::/20".parse::<Ipv6Cidr>().unwrap(), > + "ffff:f000::/21".parse::<Ipv6Cidr>().unwrap(), > + "ffff:f800::/22".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fc00::/23".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fe00::/24".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ff00::/25".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ff80::/26".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffc0::/27".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffe0::/28".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fff0::/29".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fff8::/30".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fffc::/31".parse::<Ipv6Cidr>().unwrap(), > + "ffff:fffe::/32".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff::/33".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:8000::/34".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:c000::/35".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:e000::/36".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:f000::/37".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:f800::/38".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fc00::/39".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fe00::/40".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ff00::/41".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ff80::/42".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffc0::/43".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffe0::/44".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fff0::/45".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fff8::/46".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fffc::/47".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:fffe::/48".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff::/49".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:8000::/50".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:c000::/51".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:e000::/52".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:f000::/53".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:f800::/54".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fc00::/55".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fe00::/56".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ff00::/57".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ff80::/58".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffc0::/59".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffe0::/60".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fff0::/61".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fff8::/62".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fffc::/63".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:fffe::/64".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff::/65".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:8000::/66".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:c000::/67".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:e000::/68".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:f000::/69".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:f800::/70".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fc00::/71".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fe00::/72".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ff00::/73".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ff80::/74".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ffc0::/75".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ffe0::/76".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fff0::/77".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fff8::/78".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fffc::/79".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:fffe::/80".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ffff::/81".parse::<Ipv6Cidr>().unwrap(), > + "ffff:ffff:ffff:ffff:ffff:8000::/82" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:c000::/83" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:e000::/84" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:f000::/85" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:f800::/86" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fc00::/87" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fe00::/88" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ff00::/89" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ff80::/90" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffc0::/91" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffe0::/92" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fff0::/93" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fff8::/94" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fffc::/95" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:fffe::/96" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff::/97" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:8000:0/98" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:c000:0/99" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:e000:0/100" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:f000:0/101" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:f800:0/102" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/103" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/104" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/105" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/106" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/108" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/109" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/110" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/111" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/113" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/114" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/115" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/116" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/117" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/118" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/119" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/120" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/121" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/122" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/123" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/124" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/125" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/126" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/127" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/128" > + .parse::<Ipv6Cidr>() > + .unwrap(), > + ], > + range.to_cidrs().as_slice() > + ); > + > + let range = > + AddressRange::new_v6([0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new([0, 0, 0, 0, 0, 0, 0, 0], 128).unwrap(),], > + range.to_cidrs().as_slice() > + ); > + > + let range = AddressRange::new_v6( > + [ > + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, > + ], > + [ > + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, > + ], > + ) > + .unwrap(); > + > + assert_eq!( > + [Ipv6Cidr::new( > + [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], > + 128 > + ) > + .unwrap(),], > + range.to_cidrs().as_slice() > + ); > + } > +} > diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs > new file mode 100644 > index 00000000..b952d71c > --- /dev/null > +++ b/proxmox-network-types/src/lib.rs > @@ -0,0 +1,2 @@ > +pub mod ip_address; > +pub mod mac_address; > diff --git a/proxmox-network-types/src/mac_address.rs b/proxmox-network-types/src/mac_address.rs > new file mode 100644 > index 00000000..2c3aad29 > --- /dev/null > +++ b/proxmox-network-types/src/mac_address.rs > @@ -0,0 +1,120 @@ > +use std::fmt::Display; > +use std::net::Ipv6Addr; > + > +use serde_with::{DeserializeFromStr, SerializeDisplay}; > +use thiserror::Error; > + > +#[derive(Error, Debug)] > +pub enum MacAddressError { > + #[error("the hostname must be from 1 to 63 characters long")] > + InvalidLength, > + #[error("the hostname contains invalid symbols")] > + InvalidSymbols, > +} > + > +/// EUI-48 MAC Address > +#[derive(Clone, Debug, DeserializeFromStr, SerializeDisplay, PartialEq, Eq, Hash, PartialOrd, Ord)] > +pub struct MacAddress([u8; 6]); > + > +static LOCAL_PART: [u8; 8] = [0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; > +static EUI64_MIDDLE_PART: [u8; 2] = [0xFF, 0xFE]; > + > +impl MacAddress { > + pub fn new(address: [u8; 6]) -> Self { > + Self(address) > + } > + > + /// generates a link local IPv6-address according to RFC 4291 (Appendix A) > + pub fn eui64_link_local_address(&self) -> Ipv6Addr { > + let head = &self.0[..3]; > + let tail = &self.0[3..]; > + > + let mut eui64_address: Vec<u8> = LOCAL_PART > + .iter() > + .chain(head.iter()) > + .chain(EUI64_MIDDLE_PART.iter()) > + .chain(tail.iter()) > + .copied() > + .collect(); > + > + // we need to flip the 7th bit of the first eui64 byte > + eui64_address[8] ^= 0x02; > + > + Ipv6Addr::from( > + TryInto::<[u8; 16]>::try_into(eui64_address).expect("is an u8 array with 16 entries"), > + ) > + } > +} > + > +impl std::str::FromStr for MacAddress { > + type Err = MacAddressError; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + let split = s.split(':'); > + > + let parsed = split > + .into_iter() > + .map(|elem| u8::from_str_radix(elem, 16)) > + .collect::<Result<Vec<u8>, _>>() > + .map_err(|_| MacAddressError::InvalidSymbols)?; > + > + if parsed.len() != 6 { > + return Err(MacAddressError::InvalidLength); > + } > + > + // SAFETY: ok because of length check > + Ok(Self(parsed.try_into().unwrap())) > + } > +} > + > +impl Display for MacAddress { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + write!( > + f, > + "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}", > + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] > + ) > + } > +} > + > + > +#[cfg(test)] > +mod tests { > + use super::*; > + use std::str::FromStr; > + > + #[test] > + fn test_parse_mac_address() { > + for input in [ > + "aa:aa:aa:11:22:33", > + "AA:BB:FF:11:22:33", > + "bc:24:11:AA:bb:Ef", > + ] { > + let mac_address = input.parse::<MacAddress>().expect("valid mac address"); > + > + assert_eq!(input.to_uppercase(), mac_address.to_string()); > + } > + > + for input in [ > + "aa:aa:aa:11:22:33:aa", > + "AA:BB:FF:11:22", > + "AA:BB:GG:11:22:33", > + "AABBGG112233", > + "", > + ] { > + input > + .parse::<MacAddress>() > + .expect_err("invalid mac address"); > + } > + } > + > + #[test] > + fn test_eui64_link_local_address() { > + let mac_address: MacAddress = "BC:24:11:49:8D:75".parse().expect("valid MAC address"); > + > + let link_local_address = > + Ipv6Addr::from_str("fe80::be24:11ff:fe49:8d75").expect("valid IPv6 address"); > + > + assert_eq!(link_local_address, mac_address.eui64_link_local_address()); > + } > +} _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [pve-devel] [PATCH access-control/cluster/docs/gui-tests/manager/network/proxmox{, -firewall, -ve-rs, -perl-rs, -widget-toolkit} v3 00/75] Add SDN Fabrics @ 2025-05-22 16:16 Stefan Hanreich 2025-05-22 16:16 ` [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate to proxmox-network-types Stefan Hanreich 0 siblings, 1 reply; 11+ messages in thread From: Stefan Hanreich @ 2025-05-22 16:16 UTC (permalink / raw) To: pve-devel Overview ======== This series allows the user to easily use dynamic routing protocols such as OpenFabric and OSPF in their clusters. It also integrates existing features, such as Ceph with the new SDN fabrics feature to enable users simple configuration of e.g. full-mesh Ceph clusters via the Web UI. This patch series adds the initial support for two routing protocols: * OpenFabric * OSPF In the future we plan on moving the existing IS-IS and BGP controllers into the fabric structure. Christoph Heiss is also currently working on adding a new Wireguard fabric, which can be combined with any other fabric types. This feature allows layering different fabrics on top of each other, so adding encryption to an existing fabric is as simple as just putting a Wireguard fabric on top, or using a Wireguard fabric as the basis. Packages are available on sani: packages/sdn-fabrics-v3 Implementation ============== Every fabric consists of zero or more nodes, which themselves consist of zero or more interfaces. Fabrics and nodes are modeled as different section config types (which means two section types for each protocol), interfaces are an array contained in a node section. For now, nodes in the fabric configuration always represent PVE nodes, but in the future nodes could also represent external members of the fabric (e.g. in a potential Wireguard fabric). An example use case for this would be securely connecting PBS or PDM instances to the PVE cluster via Wireguard. Most of the functionality is implemented in Rust and exposed to the existing SDN module via perlmod. This includes configuration reading / writing, FRR config generation from the section config and API CRUD methods. Some functionality, like digest matching and permission checking is still handled on the perl side, due to the lack of facilities in Rust for that. Configuration Format -------------------- The whole configuration is now contained in just one configuration file `/etc/pve/sdn/fabrics.cfg`. This makes handling the fabrics configuration easier in many different areas: locking, digest calculation, validation. For every protocol there are two different section types (fabric and node). As an example the two section types for OSPF are 'ospf_fabric' and 'ospf_node'. The ID of a fabric is a simple name, at most 8 alphanumeric characters since we use it for generating network interfaces names with a prefix. This is analogous to existing SDN entities, e.g. VNet. A node can only be uniquely identified by its id (which is equivalent to the hostname of the node), as well as the fabric_id. This is because a node can be part of multiple fabrics. An example how the configuration looks like for a full-mesh 3-node Openfabric fabric called 'example': openfabric_fabric: example csnp_interval 3 hello_interval 3 ip6_prefix 2001:db8::/64 ip_prefix 192.0.2.0/24 openfabric_node: example_deadeye interfaces name=eth1,ip=198.51.100.0/31 interfaces name=eth2 ip 192.0.2.1 ip6 2001:db8::1 openfabric_node: example_pathfinder interfaces name=eth1,ip=198.51.100.2/31 interfaces name=eth2 ip 192.0.2.2 ip6 2001:db8::2 openfabric_node: example_raider interfaces name=eth1,ip=198.51.100.4/31 interfaces name=eth2 ip 192.0.2.3 ip6 2001:db8::3 We parse the configuration file flat in rust, and then afterwards split them into Fabric / Node structs and store them hierarchically (fabric -> node) in a dedicated FabricConfig struct. This struct provides the CRUD methods for manipulating the FabricConfig safely as well as serializing the FabricConfig back into its section config format. To prevent having to duplicate common properties for every protocol, we introduced a generic (Fabric|Node)Section<T> struct that contains all common properties. Protocol-specific properties can be defined by the generic type parameter T. This saves us from duplicating a lot of code (which was an initial problem with the intermediate configuration) and conversions can be simplified by providing a generic implementation that every protocol uses. This design also means that adding new protocols to the configuration is quite straightforward: It is only required to add structs with the protocol-specific properties in Rust and add them to the enums defining the Section Config. The commit adding OSPF support shows how simple it is to add a new protocol. Validation ---------- The hierarchical nature of the configuration and the relationship between nodes inside the fabrics requires validation of sections relative to other sections. For this matter we introduced a new Validatable trait as well as a struct that wraps valid configuration in a Valid<T> struct. For more information on that see the respective commit. API & Permissions ----------------- The whole API is contained in the /cluster/sdn/fabrics subfolder and contains submodules for fabric / node. A quick overview of the methods provided by the API: GET /all - list fabrics & nodes GET /fabric - list all fabrics POST /fabric - create a fabric GET /fabric/{fabric_id} - get a single fabric PUT /fabric/{fabric_id} - update a fabric DELETE /fabric/{fabric_id} - delete a fabric GET /node - list all nodes (regardless of fabric) GET /node/{fabric_id} - list all nodes belonging to fabric {fabric_id} POST /node/{fabric_id} - create a node in fabric {fabric_id} GET /node/{fabric_id}/{node_id} - get a single node PUT /node/{fabric_id}/{node_id} - update a single node DELETE /node/{fabric_id}/{node_id} - delete a single node FRR Configuration ----------------- For the FRR-specific functionality we introduced a new proxmox-frr crate that models the different entities in the FRR configuration format (routers, interfaces, route-maps, ...) and provides serializers for those structs. For more information see the respective FRR commits. When applying the SDN configuration, perl calls into perlmod to utilize the proxmox-frr crate for generating the FRR configuration of the fabrics. We also introduce a proxmox-sdn-types crate, where we extracted generic fabric types (e.g., openfabric::HelloInterval), so we can reuse them across multiple crates (proxmox-frr, proxmox-ve-config, ..). UI -- The UI allows users to easily create different types of fabrics. One can add Nodes to the fabrics by selecting them from a dropdown which shows all the nodes in the cluster. Additionally the user can then select the interfaces of the node which should be added to the fabric. There are also protocol-specific options such as "passive", "hello-interval" etc. available to select on the interface. There are also options spanning whole fabrics: the "hello-interval" option on openfabric for example, can be set on the fabric and will be applied to every interface. We are also working on the integration of status reporting into the sidebar, which also includes an integration into pvestatd. The plan is to show the status for each node, which routes are learned, neighbor status and possibly topology from the POV of each node. Since this patch series is already quite huge and the sidebar integration is still a work in progress it is not included here. Integration with existing features ---------------------------------- We also provide a UI for the Ceph, Migration Network, VXLAN zone and EVPN controller integrations. Users can configure fabrics for those components simply by selecting them from a dropdown, providing a streamlined experience and nice integration with existing features. Refactoring =========== This patch series required some rework of existing functionality, mostly how SDN generates the FRR configuration and writes /etc/network/interfaces. Prior the FRR configuration was generated exclusively from the controllers, but fabrics need to write it as well. Same goes for the interfaces file, which got written by the Zone plugin, but Fabrics need to write this file as well. For this we moved the FRR and ifupdown config generation one level up to the SDN module, which now calls into the respective child modules to generate the FRR / ifupdown configuration. Dependencies ============ This series relies on the FRR 10.2.2 backport series, since it fixes potential issues with EVPN + Openfabric/OSPF: https://lore.proxmox.com/all/20250418112114.2747673-1-s.hanreich@proxmox.com/ proxmox-frr depends on proxmox-network-types proxmox-frr depends on proxmox-sdn-types proxmox-ve-config depends on proxmox-frr proxmox-ve-config depends on proxmox-network-types proxmox-ve-config depends on proxmox-sdn-types proxmox-ve-config depends on proxmox-serde proxmox-ve-config depends on proxmox-api-macro proxmox-firewall depends on proxmox-ve-config proxmox-perl-rs depends on proxmox-ve-config proxmox-perl-rs depends on proxmox-frr proxmox-perl-rs depends on proxmox-network-types pve-network depends on proxmox-perl-rs pve-network depends on pve-cluster pve-network depends on pve-access-control pve-docs depends on pve-gui-tests pve-manager depends on proxmox-widget-toolkit pve-manager depends on pve-docs pve-manager depends on pve-network pve-manager depends on pve-access-control pve-network commits 4-7 do not build independently, because it's one refactor but split across multiple commits so it's easier to follow the steps during the refactor. We could consider squashing those commits on applying, so each commit still builds indepedently. Shoutout to Gabriel for his great work on this patch series! Changelog v3: ============= * Improved dual-stack support considerably * Completely reworked configuration format and the respective Rust representation (see above for more details) * Completely reworked API design (see above for more details) * refactored UI code and split NodeEdit panels into protocol-specific components * Adapted permission paths to represent API structure * Integrated Fabrics with the VXLAN zone + EVPN controller * Added UI integration for fabrics to the following components: * Migration Network Settings * Ceph Installation Wizard * VXLAN Zone * EVPN controller * added some quality of life features to the UI (e.g. Create another Node) * many smaller bug fixes Changelog v2: ============= * split proxmox-network-types (this is done in a separate series) * move Cidr-types and hostname to proxmox-network-types in the proxmox repo * rename the proxmox-ve-rs/proxmox-network-types crate to proxmox-sdn-types and put all the openfabric/ospf common types There * fix ospf route-map generation and loopback_prefixes * fix integration tests and add some more * add fabric_id to OSPF, which acts as a primary (but arbitrary) id. The area also has to be unique, but is only a required property now. * this makes permissions easier, as every protocol has a "fabric_id" property we can check * the users can choose a arbitrary name for the fabric and are not limited just by numbers and ip-addresses * improve documentation wording * add screenshots to documentation * implement permissions in pve-access-control and pve-network * made CRUD order in API modules and Common module consistent * improve pve-network API descriptions * improve pve-network API return types * add helpers for common options * refactored duplicated API types into a single variable inside the API modules * rework FRR reload logic - it now reloads only when daemons file stayed the same, otherwise it restarts * add fabric_id and node_id properties to the node section in OpenFabric and OSPF (this allows us to be more generic over both protocols, useful in e.g. frontend and permissions) * make frontend components generic over protocols * drop similar-asserts and use insta instead for integration tests * added missing htmlencodes to tooltips / warning messages / tree column outputs * hide action icons when node / fabric gets deleted * added directory index to the root fabric method * add digest to update calls * improved format for fabrics in running configuration * improved logic of set_daemon_status * check for existence of /etc/frr/daemons before trying to read it * OSPF interfaces now must have an IP or be unnumbered Open issues: Directory index is still missing for the ospf/openfabric subfolders, since we don't have a 'GET /' endpoint there - could be added in a followup? Network interfaces that have an entry in the interfaces file with the manual stanza, do not get their IPs deconfigured when deleting the interfaces from a fabric. This issue is documented. Changelog v1: ============= proxmox-ve-rs ------------- * remove intermediate-config, convert section-config directly to frr-types. * add validation layer to validate the section-config * simplify openfabric `net` to `router-id` * add loopback prefixes to ensure that all router-ids are in a specific subnet * generate router-map and access-lists to rewrite the source address of all the routes received through openfabric and ospf * add integration tests * add option for ospf unnumbered * only allow ipv4 on ospf pve-network ------------- * rework frr config generation * rework etc/network/interfaces config generation * revert "return loopback interface" proxmox-perl-rs ------------- * generate /etc/network/interfaces config to set ip-addresses * auto-generate dummy interface for every fabric pve-manager ------------- * simplify a lot * remove interface entries in tree * hide specific openfabric/ospf options (hello-interval, passive etc.) frr (external) -------------- * fix --dummy_as_loopback bug (already on staging) RFC === Changelog v2: ============= proxmox-ve-rs ------------- * serialize internal representation directly to the frr format * add integration tests to proxmox-frr * change internal representation to use BTreeMap instead of HashMap (so that the test output is ordered) * move some stuff from proxmox-frr and proxmox-ve-config to proxmox-network-types pve-network ----------- * generate frr config and append to running config directly (without going through perl frr merging) * check permissions on each fabric when listing pve-manager ----------- * autogenerate net and router-id when selecting the first interface pve-cluster ----------- * update the config files in status.c (pve-cluster) (thanks @Thomas) frr (external) -------------- * got this one merged: https://github.com/FRRouting/frr/pull/18242, so we *could* automatically add dummy interfaces Big thanks to Gabriel Goller for his help and support throughout this series! proxmox: Stefan Hanreich (4): network-types: initial commit network-types: make cidr and mac-address types usable by the api network-types: add api types for ipv4/6 api-macro: add allof schema to enum Cargo.toml | 2 + proxmox-api-macro/src/api/enums.rs | 1 + proxmox-network-types/Cargo.toml | 22 + proxmox-network-types/debian/changelog | 5 + proxmox-network-types/debian/copyright | 18 + proxmox-network-types/debian/debcargo.toml | 7 + proxmox-network-types/src/ip_address.rs | 1572 ++++++++++++++++++++ proxmox-network-types/src/lib.rs | 5 + proxmox-network-types/src/mac_address.rs | 146 ++ 9 files changed, 1778 insertions(+) create mode 100644 proxmox-network-types/Cargo.toml create mode 100644 proxmox-network-types/debian/changelog create mode 100644 proxmox-network-types/debian/copyright create mode 100644 proxmox-network-types/debian/debcargo.toml create mode 100644 proxmox-network-types/src/ip_address.rs create mode 100644 proxmox-network-types/src/lib.rs create mode 100644 proxmox-network-types/src/mac_address.rs proxmox-firewall: Stefan Hanreich (1): firewall: nftables: migrate to proxmox-network-types Cargo.toml | 1 + proxmox-firewall/Cargo.toml | 1 + proxmox-firewall/src/firewall.rs | 8 ++++---- proxmox-firewall/src/object.rs | 4 +++- proxmox-firewall/src/rule.rs | 3 ++- proxmox-nftables/Cargo.toml | 3 ++- proxmox-nftables/src/expression.rs | 7 +++---- proxmox-nftables/src/types.rs | 2 +- 8 files changed, 17 insertions(+), 12 deletions(-) proxmox-ve-rs: Gabriel Goller (7): frr: create proxmox-frr crate frr: add common frr types frr: add openfabric types frr: add ospf types frr: add route-map types frr: add generic types over openfabric and ospf ve-config: add integrations tests Stefan Hanreich (14): config: use proxmox_serde perl helpers ve-config: move types to proxmox-network-types sdn-types: initial commit config: sdn: fabrics: add section types config: sdn: fabrics: add node section types config: sdn: fabrics: add interface name struct config: sdn: fabrics: add openfabric properties config: sdn: fabrics: add ospf properties config: sdn: fabrics: add api types config: sdn: fabrics: add section config config: sdn: fabrics: add fabric config common: sdn: fabrics: implement validation sdn: fabrics: config: add conversion from / to section config sdn: fabrics: implement FRR configuration generation Cargo.toml | 14 + proxmox-frr/Cargo.toml | 23 + proxmox-frr/debian/changelog | 5 + proxmox-frr/debian/control | 49 + proxmox-frr/debian/copyright | 18 + proxmox-frr/debian/debcargo.toml | 7 + proxmox-frr/src/lib.rs | 231 +++ proxmox-frr/src/openfabric.rs | 114 ++ proxmox-frr/src/ospf.rs | 179 +++ proxmox-frr/src/route_map.rs | 233 +++ proxmox-frr/src/serializer.rs | 203 +++ proxmox-sdn-types/Cargo.toml | 19 + proxmox-sdn-types/debian/changelog | 5 + proxmox-sdn-types/debian/control | 53 + proxmox-sdn-types/debian/copyright | 18 + proxmox-sdn-types/debian/debcargo.toml | 7 + proxmox-sdn-types/src/area.rs | 50 + proxmox-sdn-types/src/lib.rs | 3 + proxmox-sdn-types/src/net.rs | 329 ++++ proxmox-sdn-types/src/openfabric.rs | 72 + proxmox-ve-config/Cargo.toml | 24 +- proxmox-ve-config/debian/control | 41 +- proxmox-ve-config/src/common/mod.rs | 2 + proxmox-ve-config/src/common/valid.rs | 53 + proxmox-ve-config/src/firewall/bridge.rs | 3 +- proxmox-ve-config/src/firewall/cluster.rs | 9 +- proxmox-ve-config/src/firewall/ct_helper.rs | 2 +- proxmox-ve-config/src/firewall/guest.rs | 14 +- proxmox-ve-config/src/firewall/host.rs | 30 +- proxmox-ve-config/src/firewall/parse.rs | 80 - .../src/firewall/types/address.rs | 1394 +---------------- proxmox-ve-config/src/firewall/types/alias.rs | 2 +- proxmox-ve-config/src/firewall/types/ipset.rs | 8 +- proxmox-ve-config/src/firewall/types/mod.rs | 1 - proxmox-ve-config/src/firewall/types/rule.rs | 5 +- .../src/firewall/types/rule_match.rs | 6 +- proxmox-ve-config/src/guest/vm.rs | 96 +- proxmox-ve-config/src/host/utils.rs | 2 +- proxmox-ve-config/src/sdn/config.rs | 9 +- proxmox-ve-config/src/sdn/fabric/frr.rs | 390 +++++ proxmox-ve-config/src/sdn/fabric/mod.rs | 732 +++++++++ .../src/sdn/fabric/section_config/fabric.rs | 256 +++ .../sdn/fabric/section_config/interface.rs | 22 + .../src/sdn/fabric/section_config/mod.rs | 109 ++ .../src/sdn/fabric/section_config/node.rs | 397 +++++ .../sdn/fabric/section_config/protocol/mod.rs | 2 + .../section_config/protocol/openfabric.rs | 141 ++ .../fabric/section_config/protocol/ospf.rs | 130 ++ proxmox-ve-config/src/sdn/frr.rs | 42 + proxmox-ve-config/src/sdn/ipam.rs | 11 +- proxmox-ve-config/src/sdn/mod.rs | 6 +- .../fabric/cfg/openfabric_default/fabrics.cfg | 18 + .../cfg/openfabric_dualstack/fabrics.cfg | 22 + .../cfg/openfabric_ipv6_only/fabrics.cfg | 18 + .../cfg/openfabric_loopback/fabrics.cfg | 18 + .../fabrics.cfg | 25 + .../cfg/openfabric_multi_fabric/fabrics.cfg | 25 + .../fabrics.cfg | 25 + .../openfabric_verification_fail/fabrics.cfg | 12 + .../tests/fabric/cfg/ospf_default/fabrics.cfg | 13 + .../cfg/ospf_loopback_prefix_fail/fabrics.cfg | 17 + .../fabric/cfg/ospf_multi_fabric/fabrics.cfg | 25 + .../cfg/ospf_verification_fail/fabrics.cfg | 13 + proxmox-ve-config/tests/fabric/helper.rs | 43 + proxmox-ve-config/tests/fabric/main.rs | 141 ++ .../fabric__openfabric_default_pve.snap | 34 + .../fabric__openfabric_default_pve1.snap | 33 + .../fabric__openfabric_dualstack_pve.snap | 46 + .../fabric__openfabric_ipv6_only_pve.snap | 34 + .../fabric__openfabric_multi_fabric_pve1.snap | 49 + .../snapshots/fabric__ospf_default_pve.snap | 32 + .../snapshots/fabric__ospf_default_pve1.snap | 28 + .../fabric__ospf_multi_fabric_pve1.snap | 45 + proxmox-ve-config/tests/sdn/main.rs | 11 +- 74 files changed, 4765 insertions(+), 1613 deletions(-) create mode 100644 proxmox-frr/Cargo.toml create mode 100644 proxmox-frr/debian/changelog create mode 100644 proxmox-frr/debian/control create mode 100644 proxmox-frr/debian/copyright create mode 100644 proxmox-frr/debian/debcargo.toml create mode 100644 proxmox-frr/src/lib.rs create mode 100644 proxmox-frr/src/openfabric.rs create mode 100644 proxmox-frr/src/ospf.rs create mode 100644 proxmox-frr/src/route_map.rs create mode 100644 proxmox-frr/src/serializer.rs create mode 100644 proxmox-sdn-types/Cargo.toml create mode 100644 proxmox-sdn-types/debian/changelog create mode 100644 proxmox-sdn-types/debian/control create mode 100644 proxmox-sdn-types/debian/copyright create mode 100644 proxmox-sdn-types/debian/debcargo.toml create mode 100644 proxmox-sdn-types/src/area.rs create mode 100644 proxmox-sdn-types/src/lib.rs create mode 100644 proxmox-sdn-types/src/net.rs create mode 100644 proxmox-sdn-types/src/openfabric.rs create mode 100644 proxmox-ve-config/src/common/valid.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/frr.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/mod.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/interface.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/mod.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/node.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/openfabric.rs create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/ospf.rs create mode 100644 proxmox-ve-config/src/sdn/frr.rs create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_default/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_dualstack/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_ipv6_only/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_loopback/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_loopback_prefix_fail/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_multi_fabric/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_same_net_on_same_node/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/openfabric_verification_fail/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/ospf_default/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/ospf_loopback_prefix_fail/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/ospf_multi_fabric/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/cfg/ospf_verification_fail/fabrics.cfg create mode 100644 proxmox-ve-config/tests/fabric/helper.rs create mode 100644 proxmox-ve-config/tests/fabric/main.rs create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__openfabric_default_pve.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__openfabric_default_pve1.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__openfabric_dualstack_pve.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__openfabric_ipv6_only_pve.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__openfabric_multi_fabric_pve1.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__ospf_default_pve.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__ospf_default_pve1.snap create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__ospf_multi_fabric_pve1.snap proxmox-perl-rs: Stefan Hanreich (5): pve-rs: Add PVE::RS::SDN::Fabrics module pve-rs: sdn: fabrics: add api methods pve-rs: sdn: fabrics: add frr config generation pve-rs: sdn: fabrics: add helper to generate ifupdown2 configuration pve-rs: sdn: fabrics: add helper for network API endpoint pve-rs/Cargo.toml | 5 +- pve-rs/Makefile | 1 + pve-rs/debian/control | 2 + pve-rs/src/bindings/mod.rs | 3 + pve-rs/src/bindings/sdn/fabrics.rs | 506 +++++++++++++++++++++++++++++ pve-rs/src/bindings/sdn/mod.rs | 1 + 6 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 pve-rs/src/bindings/sdn/fabrics.rs create mode 100644 pve-rs/src/bindings/sdn/mod.rs pve-cluster: Stefan Hanreich (1): cfs: add fabrics.cfg to observed files debian/pve-cluster.postinst | 24 ++++++++++++++++++++++++ src/PVE/Cluster.pm | 3 +-- src/pmxcfs/status.c | 3 +-- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 debian/pve-cluster.postinst pve-access-control: Stefan Hanreich (1): permissions: add ACL paths for SDN fabrics src/PVE/AccessControl.pm | 2 ++ 1 file changed, 2 insertions(+) pve-network: Gabriel Goller (1): debian: add dependency to proxmox-perl-rs Stefan Hanreich (20): sdn: fix value returned by pending_config fabrics: add fabrics module refactor: controller: move frr methods into helper frr: add new helpers for reloading frr configuration controllers: define new api for frr config generation sdn: add frr config generation helpers sdn: api: add check for rewriting frr configuration test: isis: add test for standalone configuration sdn: frr: add daemon status to frr helper sdn: commit fabrics config to running configuration fabrics: generate ifupdown configuration fabrics: add jsonschema for fabrics and nodes api: fabrics: add root-level module api: fabrics: add fabric submodule api: fabrics: add node submodule api: fabrics: add fabricnode submodule controller: evpn: add fabrics integration zone: vxlan: add fabrics integration test: fabrics: add test cases for ospf and openfabric + evpn frr: bump frr config version to 10.2.2 debian/control | 17 +- src/PVE/API2/Network/SDN.pm | 22 +- src/PVE/API2/Network/SDN/Fabrics.pm | 180 +++++++ src/PVE/API2/Network/SDN/Fabrics/Fabric.pm | 248 ++++++++++ .../API2/Network/SDN/Fabrics/FabricNode.pm | 242 +++++++++ src/PVE/API2/Network/SDN/Fabrics/Makefile | 8 + src/PVE/API2/Network/SDN/Fabrics/Node.pm | 113 +++++ src/PVE/API2/Network/SDN/Makefile | 3 +- src/PVE/Network/SDN.pm | 158 +++++- src/PVE/Network/SDN/Controllers.pm | 67 +-- src/PVE/Network/SDN/Controllers/BgpPlugin.pm | 21 +- src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 441 +++++------------ src/PVE/Network/SDN/Controllers/IsisPlugin.pm | 21 +- src/PVE/Network/SDN/Controllers/Plugin.pm | 31 +- src/PVE/Network/SDN/Fabrics.pm | 290 +++++++++++ src/PVE/Network/SDN/Frr.pm | 465 ++++++++++++++++++ src/PVE/Network/SDN/Makefile | 2 +- src/PVE/Network/SDN/Zones.pm | 10 - src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 56 ++- src/PVE/Network/SDN/Zones/VxlanPlugin.pm | 58 ++- src/test/run_test_zones.pl | 11 +- .../expected_controller_config | 2 +- .../expected_controller_config | 2 +- .../evpn/ebgp/expected_controller_config | 2 +- .../ebgp_loopback/expected_controller_config | 2 +- .../evpn/exitnode/expected_controller_config | 2 +- .../expected_controller_config | 2 +- .../expected_controller_config | 2 +- .../exitnode_snat/expected_controller_config | 2 +- .../expected_controller_config | 2 +- .../evpn/ipv4/expected_controller_config | 2 +- .../evpn/ipv4ipv6/expected_controller_config | 2 +- .../expected_controller_config | 2 +- .../evpn/ipv6/expected_controller_config | 2 +- .../ipv6underlay/expected_controller_config | 2 +- .../evpn/isis/expected_controller_config | 2 +- .../isis_loopback/expected_controller_config | 2 +- .../expected_controller_config | 22 + .../isis_standalone/expected_sdn_interfaces | 1 + .../zones/evpn/isis_standalone/interfaces | 12 + .../zones/evpn/isis_standalone/sdn_config | 21 + .../expected_controller_config | 2 +- .../multiplezones/expected_controller_config | 2 +- .../expected_controller_config | 74 +++ .../openfabric_fabric/expected_sdn_interfaces | 56 +++ .../zones/evpn/openfabric_fabric/interfaces | 6 + .../zones/evpn/openfabric_fabric/sdn_config | 79 +++ .../ospf_fabric/expected_controller_config | 68 +++ .../evpn/ospf_fabric/expected_sdn_interfaces | 53 ++ src/test/zones/evpn/ospf_fabric/interfaces | 6 + src/test/zones/evpn/ospf_fabric/sdn_config | 76 +++ .../evpn/rt_import/expected_controller_config | 2 +- .../evpn/vxlanport/expected_controller_config | 2 +- 53 files changed, 2454 insertions(+), 524 deletions(-) create mode 100644 src/PVE/API2/Network/SDN/Fabrics.pm create mode 100644 src/PVE/API2/Network/SDN/Fabrics/Fabric.pm create mode 100644 src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm create mode 100644 src/PVE/API2/Network/SDN/Fabrics/Makefile create mode 100644 src/PVE/API2/Network/SDN/Fabrics/Node.pm create mode 100644 src/PVE/Network/SDN/Fabrics.pm create mode 100644 src/PVE/Network/SDN/Frr.pm create mode 100644 src/test/zones/evpn/isis_standalone/expected_controller_config create mode 100644 src/test/zones/evpn/isis_standalone/expected_sdn_interfaces create mode 100644 src/test/zones/evpn/isis_standalone/interfaces create mode 100644 src/test/zones/evpn/isis_standalone/sdn_config create mode 100644 src/test/zones/evpn/openfabric_fabric/expected_controller_config create mode 100644 src/test/zones/evpn/openfabric_fabric/expected_sdn_interfaces create mode 100644 src/test/zones/evpn/openfabric_fabric/interfaces create mode 100644 src/test/zones/evpn/openfabric_fabric/sdn_config create mode 100644 src/test/zones/evpn/ospf_fabric/expected_controller_config create mode 100644 src/test/zones/evpn/ospf_fabric/expected_sdn_interfaces create mode 100644 src/test/zones/evpn/ospf_fabric/interfaces create mode 100644 src/test/zones/evpn/ospf_fabric/sdn_config proxmox-widget-toolkit: Stefan Hanreich (1): network selector: add type parameter src/form/NetworkSelector.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) pve-manager: Gabriel Goller (3): api: use new sdn config generation functions fabrics: Add main FabricView utils: avoid line-break in pending changes message Stefan Hanreich (15): ui: fabrics: add model definitions for fabrics fabric: add common interface panel fabric: add OpenFabric interface properties fabric: add OSPF interface properties fabric: add generic node edit panel fabric: add OpenFabric node edit fabric: add OSPF node edit fabric: add generic fabric edit panel fabric: add OpenFabric fabric edit panel fabric: add OSPF fabric edit panel ui: permissions: add ACL path for fabrics api: network: add include_sdn / fabric type ui: add sdn networks to ceph / migration ui: sdn: add evpn controller fabric integration ui: sdn: vxlan: add fabric property PVE/API2/Network.pm | 56 ++- www/manager6/Makefile | 11 + www/manager6/Utils.js | 2 +- www/manager6/ceph/CephInstallWizard.js | 2 + www/manager6/data/PermPathStore.js | 1 + www/manager6/dc/Config.js | 8 + www/manager6/dc/OptionView.js | 1 + www/manager6/sdn/FabricsView.js | 464 ++++++++++++++++++ www/manager6/sdn/controllers/Base.js | 17 + www/manager6/sdn/controllers/EvpnEdit.js | 35 +- www/manager6/sdn/fabrics/Common.js | 36 ++ www/manager6/sdn/fabrics/FabricEdit.js | 57 +++ www/manager6/sdn/fabrics/InterfacePanel.js | 220 +++++++++ www/manager6/sdn/fabrics/NodeEdit.js | 224 +++++++++ .../sdn/fabrics/openfabric/FabricEdit.js | 47 ++ .../sdn/fabrics/openfabric/InterfacePanel.js | 34 ++ .../sdn/fabrics/openfabric/NodeEdit.js | 22 + www/manager6/sdn/fabrics/ospf/FabricEdit.js | 20 + .../sdn/fabrics/ospf/InterfacePanel.js | 3 + www/manager6/sdn/fabrics/ospf/NodeEdit.js | 8 + www/manager6/sdn/zones/VxlanEdit.js | 52 +- 21 files changed, 1303 insertions(+), 17 deletions(-) create mode 100644 www/manager6/sdn/FabricsView.js create mode 100644 www/manager6/sdn/fabrics/Common.js create mode 100644 www/manager6/sdn/fabrics/FabricEdit.js create mode 100644 www/manager6/sdn/fabrics/InterfacePanel.js create mode 100644 www/manager6/sdn/fabrics/NodeEdit.js create mode 100644 www/manager6/sdn/fabrics/openfabric/FabricEdit.js create mode 100644 www/manager6/sdn/fabrics/openfabric/InterfacePanel.js create mode 100644 www/manager6/sdn/fabrics/openfabric/NodeEdit.js create mode 100644 www/manager6/sdn/fabrics/ospf/FabricEdit.js create mode 100644 www/manager6/sdn/fabrics/ospf/InterfacePanel.js create mode 100644 www/manager6/sdn/fabrics/ospf/NodeEdit.js pve-gui-tests: Gabriel Goller (1): pve: add sdn/fabrics screenshots create_fabrics_screenshots | 198 +++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100755 create_fabrics_screenshots pve-docs: Gabriel Goller (1): fabrics: add initial documentation for sdn fabrics pvesdn.adoc | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) Summary over all repositories: 178 files changed, 11299 insertions(+), 2173 deletions(-) -- Generated by git-murpp 0.8.0 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate to proxmox-network-types 2025-05-22 16:16 [pve-devel] [PATCH access-control/cluster/docs/gui-tests/manager/network/proxmox{, -firewall, -ve-rs, -perl-rs, -widget-toolkit} v3 00/75] Add SDN Fabrics Stefan Hanreich @ 2025-05-22 16:16 ` Stefan Hanreich 0 siblings, 0 replies; 11+ messages in thread From: Stefan Hanreich @ 2025-05-22 16:16 UTC (permalink / raw) To: pve-devel The fabrics patch series moved some generic network types into its own crate, so they can be reused across crates. Migrate proxmox-firewall to use the new proxmox-network-types crate instead of proxmox_ve_config. No functional changes intended. Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> --- Cargo.toml | 1 + proxmox-firewall/Cargo.toml | 1 + proxmox-firewall/src/firewall.rs | 8 ++++---- proxmox-firewall/src/object.rs | 4 +++- proxmox-firewall/src/rule.rs | 3 ++- proxmox-nftables/Cargo.toml | 3 ++- proxmox-nftables/src/expression.rs | 7 +++---- proxmox-nftables/src/types.rs | 2 +- 8 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdb4a53..97138ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ resolver = "2" [workspace.dependencies] proxmox-ve-config = { version = "0.2.3" } +proxmox-network-types = { version = "0.1" } diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml index a9abf93..167608d 100644 --- a/proxmox-firewall/Cargo.toml +++ b/proxmox-firewall/Cargo.toml @@ -22,6 +22,7 @@ signal-hook = "0.3" proxmox-nftables = { path = "../proxmox-nftables", features = ["config-ext"] } proxmox-ve-config = { workspace = true } +proxmox-network-types = { workspace = true } [dev-dependencies] insta = { version = "1.21", features = ["json"] } diff --git a/proxmox-firewall/src/firewall.rs b/proxmox-firewall/src/firewall.rs index 086b96c..635cf42 100644 --- a/proxmox-firewall/src/firewall.rs +++ b/proxmox-firewall/src/firewall.rs @@ -20,7 +20,7 @@ use proxmox_ve_config::firewall::ct_helper::get_cthelper; use proxmox_ve_config::firewall::guest::Config as GuestConfig; use proxmox_ve_config::firewall::host::Config as HostConfig; -use proxmox_ve_config::firewall::types::address::Ipv6Cidr; +use proxmox_network_types::ip_address::{Cidr, Ipv6Cidr}; use proxmox_ve_config::firewall::types::ipset::{ Ipfilter, Ipset, IpsetEntry, IpsetName, IpsetScope, }; @@ -808,14 +808,14 @@ impl Firewall { let cidr = Ipv6Cidr::from(network_device.mac_address().eui64_link_local_address()); - ipset.push(cidr.into()); + ipset.push(IpsetEntry::from(Cidr::from(cidr))); if let Some(ip_address) = network_device.ip() { - ipset.push(IpsetEntry::from(ip_address)); + ipset.push(IpsetEntry::from(Cidr::from(ip_address))); } if let Some(ip6_address) = network_device.ip6() { - ipset.push(IpsetEntry::from(ip6_address)); + ipset.push(IpsetEntry::from(Cidr::from(ip6_address))); } commands.append(&mut ipset.to_nft_objects(&env)?); diff --git a/proxmox-firewall/src/object.rs b/proxmox-firewall/src/object.rs index cf7e773..cbfadba 100644 --- a/proxmox-firewall/src/object.rs +++ b/proxmox-firewall/src/object.rs @@ -11,11 +11,13 @@ use proxmox_nftables::{ use proxmox_ve_config::{ firewall::{ ct_helper::CtHelperMacro, - types::{address::Family, alias::AliasName, ipset::IpsetAddress, Alias, Ipset}, + types::{alias::AliasName, ipset::IpsetAddress, Alias, Ipset}, }, guest::types::Vmid, }; +use proxmox_network_types::ip_address::Family; + use crate::config::FirewallConfig; pub(crate) struct NftObjectEnv<'a, 'b> { diff --git a/proxmox-firewall/src/rule.rs b/proxmox-firewall/src/rule.rs index 14ee544..16a0b5a 100644 --- a/proxmox-firewall/src/rule.rs +++ b/proxmox-firewall/src/rule.rs @@ -12,7 +12,6 @@ use proxmox_ve_config::{ ct_helper::CtHelperMacro, fw_macros::{get_macro, FwMacro}, types::{ - address::Family, alias::AliasName, ipset::{Ipfilter, IpsetName}, log::LogRateLimit, @@ -26,6 +25,8 @@ use proxmox_ve_config::{ guest::types::Vmid, }; +use proxmox_network_types::ip_address::Family; + use crate::config::FirewallConfig; #[derive(Debug, Clone)] diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml index 4ff6f41..85f07f0 100644 --- a/proxmox-nftables/Cargo.toml +++ b/proxmox-nftables/Cargo.toml @@ -11,7 +11,7 @@ description = "Proxmox VE nftables" license = "AGPL-3" [features] -config-ext = ["dep:proxmox-ve-config"] +config-ext = ["dep:proxmox-ve-config", "dep:proxmox-network-types"] [dependencies] log = "0.4" @@ -23,3 +23,4 @@ serde_json = "1" serde_plain = "1" proxmox-ve-config = { workspace = true, optional = true } +proxmox-network-types = { workspace = true, optional = true } diff --git a/proxmox-nftables/src/expression.rs b/proxmox-nftables/src/expression.rs index e9ef94f..bac0763 100644 --- a/proxmox-nftables/src/expression.rs +++ b/proxmox-nftables/src/expression.rs @@ -1,17 +1,16 @@ use crate::types::{ElemConfig, Verdict}; -use proxmox_ve_config::firewall::types::address::IpRange; use proxmox_ve_config::host::types::BridgeName; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::address::{Family, IpEntry, IpList}; +use proxmox_network_types::ip_address::{Cidr, Family, IpRange}; +#[cfg(feature = "config-ext")] +use proxmox_ve_config::firewall::types::address::{IpEntry, IpList}; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::port::{PortEntry, PortList}; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::rule_match::{IcmpCode, IcmpType, Icmpv6Code, Icmpv6Type}; -#[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::Cidr; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] diff --git a/proxmox-nftables/src/types.rs b/proxmox-nftables/src/types.rs index 320c757..c613e64 100644 --- a/proxmox-nftables/src/types.rs +++ b/proxmox-nftables/src/types.rs @@ -8,7 +8,7 @@ use crate::{Expression, Statement}; use serde::{Deserialize, Serialize}; #[cfg(feature = "config-ext")] -use proxmox_ve_config::firewall::types::address::Family; +use proxmox_network_types::ip_address::Family; #[cfg(feature = "config-ext")] use proxmox_ve_config::firewall::types::ipset::IpsetName; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-05-22 16:20 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2025-04-01 14:52 [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox v3 2/2] network-types: add hostname type Stefan Hanreich 2025-04-04 7:31 ` Wolfgang Bumiller 2025-04-04 7:51 ` Stefan Hanreich 2025-04-04 9:22 ` Wolfgang Bumiller 2025-04-04 11:26 ` Stefan Hanreich 2025-04-04 11:41 ` Wolfgang Bumiller 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-ve-rs v3 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich 2025-04-01 14:52 ` [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate " Stefan Hanreich 2025-04-02 9:06 ` [pve-devel] [PATCH proxmox v3 1/2] network-types: initial commit Christoph Heiss 2025-05-22 16:16 [pve-devel] [PATCH access-control/cluster/docs/gui-tests/manager/network/proxmox{, -firewall, -ve-rs, -perl-rs, -widget-toolkit} v3 00/75] Add SDN Fabrics Stefan Hanreich 2025-05-22 16:16 ` [pve-devel] [PATCH proxmox-firewall v3 1/1] firewall: nftables: migrate to proxmox-network-types Stefan Hanreich
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.