From 4877ee09519b312d32375adc5884ae27bd9dca46 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 15 Dec 2025 17:41:47 +0000 Subject: [PATCH 01/10] WIP --- wicket-common/src/example.rs | 1 + wicket-common/src/rack_setup.rs | 9 +++++++++ wicketd/src/rss_config.rs | 7 +++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/wicket-common/src/example.rs b/wicket-common/src/example.rs index b6bec37e8e3..60f5e30f030 100644 --- a/wicket-common/src/example.rs +++ b/wicket-common/src/example.rs @@ -197,6 +197,7 @@ impl ExampleRackSetupData { }); let rack_network_config = UserSpecifiedRackNetworkConfig { + rack_subnet_address: Ipv6Addr::new(0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0), infra_ip_first: "172.30.0.1".parse().unwrap(), infra_ip_last: "172.30.0.10".parse().unwrap(), #[rustfmt::skip] diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index b4da40cd9b2..aebe0a9055b 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -20,6 +20,8 @@ use omicron_common::api::internal::shared::UplinkAddressConfig; use owo_colors::OwoColorize; use owo_colors::Style; use oxnet::IpNet; +use oxnet::IpNetPrefixError; +use oxnet::Ipv6Net; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -99,6 +101,7 @@ pub struct BootstrapSledDescription { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct UserSpecifiedRackNetworkConfig { + pub rack_subnet_address: Ipv6Addr, pub infra_ip_first: Ipv4Addr, pub infra_ip_last: Ipv4Addr, // Map of switch -> port -> configuration, under the assumption that @@ -169,6 +172,12 @@ impl UserSpecifiedRackNetworkConfig { iter0.chain(iter1) } + + pub fn rack_subnet(&self) -> Result { + // Do not allow rack0 + // must be fd + Ipv6Net::new(self.rack_subnet_address, 56) + } } /// User-specified version of [`PortConfigV2`]. diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 0223a565dfa..51d83fc574d 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -55,7 +55,7 @@ use wicketd_api::SetBgpAuthKeyStatus; // TODO-correctness For now, we always use the same rack subnet when running // RSS. When we get to multirack, this will be wrong, but there are many other // RSS-related things that need to change then too. -static RACK_SUBNET: LazyLock> = LazyLock::new(|| { +static DEFAULT_RACK_SUBNET: LazyLock> = LazyLock::new(|| { let ip = Ipv6Addr::new(0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0); Ipv6Subnet::new(ip) }); @@ -659,10 +659,13 @@ fn validate_rack_network_config( } } + // TODO more validation? + let rack_subnet = config.rack_subnet()?; + // TODO Add more client side checks on `rack_network_config` contents? Ok(bootstrap_agent_client::types::RackNetworkConfigV2 { - rack_subnet: RACK_SUBNET.net(), + rack_subnet, infra_ip_first: config.infra_ip_first, infra_ip_last: config.infra_ip_last, ports: config From 98b4e62c0c3950b0f09ca6b0143e7f517252d746 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 14 Jan 2026 18:24:13 +0000 Subject: [PATCH 02/10] validation and testing --- openapi/wicketd.json | 5 +++++ wicket-common/src/example.rs | 20 ++++++++++--------- wicket-common/src/rack_setup.rs | 15 ++++++++++---- .../src/cli/rack_setup/config_template.toml | 1 + wicket/src/cli/rack_setup/config_toml.rs | 2 ++ wicket/src/ui/panes/rack_setup.rs | 10 +++++++++- wicket/tests/output/example_non_empty.toml | 1 + wicketd/src/rss_config.rs | 18 ++++------------- 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/openapi/wicketd.json b/openapi/wicketd.json index c0b41c90d8d..5993341c610 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -7784,6 +7784,10 @@ "type": "string", "format": "ipv4" }, + "rack_subnet_address": { + "type": "string", + "format": "ipv6" + }, "switch0": { "type": "object", "additionalProperties": { @@ -7801,6 +7805,7 @@ "bgp", "infra_ip_first", "infra_ip_last", + "rack_subnet_address", "switch0", "switch1" ], diff --git a/wicket-common/src/example.rs b/wicket-common/src/example.rs index 60f5e30f030..670486af50b 100644 --- a/wicket-common/src/example.rs +++ b/wicket-common/src/example.rs @@ -197,13 +197,15 @@ impl ExampleRackSetupData { }); let rack_network_config = UserSpecifiedRackNetworkConfig { - rack_subnet_address: Ipv6Addr::new(0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0), + rack_subnet_address: Ipv6Addr::new( + 0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0, + ), infra_ip_first: "172.30.0.1".parse().unwrap(), infra_ip_last: "172.30.0.10".parse().unwrap(), #[rustfmt::skip] switch0: btreemap! { - "port0".to_owned() => UserSpecifiedPortConfig { - addresses: vec!["172.30.0.1/24".parse().unwrap()], + "port0".to_owned() => UserSpecifiedPortConfig { + addresses: vec!["172.30.0.1/24".parse().unwrap()], routes: vec![RouteConfig { destination: "0.0.0.0/0".parse().unwrap(), nexthop: "172.30.0.10".parse().unwrap(), @@ -213,11 +215,11 @@ impl ExampleRackSetupData { bgp_peers: switch0_port0_bgp_peers, uplink_port_speed: PortSpeed::Speed400G, uplink_port_fec: Some(PortFec::Firecode), - lldp: switch0_port0_lldp, - tx_eq, - autoneg: true, - }, - }, + lldp: switch0_port0_lldp, + tx_eq, + autoneg: true, + }, + }, #[rustfmt::skip] switch1: btreemap! { // Use the same port name as in switch0 to test that it doesn't @@ -234,7 +236,7 @@ impl ExampleRackSetupData { uplink_port_speed: PortSpeed::Speed400G, uplink_port_fec: None, lldp: switch1_port0_lldp, - tx_eq, + tx_eq, autoneg: true, }, }, diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index aebe0a9055b..a31a706aed1 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -20,7 +20,6 @@ use omicron_common::api::internal::shared::UplinkAddressConfig; use owo_colors::OwoColorize; use owo_colors::Style; use oxnet::IpNet; -use oxnet::IpNetPrefixError; use oxnet::Ipv6Net; use schemars::JsonSchema; use serde::Deserialize; @@ -173,10 +172,18 @@ impl UserSpecifiedRackNetworkConfig { iter0.chain(iter1) } - pub fn rack_subnet(&self) -> Result { + pub fn rack_subnet(&self) -> Result { + // first octet must be fd + if self.rack_subnet_address.octets()[0] != 0xfd { + return Err("rack subnet address must begin with 0xfd".into()); + }; + // Do not allow rack0 - // must be fd - Ipv6Net::new(self.rack_subnet_address, 56) + if self.rack_subnet_address.octets()[6] == 0x00 { + return Err("rack number (seventh octet) cannot be 0".into()); + }; + + Ipv6Net::new(self.rack_subnet_address, 56).map_err(|e| e.to_string()) } } diff --git a/wicket/src/cli/rack_setup/config_template.toml b/wicket/src/cli/rack_setup/config_template.toml index b5bcaa202c8..c0363c43e62 100644 --- a/wicket/src/cli/rack_setup/config_template.toml +++ b/wicket/src/cli/rack_setup/config_template.toml @@ -57,6 +57,7 @@ allow = "any" # TODO: docs on network config [rack_network_config] +rack_subnet_address = "" infra_ip_first = "" infra_ip_last = "" diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 7ff481b46b9..9fa7afcb515 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -245,9 +245,11 @@ fn populate_network_table( }; for (property, value) in [ + ("rack_subnet_address", config.rack_subnet_address.to_string()), ("infra_ip_first", config.infra_ip_first.to_string()), ("infra_ip_last", config.infra_ip_last.to_string()), ] { + println!("property: {property}, value: {value}"); *table.get_mut(property).unwrap().as_value_mut().unwrap() = Value::String(Formatted::new(value)); } diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index 6dfeaf86f6a..d0134a6c77f 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -710,6 +710,12 @@ fn rss_config_text<'a>( "External DNS zone name: ", Cow::from(external_dns_zone_name.as_str()), ), + ( + "Rack subnet address (IPv6 /56): ", + rack_network_config.as_ref().map_or("".into(), |c| { + c.rack_subnet_address.to_string().into() + }), + ), ( "Infrastructure first IP: ", rack_network_config @@ -755,7 +761,9 @@ fn rss_config_text<'a>( // This style ensures that if a new field is added to the struct, it // fails to compile. let UserSpecifiedRackNetworkConfig { - // infra_ip_first and infra_ip_last have already been handled above. + // rack_subnet_address, infra_ip_first, and infra_ip_last + // have already been handled above. + rack_subnet_address: _, infra_ip_first: _, infra_ip_last: _, // switch0 and switch1 re handled via the iter_uplinks iterator. diff --git a/wicket/tests/output/example_non_empty.toml b/wicket/tests/output/example_non_empty.toml index 1cd4582a438..5d808365704 100644 --- a/wicket/tests/output/example_non_empty.toml +++ b/wicket/tests/output/example_non_empty.toml @@ -69,6 +69,7 @@ allow = "any" # TODO: docs on network config [rack_network_config] +rack_subnet_address = "fd00:1122:3344:100::" infra_ip_first = "172.30.0.1" infra_ip_last = "172.30.0.10" diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 51d83fc574d..4e98cb7d906 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -20,8 +20,6 @@ use display_error_chain::DisplayErrorChain; use omicron_certificates::CertificateError; use omicron_common::address; use omicron_common::address::Ipv4Range; -use omicron_common::address::Ipv6Subnet; -use omicron_common::address::RACK_PREFIX; use omicron_common::api::external::AllowedSourceIps; use omicron_common::api::external::SwitchLocation; use sled_hardware_types::Baseboard; @@ -32,8 +30,6 @@ use std::collections::BTreeSet; use std::collections::btree_map; use std::mem; use std::net::IpAddr; -use std::net::Ipv6Addr; -use std::sync::LazyLock; use thiserror::Error; use wicket_common::inventory::MgsV1Inventory; use wicket_common::inventory::SpType; @@ -52,14 +48,6 @@ use wicketd_api::CurrentRssUserConfig; use wicketd_api::CurrentRssUserConfigSensitive; use wicketd_api::SetBgpAuthKeyStatus; -// TODO-correctness For now, we always use the same rack subnet when running -// RSS. When we get to multirack, this will be wrong, but there are many other -// RSS-related things that need to change then too. -static DEFAULT_RACK_SUBNET: LazyLock> = LazyLock::new(|| { - let ip = Ipv6Addr::new(0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0); - Ipv6Subnet::new(ip) -}); - const RECOVERY_SILO_NAME: &str = "recovery"; const RECOVERY_SILO_USERNAME: &str = "recovery"; @@ -659,8 +647,10 @@ fn validate_rack_network_config( } } - // TODO more validation? - let rack_subnet = config.rack_subnet()?; + let rack_subnet = match config.rack_subnet() { + Ok(v) => v, + Err(e) => bail!(e), + }; // TODO Add more client side checks on `rack_network_config` contents? From 731b5668761365ce27bc0298be2a2be0e1adeaae Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Wed, 14 Jan 2026 23:46:41 +0000 Subject: [PATCH 03/10] allow rack subnet address to be optional --- openapi/wicketd.json | 2 +- wicket-common/src/example.rs | 7 +++-- wicket-common/src/rack_setup.rs | 17 +--------- wicket/src/cli/rack_setup/config_toml.rs | 11 +++++-- wicket/src/ui/panes/rack_setup.rs | 6 +++- wicketd/Cargo.toml | 2 +- wicketd/src/rss_config.rs | 40 +++++++++++++++++++++++- 7 files changed, 60 insertions(+), 25 deletions(-) diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 5993341c610..50851f0cf82 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -7785,6 +7785,7 @@ "format": "ipv4" }, "rack_subnet_address": { + "nullable": true, "type": "string", "format": "ipv6" }, @@ -7805,7 +7806,6 @@ "bgp", "infra_ip_first", "infra_ip_last", - "rack_subnet_address", "switch0", "switch1" ], diff --git a/wicket-common/src/example.rs b/wicket-common/src/example.rs index 670486af50b..c5e21cbdfa4 100644 --- a/wicket-common/src/example.rs +++ b/wicket-common/src/example.rs @@ -196,10 +196,11 @@ impl ExampleRackSetupData { management_addrs: Some(vec!["172.32.0.4".parse().unwrap()]), }); + let rack_subnet_address = + Some(Ipv6Addr::new(0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0)); + let rack_network_config = UserSpecifiedRackNetworkConfig { - rack_subnet_address: Ipv6Addr::new( - 0xfd00, 0x1122, 0x3344, 0x0100, 0, 0, 0, 0, - ), + rack_subnet_address, infra_ip_first: "172.30.0.1".parse().unwrap(), infra_ip_last: "172.30.0.10".parse().unwrap(), #[rustfmt::skip] diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index a31a706aed1..9cbb99d2411 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -20,7 +20,6 @@ use omicron_common::api::internal::shared::UplinkAddressConfig; use owo_colors::OwoColorize; use owo_colors::Style; use oxnet::IpNet; -use oxnet::Ipv6Net; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -100,7 +99,7 @@ pub struct BootstrapSledDescription { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct UserSpecifiedRackNetworkConfig { - pub rack_subnet_address: Ipv6Addr, + pub rack_subnet_address: Option, pub infra_ip_first: Ipv4Addr, pub infra_ip_last: Ipv4Addr, // Map of switch -> port -> configuration, under the assumption that @@ -171,20 +170,6 @@ impl UserSpecifiedRackNetworkConfig { iter0.chain(iter1) } - - pub fn rack_subnet(&self) -> Result { - // first octet must be fd - if self.rack_subnet_address.octets()[0] != 0xfd { - return Err("rack subnet address must begin with 0xfd".into()); - }; - - // Do not allow rack0 - if self.rack_subnet_address.octets()[6] == 0x00 { - return Err("rack number (seventh octet) cannot be 0".into()); - }; - - Ipv6Net::new(self.rack_subnet_address, 56).map_err(|e| e.to_string()) - } } /// User-specified version of [`PortConfigV2`]. diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 9fa7afcb515..3339023e71c 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -244,12 +244,19 @@ fn populate_network_table( return; }; + if let Some(rack_subnet_address) = config.rack_subnet_address { + *table + .get_mut("rack_subnet_address") + .unwrap() + .as_value_mut() + .unwrap() = + Value::String(Formatted::new(rack_subnet_address.to_string())); + } + for (property, value) in [ - ("rack_subnet_address", config.rack_subnet_address.to_string()), ("infra_ip_first", config.infra_ip_first.to_string()), ("infra_ip_last", config.infra_ip_last.to_string()), ] { - println!("property: {property}, value: {value}"); *table.get_mut(property).unwrap().as_value_mut().unwrap() = Value::String(Formatted::new(value)); } diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index d0134a6c77f..59c98676827 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -713,7 +713,11 @@ fn rss_config_text<'a>( ( "Rack subnet address (IPv6 /56): ", rack_network_config.as_ref().map_or("".into(), |c| { - c.rack_subnet_address.to_string().into() + match c.rack_subnet_address { + Some(v) => v.to_string(), + None => "".to_string(), + } + .into() }), ), ( diff --git a/wicketd/Cargo.toml b/wicketd/Cargo.toml index 9f091568fa9..2fb2a4187b1 100644 --- a/wicketd/Cargo.toml +++ b/wicketd/Cargo.toml @@ -43,6 +43,7 @@ serde_json.workspace = true sha2.workspace = true slog-dtrace.workspace = true slog.workspace = true +rand.workspace = true thiserror.workspace = true tufaceous-artifact.workspace = true tufaceous-lib.workspace = true @@ -88,7 +89,6 @@ maplit.workspace = true omicron-test-utils.workspace = true openapi-lint.workspace = true openapiv3.workspace = true -rand.workspace = true serde_json.workspace = true sled-agent-config-reconciler = { workspace = true, features = ["testing"] } sled-agent-types.workspace = true diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 4e98cb7d906..477e309c684 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -22,6 +22,7 @@ use omicron_common::address; use omicron_common::address::Ipv4Range; use omicron_common::api::external::AllowedSourceIps; use omicron_common::api::external::SwitchLocation; +use oxnet::Ipv6Net; use sled_hardware_types::Baseboard; use slog::debug; use slog::warn; @@ -30,6 +31,7 @@ use std::collections::BTreeSet; use std::collections::btree_map; use std::mem; use std::net::IpAddr; +use std::net::Ipv6Addr; use thiserror::Error; use wicket_common::inventory::MgsV1Inventory; use wicket_common::inventory::SpType; @@ -647,7 +649,7 @@ fn validate_rack_network_config( } } - let rack_subnet = match config.rack_subnet() { + let rack_subnet = match validate_rack_subnet(config.rack_subnet_address) { Ok(v) => v, Err(e) => bail!(e), }; @@ -679,6 +681,42 @@ fn validate_rack_network_config( }) } +pub fn validate_rack_subnet( + subnet_address: Option, +) -> Result { + use rand::prelude::*; + + let rack_subnet_address = match subnet_address { + Some(addr) => addr, + None => { + let mut rng = rand::rng(); + let a: u16 = 0xfd00 + Into::::into(rng.random::()); + Ipv6Addr::new( + a, + rng.random::(), + rng.random::(), + 0x0100, + 0, + 0, + 0, + 0, + ) + } + }; + + // first octet must be fd + if rack_subnet_address.octets()[0] != 0xfd { + return Err("rack subnet address must begin with 0xfd".into()); + }; + + // Do not allow rack0 + if rack_subnet_address.octets()[6] == 0x00 { + return Err("rack number (seventh octet) cannot be 0".into()); + }; + + Ipv6Net::new(rack_subnet_address, 56).map_err(|e| e.to_string()) +} + /// Builds a `BaPortConfigV2` from a `UserSpecifiedPortConfig`. /// /// Assumes that all auth keys are present in `bgp_auth_keys`. From 0af724c1588f9000c0b924262e4abbaaa173487d Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 15 Jan 2026 00:22:22 +0000 Subject: [PATCH 04/10] do not require empty string for optional field --- wicket/src/cli/rack_setup/config_template.toml | 1 - wicket/src/cli/rack_setup/config_toml.rs | 15 +++++++++------ wicket/tests/output/example_non_empty.toml | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wicket/src/cli/rack_setup/config_template.toml b/wicket/src/cli/rack_setup/config_template.toml index c0363c43e62..b5bcaa202c8 100644 --- a/wicket/src/cli/rack_setup/config_template.toml +++ b/wicket/src/cli/rack_setup/config_template.toml @@ -57,7 +57,6 @@ allow = "any" # TODO: docs on network config [rack_network_config] -rack_subnet_address = "" infra_ip_first = "" infra_ip_last = "" diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 3339023e71c..c7d0e08de0f 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -245,12 +245,15 @@ fn populate_network_table( }; if let Some(rack_subnet_address) = config.rack_subnet_address { - *table - .get_mut("rack_subnet_address") - .unwrap() - .as_value_mut() - .unwrap() = - Value::String(Formatted::new(rack_subnet_address.to_string())); + let value = Value::String(Formatted::new(rack_subnet_address.to_string())); + match table.entry("rack_subnet_address") { + toml_edit::Entry::Occupied(mut entry) => { + entry.insert( Item::Value(value)); + }, + toml_edit::Entry::Vacant(entry) => { + entry.insert(Item::Value(value)); + }, + } } for (property, value) in [ diff --git a/wicket/tests/output/example_non_empty.toml b/wicket/tests/output/example_non_empty.toml index 5d808365704..078fa8fa79a 100644 --- a/wicket/tests/output/example_non_empty.toml +++ b/wicket/tests/output/example_non_empty.toml @@ -69,9 +69,9 @@ allow = "any" # TODO: docs on network config [rack_network_config] -rack_subnet_address = "fd00:1122:3344:100::" infra_ip_first = "172.30.0.1" infra_ip_last = "172.30.0.10" +rack_subnet_address = "fd00:1122:3344:100::" [rack_network_config.switch0.port0] routes = [{ nexthop = "172.30.0.10", destination = "0.0.0.0/0", vlan_id = 1 }] From 19cf6de0334dfd4098866da857e3353db7586387 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Thu, 15 Jan 2026 00:26:23 +0000 Subject: [PATCH 05/10] fixup! do not require empty string for optional field --- wicket/src/cli/rack_setup/config_toml.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index c7d0e08de0f..8c291def8e8 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -245,14 +245,15 @@ fn populate_network_table( }; if let Some(rack_subnet_address) = config.rack_subnet_address { - let value = Value::String(Formatted::new(rack_subnet_address.to_string())); + let value = + Value::String(Formatted::new(rack_subnet_address.to_string())); match table.entry("rack_subnet_address") { toml_edit::Entry::Occupied(mut entry) => { - entry.insert( Item::Value(value)); - }, + entry.insert(Item::Value(value)); + } toml_edit::Entry::Vacant(entry) => { entry.insert(Item::Value(value)); - }, + } } } From 8ed024fff3cd3fcd7ac7cc572a3598a2f69d4dfc Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 19 Jan 2026 18:13:09 +0000 Subject: [PATCH 06/10] PR suggestions --- wicket/src/ui/panes/rack_setup.rs | 2 +- wicketd/src/rss_config.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index 59c98676827..2c38e15a144 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -715,7 +715,7 @@ fn rss_config_text<'a>( rack_network_config.as_ref().map_or("".into(), |c| { match c.rack_subnet_address { Some(v) => v.to_string(), - None => "".to_string(), + None => "(will be chosen randomly)".to_string(), } .into() }), diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 477e309c684..bf0b6f343d9 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -714,6 +714,11 @@ pub fn validate_rack_subnet( return Err("rack number (seventh octet) cannot be 0".into()); }; + // Do not allow addresses more specific than /56 + if rack_subnet_address.octets()[7..].iter().any(|x| *x != 0x00) { + return Err("rack subnet address is /56, but a more specific prefix was provided".into()); + }; + Ipv6Net::new(rack_subnet_address, 56).map_err(|e| e.to_string()) } From 4bf4f5e2040adf96eed09b25a357d4945f452b18 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 19 Jan 2026 22:22:56 +0000 Subject: [PATCH 07/10] fixup! PR suggestions --- wicket/src/ui/panes/rack_setup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index 2c38e15a144..6943cc0ded8 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -712,10 +712,10 @@ fn rss_config_text<'a>( ), ( "Rack subnet address (IPv6 /56): ", - rack_network_config.as_ref().map_or("".into(), |c| { + rack_network_config.as_ref().map_or("(will be chosen randomly)".into(), |c| { match c.rack_subnet_address { Some(v) => v.to_string(), - None => "(will be chosen randomly)".to_string(), + None => "(chosen randomly)".to_string(), } .into() }), From e1db2d2575fcef0ffb538a9db774a45e8b82b3f1 Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Mon, 19 Jan 2026 23:04:58 +0000 Subject: [PATCH 08/10] fixup! PR suggestions --- wicket/src/ui/panes/rack_setup.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index 6943cc0ded8..b6dd815eee7 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -712,13 +712,16 @@ fn rss_config_text<'a>( ), ( "Rack subnet address (IPv6 /56): ", - rack_network_config.as_ref().map_or("(will be chosen randomly)".into(), |c| { - match c.rack_subnet_address { - Some(v) => v.to_string(), - None => "(chosen randomly)".to_string(), - } - .into() - }), + rack_network_config.as_ref().map_or( + "(will be chosen randomly)".into(), + |c| { + match c.rack_subnet_address { + Some(v) => v.to_string(), + None => "(chosen randomly)".to_string(), + } + .into() + }, + ), ), ( "Infrastructure first IP: ", From 29a96d81903657c0e1dedfec327135b2a71f7981 Mon Sep 17 00:00:00 2001 From: Levon Tarver <11586085+internet-diglett@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:13:56 -0600 Subject: [PATCH 09/10] Update wicketd/src/rss_config.rs help rustfmt with long strings :D Co-authored-by: John Gallagher --- wicketd/src/rss_config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index bf0b6f343d9..e61b2ba037f 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -716,7 +716,8 @@ pub fn validate_rack_subnet( // Do not allow addresses more specific than /56 if rack_subnet_address.octets()[7..].iter().any(|x| *x != 0x00) { - return Err("rack subnet address is /56, but a more specific prefix was provided".into()); + return Err("rack subnet address is /56, \ + but a more specific prefix was provided".into()); }; Ipv6Net::new(rack_subnet_address, 56).map_err(|e| e.to_string()) From a0d858fd395a30d8c9040d3566882c7573c75c3c Mon Sep 17 00:00:00 2001 From: Levon Tarver Date: Tue, 20 Jan 2026 18:25:48 +0000 Subject: [PATCH 10/10] fixup! Update wicketd/src/rss_config.rs --- wicketd/src/rss_config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index e61b2ba037f..5b38951aa20 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -717,7 +717,8 @@ pub fn validate_rack_subnet( // Do not allow addresses more specific than /56 if rack_subnet_address.octets()[7..].iter().any(|x| *x != 0x00) { return Err("rack subnet address is /56, \ - but a more specific prefix was provided".into()); + but a more specific prefix was provided" + .into()); }; Ipv6Net::new(rack_subnet_address, 56).map_err(|e| e.to_string())