public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit
@ 2025-04-01 13:36 Stefan Hanreich
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox v2 2/2] network-types: add hostname type Stefan Hanreich
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Stefan Hanreich @ 2025-04-01 13:36 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] 6+ messages in thread

* [pve-devel] [PATCH proxmox v2 2/2] network-types: add hostname type
  2025-04-01 13:36 [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
@ 2025-04-01 13:36 ` Stefan Hanreich
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Stefan Hanreich @ 2025-04-01 13:36 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 v1:
    * added unit tests

 proxmox-network-types/src/hostname.rs | 94 +++++++++++++++++++++++++++
 proxmox-network-types/src/lib.rs      |  1 +
 2 files changed, 95 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..ebe79986
--- /dev/null
+++ b/proxmox-network-types/src/hostname.rs
@@ -0,0 +1,94 @@
+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 contains invalid symbols")]
+    InvalidSymbols,
+}
+
+/// Hostname of a Linux machine
+///
+/// A hostname is at most 63 characters long and must only contain lowercase alphanumeric
+/// characters as well as hyphens. It must not start or end with a hyphen.
+#[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: impl AsRef<str>) -> Result<Self, HostnameError> {
+        let name_ref = name.as_ref();
+
+        if !(1..63).contains(&name_ref.len()) {
+            return Err(HostnameError::InvalidLength);
+        }
+
+        let host_name = name_ref.to_lowercase();
+
+        let mut characters = host_name.chars();
+
+        // first character must not be a hyphen
+        // SAFETY: ok because of length check
+        if !characters.next().unwrap().is_alphanumeric() {
+            return Err(HostnameError::InvalidSymbols);
+        }
+
+        if !characters.all(|c| c.is_alphanumeric() || c == '-') {
+            return Err(HostnameError::InvalidSymbols);
+        }
+
+        // last character must not be a hyphen
+        // SAFETY: ok because of length check
+        if !host_name.chars().next_back().unwrap().is_alphanumeric() {
+            return Err(HostnameError::InvalidSymbols);
+        }
+
+        Ok(Self(host_name))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_hostname() {
+        for valid_hostname in ["debian", "0host", "some-host-123", "123"] {
+            Hostname::new(valid_hostname).expect("valid hostname");
+        }
+
+        for invalid_hostname in ["-debian", "0host-", "some/host", "", "thisisawaywaywaytoolonghostnameheremustnotbemorethan63characters"] {
+            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] 6+ messages in thread

* [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types
  2025-04-01 13:36 [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox v2 2/2] network-types: add hostname type Stefan Hanreich
@ 2025-04-01 13:36 ` Stefan Hanreich
  2025-04-02  8:39   ` Christoph Heiss
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-firewall v2 1/1] firewall: nftables: migrate " Stefan Hanreich
  2025-04-01 14:53 ` [pve-devel] superseded: [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
  3 siblings, 1 reply; 6+ messages in thread
From: Stefan Hanreich @ 2025-04-01 13:36 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] 6+ messages in thread

* [pve-devel] [PATCH proxmox-firewall v2 1/1] firewall: nftables: migrate to proxmox-network-types
  2025-04-01 13:36 [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox v2 2/2] network-types: add hostname type Stefan Hanreich
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich
@ 2025-04-01 13:36 ` Stefan Hanreich
  2025-04-01 14:53 ` [pve-devel] superseded: [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
  3 siblings, 0 replies; 6+ messages in thread
From: Stefan Hanreich @ 2025-04-01 13:36 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] 6+ messages in thread

* [pve-devel] superseded: [PATCH proxmox v2 1/2] network-types: initial commit
  2025-04-01 13:36 [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
                   ` (2 preceding siblings ...)
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-firewall v2 1/1] firewall: nftables: migrate " Stefan Hanreich
@ 2025-04-01 14:53 ` Stefan Hanreich
  3 siblings, 0 replies; 6+ messages in thread
From: Stefan Hanreich @ 2025-04-01 14:53 UTC (permalink / raw)
  To: pve-devel

https://lore.proxmox.com/pve-devel/20250401133616.331046-1-s.hanreich@proxmox.com/T/#t

On 4/1/25 15:36, 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] 6+ messages in thread

* Re: [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types
  2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich
@ 2025-04-02  8:39   ` Christoph Heiss
  0 siblings, 0 replies; 6+ messages in thread
From: Christoph Heiss @ 2025-04-02  8:39 UTC (permalink / raw)
  To: Stefan Hanreich; +Cc: Proxmox VE development discussion

Just a quick note:

On Tue Apr 1, 2025 at 3:36 PM CEST, Stefan Hanreich wrote:
[..]
> 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
[..]
> -        assert_eq!(
> -            [Ipv6Cidr::new(
> -                [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF],
> -                128
> -            )
> -            .unwrap(),],
> -            range.to_cidrs().as_slice()
> -        );
> -    }
>  }
> +

nit: new blank line at eof, guess just an accidental typo? :^)

Came across it while applying the patch for a quick compile-test.

  Applying: ve-config: move types to proxmox-network-types
  .git/rebase-apply/patch:1548: new blank line at EOF.
  +
  warning: 1 line adds whitespace errors.

Just wanted to mention, can easily be fixed up during applying as well.
Definitely not worth a resend in any case, IMO.



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-04-02  8:39 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-01 13:36 [pve-devel] [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich
2025-04-01 13:36 ` [pve-devel] [PATCH proxmox v2 2/2] network-types: add hostname type Stefan Hanreich
2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-ve-rs v2 1/1] ve-config: move types to proxmox-network-types Stefan Hanreich
2025-04-02  8:39   ` Christoph Heiss
2025-04-01 13:36 ` [pve-devel] [PATCH proxmox-firewall v2 1/1] firewall: nftables: migrate " Stefan Hanreich
2025-04-01 14:53 ` [pve-devel] superseded: [PATCH proxmox v2 1/2] network-types: initial commit Stefan Hanreich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal