When a cidr address in a FRR access-list is not canonicalized (i.e. is
not a network address) then we get a warning in the journal and
frr-reload.py will even fail. So we need to convert the address entered
by the user into a network address. Factor out the already existing
helper and add a new method to do this. Also add some unit tests.

Signed-off-by: Gabriel Goller <g.gol...@proxmox.com>
---
 proxmox-network-types/src/ip_address.rs | 209 ++++++++++++++++++++++--
 1 file changed, 195 insertions(+), 14 deletions(-)

diff --git a/proxmox-network-types/src/ip_address.rs 
b/proxmox-network-types/src/ip_address.rs
index b91ede445070..1c72197534ce 100644
--- a/proxmox-network-types/src/ip_address.rs
+++ b/proxmox-network-types/src/ip_address.rs
@@ -322,6 +322,15 @@ impl Ipv4Cidr {
         self.mask
     }
 
+    /// Get the canonical representation of a IPv4 CIDR address.
+    ///
+    /// This normalizes the address, so we get the first address of a CIDR 
subnet (e.g.
+    /// 2.2.2.200/24 -> 2.2.2.0) we do this by using a bitwise AND operation 
over the address and
+    /// the u32::MAX (all ones) shifted by the mask.
+    fn normalize(addr: u32, mask: u8) -> u32 {
+        addr & u32::MAX.checked_shl((32 - mask).into()).unwrap_or(0)
+    }
+
     /// Checks if the two CIDRs overlap.
     ///
     /// CIDRs are always disjoint so we only need to check if one CIDR contains
@@ -329,14 +338,20 @@ impl Ipv4Cidr {
     pub fn overlaps(&self, other: &Ipv4Cidr) -> bool {
         // we normalize by the smallest mask, so the larger of the two subnets.
         let min_mask = self.mask().min(other.mask());
-        // this normalizes the address, so we get the first address of a CIDR
-        // (e.g. 2.2.2.200/24 -> 2.2.2.0) we do this by using a bitwise AND
-        // operation over the address and the u32::MAX (all ones) shifted by
-        // the mask.
-        let normalize =
-            |addr: u32| addr & u32::MAX.checked_shl((32 - 
min_mask).into()).unwrap_or(0);
         // if the prefix is the same we have an overlap
-        normalize(self.address().to_bits()) == 
normalize(other.address().to_bits())
+        Self::normalize(self.address().to_bits(), min_mask)
+            == Self::normalize(other.address().to_bits(), min_mask)
+    }
+
+    /// Get the canonical version of the CIDR.
+    ///
+    /// A canonicalized CIDR is a the normalized address, so the first address 
in the subnet
+    /// (sometimes also called "network address"). E.g. 2.2.2.5/24 -> 
2.2.2.0/24
+    pub fn canonical(&self) -> Self {
+        Self {
+            addr: Ipv4Addr::from_bits(Self::normalize(self.addr.to_bits(), 
self.mask())),
+            mask: self.mask(),
+        }
     }
 }
 
@@ -424,6 +439,15 @@ impl Ipv6Cidr {
         self.mask
     }
 
+    /// Get the canonical representation of a IPv6 CIDR address.
+    ///
+    /// This normalizes the address, so we get the first address of a CIDR 
subnet (e.g.
+    /// 2001:db8::4/64 -> 2001:db8::0/64) we do this by using a bitwise AND 
operation over the address and
+    /// the u128::MAX (all ones) shifted by the mask.
+    fn normalize(addr: u128, mask: u8) -> u128 {
+        addr & u128::MAX.checked_shl((128 - mask).into()).unwrap_or(0)
+    }
+
     /// Checks if the two CIDRs overlap.
     ///
     /// CIDRs are always disjoint so we only need to check if one CIDR contains
@@ -431,14 +455,20 @@ impl Ipv6Cidr {
     pub fn overlaps(&self, other: &Ipv6Cidr) -> bool {
         // we normalize by the smallest mask, so the larger of the two subnets.
         let min_mask = self.mask().min(other.mask());
-        // this normalizes the address, so we get the first address of a CIDR
-        // (e.g. 2001:db8::200/64 -> 2001:db8::0) we do this by using a 
bitwise AND
-        // operation over the address and the u128::MAX (all ones) shifted by
-        // the mask.
-        let normalize =
-            |addr: u128| addr & u128::MAX.checked_shl((128 - 
min_mask).into()).unwrap_or(0);
         // if the prefix is the same we have an overlap
-        normalize(self.address().to_bits()) == 
normalize(other.address().to_bits())
+        Self::normalize(self.address().to_bits(), min_mask)
+            == Self::normalize(other.address().to_bits(), min_mask)
+    }
+
+    /// Get the canonical version of the CIDR.
+    ///
+    /// A canonicalized CIDR is a the normalized address, so the first address 
in the subnet
+    /// (sometimes also called "network address"). E.g. 2001:db8::5/64 -> 
2001:db8::0/64
+    pub fn canonical(&self) -> Self {
+        Self {
+            addr: Ipv6Addr::from_bits(Self::normalize(self.addr.to_bits(), 
self.mask())),
+            mask: self.mask(),
+        }
     }
 }
 
@@ -1855,4 +1885,155 @@ mod tests {
                 )
         );
     }
