Implement check if the address entered by the user is valid within the
given subnet, i.e. not a network address or broadcast address.

Partially closes [0].

[0] https://bugzilla.proxmox.com/show_bug.cgi?id=5757

Signed-off-by: Michael Köppl <m.koe...@proxmox.com>
---
Some input / discussion would be much appreciated here, since this might
again be considered too restrictive. Multiple questions came up during
in-person discussion:
* Is check for broadcast address desired or is it considered a valid
configuration for PVE?
* Is IPv6 check necessary and if so, is allowing to set the address to a
broadcast address a valid setting for IPv6?

 proxmox-installer-common/src/utils.rs | 47 +++++++++++++++++++++++----
 1 file changed, 41 insertions(+), 6 deletions(-)

diff --git a/proxmox-installer-common/src/utils.rs 
b/proxmox-installer-common/src/utils.rs
index 49f1c9f..3d79749 100644
--- a/proxmox-installer-common/src/utils.rs
+++ b/proxmox-installer-common/src/utils.rs
@@ -16,6 +16,10 @@ pub enum CidrAddressParseError {
     InvalidAddr(AddrParseError),
     /// The mask could not be parsed.
     InvalidMask(Option<ParseIntError>),
+    /// The IP address is a network address.
+    IsNetworkAddr,
+    /// The IP address is a broadcast address.
+    IsBroadcastAddr,
 }
 
 impl fmt::Display for CidrAddressParseError {
@@ -26,6 +30,10 @@ impl fmt::Display for CidrAddressParseError {
             }
             CidrAddressParseError::InvalidAddr(addr_parse_error) => 
format!("{addr_parse_error}"),
             CidrAddressParseError::InvalidMask(parse_int_error) => 
format!("{:?}", parse_int_error),
+            CidrAddressParseError::IsNetworkAddr => String::from("Address is a 
network address"),
+            CidrAddressParseError::IsBroadcastAddr => {
+                String::from("Address is a broadcast address")
+            }
         };
 
         write!(f, "Invalid CIDR: {msg}")
@@ -62,10 +70,12 @@ impl CidrAddress {
         let addr = addr.into();
 
         if mask > mask_limit(&addr) {
-            Err(CidrAddressParseError::InvalidMask(None))
-        } else {
-            Ok(Self { addr, mask })
+            return Err(CidrAddressParseError::InvalidMask(None));
         }
+
+        check_addr_valid_in_subnet(&addr, mask)?;
+
+        Ok(Self { addr, mask })
     }
 
     /// Returns only the IP address part of the address.
@@ -104,10 +114,12 @@ impl FromStr for CidrAddress {
             .map_err(|err| CidrAddressParseError::InvalidMask(Some(err)))?;
 
         if mask > mask_limit(&addr) {
-            Err(CidrAddressParseError::InvalidMask(None))
-        } else {
-            Ok(Self { addr, mask })
+            return Err(CidrAddressParseError::InvalidMask(None));
         }
+
+        check_addr_valid_in_subnet(&addr, mask)?;
+
+        Ok(Self { addr, mask })
     }
 }
 
@@ -134,6 +146,29 @@ fn mask_limit(addr: &IpAddr) -> usize {
     if addr.is_ipv4() { 32 } else { 128 }
 }
 
+fn check_addr_valid_in_subnet(addr: &IpAddr, mask: usize) -> Result<(), 
CidrAddressParseError> {
+    match &addr {
+        IpAddr::V4(addr_v4) => {
+            let num_host_bits = 32 - mask;
+            let host_part_mask = (1u32 << num_host_bits) - 1;
+
+            let ip_bits = u32::from_be_bytes(addr_v4.octets());
+
+            let network_addr = ip_bits & !host_part_mask;
+            let broadcast_addr = network_addr | host_part_mask;
+
+            if ip_bits == network_addr {
+                Err(CidrAddressParseError::IsNetworkAddr)
+            } else if ip_bits == broadcast_addr {
+                Err(CidrAddressParseError::IsBroadcastAddr)
+            } else {
+                Ok(())
+            }
+        }
+        IpAddr::V6(_) => Ok(()),
+    }
+}
+
 /// Possible errors that might occur when parsing FQDNs.
 #[derive(Debug, Eq, PartialEq)]
 pub enum FqdnParseError {
-- 
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