Skip to content
Draft
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions common/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,51 @@ impl Iterator for IpRangeIter {
}
}

/// Trait for any IP address type.
pub trait Ip:
Clone
+ Copy
+ std::fmt::Debug
+ Diffable
+ Eq
+ JsonSchema
+ std::hash::Hash
+ PartialOrd
+ PartialEq
+ Ord
+ Serialize
{
}
impl Ip for Ipv4Addr {}
impl Ip for Ipv6Addr {}
impl Ip for IpAddr {}

/// An IP address of a specific version, IPv4 or IPv6.
pub trait ConcreteIp: Ip {
fn into_ipaddr(self) -> IpAddr;
fn into_ipnet(self) -> ipnetwork::IpNetwork;
}

impl ConcreteIp for Ipv4Addr {
fn into_ipaddr(self) -> IpAddr {
IpAddr::V4(self)
}

fn into_ipnet(self) -> ipnetwork::IpNetwork {
ipnetwork::IpNetwork::V4(ipnetwork::Ipv4Network::from(self))
}
}

impl ConcreteIp for Ipv6Addr {
fn into_ipaddr(self) -> IpAddr {
IpAddr::V6(self)
}

fn into_ipnet(self) -> ipnetwork::IpNetwork {
ipnetwork::IpNetwork::V6(ipnetwork::Ipv6Network::from(self))
}
}

#[cfg(test)]
mod test {
use serde_json::json;
Expand Down
116 changes: 109 additions & 7 deletions common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use omicron_uuid_kinds::InstanceUuid;
use omicron_uuid_kinds::SledUuid;
use oxnet::IpNet;
use oxnet::Ipv4Net;
use oxnet::Ipv6Net;
use parse_display::Display;
use parse_display::FromStr;
use rand::Rng;
Expand All @@ -43,6 +44,7 @@ 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::ParseIntError;
use std::num::{NonZeroU16, NonZeroU32};
use std::ops::Deref;
Expand Down Expand Up @@ -2608,18 +2610,118 @@ pub struct InstanceNetworkInterface {
/// The MAC address assigned to this interface.
pub mac: MacAddr,

/// The IP address assigned to this interface.
// TODO-correctness: We need to split this into an optional V4 and optional
// V6 address, at least one of which must be specified.
pub ip: IpAddr,
/// True if this interface is the primary for the instance to which it's
/// attached.
pub primary: bool,

/// A set of additional networks that this interface may send and
/// The VPC-private IP stack for this interface.
pub ip_stack: PrivateIpStack,
}

/// The VPC-private IP stack for a network interface.
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum PrivateIpStack {
/// The interface has only an IPv4 stack.
V4(PrivateIpv4Stack),
/// The interface has only an IPv6 stack.
V6(PrivateIpv6Stack),
/// The interface is dual-stack IPv4 and IPv6.
DualStack { v4: PrivateIpv4Stack, v6: PrivateIpv6Stack },
}

impl PrivateIpStack {
/// Return the IPv4 stack, if it exists.
pub fn ipv4_stack(&self) -> Option<&PrivateIpv4Stack> {
match self {
PrivateIpStack::V4(v4) | PrivateIpStack::DualStack { v4, .. } => {
Some(v4)
}
PrivateIpStack::V6(_) => None,
}
}

/// Return the VPC-private IPv4 address, if it exists.
pub fn ipv4_addr(&self) -> Option<&Ipv4Addr> {
self.ipv4_stack().map(|s| &s.ip)
}

/// Return the IPv6 stack, if it exists.
pub fn ipv6_stack(&self) -> Option<&PrivateIpv6Stack> {
match self {
PrivateIpStack::V6(v6) | PrivateIpStack::DualStack { v6, .. } => {
Some(v6)
}
PrivateIpStack::V4(_) => None,
}
}

/// Return the VPC-private IPv6 address, if it exists.
pub fn ipv6_addr(&self) -> Option<&Ipv6Addr> {
self.ipv6_stack().map(|s| &s.ip)
}

/// Return true if this is an IPv4-only stack, and false otherwise.
pub fn is_ipv4_only(&self) -> bool {
matches!(self, PrivateIpStack::V4(_))
}

/// Return true if this is an IPv6-only stack, and false otherwise.
pub fn is_ipv6_only(&self) -> bool {
matches!(self, PrivateIpStack::V6(_))
}

/// Return true if this is dual-stack, and false otherwise.
pub fn is_dual_stack(&self) -> bool {
matches!(self, PrivateIpStack::DualStack { .. })
}

/// Return the IPv4 transit IPs, if they exist.
pub fn ipv4_transit_ips(&self) -> Option<&[Ipv4Net]> {
self.ipv4_stack().map(|c| c.transit_ips.as_slice())
}

/// Return the IPv6 transit IPs, if they exist.
pub fn ipv6_transit_ips(&self) -> Option<&[Ipv6Net]> {
self.ipv6_stack().map(|c| c.transit_ips.as_slice())
}

/// Return all transit IPs, of any IP version.
pub fn all_transit_ips(&self) -> impl Iterator<Item = IpNet> + '_ {
let v4 = self
.ipv4_transit_ips()
.into_iter()
.flatten()
.copied()
.map(Into::into);
let v6 = self
.ipv6_transit_ips()
.into_iter()
.flatten()
.copied()
.map(Into::into);
v4.chain(v6)
}
}

