Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions nexus/src/db/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
63 changes: 63 additions & 0 deletions nexus/src/db/ipv6.rs
Original file line number Diff line number Diff line change
@@ -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<DB> ToSql<Inet, DB> for Ipv6Addr
where
DB: Backend,
IpNetwork: ToSql<Inet, DB>,
{
fn to_sql<W: std::io::Write>(
&self,
out: &mut Output<W, DB>,
) -> serialize::Result {
IpNetwork::V6(Ipv6Network::from(self.0)).to_sql(out)
}
}

impl<DB> FromSql<Inet, DB> for Ipv6Addr
where
DB: Backend,
IpNetwork: FromSql<Inet, DB>,
{
fn from_sql(bytes: RawValue<DB>) -> deserialize::Result<Self> {
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()
)))
}
}
}
}
1 change: 1 addition & 0 deletions nexus/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 16 additions & 27 deletions nexus/src/db/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions nexus/src/external_api/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -224,7 +224,7 @@ impl From<model::Rack> for Rack {
pub struct Sled {
#[serde(flatten)]
pub identity: IdentityMetadata,
pub service_address: SocketAddr,
pub service_address: SocketAddrV6,
}

impl From<model::Sled> for Sled {
Expand Down
3 changes: 2 additions & 1 deletion nexus/src/internal_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ 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;

/// Sent by a sled agent on startup to Nexus to request further instruction
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct SledAgentStartupInfo {
/// the address of the sled agent's API endpoint
pub sa_address: SocketAddr,
pub sa_address: SocketAddrV6,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I don't quite understand: I expected this to trigger a change to the generated OpenAPI spec, and a corresponding change in the sled agents (simulated and real) to use a v6 address. But it looks like the generated type for this represents this field with a string:

sa_address: sa_address.to_string(),

so the distinction between v4+v6 and v6-only is not there. I gather that's because json-schema says the schema for an IP address is just a string:
https://docs.rs/schemars/latest/src/schemars/json_schema_impls/primitives.rs.html#54

Kind of a bummer...but well beyond the scope of this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's definitely a bummer. It seems that a large number of types in the generated clients are serialized as a string, and require that explicit .to_string() call. I'm not sure why, since it seems like the serialization should happen after the type is constructed. But yeah, beyond the scope of this PR. Is there an issue or something you'd like me to create for this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Thanks.

}

/// Sent by a sled agent on startup to Nexus to request further instruction
Expand Down
3 changes: 2 additions & 1 deletion nexus/src/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down