From 8975897872cb63a072c60eea4a081c3d164ff588 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Thu, 23 May 2024 17:40:02 -0700 Subject: [PATCH] use `oxnet::{ IpNet, Ipv4Net, Ipv6Net }` (#5810) --- Cargo.lock | 39 +- Cargo.toml | 1 + clients/ddm-admin-client/src/lib.rs | 2 +- clients/nexus-client/Cargo.toml | 1 + clients/nexus-client/src/lib.rs | 20 +- clients/sled-agent-client/Cargo.toml | 1 + clients/sled-agent-client/src/lib.rs | 35 +- common/Cargo.toml | 1 + common/src/address.rs | 114 ++-- common/src/api/external/mod.rs | 548 ++---------------- common/src/api/internal/nexus.rs | 4 +- common/src/api/internal/shared.rs | 39 +- illumos-utils/Cargo.toml | 1 + illumos-utils/src/opte/firewall_rules.rs | 22 +- internal-dns/src/resolver.rs | 2 +- nexus/Cargo.toml | 1 + nexus/db-model/Cargo.toml | 1 + nexus/db-model/src/ipv4_nat_entry.rs | 4 +- nexus/db-model/src/ipv4net.rs | 17 +- nexus/db-model/src/ipv6net.rs | 27 +- nexus/db-model/src/lib.rs | 29 +- nexus/db-model/src/network_interface.rs | 2 +- nexus/db-model/src/vpc.rs | 27 +- nexus/db-model/src/vpc_subnet.rs | 4 +- nexus/db-queries/Cargo.toml | 1 + .../src/db/datastore/ipv4_nat_entry.rs | 47 +- nexus/db-queries/src/db/datastore/mod.rs | 4 +- .../src/db/datastore/network_interface.rs | 7 +- nexus/db-queries/src/db/datastore/rack.rs | 72 +-- .../src/db/datastore/switch_port.rs | 2 +- nexus/db-queries/src/db/datastore/vpc.rs | 4 +- .../src/db/queries/network_interface.rs | 56 +- nexus/db-queries/src/db/queries/vpc_subnet.rs | 28 +- nexus/defaults/Cargo.toml | 1 + nexus/defaults/src/lib.rs | 28 +- nexus/networking/Cargo.toml | 1 + nexus/networking/src/firewall_rules.rs | 11 +- nexus/reconfigurator/execution/Cargo.toml | 1 + .../execution/src/external_networking.rs | 5 +- nexus/reconfigurator/planning/Cargo.toml | 1 + .../planning/src/blueprint_builder/builder.rs | 4 +- .../blueprint_builder/external_networking.rs | 6 +- .../output/planner_nonprovisionable_2_2a.txt | 10 +- nexus/src/app/allow_list.rs | 4 +- .../app/background/sync_service_zone_nat.rs | 17 +- nexus/src/app/bgp.rs | 8 +- nexus/src/app/instance_network.rs | 12 +- nexus/src/app/sagas/vpc_create.rs | 16 +- nexus/src/app/switch_interface.rs | 3 +- nexus/src/app/vpc_subnet.rs | 19 +- nexus/src/context.rs | 3 +- nexus/test-utils/src/lib.rs | 4 +- nexus/tests/integration_tests/allow_list.rs | 13 +- nexus/tests/integration_tests/endpoints.rs | 3 +- nexus/tests/integration_tests/instances.rs | 7 +- .../integration_tests/subnet_allocation.rs | 23 +- nexus/tests/integration_tests/vpc_subnets.rs | 24 +- nexus/tests/integration_tests/vpcs.rs | 5 +- nexus/types/Cargo.toml | 1 + nexus/types/src/external_api/params.rs | 5 +- nexus/types/src/external_api/views.rs | 5 +- openapi/bootstrap-agent.json | 17 +- openapi/nexus-internal.json | 17 +- openapi/nexus.json | 17 +- openapi/sled-agent.json | 19 +- openapi/wicketd.json | 17 +- schema/all-zone-requests.json | 23 +- schema/all-zones-requests.json | 23 +- schema/deployment-config.json | 9 +- schema/rss-service-plan-v3.json | 23 +- schema/rss-sled-plan.json | 25 +- schema/start-sled-agent-request.json | 9 +- sled-agent/Cargo.toml | 1 + sled-agent/src/bootstrap/early_networking.rs | 19 +- sled-agent/src/bootstrap/server.rs | 2 +- sled-agent/src/rack_setup/plan/service.rs | 26 +- sled-agent/src/services.rs | 14 +- sled-agent/src/sim/server.rs | 4 +- tools/dendrite_openapi_version | 0 wicket-common/Cargo.toml | 1 + wicket-common/src/rack_setup.rs | 2 +- wicket/src/ui/panes/rack_setup.rs | 4 +- wicketd/src/rss_config.rs | 2 +- workspace-hack/Cargo.toml | 4 +- 84 files changed, 666 insertions(+), 1015 deletions(-) mode change 100644 => 100755 tools/dendrite_openapi_version diff --git a/Cargo.lock b/Cargo.lock index 1dfaff0d77..eba31ceca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2026,7 +2026,7 @@ dependencies = [ [[package]] name = "dropshot" version = "0.10.2-dev" -source = "git+https://github.com/oxidecomputer/dropshot?branch=main#2fdf37183d2fac385e0f66f48903bc567f2e8e26" +source = "git+https://github.com/oxidecomputer/dropshot?branch=main#0cd0e828d096578392b6a5524334d44fd10ef6da" dependencies = [ "async-stream", "async-trait", @@ -2072,7 +2072,7 @@ dependencies = [ [[package]] name = "dropshot_endpoint" version = "0.10.2-dev" -source = "git+https://github.com/oxidecomputer/dropshot?branch=main#2fdf37183d2fac385e0f66f48903bc567f2e8e26" +source = "git+https://github.com/oxidecomputer/dropshot?branch=main#0cd0e828d096578392b6a5524334d44fd10ef6da" dependencies = [ "proc-macro2", "quote", @@ -3506,6 +3506,7 @@ dependencies = [ "opte-ioctl", "oxide-vpc", "oxlog", + "oxnet", "regress", "schemars", "serde", @@ -4023,7 +4024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -4495,6 +4496,7 @@ dependencies = [ "omicron-passwords", "omicron-uuid-kinds", "omicron-workspace-hack", + "oxnet", "progenitor", "regress", "reqwest", @@ -4551,6 +4553,7 @@ dependencies = [ "omicron-uuid-kinds", "omicron-workspace-hack", "once_cell", + "oxnet", "parse-display", "pq-sys", "rand 0.8.5", @@ -4620,6 +4623,7 @@ dependencies = [ "openssl", "oso", "oximeter", + "oxnet", "paste", "pem", "petgraph", @@ -4661,6 +4665,7 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "once_cell", + "oxnet", "rand 0.8.5", "serde_json", ] @@ -4740,6 +4745,7 @@ dependencies = [ "nexus-db-queries", "omicron-common", "omicron-workspace-hack", + "oxnet", "reqwest", "sled-agent-client", "slog", @@ -4775,6 +4781,7 @@ dependencies = [ "omicron-test-utils", "omicron-uuid-kinds", "omicron-workspace-hack", + "oxnet", "pq-sys", "reqwest", "sled-agent-client", @@ -4805,6 +4812,7 @@ dependencies = [ "omicron-test-utils", "omicron-uuid-kinds", "omicron-workspace-hack", + "oxnet", "proptest", "rand 0.8.5", "sled-agent-client", @@ -4920,6 +4928,7 @@ dependencies = [ "omicron-uuid-kinds", "omicron-workspace-hack", "openssl", + "oxnet", "parse-display", "proptest", "schemars", @@ -5240,6 +5249,7 @@ dependencies = [ "omicron-uuid-kinds", "omicron-workspace-hack", "once_cell", + "oxnet", "parse-display", "progenitor", "progenitor-client", @@ -5444,6 +5454,7 @@ dependencies = [ "oximeter-db", "oximeter-instruments", "oximeter-producer", + "oxnet", "parse-display", "paste", "pem", @@ -5696,6 +5707,7 @@ dependencies = [ "oximeter", "oximeter-instruments", "oximeter-producer", + "oxnet", "pretty_assertions", "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=6d7ed9a033babc054db9eff5b59dee978d2b0d76)", "propolis-mock-server", @@ -6328,6 +6340,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "oxnet" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/oxnet?branch=main#42b4d3c77c7f5f2636cd6c4bbf37ac3eada047e0" +dependencies = [ + "ipnetwork", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "p256" version = "0.13.2" @@ -8196,9 +8219,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" +checksum = "b0218ceea14babe24a4a5836f86ade86c1effbc198164e619194cb5069187e29" dependencies = [ "bytes", "chrono", @@ -8211,9 +8234,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" +checksum = "3ed5a1ccce8ff962e31a165d41f6e2a2dd1245099dc4d594f5574a86cd90f4d3" dependencies = [ "proc-macro2", "quote", @@ -8697,6 +8720,7 @@ dependencies = [ "omicron-common", "omicron-uuid-kinds", "omicron-workspace-hack", + "oxnet", "progenitor", "regress", "reqwest", @@ -10972,6 +10996,7 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "owo-colors", + "oxnet", "schemars", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index ed2b7cdcfe..a350f59f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -331,6 +331,7 @@ omicron-certificates = { path = "certificates" } omicron-passwords = { path = "passwords" } omicron-workspace-hack = "0.1.0" oxlog = { path = "dev-tools/oxlog" } +oxnet = { git = "https://github.com/oxidecomputer/oxnet", branch = "main" } nexus-test-interface = { path = "nexus/test-interface" } nexus-test-utils-macros = { path = "nexus/test-utils-macros" } nexus-test-utils = { path = "nexus/test-utils" } diff --git a/clients/ddm-admin-client/src/lib.rs b/clients/ddm-admin-client/src/lib.rs index 5be2dd53bd..b926ee2971 100644 --- a/clients/ddm-admin-client/src/lib.rs +++ b/clients/ddm-admin-client/src/lib.rs @@ -82,7 +82,7 @@ impl Client { let me = self.clone(); tokio::spawn(async move { let prefix = - Ipv6Prefix { addr: address.net().network(), len: SLED_PREFIX }; + Ipv6Prefix { addr: address.net().prefix(), len: SLED_PREFIX }; retry_notify(retry_policy_internal_service_aggressive(), || async { info!( me.log, "Sending prefix to ddmd for advertisement"; diff --git a/clients/nexus-client/Cargo.toml b/clients/nexus-client/Cargo.toml index 1d5cced21c..b4e299da67 100644 --- a/clients/nexus-client/Cargo.toml +++ b/clients/nexus-client/Cargo.toml @@ -14,6 +14,7 @@ ipnetwork.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true +oxnet.workspace = true progenitor.workspace = true regress.workspace = true reqwest = { workspace = true, features = ["rustls-tls", "stream"] } diff --git a/clients/nexus-client/src/lib.rs b/clients/nexus-client/src/lib.rs index ae8f0c93db..bcdd3971c0 100644 --- a/clients/nexus-client/src/lib.rs +++ b/clients/nexus-client/src/lib.rs @@ -419,33 +419,27 @@ impl TryFrom } } -impl TryFrom<&omicron_common::api::external::Ipv4Net> for types::Ipv4Net { +impl TryFrom<&oxnet::Ipv4Net> for types::Ipv4Net { type Error = String; - fn try_from( - net: &omicron_common::api::external::Ipv4Net, - ) -> Result { + fn try_from(net: &oxnet::Ipv4Net) -> Result { types::Ipv4Net::try_from(net.to_string()).map_err(|e| e.to_string()) } } -impl TryFrom<&omicron_common::api::external::Ipv6Net> for types::Ipv6Net { +impl TryFrom<&oxnet::Ipv6Net> for types::Ipv6Net { type Error = String; - fn try_from( - net: &omicron_common::api::external::Ipv6Net, - ) -> Result { + fn try_from(net: &oxnet::Ipv6Net) -> Result { types::Ipv6Net::try_from(net.to_string()).map_err(|e| e.to_string()) } } -impl TryFrom<&omicron_common::api::external::IpNet> for types::IpNet { +impl TryFrom<&oxnet::IpNet> for types::IpNet { type Error = String; - fn try_from( - net: &omicron_common::api::external::IpNet, - ) -> Result { - use omicron_common::api::external::IpNet; + fn try_from(net: &oxnet::IpNet) -> Result { + use oxnet::IpNet; match net { IpNet::V4(v4) => types::Ipv4Net::try_from(v4).map(types::IpNet::V4), IpNet::V6(v6) => types::Ipv6Net::try_from(v6).map(types::IpNet::V6), diff --git a/clients/sled-agent-client/Cargo.toml b/clients/sled-agent-client/Cargo.toml index 3f3d82bf80..caca3c8c73 100644 --- a/clients/sled-agent-client/Cargo.toml +++ b/clients/sled-agent-client/Cargo.toml @@ -22,4 +22,5 @@ slog.workspace = true uuid.workspace = true omicron-workspace-hack.workspace = true omicron-uuid-kinds.workspace = true +oxnet.workspace = true serde_json.workspace = true diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 4ac7eed27c..24bb2a6df8 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -413,24 +413,23 @@ impl From for omicron_common::api::external::DiskState { } } -impl From for types::Ipv4Net { - fn from(n: omicron_common::api::external::Ipv4Net) -> Self { +impl From for types::Ipv4Net { + fn from(n: oxnet::Ipv4Net) -> Self { Self::try_from(n.to_string()).unwrap_or_else(|e| panic!("{}: {}", n, e)) } } -impl From for types::Ipv6Net { - fn from(n: omicron_common::api::external::Ipv6Net) -> Self { +impl From for types::Ipv6Net { + fn from(n: oxnet::Ipv6Net) -> Self { Self::try_from(n.to_string()).unwrap_or_else(|e| panic!("{}: {}", n, e)) } } -impl From for types::IpNet { - fn from(s: omicron_common::api::external::IpNet) -> Self { - use omicron_common::api::external::IpNet; +impl From for types::IpNet { + fn from(s: oxnet::IpNet) -> Self { match s { - IpNet::V4(v4) => Self::V4(v4.into()), - IpNet::V6(v6) => Self::V6(v6.into()), + oxnet::IpNet::V4(v4) => Self::V4(v4.into()), + oxnet::IpNet::V6(v6) => Self::V6(v6.into()), } } } @@ -441,14 +440,20 @@ impl From for types::Ipv4Net { } } -impl From for ipnetwork::Ipv4Network { +impl From for types::Ipv4Network { + fn from(n: ipnetwork::Ipv4Network) -> Self { + Self::try_from(n.to_string()).unwrap_or_else(|e| panic!("{}: {}", n, e)) + } +} + +impl From for oxnet::Ipv4Net { fn from(n: types::Ipv4Net) -> Self { n.parse().unwrap() } } -impl From for types::Ipv4Network { - fn from(n: ipnetwork::Ipv4Network) -> Self { +impl From for types::Ipv4Network { + fn from(n: oxnet::Ipv4Net) -> Self { Self::try_from(n.to_string()).unwrap_or_else(|e| panic!("{}: {}", n, e)) } } @@ -484,6 +489,12 @@ impl From for ipnetwork::IpNetwork { } } +impl From for ipnetwork::Ipv4Network { + fn from(n: types::Ipv4Net) -> Self { + n.parse().unwrap() + } +} + impl From for types::Ipv4Net { fn from(n: std::net::Ipv4Addr) -> Self { Self::try_from(format!("{n}/32")) diff --git a/common/Cargo.toml b/common/Cargo.toml index 04010af4be..b51e1bb070 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -28,6 +28,7 @@ ipnetwork.workspace = true macaddr.workspace = true mg-admin-client.workspace = true omicron-uuid-kinds.workspace = true +oxnet.workspace = true proptest = { workspace = true, optional = true } rand.workspace = true reqwest = { workspace = true, features = ["rustls-tls", "stream"] } diff --git a/common/src/address.rs b/common/src/address.rs index 817070d399..b7476d6ff4 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -7,9 +7,10 @@ //! This addressing functionality is shared by both initialization services //! and Nexus, who need to agree upon addressing schemes. -use crate::api::external::{self, Error, Ipv4Net, Ipv6Net}; -use ipnetwork::{Ipv4Network, Ipv6Network}; +use crate::api::external::{self, Error}; +use ipnetwork::Ipv6Network; use once_cell::sync::Lazy; +use oxnet::{Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6}; @@ -72,6 +73,12 @@ pub const WICKETD_NEXUS_PROXY_PORT: u16 = 12229; pub const NTP_PORT: u16 = 123; +/// The length for all VPC IPv6 prefixes +pub const VPC_IPV6_PREFIX_LENGTH: u8 = 48; + +/// The prefix length for all VPC subnets +pub const VPC_SUBNET_IPV6_PREFIX_LENGTH: u8 = 64; + // The number of ports available to an SNAT IP. // Note that for static NAT, this value isn't used, and all ports are available. // @@ -104,61 +111,50 @@ pub const NUM_SOURCE_NAT_PORTS: u16 = 1 << 14; // Furthermore, all the below *_OPTE_IPV6_SUBNET constants are // /64's within this prefix. pub static SERVICE_VPC_IPV6_PREFIX: Lazy = Lazy::new(|| { - Ipv6Net( - Ipv6Network::new( - Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 0, 0, 0, 0, 0), - Ipv6Net::VPC_IPV6_PREFIX_LENGTH, - ) - .unwrap(), + Ipv6Net::new( + Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 0, 0, 0, 0, 0), + VPC_IPV6_PREFIX_LENGTH, ) + .unwrap() }); /// The IPv4 subnet for External DNS OPTE ports. -pub static DNS_OPTE_IPV4_SUBNET: Lazy = Lazy::new(|| { - Ipv4Net(Ipv4Network::new(Ipv4Addr::new(172, 30, 1, 0), 24).unwrap()) -}); +pub static DNS_OPTE_IPV4_SUBNET: Lazy = + Lazy::new(|| Ipv4Net::new(Ipv4Addr::new(172, 30, 1, 0), 24).unwrap()); /// The IPv6 subnet for External DNS OPTE ports. pub static DNS_OPTE_IPV6_SUBNET: Lazy = Lazy::new(|| { - Ipv6Net( - Ipv6Network::new( - Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 1, 0, 0, 0, 0), - Ipv6Net::VPC_SUBNET_IPV6_PREFIX_LENGTH, - ) - .unwrap(), + Ipv6Net::new( + Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 1, 0, 0, 0, 0), + VPC_SUBNET_IPV6_PREFIX_LENGTH, ) + .unwrap() }); /// The IPv4 subnet for Nexus OPTE ports. -pub static NEXUS_OPTE_IPV4_SUBNET: Lazy = Lazy::new(|| { - Ipv4Net(Ipv4Network::new(Ipv4Addr::new(172, 30, 2, 0), 24).unwrap()) -}); +pub static NEXUS_OPTE_IPV4_SUBNET: Lazy = + Lazy::new(|| Ipv4Net::new(Ipv4Addr::new(172, 30, 2, 0), 24).unwrap()); /// The IPv6 subnet for Nexus OPTE ports. pub static NEXUS_OPTE_IPV6_SUBNET: Lazy = Lazy::new(|| { - Ipv6Net( - Ipv6Network::new( - Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 2, 0, 0, 0, 0), - Ipv6Net::VPC_SUBNET_IPV6_PREFIX_LENGTH, - ) - .unwrap(), + Ipv6Net::new( + Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 2, 0, 0, 0, 0), + VPC_SUBNET_IPV6_PREFIX_LENGTH, ) + .unwrap() }); /// The IPv4 subnet for Boundary NTP OPTE ports. -pub static NTP_OPTE_IPV4_SUBNET: Lazy = Lazy::new(|| { - Ipv4Net(Ipv4Network::new(Ipv4Addr::new(172, 30, 3, 0), 24).unwrap()) -}); +pub static NTP_OPTE_IPV4_SUBNET: Lazy = + Lazy::new(|| Ipv4Net::new(Ipv4Addr::new(172, 30, 3, 0), 24).unwrap()); /// The IPv6 subnet for Boundary NTP OPTE ports. pub static NTP_OPTE_IPV6_SUBNET: Lazy = Lazy::new(|| { - Ipv6Net( - Ipv6Network::new( - Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 3, 0, 0, 0, 0), - Ipv6Net::VPC_SUBNET_IPV6_PREFIX_LENGTH, - ) - .unwrap(), + Ipv6Net::new( + Ipv6Addr::new(0xfd77, 0xe9d2, 0x9cd9, 3, 0, 0, 0, 0), + VPC_SUBNET_IPV6_PREFIX_LENGTH, ) + .unwrap() }); // Anycast is a mechanism in which a single IP address is shared by multiple @@ -188,7 +184,7 @@ pub const CP_SERVICES_RESERVED_ADDRESSES: u16 = 0xFFFF; // to assume that addresses in this subnet are available. pub const SLED_RESERVED_ADDRESSES: u16 = 32; -/// Wraps an [`Ipv6Network`] with a compile-time prefix length. +/// Wraps an [`Ipv6Net`] with a compile-time prefix length. #[derive(Debug, Clone, Copy, JsonSchema, Serialize, Hash, PartialEq, Eq)] #[schemars(rename = "Ipv6Subnet")] pub struct Ipv6Subnet { @@ -198,23 +194,23 @@ pub struct Ipv6Subnet { impl Ipv6Subnet { pub fn new(addr: Ipv6Addr) -> Self { // Create a network with the compile-time prefix length. - let net = Ipv6Network::new(addr, N).unwrap(); + let net = Ipv6Net::new(addr, N).unwrap(); // Ensure the address is set to within-prefix only components. - let net = Ipv6Network::new(net.network(), N).unwrap(); - Self { net: Ipv6Net(net) } + let net = Ipv6Net::new(net.prefix(), N).unwrap(); + Self { net } } /// Returns the underlying network. - pub fn net(&self) -> Ipv6Network { - self.net.0 + pub fn net(&self) -> Ipv6Net { + self.net } } impl From for Ipv6Subnet { fn from(net: Ipv6Network) -> Self { // Ensure the address is set to within-prefix only components. - let net = Ipv6Network::new(net.network(), N).unwrap(); - Self { net: Ipv6Net(net) } + let net = Ipv6Net::new(net.network(), N).unwrap(); + Self { net } } } @@ -230,13 +226,13 @@ impl<'de, const N: u8> Deserialize<'de> for Ipv6Subnet { } let Inner { net } = Inner::deserialize(deserializer)?; - if net.prefix() == N { + if net.width() == N { Ok(Self { net }) } else { Err(::custom(format!( "expected prefix {} but found {}", N, - net.prefix(), + net.width(), ))) } } @@ -252,9 +248,9 @@ impl DnsSubnet { /// Returns the DNS server address within the subnet. /// /// This is the first address within the subnet. - pub fn dns_address(&self) -> Ipv6Network { - Ipv6Network::new( - self.subnet.net().iter().nth(DNS_ADDRESS_INDEX).unwrap(), + pub fn dns_address(&self) -> Ipv6Net { + Ipv6Net::new( + self.subnet.net().nth(DNS_ADDRESS_INDEX as u128).unwrap(), SLED_PREFIX, ) .unwrap() @@ -264,9 +260,9 @@ impl DnsSubnet { /// to be able to contact the DNS server. /// /// This is the second address within the subnet. - pub fn gz_address(&self) -> Ipv6Network { - Ipv6Network::new( - self.subnet.net().iter().nth(GZ_ADDRESS_INDEX).unwrap(), + pub fn gz_address(&self) -> Ipv6Net { + Ipv6Net::new( + self.subnet.net().nth(GZ_ADDRESS_INDEX as u128).unwrap(), SLED_PREFIX, ) .unwrap() @@ -281,7 +277,7 @@ pub struct ReservedRackSubnet(pub Ipv6Subnet); impl ReservedRackSubnet { /// Returns the subnet for the reserved rack subnet. pub fn new(subnet: Ipv6Subnet) -> Self { - ReservedRackSubnet(Ipv6Subnet::::new(subnet.net().ip())) + ReservedRackSubnet(Ipv6Subnet::::new(subnet.net().addr())) } /// Returns the DNS addresses from this reserved rack subnet. @@ -308,7 +304,7 @@ pub fn get_internal_dns_server_addresses(addr: Ipv6Addr) -> Vec { &reserved_rack_subnet.get_dns_subnets()[0..DNS_REDUNDANCY]; dns_subnets .iter() - .map(|dns_subnet| IpAddr::from(dns_subnet.dns_address().ip())) + .map(|dns_subnet| IpAddr::from(dns_subnet.dns_address().addr())) .collect() } @@ -320,7 +316,7 @@ const SWITCH_ZONE_ADDRESS_INDEX: usize = 2; /// This address will come from the first address of the [`SLED_PREFIX`] subnet. pub fn get_sled_address(sled_subnet: Ipv6Subnet) -> SocketAddrV6 { let sled_agent_ip = - sled_subnet.net().iter().nth(SLED_AGENT_ADDRESS_INDEX).unwrap(); + sled_subnet.net().nth(SLED_AGENT_ADDRESS_INDEX as u128).unwrap(); SocketAddrV6::new(sled_agent_ip, SLED_AGENT_PORT, 0, 0) } @@ -330,7 +326,7 @@ pub fn get_sled_address(sled_subnet: Ipv6Subnet) -> SocketAddrV6 { pub fn get_switch_zone_address( sled_subnet: Ipv6Subnet, ) -> Ipv6Addr { - sled_subnet.net().iter().nth(SWITCH_ZONE_ADDRESS_INDEX).unwrap() + sled_subnet.net().nth(SWITCH_ZONE_ADDRESS_INDEX as u128).unwrap() } /// Returns a sled subnet within a rack subnet. @@ -340,7 +336,7 @@ pub fn get_64_subnet( rack_subnet: Ipv6Subnet, index: u8, ) -> Ipv6Subnet { - let mut rack_network = rack_subnet.net().network().octets(); + let mut rack_network = rack_subnet.net().addr().octets(); // To set bits distinguishing the /64 from the /56, we modify the 7th octet. rack_network[7] = index; @@ -680,7 +676,7 @@ mod test { assert_eq!( // Note that these bits (indicating the rack) are zero. // vv - "fd00:1122:3344:0000::/56".parse::().unwrap(), + "fd00:1122:3344:0000::/56".parse::().unwrap(), rack_subnet.0.net(), ); @@ -690,11 +686,11 @@ mod test { // The DNS address and GZ address should be only differing by one. assert_eq!( - "fd00:1122:3344:0001::1/64".parse::().unwrap(), + "fd00:1122:3344:0001::1/64".parse::().unwrap(), dns_subnets[0].dns_address(), ); assert_eq!( - "fd00:1122:3344:0001::2/64".parse::().unwrap(), + "fd00:1122:3344:0001::2/64".parse::().unwrap(), dns_subnets[0].gz_address(), ); } diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 1c01782cc6..07a7776f1e 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -22,6 +22,7 @@ use dropshot::HttpError; pub use dropshot::PaginationOrder; pub use error::*; use futures::stream::BoxStream; +use oxnet::IpNet; use parse_display::Display; use parse_display::FromStr; use rand::thread_rng; @@ -39,7 +40,6 @@ use std::fmt::Formatter; use std::fmt::Result as FormatResult; use std::net::IpAddr; use std::net::Ipv4Addr; -use std::net::Ipv6Addr; use std::num::{NonZeroU16, NonZeroU32}; use std::str::FromStr; use uuid::Uuid; @@ -1229,398 +1229,33 @@ impl DiskState { } } -/// An `Ipv4Net` represents a IPv4 subnetwork, including the address and network mask. -#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Eq, Serialize)] -pub struct Ipv4Net(pub ipnetwork::Ipv4Network); - -impl Ipv4Net { - /// Constructs a new `Ipv4Net` representing a single IP. - pub fn single(ip: Ipv4Addr) -> Self { - Ipv4Net( - ipnetwork::Ipv4Network::new(ip, 32).expect("32 is within range"), - ) - } - - /// Return `true` if this IPv4 subnetwork is from an RFC 1918 private - /// address space. - pub fn is_private(&self) -> bool { - self.0.network().is_private() - } -} - -impl std::ops::Deref for Ipv4Net { - type Target = ipnetwork::Ipv4Network; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::fmt::Display for Ipv4Net { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl JsonSchema for Ipv4Net { - fn schema_name() -> String { - "Ipv4Net".to_string() - } - - fn json_schema( - _: &mut schemars::gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - metadata: Some(Box::new(schemars::schema::Metadata { - title: Some("An IPv4 subnet".to_string()), - description: Some( - "An IPv4 subnet, including prefix and subnet mask" - .to_string(), - ), - examples: vec!["192.168.1.0/24".into()], - ..Default::default() - })), - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - pattern: Some( - concat!( - r#"^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}"#, - r#"([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"#, - r#"/([0-9]|1[0-9]|2[0-9]|3[0-2])$"#, - ) - .to_string(), - ), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} - -/// An `Ipv6Net` represents a IPv6 subnetwork, including the address and network mask. -#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Eq, Serialize)] -pub struct Ipv6Net(pub ipnetwork::Ipv6Network); - -impl Ipv6Net { +pub trait Ipv6NetExt { /// The length for all VPC IPv6 prefixes - pub const VPC_IPV6_PREFIX_LENGTH: u8 = 48; + const VPC_IPV6_PREFIX_LENGTH: u8 = 48; - /// The prefix length for all VPC Sunets - pub const VPC_SUBNET_IPV6_PREFIX_LENGTH: u8 = 64; - - /// Constructs a new `Ipv6Net` representing a single IPv6 address. - pub fn single(ip: Ipv6Addr) -> Self { - Ipv6Net( - ipnetwork::Ipv6Network::new(ip, 128).expect("128 is within range"), - ) - } - - /// Return `true` if this subnetwork is in the IPv6 Unique Local Address - /// range defined in RFC 4193, e.g., `fd00:/8` - pub fn is_unique_local(&self) -> bool { - // TODO: Delegate to `Ipv6Addr::is_unique_local()` when stabilized. - self.0.network().octets()[0] == 0xfd - } + /// The prefix length for all VPC Subnets + const VPC_SUBNET_IPV6_PREFIX_LENGTH: u8 = 64; /// Return `true` if this subnetwork is a valid VPC prefix. /// /// This checks that the subnet is a unique local address, and has the VPC /// prefix length required. - pub fn is_vpc_prefix(&self) -> bool { - self.is_unique_local() - && self.0.prefix() == Self::VPC_IPV6_PREFIX_LENGTH - } + fn is_vpc_prefix(&self) -> bool; /// Return `true` if this subnetwork is a valid VPC Subnet, given the VPC's /// prefix. - pub fn is_vpc_subnet(&self, vpc_prefix: &Ipv6Net) -> bool { - self.is_unique_local() - && self.is_subnet_of(vpc_prefix.0) - && self.prefix() == Self::VPC_SUBNET_IPV6_PREFIX_LENGTH - } -} - -impl std::ops::Deref for Ipv6Net { - type Target = ipnetwork::Ipv6Network; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::fmt::Display for Ipv6Net { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for Ipv6Net { - fn from(n: ipnetwork::Ipv6Network) -> Ipv6Net { - Self(n) - } -} - -const IPV6_NET_REGEX: &str = concat!( - r#"^("#, - r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"#, - r#"([0-9a-fA-F]{1,4}:){1,7}:|"#, - r#"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"#, - r#"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"#, - r#"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"#, - r#"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"#, - r#"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"#, - r#"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"#, - r#":((:[0-9a-fA-F]{1,4}){1,7}|:)|"#, - r#"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"#, - r#"::(ffff(:0{1,4}){0,1}:){0,1}"#, - r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#, - r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"#, - r#"([0-9a-fA-F]{1,4}:){1,4}:"#, - r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#, - r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#, - r#")\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"#, -); - -#[cfg(test)] -#[test] -fn test_ipv6_regex() { - let re = regress::Regex::new(IPV6_NET_REGEX).unwrap(); - for case in [ - "1:2:3:4:5:6:7:8", - "1:a:2:b:3:c:4:d", - "1::", - "::1", - "::", - "1::3:4:5:6:7:8", - "1:2::4:5:6:7:8", - "1:2:3::5:6:7:8", - "1:2:3:4::6:7:8", - "1:2:3:4:5::7:8", - "1:2:3:4:5:6::8", - "1:2:3:4:5:6:7::", - "2001::", - "fd00::", - "::100:1", - "fd12:3456::", - ] { - for prefix in 0..=128 { - let net = format!("{case}/{prefix}"); - assert!( - re.find(&net).is_some(), - "Expected to match IPv6 case: {}", - prefix, - ); - } - } -} - -impl JsonSchema for Ipv6Net { - fn schema_name() -> String { - "Ipv6Net".to_string() - } - - fn json_schema( - _: &mut schemars::gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - metadata: Some(Box::new(schemars::schema::Metadata { - title: Some("An IPv6 subnet".to_string()), - description: Some( - "An IPv6 subnet, including prefix and subnet mask" - .to_string(), - ), - examples: vec!["fd12:3456::/64".into()], - ..Default::default() - })), - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - pattern: Some(IPV6_NET_REGEX.to_string()), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} - -/// An `IpNet` represents an IP network, either IPv4 or IPv6. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum IpNet { - V4(Ipv4Net), - V6(Ipv6Net), -} - -impl IpNet { - /// Constructs a new `IpNet` representing a single IP. - pub fn single(ip: IpAddr) -> Self { - match ip { - IpAddr::V4(ip) => IpNet::V4(Ipv4Net::single(ip)), - IpAddr::V6(ip) => IpNet::V6(Ipv6Net::single(ip)), - } - } - - /// Return the underlying address. - pub fn ip(&self) -> IpAddr { - match self { - IpNet::V4(inner) => inner.ip().into(), - IpNet::V6(inner) => inner.ip().into(), - } - } - - /// Return the underlying prefix length. - pub fn prefix(&self) -> u8 { - match self { - IpNet::V4(inner) => inner.prefix(), - IpNet::V6(inner) => inner.prefix(), - } - } - - /// Return the first address in this subnet - pub fn first_address(&self) -> IpAddr { - match self { - IpNet::V4(inner) => IpAddr::from(inner.iter().next().unwrap()), - IpNet::V6(inner) => IpAddr::from(inner.iter().next().unwrap()), - } - } - - /// Return the last address in this subnet. - /// - /// For a subnet of size 1, e.g., a /32, this is the same as the first - /// address. - // NOTE: This is a workaround for the fact that the `ipnetwork` crate's - // iterator provides only the `Iterator::next()` method. That means that - // finding the last address is linear in the size of the subnet, which is - // completely untenable and totally avoidable with some addition. In the - // long term, we should either put up a patch to the `ipnetwork` crate or - // move the `ipnet` crate, which does provide an efficient iterator - // implementation. - pub fn last_address(&self) -> IpAddr { - match self { - IpNet::V4(inner) => { - let base: u32 = inner.network().into(); - let size = inner.size() - 1; - std::net::IpAddr::V4(std::net::Ipv4Addr::from(base + size)) - } - IpNet::V6(inner) => { - let base: u128 = inner.network().into(); - let size = inner.size() - 1; - std::net::IpAddr::V6(std::net::Ipv6Addr::from(base + size)) - } - } - } - - /// Return true if the provided address is contained in self. - /// - /// This returns false if the address and the network are of different IP - /// families. - pub fn contains(&self, addr: IpAddr) -> bool { - match (self, addr) { - (IpNet::V4(net), IpAddr::V4(ip)) => net.contains(ip), - (IpNet::V6(net), IpAddr::V6(ip)) => net.contains(ip), - (_, _) => false, - } - } -} - -impl From for IpNet { - fn from(n: ipnetwork::IpNetwork) -> Self { - match n { - ipnetwork::IpNetwork::V4(v4) => IpNet::V4(Ipv4Net(v4)), - ipnetwork::IpNetwork::V6(v6) => IpNet::V6(Ipv6Net(v6)), - } - } -} - -// NOTE: We deliberately do *NOT* implement `From for IpNet`. -// This is because there are many ways to convert an address into a network. -// See https://github.com/oxidecomputer/omicron/issues/5687. - -impl From for IpNet { - fn from(n: Ipv4Net) -> IpNet { - IpNet::V4(n) - } -} - -impl From for IpNet { - fn from(n: Ipv6Net) -> IpNet { - IpNet::V6(n) - } -} - -impl std::fmt::Display for IpNet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - IpNet::V4(inner) => write!(f, "{}", inner), - IpNet::V6(inner) => write!(f, "{}", inner), - } - } + fn is_vpc_subnet(&self, vpc_prefix: &Self) -> bool; } -impl FromStr for IpNet { - type Err = String; - - fn from_str(s: &str) -> Result { - let net = - s.parse::().map_err(|e| e.to_string())?; - match net { - ipnetwork::IpNetwork::V4(net) => Ok(IpNet::from(Ipv4Net(net))), - ipnetwork::IpNetwork::V6(net) => Ok(IpNet::from(Ipv6Net(net))), - } +impl Ipv6NetExt for oxnet::Ipv6Net { + fn is_vpc_prefix(&self) -> bool { + self.is_unique_local() && self.width() == Self::VPC_IPV6_PREFIX_LENGTH } -} -impl From for ipnetwork::IpNetwork { - fn from(net: IpNet) -> ipnetwork::IpNetwork { - match net { - IpNet::V4(net) => ipnetwork::IpNetwork::from(net.0), - IpNet::V6(net) => ipnetwork::IpNetwork::from(net.0), - } - } -} - -impl Serialize for IpNet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - IpNet::V4(v4) => v4.serialize(serializer), - IpNet::V6(v6) => v6.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for IpNet { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let net = ipnetwork::IpNetwork::deserialize(deserializer)?; - match net { - ipnetwork::IpNetwork::V4(net) => Ok(IpNet::from(Ipv4Net(net))), - ipnetwork::IpNetwork::V6(net) => Ok(IpNet::from(Ipv6Net(net))), - } - } -} - -impl JsonSchema for IpNet { - fn schema_name() -> String { - "IpNet".to_string() - } - - fn json_schema( - gen: &mut schemars::gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - one_of: Some(vec![ - label_schema("v4", gen.subschema_for::()), - label_schema("v6", gen.subschema_for::()), - ]), - ..Default::default() - })), - ..Default::default() - } - .into() + fn is_vpc_subnet(&self, vpc_prefix: &Self) -> bool { + self.is_unique_local() + && self.is_subnet_of(vpc_prefix) + && self.width() == Self::VPC_SUBNET_IPV6_PREFIX_LENGTH } } @@ -1907,7 +1542,7 @@ pub enum VpcFirewallRuleTarget { /// The rule applies to a specific IP address Ip(IpAddr), /// The rule applies to a specific IP subnet - IpNet(IpNet), + IpNet(oxnet::IpNet), // Tags not yet implemented // Tag(Name), } @@ -1938,7 +1573,7 @@ pub enum VpcFirewallRuleHostFilter { /// The rule applies to traffic from/to a specific IP address Ip(IpAddr), /// The rule applies to traffic from/to a specific IP subnet - IpNet(IpNet), + IpNet(oxnet::IpNet), // TODO: Internet gateways not yet implemented // #[display("inetgw:{0}")] // InternetGateway(Name), @@ -2446,7 +2081,7 @@ pub struct LoopbackAddress { pub switch_location: String, /// The loopback IP address and prefix length. - pub address: IpNet, + pub address: oxnet::IpNet, } /// A switch port represents a physical external port on a rack switch. @@ -2688,7 +2323,7 @@ pub struct LldpConfig { pub system_description: String, /// THE LLDP management IP TLV. - pub management_ip: IpNet, + pub management_ip: oxnet::IpNet, } /// Describes the kind of an switch interface. @@ -2755,10 +2390,10 @@ pub struct SwitchPortRouteConfig { pub interface_name: String, /// The route's destination network. - pub dst: IpNet, + pub dst: oxnet::IpNet, /// The route's gateway address. - pub gw: IpNet, + pub gw: oxnet::IpNet, /// The VLAN identifier for the route. Use this if the gateway is reachable /// over an 802.1Q tagged L2 segment. @@ -2887,7 +2522,7 @@ pub struct BgpAnnouncement { pub address_lot_block_id: Uuid, /// The IP network being announced. - pub network: IpNet, + pub network: oxnet::IpNet, } /// An IP address configuration for a port settings object. @@ -2900,7 +2535,7 @@ pub struct SwitchPortAddressConfig { pub address_lot_block_id: Uuid, /// The IP address and prefix. - pub address: IpNet, + pub address: oxnet::IpNet, /// The interface name this address belongs to. // TODO: https://github.com/oxidecomputer/omicron/issues/3050 @@ -3027,7 +2662,7 @@ impl AggregateBgpMessageHistory { #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] pub struct BgpImportedRouteIpv4 { /// The destination network prefix. - pub prefix: Ipv4Net, + pub prefix: oxnet::Ipv4Net, /// The nexthop the prefix is reachable through. pub nexthop: Ipv4Addr, @@ -3180,7 +2815,7 @@ pub enum ImportExportPolicy { /// Do not perform any filtering. #[default] NoFiltering, - Allow(Vec), + Allow(Vec), } #[cfg(test)] @@ -3188,7 +2823,6 @@ mod test { use serde::Deserialize; use serde::Serialize; - use super::IpNet; use super::RouteDestination; use super::RouteTarget; use super::SemverVersion; @@ -3644,31 +3278,29 @@ mod test { #[test] fn test_ipv6_net_operations() { - use super::Ipv6Net; - assert!(Ipv6Net("fd00::/8".parse().unwrap()).is_unique_local()); - assert!(!Ipv6Net("fe00::/8".parse().unwrap()).is_unique_local()); - - assert!(Ipv6Net("fd00::/48".parse().unwrap()).is_vpc_prefix()); - assert!(!Ipv6Net("fe00::/48".parse().unwrap()).is_vpc_prefix()); - assert!(!Ipv6Net("fd00::/40".parse().unwrap()).is_vpc_prefix()); - - let vpc_prefix = Ipv6Net("fd00::/48".parse().unwrap()); - assert!( - Ipv6Net("fd00::/64".parse().unwrap()).is_vpc_subnet(&vpc_prefix) - ); - assert!( - !Ipv6Net("fd10::/64".parse().unwrap()).is_vpc_subnet(&vpc_prefix) - ); - assert!( - !Ipv6Net("fd00::/63".parse().unwrap()).is_vpc_subnet(&vpc_prefix) - ); - } - - #[test] - fn test_ipv4_net_operations() { - use super::{IpNet, Ipv4Net}; - let x: IpNet = "0.0.0.0/0".parse().unwrap(); - assert_eq!(x, IpNet::V4(Ipv4Net("0.0.0.0/0".parse().unwrap()))) + use super::Ipv6NetExt; + use oxnet::Ipv6Net; + + assert!("fd00::/8".parse::().unwrap().is_unique_local()); + assert!(!"fe00::/8".parse::().unwrap().is_unique_local()); + + assert!("fd00::/48".parse::().unwrap().is_vpc_prefix()); + assert!(!"fe00::/48".parse::().unwrap().is_vpc_prefix()); + assert!(!"fd00::/40".parse::().unwrap().is_vpc_prefix()); + + let vpc_prefix = "fd00::/48".parse::().unwrap(); + assert!("fd00::/64" + .parse::() + .unwrap() + .is_vpc_subnet(&vpc_prefix)); + assert!(!"fd10::/64" + .parse::() + .unwrap() + .is_vpc_subnet(&vpc_prefix)); + assert!(!"fd00::/63" + .parse::() + .unwrap() + .is_vpc_subnet(&vpc_prefix)); } #[test] @@ -3799,92 +3431,6 @@ mod test { assert!("hash:super_random".parse::().is_err()); } - #[test] - fn test_ipnet_serde() { - //TODO: none of this actually exercises - // schemars::schema::StringValidation bits and the schemars - // documentation is not forthcoming on how this might be accomplished. - let net_str = "fd00:2::/32"; - let net = IpNet::from_str(net_str).unwrap(); - let ser = serde_json::to_string(&net).unwrap(); - - assert_eq!(format!(r#""{}""#, net_str), ser); - let net_des = serde_json::from_str::(&ser).unwrap(); - assert_eq!(net, net_des); - - let net_str = "fd00:47::1/64"; - let net = IpNet::from_str(net_str).unwrap(); - let ser = serde_json::to_string(&net).unwrap(); - - assert_eq!(format!(r#""{}""#, net_str), ser); - let net_des = serde_json::from_str::(&ser).unwrap(); - assert_eq!(net, net_des); - - let net_str = "192.168.1.1/16"; - let net = IpNet::from_str(net_str).unwrap(); - let ser = serde_json::to_string(&net).unwrap(); - - assert_eq!(format!(r#""{}""#, net_str), ser); - let net_des = serde_json::from_str::(&ser).unwrap(); - assert_eq!(net, net_des); - - let net_str = "0.0.0.0/0"; - let net = IpNet::from_str(net_str).unwrap(); - let ser = serde_json::to_string(&net).unwrap(); - - assert_eq!(format!(r#""{}""#, net_str), ser); - let net_des = serde_json::from_str::(&ser).unwrap(); - assert_eq!(net, net_des); - } - - #[test] - fn test_ipnet_first_last_address() { - use std::net::IpAddr; - use std::net::Ipv4Addr; - use std::net::Ipv6Addr; - let net: IpNet = "fd00::/128".parse().unwrap(); - assert_eq!( - net.first_address(), - IpAddr::from(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0)), - ); - assert_eq!( - net.last_address(), - IpAddr::from(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0)), - ); - - let net: IpNet = "fd00::/64".parse().unwrap(); - assert_eq!( - net.first_address(), - IpAddr::from(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0)), - ); - assert_eq!( - net.last_address(), - IpAddr::from(Ipv6Addr::new( - 0xfd00, 0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff - )), - ); - - let net: IpNet = "10.0.0.0/16".parse().unwrap(); - assert_eq!( - net.first_address(), - IpAddr::from(Ipv4Addr::new(10, 0, 0, 0)), - ); - assert_eq!( - net.last_address(), - IpAddr::from(Ipv4Addr::new(10, 0, 255, 255)), - ); - - let net: IpNet = "10.0.0.0/32".parse().unwrap(); - assert_eq!( - net.first_address(), - IpAddr::from(Ipv4Addr::new(10, 0, 0, 0)), - ); - assert_eq!( - net.last_address(), - IpAddr::from(Ipv4Addr::new(10, 0, 0, 0)), - ); - } - #[test] fn test_macaddr() { use super::MacAddr; diff --git a/common/src/api/internal/nexus.rs b/common/src/api/internal/nexus.rs index 20516e702b..de611262bf 100644 --- a/common/src/api/internal/nexus.rs +++ b/common/src/api/internal/nexus.rs @@ -6,7 +6,7 @@ use crate::api::external::{ ByteCount, DiskState, Generation, Hostname, InstanceCpuCount, - InstanceState, IpNet, SemverVersion, Vni, + InstanceState, SemverVersion, Vni, }; use chrono::{DateTime, Utc}; use omicron_uuid_kinds::DownstairsRegionKind; @@ -251,7 +251,7 @@ mod tests { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(tag = "type", content = "value", rename_all = "snake_case")] pub enum HostIdentifier { - Ip(IpNet), + Ip(oxnet::IpNet), Vpc(Vni), } diff --git a/common/src/api/internal/shared.rs b/common/src/api/internal/shared.rs index 9d9ff083e4..b0d3232eed 100644 --- a/common/src/api/internal/shared.rs +++ b/common/src/api/internal/shared.rs @@ -6,9 +6,10 @@ use crate::{ address::NUM_SOURCE_NAT_PORTS, - api::external::{self, BfdMode, ImportExportPolicy, IpNet, Name}, + api::external::{self, BfdMode, ImportExportPolicy, Name}, }; use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; +use oxnet::IpNet; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::{ @@ -53,7 +54,7 @@ pub struct NetworkInterface { pub name: external::Name, pub ip: IpAddr, pub mac: external::MacAddr, - pub subnet: external::IpNet, + pub subnet: IpNet, pub vni: external::Vni, pub primary: bool, pub slot: u8, @@ -527,9 +528,9 @@ impl TryFrom> for AllowedSourceIps { } } -impl TryFrom<&[IpNetwork]> for AllowedSourceIps { +impl TryFrom<&[ipnetwork::IpNetwork]> for AllowedSourceIps { type Error = &'static str; - fn try_from(list: &[IpNetwork]) -> Result { + fn try_from(list: &[ipnetwork::IpNetwork]) -> Result { IpAllowList::try_from(list).map(Self::List) } } @@ -580,45 +581,43 @@ impl TryFrom> for IpAllowList { } } -impl TryFrom<&[IpNetwork]> for IpAllowList { +impl TryFrom<&[ipnetwork::IpNetwork]> for IpAllowList { type Error = &'static str; - fn try_from(list: &[IpNetwork]) -> Result { + + fn try_from(list: &[ipnetwork::IpNetwork]) -> Result { if list.is_empty() { return Err("IP allowlist must not be empty"); } - Ok(Self(list.iter().copied().map(Into::into).collect())) + Ok(Self(list.into_iter().map(|net| (*net).into()).collect())) } } #[cfg(test)] mod tests { - use crate::api::{ - external::{IpNet, Ipv4Net, Ipv6Net}, - internal::shared::AllowedSourceIps, - }; - use ipnetwork::{Ipv4Network, Ipv6Network}; + use crate::api::internal::shared::AllowedSourceIps; + use oxnet::{IpNet, Ipv4Net, Ipv6Net}; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] fn test_deserialize_allowed_source_ips() { let parsed: AllowedSourceIps = serde_json::from_str( - r#"{"allow":"list","ips":["127.0.0.1","10.0.0.0/24","fd00::1/64"]}"#, + r#"{"allow":"list","ips":["127.0.0.1/32","10.0.0.0/24","fd00::1/64"]}"#, ) .unwrap(); assert_eq!( parsed, AllowedSourceIps::try_from(vec![ - IpNet::V4(Ipv4Net::single(Ipv4Addr::LOCALHOST)), - IpNet::V4(Ipv4Net( - Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 24).unwrap() - )), - IpNet::V6(Ipv6Net( - Ipv6Network::new( + Ipv4Net::host_net(Ipv4Addr::LOCALHOST).into(), + IpNet::V4( + Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0), 24).unwrap() + ), + IpNet::V6( + Ipv6Net::new( Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), 64 ) .unwrap() - )), + ), ]) .unwrap() ); diff --git a/illumos-utils/Cargo.toml b/illumos-utils/Cargo.toml index fa50dd2822..3d17745b7e 100644 --- a/illumos-utils/Cargo.toml +++ b/illumos-utils/Cargo.toml @@ -25,6 +25,7 @@ omicron-common.workspace = true omicron-uuid-kinds.workspace = true oxide-vpc.workspace = true oxlog.workspace = true +oxnet.workspace = true schemars.workspace = true serde.workspace = true slog.workspace = true diff --git a/illumos-utils/src/opte/firewall_rules.rs b/illumos-utils/src/opte/firewall_rules.rs index 02882a226b..1df0e7421a 100644 --- a/illumos-utils/src/opte/firewall_rules.rs +++ b/illumos-utils/src/opte/firewall_rules.rs @@ -7,7 +7,6 @@ use crate::opte::params::VpcFirewallRule; use crate::opte::Vni; use macaddr::MacAddr6; -use omicron_common::api::external::IpNet; use omicron_common::api::external::VpcFirewallRuleAction; use omicron_common::api::external::VpcFirewallRuleDirection; use omicron_common::api::external::VpcFirewallRuleProtocol; @@ -27,6 +26,7 @@ use oxide_vpc::api::Ipv6PrefixLen; use oxide_vpc::api::Ports; use oxide_vpc::api::ProtoFilter; use oxide_vpc::api::Protocol; +use oxnet::IpNet; trait FromVpcFirewallRule { fn action(&self) -> FirewallAction; @@ -65,26 +65,22 @@ impl FromVpcFirewallRule for VpcFirewallRule { Some(ref hosts) if !hosts.is_empty() => hosts .iter() .map(|host| match host { - HostIdentifier::Ip(IpNet::V4(net)) - if net.prefix() == 32 => - { - Address::Ip(IpAddr::Ip4(net.ip().into())) + HostIdentifier::Ip(IpNet::V4(net)) if net.is_host_net() => { + Address::Ip(IpAddr::Ip4(net.addr().into())) } HostIdentifier::Ip(IpNet::V4(net)) => { Address::Subnet(IpCidr::Ip4(Ipv4Cidr::new( - net.ip().into(), - Ipv4PrefixLen::new(net.prefix()).unwrap(), + net.addr().into(), + Ipv4PrefixLen::new(net.width()).unwrap(), ))) } - HostIdentifier::Ip(IpNet::V6(net)) - if net.prefix() == 128 => - { - Address::Ip(IpAddr::Ip6(net.ip().into())) + HostIdentifier::Ip(IpNet::V6(net)) if net.is_host_net() => { + Address::Ip(IpAddr::Ip6(net.addr().into())) } HostIdentifier::Ip(IpNet::V6(net)) => { Address::Subnet(IpCidr::Ip6(Ipv6Cidr::new( - net.ip().into(), - Ipv6PrefixLen::new(net.prefix()).unwrap(), + net.addr().into(), + Ipv6PrefixLen::new(net.width()).unwrap(), ))) } HostIdentifier::Vpc(vni) => { diff --git a/internal-dns/src/resolver.rs b/internal-dns/src/resolver.rs index a7796f559a..670b4b420c 100644 --- a/internal-dns/src/resolver.rs +++ b/internal-dns/src/resolver.rs @@ -118,7 +118,7 @@ impl Resolver { .get_dns_subnets() .into_iter() .map(|dns_subnet| { - let ip_addr = IpAddr::V6(dns_subnet.dns_address().ip()); + let ip_addr = IpAddr::V6(dns_subnet.dns_address().addr()); SocketAddr::new(ip_addr, DNS_PORT) }) .collect() diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index 9793c32bf8..0b0bd097bc 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -51,6 +51,7 @@ once_cell.workspace = true openssl.workspace = true oximeter-client.workspace = true oximeter-db.workspace = true +oxnet.workspace = true parse-display.workspace = true paste.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. diff --git a/nexus/db-model/Cargo.toml b/nexus/db-model/Cargo.toml index 1118b7c9bd..a7b6cd9de1 100644 --- a/nexus/db-model/Cargo.toml +++ b/nexus/db-model/Cargo.toml @@ -22,6 +22,7 @@ macaddr.workspace = true newtype_derive.workspace = true omicron-uuid-kinds.workspace = true once_cell.workspace = true +oxnet.workspace = true parse-display.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. pq-sys = "*" diff --git a/nexus/db-model/src/ipv4_nat_entry.rs b/nexus/db-model/src/ipv4_nat_entry.rs index c3763346c6..4ff1ee9171 100644 --- a/nexus/db-model/src/ipv4_nat_entry.rs +++ b/nexus/db-model/src/ipv4_nat_entry.rs @@ -81,10 +81,10 @@ pub struct Ipv4NatEntryView { impl From for Ipv4NatEntryView { fn from(value: Ipv4NatChange) -> Self { Self { - external_address: value.external_address.ip(), + external_address: value.external_address.addr(), first_port: value.first_port.into(), last_port: value.last_port.into(), - sled_address: value.sled_address.ip(), + sled_address: value.sled_address.addr(), vni: value.vni.0, mac: *value.mac, gen: value.version, diff --git a/nexus/db-model/src/ipv4net.rs b/nexus/db-model/src/ipv4net.rs index eaf8a6eed8..b2cf6ffefa 100644 --- a/nexus/db-model/src/ipv4net.rs +++ b/nexus/db-model/src/ipv4net.rs @@ -10,7 +10,6 @@ use diesel::serialize::{self, ToSql}; use diesel::sql_types; use ipnetwork::IpNetwork; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; -use omicron_common::api::external; use serde::Deserialize; use serde::Serialize; use std::net::Ipv4Addr; @@ -27,10 +26,10 @@ use std::net::Ipv4Addr; Deserialize, )] #[diesel(sql_type = sql_types::Inet)] -pub struct Ipv4Net(pub external::Ipv4Net); +pub struct Ipv4Net(pub oxnet::Ipv4Net); -NewtypeFrom! { () pub struct Ipv4Net(external::Ipv4Net); } -NewtypeDeref! { () pub struct Ipv4Net(external::Ipv4Net); } +NewtypeFrom! { () pub struct Ipv4Net(oxnet::Ipv4Net); } +NewtypeDeref! { () pub struct Ipv4Net(oxnet::Ipv4Net); } impl Ipv4Net { /// Check if an address is a valid user-requestable address for this subnet @@ -41,19 +40,19 @@ impl Ipv4Net { if !self.contains(addr) { return Err(RequestAddressError::OutsideSubnet( addr.into(), - self.0 .0.into(), + oxnet::IpNet::from(self.0).into(), )); } // Only the first N addresses are reserved if self - .iter() + .addr_iter() .take(NUM_INITIAL_RESERVED_IP_ADDRESSES) .any(|this| this == addr) { return Err(RequestAddressError::Reserved); } // Last address in the subnet is reserved - if addr == self.broadcast() { + if addr == self.broadcast().expect("narrower subnet than expected") { return Err(RequestAddressError::Broadcast); } @@ -67,7 +66,7 @@ impl ToSql for Ipv4Net { out: &mut serialize::Output<'a, '_, Pg>, ) -> serialize::Result { >::to_sql( - &IpNetwork::V4(*self.0), + &IpNetwork::V4(self.0.into()), &mut out.reborrow(), ) } @@ -81,7 +80,7 @@ where fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { let inet = IpNetwork::from_sql(bytes)?; match inet { - IpNetwork::V4(net) => Ok(Ipv4Net(external::Ipv4Net(net))), + IpNetwork::V4(net) => Ok(Ipv4Net(net.into())), _ => Err("Expected IPV4".into()), } } diff --git a/nexus/db-model/src/ipv6net.rs b/nexus/db-model/src/ipv6net.rs index d516b67ed9..adcf732f42 100644 --- a/nexus/db-model/src/ipv6net.rs +++ b/nexus/db-model/src/ipv6net.rs @@ -9,7 +9,6 @@ use diesel::serialize::{self, ToSql}; use diesel::sql_types; use ipnetwork::IpNetwork; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; -use omicron_common::api::external; use rand::{rngs::StdRng, SeedableRng}; use serde::Deserialize; use serde::Serialize; @@ -29,10 +28,10 @@ use crate::RequestAddressError; Deserialize, )] #[diesel(sql_type = sql_types::Inet)] -pub struct Ipv6Net(pub external::Ipv6Net); +pub struct Ipv6Net(pub oxnet::Ipv6Net); -NewtypeFrom! { () pub struct Ipv6Net(external::Ipv6Net); } -NewtypeDeref! { () pub struct Ipv6Net(external::Ipv6Net); } +NewtypeFrom! { () pub struct Ipv6Net(oxnet::Ipv6Net); } +NewtypeDeref! { () pub struct Ipv6Net(oxnet::Ipv6Net); } impl Ipv6Net { /// Generate a random subnetwork from this one, of the given prefix length. @@ -48,10 +47,10 @@ impl Ipv6Net { use rand::RngCore; const MAX_IPV6_SUBNET_PREFIX: u8 = 128; - if prefix < self.prefix() || prefix > MAX_IPV6_SUBNET_PREFIX { + if prefix < self.width() || prefix > MAX_IPV6_SUBNET_PREFIX { return None; } - if prefix == self.prefix() { + if prefix == self.width() { return Some(*self); } @@ -72,17 +71,17 @@ impl Ipv6Net { let full_mask = !(u128::MAX >> prefix); // Get the existing network address and mask. - let network = u128::from_be_bytes(self.network().octets()); - let network_mask = u128::from_be_bytes(self.mask().octets()); + let network = u128::from(self.prefix()); + let network_mask = u128::from(self.mask_addr()); // Take random bits _only_ where the new mask is set. let random_mask = full_mask ^ network_mask; let out = (network & network_mask) | (random & random_mask); - let addr = std::net::Ipv6Addr::from(out.to_be_bytes()); - let net = ipnetwork::Ipv6Network::new(addr, prefix) + let addr = std::net::Ipv6Addr::from(out); + let net = oxnet::Ipv6Net::new(addr, prefix) .expect("Failed to create random subnet"); - Some(Self(external::Ipv6Net(net))) + Some(Self(net)) } /// Check if an address is a valid user-requestable address for this subnet @@ -93,7 +92,7 @@ impl Ipv6Net { if !self.contains(addr) { return Err(RequestAddressError::OutsideSubnet( addr.into(), - self.0 .0.into(), + oxnet::IpNet::from(self.0).into(), )); } // Only the first N addresses are reserved @@ -114,7 +113,7 @@ impl ToSql for Ipv6Net { out: &mut serialize::Output<'a, '_, Pg>, ) -> serialize::Result { >::to_sql( - &IpNetwork::V6(self.0 .0), + &IpNetwork::V6(self.0.into()), &mut out.reborrow(), ) } @@ -128,7 +127,7 @@ where fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { let inet = IpNetwork::from_sql(bytes)?; match inet { - IpNetwork::V6(net) => Ok(Ipv6Net(external::Ipv6Net(net))), + IpNetwork::V6(net) => Ok(Ipv6Net(net.into())), _ => Err("Expected IPV6".into()), } } diff --git a/nexus/db-model/src/lib.rs b/nexus/db-model/src/lib.rs index bd16719633..c57836a567 100644 --- a/nexus/db-model/src/lib.rs +++ b/nexus/db-model/src/lib.rs @@ -437,12 +437,10 @@ mod tests { use crate::RequestAddressError; use super::VpcSubnet; - use ipnetwork::Ipv4Network; - use ipnetwork::Ipv6Network; use omicron_common::api::external::IdentityMetadataCreateParams; - use omicron_common::api::external::IpNet; - use omicron_common::api::external::Ipv4Net; - use omicron_common::api::external::Ipv6Net; + use oxnet::IpNet; + use oxnet::Ipv4Net; + use oxnet::Ipv6Net; use std::net::IpAddr; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -450,9 +448,8 @@ mod tests { #[test] fn test_vpc_subnet_check_requestable_addr() { - let ipv4_block = - Ipv4Net("192.168.0.0/16".parse::().unwrap()); - let ipv6_block = Ipv6Net("fd00::/48".parse::().unwrap()); + let ipv4_block = "192.168.0.0/16".parse::().unwrap(); + let ipv6_block = "fd00::/48".parse::().unwrap(); let identity = IdentityMetadataCreateParams { name: "net-test-vpc".parse().unwrap(), description: "A test VPC".parse().unwrap(), @@ -511,9 +508,7 @@ mod tests { #[test] fn test_ipv6_net_random_subnet() { - let base = super::Ipv6Net(Ipv6Net( - "fd00::/48".parse::().unwrap(), - )); + let base = super::Ipv6Net("fd00::/48".parse::().unwrap()); assert!( base.random_subnet(8).is_none(), "random_subnet() should fail when prefix is less than the base prefix" @@ -524,11 +519,11 @@ mod tests { ); let subnet = base.random_subnet(64).unwrap(); assert_eq!( - subnet.prefix(), + subnet.width(), 64, "random_subnet() returned an incorrect prefix" ); - let octets = subnet.network().octets(); + let octets = subnet.prefix().octets(); const EXPECTED_RANDOM_BYTES: [u8; 8] = [253, 0, 0, 0, 0, 0, 111, 127]; assert_eq!(octets[..8], EXPECTED_RANDOM_BYTES); assert!( @@ -536,15 +531,15 @@ mod tests { "Host address portion should be 0" ); assert!( - base.is_supernet_of(subnet.0 .0), + base.is_supernet_of(&subnet.0), "random_subnet should generate an actual subnet" ); - assert_eq!(base.random_subnet(base.prefix()), Some(base)); + assert_eq!(base.random_subnet(base.width()), Some(base)); } #[test] fn test_ip_subnet_check_requestable_address() { - let subnet = super::Ipv4Net(Ipv4Net("192.168.0.0/16".parse().unwrap())); + let subnet = super::Ipv4Net("192.168.0.0/16".parse().unwrap()); subnet.check_requestable_addr("192.168.0.10".parse().unwrap()).unwrap(); subnet.check_requestable_addr("192.168.1.0".parse().unwrap()).unwrap(); let addr = "192.178.0.10".parse().unwrap(); @@ -569,7 +564,7 @@ mod tests { Err(RequestAddressError::Broadcast) ); - let subnet = super::Ipv6Net(Ipv6Net("fd00::/64".parse().unwrap())); + let subnet = super::Ipv6Net("fd00::/64".parse().unwrap()); subnet.check_requestable_addr("fd00::a".parse().unwrap()).unwrap(); assert_eq!( subnet.check_requestable_addr("fd00::1".parse().unwrap()), diff --git a/nexus/db-model/src/network_interface.rs b/nexus/db-model/src/network_interface.rs index 8520afdb76..95bb8bb7f2 100644 --- a/nexus/db-model/src/network_interface.rs +++ b/nexus/db-model/src/network_interface.rs @@ -73,7 +73,7 @@ pub struct NetworkInterface { impl NetworkInterface { pub fn into_internal( self, - subnet: external::IpNet, + subnet: oxnet::IpNet, ) -> internal::shared::NetworkInterface { internal::shared::NetworkInterface { id: self.id(), diff --git a/nexus/db-model/src/vpc.rs b/nexus/db-model/src/vpc.rs index 8a4dc0e349..88879a0436 100644 --- a/nexus/db-model/src/vpc.rs +++ b/nexus/db-model/src/vpc.rs @@ -14,6 +14,7 @@ use nexus_types::external_api::params; use nexus_types::external_api::views; use nexus_types::identity::Resource; use omicron_common::api::external; +use omicron_common::api::external::Ipv6NetExt; use serde::Deserialize; use serde::Serialize; use uuid::Uuid; @@ -83,22 +84,20 @@ impl IncompleteVpc { params: params::VpcCreate, ) -> Result { let identity = VpcIdentity::new(vpc_id, params.identity); - let ipv6_prefix = IpNetwork::from( - match params.ipv6_prefix { - None => defaults::random_vpc_ipv6_prefix(), - Some(prefix) => { - if prefix.is_vpc_prefix() { - Ok(prefix) - } else { - Err(external::Error::invalid_request( - "VPC IPv6 address prefixes must be in the \ + let ipv6_prefix = oxnet::IpNet::from(match params.ipv6_prefix { + None => defaults::random_vpc_ipv6_prefix(), + Some(prefix) => { + if prefix.is_vpc_prefix() { + Ok(prefix) + } else { + Err(external::Error::invalid_request( + "VPC IPv6 address prefixes must be in the \ Unique Local Address range `fd00::/48` (RFD 4193)", - )) - } + )) } - }? - .0, - ); + } + }?) + .into(); Ok(Self { identity, project_id, diff --git a/nexus/db-model/src/vpc_subnet.rs b/nexus/db-model/src/vpc_subnet.rs index 407c933ef2..f3c90a908e 100644 --- a/nexus/db-model/src/vpc_subnet.rs +++ b/nexus/db-model/src/vpc_subnet.rs @@ -50,8 +50,8 @@ impl VpcSubnet { subnet_id: Uuid, vpc_id: Uuid, identity: external::IdentityMetadataCreateParams, - ipv4_block: external::Ipv4Net, - ipv6_block: external::Ipv6Net, + ipv4_block: oxnet::Ipv4Net, + ipv6_block: oxnet::Ipv6Net, ) -> Self { let identity = VpcSubnetIdentity::new(subnet_id, identity); Self { diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index 0754f7389f..135f2fcdf7 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -33,6 +33,7 @@ newtype_derive.workspace = true once_cell.workspace = true openssl.workspace = true oso.workspace = true +oxnet.workspace = true paste.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. pq-sys = "*" diff --git a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs index fa3939b8ac..5b370f27a9 100644 --- a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs +++ b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs @@ -406,13 +406,11 @@ mod test { // Each change (creation / deletion) to the NAT table should increment the // version number of the row in the NAT table - let external_address = external::Ipv4Net( - ipnetwork::Ipv4Network::try_from("10.0.0.100").unwrap(), - ); + let external_address = + oxnet::Ipv4Net::host_net("10.0.0.100".parse().unwrap()); - let sled_address = external::Ipv6Net( - ipnetwork::Ipv6Network::try_from("fd00:1122:3344:104::1").unwrap(), - ); + let sled_address = + oxnet::Ipv6Net::host_net("fd00:1122:3344:104::1".parse().unwrap()); // Add a nat entry. let nat1 = Ipv4NatValues { @@ -565,13 +563,11 @@ mod test { // Each change (creation / deletion) to the NAT table should increment the // version number of the row in the NAT table - let external_address = external::Ipv4Net( - ipnetwork::Ipv4Network::try_from("10.0.0.100").unwrap(), - ); + let external_address = + oxnet::Ipv4Net::host_net("10.0.0.100".parse().unwrap()); - let sled_address = external::Ipv6Net( - ipnetwork::Ipv6Network::try_from("fd00:1122:3344:104::1").unwrap(), - ); + let sled_address = + oxnet::Ipv6Net::host_net("fd00:1122:3344:104::1".parse().unwrap()); // Add a nat entry. let nat1 = Ipv4NatValues { @@ -711,13 +707,11 @@ mod test { // 1. an entry should be deleted during the next sync // 2. an entry that should be kept during the next sync - let external_address = external::Ipv4Net( - ipnetwork::Ipv4Network::try_from("10.0.0.100").unwrap(), - ); + let external_address = + oxnet::Ipv4Net::host_net("10.0.0.100".parse().unwrap()); - let sled_address = external::Ipv6Net( - ipnetwork::Ipv6Network::try_from("fd00:1122:3344:104::1").unwrap(), - ); + let sled_address = + oxnet::Ipv6Net::host_net("fd00:1122:3344:104::1".parse().unwrap()); // Add a nat entry. let nat1 = Ipv4NatValues { @@ -833,13 +827,12 @@ mod test { let addresses = (0..=255).map(|i| { let addr = Ipv4Addr::new(10, 0, 0, i); - let net = ipnetwork::Ipv4Network::new(addr, 32).unwrap(); - external::Ipv4Net(net) + let net = oxnet::Ipv4Net::new(addr, 32).unwrap(); + net }); - let sled_address = external::Ipv6Net( - ipnetwork::Ipv6Network::try_from("fd00:1122:3344:104::1").unwrap(), - ); + let sled_address = + oxnet::Ipv6Net::host_net("fd00:1122:3344:104::1".parse().unwrap()); let nat_entries = addresses.map(|external_address| { // build a bunch of nat entries @@ -908,7 +901,7 @@ mod test { .expect("did not find a deleted nat entry with a matching version number"); assert_eq!( - deleted_nat.external_address.ip(), + deleted_nat.external_address.addr(), change.external_address ); assert_eq!( @@ -917,7 +910,7 @@ mod test { ); assert_eq!(deleted_nat.last_port, change.last_port.into()); assert_eq!( - deleted_nat.sled_address.ip(), + deleted_nat.sled_address.addr(), change.sled_address ); assert_eq!(*deleted_nat.mac, change.mac); @@ -933,13 +926,13 @@ mod test { assert!(added_nat.version_removed.is_none()); assert_eq!( - added_nat.external_address.ip(), + added_nat.external_address.addr(), change.external_address ); assert_eq!(added_nat.first_port, change.first_port.into()); assert_eq!(added_nat.last_port, change.last_port.into()); assert_eq!( - added_nat.sled_address.ip(), + added_nat.sled_address.addr(), change.sled_address ); assert_eq!(*added_nat.mac, change.mac); diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 7c47489477..a69e91dff4 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -1592,8 +1592,8 @@ mod test { name: external::Name::try_from(String::from("name")).unwrap(), description: String::from("description"), }, - external::Ipv4Net("172.30.0.0/22".parse().unwrap()), - external::Ipv6Net("fd00::/64".parse().unwrap()), + "172.30.0.0/22".parse().unwrap(), + "fd00::/64".parse().unwrap(), ); let values = FilterConflictingVpcSubnetRangesQuery::new(subnet); let query = diff --git a/nexus/db-queries/src/db/datastore/network_interface.rs b/nexus/db-queries/src/db/datastore/network_interface.rs index f552e845c6..af3f832e35 100644 --- a/nexus/db-queries/src/db/datastore/network_interface.rs +++ b/nexus/db-queries/src/db/datastore/network_interface.rs @@ -68,9 +68,9 @@ impl From for omicron_common::api::internal::shared::NetworkInterface { nic: NicInfo, ) -> omicron_common::api::internal::shared::NetworkInterface { let ip_subnet = if nic.ip.is_ipv4() { - external::IpNet::V4(nic.ipv4_block.0) + oxnet::IpNet::V4(nic.ipv4_block.0) } else { - external::IpNet::V6(nic.ipv6_block.0) + oxnet::IpNet::V6(nic.ipv6_block.0) }; let kind = match nic.kind { NetworkInterfaceKind::Instance => { @@ -894,8 +894,7 @@ mod tests { // Insert 10 Nexus NICs let ip_range = NEXUS_OPTE_IPV4_SUBNET - .0 - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES) .take(10); let mut macs = external::MacAddr::iter_system(); diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index 04901c7785..b8275b56d4 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -1038,6 +1038,7 @@ mod test { use omicron_uuid_kinds::{ExternalIpUuid, OmicronZoneUuid}; use omicron_uuid_kinds::{GenericUuid, ZpoolUuid}; use omicron_uuid_kinds::{SledUuid, TypedUuid}; + use oxnet::IpNet; use sled_agent_client::types::OmicronZoneDataset; use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; @@ -1334,22 +1335,22 @@ mod test { let external_dns_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)); let external_dns_pip = DNS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let external_dns_id = OmicronZoneUuid::new_v4(); let nexus_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 6)); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let nexus_id = OmicronZoneUuid::new_v4(); let ntp1_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 5)); let ntp1_pip = NTP_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let ntp1_id = OmicronZoneUuid::new_v4(); let ntp2_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 5)); let ntp2_pip = NTP_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 2) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 2) .unwrap(); let ntp2_id = OmicronZoneUuid::new_v4(); let ntp3_id = OmicronZoneUuid::new_v4(); @@ -1381,10 +1382,7 @@ mod test { name: "external-dns".parse().unwrap(), ip: external_dns_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **DNS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from(*DNS_OPTE_IPV4_SUBNET), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1410,10 +1408,7 @@ mod test { name: "ntp1".parse().unwrap(), ip: ntp1_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NTP_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from(*NTP_OPTE_IPV4_SUBNET), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1457,10 +1452,9 @@ mod test { name: "nexus".parse().unwrap(), ip: nexus_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NEXUS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from( + *NEXUS_OPTE_IPV4_SUBNET, + ), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1486,10 +1480,7 @@ mod test { name: "ntp2".parse().unwrap(), ip: ntp2_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NTP_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from(*NTP_OPTE_IPV4_SUBNET), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1677,10 +1668,10 @@ mod test { let nexus_id1 = OmicronZoneUuid::new_v4(); let nexus_id2 = OmicronZoneUuid::new_v4(); let nexus_pip1 = NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let nexus_pip2 = NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 2) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 2) .unwrap(); let mut macs = MacAddr::iter_system(); @@ -1711,10 +1702,9 @@ mod test { name: "nexus1".parse().unwrap(), ip: nexus_pip1.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NEXUS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from( + *NEXUS_OPTE_IPV4_SUBNET, + ), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1743,10 +1733,9 @@ mod test { name: "nexus2".parse().unwrap(), ip: nexus_pip2.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NEXUS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: oxnet::IpNet::from( + *NEXUS_OPTE_IPV4_SUBNET, + ), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -1951,7 +1940,7 @@ mod test { let nexus_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let nexus_id = OmicronZoneUuid::new_v4(); let mut macs = MacAddr::iter_system(); @@ -1981,10 +1970,7 @@ mod test { name: "nexus".parse().unwrap(), ip: nexus_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NEXUS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from(*NEXUS_OPTE_IPV4_SUBNET), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -2052,11 +2038,11 @@ mod test { // Request two services which happen to be using the same IP address. let external_dns_id = OmicronZoneUuid::new_v4(); let external_dns_pip = DNS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let nexus_id = OmicronZoneUuid::new_v4(); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap(); let mut macs = MacAddr::iter_system(); @@ -2086,10 +2072,7 @@ mod test { name: "external-dns".parse().unwrap(), ip: external_dns_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **DNS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from(*DNS_OPTE_IPV4_SUBNET), vni: Vni::SERVICES_VNI, primary: true, slot: 0, @@ -2118,10 +2101,9 @@ mod test { name: "nexus".parse().unwrap(), ip: nexus_pip.into(), mac: macs.next().unwrap(), - subnet: IpNetwork::from( - **NEXUS_OPTE_IPV4_SUBNET, - ) - .into(), + subnet: IpNet::from( + *NEXUS_OPTE_IPV4_SUBNET, + ), vni: Vni::SERVICES_VNI, primary: true, slot: 0, diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 3f3717a9c4..edb16e95ac 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -529,7 +529,7 @@ impl DataStore { let (block, rsvd_block) = crate::db::datastore::address_lot::try_reserve_block( address_lot_id, - address.address.ip().into(), + address.address.addr().into(), // TODO: Should we allow anycast addresses for switch_ports? // anycast false, diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 91843abf2e..98af47f0e2 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -1168,8 +1168,8 @@ impl DataStore { let mut result = BTreeMap::new(); for subnet in subnets { let entry = result.entry(subnet.name).or_insert_with(Vec::new); - entry.push(IpNetwork::V4(subnet.ipv4_block.0 .0)); - entry.push(IpNetwork::V6(subnet.ipv6_block.0 .0)); + entry.push(IpNetwork::V4(subnet.ipv4_block.0.into())); + entry.push(IpNetwork::V6(subnet.ipv6_block.0.into())); } Ok(result) } diff --git a/nexus/db-queries/src/db/queries/network_interface.rs b/nexus/db-queries/src/db/queries/network_interface.rs index a9cea9826a..69c1827b6d 100644 --- a/nexus/db-queries/src/db/queries/network_interface.rs +++ b/nexus/db-queries/src/db/queries/network_interface.rs @@ -1061,7 +1061,7 @@ impl InsertQuery { let next_mac_subquery = NextMacAddress::new(interface.subnet.vpc_id, interface.kind); let next_ipv4_address_subquery = NextIpv4Address::new( - interface.subnet.ipv4_block.0 .0, + interface.subnet.ipv4_block.0.into(), interface.subnet.identity.id, ); let next_slot_subquery = NextNicSlot::new(interface.parent_id); @@ -1859,8 +1859,6 @@ mod tests { use crate::db::queries::network_interface::NextMacShifts; use async_bb8_diesel::AsyncRunQueryDsl; use dropshot::test_util::LogContext; - use ipnetwork::Ipv4Network; - use ipnetwork::Ipv6Network; use model::NetworkInterfaceKind; use nexus_test_utils::db::test_setup_database; use nexus_types::external_api::params; @@ -1871,11 +1869,11 @@ mod tests { use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceCpuCount; - use omicron_common::api::external::Ipv4Net; - use omicron_common::api::external::Ipv6Net; use omicron_common::api::external::MacAddr; use omicron_test_utils::dev; use omicron_test_utils::dev::db::CockroachInstance; + use oxnet::Ipv4Net; + use oxnet::Ipv6Net; use std::collections::HashSet; use std::convert::TryInto; use std::net::IpAddr; @@ -1995,25 +1993,13 @@ mod tests { let vpc_id = Uuid::new_v4(); let mut subnets = Vec::with_capacity(n_subnets as _); for i in 0..n_subnets { - let ipv4net = Ipv4Net( - Ipv4Network::new(Ipv4Addr::new(172, 30, 0, i), 28).unwrap(), - ); - let ipv6net = Ipv6Net( - Ipv6Network::new( - Ipv6Addr::new( - 0xfd12, - 0x3456, - 0x7890, - i.into(), - 0, - 0, - 0, - 0, - ), - 64, - ) - .unwrap(), - ); + let ipv4net = + Ipv4Net::new(Ipv4Addr::new(172, 30, 0, i), 28).unwrap(); + let ipv6net = Ipv6Net::new( + Ipv6Addr::new(0xfd12, 0x3456, 0x7890, i.into(), 0, 0, 0, 0), + 64, + ) + .unwrap(); let subnet = VpcSubnet::new( Uuid::new_v4(), vpc_id, @@ -2033,9 +2019,11 @@ mod tests { self.subnets .iter() .map(|subnet| { - subnet.ipv4_block.size() as usize - - NUM_INITIAL_RESERVED_IP_ADDRESSES - - 1 + let size_minus_1 = match subnet.ipv4_block.size() { + Some(n) => n - 1, + None => u32::MAX, + } as usize; + size_minus_1 - NUM_INITIAL_RESERVED_IP_ADDRESSES }) .collect() } @@ -2148,7 +2136,7 @@ mod tests { let service_id = Uuid::new_v4(); let ip = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap(); let interface = IncompleteNetworkInterface::new_service( @@ -2316,7 +2304,7 @@ mod tests { TestContext::new("test_insert_sequential_ip_allocation", 2).await; let addresses = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES); for (i, expected_address) in addresses.take(2).enumerate() { @@ -2412,7 +2400,7 @@ mod tests { let service_id = Uuid::new_v4(); let ip = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap(); let mac = MacAddr::random_system(); @@ -2447,7 +2435,7 @@ mod tests { let mut used_macs = HashSet::new(); let mut ips = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES); for slot in 0..u8::try_from(MAX_NICS_PER_INSTANCE).unwrap() { let service_id = Uuid::new_v4(); @@ -2487,7 +2475,7 @@ mod tests { let mut ips = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES); // Insert a service NIC @@ -2547,12 +2535,12 @@ mod tests { let ip0 = context.net1.subnets[0] .ipv4_block - .iter() + .addr_iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap(); let ip1 = context.net1.subnets[1] .ipv4_block - .iter() + .addr_iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap(); diff --git a/nexus/db-queries/src/db/queries/vpc_subnet.rs b/nexus/db-queries/src/db/queries/vpc_subnet.rs index 9ddec32080..72f2771a1e 100644 --- a/nexus/db-queries/src/db/queries/vpc_subnet.rs +++ b/nexus/db-queries/src/db/queries/vpc_subnet.rs @@ -43,7 +43,9 @@ impl SubnetError { DatabaseErrorKind::NotNullViolation, ref info, ) if info.message() == IPV4_OVERLAP_ERROR_MESSAGE => { - SubnetError::OverlappingIpRange(subnet.ipv4_block.0 .0.into()) + SubnetError::OverlappingIpRange(ipnetwork::IpNetwork::V4( + subnet.ipv4_block.0.into(), + )) } // Attempt to insert overlapping IPv6 subnet @@ -51,7 +53,9 @@ impl SubnetError { DatabaseErrorKind::NotNullViolation, ref info, ) if info.message() == IPV6_OVERLAP_ERROR_MESSAGE => { - SubnetError::OverlappingIpRange(subnet.ipv6_block.0 .0.into()) + SubnetError::OverlappingIpRange(ipnetwork::IpNetwork::V6( + subnet.ipv6_block.0.into(), + )) } // Conflicting name for the subnet within a VPC @@ -233,8 +237,10 @@ pub struct FilterConflictingVpcSubnetRangesQuery { impl FilterConflictingVpcSubnetRangesQuery { pub fn new(subnet: VpcSubnet) -> Self { - let ipv4_block = ipnetwork::IpNetwork::from(subnet.ipv4_block.0 .0); - let ipv6_block = ipnetwork::IpNetwork::from(subnet.ipv6_block.0 .0); + let ipv4_block = + ipnetwork::Ipv4Network::from(subnet.ipv4_block.0).into(); + let ipv6_block = + ipnetwork::Ipv6Network::from(subnet.ipv6_block.0).into(); Self { subnet, ipv4_block, ipv6_block } } } @@ -394,8 +400,6 @@ mod test { use ipnetwork::IpNetwork; use nexus_test_utils::db::test_setup_database; use omicron_common::api::external::IdentityMetadataCreateParams; - use omicron_common::api::external::Ipv4Net; - use omicron_common::api::external::Ipv6Net; use omicron_common::api::external::Name; use omicron_test_utils::dev; use std::convert::TryInto; @@ -409,10 +413,10 @@ mod test { name: name.clone(), description: description.to_string(), }; - let ipv4_block = Ipv4Net("172.30.0.0/22".parse().unwrap()); - let other_ipv4_block = Ipv4Net("172.31.0.0/22".parse().unwrap()); - let ipv6_block = Ipv6Net("fd12:3456:7890::/64".parse().unwrap()); - let other_ipv6_block = Ipv6Net("fd00::/64".parse().unwrap()); + let ipv4_block = "172.30.0.0/22".parse().unwrap(); + let other_ipv4_block = "172.31.0.0/22".parse().unwrap(); + let ipv6_block = "fd12:3456:7890::/64".parse().unwrap(); + let other_ipv6_block = "fd00::/64".parse().unwrap(); let name = "a-name".to_string().try_into().unwrap(); let other_name = "b-name".to_string().try_into().unwrap(); let description = "some description".to_string(); @@ -491,7 +495,7 @@ mod test { .expect_err("Should not be able to insert VPC Subnet with overlapping IPv6 range"); assert_eq!( err, - SubnetError::OverlappingIpRange(IpNetwork::from(ipv6_block.0)), + SubnetError::OverlappingIpRange(ipnetwork::IpNetwork::from(oxnet::IpNet::from(ipv6_block))), "SubnetError variant should include the exact IP range that overlaps" ); let new_row = VpcSubnet::new( @@ -507,7 +511,7 @@ mod test { .expect_err("Should not be able to insert VPC Subnet with overlapping IPv4 range"); assert_eq!( err, - SubnetError::OverlappingIpRange(IpNetwork::from(ipv4_block.0)), + SubnetError::OverlappingIpRange(ipnetwork::IpNetwork::from(oxnet::IpNet::from(ipv4_block))), "SubnetError variant should include the exact IP range that overlaps" ); diff --git a/nexus/defaults/Cargo.toml b/nexus/defaults/Cargo.toml index d6f8e54220..1d941deb8e 100644 --- a/nexus/defaults/Cargo.toml +++ b/nexus/defaults/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] ipnetwork.workspace = true once_cell.workspace = true +oxnet.workspace = true rand.workspace = true serde_json.workspace = true diff --git a/nexus/defaults/src/lib.rs b/nexus/defaults/src/lib.rs index dd08b4e4ab..32def47b9e 100644 --- a/nexus/defaults/src/lib.rs +++ b/nexus/defaults/src/lib.rs @@ -4,12 +4,10 @@ //! Default values for data in the Nexus API, when not provided explicitly in a request. -use ipnetwork::Ipv4Network; -use ipnetwork::Ipv6Network; use omicron_common::api::external; -use omicron_common::api::external::Ipv4Net; -use omicron_common::api::external::Ipv6Net; use once_cell::sync::Lazy; +use oxnet::Ipv4Net; +use oxnet::Ipv6Net; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -20,10 +18,8 @@ pub const DEFAULT_PRIMARY_NIC_NAME: &str = "net0"; /// The default IPv4 subnet range assigned to the default VPC Subnet, when /// the VPC is created, if one is not provided in the request. See /// for details. -pub static DEFAULT_VPC_SUBNET_IPV4_BLOCK: Lazy = - Lazy::new(|| { - Ipv4Net(Ipv4Network::new(Ipv4Addr::new(172, 30, 0, 0), 22).unwrap()) - }); +pub static DEFAULT_VPC_SUBNET_IPV4_BLOCK: Lazy = + Lazy::new(|| Ipv4Net::new(Ipv4Addr::new(172, 30, 0, 0), 22).unwrap()); pub static DEFAULT_FIREWALL_RULES: Lazy = Lazy::new(|| { @@ -73,24 +69,24 @@ pub fn random_vpc_ipv6_prefix() -> Result { "Unable to allocate random IPv6 address range", ) })?; - Ok(Ipv6Net( - Ipv6Network::new( - Ipv6Addr::from(bytes), - Ipv6Net::VPC_IPV6_PREFIX_LENGTH, - ) - .unwrap(), - )) + Ok(Ipv6Net::new( + Ipv6Addr::from(bytes), + omicron_common::address::VPC_IPV6_PREFIX_LENGTH, + ) + .unwrap()) } #[cfg(test)] mod tests { + use omicron_common::api::external::Ipv6NetExt; + use super::*; #[test] fn test_random_vpc_ipv6_prefix() { let network = random_vpc_ipv6_prefix().unwrap(); assert!(network.is_vpc_prefix()); - let octets = network.network().octets(); + let octets = network.prefix().octets(); assert!(octets[6..].iter().all(|x| *x == 0)); } } diff --git a/nexus/networking/Cargo.toml b/nexus/networking/Cargo.toml index db163d5aa6..510fd6ca27 100644 --- a/nexus/networking/Cargo.toml +++ b/nexus/networking/Cargo.toml @@ -12,6 +12,7 @@ futures.workspace = true ipnetwork.workspace = true nexus-db-queries.workspace = true omicron-common.workspace = true +oxnet.workspace = true reqwest.workspace = true sled-agent-client.workspace = true slog.workspace = true diff --git a/nexus/networking/src/firewall_rules.rs b/nexus/networking/src/firewall_rules.rs index 623c545702..a656c673ca 100644 --- a/nexus/networking/src/firewall_rules.rs +++ b/nexus/networking/src/firewall_rules.rs @@ -19,10 +19,10 @@ use nexus_db_queries::db::DataStore; use omicron_common::api::external; use omicron_common::api::external::AllowedSourceIps; use omicron_common::api::external::Error; -use omicron_common::api::external::IpNet; use omicron_common::api::external::ListResultVec; use omicron_common::api::internal::nexus::HostIdentifier; use omicron_common::api::internal::shared::NetworkInterface; +use oxnet::IpNet; use slog::debug; use slog::error; use slog::info; @@ -353,7 +353,7 @@ pub async fn resolve_firewall_rules_for_sled_agent( .unwrap_or(&no_interfaces) { host_addrs.push( - HostIdentifier::Ip(IpNet::single( + HostIdentifier::Ip(IpNet::host_net( interface.ip, )) .into(), @@ -362,7 +362,7 @@ pub async fn resolve_firewall_rules_for_sled_agent( } external::VpcFirewallRuleHostFilter::Subnet(name) => { for subnet in subnet_networks - .get(&name) + .get(name) .unwrap_or(&no_networks) { host_addrs.push( @@ -373,7 +373,8 @@ pub async fn resolve_firewall_rules_for_sled_agent( } external::VpcFirewallRuleHostFilter::Ip(addr) => { host_addrs.push( - HostIdentifier::Ip(IpNet::single(*addr)).into(), + HostIdentifier::Ip(IpNet::host_net(*addr)) + .into(), ) } external::VpcFirewallRuleHostFilter::IpNet(net) => { @@ -381,7 +382,7 @@ pub async fn resolve_firewall_rules_for_sled_agent( } external::VpcFirewallRuleHostFilter::Vpc(name) => { for interface in vpc_interfaces - .get(&name) + .get(name) .unwrap_or(&no_interfaces) { host_addrs.push( diff --git a/nexus/reconfigurator/execution/Cargo.toml b/nexus/reconfigurator/execution/Cargo.toml index 137cde9255..34056b45a1 100644 --- a/nexus/reconfigurator/execution/Cargo.toml +++ b/nexus/reconfigurator/execution/Cargo.toml @@ -22,6 +22,7 @@ nexus-networking.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true +oxnet.workspace = true reqwest.workspace = true sled-agent-client.workspace = true slog.workspace = true diff --git a/nexus/reconfigurator/execution/src/external_networking.rs b/nexus/reconfigurator/execution/src/external_networking.rs index cff912c137..13cf601135 100644 --- a/nexus/reconfigurator/execution/src/external_networking.rs +++ b/nexus/reconfigurator/execution/src/external_networking.rs @@ -443,10 +443,10 @@ mod tests { use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_common::address::NTP_OPTE_IPV4_SUBNET; use omicron_common::address::NUM_SOURCE_NAT_PORTS; - use omicron_common::api::external::IpNet; use omicron_common::api::external::MacAddr; use omicron_common::api::external::Vni; use omicron_uuid_kinds::ExternalIpUuid; + use oxnet::IpNet; use std::net::IpAddr; use std::net::Ipv6Addr; use std::net::SocketAddr; @@ -491,7 +491,6 @@ mod tests { }, name: "test-nexus".parse().expect("bad name"), ip: NEXUS_OPTE_IPV4_SUBNET - .iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap() .into(), @@ -517,7 +516,6 @@ mod tests { }, name: "test-external-dns".parse().expect("bad name"), ip: DNS_OPTE_IPV4_SUBNET - .iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap() .into(), @@ -546,7 +544,6 @@ mod tests { }, name: "test-external-ntp".parse().expect("bad name"), ip: NTP_OPTE_IPV4_SUBNET - .iter() .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES) .unwrap() .into(), diff --git a/nexus/reconfigurator/planning/Cargo.toml b/nexus/reconfigurator/planning/Cargo.toml index ba935bdba0..7bbc9aa36b 100644 --- a/nexus/reconfigurator/planning/Cargo.toml +++ b/nexus/reconfigurator/planning/Cargo.toml @@ -20,6 +20,7 @@ nexus-inventory.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true +oxnet.workspace = true rand.workspace = true sled-agent-client.workspace = true slog.workspace = true diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 45aea75473..1efefb9817 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -526,7 +526,7 @@ impl<'a> BlueprintBuilder<'a> { // these are at known, fixed addresses relative to the AZ subnet // (which itself is a known-prefix parent subnet of the sled subnet). let dns_servers = - get_internal_dns_server_addresses(sled_subnet.net().network()); + get_internal_dns_server_addresses(sled_subnet.net().prefix()); // The list of boundary NTP servers is not necessarily stored // anywhere (unless there happens to be another internal NTP zone @@ -758,7 +758,7 @@ impl<'a> BlueprintBuilder<'a> { let sled_subnet = self.sled_resources(sled_id)?.subnet; let allocator = self.sled_ip_allocators.entry(sled_id).or_insert_with(|| { - let sled_subnet_addr = sled_subnet.net().network(); + let sled_subnet_addr = sled_subnet.net().prefix(); let minimum = sled_subnet_addr .saturating_add(u128::from(SLED_RESERVED_ADDRESSES)); let maximum = sled_subnet_addr diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/external_networking.rs b/nexus/reconfigurator/planning/src/blueprint_builder/external_networking.rs index b9100f518d..950ce89c43 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/external_networking.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/external_networking.rs @@ -16,8 +16,8 @@ use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_common::address::NEXUS_OPTE_IPV6_SUBNET; use omicron_common::address::NTP_OPTE_IPV4_SUBNET; use omicron_common::address::NTP_OPTE_IPV6_SUBNET; -use omicron_common::api::external::IpNet; use omicron_common::api::external::MacAddr; +use oxnet::IpNet; use std::collections::HashSet; use std::hash::Hash; use std::net::IpAddr; @@ -191,14 +191,12 @@ impl<'a> BuilderExternalNetworking<'a> { // of used resources we built above if needed. let nexus_v4_ips = AvailableIterator::new( NEXUS_OPTE_IPV4_SUBNET - .0 - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), existing_nexus_v4_ips, ); let nexus_v6_ips = AvailableIterator::new( NEXUS_OPTE_IPV6_SUBNET - .0 .iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), existing_nexus_v6_ips, diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt index 648c082c0f..fa61fa2758 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt @@ -196,12 +196,10 @@ ERRORS: ), ), subnet: V4( - Ipv4Net( - Ipv4Network { - addr: 172.30.2.0, - prefix: 24, - }, - ), + Ipv4Net { + addr: 172.30.2.0, + width: 24, + }, ), vni: Vni( 100, diff --git a/nexus/src/app/allow_list.rs b/nexus/src/app/allow_list.rs index 6b32f0c6f3..d25400a512 100644 --- a/nexus/src/app/allow_list.rs +++ b/nexus/src/app/allow_list.rs @@ -64,14 +64,14 @@ impl super::Nexus { let mut contains_remote = false; for entry in list.iter() { contains_remote |= entry.contains(remote_addr); - if entry.ip().is_unspecified() { + if entry.addr().is_unspecified() { return Err(Error::invalid_request( "Source IP allowlist may not contain the \ unspecified address. Use \"any\" to allow \ any source to connect to user-facing services.", )); } - if entry.prefix() == 0 { + if entry.width() == 0 { return Err(Error::invalid_request( "Source IP allowlist entries may not have \ a netmask of /0.", diff --git a/nexus/src/app/background/sync_service_zone_nat.rs b/nexus/src/app/background/sync_service_zone_nat.rs index d1bb9955d7..b0a4c8cef2 100644 --- a/nexus/src/app/background/sync_service_zone_nat.rs +++ b/nexus/src/app/background/sync_service_zone_nat.rs @@ -19,7 +19,6 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db::lookup::LookupPath; use nexus_db_queries::db::DataStore; use omicron_common::address::{MAX_PORT, MIN_PORT}; -use omicron_common::api::external; use omicron_uuid_kinds::GenericUuid; use serde_json::json; use sled_agent_client::types::OmicronZoneType; @@ -125,9 +124,7 @@ impl BackgroundTask for ServiceZoneNatTracker { } }; - let sled_address = external::Ipv6Net( - ipnetwork::Ipv6Network::new(*sled.ip, 128).unwrap(), - ); + let sled_address = oxnet::Ipv6Net::host_net(*sled.ip); let zones_config: sled_agent_client::types::OmicronZonesConfig = zones_found.zones; @@ -152,16 +149,14 @@ impl BackgroundTask for ServiceZoneNatTracker { }; let external_address = - ipnetwork::Ipv4Network::new(external_ip, 32) + oxnet::Ipv4Net::new(external_ip, 32) .unwrap(); let (snat_first_port, snat_last_port) = snat_cfg.port_range_raw(); let nat_value = Ipv4NatValues { external_address: nexus_db_model::Ipv4Net( - omicron_common::api::external::Ipv4Net( external_address, - ), ), first_port: snat_first_port.into(), last_port: snat_last_port.into(), @@ -187,14 +182,12 @@ impl BackgroundTask for ServiceZoneNatTracker { }; let external_address = - ipnetwork::Ipv4Network::new(external_ip, 32) + oxnet::Ipv4Net::new(external_ip, 32) .unwrap(); let nat_value = Ipv4NatValues { external_address: nexus_db_model::Ipv4Net( - omicron_common::api::external::Ipv4Net( external_address, - ), ), first_port: MIN_PORT.into(), last_port: MAX_PORT.into(), @@ -234,14 +227,12 @@ impl BackgroundTask for ServiceZoneNatTracker { }; let external_address = - ipnetwork::Ipv4Network::new(external_ip, 32) + oxnet::Ipv4Net::new(external_ip, 32) .unwrap(); let nat_value = Ipv4NatValues { external_address: nexus_db_model::Ipv4Net( - omicron_common::api::external::Ipv4Net( external_address, - ), ), first_port: MIN_PORT.into(), last_port: MAX_PORT.into(), diff --git a/nexus/src/app/bgp.rs b/nexus/src/app/bgp.rs index d41eaf2e6a..b6e3f25263 100644 --- a/nexus/src/app/bgp.rs +++ b/nexus/src/app/bgp.rs @@ -10,8 +10,7 @@ use nexus_db_queries::context::OpContext; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, BgpImportedRouteIpv4, BgpMessageHistory, BgpPeerStatus, CreateResult, - DeleteResult, Ipv4Net, ListResultVec, LookupResult, NameOrId, - SwitchBgpHistory, + DeleteResult, ListResultVec, LookupResult, NameOrId, SwitchBgpHistory, }; use std::net::IpAddr; @@ -202,8 +201,7 @@ impl super::Nexus { { Ok(result) => { for (prefix, paths) in result.into_inner().iter() { - let ipnet: ipnetwork::Ipv4Network = match prefix.parse() - { + let ipnet = match prefix.parse() { Ok(p) => p, Err(e) => { error!( @@ -220,7 +218,7 @@ impl super::Nexus { }; let x = BgpImportedRouteIpv4 { switch: *switch, - prefix: Ipv4Net(ipnet), + prefix: ipnet, id: p .bgp .as_ref() diff --git a/nexus/src/app/instance_network.rs b/nexus/src/app/instance_network.rs index de4de492e0..3c607bae78 100644 --- a/nexus/src/app/instance_network.rs +++ b/nexus/src/app/instance_network.rs @@ -6,7 +6,6 @@ use crate::app::switch_port; use ipnetwork::IpNetwork; -use ipnetwork::Ipv6Network; use nexus_db_model::ExternalIp; use nexus_db_model::IpAttachState; use nexus_db_model::Ipv4NatEntry; @@ -18,11 +17,11 @@ use nexus_db_queries::db; use nexus_db_queries::db::lookup::LookupPath; use nexus_db_queries::db::DataStore; use omicron_common::api::external::Error; -use omicron_common::api::external::Ipv4Net; -use omicron_common::api::external::Ipv6Net; use omicron_common::api::internal::nexus; use omicron_common::api::internal::shared::NetworkInterface; use omicron_common::api::internal::shared::SwitchLocation; +use oxnet::Ipv4Net; +use oxnet::Ipv6Net; use std::collections::HashSet; use std::str::FromStr; use uuid::Uuid; @@ -511,8 +510,7 @@ pub(crate) async fn instance_ensure_dpd_config( )); } - let sled_address = - Ipv6Net(Ipv6Network::new(*sled_ip_address.ip(), 128).unwrap()); + let sled_address = Ipv6Net::host_net(*sled_ip_address.ip()); // If all of our IPs are attached or are guaranteed to be owned // by the saga calling this fn, then we need to disregard and @@ -653,7 +651,7 @@ pub(crate) async fn probe_ensure_dpd_config( } } - let sled_address = Ipv6Net(Ipv6Network::new(sled_ip_address, 128).unwrap()); + let sled_address = Ipv6Net::host_net(sled_ip_address); for target_ip in ips .iter() @@ -1011,7 +1009,7 @@ async fn ensure_nat_entry( match target_ip.ip { IpNetwork::V4(v4net) => { let nat_entry = Ipv4NatValues { - external_address: Ipv4Net(v4net).into(), + external_address: Ipv4Net::from(v4net).into(), first_port: target_ip.first_port, last_port: target_ip.last_port, sled_address: sled_address.into(), diff --git a/nexus/src/app/sagas/vpc_create.rs b/nexus/src/app/sagas/vpc_create.rs index fdd117b850..cc40a8d43a 100644 --- a/nexus/src/app/sagas/vpc_create.rs +++ b/nexus/src/app/sagas/vpc_create.rs @@ -291,15 +291,13 @@ async fn svc_create_subnet( // Allocate the first /64 sub-range from the requested or created // prefix. - let ipv6_block = external::Ipv6Net( - ipnetwork::Ipv6Network::new(db_vpc.ipv6_prefix.network(), 64) - .map_err(|_| { - external::Error::internal_error( - "Failed to allocate default IPv6 subnet", - ) - }) - .map_err(ActionError::action_failed)?, - ); + let ipv6_block = oxnet::Ipv6Net::new(db_vpc.ipv6_prefix.prefix(), 64) + .map_err(|_| { + external::Error::internal_error( + "Failed to allocate default IPv6 subnet", + ) + }) + .map_err(ActionError::action_failed)?; let subnet = db::model::VpcSubnet::new( default_subnet_id, diff --git a/nexus/src/app/switch_interface.rs b/nexus/src/app/switch_interface.rs index c3ce0f553c..bb4cba4c7b 100644 --- a/nexus/src/app/switch_interface.rs +++ b/nexus/src/app/switch_interface.rs @@ -11,8 +11,9 @@ use nexus_db_queries::db::lookup; use nexus_db_queries::db::lookup::LookupPath; use omicron_common::api::external::LookupResult; use omicron_common::api::external::{ - CreateResult, DataPageParams, DeleteResult, Error, IpNet, ListResultVec, + CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, }; +use oxnet::IpNet; use std::sync::Arc; use uuid::Uuid; diff --git a/nexus/src/app/vpc_subnet.rs b/nexus/src/app/vpc_subnet.rs index 4c5a569201..f081f351db 100644 --- a/nexus/src/app/vpc_subnet.rs +++ b/nexus/src/app/vpc_subnet.rs @@ -19,6 +19,7 @@ use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; +use omicron_common::api::external::Ipv6NetExt; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; use omicron_common::api::external::NameOrId; @@ -74,13 +75,13 @@ impl super::Nexus { let (.., authz_vpc, db_vpc) = vpc_lookup.fetch().await?; // Validate IPv4 range - if !params.ipv4_block.network().is_private() { + if !params.ipv4_block.prefix().is_private() { return Err(external::Error::invalid_request( "VPC Subnet IPv4 address ranges must be from a private range", )); } - if params.ipv4_block.prefix() < MIN_VPC_IPV4_SUBNET_PREFIX - || params.ipv4_block.prefix() + if params.ipv4_block.width() < MIN_VPC_IPV4_SUBNET_PREFIX + || params.ipv4_block.width() > self.tunables.max_vpc_ipv4_subnet_prefix { return Err(external::Error::invalid_request(&format!( @@ -116,7 +117,7 @@ impl super::Nexus { let ipv6_block = db_vpc .ipv6_prefix .random_subnet( - external::Ipv6Net::VPC_SUBNET_IPV6_PREFIX_LENGTH, + oxnet::Ipv6Net::VPC_SUBNET_IPV6_PREFIX_LENGTH, ) .map(|block| block.0) .ok_or_else(|| { @@ -148,7 +149,7 @@ impl super::Nexus { self.log, "autogenerated random IPv6 range overlap"; "subnet_id" => ?subnet_id, - "ipv6_block" => %ipv6_block.0 + "ipv6_block" => %ipv6_block ); retry += 1; continue; @@ -193,10 +194,10 @@ impl super::Nexus { if !ipv6_block.is_vpc_subnet(&db_vpc.ipv6_prefix) { return Err(external::Error::invalid_request(&format!( concat!( - "VPC Subnet IPv6 address range '{}' is not valid for ", - "VPC with IPv6 prefix '{}'", - ), - ipv6_block, db_vpc.ipv6_prefix.0 .0, + "VPC Subnet IPv6 address range '{}' is not valid for ", + "VPC with IPv6 prefix '{}'", + ), + ipv6_block, db_vpc.ipv6_prefix.0, ))); } let subnet = db::model::VpcSubnet::new( diff --git a/nexus/src/context.rs b/nexus/src/context.rs index 72ecd6b8ac..1512671056 100644 --- a/nexus/src/context.rs +++ b/nexus/src/context.rs @@ -212,7 +212,8 @@ impl ServerContext { // Set up DNS Client let resolver = match config.deployment.internal_dns { nexus_config::InternalDns::FromSubnet { subnet } => { - let az_subnet = Ipv6Subnet::::new(subnet.net().ip()); + let az_subnet = + Ipv6Subnet::::new(subnet.net().addr()); info!( log, "Setting up resolver using DNS servers for subnet: {:?}", diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index a078ce2a61..d4af109849 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -675,7 +675,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { nic: NetworkInterface { id: Uuid::new_v4(), ip: NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap() .into(), kind: NetworkInterfaceKind::Service { @@ -1029,7 +1029,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { nic: NetworkInterface { id: Uuid::new_v4(), ip: DNS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap() .into(), kind: NetworkInterfaceKind::Service { diff --git a/nexus/tests/integration_tests/allow_list.rs b/nexus/tests/integration_tests/allow_list.rs index dc206843f7..336a33273d 100644 --- a/nexus/tests/integration_tests/allow_list.rs +++ b/nexus/tests/integration_tests/allow_list.rs @@ -9,7 +9,7 @@ use nexus_test_utils::http_testing::{AuthnMode, NexusRequest}; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::{params, views}; use omicron_common::api::external::AllowedSourceIps; -use omicron_common::api::external::IpNet; +use oxnet::IpNet; use std::net::IpAddr; use std::net::Ipv4Addr; @@ -75,8 +75,9 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { } // Set the list with exactly one IP, make sure it's the same. - let allowed_ips = AllowedSourceIps::try_from(vec![IpNet::single(our_addr)]) - .expect("Expected a valid IP list"); + let allowed_ips = + AllowedSourceIps::try_from(vec![IpNet::host_net(our_addr)]) + .expect("Expected a valid IP list"); update_list_and_compare(client, allowed_ips).await; // Add our IP in the front and end, and still make sure that works. @@ -84,8 +85,8 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { // This is a regression for // https://github.com/oxidecomputer/omicron/issues/5727. let addrs = vec![ - IpNet::single(our_addr), - IpNet::single(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))), + IpNet::host_net(our_addr), + IpNet::host_net(IpAddr::from(Ipv4Addr::new(10, 0, 0, 1))), ]; let allowed_ips = AllowedSourceIps::try_from(addrs.clone()) .expect("Expected a valid IP list"); @@ -101,7 +102,7 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { // Check that we cannot make the request with a list that doesn't include // us. - let addrs = vec![IpNet::single(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)))]; + let addrs = vec![IpNet::host_net(IpAddr::from(Ipv4Addr::new(1, 1, 1, 1)))]; let allowed_ips = AllowedSourceIps::try_from(addrs.clone()) .expect("Expected a valid IP list"); let new_list = params::AllowListUpdate { allowed_ips: allowed_ips.clone() }; diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index cc73ab088c..7672bbc034 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -30,7 +30,6 @@ use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::InstanceCpuCount; -use omicron_common::api::external::Ipv4Net; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; use omicron_common::api::external::RouteDestination; @@ -201,7 +200,7 @@ pub static DEMO_VPC_SUBNET_CREATE: Lazy = name: DEMO_VPC_SUBNET_NAME.clone(), description: String::from(""), }, - ipv4_block: Ipv4Net("10.1.2.3/8".parse().unwrap()), + ipv4_block: "10.1.2.3/8".parse().unwrap(), ipv6_block: None, }); diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 51e2552e85..565e2fbafb 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -56,7 +56,6 @@ use omicron_common::api::external::Instance; use omicron_common::api::external::InstanceCpuCount; use omicron_common::api::external::InstanceNetworkInterface; use omicron_common::api::external::InstanceState; -use omicron_common::api::external::Ipv4Net; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; use omicron_common::api::external::Vni; @@ -1684,7 +1683,7 @@ async fn test_instance_with_new_custom_network_interfaces( name: non_default_subnet_name.clone(), description: String::from("A non-default subnet"), }, - ipv4_block: Ipv4Net("172.31.0.0/24".parse().unwrap()), + ipv4_block: "172.31.0.0/24".parse().unwrap(), ipv6_block: None, }; let _response = NexusRequest::objects_post( @@ -1830,7 +1829,7 @@ async fn test_instance_create_delete_network_interface( name: Name::try_from(String::from("secondary")).unwrap(), description: String::from("A secondary VPC subnet"), }, - ipv4_block: Ipv4Net("172.31.0.0/24".parse().unwrap()), + ipv4_block: "172.31.0.0/24".parse().unwrap(), ipv6_block: None, }; let _response = NexusRequest::objects_post( @@ -2071,7 +2070,7 @@ async fn test_instance_update_network_interfaces( name: Name::try_from(String::from("secondary")).unwrap(), description: String::from("A secondary VPC subnet"), }, - ipv4_block: Ipv4Net("172.31.0.0/24".parse().unwrap()), + ipv4_block: "172.31.0.0/24".parse().unwrap(), ipv6_block: None, }; let _response = NexusRequest::objects_post( diff --git a/nexus/tests/integration_tests/subnet_allocation.rs b/nexus/tests/integration_tests/subnet_allocation.rs index 0efc659890..794c769da4 100644 --- a/nexus/tests/integration_tests/subnet_allocation.rs +++ b/nexus/tests/integration_tests/subnet_allocation.rs @@ -9,7 +9,6 @@ use dropshot::test_util::ClientTestContext; use dropshot::HttpErrorResponseBody; use http::method::Method; use http::StatusCode; -use ipnetwork::Ipv4Network; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; @@ -22,8 +21,9 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, - InstanceNetworkInterface, Ipv4Net, + InstanceNetworkInterface, }; +use oxnet::Ipv4Net; use std::net::Ipv4Addr; type ControlPlaneTestContext = @@ -101,7 +101,7 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { let subnets_url = format!("/v1/vpc-subnets?{}", vpc_selector); let subnet_name = "small"; let network_address = Ipv4Addr::new(192, 168, 42, 0); - let subnet = Ipv4Network::new(network_address, subnet_size) + let subnet = Ipv4Net::new(network_address, subnet_size) .expect("Invalid IPv4 network"); let subnet_create = params::VpcSubnetCreate { identity: IdentityMetadataCreateParams { @@ -109,7 +109,7 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { description: String::from("a small subnet"), }, // Use the minimum subnet size - ipv4_block: Ipv4Net(subnet), + ipv4_block: subnet, ipv6_block: None, }; NexusRequest::objects_post(client, &subnets_url, &Some(&subnet_create)) @@ -132,12 +132,13 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { }, ]); - // Create enough instances to fill the subnet. There are subnet.size() total - // addresses, 6 of which are reserved. - let n_final_reserved_addresses = 1; - let n_reserved_addresses = - NUM_INITIAL_RESERVED_IP_ADDRESSES + n_final_reserved_addresses; - let subnet_size = subnet.size() as usize - n_reserved_addresses; + // Create enough instances to fill the subnet. There are subnet.size() + // total addresses, 6 of which are reserved. + let subnet_size_minus_1 = match subnet.size() { + Some(n) => n - 1, + None => u32::MAX, + } as usize; + let subnet_size = subnet_size_minus_1 - NUM_INITIAL_RESERVED_IP_ADDRESSES; for i in 0..subnet_size { create_instance_with( client, @@ -178,7 +179,7 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { network_interfaces.sort_by(|a, b| a.ip.cmp(&b.ip)); for (iface, addr) in network_interfaces .iter() - .zip(subnet.iter().skip(NUM_INITIAL_RESERVED_IP_ADDRESSES)) + .zip(subnet.addr_iter().skip(NUM_INITIAL_RESERVED_IP_ADDRESSES)) { assert_eq!( iface.ip, diff --git a/nexus/tests/integration_tests/vpc_subnets.rs b/nexus/tests/integration_tests/vpc_subnets.rs index 0814512cf2..dcc96d08bf 100644 --- a/nexus/tests/integration_tests/vpc_subnets.rs +++ b/nexus/tests/integration_tests/vpc_subnets.rs @@ -20,8 +20,8 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::{params, views::VpcSubnet}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; -use omicron_common::api::external::Ipv4Net; -use omicron_common::api::external::Ipv6Net; +use omicron_common::api::external::Ipv6NetExt; +use oxnet::Ipv6Net; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; @@ -160,16 +160,15 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "not found: vpc-subnet with name \"subnet1\""); // Create a VPC Subnet. - let ipv4_block = Ipv4Net("10.0.0.0/24".parse().unwrap()); - let other_ipv4_block = Ipv4Net("172.31.0.0/16".parse().unwrap()); - // Create the first two available IPv6 address ranges. */ - let prefix = vpc.ipv6_prefix.network(); - let ipv6_block = Ipv6Net(ipnetwork::Ipv6Network::new(prefix, 64).unwrap()); + let ipv4_block = "10.0.0.0/24".parse().unwrap(); + let other_ipv4_block = "172.31.0.0/16".parse().unwrap(); + // Create the first two available IPv6 address ranges. + let prefix = vpc.ipv6_prefix.prefix(); + let ipv6_block = Ipv6Net::new(prefix, 64).unwrap(); let mut segments = prefix.segments(); segments[3] = 1; let addr = std::net::Ipv6Addr::from(segments); - let other_ipv6_block = - Some(Ipv6Net(ipnetwork::Ipv6Network::new(addr, 64).unwrap())); + let other_ipv6_block = Some(Ipv6Net::new(addr, 64).unwrap()); let new_subnet = params::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), @@ -291,7 +290,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "not found: vpc-subnet with name \"subnet2\""); // create second subnet, this time with an autogenerated IPv6 range. - let ipv4_block = Ipv4Net("192.168.0.0/16".parse().unwrap()); + let ipv4_block = "192.168.0.0/16".parse().unwrap(); let new_subnet = params::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet2_name.parse().unwrap(), @@ -435,10 +434,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { "it's also below the net" ); assert_eq!(subnet_same_name.vpc_id, vpc2.identity.id); - assert_eq!( - subnet_same_name.ipv4_block, - Ipv4Net("192.168.0.0/16".parse().unwrap()) - ); + assert_eq!(subnet_same_name.ipv4_block, "192.168.0.0/16".parse().unwrap()); assert!(subnet_same_name.ipv6_block.is_unique_local()); } diff --git a/nexus/tests/integration_tests/vpcs.rs b/nexus/tests/integration_tests/vpcs.rs index cc9aea4d11..1ceebd8cff 100644 --- a/nexus/tests/integration_tests/vpcs.rs +++ b/nexus/tests/integration_tests/vpcs.rs @@ -18,7 +18,6 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::{params, views::Vpc}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; -use omicron_common::api::external::Ipv6Net; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; @@ -76,7 +75,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { // Make sure creating a VPC fails if we specify an IPv6 prefix that is // not a valid ULA range. - let bad_prefix = Ipv6Net("2000:1000::/48".parse().unwrap()); + let bad_prefix = "2000:1000::/48".parse().unwrap(); NexusRequest::new( RequestBuilder::new(client, Method::POST, &vpcs_url) .expect_status(Some(StatusCode::BAD_REQUEST)) @@ -101,7 +100,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { assert_eq!(vpc.identity.description, "vpc description"); assert_eq!(vpc.dns_name, "abc"); assert_eq!( - vpc.ipv6_prefix.prefix(), + vpc.ipv6_prefix.width(), 48, "Expected a 48-bit ULA IPv6 address prefix" ); diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index 802727b1ab..df976e2444 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -19,6 +19,7 @@ humantime.workspace = true ipnetwork.workspace = true omicron-uuid-kinds.workspace = true openssl.workspace = true +oxnet.workspace = true parse-display.workspace = true schemars = { workspace = true, features = ["chrono", "uuid1"] } serde.workspace = true diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 1b252c77cb..3f53503cc2 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -11,9 +11,10 @@ use chrono::{DateTime, Utc}; use omicron_common::api::external::{ AddressLotKind, AllowedSourceIps, BfdMode, BgpPeer, ByteCount, Hostname, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, - InstanceCpuCount, IpNet, Ipv4Net, Ipv6Net, LinkFec, LinkSpeed, Name, - NameOrId, PaginationOrder, RouteDestination, RouteTarget, SemverVersion, + InstanceCpuCount, LinkFec, LinkSpeed, Name, NameOrId, PaginationOrder, + RouteDestination, RouteTarget, SemverVersion, }; +use oxnet::{IpNet, Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{ de::{self, Visitor}, diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 1e90d04b55..2fa94b0e80 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -13,9 +13,10 @@ use chrono::DateTime; use chrono::Utc; use omicron_common::api::external::{ AllowedSourceIps as ExternalAllowedSourceIps, ByteCount, Digest, Error, - IdentityMetadata, InstanceState, Ipv4Net, Ipv6Net, Name, ObjectIdentity, - RoleName, SimpleIdentity, + IdentityMetadata, InstanceState, Name, ObjectIdentity, RoleName, + SimpleIdentity, }; +use oxnet::{Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/openapi/bootstrap-agent.json b/openapi/bootstrap-agent.json index f177c27f55..ddfc1e91f8 100644 --- a/openapi/bootstrap-agent.json +++ b/openapi/bootstrap-agent.json @@ -620,6 +620,11 @@ ] }, "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, "oneOf": [ { "title": "v4", @@ -683,7 +688,12 @@ "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, @@ -714,6 +724,11 @@ "example": "fd12:3456::/64", "title": "An IPv6 subnet", "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index c7d476994d..ad109a18fa 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -3174,6 +3174,11 @@ ] }, "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, "oneOf": [ { "title": "v4", @@ -3284,7 +3289,12 @@ "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, @@ -3315,6 +3325,11 @@ "example": "fd12:3456::/64", "title": "An IPv6 subnet", "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, diff --git a/openapi/nexus.json b/openapi/nexus.json index 2bf6f0a6ff..a0789aecde 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -14314,6 +14314,11 @@ ] }, "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, "oneOf": [ { "title": "v4", @@ -14595,7 +14600,12 @@ "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, @@ -14642,6 +14652,11 @@ "example": "fd12:3456::/64", "title": "An IPv6 subnet", "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, diff --git a/openapi/sled-agent.json b/openapi/sled-agent.json index 7a951a6d15..763a67910f 100644 --- a/openapi/sled-agent.json +++ b/openapi/sled-agent.json @@ -3388,6 +3388,11 @@ ] }, "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, "oneOf": [ { "title": "v4", @@ -3431,7 +3436,12 @@ "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, @@ -3444,6 +3454,11 @@ "example": "fd12:3456::/64", "title": "An IPv6 subnet", "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, @@ -3453,7 +3468,7 @@ "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\")[/](12[0-8]|1[0-1][0-9]|[0-9]?[0-9])$" }, "Ipv6Subnet": { - "description": "Wraps an [`Ipv6Network`] with a compile-time prefix length.", + "description": "Wraps an [`Ipv6Net`] with a compile-time prefix length.", "type": "object", "properties": { "net": { diff --git a/openapi/wicketd.json b/openapi/wicketd.json index cb06c0cadf..762fbfade0 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -1666,6 +1666,11 @@ ] }, "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, "oneOf": [ { "title": "v4", @@ -1729,7 +1734,12 @@ "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, @@ -1760,6 +1770,11 @@ "example": "fd12:3456::/64", "title": "An IPv6 subnet", "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, diff --git a/schema/all-zone-requests.json b/schema/all-zone-requests.json index 7fe9b139eb..fde6ee18a4 100644 --- a/schema/all-zone-requests.json +++ b/schema/all-zone-requests.json @@ -173,16 +173,26 @@ } ] } - ] + ], + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + } }, "Ipv4Net": { "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", "examples": [ "192.168.1.0/24" ], "type": "string", - "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + } }, "Ipv6Net": { "title": "An IPv6 subnet", @@ -191,7 +201,12 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "MacAddr": { "title": "A MAC address", diff --git a/schema/all-zones-requests.json b/schema/all-zones-requests.json index bb4dba2520..526e41376f 100644 --- a/schema/all-zones-requests.json +++ b/schema/all-zones-requests.json @@ -57,16 +57,26 @@ } ] } - ] + ], + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + } }, "Ipv4Net": { "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", "examples": [ "192.168.1.0/24" ], "type": "string", - "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + } }, "Ipv6Net": { "title": "An IPv6 subnet", @@ -75,7 +85,12 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "MacAddr": { "title": "A MAC address", diff --git a/schema/deployment-config.json b/schema/deployment-config.json index 9fa4ba2159..7b737c52b2 100644 --- a/schema/deployment-config.json +++ b/schema/deployment-config.json @@ -132,10 +132,15 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "Ipv6Subnet": { - "description": "Wraps an [`Ipv6Network`] with a compile-time prefix length.", + "description": "Wraps an [`Ipv6Net`] with a compile-time prefix length.", "type": "object", "required": [ "net" diff --git a/schema/rss-service-plan-v3.json b/schema/rss-service-plan-v3.json index bab3e916ba..d1540ca351 100644 --- a/schema/rss-service-plan-v3.json +++ b/schema/rss-service-plan-v3.json @@ -171,16 +171,26 @@ } ] } - ] + ], + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + } }, "Ipv4Net": { "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", "examples": [ "192.168.1.0/24" ], "type": "string", - "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + } }, "Ipv6Net": { "title": "An IPv6 subnet", @@ -189,7 +199,12 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "MacAddr": { "title": "A MAC address", diff --git a/schema/rss-sled-plan.json b/schema/rss-sled-plan.json index 95ca5b90ba..204dddff99 100644 --- a/schema/rss-sled-plan.json +++ b/schema/rss-sled-plan.json @@ -493,7 +493,12 @@ } ] } - ] + ], + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + } }, "IpNetwork": { "oneOf": [ @@ -538,12 +543,17 @@ }, "Ipv4Net": { "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and subnet mask", + "description": "An IPv4 subnet, including prefix and prefix length", "examples": [ "192.168.1.0/24" ], "type": "string", - "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" + } }, "Ipv4Network": { "type": "string", @@ -575,7 +585,12 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "Ipv6Network": { "type": "string", @@ -601,7 +616,7 @@ } }, "Ipv6Subnet": { - "description": "Wraps an [`Ipv6Network`] with a compile-time prefix length.", + "description": "Wraps an [`Ipv6Net`] with a compile-time prefix length.", "type": "object", "required": [ "net" diff --git a/schema/start-sled-agent-request.json b/schema/start-sled-agent-request.json index 7a7745617c..98dfcea61c 100644 --- a/schema/start-sled-agent-request.json +++ b/schema/start-sled-agent-request.json @@ -32,10 +32,15 @@ "fd12:3456::/64" ], "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + } }, "Ipv6Subnet": { - "description": "Wraps an [`Ipv6Network`] with a compile-time prefix length.", + "description": "Wraps an [`Ipv6Net`] with a compile-time prefix length.", "type": "object", "required": [ "net" diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index 52533016dc..167ac987ca 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -56,6 +56,7 @@ once_cell.workspace = true oximeter.workspace = true oximeter-instruments.workspace = true oximeter-producer.workspace = true +oxnet.workspace = true propolis-client.workspace = true propolis-mock-server.workspace = true # Only used by the simulated sled agent rand = { workspace = true, features = ["getrandom"] } diff --git a/sled-agent/src/bootstrap/early_networking.rs b/sled-agent/src/bootstrap/early_networking.rs index 2714c220c7..8727a01eae 100644 --- a/sled-agent/src/bootstrap/early_networking.rs +++ b/sled-agent/src/bootstrap/early_networking.rs @@ -23,7 +23,7 @@ use mg_admin_client::types::{ use mg_admin_client::Client as MgdClient; use omicron_common::address::DENDRITE_PORT; use omicron_common::address::{MGD_PORT, MGS_PORT}; -use omicron_common::api::external::{BfdMode, ImportExportPolicy, IpNet}; +use omicron_common::api::external::{BfdMode, ImportExportPolicy}; use omicron_common::api::internal::shared::{ BgpConfig, PortConfigV1, PortFec, PortSpeed, RackNetworkConfig, RackNetworkConfigV1, SwitchLocation, UplinkConfig, @@ -34,6 +34,7 @@ use omicron_common::backoff::{ }; use omicron_common::OMICRON_DPD_TAG; use omicron_ddm_admin_client::DdmError; +use oxnet::IpNet; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use slog::Logger; @@ -515,12 +516,12 @@ impl<'a> EarlyNetworkSetup<'a> { .iter() .map(|x| match x { IpNet::V4(p) => Prefix::V4(Prefix4 { - length: p.prefix(), - value: p.ip(), + length: p.width(), + value: p.addr(), }), IpNet::V6(p) => Prefix::V6(Prefix6 { - length: p.prefix(), - value: p.ip(), + length: p.width(), + value: p.addr(), }), }) .collect(), @@ -537,12 +538,12 @@ impl<'a> EarlyNetworkSetup<'a> { .iter() .map(|x| match x { IpNet::V4(p) => Prefix::V4(Prefix4 { - length: p.prefix(), - value: p.ip(), + length: p.width(), + value: p.addr(), }), IpNet::V6(p) => Prefix::V6(Prefix6 { - length: p.prefix(), - value: p.ip(), + length: p.width(), + value: p.addr(), }), }) .collect(), diff --git a/sled-agent/src/bootstrap/server.rs b/sled-agent/src/bootstrap/server.rs index d4c17e20a6..369437d3aa 100644 --- a/sled-agent/src/bootstrap/server.rs +++ b/sled-agent/src/bootstrap/server.rs @@ -406,7 +406,7 @@ async fn start_sled_agent( ddmd_client.advertise_prefix(request.body.subnet); let az_prefix = - Ipv6Subnet::::new(request.body.subnet.net().network()); + Ipv6Subnet::::new(request.body.subnet.net().addr()); let addr = request.body.subnet.net().iter().nth(1).unwrap(); let dns_servers = Resolver::servers_from_subnet(az_prefix); ddmd_client.enable_stats( diff --git a/sled-agent/src/rack_setup/plan/service.rs b/sled-agent/src/rack_setup/plan/service.rs index b4a6fe76f6..a763d61923 100644 --- a/sled-agent/src/rack_setup/plan/service.rs +++ b/sled-agent/src/rack_setup/plan/service.rs @@ -384,11 +384,11 @@ impl Plan { &reserved_rack_subnet.get_dns_subnets()[0..DNS_REDUNDANCY]; let rack_dns_servers = dns_subnets .into_iter() - .map(|dns_subnet| dns_subnet.dns_address().ip().into()) + .map(|dns_subnet| dns_subnet.dns_address().addr().into()) .collect::>(); for i in 0..dns_subnets.len() { let dns_subnet = &dns_subnets[i]; - let ip = dns_subnet.dns_address().ip(); + let ip = dns_subnet.dns_address().addr(); let sled = { let which_sled = sled_allocator.next().ok_or(PlanError::NotEnoughSleds)?; @@ -419,7 +419,7 @@ impl Plan { }, http_address, dns_address, - gz_address: dns_subnet.gz_address().ip(), + gz_address: dns_subnet.gz_address().addr(), gz_address_index: i.try_into().expect("Giant indices?"), }, }); @@ -961,39 +961,29 @@ impl ServicePortBuilder { let dns_v4_ips = Box::new( DNS_OPTE_IPV4_SUBNET - .0 - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); let dns_v6_ips = Box::new( - DNS_OPTE_IPV6_SUBNET - .0 - .iter() - .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), + DNS_OPTE_IPV6_SUBNET.iter().skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); let nexus_v4_ips = Box::new( NEXUS_OPTE_IPV4_SUBNET - .0 - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); let nexus_v6_ips = Box::new( NEXUS_OPTE_IPV6_SUBNET - .0 .iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); let ntp_v4_ips = Box::new( NTP_OPTE_IPV4_SUBNET - .0 - .iter() + .addr_iter() .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); let ntp_v6_ips = Box::new( - NTP_OPTE_IPV6_SUBNET - .0 - .iter() - .skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), + NTP_OPTE_IPV6_SUBNET.iter().skip(NUM_INITIAL_RESERVED_IP_ADDRESSES), ); Self { internal_services_ip_pool, diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index c9a5014402..ff10d4aed7 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -2543,7 +2543,7 @@ impl ServiceManager { ); smfh.setprop( "config/rack-subnet", - &rack_subnet.net().ip().to_string(), + &rack_subnet.net().addr().to_string(), )?; } @@ -2711,7 +2711,7 @@ impl ServiceManager { // network address, without the mask. smfh.setprop( format!("config/techport{i}_prefix"), - prefix.net().network().to_string(), + prefix.net().addr(), )?; } smfh.setprop("config/pkt_source", pkt_source)?; @@ -3995,12 +3995,12 @@ impl ServiceManager { info!( self.inner.log, "configuring wicketd"; - "rack_subnet" => %rack_subnet.net().ip(), + "rack_subnet" => %rack_subnet.net().addr(), ); smfh.setprop( "config/rack-subnet", - &rack_subnet.net().ip().to_string(), + &rack_subnet.net().addr().to_string(), )?; smfh.refresh()?; @@ -5066,9 +5066,9 @@ mod test { fn test_bootstrap_addr_to_techport_prefixes() { let ba: Ipv6Addr = "fdb0:1122:3344:5566::".parse().unwrap(); let prefixes = ServiceManager::bootstrap_addr_to_techport_prefixes(&ba); - assert!(prefixes.iter().all(|p| p.net().prefix() == 64)); - let prefix0 = prefixes[0].net().network(); - let prefix1 = prefixes[1].net().network(); + assert!(prefixes.iter().all(|p| p.net().width() == 64)); + let prefix0 = prefixes[0].net().prefix(); + let prefix1 = prefixes[1].net().prefix(); assert_eq!(prefix0.segments()[1..], ba.segments()[1..]); assert_eq!(prefix1.segments()[1..], ba.segments()[1..]); assert_eq!(prefix0.segments()[0], 0xfdb1); diff --git a/sled-agent/src/sim/server.rs b/sled-agent/src/sim/server.rs index ebee0adc1f..e3ce4ad4e4 100644 --- a/sled-agent/src/sim/server.rs +++ b/sled-agent/src/sim/server.rs @@ -401,7 +401,7 @@ pub async fn run_standalone_server( kind: NetworkInterfaceKind::Service { id }, name: "nexus".parse().unwrap(), ip: NEXUS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap() .into(), mac: macs.next().unwrap(), @@ -444,7 +444,7 @@ pub async fn run_standalone_server( kind: NetworkInterfaceKind::Service { id }, name: "external-dns".parse().unwrap(), ip: DNS_OPTE_IPV4_SUBNET - .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) + .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES + 1) .unwrap() .into(), mac: macs.next().unwrap(), diff --git a/tools/dendrite_openapi_version b/tools/dendrite_openapi_version old mode 100644 new mode 100755 diff --git a/wicket-common/Cargo.toml b/wicket-common/Cargo.toml index 39efc2ce20..685514f399 100644 --- a/wicket-common/Cargo.toml +++ b/wicket-common/Cargo.toml @@ -11,6 +11,7 @@ workspace = true anyhow.workspace = true omicron-common.workspace = true owo-colors.workspace = true +oxnet.workspace = true gateway-client.workspace = true ipnetwork.workspace = true maplit.workspace = true diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index 5e89bfdde2..9221153398 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -9,7 +9,6 @@ pub use gateway_client::types::SpType as GatewaySpType; use ipnetwork::IpNetwork; use omicron_common::address; use omicron_common::api::external::ImportExportPolicy; -use omicron_common::api::external::IpNet; use omicron_common::api::external::Name; use omicron_common::api::external::SwitchLocation; use omicron_common::api::internal::shared::AllowedSourceIps; @@ -21,6 +20,7 @@ use omicron_common::api::internal::shared::RouteConfig; use omicron_common::update::ArtifactHash; use owo_colors::OwoColorize; use owo_colors::Style; +use oxnet::IpNet; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index f74baa3f2c..941f5f7dc1 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -1093,8 +1093,8 @@ fn rss_config_text<'a>( Some(AllowedSourceIps::List(list)) => list .iter() .map(|net| { - let as_str = if net.first_address() == net.last_address() { - net.ip().to_string() + let as_str = if net.is_host_net() { + net.addr().to_string() } else { net.to_string() }; diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 9f2910bcc2..c90f672500 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -652,7 +652,7 @@ fn validate_rack_network_config( // TODO Add more client side checks on `rack_network_config` contents? Ok(bootstrap_agent_client::types::RackNetworkConfigV1 { - rack_subnet: RACK_SUBNET.net(), + rack_subnet: RACK_SUBNET.net().into(), infra_ip_first: config.infra_ip_first, infra_ip_last: config.infra_ip_last, ports: config diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index d8c9e7c634..3b5e1917d0 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -89,7 +89,7 @@ regex-automata = { version = "0.4.6", default-features = false, features = ["dfa regex-syntax = { version = "0.8.3" } reqwest = { version = "0.11.27", features = ["blocking", "cookies", "json", "rustls-tls", "stream"] } ring = { version = "0.17.8", features = ["std"] } -schemars = { version = "0.8.19", features = ["bytes", "chrono", "uuid1"] } +schemars = { version = "0.8.20", features = ["bytes", "chrono", "uuid1"] } scopeguard = { version = "1.2.0" } semver = { version = "1.0.23", features = ["serde"] } serde = { version = "1.0.202", features = ["alloc", "derive", "rc"] } @@ -194,7 +194,7 @@ regex-automata = { version = "0.4.6", default-features = false, features = ["dfa regex-syntax = { version = "0.8.3" } reqwest = { version = "0.11.27", features = ["blocking", "cookies", "json", "rustls-tls", "stream"] } ring = { version = "0.17.8", features = ["std"] } -schemars = { version = "0.8.19", features = ["bytes", "chrono", "uuid1"] } +schemars = { version = "0.8.20", features = ["bytes", "chrono", "uuid1"] } scopeguard = { version = "1.2.0" } semver = { version = "1.0.23", features = ["serde"] } serde = { version = "1.0.202", features = ["alloc", "derive", "rc"] }