/// The VPC-private IPv4 stack for a network interface
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
pub struct PrivateIpv4Stack {
/// The VPC-private IPv4 address for the interface.
pub ip: Ipv4Addr,
/// A set of additional IPv4 networks that this interface may send and
/// receive traffic on.
#[serde(default)]
pub transit_ips: Vec<IpNet>,
pub transit_ips: Vec<Ipv4Net>,
}

/// The VPC-private IPv6 stack for a network interface
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
pub struct PrivateIpv6Stack {
/// The VPC-private IPv6 address for the interface.
pub ip: Ipv6Addr,
/// A set of additional IPv6 networks that this interface may send and
/// receive traffic on.
pub transit_ips: Vec<Ipv6Net>,
}

#[derive(
Expand Down
54 changes: 10 additions & 44 deletions common/src/api/internal/shared/external_ip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

pub mod v1;

use crate::address::ConcreteIp;
use crate::address::Ip;
use crate::address::NUM_SOURCE_NAT_PORTS;
use daft::Diffable;
use itertools::Either;
Expand All @@ -17,28 +19,6 @@ use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;

/// Trait for any IP address type.
///
/// This is used to constrain the external addressing types below.
pub trait Ip:
Clone
+ Copy
+ std::fmt::Debug
+ Diffable
+ Eq
+ JsonSchema
+ std::hash::Hash
+ PartialOrd
+ PartialEq
+ Ord
+ Serialize
+ SnatSchema
{
}
impl Ip for Ipv4Addr {}
impl Ip for Ipv6Addr {}
impl Ip for IpAddr {}

/// Helper trait specifying the name of the JSON Schema for a `SourceNatConfig`.
///
/// This exists so we can use a generic type and have the names of the concrete
Expand All @@ -65,23 +45,6 @@ impl SnatSchema for IpAddr {
}
}

/// An IP address of a specific version, IPv4 or IPv6.
pub trait ConcreteIp: Ip {
fn into_ipaddr(self) -> IpAddr;
}

impl ConcreteIp for Ipv4Addr {
fn into_ipaddr(self) -> IpAddr {
IpAddr::V4(self)
}
}

impl ConcreteIp for Ipv6Addr {
fn into_ipaddr(self) -> IpAddr {
IpAddr::V6(self)
}
}

/// Helper trait specifying the name of the JSON Schema for an
/// `ExternalIpConfig` object.
///
Expand Down Expand Up @@ -140,7 +103,7 @@ pub struct SourceNatConfig<T: Ip> {
// should be fine as long as we're using JSON or similar formats.)
#[derive(Deserialize, JsonSchema)]
#[serde(remote = "SourceNatConfig")]
struct SourceNatConfigShadow<T: Ip> {
struct SourceNatConfigShadow<T: Ip + SnatSchema> {
/// The external address provided to the instance or service.
ip: T,
/// The first port used for source NAT, inclusive.
Expand All @@ -155,7 +118,7 @@ pub type SourceNatConfigGeneric = SourceNatConfig<IpAddr>;

impl<T> JsonSchema for SourceNatConfig<T>
where
T: Ip,
T: Ip + SnatSchema,
{
fn schema_name() -> String {
<T as SnatSchema>::json_schema_name()
Expand All @@ -170,7 +133,10 @@ where

// We implement `Deserialize` manually to add validity checking on the port
// range.
impl<'de, T: Ip + Deserialize<'de>> Deserialize<'de> for SourceNatConfig<T> {
impl<'de, T> Deserialize<'de> for SourceNatConfig<T>
where
T: Ip + SnatSchema + Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
Expand Down Expand Up @@ -346,7 +312,7 @@ pub type ExternalIpv6Config = ExternalIps<Ipv6Addr>;
#[serde(remote = "ExternalIps")]
struct ExternalIpsShadow<T>
where
T: ConcreteIp,
T: ConcreteIp + SnatSchema + ExternalIpSchema,
{
/// Source NAT configuration, for outbound-only connectivity.
source_nat: Option<SourceNatConfig<T>>,
Expand Down Expand Up @@ -384,7 +350,7 @@ impl JsonSchema for ExternalIpv6Config {
// SNAT, ephemeral, and floating IPs in the input data.
impl<'de, T> Deserialize<'de> for ExternalIps<T>
where
T: ConcreteIp + Deserialize<'de>,
T: ConcreteIp + SnatSchema + ExternalIpSchema + Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down
2 changes: 1 addition & 1 deletion end-to-end-tests/src/instance_launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async fn instance_launch() -> Result<()> {
name: disk_name.clone(),
}),
disks: Vec::new(),
network_interfaces: InstanceNetworkInterfaceAttachment::Default,
network_interfaces: InstanceNetworkInterfaceAttachment::DefaultIpv4,
external_ips: vec![ExternalIpCreate::Ephemeral { pool: None }],
user_data: String::new(),
ssh_public_keys: Some(vec![oxide_client::types::NameOrId::Name(
Expand Down
Loading
Loading