+
+    #[test]
+    fn test_ipv4_canonical() {
+        let cidr = Ipv4Cidr::new("192.168.1.100".parse::<Ipv4Addr>().unwrap(), 
24).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(192, 168, 1, 0));
+        assert_eq!(canonical.mask, 24);
+
+        let cidr = Ipv4Cidr::new("10.50.75.200".parse::<Ipv4Addr>().unwrap(), 
16).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(10, 50, 0, 0));
+        assert_eq!(canonical.mask, 16);
+
+        let cidr = Ipv4Cidr::new("172.16.100.50".parse::<Ipv4Addr>().unwrap(), 
8).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(172, 0, 0, 0));
+        assert_eq!(canonical.mask, 8);
+
+        let cidr = Ipv4Cidr::new("192.168.1.1".parse::<Ipv4Addr>().unwrap(), 
32).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(192, 168, 1, 1));
+        assert_eq!(canonical.mask, 32);
+
+        let cidr = 
Ipv4Cidr::new("255.255.255.255".parse::<Ipv4Addr>().unwrap(), 0).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(0, 0, 0, 0));
+        assert_eq!(canonical.mask, 0);
+
+        let cidr = Ipv4Cidr::new("192.168.1.103".parse::<Ipv4Addr>().unwrap(), 
30).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(192, 168, 1, 100));
+        assert_eq!(canonical.mask, 30);
+
+        let cidr = Ipv4Cidr::new("10.10.15.128".parse::<Ipv4Addr>().unwrap(), 
23).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv4Addr::new(10, 10, 14, 0));
+        assert_eq!(canonical.mask, 23);
+
+        let cidr = Ipv4Cidr::new("203.0.113.99".parse::<Ipv4Addr>().unwrap(), 
25).unwrap();
+        let canonical1 = cidr.canonical();
+        let canonical2 = canonical1.canonical();
+        assert_eq!(canonical1.addr, canonical2.addr);
+        assert_eq!(canonical1.mask, canonical2.mask);
+    }
+
+    #[test]
+    fn test_ipv6_canonical() {
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:85a3::8a2e:370:7334".parse::<Ipv6Addr>().unwrap(),
+            64,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0)
+        );
+        assert_eq!(canonical.mask, 64);
+
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:1234:5678:9abc:def0:1234:5678"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            48,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0, 0, 0, 0, 0)
+        );
+        assert_eq!(canonical.mask, 48);
+
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:abcd:ef01:2345:6789:abcd:ef01"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            32,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
+        );
+        assert_eq!(canonical.mask, 32);
+
+        let cidr = Ipv6Cidr::new("2001:db8::1".parse::<Ipv6Addr>().unwrap(), 
128).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)
+        );
+        assert_eq!(canonical.mask, 128);
+
+        let cidr = Ipv6Cidr::new(
+            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            0,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
+        assert_eq!(canonical.mask, 0);
+
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:1234:5600:ffff:ffff:ffff:ffff"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            56,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0x5600, 0, 0, 0, 0)
+        );
+        assert_eq!(canonical.mask, 56);
+
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:1234:5678:9abc:def0:ffff:ffff"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            96,
+        )
+        .unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(
+            canonical.addr,
+            Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0x5678, 0x9abc, 0xdef0, 0, 0)
+        );
+        assert_eq!(canonical.mask, 96);
+
+        let cidr = Ipv6Cidr::new(
+            "2001:db8:cafe:face:dead:beef:1234:5678"
+                .parse::<Ipv6Addr>()
+                .unwrap(),
+            80,
+        )
+        .unwrap();
+        let canonical1 = cidr.canonical();
+        let canonical2 = canonical1.canonical();
+        assert_eq!(canonical1.addr, canonical2.addr);
+        assert_eq!(canonical1.mask, canonical2.mask);
+
+        let cidr = Ipv6Cidr::new("fe80::1".parse::<Ipv6Addr>().unwrap(), 
64).unwrap();
+        let canonical = cidr.canonical();
+        assert_eq!(canonical.addr, Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0));
+        assert_eq!(canonical.mask, 64);
+    }
 }
-- 
2.39.5



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

Reply via email to