public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Shannon Sterz" <s.sterz@proxmox.com>
To: "Gabriel Goller" <g.goller@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox-ve-rs v7 04/21] sdn-types: support variable-length NET identifier
Date: Thu, 26 Mar 2026 16:15:32 +0100	[thread overview]
Message-ID: <DHCT9NUK1YED.1UJG914SKJ5K6@proxmox.com> (raw)
In-Reply-To: <20260323134934.243110-5-g.goller@proxmox.com>

On Mon Mar 23, 2026 at 2:48 PM CET, Gabriel Goller wrote:
> The NET (Network Entity Title) can actually be variable-length. We only
> use the minimum length one (which corresponds to an ipv4 address) in the
> fabrics, but in the ISIS tests we also use a longer NET. Support the
> longer NET as well.
>
> Reviewed-by: Hannes Laimer <h.laimer@proxmox.com>
> Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
> ---
>  proxmox-sdn-types/src/net.rs | 136 +++++++++++++++++++++++++++++++----
>  1 file changed, 121 insertions(+), 15 deletions(-)
>
> diff --git a/proxmox-sdn-types/src/net.rs b/proxmox-sdn-types/src/net.rs
> index 3cd1e4f80ed7..f141fa5ab6b4 100644
> --- a/proxmox-sdn-types/src/net.rs
> +++ b/proxmox-sdn-types/src/net.rs
> @@ -10,7 +10,8 @@ use proxmox_schema::{api, api_string_type, const_regex, ApiStringFormat, Updater
>
>  const_regex! {
>      NET_AFI_REGEX = r"^(?:[a-fA-F0-9]{2})$";
> -    NET_AREA_REGEX = r"^(?:[a-fA-F0-9]{4})$";
> +    // Variable length area: 0 to 13 bytes (0 to 26 hex digits) according to ISO 10589
> +    NET_AREA_REGEX = r"^(?:[a-fA-F0-9]{0,26})$";

maybe this should be `^(?:[a-fA-F0-9]{2}){0,13}$`? Otherwise you could
have an uneven amount of "nibbles" and it might not be clear how to
interpret them (e.g. is ADC "AD 0C", "A0 DC", or "AD C0").

>      NET_SYSTEM_ID_REGEX = r"^(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})$";
>      NET_SELECTOR_REGEX = r"^(?:[a-fA-F0-9]{2})$";
>  }
> @@ -39,9 +40,9 @@ impl UpdaterType for NetAFI {
>  }
>
>  api_string_type! {
> -    /// Area identifier: 0001 IS-IS area number (numerical area 1)
> -    /// The second part (system) of the `net` identifier. Every node has to have a different system
> -    /// number.
> +    /// Area identifier: Variable length (0-13 bytes / 0-26 hex digits) according to ISO 10589
> +    /// IS-IS area number that identifies the routing domain. All routers in the same area must
> +    /// have the same area identifier. Can be empty or up to 26 hex digits.
>      #[api(format: &NET_AREA_FORMAT)]
>      #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
>      struct NetArea(String);
> @@ -146,6 +147,7 @@ pub struct Net {
>      selector: NetSelector,
>  }
>
> +
>  impl UpdaterType for Net {
>      type Updater = Option<Net>;
>  }
> @@ -156,27 +158,58 @@ impl std::str::FromStr for Net {
>      fn from_str(s: &str) -> Result<Self, Self::Err> {
>          let parts: Vec<&str> = s.split(".").collect();
>
> -        if parts.len() != 6 {
> -            bail!("invalid NET format: {s}")
> +        // Minimum: AFI.SystemID(3 parts).Selector = 5 parts
> +        // With area: AFI.Area.SystemID(3 parts).Selector = 6+ parts
> +        if parts.len() < 5 {
> +            bail!("invalid NET format: {s} (expected at least AFI.SystemID.Selector)")
>          }
>
> -        let system = format!("{}.{}.{}", parts[2], parts[3], parts[4],);
> +        // Last part is selector (2 hex digits)
> +        let selector_idx = parts.len() - 1;
> +        let selector = parts[selector_idx];
> +
> +        // Three parts before selector are system ID (xxxx.xxxx.xxxx)
> +        let system_id_parts = &parts[selector_idx - 3..selector_idx];
> +        let system = format!(
> +            "{}.{}.{}",
> +            system_id_parts[0], system_id_parts[1], system_id_parts[2]
> +        );
> +
> +        // First part is AFI (2 hex digits)
> +        let afi = parts[0];
> +
> +        // Everything between AFI and system ID is the area (can be empty)
> +        let area_parts = &parts[1..selector_idx - 3];
> +        let area = area_parts.join("");
>
>          Ok(Self {
> -            afi: NetAFI::from_string(parts[0].to_string())?,
> -            area: NetArea::from_string(parts[1].to_string())?,
> -            system: NetSystemId::from_string(system.to_string())?,
> -            selector: NetSelector::from_string(parts[5].to_string())?,
> +            afi: NetAFI::from_str(afi)?,
> +            area: NetArea::from_string(area)?,
> +            system: NetSystemId::from_string(system)?,
> +            selector: NetSelector::from_str(selector)?,
>          })
>      }
>  }
>
>  impl Display for Net {
>      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> +        // Format area with dots every 4 hex digits for readability
> +        let area_str = self.area.0.as_str();
> +        let area_formatted = if area_str.is_empty() {
> +            String::new()
> +        } else {
> +            let chunks: Vec<&str> = area_str
> +                .as_bytes()
> +                .chunks(4)
> +                .map(|chunk| std::str::from_utf8(chunk).unwrap())
> +                .collect();
> +            format!(".{}", chunks.join("."))
> +        };
> +
>          write!(
>              f,
> -            "{}.{}.{}.{}",
> -            self.afi, self.area, self.system, self.selector
> +            "{}{}.{}.{}",
> +            self.afi, area_formatted, self.system, self.selector
>          )
>      }
>  }
> @@ -258,8 +291,16 @@ mod tests {
>          let input = "409.0001.1921.6800.1002.00";
>          input.parse::<Net>().expect_err("invalid AFI");
>
> -        let input = "49.00001.1921.6800.1002.00";
> -        input.parse::<Net>().expect_err("invalid area");
> +        // Area can now be variable length (0-26 hex digits), so 5 digits is valid
> +        // but 27 digits would be invalid
> +        let input = "49.0123.4567.8901.2345.6789.0123.4569.1921.6800.1002.00";
> +        input
> +            .parse::<Net>()
> +            .expect_err("area too long (>26 hex digits)");
> +
> +        // Too few parts
> +        let input = "49.1921.6800.00";
> +        input.parse::<Net>().expect_err("not enough parts");
>      }
>
>      #[test]
> @@ -320,4 +361,69 @@ mod tests {
>          let net4: Net = ip4.into();
>          assert_eq!(format!("{net4}"), "49.0001.0000.0000.0000.00");
>      }
> +
> +    #[test]
> +    fn test_net_variable_length_area() {
> +        // Test with no area (just AFI)
> +        let input = "49.1921.6800.1002.00";
> +        let net = input.parse::<Net>().expect("should parse NET with no area");
> +        assert_eq!(net.afi, NetAFI("49".to_owned()));
> +        assert_eq!(net.area, NetArea("".to_owned()));
> +        assert_eq!(net.system, NetSystemId("1921.6800.1002".to_owned()));
> +        assert_eq!(net.selector, NetSelector("00".to_owned()));
> +        assert_eq!(format!("{net}"), "49.1921.6800.1002.00");
> +
> +        // Test with 2 hex digit area
> +        let input = "49.01.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 2-digit area");
> +        assert_eq!(net.area, NetArea("01".to_owned()));
> +        assert_eq!(format!("{net}"), "49.01.1921.6800.1002.00");
> +
> +        // Test with 4 hex digit area (standard)
> +        let input = "49.0001.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 4-digit area");
> +        assert_eq!(net.area, NetArea("0001".to_owned()));
> +        assert_eq!(format!("{net}"), "49.0001.1921.6800.1002.00");
> +
> +        // Test with 8 hex digit area (formatted with dots every 4 digits)
> +        let input = "49.0001.0002.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 8-digit area");
> +        assert_eq!(net.area, NetArea("00010002".to_owned()));
> +        // Should be formatted with dots every 4 hex digits
> +        assert_eq!(format!("{net}"), "49.0001.0002.1921.6800.1002.00");
> +
> +        // Test with 12 hex digit area
> +        let input = "49.0001.0002.0003.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 12-digit area");
> +        assert_eq!(net.area, NetArea("000100020003".to_owned()));
> +        assert_eq!(format!("{net}"), "49.0001.0002.0003.1921.6800.1002.00");
> +
> +        // Test with odd-length area (5 hex digits)
> +        let input = "49.12345.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 5-digit area");
> +        assert_eq!(net.area, NetArea("12345".to_owned()));
> +        // Should be formatted with dots every 4 digits, last chunk has 1 digit
> +        assert_eq!(format!("{net}"), "49.1234.5.1921.6800.1002.00");
> +
> +        // Test with maximum length area (26 hex digits = 13 bytes)
> +        let input = "49.0123.4567.89ab.cdef.0123.4567.1921.6800.1002.00";
> +        let net = input
> +            .parse::<Net>()
> +            .expect("should parse NET with 26-digit area");
> +        assert_eq!(net.area, NetArea("0123456789abcdef01234567".to_owned()));
> +        assert_eq!(
> +            format!("{net}"),
> +            "49.0123.4567.89ab.cdef.0123.4567.1921.6800.1002.00"
> +        );
> +    }
>  }





  reply	other threads:[~2026-03-26 15:15 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-23 13:48 [PATCH manager/network/proxmox{-ve-rs,-perl-rs} v7 00/21] Generate frr config using jinja templates and rust types Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 01/21] ve-config: firewall: cargo fmt Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 02/21] frr: add proxmox-frr-templates package that contains templates Gabriel Goller
