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"
> + );
> + }
> }
next prev parent 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