public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Michael Köppl" <m.koeppl@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [RFC PATCH pve-installer 6/6] closes #5757: common: add checks for valid IPv4 address within subnet
Date: Tue, 22 Apr 2025 18:27:39 +0200	[thread overview]
Message-ID: <20250422162739.255641-7-m.koeppl@proxmox.com> (raw)
In-Reply-To: <20250422162739.255641-1-m.koeppl@proxmox.com>

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.koeppl@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

  parent reply	other threads:[~2025-04-22 16:28 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-22 16:27 [pve-devel] [PATCH installer 0/6] add early disk and network sanity checks Michael Köppl
2025-04-22 16:27 ` [pve-devel] [PATCH pve-installer 1/6] auto: add early answer file sanity check for RAID configurations Michael Köppl
2025-04-28 11:25   ` Christoph Heiss
2025-04-28 14:31     ` Michael Köppl
2025-04-29  8:26       ` Christoph Heiss
2025-04-29  9:32         ` Michael Köppl
2025-04-29  9:40           ` Christoph Heiss
2025-04-22 16:27 ` [pve-devel] [PATCH pve-installer 2/6] common: use get_min_disks as single source of truth for RAID config checks Michael Köppl
2025-04-28 11:48   ` Christoph Heiss
2025-04-28 15:36     ` Michael Köppl
2025-04-22 16:27 ` [pve-devel] [RFC PATCH pve-installer 3/6] close #5887: add sanity check for LVM swapsize and maxroot Michael Köppl
2025-04-28 12:00   ` Christoph Heiss
2025-04-29 11:30     ` Michael Köppl
2025-04-22 16:27 ` [pve-devel] [PATCH pve-installer 4/6] run rustfmt Michael Köppl
2025-04-23 11:56   ` Christoph Heiss
2025-04-25 12:22     ` Michael Köppl
2025-04-22 16:27 ` [pve-devel] [PATCH pve-installer 5/6] common: add more descriptive errors for invalid network configs Michael Köppl
2025-04-28 12:20   ` Christoph Heiss
2025-04-22 16:27 ` Michael Köppl [this message]
2025-04-28 10:22   ` [pve-devel] [RFC PATCH pve-installer 6/6] closes #5757: common: add checks for valid IPv4 address within subnet Christoph Heiss
2025-04-28 14:20     ` Michael Köppl
2025-04-28 12:25 ` [pve-devel] [PATCH installer 0/6] add early disk and network sanity checks Christoph Heiss
2025-04-29 14:14   ` Michael Köppl

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250422162739.255641-7-m.koeppl@proxmox.com \
    --to=m.koeppl@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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