2026-03-26 15:15   ` Shannon Sterz
2026-03-27  9:07     ` Gabriel Goller
2026-03-27  9:12       ` Gabriel Goller
2026-03-27 10:10         ` Thomas Lamprecht
2026-03-27 10:17           ` Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 03/21] ve-config: remove FrrConfigBuilder struct Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 04/21] sdn-types: support variable-length NET identifier Gabriel Goller
2026-03-26 15:15   ` Shannon Sterz [this message]
2026-03-27  9:25     ` Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 05/21] frr: add template serializer and serialize fabrics using templates Gabriel Goller
2026-03-27  0:37   ` Thomas Lamprecht
2026-03-27  9:45     ` Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 06/21] frr: add isis configuration and templates Gabriel Goller
2026-03-27  0:41   ` Thomas Lamprecht
2026-03-27  9:53     ` Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 07/21] frr: support custom frr configuration lines Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 08/21] frr: add bgp support with templates and serialization Gabriel Goller
2026-03-27  0:50   ` Thomas Lamprecht
2026-03-27 10:05     ` Gabriel Goller
2026-03-23 13:48 ` [PATCH proxmox-ve-rs v7 09/21] frr: enable minijinja strict undefined behavior mode Gabriel Goller
2026-03-23 13:49 ` [PATCH proxmox-ve-rs v7 10/21] frr: add vtysh integration tests for proxmox-frr Gabriel Goller
2026-03-24  9:03   ` Gabriel Goller
2026-03-23 13:49 ` [PATCH proxmox-perl-rs v7 11/21] sdn: add function to generate the frr config for all daemons Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 12/21] tests: use Test::Differences to make test assertions Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 13/21] test: add tests for frr.conf.local merging Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 14/21] test: bgp: add some various integration tests Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 15/21] sdn: write structured frr config that can be rendered using templates Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 16/21] sdn: remove duplicate comment line '!' in frr config Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 17/21] tests: rearrange some statements in the " Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 18/21] sdn: adjust frr.conf.local merging to rust template types Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 19/21] test: adjust frr_local_merge test for new template generation Gabriel Goller
2026-03-23 13:49 ` [PATCH pve-network v7 20/21] api: add dry-run endpoint for sdn apply to preview changes Gabriel Goller
2026-03-27  1:07   ` Thomas Lamprecht
2026-03-23 13:49 ` [PATCH pve-manager v7 21/21] sdn: add dry-run diff view for sdn apply Gabriel Goller
2026-03-26 13:40 ` [PATCH manager/network/proxmox{-ve-rs,-perl-rs} v7 00/21] Generate frr config using jinja templates and rust types Wolfgang Bumiller
2026-03-27  1:11 ` Thomas Lamprecht
2026-03-27 10:06   ` Gabriel Goller

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=DHCT9NUK1YED.1UJG914SKJ5K6@proxmox.com \
    --to=s.sterz@proxmox.com \
    --cc=g.goller@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