diff --git a/nexus/src/db/datastore.rs b/nexus/src/db/datastore.rs index 2a309362213..637d5747b7a 100644 --- a/nexus/src/db/datastore.rs +++ b/nexus/src/db/datastore.rs @@ -2772,6 +2772,8 @@ mod test { }; use omicron_test_utils::dev; use std::collections::HashSet; + use std::net::Ipv6Addr; + use std::net::SocketAddrV6; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; use uuid::Uuid; @@ -2903,8 +2905,12 @@ mod test { // Creates a test sled, returns its UUID. async fn create_test_sled(datastore: &DataStore) -> Uuid { - let bogus_addr = - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let bogus_addr = SocketAddrV6::new( + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), + 8080, + 0, + 0, + ); let sled_id = Uuid::new_v4(); let sled = Sled::new(sled_id, bogus_addr.clone()); datastore.sled_upsert(sled).await.unwrap(); diff --git a/nexus/src/db/ipv6.rs b/nexus/src/db/ipv6.rs new file mode 100644 index 00000000000..c713b3435f5 --- /dev/null +++ b/nexus/src/db/ipv6.rs @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Database-friendly IPv6 addresses + +use diesel::backend::Backend; +use diesel::backend::RawValue; +use diesel::deserialize; +use diesel::deserialize::FromSql; +use diesel::serialize; +use diesel::serialize::Output; +use diesel::serialize::ToSql; +use diesel::sql_types::Inet; +use ipnetwork::IpNetwork; +use ipnetwork::Ipv6Network; +use omicron_common::api::external::Error; + +#[derive( + Clone, Copy, AsExpression, FromSqlRow, PartialEq, Ord, PartialOrd, Eq, +)] +#[sql_type = "Inet"] +pub struct Ipv6Addr(std::net::Ipv6Addr); + +NewtypeDebug! { () pub struct Ipv6Addr(std::net::Ipv6Addr); } +NewtypeFrom! { () pub struct Ipv6Addr(std::net::Ipv6Addr); } +NewtypeDeref! { () pub struct Ipv6Addr(std::net::Ipv6Addr); } + +impl From<&std::net::Ipv6Addr> for Ipv6Addr { + fn from(addr: &std::net::Ipv6Addr) -> Self { + Self(*addr) + } +} + +impl ToSql for Ipv6Addr +where + DB: Backend, + IpNetwork: ToSql, +{ + fn to_sql( + &self, + out: &mut Output, + ) -> serialize::Result { + IpNetwork::V6(Ipv6Network::from(self.0)).to_sql(out) + } +} + +impl FromSql for Ipv6Addr +where + DB: Backend, + IpNetwork: FromSql, +{ + fn from_sql(bytes: RawValue) -> deserialize::Result { + match IpNetwork::from_sql(bytes)?.ip() { + std::net::IpAddr::V6(ip) => Ok(Self(ip)), + v4 => { + Err(Box::new(Error::internal_error( + format!("Expected an IPv6 address from the database, found IPv4: '{}'", v4).as_str() + ))) + } + } + } +} diff --git a/nexus/src/db/mod.rs b/nexus/src/db/mod.rs index 46d15f4ee2d..1ea4d28bb05 100644 --- a/nexus/src/db/mod.rs +++ b/nexus/src/db/mod.rs @@ -14,6 +14,7 @@ pub mod datastore; mod error; mod explain; pub mod fixed_data; +pub mod ipv6; pub mod lookup; mod pagination; mod pool; diff --git a/nexus/src/db/model.rs b/nexus/src/db/model.rs index 268e2beb3dc..43c06088709 100644 --- a/nexus/src/db/model.rs +++ b/nexus/src/db/model.rs @@ -6,6 +6,7 @@ use crate::db::collection_insert::DatastoreCollection; use crate::db::identity::{Asset, Resource}; +use crate::db::ipv6; use crate::db::schema::{ console_session, dataset, disk, image, instance, metric_producer, network_interface, organization, oximeter, project, rack, region, @@ -37,6 +38,7 @@ use std::net::IpAddr; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::net::SocketAddr; +use std::net::SocketAddrV6; use uuid::Uuid; // TODO: Break up types into multiple files @@ -628,12 +630,12 @@ pub struct Sled { rcgen: Generation, // ServiceAddress (Sled Agent). - pub ip: ipnetwork::IpNetwork, + pub ip: ipv6::Ipv6Addr, // TODO: Make use of SqlU16 pub port: i32, /// The last IP address provided to an Oxide service on this sled - pub last_used_address: IpNetwork, + pub last_used_address: ipv6::Ipv6Addr, } // TODO-correctness: We need a small offset here, while services and @@ -645,46 +647,33 @@ pub struct Sled { // See https://github.com/oxidecomputer/omicron/issues/732 for tracking issue. pub(crate) const STATIC_IPV6_ADDRESS_OFFSET: u16 = 20; impl Sled { - // TODO-cleanup: We should be using IPv6 only for Oxide services, including - // `std::net::Ipv6Addr` and `SocketAddrV6`. The v4/v6 enums should only be - // used for managing customer addressing information, or when needed to - // interact with the database. - pub fn new(id: Uuid, addr: SocketAddr) -> Self { + pub fn new(id: Uuid, addr: SocketAddrV6) -> Self { let last_used_address = { - match addr.ip() { - IpAddr::V6(ip) => { - let mut segments = ip.segments(); - segments[7] += STATIC_IPV6_ADDRESS_OFFSET; - ipnetwork::IpNetwork::from(IpAddr::from(Ipv6Addr::from( - segments, - ))) - } - IpAddr::V4(ip) => { - // TODO-correctness: This match arm should disappear when we - // support only IPv6 for underlay addressing. - let x = u32::from_be_bytes(ip.octets()) - + u32::from(STATIC_IPV6_ADDRESS_OFFSET); - ipnetwork::IpNetwork::from(IpAddr::from(Ipv4Addr::from(x))) - } - } + let mut segments = addr.ip().segments(); + segments[7] += STATIC_IPV6_ADDRESS_OFFSET; + ipv6::Ipv6Addr::from(Ipv6Addr::from(segments)) }; Self { identity: SledIdentity::new(id), time_deleted: None, rcgen: Generation::new(), - ip: addr.ip().into(), + ip: ipv6::Ipv6Addr::from(addr.ip()), port: addr.port().into(), last_used_address, } } - pub fn address(&self) -> SocketAddr { + pub fn ip(&self) -> Ipv6Addr { + self.ip.into() + } + + pub fn address(&self) -> SocketAddrV6 { // TODO: avoid this unwrap self.address_with_port(u16::try_from(self.port).unwrap()) } - pub fn address_with_port(&self, port: u16) -> SocketAddr { - SocketAddr::new(self.ip.ip(), port) + pub fn address_with_port(&self, port: u16) -> SocketAddrV6 { + SocketAddrV6::new(self.ip(), port, 0, 0) } } diff --git a/nexus/src/external_api/views.rs b/nexus/src/external_api/views.rs index fdceddacc1a..5fc4b22a39d 100644 --- a/nexus/src/external_api/views.rs +++ b/nexus/src/external_api/views.rs @@ -14,7 +14,7 @@ use omicron_common::api::external::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::net::SocketAddr; +use std::net::SocketAddrV6; use uuid::Uuid; // SILOS @@ -224,7 +224,7 @@ impl From for Rack { pub struct Sled { #[serde(flatten)] pub identity: IdentityMetadata, - pub service_address: SocketAddr, + pub service_address: SocketAddrV6, } impl From for Sled { diff --git a/nexus/src/internal_api/params.rs b/nexus/src/internal_api/params.rs index 96f078b9c6d..185a55df077 100644 --- a/nexus/src/internal_api/params.rs +++ b/nexus/src/internal_api/params.rs @@ -8,6 +8,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; use std::net::SocketAddr; +use std::net::SocketAddrV6; use std::str::FromStr; use uuid::Uuid; @@ -15,7 +16,7 @@ use uuid::Uuid; #[derive(Serialize, Deserialize, JsonSchema)] pub struct SledAgentStartupInfo { /// the address of the sled agent's API endpoint - pub sa_address: SocketAddr, + pub sa_address: SocketAddrV6, } /// Sent by a sled agent on startup to Nexus to request further instruction diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index f443f9ff236..d3b0f316a9a 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -73,6 +73,7 @@ use sled_agent_client::Client as SledAgentClient; use slog::Logger; use std::convert::{TryFrom, TryInto}; use std::net::SocketAddr; +use std::net::SocketAddrV6; use std::num::NonZeroU32; use std::path::Path; use std::sync::Arc; @@ -466,7 +467,7 @@ impl Nexus { pub async fn upsert_sled( &self, id: Uuid, - address: SocketAddr, + address: SocketAddrV6, ) -> Result<(), Error> { info!(self.log, "registered sled agent"; "sled_uuid" => id.to_string()); let sled = db::model::Sled::new(id, address);