diff --git a/.github/buildomat/common.sh b/.github/buildomat/common.sh index cfb0407..b02113c 100644 --- a/.github/buildomat/common.sh +++ b/.github/buildomat/common.sh @@ -1,9 +1,19 @@ #!/bin/bash +# The tofino2 has 20 stages, and the current sidecar.p4 needs all 20 of them. +# Specifying the number of stages isn't strictly necessary, but it allows us to +# track when we exceed the current ceiling. The underlying intention is to grow +# deliberately and thoughtfully, given the limited space on the ASIC. +# +# Note: this now seems silly since we have maxed out the number of stages, but +# we want to leave this check and note in place should we ever find a way to +# reduce our footprint below 20 stages. +TOFINO_STAGES=20 + # These describe which version of the SDE to download and where to find it -SDE_COMMIT=eb08b0b0e55792bbba206202603da0fd791ea555 -SDE_PKG_SHA256=fbaea9ccd7ece70cdc20207e2f9b4750243468c6fe95c6a1fae9734201307db7 -SDE_DEB_SHA256=14050fd9a08b8b0b2ec7c827fa06a51516369a81bcf616d16699302b58d8ea9a +SDE_COMMIT=e61fe02c3c1c384b2e212c90177fcea76a31fd4e +SDE_PKG_SHA256=8a87a9b0bed3c5440a173a7a41361bdeb5e7a848882da6b4aa48c8fb0043f3bd +SDE_DEB_SHA256=a292e2dd5311647c4852bb41f2532dd1fdf30325b6d04cccb7e85b873e521d5f [ `uname -s` == "SunOS" ] && SERIES=illumos [ `uname -s` == "SunOS" ] || SERIES=linux diff --git a/.github/buildomat/jobs/image.sh b/.github/buildomat/jobs/image.sh index 89986f6..02d128c 100755 --- a/.github/buildomat/jobs/image.sh +++ b/.github/buildomat/jobs/image.sh @@ -101,11 +101,7 @@ pfexec chown "$UID" /out banner "P4 Codegen" # Add gcc-12 so the p4 compiler can find cpp -# The tofino2 has 20 stages, but the current sidecar.p4 will fit into 19. We -# add the "--stages 19" here to detect if/when the program grows beyond that -# limit. It's not necessarily a problem if we grow, but given the limited space -# on the ASIC, we want to grow deliberatately and thoughtfully. -PATH=/opt/gcc-12/bin:$PATH cargo xtask codegen --stages 19 +PATH=/opt/gcc-12/bin:$PATH cargo xtask codegen --stages $TOFINO_STAGES # Preserve all the diagnostics spit out by the compiler mkdir -p /out/p4c-diags diff --git a/.github/buildomat/jobs/packet-test.sh b/.github/buildomat/jobs/packet-test.sh index aad8689..1c22ec2 100755 --- a/.github/buildomat/jobs/packet-test.sh +++ b/.github/buildomat/jobs/packet-test.sh @@ -77,7 +77,7 @@ export SDE=/opt/oxide/tofino_sde banner "Build" cargo build --features=tofino_asic --bin dpd --bin swadm -cargo xtask codegen --stages 19 +cargo xtask codegen --stages $TOFINO_STAGES banner "Test" sudo -E ./tools/veth_setup.sh diff --git a/asic/src/softnpu/table.rs b/asic/src/softnpu/table.rs index d4ccc96..3f8a23e 100644 --- a/asic/src/softnpu/table.rs +++ b/asic/src/softnpu/table.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use slog::{error, trace}; use softnpu_lib::{ManagementRequest, TableAdd, TableRemove}; @@ -31,6 +31,8 @@ const LOCAL_V6: &str = "ingress.local.local_v6"; const LOCAL_V4: &str = "ingress.local.local_v4"; const NAT_V4: &str = "ingress.nat.nat_v4"; const NAT_V6: &str = "ingress.nat.nat_v6"; +const ATTACHED_SUBNET_V4: &str = "ingress.attached.attached_subnet_v4"; +const ATTACHED_SUBNET_V6: &str = "ingress.attached.attached_subnet_v6"; const _NAT_ICMP_V6: &str = "ingress.nat.nat_icmp_v6"; const _NAT_ICMP_V4: &str = "ingress.nat.nat_icmp_v4"; const RESOLVER_V4: &str = "ingress.resolver.resolver_v4"; @@ -54,12 +56,16 @@ const ARP: &str = "pipe.Ingress.l3_router.Router4.Arp.tbl"; const DPD_MAC_REWRITE: &str = "pipe.Ingress.mac_rewrite.mac_rewrite"; const NAT_INGRESS4: &str = "pipe.Ingress.nat_ingress.ingress_ipv4"; const NAT_INGRESS6: &str = "pipe.Ingress.nat_ingress.ingress_ipv6"; +const ATTACHED_SUBNET_INGRESS4: &str = + "pipe.Ingress.attached_subnet_ingress.attached_subnets_v4"; +const ATTACHED_SUBNET_INGRESS6: &str = + "pipe.Ingress.attached_subnet_ingress.attached_subnets_v6"; // All tables are defined to be 1024 entries deep const TABLE_SIZE: usize = 4096; impl TableOps for Table { - fn new(_hdl: &Handle, name: &str) -> AsicResult { + fn new(hdl: &Handle, name: &str) -> AsicResult
{ // TODO just mapping sidecar.p4 things onto simplified sidecar-lite.p4 // things to get started. let (id, dpd_id) = match name { @@ -84,8 +90,16 @@ impl TableOps for Table { } NAT_INGRESS4 => (Some(NAT_V4.into()), Some(NAT_INGRESS4.into())), NAT_INGRESS6 => (Some(NAT_V6.into()), Some(NAT_INGRESS6.into())), + ATTACHED_SUBNET_INGRESS4 => ( + Some(ATTACHED_SUBNET_V4.into()), + Some(ATTACHED_SUBNET_INGRESS4.into()), + ), + ATTACHED_SUBNET_INGRESS6 => ( + Some(ATTACHED_SUBNET_V6.into()), + Some(ATTACHED_SUBNET_INGRESS6.into()), + ), x => { - println!("TABLE NOT HANDLED {x}"); + error!(hdl.log, "TABLE NOT HANDLED {x}"); (None, None) } }; @@ -382,7 +396,9 @@ impl TableOps for Table { ("rewrite", params) } (NAT_INGRESS4, "forward_ipv4_to") - | (NAT_INGRESS6, "forward_ipv6_to") => { + | (NAT_INGRESS6, "forward_ipv6_to") + | (ATTACHED_SUBNET_INGRESS4, "forward_to_v4") + | (ATTACHED_SUBNET_INGRESS6, "forward_to_v6") => { let mut target = Vec::new(); let mut vni = Vec::new(); let mut mac = Vec::new(); @@ -463,7 +479,7 @@ impl TableOps for Table { ("forward_to_sled", params) } (tbl, x) => { - println!("ACTION NOT HANDLED {tbl} {x}"); + error!(hdl.log, "ACTION NOT HANDLED {tbl} {x}"); return Ok(()); } }; @@ -610,7 +626,7 @@ fn keyset_data(match_data: Vec, table: &str) -> Vec { MatchEntryValue::Lpm(x) => { let mut data: Vec = Vec::new(); match table { - ROUTER_V4_IDX => { + ROUTER_V4_IDX | ATTACHED_SUBNET_V4 => { // prefix for longest prefix match operation // "dst_addr" => hdr.ipv4.dst: lpm => bit<32> serialize_value_type_be(&x.prefix, &mut data); diff --git a/common/src/attached_subnet.rs b/common/src/attached_subnet.rs new file mode 100644 index 0000000..1cb28e7 --- /dev/null +++ b/common/src/attached_subnet.rs @@ -0,0 +1,26 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use oxnet::IpNet; +use std::fmt; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::network::InstanceTarget; + +/** represents an external subnet mapping */ +#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct AttachedSubnetEntry { + pub subnet: IpNet, + pub tgt: InstanceTarget, +} + +impl fmt::Display for AttachedSubnetEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}->{}", self.subnet, self.tgt) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index f09c6f8..4294006 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::collections::BTreeSet; use std::convert::TryFrom; @@ -11,6 +11,7 @@ use std::sync::Once; use std::time::Duration; use std::time::Instant; +pub mod attached_subnet; pub mod counters; pub mod logging; pub mod nat; diff --git a/common/src/nat.rs b/common/src/nat.rs index 3a3842a..8782aba 100644 --- a/common/src/nat.rs +++ b/common/src/nat.rs @@ -2,97 +2,14 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company -use std::fmt; use std::net::{Ipv4Addr, Ipv6Addr}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::network::MacAddr; - -/// A Geneve Virtual Network Identifier. -/// -/// A Geneve VNI is a 24-bit value used to identify virtual networks -/// encapsulated using the Generic Network Virtualization Encapsulation (Geneve) -/// protocol (RFC 8926). -#[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - JsonSchema, - PartialEq, - PartialOrd, - Ord, - Serialize, -)] -#[serde(try_from = "u32")] -pub struct Vni(u32); - -impl Vni { - const MAX_VNI: u32 = 0x00FF_FFFF; - const ERR_MSG: &'static str = "VNI out of 24-bit range"; - - /// Construct a new VNI, validating that it's a valid 24-bit value. - pub const fn new(vni: u32) -> Option { - // bool.then_some is not const, unforunately - if vni <= Self::MAX_VNI { - Some(Self(vni)) - } else { - None - } - } - - /// Return the VNI as a u32. - pub const fn as_u32(&self) -> u32 { - self.0 - } -} - -impl core::convert::TryFrom for Vni { - type Error = &'static str; - - fn try_from(vni: u32) -> Result { - Self::new(vni).ok_or(Self::ERR_MSG) - } -} - -impl core::str::FromStr for Vni { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s.parse::().map(Vni::new) { - Err(_) | Ok(None) => Err(Self::ERR_MSG), - Ok(Some(vni)) => Ok(vni), - } - } -} - -impl fmt::Display for Vni { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/** represents an internal NAT target */ -#[derive( - Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, -)] -pub struct NatTarget { - pub internal_ip: Ipv6Addr, - pub inner_mac: MacAddr, - pub vni: Vni, -} - -impl fmt::Display for NatTarget { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}/{}/{}", self.internal_ip, self.inner_mac, self.vni) - } -} +use crate::network::NatTarget; /** represents an IPv6 NAT reservation */ #[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema)] @@ -130,7 +47,7 @@ impl PartialEq for Ipv4Nat { #[cfg(test)] mod tests { - use super::Vni; + use crate::network::Vni; #[test] fn test_vni() { diff --git a/common/src/network.rs b/common/src/network.rs index f34ac11..20f993c 100644 --- a/common/src/network.rs +++ b/common/src/network.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::fmt; use std::net::Ipv6Addr; @@ -248,6 +248,125 @@ pub fn validate_vlan(id: impl Into) -> Result<(), VlanError> { } } +/// A Geneve Virtual Network Identifier. +/// +/// A Geneve VNI is a 24-bit value used to identify virtual networks +/// encapsulated using the Generic Network Virtualization Encapsulation (Geneve) +/// protocol (RFC 8926). +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + JsonSchema, + PartialEq, + PartialOrd, + Ord, + Serialize, +)] +#[serde(try_from = "u32")] +pub struct Vni(u32); + +impl Vni { + pub const MAX_VNI: u32 = 0x00FF_FFFF; + const ERR_MSG: &'static str = "VNI out of 24-bit range"; + + /// Construct a new VNI, validating that it's a valid 24-bit value. + pub const fn new(vni: u32) -> Option { + // bool.then_some is not const, unforunately + if vni <= Self::MAX_VNI { + Some(Self(vni)) + } else { + None + } + } + + /// Return the VNI as a u32. + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +impl core::convert::TryFrom for Vni { + type Error = &'static str; + + fn try_from(vni: u32) -> Result { + Self::new(vni).ok_or(Self::ERR_MSG) + } +} + +impl core::str::FromStr for Vni { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.parse::().map(Vni::new) { + Err(_) | Ok(None) => Err(Self::ERR_MSG), + Ok(Some(vni)) => Ok(vni), + } + } +} + +impl fmt::Display for Vni { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/** represents an internal target for either NAT or an external subnet mapping */ +#[derive( + Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, +)] +pub struct InstanceTarget { + pub internal_ip: Ipv6Addr, + pub inner_mac: MacAddr, + pub vni: Vni, +} + +impl fmt::Display for InstanceTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}/{}", self.internal_ip, self.inner_mac, self.vni) + } +} + +/// represents an internal NAT target +// This is identical to the more generically named InstanceTarget, which should +// be used for new APIs. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize, +)] +pub struct NatTarget { + pub internal_ip: Ipv6Addr, + pub inner_mac: MacAddr, + pub vni: Vni, +} + +impl fmt::Display for NatTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}/{}", self.internal_ip, self.inner_mac, self.vni) + } +} + +impl From for InstanceTarget { + fn from(value: NatTarget) -> Self { + InstanceTarget { + internal_ip: value.internal_ip, + inner_mac: value.inner_mac, + vni: value.vni, + } + } +} + +impl From for NatTarget { + fn from(value: InstanceTarget) -> Self { + NatTarget { + internal_ip: value.internal_ip, + inner_mac: value.inner_mac, + vni: value.vni, + } + } +} #[cfg(test)] mod tests { use super::Ipv6Addr; diff --git a/dpd-api/src/lib.rs b/dpd-api/src/lib.rs index 9f2f9da..385b3cc 100644 --- a/dpd-api/src/lib.rs +++ b/dpd-api/src/lib.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! DPD endpoint definitions. @@ -12,9 +12,10 @@ use std::{ }; use common::{ + attached_subnet::AttachedSubnetEntry, counters::{FecRSCounters, PcsCounters, RMonCounters, RMonCountersAll}, - nat::{Ipv4Nat, Ipv6Nat, NatTarget}, - network::MacAddr, + nat::{Ipv4Nat, Ipv6Nat}, + network::{InstanceTarget, MacAddr, NatTarget}, ports::{ Ipv4Entry, Ipv6Entry, PortFec, PortId, PortPrbsMode, PortSpeed, TxEq, TxEqSwHw, @@ -37,7 +38,7 @@ use dropshot::{ Query, RequestContext, ResultsPage, TypedBody, }; use dropshot_api_manager_types::api_versions; -use oxnet::{Ipv4Net, Ipv6Net}; +use oxnet::{IpNet, Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use transceiver_controller::{ @@ -56,6 +57,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (3, ATTACHED_SUBNETS), (2, DUAL_STACK_NAT_WORKFLOW), (1, INITIAL), ]); @@ -1153,6 +1155,75 @@ pub trait DpdApi { rqctx: RequestContext, ) -> Result; + /** + * Get all of the external subnets with internal mappings + */ + #[endpoint { + method = GET, + path = "/attached_subnet", + versions = VERSION_ATTACHED_SUBNETS.., + }] + async fn attached_subnet_list( + rqctx: RequestContext, + query: Query>, + ) -> Result>, HttpError>; + + /** + * Get the mapping for the given external subnet. + */ + #[endpoint { + method = GET, + path = "/attached_subnet/{subnet}", + versions = VERSION_ATTACHED_SUBNETS.., + }] + async fn attached_subnet_get( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError>; + + /** + * Add a mapping to an internal target for an external subnet address. + * + * These identify the gimlet on which a guest is running, and gives OPTE the + * information it needs to identify the guest VM that uses the external + * subnet. + */ + #[endpoint { + method = PUT, + path = "/attached_subnet/{subnet}", + versions = VERSION_ATTACHED_SUBNETS.., + }] + async fn attached_subnet_create( + rqctx: RequestContext, + path: Path, + target: TypedBody, + ) -> Result; + + /** + * Delete the mapping for an external subnet + */ + #[endpoint { + method = DELETE, + path = "/attached_subnet/{subnet}", + versions = VERSION_ATTACHED_SUBNETS.., + }] + async fn attached_subnet_delete( + rqctx: RequestContext, + path: Path, + ) -> Result; + + /** + * Clear all external subnet mappings + */ + #[endpoint { + method = DELETE, + path = "/attached_subnet", + versions = VERSION_ATTACHED_SUBNETS.., + }] + async fn attached_subnet_reset( + rqctx: RequestContext, + ) -> Result; + /** * Clear all settings associated with a specific tag. * @@ -2013,6 +2084,12 @@ pub struct RoutePathV4 { pub cidr: Ipv4Net, } +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct SubnetPath { + /// The external subnet in CIDR notation being managed + pub subnet: IpNet, +} + /// Represents a single subnet->target route entry #[derive(Deserialize, Serialize, JsonSchema)] pub struct RouteTargetIpv4Path { @@ -2060,6 +2137,15 @@ pub struct Ipv6RouteToken { pub cidr: Ipv6Net, } +/** + * Represents a cursor into a paginated request for the contents of the + * external subnets table. + */ +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct AttachedSubnetToken { + pub cidr: IpNet, +} + #[derive(Deserialize, Serialize, JsonSchema)] pub struct PortIpv4Path { pub port: String, diff --git a/dpd-client/src/lib.rs b/dpd-client/src/lib.rs index 0bb4a7c..0e8c414 100644 --- a/dpd-client/src/lib.rs +++ b/dpd-client/src/lib.rs @@ -2,13 +2,12 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Client library for the Dendrite data plane daemon. pub use common::ROLLBACK_FAILURE_ERROR_CODE; use common::counters; -use common::nat; use common::network; use common::ports; use slog::Logger; @@ -307,25 +306,41 @@ impl From for types::RMonCounters { } } -impl TryFrom for nat::Vni { +impl TryFrom for network::Vni { type Error = String; - fn try_from(t: types::Vni) -> Result { - nat::Vni::new(t.0) + fn try_from(t: types::Vni) -> Result { + network::Vni::new(t.0) .ok_or_else(|| String::from("VNI is out of valid range")) } } -impl From for types::Vni { - fn from(t: nat::Vni) -> types::Vni { +impl From for types::Vni { + fn from(t: network::Vni) -> types::Vni { types::Vni(t.as_u32()) } } -impl TryFrom for nat::NatTarget { +impl TryFrom for network::InstanceTarget { type Error = String; - fn try_from(t: types::NatTarget) -> Result { + fn try_from( + t: types::InstanceTarget, + ) -> Result { + Ok(Self { + internal_ip: t.internal_ip, + inner_mac: t.inner_mac.into(), + vni: t.vni.try_into()?, + }) + } +} + +impl TryFrom for network::NatTarget { + type Error = String; + + fn try_from( + t: types::NatTarget, + ) -> Result { Ok(Self { internal_ip: t.internal_ip, inner_mac: t.inner_mac.into(), diff --git a/dpd-client/tests/integration_tests/attached_subnet.rs b/dpd-client/tests/integration_tests/attached_subnet.rs new file mode 100644 index 0000000..a9a3c48 --- /dev/null +++ b/dpd-client/tests/integration_tests/attached_subnet.rs @@ -0,0 +1,684 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; +use std::sync::Arc; + +use ::common::network::MacAddr; +use ::common::network::Vni; +use anyhow::Context; +use dpd_client::ClientInfo; +use dpd_client::types; +use oxnet::IpNet; +use packet::Endpoint; +use packet::eth; +use packet::geneve; +use packet::ipv6; +use packet::udp; + +use crate::integration_tests::common; +use crate::integration_tests::common::prelude::*; + +#[tokio::test] +#[ignore] +async fn test_api() -> TestResult { + let switch = &*get_switch().await; + + let attached_subnet = "192.168.1.1/24".parse()?; + let inner_mac = MacAddr::new(2, 4, 6, 8, 10, 12); + let vni = Vni::new(222).unwrap(); + let internal_ip = "fd00:1122:7788:0101::4".parse::().unwrap(); + let tgt = types::InstanceTarget { + internal_ip, + inner_mac: inner_mac.into(), + vni: vni.into(), + }; + + switch + .client + .attached_subnet_create(&attached_subnet, &tgt) + .await + .expect("Should be able to add valid external subnet entry"); + assert_eq!( + switch + .client + .attached_subnet_get(&attached_subnet) + .await + .unwrap() + .into_inner(), + tgt, + "Failed to retrieve existing external subnet entry", + ); + // Remove non-existent entry and be sure it fails + switch + .client + .attached_subnet_delete(&attached_subnet) + .await + .expect("Failed to delete existing NAT entry"); + + Ok(()) +} + +#[derive(Debug)] +enum L4Protocol { + Tcp, + Udp, + Icmp, +} + +#[derive(Debug)] +struct ExternalTest { + // packet source/destination from the vm client's perspective + pkt_src_ip: String, + pkt_src_mac: String, + pkt_src_port: u16, + pkt_dst_ip: String, + pkt_dst_mac: String, + pkt_dst_port: u16, + + // uplink network info + uplink_port: PhysPort, + uplink_route: String, // subnet to which the switch is connected + upstream_router_ip: String, // ip address of the upstream router + upstream_router_mac: String, // mac address of the upstream router + + // External subnet mapped to an instance + attached_subnet: String, + // MAC address of the nic inside the targetted instance + instance_mac: String, + + // local routing info - how OPTE routes client packet to switch logic + gimlet_ip: String, // ip address of the gimlet + gimlet_mac: String, // mac address of the gimlet + backplane_port: PhysPort, // switch port to which the gimlet is attached + backplane_ip: String, // ip address of the gimlet's switch port + + l4_protocol: L4Protocol, + geneve_vni: u32, +} + +// On egress, the switch decapsulates the geneve packet just as it does for NAT. +// We then route the packet based on the target IP in the internal header.` +async fn test_egress(switch: &Switch, test: &ExternalTest) -> TestResult { + let router_mac = test.upstream_router_mac.parse()?; + let attached_subnet = test.attached_subnet.parse()?; + + // set up the switch internals so we can route the packet from the gimlet + // port to the uplink switch port + let (port_id, link_id) = switch.link_id(test.backplane_port).unwrap(); + let backplane_ip = test.backplane_ip.parse::().unwrap(); + let entry = types::Ipv6Entry { + addr: backplane_ip, + tag: switch.client.inner().tag.clone(), + }; + switch + .client + .link_ipv6_create(&port_id, &link_id, &entry) + .await + .unwrap(); + + // populate the ndp/arp table with the upstream router's mac and IP. + if test.upstream_router_ip.parse::().is_ok() { + common::set_route_ipv4( + switch, + &test.uplink_route, + test.uplink_port, + &test.upstream_router_ip, + ) + .await?; + common::add_arp_ipv4(switch, &test.upstream_router_ip, router_mac) + .await?; + } else if test.upstream_router_ip.parse::().is_ok() { + common::set_route_ipv6( + switch, + &test.uplink_route, + test.uplink_port, + &test.upstream_router_ip, + ) + .await + .context("setting ipv6 route")?; + common::add_neighbor_ipv6(switch, &test.upstream_router_ip, router_mac) + .await?; + } + + let tgt = types::InstanceTarget { + internal_ip: test.gimlet_ip.parse().unwrap(), + inner_mac: test.instance_mac.parse::()?.into(), + vni: test.geneve_vni.into(), + }; + switch + .client + .attached_subnet_create(&attached_subnet, &tgt) + .await + .unwrap(); + + // Build a packet coming from an instance on an external subnet. + // The inner packet may be IPv4 or IPv6 with a source address on the + // external subnet, and the outer packet will be IPv6 coming from a gimlet. + let src = + Endpoint::parse(&test.pkt_src_mac, &test.pkt_src_ip, test.pkt_src_port) + .unwrap(); + let dst = + Endpoint::parse(&test.pkt_dst_mac, &test.pkt_dst_ip, test.pkt_dst_port) + .unwrap(); + let payload_pkt = match test.l4_protocol { + L4Protocol::Udp => common::gen_udp_packet(src, dst), + L4Protocol::Tcp => common::gen_tcp_packet(src, dst), + L4Protocol::Icmp => common::gen_icmp_packet(src, dst), + }; + + let switch_mac = switch + .get_port_mac(test.backplane_port) + .unwrap() + .to_string(); + let payload = payload_pkt.deparse().unwrap().to_vec(); + let to_send = common::gen_geneve_packet( + Endpoint::parse(&test.gimlet_mac, &test.gimlet_ip, 3333).unwrap(), + Endpoint::parse( + &switch_mac, + &test.backplane_ip, + geneve::GENEVE_UDP_PORT, + ) + .unwrap(), + match attached_subnet { + IpNet::V4(_) => eth::ETHER_IPV4, + IpNet::V6(_) => eth::ETHER_IPV6, + }, + test.geneve_vni, + &[], + &payload[14..], + ); + + let mut to_recv = + common::gen_packet_routed(switch, test.uplink_port, &payload_pkt); + eth::EthHdr::rewrite_dmac(&mut to_recv, router_mac); + + let send = TestPacket { + packet: Arc::new(to_send), + port: test.backplane_port, + }; + let expected = TestPacket { + packet: Arc::new(to_recv), + port: test.uplink_port, + }; + + switch.packet_test(vec![send], vec![expected]) +} + +// On ingress, we wrap the incoming packet with a geneve header and forward to +// the correct gimlet. +async fn test_ingress(switch: &Switch, test: &ExternalTest) -> TestResult { + let attached_subnet = test.attached_subnet.parse()?; + let gimlet_mac = test.gimlet_mac.parse().unwrap(); + let (port_id, link_id) = switch.link_id(test.backplane_port).unwrap(); + let backplane_ip = test.backplane_ip.parse::().unwrap(); + let entry = types::Ipv6Entry { + addr: backplane_ip, + tag: switch.client.inner().tag.clone(), + }; + + // Create the backplane port on the sidecar, linking to the gimlet + switch + .client + .link_ipv6_create(&port_id, &link_id, &entry) + .await + .unwrap(); + let cidr = + oxnet::Ipv6Net::new(test.gimlet_ip.parse().unwrap(), 64).unwrap(); + let route = types::Ipv6RouteUpdate { + cidr, + target: types::Ipv6Route { + tag: switch.client.inner().tag.clone(), + port_id, + link_id, + tgt_ip: test.gimlet_ip.parse().unwrap(), + vlan_id: None, + }, + replace: false, + }; + + // Add the route to the gimlet over the backplane, and populate the neighbor + // table with its mac address. + switch.client.route_ipv6_set(&route).await.unwrap(); + common::add_neighbor_ipv6(switch, &test.gimlet_ip, gimlet_mac).await?; + + // Set up an external subnet owned by an instance on the test gimlet + let tgt = types::InstanceTarget { + internal_ip: test.gimlet_ip.parse().unwrap(), + inner_mac: test.instance_mac.parse::()?.into(), + vni: test.geneve_vni.into(), + }; + switch + .client + .attached_subnet_create(&attached_subnet, &tgt) + .await + .expect("Should be able to add valid external subnet entry"); + + // Build a packet coming from an external host via an upstream router, to an + // uplinked switch port. The packet is addressed to an address on the + // external subnet. + let load = vec![0xaau8, 0xbb, 0xcc, 0xdd, 0xee]; + let switch_mac = switch.get_port_mac(test.uplink_port).unwrap().to_string(); + let ingress_pkt = common::gen_udp_packet_loaded( + Endpoint::parse( + &test.upstream_router_mac, + &test.pkt_src_ip, + test.pkt_src_port, + ) + .unwrap(), + Endpoint::parse(&switch_mac, &test.pkt_dst_ip, test.pkt_dst_port) + .unwrap(), + &load, + ); + + // Convert the packet into a binary payload that will be encapsulated into a + // geneve packet. We also rewrite the destination mac of the packet to + // match the mac of the targetted instance, mirroring the rewrite done by + // the p4 code. + let ingress_payload = { + let mut encapped = ingress_pkt.clone(); + let eth = encapped.hdrs.eth_hdr.as_mut().unwrap(); + eth.eth_smac = MacAddr::new(0, 0, 0, 0, 0, 0); + eth.eth_dmac = test.instance_mac.parse().unwrap(); + encapped.deparse().unwrap().to_vec() + }; + + // build the encapsulated packet for transporting the packet from the + // switch to OPTE + let backplane_port_mac = switch + .get_port_mac(test.backplane_port) + .unwrap() + .to_string(); + + // XXX: The switch should be using the switch port IP, but isn't yet. + let switch_port_ip = "::0"; + + // Build the encapsulated packet we expect to receive from tofino + let mut forward_pkt = common::gen_external_geneve_packet( + Endpoint::parse( + &backplane_port_mac, + switch_port_ip, + geneve::GENEVE_UDP_PORT, + ) + .unwrap(), + Endpoint::parse( + &test.gimlet_mac, + &test.gimlet_ip, + geneve::GENEVE_UDP_PORT, + ) + .unwrap(), + eth::ETHER_ETHER, + test.geneve_vni, + &ingress_payload, + ); + + /* Adjust for transition from switch port to gimlet port */ + ipv6::Ipv6Hdr::adjust_hlim(&mut forward_pkt, -1); + udp::UdpHdr::update_checksum(&mut forward_pkt); + + let send = vec![TestPacket { + packet: Arc::new(ingress_pkt), + port: test.uplink_port, + }]; + let expected = vec![TestPacket { + packet: Arc::new(forward_pkt), + port: test.backplane_port, + }]; + + switch.packet_test(send, expected) +} + +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ +// src: 203.0.113.11 │ +// │dst: 198.51.100.5 0.0.0.0 -> 169.254.10.2 +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ +// ▲ +// │ ┏━━━━━━━━━━━━┻━┓ 203.0.113.0/24 -> () +// │ ┌──┻─┐ ┃ │ +// └─│ p0 │ switch ┃ 0.0.0.0/0 -> 172.30.0.5 +// └┬─┳─┘ ┃ fd00:1::1/64 │ │ +// ┌ ─ ─ ┗━━━━━━━━━━━━━━┛ │┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ▲ ┃ │ sled │ ┃ +// │ │ │┃ ┌──────┐ ┃ +// 169.254.10.1/31 │ ┌──┻──┐ │ │ │ ┃ +// └─────────────│ phy │─▶│ OPTE │◀┐┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └──┳──┘ │ │ │ src: 203.0.113.11 │ ┃ +// src: fd00:1::1 │ ┃ └──────┘ ││dst: 198.51.100.5│ ┃ +// │dst: fd00:99::1 ┃ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ │ ┏━━━━━━━━━━━━━━┓┃ +// │ src: 203.0.113.11 │ ┃ │ ┌──┻──┐ ┃┃ +// │dst: 198.51.100.5 │ ┃ └──│opte0│instance ┃┃ +// │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ └┬─┳──┘ ┃┃ +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ ┗━━━━━━┳━━━━┳━━┛┃ +// ┗━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┛ +// │ │ +// 172.30.0.1 +// │ │ +// 172.30.0.5 +// │ +// 203.0.113.0/24 + +async fn test_egress_ipv4( + switch: &Switch, + l4_protocol: L4Protocol, +) -> TestResult { + let test = ExternalTest { + pkt_src_ip: "203.0.113.11".to_string(), + pkt_src_mac: "a1:a2:a3:a4:a5:a6".to_string(), + pkt_src_port: 3333, + pkt_dst_ip: "198.51.100.5".to_string(), + pkt_dst_mac: "b1:b2:b3:b4:b5:b6".to_string(), + pkt_dst_port: 4444, + + upstream_router_ip: "192.168.1.5".to_string(), + upstream_router_mac: "c1:c2:c3:c4:c5:c6".to_string(), + + attached_subnet: "203.0.113.0/24".to_string(), + instance_mac: "d1:d2:d3:d4:d5:d6".to_string(), + + uplink_port: PhysPort(14), + uplink_route: "0.0.0.0/0".to_string(), + + backplane_port: PhysPort(10), + backplane_ip: "fd00:99::1".to_string(), + + gimlet_mac: "e1:e2:e3:e4:e5:e6".to_string(), + gimlet_ip: "fd00:1::1".to_string(), + + l4_protocol, + geneve_vni: 32, + }; + + test_egress(switch, &test).await +} + +// TCP packet to/from IPv4 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv4_tcp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv4(switch, L4Protocol::Tcp).await +} + +// UDP packet to/from IPv4 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv4_udp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv4(switch, L4Protocol::Udp).await +} + +// ICMP packet to/from IPv4 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv4_icmp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv4(switch, L4Protocol::Icmp).await +} + +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ +// src: 198.51.100.5 │ +// │dst: 203.0.113.11 203.0.113.0/24 -> (tep: fd00:1::1, vni: 38) +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ +// │ +// │ ┏━━━━━━━━━━━━┻━┓ (prefix: 203.0.113.0/24, vni: 38) -> port0 +// │ ┌──┻─┐ ┃ │ +// └▶│ p0 │ switch ┃ +// └┬─┳─┘ ┃ fd00:1::1/64 │ +// ┌ ─ ─ ┗━━━━━━━━━━━━━━┛ │┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// │ ┃ │ sled ┃ +// │ │ │┃ ┌──────┐ ┃ +// 169.254.10.1/31 │ ┌──┻──┐ │ │ ┃ +// └────────────▶│ phy │─▶│ OPTE │─┐┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └──┳──┘ │ │ │ src: 198.51.100.5 │ ┃ +// src: fd00:99::1 │ ┃ └──────┘ ││dst: 203.0.113.11 ┃ +// │dst: fd00:1::1 ┃ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ │ ┏━━━━━━━━━━━━━━┓┃ +// │ src: 198.51.100.5 │ ┃ │ ┌──┻──┐ ┃┃ +// │dst: 203.0.113.11 │ ┃ └─▶│opte0│instance ┃┃ +// │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ └┬─┳──┘ ┃┃ +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ ┗━━━━━━┳━━━━┳━━┛┃ +// ┗━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┛ +// │ │ +// 172.30.0.1 +// │ │ +// 172.30.0.5 +// │ +// 203.0.113.0/24 + +async fn test_ingress_ipv4( + switch: &Switch, + l4_protocol: L4Protocol, +) -> TestResult { + let test = ExternalTest { + pkt_src_ip: "198.51.100.5".to_string(), + pkt_src_mac: "a1:a2:a3:a4:a5:a6".to_string(), + pkt_src_port: 3333, + pkt_dst_ip: "203.0.113.11".to_string(), + pkt_dst_mac: "b1:b2:b3:b4:b5:b6".to_string(), + pkt_dst_port: 4444, + + attached_subnet: "203.0.113.0/24".to_string(), + instance_mac: "c1:c2:c3:c4:c5:c6".to_string(), + + upstream_router_ip: "192.168.1.1".to_string(), + upstream_router_mac: "d1:d2:d3:d4:d5:d6".to_string(), + + uplink_port: PhysPort(14), + uplink_route: "0.0.0.0/0".to_string(), + + backplane_port: PhysPort(10), + backplane_ip: "fd00:99::1".to_string(), + + gimlet_mac: "e1:e2:e3:e4:e5:e6".to_string(), + gimlet_ip: "fd00:1::1".to_string(), + + l4_protocol, + geneve_vni: 38, + }; + + test_ingress(switch, &test).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv4_udp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv4(switch, L4Protocol::Udp).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv4_tcp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv4(switch, L4Protocol::Tcp).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv4_icmp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv4(switch, L4Protocol::Icmp).await +} + +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ +// src: fa00:22::1 │ +// │dst: fc00:13::1 0.0.0.0 -> 169.254.10.2 +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ +// ▲ +// │ ┏━━━━━━━━━━━━┻━┓ fa00::1/10 -> () +// │ ┌──┻─┐ ┃ │ +// └─│ p0 │ switch ┃ 0.0.0.0/0 -> 172.30.0.5 +// └┬─┳─┘ ┃ fd00:1::1/64 │ │ +// ┌ ─ ─ ┗━━━━━━━━━━━━━━┛ │┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ▲ ┃ │ sled │ ┃ +// │ │ │┃ ┌──────┐ ┃ +// 169.254.10.1/31 │ ┌──┻──┐ │ │ │ ┃ +// └─────────────│ phy │─▶│ OPTE │◀┐┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └──┳──┘ │ │ │ src: fa00:22::1 ││ ┃ +// src: fd00:1::1 │ ┃ └──────┘ ││dst: fc00:13::1 │ ┃ +// │dst: fd00:99::1 ┃ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ │ ┏━━━━━━━━━━━━━━┓┃ +// │ src: fa00:22::1 │ ┃ │ ┌──┻──┐ ┃┃ +// │dst: fc00:13::1 │ ┃ └──│opte0│instance ┃┃ +// │ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ └┬─┳──┘ ┃┃ +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ ┗━━━━━━┳━━━━┳━━┛┃ +// ┗━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┛ +// │ │ +// 172.30.0.1 +// │ │ +// 172.30.0.5 +// │ +// fa00::1/10 + +async fn test_egress_ipv6( + switch: &Switch, + l4_protocol: L4Protocol, +) -> TestResult { + let test = ExternalTest { + pkt_src_ip: "fa00:22::1".to_string(), + pkt_src_mac: "a1:a2:a3:a4:a5:a6".to_string(), + pkt_src_port: 3333, + pkt_dst_ip: "fc00:13::1".to_string(), + pkt_dst_mac: "b1:b2:b3:b4:b5:b6".to_string(), + pkt_dst_port: 4444, + + upstream_router_ip: "fc00:11::1".to_string(), + upstream_router_mac: "c1:c2:c3:c4:c5:c6".to_string(), + + attached_subnet: "fa00::1/10".to_string(), + instance_mac: "d1:d2:d3:d4:d5:d6".to_string(), + + uplink_port: PhysPort(14), + uplink_route: "::1/0".to_string(), + + backplane_port: PhysPort(10), + backplane_ip: "fd00:99::1".to_string(), + + gimlet_mac: "e1:e2:e3:e4:e5:e6".to_string(), + gimlet_ip: "fd00:1::1".to_string(), + + l4_protocol, + geneve_vni: 32, + }; + + test_egress(switch, &test).await +} + +// TCP packet to/from IPv6 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv6_tcp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv6(switch, L4Protocol::Tcp).await +} + +// UDP packet to/from IPv6 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv6_udp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv6(switch, L4Protocol::Udp).await +} + +// ICMP packet to/from IPv6 addresses on the external subnet +#[tokio::test] +#[ignore] +async fn test_egress_ipv6_icmp() -> TestResult { + let switch = &*get_switch().await; + + test_egress_ipv6(switch, L4Protocol::Icmp).await +} + +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ +// src: fa00:22::1 │ +// │dst: fc00:13::1 fc00::1/10 -> (tep: fd00:1::1, vni: 38) +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ +// │ +// │ ┏━━━━━━━━━━━━┻━┓ (prefix: fc00::1/10, vni: 38) -> port0 +// │ ┌──┻─┐ ┃ │ +// └▶│ p0 │ switch ┃ +// └┬─┳─┘ ┃ fd00:1::1/64 │ +// ┌ ─ ─ ┗━━━━━━━━━━━━━━┛ │┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// │ ┃ │ sled ┃ +// │ │ │┃ ┌──────┐ ┃ +// 169.254.10.1/31 │ ┌──┻──┐ │ │ ┃ +// └────────────▶│ phy │─▶│ OPTE │─┐┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └──┳──┘ │ │ │ src: fa00::22::1 │ ┃ +// src: fd00:99::1 │ ┃ └──────┘ ││dst: fc00::13::1 ┃ +// │dst: fd00:1::1 ┃ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ +// ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┃ │ ┏━━━━━━━━━━━━━━┓┃ +// │ src: fa00:22::1 │ ┃ │ ┌──┻──┐ ┃┃ +// │dst: fc00:13::1 │ ┃ └─▶│opte0│instance ┃┃ +// │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ └┬─┳──┘ ┃┃ +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┃ ┗━━━━━━┳━━━━┳━━┛┃ +// ┗━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┛ +// │ │ +// 172.30.0.1 +// │ │ +// 172.30.0.5 +// │ +// fc00::1/10 + +async fn test_ingress_ipv6( + switch: &Switch, + l4_protocol: L4Protocol, +) -> TestResult { + let test = ExternalTest { + pkt_src_ip: "fa00:22::1".to_string(), + pkt_src_mac: "a1:a2:a3:a4:a5:a6".to_string(), + pkt_src_port: 3333, + pkt_dst_ip: "fc00:13::1".to_string(), + pkt_dst_mac: "b1:b2:b3:b4:b5:b6".to_string(), + pkt_dst_port: 4444, + + attached_subnet: "fc00::1/10".to_string(), + instance_mac: "c1:c2:c3:c4:c5:c6".to_string(), + + upstream_router_ip: "fb00::1".to_string(), + upstream_router_mac: "d1:d2:d3:d4:d5:d6".to_string(), + uplink_port: PhysPort(14), + uplink_route: "::1/0".to_string(), + + backplane_port: PhysPort(10), + backplane_ip: "fd00:99::1".to_string(), + + gimlet_mac: "e1:e2:e3:e4:e5:e6".to_string(), + gimlet_ip: "fd00:1::1".to_string(), + + l4_protocol, + geneve_vni: 38, + }; + + test_ingress(switch, &test).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv6_udp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv6(switch, L4Protocol::Udp).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv6_tcp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv6(switch, L4Protocol::Tcp).await +} + +#[tokio::test] +#[ignore] +async fn test_ingress_ipv6_icmp() -> TestResult { + let switch = &*get_switch().await; + test_ingress_ipv6(switch, L4Protocol::Icmp).await +} diff --git a/dpd-client/tests/integration_tests/common.rs b/dpd-client/tests/integration_tests/common.rs index 5b315cb..41c5f3f 100644 --- a/dpd-client/tests/integration_tests/common.rs +++ b/dpd-client/tests/integration_tests/common.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::fmt::Write; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; @@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex, mpsc}; use std::time::Duration; use std::{fmt, thread}; +use anyhow::Context; use anyhow::anyhow; use oxnet::Ipv4Net; use oxnet::Ipv6Net; @@ -119,7 +120,7 @@ pub struct TestPacket { pub packet: Arc, } -fn dump_hex(a: &[u8]) { +pub fn dump_hex(a: &[u8]) { const CHUNK_SIZE: usize = 16; let mut off = 0; @@ -1086,8 +1087,8 @@ async fn set_route_ipv6_common( vlan_id: Option, ) -> TestResult { let (port_id, link_id) = switch.link_id(phys_port).unwrap(); - let cidr = subnet.parse::()?; - let tgt_ip: Ipv6Addr = gw.parse()?; + let cidr = subnet.parse::().context("parsing route subnet")?; + let tgt_ip: Ipv6Addr = gw.parse().context("parsing route gateway")?; let route = types::Ipv6RouteUpdate { cidr, diff --git a/dpd-client/tests/integration_tests/mod.rs b/dpd-client/tests/integration_tests/mod.rs index 89f70be..7f1522a 100644 --- a/dpd-client/tests/integration_tests/mod.rs +++ b/dpd-client/tests/integration_tests/mod.rs @@ -2,8 +2,9 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company +mod attached_subnet; mod common; mod counters; mod geneve; diff --git a/dpd-client/tests/integration_tests/nat.rs b/dpd-client/tests/integration_tests/nat.rs index 71583d6..7232f6b 100644 --- a/dpd-client/tests/integration_tests/nat.rs +++ b/dpd-client/tests/integration_tests/nat.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -11,8 +11,8 @@ use std::sync::Arc; use anyhow::anyhow; use oxnet::Ipv6Net; -use ::common::nat::Vni; use ::common::network::MacAddr; +use ::common::network::Vni; use dpd_client::ClientInfo; use dpd_client::types; use packet::Endpoint; diff --git a/dpd-client/tests/integration_tests/table_tests.rs b/dpd-client/tests/integration_tests/table_tests.rs index 88eb7a8..f57686a 100644 --- a/dpd-client/tests/integration_tests/table_tests.rs +++ b/dpd-client/tests/integration_tests/table_tests.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::net::IpAddr; use std::net::Ipv4Addr; @@ -31,12 +31,15 @@ use dpd_client::types; // oximeter, which will let this test query dpd for the table size rather than // hardcoding it below. // -// NOTE for this entry, we expect the size to be 4095, but there are 4 entries -// that are unaccounted for. This is being tracked in issue #1013. -// This table has further shrunk to 4022 entries with the open source -// compiler. That is being tracked as issue #1092, which will presumably -// subsume #1013. -// update: with the move to 8192 entries we're now at 8190 entries. +// The LPM tables are packed using a cuckoo hash algorithm. The conflict +// resolution approach can result in some slots not being available for some +// patterns of entries. Exactly which tables and which entries are unavailable +// can change depending on table layout on the ASIC. All of this means that it +// is normal for these table sizes to change slightly after updating the P4 +// code. If the table size appears to change dramatically, that's worth +// investigating. If it only changes by an entry or two, it's fine to just +// adjust the constant below to match the observed result. +// const IPV4_LPM_SIZE: usize = 8190; // ipv4 forwarding table const IPV6_LPM_SIZE: usize = 1023; // ipv6 forwarding table const SWITCH_IPV4_ADDRS_SIZE: usize = 511; // ipv4 addrs assigned to our ports diff --git a/dpd-types/src/mcast.rs b/dpd-types/src/mcast.rs index cf90b5f..1740763 100644 --- a/dpd-types/src/mcast.rs +++ b/dpd-types/src/mcast.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Public types for multicast group management. @@ -11,7 +11,7 @@ use std::{ net::{IpAddr, Ipv6Addr}, }; -use common::{nat::NatTarget, ports::PortId}; +use common::{network::NatTarget, ports::PortId}; use oxnet::{Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/dpd/p4/constants.p4 b/dpd/p4/constants.p4 index 860d995..6c06f7e 100644 --- a/dpd/p4/constants.p4 +++ b/dpd/p4/constants.p4 @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company const bit<16> L2_ISOLATED_FLAG = 0x8000; @@ -18,6 +18,8 @@ const int SWITCH_IPV4_ADDRS_SIZE = 512; // ipv4 addrs assigned to our ports const int SWITCH_IPV6_ADDRS_SIZE = 512; // ipv6 addrs assigned to our ports const int IPV4_MULTICAST_TABLE_SIZE = 1024; // multicast routing table(s) for IPv4 const int IPV6_MULTICAST_TABLE_SIZE = 1024; // multicast routing table(s) for IPv6 +const int ATTACHED_SUBNETS_V4_SIZE = 512; // external subnets mapped to instances +const int ATTACHED_SUBNETS_V6_SIZE = 512; // external subnets mapped to instances const bit<8> SC_FWD_FROM_USERSPACE = 0x00; const bit<8> SC_FWD_TO_USERSPACE = 0x01; diff --git a/dpd/p4/metadata.p4 b/dpd/p4/metadata.p4 index e8c29d7..1e75d9c 100644 --- a/dpd/p4/metadata.p4 +++ b/dpd/p4/metadata.p4 @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company /* Flexible bridge header for passing metadata between ingress and egress * pipelines. @@ -23,6 +23,7 @@ struct sidecar_ingress_meta_t { bool nat_egress_hit; // NATed packet from guest -> uplink bool nat_ingress_hit; // NATed packet from uplink -> guest bool nat_ingress_port; // This port accepts only NAT traffic + bool encap_needed; ipv4_addr_t nexthop_ipv4; // ip address of next router ipv6_addr_t nexthop_ipv6; // ip address of next router bit<10> pkt_type; diff --git a/dpd/p4/parser.p4 b/dpd/p4/parser.p4 index 2144f25..203ddad 100644 --- a/dpd/p4/parser.p4 +++ b/dpd/p4/parser.p4 @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company parser IngressParser( packet_in pkt, @@ -36,6 +36,7 @@ parser IngressParser( meta.nat_ingress_tgt = 0; meta.nat_inner_mac = 0; meta.nat_geneve_vni = 0; + meta.encap_needed = false; meta.icmp_recalc = false; meta.icmp_csum = 0; meta.l4_src_port = 0; diff --git a/dpd/p4/sidecar.p4 b/dpd/p4/sidecar.p4 index 3f30fab..3a9d3ff 100644 --- a/dpd/p4/sidecar.p4 +++ b/dpd/p4/sidecar.p4 @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company #if __TARGET_TOFINO__ == 2 #include @@ -135,6 +135,7 @@ control Filter( ipv6_ctr.count(); } + // Table of the IPv4 addresses assigned to ports on the switch. table switch_ipv4_addr { key = { @@ -227,7 +228,7 @@ control Filter( } } - if (!meta.is_mcast || meta.is_link_local_mcastv6) { + if (!meta.is_mcast || meta.is_link_local_mcastv6 && !meta.encap_needed) { switch_ipv6_addr.apply(); } } @@ -401,7 +402,7 @@ control Services( * as the restructuring is likely to have knock-on effects in * dpd and sidecar-lite. */ - if (!meta.is_switch_address && meta.nat_ingress_port && !meta.nat_ingress_hit) { + if (!meta.is_switch_address && meta.nat_ingress_port && !meta.encap_needed) { // For packets that were not marked for NAT ingress, but which // arrived on an uplink port that only allows in traffic that // is meant to be NAT encapsulated. @@ -417,6 +418,54 @@ control Services( } } +control AttachedSubnetIngress ( + in sidecar_headers_t hdr, + inout sidecar_ingress_meta_t meta +) { + DirectCounter>(CounterType_t.PACKETS_AND_BYTES) attached_subnets_v4_ctr; + DirectCounter>(CounterType_t.PACKETS_AND_BYTES) attached_subnets_v6_ctr; + + action forward_to_v4(ipv6_addr_t target, mac_addr_t inner_mac, geneve_vni_t vni) { + meta.encap_needed = true; + meta.nat_ingress_tgt = target; + meta.nat_inner_mac = inner_mac; + meta.nat_geneve_vni = vni; + attached_subnets_v4_ctr.count(); + } + + table attached_subnets_v4 { + key = { hdr.ipv4.dst_addr: lpm; } + actions = { forward_to_v4 ; } + + const size = ATTACHED_SUBNETS_V4_SIZE + 1; + counters = attached_subnets_v4_ctr; + } + + action forward_to_v6(ipv6_addr_t target, mac_addr_t inner_mac, geneve_vni_t vni) { + meta.encap_needed = true; + meta.nat_ingress_tgt = target; + meta.nat_inner_mac = inner_mac; + meta.nat_geneve_vni = vni; + attached_subnets_v6_ctr.count(); + } + + table attached_subnets_v6 { + key = { hdr.ipv6.dst_addr: lpm; } + actions = { forward_to_v6 ; } + + const size = ATTACHED_SUBNETS_V6_SIZE + 1; + counters = attached_subnets_v6_ctr; + } + + apply { + if (hdr.ipv4.isValid()) { + attached_subnets_v4.apply(); + } else if (hdr.ipv6.isValid()) { + attached_subnets_v6.apply(); + } + } +} + control NatIngress ( inout sidecar_headers_t hdr, inout sidecar_ingress_meta_t meta, @@ -503,6 +552,7 @@ control NatIngress ( meta.nat_ingress_tgt = target; meta.nat_inner_mac = inner_mac; meta.nat_geneve_vni = vni; + meta.encap_needed = true; ipv4_ingress_ctr.count(); } @@ -523,6 +573,7 @@ control NatIngress ( meta.nat_ingress_tgt = target; meta.nat_inner_mac = inner_mac; meta.nat_geneve_vni = vni; + meta.encap_needed = true; ipv6_ingress_ctr.count(); } @@ -556,6 +607,7 @@ control NatIngress ( meta.nat_ingress_tgt = target; meta.nat_inner_mac = inner_mac; meta.nat_geneve_vni = vni; + meta.encap_needed = true; mcast_ipv4_ingress_ctr.count(); } @@ -572,6 +624,7 @@ control NatIngress ( meta.nat_ingress_tgt = target; meta.nat_inner_mac = inner_mac; meta.nat_geneve_vni = vni; + meta.encap_needed = true; mcast_ipv6_ingress_ctr.count(); } @@ -628,7 +681,7 @@ control NatIngress ( table ingress_hit { key = { - meta.nat_ingress_hit : exact; + meta.encap_needed : exact; hdr.tcp.isValid() : ternary; hdr.udp.isValid() : ternary; hdr.icmp.isValid() : ternary; @@ -657,7 +710,7 @@ control NatIngress ( if (hdr.ipv4.isValid()) { if (meta.is_mcast) { ingress_ipv4_mcast.apply(); - } else { + } else if (!meta.encap_needed) { ingress_ipv4.apply(); } } else if (hdr.ipv6.isValid()) { @@ -815,7 +868,7 @@ control NatEgress ( ( true, false, false, false, true ) : decap_ipv4_icmp; ( false, true, true, false, false ) : decap_ipv6_tcp; ( false, true, false, true, false ) : decap_ipv6_udp; - ( false, true, false, true, true ) : decap_ipv6_icmp; + ( false, true, false, false, true ) : decap_ipv6_icmp; } default_action = drop; @@ -877,7 +930,7 @@ control RouterLookupIndex6( res.nexthop = 0; index_ctr.count(); } - + /* * The select_route table contains 2048 pre-computed entries. * It lives in another file just to keep this one manageable. @@ -1943,6 +1996,7 @@ control Ingress( inout ingress_intrinsic_metadata_for_tm_t ig_tm_md) { Filter() filter; + AttachedSubnetIngress() attached_subnet_ingress; Services() services; NatIngress() nat_ingress; NatEgress() nat_egress; @@ -1970,6 +2024,10 @@ control Ingress( filter.apply(hdr, meta, ig_intr_md); } + if (!meta.is_mcast || meta.is_link_local_mcastv6) { + attached_subnet_ingress.apply(hdr, meta); + } + // We perform NAT ingress before multicast replication to ensure // that the NAT'd outer address is used for multicast // replication to inbound groups @@ -2008,7 +2066,7 @@ control Ingress( ig_tm_md.bypass_egress = 1w1; } - if (meta.nat_ingress_hit) { + if (meta.encap_needed) { // This works around a few things which cropped up in // supporting several concurrent Geneve options: // @@ -2063,12 +2121,12 @@ control IngressDeparser(packet_out pkt, // checksum engine, exceeding the hardware's limit. Rewriting // the logic as seen below somehow makes the independence // apparent to the compiler. - if (meta.nat_ingress_hit && hdr.inner_ipv4.isValid() && + if (meta.encap_needed && hdr.inner_ipv4.isValid() && hdr.inner_udp.isValid()) { hdr.udp.checksum = nat_checksum.update({ COMMON_FIELDS, IPV4_FIELDS, hdr.inner_udp}); } - if (meta.nat_ingress_hit && hdr.inner_ipv4.isValid() && + if (meta.encap_needed && hdr.inner_ipv4.isValid() && hdr.inner_tcp.isValid()) { hdr.udp.checksum = nat_checksum.update({ COMMON_FIELDS, IPV4_FIELDS, hdr.inner_tcp}); @@ -2076,19 +2134,19 @@ control IngressDeparser(packet_out pkt, /* COMPILER BUG: I cannot convince the tofino to compute this correctly. * Conveniently, we don't actually need it, see RFC 6935. * - * if (meta.nat_ingress_hit && hdr.inner_ipv4.isValid() && + * if (meta.encap_needed && hdr.inner_ipv4.isValid() && * hdr.inner_icmp.isValid()) { * hdr.udp.checksum = nat_checksum.update({ * COMMON_FIELDS, IPV4_FIELDS, hdr.inner_icmp}); * } * */ - if (meta.nat_ingress_hit && hdr.inner_ipv6.isValid() && + if (meta.encap_needed && hdr.inner_ipv6.isValid() && hdr.inner_udp.isValid()) { hdr.udp.checksum = nat_checksum.update({ COMMON_FIELDS, IPV6_FIELDS, hdr.inner_udp}); } - if (meta.nat_ingress_hit && hdr.inner_ipv6.isValid() && + if (meta.encap_needed && hdr.inner_ipv6.isValid() && hdr.inner_tcp.isValid()) { hdr.udp.checksum = nat_checksum.update({ COMMON_FIELDS, IPV6_FIELDS, hdr.inner_tcp}); diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 8d94e64..cd92224 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Dendrite HTTP API types and endpoint functions. @@ -65,6 +65,7 @@ use common::ports::TxEq; #[cfg(any(feature = "softnpu", feature = "tofino_asic"))] use common::ports::TxEqSwHw; +use crate::attached_subnet; use crate::counters; use crate::mcast; use crate::oxstats; @@ -74,8 +75,9 @@ use crate::switch_port::LedState; use crate::transceivers::PowerState; use crate::types::DpdError; use crate::{Switch, arp, loopback, nat, ports, route}; -use common::nat::{Ipv4Nat, Ipv6Nat, NatTarget}; -use common::network::MacAddr; +use common::attached_subnet::AttachedSubnetEntry; +use common::nat::{Ipv4Nat, Ipv6Nat}; +use common::network::{InstanceTarget, MacAddr, NatTarget}; use common::ports::PortId; use common::ports::QsfpPort; use common::ports::{Ipv4Entry, Ipv6Entry, PortPrbsMode}; @@ -1554,6 +1556,82 @@ impl DpdApi for DpdApiImpl { } } + async fn attached_subnet_list( + rqctx: RequestContext>, + query: Query>, + ) -> Result>, HttpError> + { + let switch: &Switch = rqctx.context(); + let pag_params = query.into_inner(); + let max = rqctx.page_limit(&pag_params)?.get(); + let subnet = match &pag_params.page { + WhichPage::First(..) => None, + WhichPage::Next(AttachedSubnetToken { cidr }) => Some(*cidr), + }; + + let entries = attached_subnet::get_mappings( + switch, + subnet, + usize::try_from(max).expect("invalid usize"), + ); + Ok(HttpResponseOk(ResultsPage::new( + entries, + &EmptyScanParams {}, + |e: &AttachedSubnetEntry, _| AttachedSubnetToken { cidr: e.subnet }, + )?)) + } + + async fn attached_subnet_get( + rqctx: RequestContext>, + path: Path, + ) -> Result, HttpError> { + let switch: &Switch = rqctx.context(); + let params = path.into_inner(); + match attached_subnet::get_mapping(switch, params.subnet) { + Ok(tgt) => Ok(HttpResponseOk(tgt)), + Err(e) => Err(e.into()), + } + } + + async fn attached_subnet_create( + rqctx: RequestContext>, + path: Path, + target: TypedBody, + ) -> Result { + let switch: &Switch = rqctx.context(); + let params = path.into_inner(); + match attached_subnet::set_mapping( + switch, + params.subnet, + target.into_inner(), + ) { + Ok(_) => Ok(HttpResponseUpdatedNoContent()), + Err(e) => Err(e.into()), + } + } + + async fn attached_subnet_delete( + rqctx: RequestContext>, + path: Path, + ) -> Result { + let switch: &Switch = rqctx.context(); + let params = path.into_inner(); + attached_subnet::clear_mapping(switch, params.subnet) + .map(|_| HttpResponseDeleted()) + .map_err(HttpError::from) + } + + async fn attached_subnet_reset( + rqctx: RequestContext>, + ) -> Result { + let switch: &Switch = rqctx.context(); + + match attached_subnet::reset(switch) { + Ok(_) => Ok(HttpResponseUpdatedNoContent()), + Err(e) => Err(e.into()), + } + } + async fn reset_all_tagged( rqctx: RequestContext>, path: Path, @@ -1608,6 +1686,13 @@ impl DpdApi for DpdApiImpl { error!(switch.log, "failed to reset multicast state: {:?}", e); err = Some(e); } + if let Err(e) = attached_subnet::reset(switch) { + error!( + switch.log, + "failed to reset external subnet state: {:?}", e + ); + err = Some(e); + } match err { Some(e) => Err(e.into()), diff --git a/dpd/src/attached_subnet.rs b/dpd/src/attached_subnet.rs new file mode 100644 index 0000000..1a5a2db --- /dev/null +++ b/dpd/src/attached_subnet.rs @@ -0,0 +1,154 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use slog::{debug, error, trace}; +use std::collections::BTreeMap; +use std::net::Ipv4Addr; + +use crate::Switch; +use crate::table::{attached_subnet_v4, attached_subnet_v6}; +use crate::types::{DpdError, DpdResult}; +use common::attached_subnet::AttachedSubnetEntry; +use common::network::InstanceTarget; +use oxnet::IpNet; + +pub struct AttachedSubnetData { + mappings: BTreeMap, +} + +/// Paginates through `AttachedSubnetEntry` +pub fn get_mappings( + switch: &Switch, + last_subnet: Option, + mut max: usize, +) -> Vec { + max = std::cmp::min(max, 64); + let all = switch.attached_subnet.lock().unwrap(); + let subnet = last_subnet + .unwrap_or(IpNet::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0).unwrap()); + + all.mappings + .values() + .filter(|e| e.subnet > subnet) + .take(max) + .cloned() + .collect() +} + +/// Find the target, if any, of the provided external subnet +pub fn get_mapping( + switch: &Switch, + subnet: IpNet, +) -> DpdResult { + let all = switch.attached_subnet.lock().unwrap(); + match all.mappings.get(&subnet) { + Some(e) => Ok(e.tgt), + None => Err(DpdError::Missing("no mapping".into())), + } +} + +/// Map the provided external subnet to the internal address/vni. +pub fn set_mapping( + switch: &Switch, + subnet: IpNet, + tgt: InstanceTarget, +) -> DpdResult<()> { + let new_entry = AttachedSubnetEntry { subnet, tgt }; + trace!(switch.log, "adding external subnet entry {}", new_entry); + + let mut all = switch.attached_subnet.lock().unwrap(); + if let Some(e) = all.mappings.get_mut(&subnet) { + if e == &new_entry { + // entry already exists + return Ok(()); + } else { + return Err(DpdError::Exists("conflicting mapping".into())); + } + } + match match subnet { + IpNet::V4(subnet) => attached_subnet_v4::add_entry(switch, subnet, tgt), + IpNet::V6(subnet) => attached_subnet_v6::add_entry(switch, subnet, tgt), + } { + Err(e) => { + error!( + switch.log, + "failed to add external subnet entry {} -> {:?}: {:?}", + subnet, + tgt, + e + ); + Err(e) + } + _ => { + debug!( + switch.log, + "added external subnet entry {} -> {:?}", subnet, tgt + ); + all.mappings.insert(subnet, new_entry); + Ok(()) + } + } +} + +/// If a mapping exists for this external subnet, delete it. +pub fn clear_mapping(switch: &Switch, subnet: IpNet) -> DpdResult<()> { + let mut all = switch.attached_subnet.lock().unwrap(); + trace!(switch.log, "clearing external subnet mapping {subnet}"); + + let Some(ent) = all.mappings.get(&subnet) else { + return Ok(()); + }; + + match match subnet { + IpNet::V4(subnet) => attached_subnet_v4::delete_entry(switch, subnet), + IpNet::V6(subnet) => attached_subnet_v6::delete_entry(switch, subnet), + } { + Err(e) => { + error!( + switch.log, + "failed to delete external subnet entry {} -> {:?}: {:?}", + subnet, + ent, + e + ); + Err(e) + } + _ => { + debug!( + switch.log, + "deleted external subnet entry {} -> {:?}", subnet, ent + ); + all.mappings.remove(&subnet); + Ok(()) + } + } +} + +pub fn reset(switch: &Switch) -> DpdResult<()> { + let mut all = switch.attached_subnet.lock().unwrap(); + + debug!(switch.log, "resetting external subnet table"); + all.mappings.clear(); + if let Err(e) = attached_subnet_v4::reset(switch) { + error!( + switch.log, + "failed to reset external ipv4 subnet table: {:?}", e + ); + } + if let Err(e) = attached_subnet_v6::reset(switch) { + error!( + switch.log, + "failed to reset external ipv6 subnet table: {:?}", e + ); + } + Ok(()) +} + +pub fn init() -> AttachedSubnetData { + AttachedSubnetData { + mappings: BTreeMap::new(), + } +} diff --git a/dpd/src/main.rs b/dpd/src/main.rs index 8de414a..ac0ce15 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Main application entry point for `dpd`, the Dendrite switch management API //! server. @@ -54,6 +54,7 @@ cfg_if::cfg_if! { mod api_server; mod arp; +mod attached_subnet; mod config; mod counters; mod fault; @@ -187,6 +188,7 @@ pub struct Switch { pub routes: TokioMutex, pub arp: Mutex, pub nat: Mutex, + pub attached_subnet: Mutex, pub loopback: Mutex, pub identifiers: Mutex>, pub oximeter_producer: Mutex>, @@ -294,6 +296,7 @@ impl Switch { routes: TokioMutex::new(route_data), arp: Mutex::new(arp::init()), nat: Mutex::new(nat::init()), + attached_subnet: Mutex::new(attached_subnet::init()), loopback: Mutex::new(loopback::init()), switch_ports, identifiers: Mutex::new(None), diff --git a/dpd/src/mcast/mod.rs b/dpd/src/mcast/mod.rs index 801b179..fa246d9 100644 --- a/dpd/src/mcast/mod.rs +++ b/dpd/src/mcast/mod.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Multicast group management and configuration. //! @@ -60,7 +60,7 @@ use std::{ }; use aal::{AsicError, AsicOps}; -use common::{nat::NatTarget, ports::PortId}; +use common::{network::NatTarget, ports::PortId}; use dpd_types::{ link::LinkId, mcast::{ diff --git a/dpd/src/mcast/rollback.rs b/dpd/src/mcast/rollback.rs index f862f38..c2c20dd 100644 --- a/dpd/src/mcast/rollback.rs +++ b/dpd/src/mcast/rollback.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Rollback contexts for multicast group operations. //! @@ -17,7 +17,7 @@ use aal::AsicOps; use oxnet::Ipv4Net; use slog::{debug, error}; -use common::{nat::NatTarget, ports::PortId}; +use common::{network::NatTarget, ports::PortId}; use super::{ Direction, IpSrc, LinkId, MulticastGroup, MulticastGroupId, diff --git a/dpd/src/mcast/validate.rs b/dpd/src/mcast/validate.rs index f039469..7671f44 100644 --- a/dpd/src/mcast/validate.rs +++ b/dpd/src/mcast/validate.rs @@ -2,11 +2,11 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use common::nat::NatTarget; +use common::network::NatTarget; use oxnet::{Ipv4Net, Ipv6Net}; use super::IpSrc; @@ -288,7 +288,7 @@ fn validate_ipv4_source_subnet(subnet: Ipv4Net) -> DpdResult<()> { #[cfg(test)] mod tests { use super::*; - use common::{nat::Vni, network::MacAddr}; + use common::network::{MacAddr, Vni}; use oxnet::Ipv4Net; use std::str::FromStr; diff --git a/dpd/src/nat.rs b/dpd/src/nat.rs index 5d027df..937fe3f 100644 --- a/dpd/src/nat.rs +++ b/dpd/src/nat.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use slog::{debug, error, trace}; use std::collections::BTreeMap; @@ -13,7 +13,8 @@ use std::ops::Bound; use crate::Switch; use crate::table::nat; use crate::types::{DpdError, DpdResult}; -use common::nat::{Ipv4Nat, Ipv6Nat, NatTarget}; +use common::nat::{Ipv4Nat, Ipv6Nat}; +use common::network::NatTarget; trait PortRange { fn low(&self) -> u16; @@ -131,7 +132,7 @@ fn find_space( #[test] fn test_mapping() { use super::MacAddr; - use common::nat::Vni; + use common::network::Vni; let dummy_target = NatTarget { internal_ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), diff --git a/dpd/src/rpw/mod.rs b/dpd/src/rpw/mod.rs index 95b0cdb..6c7737e 100644 --- a/dpd/src/rpw/mod.rs +++ b/dpd/src/rpw/mod.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::{ collections::BTreeMap, @@ -10,10 +10,7 @@ use std::{ }; use anyhow::{Result, anyhow}; -use common::{ - nat::{NatTarget, Vni}, - network::MacAddr, -}; +use common::network::{MacAddr, NatTarget, Vni}; use internal_dns_resolver::Resolver; use internal_dns_types::names::ServiceName; use slog::{Logger, debug, error, info, o}; diff --git a/dpd/src/table/attached_subnet_v4.rs b/dpd/src/table/attached_subnet_v4.rs new file mode 100644 index 0000000..8f729fb --- /dev/null +++ b/dpd/src/table/attached_subnet_v4.rs @@ -0,0 +1,85 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::convert::TryInto; +use std::net::Ipv6Addr; + +use slog::debug; + +use aal::{ActionParse, MatchParse}; +use aal_macros::*; +use oxnet::Ipv4Net; + +use crate::Switch; +use crate::table::*; +use common::network::{InstanceTarget, MacAddr}; + +pub const EXT_SUBNET_IPV4_TABLE_NAME: &str = + "pipe.Ingress.attached_subnet_ingress.attached_subnets_v4"; + +// Used to identify entries in the external subnet table +#[derive(MatchParse, Hash, Debug)] +struct AttachedSubnetV4MatchKey { + #[match_xlate(type = "lpm", name = "dst_addr")] + subnet: Ipv4Net, +} + +#[derive(ActionParse)] +enum AttachedSubnetV4Action { + #[action_xlate(name = "forward_to_v4")] + Forward { + target: Ipv6Addr, + inner_mac: MacAddr, + vni: u32, + }, +} + +pub fn add_entry( + s: &Switch, + subnet: Ipv4Net, + tgt: InstanceTarget, +) -> DpdResult<()> { + let match_key = AttachedSubnetV4MatchKey { subnet }; + let action_key = AttachedSubnetV4Action::Forward { + target: tgt.internal_ip, + inner_mac: tgt.inner_mac, + vni: tgt.vni.as_u32(), + }; + + debug!( + s.log, + "add external subnet entry {} -> {:?}", match_key.subnet, tgt + ); + + s.table_entry_add(TableType::AttachedSubnetIpv4, &match_key, &action_key) +} + +pub fn delete_entry(s: &Switch, subnet: Ipv4Net) -> DpdResult<()> { + let match_key = AttachedSubnetV4MatchKey { subnet }; + debug!(s.log, "remove external subnet entry {}", match_key.subnet); + s.table_entry_del(TableType::AttachedSubnetIpv4, &match_key) +} + +pub fn table_dump(s: &Switch) -> DpdResult { + s.table_dump::( + TableType::AttachedSubnetIpv4, + ) +} + +pub fn counter_fetch( + s: &Switch, + force_sync: bool, +) -> DpdResult> { + s.counter_fetch::( + force_sync, + TableType::AttachedSubnetIpv4, + ) +} + +/// Delete all the external subnet entries +pub fn reset(s: &Switch) -> DpdResult<()> { + s.table_clear(TableType::AttachedSubnetIpv4) +} diff --git a/dpd/src/table/attached_subnet_v6.rs b/dpd/src/table/attached_subnet_v6.rs new file mode 100644 index 0000000..0465c8f --- /dev/null +++ b/dpd/src/table/attached_subnet_v6.rs @@ -0,0 +1,84 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company +use std::convert::TryInto; +use std::net::Ipv6Addr; + +use slog::debug; + +use aal::{ActionParse, MatchParse}; +use aal_macros::*; +use oxnet::Ipv6Net; + +use crate::Switch; +use crate::table::*; +use common::network::{InstanceTarget, MacAddr}; + +pub const EXT_SUBNET_IPV6_TABLE_NAME: &str = + "pipe.Ingress.attached_subnet_ingress.attached_subnets_v6"; + +// Used to identify entries in the external subnet table +#[derive(MatchParse, Hash, Debug)] +struct AttachedSubnetV6MatchKey { + #[match_xlate(type = "lpm", name = "dst_addr")] + subnet: Ipv6Net, +} + +#[derive(ActionParse)] +enum AttachedSubnetV6Action { + #[action_xlate(name = "forward_to_v6")] + Forward { + target: Ipv6Addr, + inner_mac: MacAddr, + vni: u32, + }, +} + +pub fn add_entry( + s: &Switch, + subnet: Ipv6Net, + tgt: InstanceTarget, +) -> DpdResult<()> { + let match_key = AttachedSubnetV6MatchKey { subnet }; + let action_key = AttachedSubnetV6Action::Forward { + target: tgt.internal_ip, + inner_mac: tgt.inner_mac, + vni: tgt.vni.as_u32(), + }; + + debug!( + s.log, + "add external subnet entry {} -> {:?}", match_key.subnet, tgt + ); + + s.table_entry_add(TableType::AttachedSubnetIpv6, &match_key, &action_key) +} + +pub fn delete_entry(s: &Switch, subnet: Ipv6Net) -> DpdResult<()> { + let match_key = AttachedSubnetV6MatchKey { subnet }; + debug!(s.log, "remove external subnet entry {}", match_key.subnet); + s.table_entry_del(TableType::AttachedSubnetIpv6, &match_key) +} + +pub fn table_dump(s: &Switch) -> DpdResult { + s.table_dump::( + TableType::AttachedSubnetIpv6, + ) +} + +pub fn counter_fetch( + s: &Switch, + force_sync: bool, +) -> DpdResult> { + s.counter_fetch::( + force_sync, + TableType::AttachedSubnetIpv6, + ) +} + +/// Delete all the external subnet entries +pub fn reset(s: &Switch) -> DpdResult<()> { + s.table_clear(TableType::AttachedSubnetIpv6) +} diff --git a/dpd/src/table/mcast/mcast_nat.rs b/dpd/src/table/mcast/mcast_nat.rs index 044c7cb..36fe0fa 100644 --- a/dpd/src/table/mcast/mcast_nat.rs +++ b/dpd/src/table/mcast/mcast_nat.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Table operations for multicast NAT entries. @@ -14,7 +14,7 @@ use super::{Ipv4MatchKey, Ipv6MatchKey}; use aal::ActionParse; use aal_macros::*; -use common::{nat::NatTarget, network::MacAddr}; +use common::network::{MacAddr, NatTarget}; use slog::debug; /// IPv4 Table for multicast NAT entries. diff --git a/dpd/src/table/mod.rs b/dpd/src/table/mod.rs index 34ca62f..16f13f0 100644 --- a/dpd/src/table/mod.rs +++ b/dpd/src/table/mod.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryFrom; use std::hash::Hash; @@ -18,6 +18,8 @@ use common::network::MacAddr; use dpd_types::views; pub mod arp_ipv4; +pub mod attached_subnet_v4; +pub mod attached_subnet_v6; pub mod mcast; pub mod nat; pub mod neighbor_ipv6; @@ -27,7 +29,7 @@ pub mod port_nat; pub mod route_ipv4; pub mod route_ipv6; -const NAME_TO_TYPE: [(&str, TableType); 22] = [ +const NAME_TO_TYPE: [(&str, TableType); 24] = [ (route_ipv4::INDEX_TABLE_NAME, TableType::RouteIdxIpv4), (route_ipv4::FORWARD_TABLE_NAME, TableType::RouteFwdIpv4), (route_ipv6::INDEX_TABLE_NAME, TableType::RouteIdxIpv6), @@ -40,6 +42,14 @@ const NAME_TO_TYPE: [(&str, TableType); 22] = [ (nat::IPV4_TABLE_NAME, TableType::NatIngressIpv4), (nat::IPV6_TABLE_NAME, TableType::NatIngressIpv6), (port_nat::TABLE_NAME, TableType::NatOnly), + ( + attached_subnet_v4::EXT_SUBNET_IPV4_TABLE_NAME, + TableType::AttachedSubnetIpv4, + ), + ( + attached_subnet_v6::EXT_SUBNET_IPV6_TABLE_NAME, + TableType::AttachedSubnetIpv6, + ), ( mcast::mcast_replication::IPV6_TABLE_NAME, TableType::McastIpv6, @@ -277,6 +287,8 @@ pub fn get_entries(switch: &Switch, name: String) -> DpdResult { TableType::ArpIpv4 => arp_ipv4::table_dump(switch), TableType::NatIngressIpv4 => nat::ipv4_table_dump(switch), TableType::NatIngressIpv6 => nat::ipv6_table_dump(switch), + TableType::AttachedSubnetIpv4 => attached_subnet_v4::table_dump(switch), + TableType::AttachedSubnetIpv6 => attached_subnet_v6::table_dump(switch), TableType::PortIpv4 => port_ip::ipv4_table_dump(switch), TableType::PortIpv6 => port_ip::ipv6_table_dump(switch), TableType::PortMac => { @@ -352,6 +364,12 @@ pub fn get_counters( TableType::PortIpv4 => port_ip::ipv4_counter_fetch(switch, force_sync), TableType::PortIpv6 => port_ip::ipv6_counter_fetch(switch, force_sync), TableType::NatOnly => port_nat::counter_fetch(switch, force_sync), + TableType::AttachedSubnetIpv4 => { + attached_subnet_v4::counter_fetch(switch, force_sync) + } + TableType::AttachedSubnetIpv6 => { + attached_subnet_v6::counter_fetch(switch, force_sync) + } TableType::McastIpv6 => { mcast::mcast_replication::ipv6_counter_fetch(switch, force_sync) } @@ -403,6 +421,8 @@ pub enum TableType { NatIngressIpv4, NatIngressIpv6, NatOnly, + AttachedSubnetIpv4, + AttachedSubnetIpv6, McastIpv6, McastIpv4SrcFilter, McastIpv6SrcFilter, diff --git a/dpd/src/table/nat.rs b/dpd/src/table/nat.rs index 43bb40b..84c2404 100644 --- a/dpd/src/table/nat.rs +++ b/dpd/src/table/nat.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryInto; use std::fmt; @@ -15,8 +15,7 @@ use aal_macros::*; use crate::Switch; use crate::table::*; -use common::nat::NatTarget; -use common::network::MacAddr; +use common::network::{MacAddr, NatTarget}; pub const IPV4_TABLE_NAME: &str = "pipe.Ingress.nat_ingress.ingress_ipv4"; pub const IPV6_TABLE_NAME: &str = "pipe.Ingress.nat_ingress.ingress_ipv6"; diff --git a/openapi/dpd/dpd-3.0.0-b02a88.json b/openapi/dpd/dpd-3.0.0-b02a88.json new file mode 100644 index 0000000..df5e141 --- /dev/null +++ b/openapi/dpd/dpd-3.0.0-b02a88.json @@ -0,0 +1,9891 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Oxide Switch Dataplane Controller", + "description": "API for managing the Oxide rack switch", + "contact": { + "url": "https://oxide.computer", + "email": "api@oxide.computer" + }, + "version": "3.0.0" + }, + "paths": { + "/all-settings": { + "delete": { + "summary": "Clear all settings.", + "description": "This removes all data entirely.", + "operationId": "reset_all", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/all-settings/{tag}": { + "delete": { + "summary": "Clear all settings associated with a specific tag.", + "description": "This removes:\n\n- All ARP or NDP table entries. - All routes - All links on all switch ports", + "operationId": "reset_all_tagged", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/arp": { + "get": { + "summary": "Fetch the configured IPv4 ARP table entries.", + "operationId": "arp_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv4 ARP table entry, mapping an IPv4 address to a MAC address.", + "operationId": "arp_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all entries in the IPv4 ARP tables.", + "operationId": "arp_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/arp/{ip}": { + "get": { + "summary": "Get a single IPv4 ARP table entry, by its IPv4 address.", + "operationId": "arp_get", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove a single IPv4 ARP entry, by its IPv4 address.", + "operationId": "arp_delete", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/attached_subnet": { + "get": { + "summary": "Get all of the external subnets with internal mappings", + "operationId": "attached_subnet_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttachedSubnetEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all external subnet mappings", + "operationId": "attached_subnet_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/attached_subnet/{subnet}": { + "get": { + "summary": "Get the mapping for the given external subnet.", + "operationId": "attached_subnet_get", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Add a mapping to an internal target for an external subnet address.", + "description": "These identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external subnet.", + "operationId": "attached_subnet_create", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete the mapping for an external subnet", + "operationId": "attached_subnet_delete", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "The external subnet in CIDR notation being managed", + "required": true, + "schema": { + "$ref": "#/components/schemas/IpNet" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/backplane-map": { + "get": { + "summary": "Return the full backplane map.", + "description": "This returns the entire mapping of all cubbies in a rack, through the cabled backplane, and into the Sidecar main board. It also includes the Tofino \"connector\", which is included in some contexts such as reporting counters.", + "operationId": "backplane_map", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_BackplaneLink", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BackplaneLink" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/backplane-map/{port_id}": { + "get": { + "summary": "Return the backplane mapping for a single switch port.", + "operationId": "port_backplane_link", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackplaneLink" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/build-info": { + "get": { + "summary": "Return detailed build information about the `dpd` server itself.", + "operationId": "build_info", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildInfo" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/channels": { + "get": { + "summary": "Get the set of available channels for all ports.", + "description": "This returns the unused MAC channels for each physical switch port. This can be used to determine how many additional links can be crated on a physical switch port.", + "operationId": "channels_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_FreeChannels", + "type": "array", + "items": { + "$ref": "#/components/schemas/FreeChannels" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fec": { + "get": { + "summary": "Get the FEC RS counters for all links.", + "operationId": "fec_rs_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkFecRSCounters", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkFecRSCounters" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fec/{port_id}/{link_id}": { + "get": { + "summary": "Get the FEC RS counters for the given link.", + "operationId": "fec_rs_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkFecRSCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/fsm/{port_id}/{link_id}": { + "get": { + "summary": "Get the autonegotiation FSM counters for the given link.", + "operationId": "link_fsm_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkFsmCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/linkup": { + "get": { + "summary": "Get the LinkUp counters for all links.", + "operationId": "link_up_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkUpCounter", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkUpCounter" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/linkup/{port_id}/{link_id}": { + "get": { + "summary": "Get the LinkUp counters for the given link.", + "operationId": "link_up_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkUpCounter" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4": { + "get": { + "summary": "Get a list of all the available p4-defined counters.", + "operationId": "counter_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4/{counter}": { + "get": { + "summary": "Get the values for a given counter.", + "description": "The name of the counter should match one of those returned by the `counter_list()` call.", + "operationId": "counter_get", + "parameters": [ + { + "in": "query", + "name": "force_sync", + "description": "Force a sync of the counters from the ASIC to memory, even if the default refresh timeout hasn't been reached.", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "path", + "name": "counter", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TableCounterEntry", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableCounterEntry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/p4/{counter}/reset": { + "post": { + "summary": "Reset a single p4-defined counter.", + "description": "The name of the counter should match one of those returned by the `counter_list()` call.", + "operationId": "counter_reset", + "parameters": [ + { + "in": "path", + "name": "counter", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/pcs": { + "get": { + "summary": "Get the physical coding sublayer (PCS) counters for all links.", + "operationId": "pcs_counters_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_LinkPcsCounters", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkPcsCounters" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/pcs/{port_id}/{link_id}": { + "get": { + "summary": "Get the Physical Coding Sublayer (PCS) counters for the given link.", + "operationId": "pcs_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkPcsCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/rmon/{port_id}/{link_id}/all": { + "get": { + "summary": "Get the full set of traffic counters for the given link.", + "operationId": "rmon_counters_get_all", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkRMonCountersAll" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/counters/rmon/{port_id}/{link_id}/subset": { + "get": { + "summary": "Get the most relevant subset of traffic counters for the given link.", + "operationId": "rmon_counters_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkRMonCounters" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/dpd-uptime": { + "get": { + "summary": "Return the server uptime.", + "operationId": "dpd_uptime", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "int64", + "type": "integer", + "format": "int64" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/dpd-version": { + "get": { + "summary": "Return the version of the `dpd` server itself.", + "operationId": "dpd_version", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/leds": { + "get": { + "summary": "Return the state of all attention LEDs on the Sidecar QSFP ports.", + "operationId": "leds_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_Led", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Led" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/links": { + "get": { + "summary": "List all links, on all switch ports.", + "operationId": "link_list_all", + "parameters": [ + { + "in": "query", + "name": "filter", + "description": "Filter links to those whose name contains the provided string.\n\nIf not provided, then all links are returned.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Link", + "type": "array", + "items": { + "$ref": "#/components/schemas/Link" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/links/tfport_data": { + "get": { + "summary": "Collect the link data consumed by `tfportd`. This app-specific convenience", + "description": "routine is meant to reduce the time and traffic expended on this once-per-second operation, by consolidating multiple per-link requests into a single per-switch request.", + "operationId": "tfport_data", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TfportData", + "type": "array", + "items": { + "$ref": "#/components/schemas/TfportData" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv4": { + "get": { + "summary": "Get loopback IPv4 addresses.", + "operationId": "loopback_ipv4_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv4Entry", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Add a loopback IPv4.", + "operationId": "loopback_ipv4_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv4/{ipv4}": { + "delete": { + "summary": "Remove one loopback IPv4 address.", + "operationId": "loopback_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv6": { + "get": { + "summary": "Get loopback IPv6 addresses.", + "operationId": "loopback_ipv6_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv6Entry", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Add a loopback IPv6.", + "operationId": "loopback_ipv6_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/loopback/ipv6/{ipv6}": { + "delete": { + "summary": "Remove one loopback IPv6 address.", + "operationId": "loopback_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/external-groups": { + "post": { + "summary": "Create an external-only multicast group configuration.", + "description": "External-only groups are used for IPv4 and non-admin-scoped IPv6 multicast traffic that doesn't require replication infrastructure. These groups use simple forwarding tables and require a NAT target.", + "operationId": "multicast_group_create_external", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupCreateExternalEntry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupExternalResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/external-groups/{group_ip}": { + "put": { + "summary": "Update an external-only multicast group configuration for a given group IP address.", + "description": "External-only groups are used for IPv4 and non-admin-scoped IPv6 multicast traffic that doesn't require replication infrastructure.", + "operationId": "multicast_group_update_external", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUpdateExternalEntry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupExternalResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/groups": { + "get": { + "summary": "List all multicast groups.", + "operationId": "multicast_groups_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponseResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Reset all multicast group configurations.", + "operationId": "multicast_reset", + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/groups/{group_ip}": { + "get": { + "summary": "Get the multicast group configuration for a given group IP address.", + "operationId": "multicast_group_get", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete a multicast group configuration by IP address.", + "operationId": "multicast_group_delete", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "type": "string", + "format": "ip" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/tags/{tag}": { + "get": { + "summary": "List all multicast groups with a given tag.", + "operationId": "multicast_groups_list_by_tag", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupResponseResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Delete all multicast groups (and associated routes) with a given tag.", + "operationId": "multicast_reset_by_tag", + "parameters": [ + { + "in": "path", + "name": "tag", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/underlay-groups": { + "post": { + "summary": "Create an underlay (internal) multicast group configuration.", + "description": "Underlay groups are used for admin-scoped IPv6 multicast traffic that requires replication infrastructure. These groups support both external and underlay members with full replication capabilities.", + "operationId": "multicast_group_create_underlay", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupCreateUnderlayEntry" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/underlay-groups/{group_ip}": { + "get": { + "summary": "Get an underlay (internal) multicast group configuration by admin-scoped", + "description": "IPv6 address.\n\nUnderlay groups handle admin-scoped IPv6 multicast traffic with replication infrastructure for external and underlay members.", + "operationId": "multicast_group_get_underlay", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "$ref": "#/components/schemas/AdminScopedIpv6" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Update an underlay (internal) multicast group configuration for a given", + "description": "group IP address.\n\nUnderlay groups are used for admin-scoped IPv6 multicast traffic that requires replication infrastructure with external and underlay members.", + "operationId": "multicast_group_update_underlay", + "parameters": [ + { + "in": "path", + "name": "group_ip", + "required": true, + "schema": { + "$ref": "#/components/schemas/AdminScopedIpv6" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUpdateUnderlayEntry" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MulticastGroupUnderlayResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/multicast/untagged": { + "delete": { + "summary": "Delete all multicast groups (and associated routes) without a tag.", + "operationId": "multicast_reset_untagged", + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4": { + "get": { + "summary": "Get all of the external addresses in use for IPv4 NAT mappings.", + "operationId": "nat_ipv4_addresses_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ipv4ResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all IPv4 NAT mappings.", + "operationId": "nat_ipv4_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4/{ipv4}": { + "get": { + "summary": "Get all of the external->internal NAT mappings for a given IPv4 address.", + "operationId": "nat_ipv4_list", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4NatResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/nat/ipv4/{ipv4}/{low}": { + "get": { + "summary": "Get the external->internal NAT mapping for the given address/port", + "operationId": "nat_ipv4_get", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear the NAT mappings for an IPv4 address and starting L3 port.", + "operationId": "nat_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv4/{ipv4}/{low}/{high}": { + "put": { + "summary": "Add an external->internal NAT mapping for the given address/port range", + "description": "This maps an external IPv6 address and L3 port range to: - A gimlet's IPv6 address - A gimlet's MAC address - A Geneve VNI\n\nThese identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external IPv6 and port range when making connections outside of an Oxide rack.", + "operationId": "nat_ipv4_create", + "parameters": [ + { + "in": "path", + "name": "high", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + { + "in": "path", + "name": "ipv4", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6": { + "get": { + "summary": "Get all of the external addresses in use for NAT mappings.", + "operationId": "nat_ipv6_addresses_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ipv6ResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "delete": { + "summary": "Clear all IPv6 NAT mappings.", + "operationId": "nat_ipv6_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6/{ipv6}": { + "get": { + "summary": "Get all of the external->internal NAT mappings for a given address.", + "operationId": "nat_ipv6_list", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6NatResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/nat/ipv6/{ipv6}/{low}": { + "get": { + "summary": "Get the external->internal NAT mapping for the given address and starting L3", + "description": "port.", + "operationId": "nat_ipv6_get", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete the NAT mapping for an IPv6 address and starting L3 port.", + "operationId": "nat_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/nat/ipv6/{ipv6}/{low}/{high}": { + "put": { + "summary": "Add an external->internal NAT mapping for the given address and L3 port", + "description": "range.\n\nThis maps an external IPv6 address and L3 port range to: - A gimlet's IPv6 address - A gimlet's MAC address - A Geneve VNI\n\nThese identify the gimlet on which a guest is running, and gives OPTE the information it needs to identify the guest VM that uses the external IPv6 and port range when making connections outside of an Oxide rack.", + "operationId": "nat_ipv6_create", + "parameters": [ + { + "in": "path", + "name": "high", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + { + "in": "path", + "name": "ipv6", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "low", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NatTarget" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ndp": { + "get": { + "summary": "Fetch the IPv6 NDP table entries.", + "description": "This returns a paginated list of all IPv6 neighbors directly connected to the switch.", + "operationId": "ndp_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv6 NDP entry, mapping an IPv6 address to a MAC address.", + "operationId": "ndp_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all entries in the the IPv6 NDP tables.", + "operationId": "ndp_reset", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ndp/{ip}": { + "get": { + "summary": "Get a single IPv6 NDP table entry, by its IPv6 address.", + "operationId": "ndp_get", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArpEntry" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove an IPv6 NDP entry, by its IPv6 address.", + "operationId": "ndp_delete", + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/port/{port_id}/settings": { + "get": { + "summary": "Get port settings atomically.", + "operationId": "port_settings_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Apply port settings atomically.", + "description": "These settings will be applied holistically, and to the extent possible atomically to a given port. In the event of a failure a rollback is attempted. If the rollback fails there will be inconsistent state. This failure mode returns the error code \"rollback failure\". For more details see the docs on the [`PortSettings`] type.", + "operationId": "port_settings_apply", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear port settings atomically.", + "operationId": "port_settings_clear", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "tag", + "description": "Restrict operations on this port to the provided tag.", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortSettings" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports": { + "get": { + "summary": "List all switch ports on the system.", + "operationId": "port_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_PortId", + "type": "array", + "items": { + "$ref": "#/components/schemas/PortId" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}": { + "get": { + "summary": "Return information about a single switch port.", + "operationId": "port_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPort" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/led": { + "get": { + "summary": "Return the current state of the attention LED on a front-facing QSFP port.", + "operationId": "led_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Led" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Override the current state of the attention LED on a front-facing QSFP port.", + "description": "The attention LED normally follows the state of the port itself. For example, if a transceiver is powered and operating normally, then the LED is solid on. An unexpected power fault would then be reflected by powering off the LED.\n\nThe client may override this behavior, explicitly setting the LED to a specified state. This can be undone, sending the LED back to its default policy, with the endpoint `/ports/{port_id}/led/auto`.", + "operationId": "led_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LedState" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/led/auto": { + "put": { + "summary": "Set the LED policy to automatic.", + "description": "The automatic LED policy ensures that the state of the LED follows the state of the switch port itself.", + "operationId": "led_set_auto", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links": { + "get": { + "summary": "List the links within a single switch port.", + "operationId": "link_list", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Link", + "type": "array", + "items": { + "$ref": "#/components/schemas/Link" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Create a link on a switch port.", + "description": "Create an interface that can be used for sending Ethernet frames on the provided switch port. This will use the first available lanes in the physical port to create an interface of the desired speed, if possible.", + "operationId": "link_create", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkId" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}": { + "get": { + "summary": "Get an existing link by ID.", + "operationId": "link_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Link" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete a link from a switch port.", + "operationId": "link_delete", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/autoneg": { + "get": { + "summary": "Return whether the link is configured to use autonegotiation with its peer", + "description": "link.", + "operationId": "link_autoneg_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is configured to use autonegotation with its peer link.", + "operationId": "link_autoneg_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ber": { + "get": { + "summary": "Return the estimated bit-error rate (BER) for a link.", + "operationId": "link_ber_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ber" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/enabled": { + "get": { + "summary": "Return whether the link is enabled.", + "operationId": "link_enabled_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Enable or disable a link.", + "operationId": "link_enabled_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/fault": { + "get": { + "summary": "Return any fault currently set on this link", + "operationId": "link_fault_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FaultCondition" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Inject a fault on this link", + "operationId": "link_fault_inject", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear any fault currently set on this link", + "operationId": "link_fault_clear", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/history": { + "get": { + "summary": "Get the event history for the given link.", + "operationId": "link_history_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkHistory" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv4": { + "get": { + "summary": "List the IPv4 addresses associated with a link.", + "operationId": "link_ipv4_list", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4EntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv4 address to a link.", + "operationId": "link_ipv4_create", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear all IPv4 addresses from a link.", + "operationId": "link_ipv4_reset", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv4/{address}": { + "delete": { + "summary": "Remove an IPv4 address from a link.", + "operationId": "link_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The IPv4 address on which to operate.", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6": { + "get": { + "summary": "List the IPv6 addresses associated with a link.", + "operationId": "link_ipv6_list", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6EntryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Add an IPv6 address to a link.", + "operationId": "link_ipv6_create", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6Entry" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Clear all IPv6 addresses from a link.", + "operationId": "link_ipv6_reset", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6/{address}": { + "delete": { + "summary": "Remove an IPv6 address from a link.", + "operationId": "link_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "address", + "description": "The IPv6 address on which to operate.", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/ipv6_enabled": { + "get": { + "summary": "Return whether the link is configured to act as an IPv6 endpoint", + "operationId": "link_ipv6_enabled_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is configured to act as an IPv6 endpoint", + "operationId": "link_ipv6_enabled_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/kr": { + "get": { + "summary": "Return whether the link is in KR mode.", + "description": "\"KR\" refers to the Ethernet standard for the link, which are defined in various clauses of the IEEE 802.3 specification. \"K\" is used to denote a link over an electrical cabled backplane, and \"R\" refers to \"scrambled encoding\", a 64B/66B bit-encoding scheme.\n\nThus this should be true iff a link is on the cabled backplane.", + "operationId": "link_kr_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Enable or disable a link.", + "operationId": "link_kr_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/linkup": { + "get": { + "summary": "Return whether a link is up.", + "operationId": "link_linkup_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/mac": { + "get": { + "summary": "Get a link's MAC address.", + "operationId": "link_mac_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MacAddr" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set a link's MAC address.", + "operationId": "link_mac_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MacAddr" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/nat_only": { + "get": { + "summary": "Return whether the link is configured to drop non-nat traffic", + "operationId": "link_nat_only_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set whether a port is configured to use drop non-nat traffic", + "operationId": "link_nat_only_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Boolean", + "type": "boolean" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/prbs": { + "get": { + "summary": "Return the link's PRBS speed and mode.", + "description": "During link training, a pseudorandom bit sequence (PRBS) is used to allow each side to synchronize their clocks and set various parameters on the underlying circuitry (such as filter gains).", + "operationId": "link_prbs_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortPrbsMode" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set a link's PRBS speed and mode.", + "operationId": "link_prbs_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortPrbsMode" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/adapt": { + "get": { + "summary": "Get the per-lane adaptation counts for each lane on this link", + "operationId": "link_rx_adapt_count_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_DfeAdaptationState", + "type": "array", + "items": { + "$ref": "#/components/schemas/DfeAdaptationState" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/anlt_status": { + "get": { + "summary": "Get the per-lane AN/LT status for each lane on this link", + "operationId": "link_an_lt_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnLtStatus" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/enc_speed": { + "get": { + "summary": "Get the per-lane speed and encoding for each lane on this link", + "operationId": "link_enc_speed_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_EncSpeed", + "type": "array", + "items": { + "$ref": "#/components/schemas/EncSpeed" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/eye": { + "get": { + "summary": "Get the per-lane eye measurements for each lane on this link", + "operationId": "link_eye_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_SerdesEye", + "type": "array", + "items": { + "$ref": "#/components/schemas/SerdesEye" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/lane_map": { + "get": { + "summary": "Get the logical->physical mappings for each lane in this port", + "operationId": "lane_map_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LaneMap" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/rx_sig": { + "get": { + "summary": "Get the per-lane rx signal info for each lane on this link", + "operationId": "link_rx_sig_info_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_RxSigInfo", + "type": "array", + "items": { + "$ref": "#/components/schemas/RxSigInfo" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/links/{link_id}/serdes/tx_eq": { + "get": { + "summary": "Get the per-lane tx eq settings for each lane on this link", + "operationId": "link_tx_eq_get", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TxEqSwHw", + "type": "array", + "items": { + "$ref": "#/components/schemas/TxEqSwHw" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Update the per-lane tx eq settings for all lanes on this link", + "operationId": "link_tx_eq_set", + "parameters": [ + { + "in": "path", + "name": "link_id", + "description": "The link in the switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxEq" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/management-mode": { + "get": { + "summary": "Return the current management mode of a QSFP switch port.", + "operationId": "management_mode_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagementMode" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Set the current management mode of a QSFP switch port.", + "operationId": "management_mode_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagementMode" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver": { + "get": { + "summary": "Return the information about a port's transceiver.", + "description": "This returns the status (presence, power state, etc) of the transceiver along with its identifying information. If the port is an optical switch port, but has no transceiver, then the identifying information is empty.\n\nIf the switch port is not a QSFP port, and thus could never have a transceiver, then \"Not Found\" is returned.", + "operationId": "transceiver_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transceiver" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/datapath": { + "get": { + "summary": "Fetch the state of the datapath for the provided transceiver.", + "operationId": "transceiver_datapath_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Datapath" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/monitors": { + "get": { + "summary": "Fetch the monitored environmental information for the provided transceiver.", + "operationId": "transceiver_monitors_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Monitors" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/power": { + "get": { + "summary": "Return the power state of a transceiver.", + "operationId": "transceiver_power_get", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PowerState" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Control the power state of a transceiver.", + "operationId": "transceiver_power_set", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PowerState" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/ports/{port_id}/transceiver/reset": { + "post": { + "summary": "Effect a module-level reset of a QSFP transceiver.", + "description": "If the QSFP port has no transceiver or is not a QSFP port, then a client error is returned.", + "operationId": "transceiver_reset", + "parameters": [ + { + "in": "path", + "name": "port_id", + "description": "The switch port on which to operate.", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4": { + "get": { + "summary": "Fetch the configured IPv4 routes, mapping IPv4 CIDR blocks to the switch port", + "description": "used for sending out that traffic, and optionally a gateway.", + "operationId": "route_ipv4_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RoutesResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "put": { + "summary": "Route an IPv4 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to replace any existing routes with a new single-path route.", + "operationId": "route_ipv4_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Route an IPv4 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to add new targets to a multipath route.", + "operationId": "route_ipv4_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv4RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4/{cidr}": { + "get": { + "summary": "Get the configured route for the given IPv4 subnet.", + "operationId": "route_ipv4_get", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv4 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv4Route", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Route" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove all targets for the given subnet", + "operationId": "route_ipv4_delete", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv4 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}": { + "delete": { + "summary": "Remove a single target for the given IPv4 subnet", + "operationId": "route_ipv4_delete_target", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The subnet being routed", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv4Net" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "path", + "name": "tgt_ip", + "description": "The next hop in the IPv4 route", + "required": true, + "schema": { + "type": "string", + "format": "ipv4" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6": { + "get": { + "summary": "Fetch the configured IPv6 routes, mapping IPv6 CIDR blocks to the switch port", + "description": "used for sending out that traffic, and optionally a gateway.", + "operationId": "route_ipv6_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RoutesResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "put": { + "summary": "Route an IPv6 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to replace any existing routes with a new single-path route.", + "operationId": "route_ipv6_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Route an IPv6 subnet to a link and a nexthop gateway.", + "description": "This call can be used to create a new single-path route or to add new targets to a multipath route.", + "operationId": "route_ipv6_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ipv6RouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6/{cidr}": { + "get": { + "summary": "Get a single IPv6 route, by its IPv6 CIDR block.", + "operationId": "route_ipv6_get", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv6 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_Ipv6Route", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Route" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Remove an IPv6 route, by its IPv6 CIDR block.", + "operationId": "route_ipv6_delete", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The IPv6 subnet in CIDR notation whose route entry is returned.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/route/ipv6/{cidr}/{port_id}/{link_id}/{tgt_ip}": { + "delete": { + "summary": "Remove a single target for the given IPv6 subnet", + "operationId": "route_ipv6_delete_target", + "parameters": [ + { + "in": "path", + "name": "cidr", + "description": "The subnet being routed", + "required": true, + "schema": { + "$ref": "#/components/schemas/Ipv6Net" + } + }, + { + "in": "path", + "name": "link_id", + "description": "The link to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/LinkId" + } + }, + { + "in": "path", + "name": "port_id", + "description": "The switch port to which packets should be sent", + "required": true, + "schema": { + "$ref": "#/components/schemas/PortId" + } + }, + { + "in": "path", + "name": "tgt_ip", + "description": "The next hop in the IPv4 route", + "required": true, + "schema": { + "type": "string", + "format": "ipv6" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/rpw/nat/gen": { + "get": { + "summary": "Get NAT generation number", + "operationId": "nat_generation", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "int64", + "type": "integer", + "format": "int64" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/rpw/nat/trigger": { + "post": { + "summary": "Trigger NAT Reconciliation", + "operationId": "nat_trigger_update", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Null", + "type": "string", + "enum": [ + null + ] + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/switch/identifiers": { + "get": { + "summary": "Get switch identifiers.", + "description": "This endpoint returns the switch identifiers, which can be used for consistent field definitions across oximeter time series schemas.", + "operationId": "switch_identifiers", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchIdentifiers" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table": { + "get": { + "summary": "Get the list of P4 tables", + "operationId": "table_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table/{table}/counters": { + "get": { + "summary": "Get any counter data from a single P4 match-action table.", + "description": "The name of the table should match one of those returned by the `table_list()` call.", + "operationId": "table_counters", + "parameters": [ + { + "in": "query", + "name": "force_sync", + "description": "Force a sync of the counters from the ASIC to memory, even if the default refresh timeout hasn't been reached.", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "path", + "name": "table", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_TableCounterEntry", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableCounterEntry" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/table/{table}/dump": { + "get": { + "summary": "Get the contents of a single P4 table.", + "description": "The name of the table should match one of those returned by the `table_list()` call.", + "operationId": "table_dump", + "parameters": [ + { + "in": "path", + "name": "table", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/transceivers": { + "get": { + "summary": "Return information about all QSFP transceivers.", + "operationId": "transceivers_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_Transceiver", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Transceiver" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "schemas": { + "AdminScopedIpv6": { + "description": "A validated admin-scoped IPv6 multicast address.\n\nAdmin-scoped addresses are ff04::/16, ff05::/16, or ff08::/16. These are used for internal/underlay multicast groups.", + "type": "string", + "format": "ipv6" + }, + "AnLtStatus": { + "description": "A collection of the data involved in the autonegiation/link-training process", + "type": "object", + "properties": { + "lanes": { + "description": "The per-lane status", + "type": "array", + "items": { + "$ref": "#/components/schemas/LaneStatus" + } + }, + "lp_pages": { + "description": "The base and extended pages received from the link partner", + "allOf": [ + { + "$ref": "#/components/schemas/LpPages" + } + ] + } + }, + "required": [ + "lanes", + "lp_pages" + ] + }, + "AnStatus": { + "description": "State of a single lane during autonegotiation", + "type": "object", + "properties": { + "an_ability": { + "description": "Are we capable of AN?", + "type": "boolean" + }, + "an_complete": { + "description": "Is autonegotiation complete?", + "type": "boolean" + }, + "ext_np_status": { + "description": "Is extended page format supported?", + "type": "boolean" + }, + "link_status": { + "description": "Allegedly: is the link up? In practice, this always seems to be false? TODO: investigate this", + "type": "boolean" + }, + "lp_an_ability": { + "description": "Can the link partner perform AN?", + "type": "boolean" + }, + "page_rcvd": { + "description": "has a base page been received?", + "type": "boolean" + }, + "parallel_detect_fault": { + "description": "A fault has been detected via the parallel detection function", + "type": "boolean" + }, + "remote_fault": { + "description": "Remote fault detected", + "type": "boolean" + } + }, + "required": [ + "an_ability", + "an_complete", + "ext_np_status", + "link_status", + "lp_an_ability", + "page_rcvd", + "parallel_detect_fault", + "remote_fault" + ] + }, + "ApplicationDescriptor": { + "description": "An Application Descriptor describes the supported datapath configurations.\n\nThis is a CMIS-specific concept. It's used for modules to advertise how it can be used by the host. Each application describes the host-side electrical interface; the media-side interface; the number of lanes required; etc.\n\nHost-side software can select one of these applications to instruct the module to use a specific set of lanes, with the interface on either side of the module.", + "type": "object", + "properties": { + "host_id": { + "description": "The electrical interface with the host side.", + "type": "string" + }, + "host_lane_assignment_options": { + "description": "The lanes on the host-side supporting this application.\n\nThis is a bit mask with a 1 identifying the lowest lane in a consecutive group of lanes to which the application can be assigned. This must be used with the `host_lane_count`. For example a value of `0b0000_0001` with a host lane count of 4 indicates that the first 4 lanes may be used in this application.\n\nAn application may support starting from multiple lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "host_lane_count": { + "description": "The number of host-side lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "media_id": { + "description": "The interface, optical or copper, with the media side.", + "allOf": [ + { + "$ref": "#/components/schemas/MediaInterfaceId" + } + ] + }, + "media_lane_assignment_options": { + "description": "The lanes on the media-side supporting this application.\n\nSee `host_lane_assignment_options` for details.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "media_lane_count": { + "description": "The number of media-side lanes.", + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "host_id", + "host_lane_assignment_options", + "host_lane_count", + "media_id", + "media_lane_assignment_options", + "media_lane_count" + ] + }, + "ArpEntry": { + "description": "Represents the mapping of an IP address to a MAC address.", + "type": "object", + "properties": { + "ip": { + "description": "The IP address for the entry.", + "type": "string", + "format": "ip" + }, + "mac": { + "description": "The MAC address to which `ip` maps.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "tag": { + "description": "A tag used to associate this entry with a client.", + "type": "string" + }, + "update": { + "description": "The time the entry was updated", + "type": "string" + } + }, + "required": [ + "ip", + "mac", + "tag", + "update" + ] + }, + "ArpEntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/ArpEntry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "AttachedSubnetEntry": { + "description": "represents an external subnet mapping", + "type": "object", + "properties": { + "subnet": { + "$ref": "#/components/schemas/IpNet" + }, + "tgt": { + "$ref": "#/components/schemas/InstanceTarget" + } + }, + "required": [ + "subnet", + "tgt" + ] + }, + "AttachedSubnetEntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/AttachedSubnetEntry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Aux1Monitor": { + "description": "The first auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The monitored property is custom, i.e., part-specific.", + "type": "object", + "properties": { + "custom": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "custom" + ], + "additionalProperties": false + }, + { + "description": "The current of the laser thermoelectric cooler.\n\nFor actively-cooled laser systems, this specifies the percentage of the maximum current the thermoelectric cooler supports. If the percentage is positive, the cooler is heating the laser. If negative, the cooler is cooling the laser.", + "type": "object", + "properties": { + "tec_current": { + "type": "number", + "format": "float" + } + }, + "required": [ + "tec_current" + ], + "additionalProperties": false + } + ] + }, + "Aux2Monitor": { + "description": "The second auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The temperature of the laser itself (degrees C).", + "type": "object", + "properties": { + "laser_temperature": { + "type": "number", + "format": "float" + } + }, + "required": [ + "laser_temperature" + ], + "additionalProperties": false + }, + { + "description": "The current of the laser thermoelectric cooler.\n\nFor actively-cooled laser systems, this specifies the percentage of the maximum current the thermoelectric cooler supports. If the percentage is positive, the cooler is heating the laser. If negative, the cooler is cooling the laser.", + "type": "object", + "properties": { + "tec_current": { + "type": "number", + "format": "float" + } + }, + "required": [ + "tec_current" + ], + "additionalProperties": false + } + ] + }, + "Aux3Monitor": { + "description": "The third auxlliary CMIS monitor.", + "oneOf": [ + { + "description": "The temperature of the laser itself (degrees C).", + "type": "object", + "properties": { + "laser_temperature": { + "type": "number", + "format": "float" + } + }, + "required": [ + "laser_temperature" + ], + "additionalProperties": false + }, + { + "description": "Measured voltage of an additional power supply (Volts).", + "type": "object", + "properties": { + "additional_supply_voltage": { + "type": "number", + "format": "float" + } + }, + "required": [ + "additional_supply_voltage" + ], + "additionalProperties": false + } + ] + }, + "AuxMonitors": { + "description": "Auxlliary monitored values for CMIS modules.", + "type": "object", + "properties": { + "aux1": { + "nullable": true, + "description": "Auxlliary monitor 1, either a custom value or TEC current.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux1Monitor" + } + ] + }, + "aux2": { + "nullable": true, + "description": "Auxlliary monitor 1, either laser temperature or TEC current.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux2Monitor" + } + ] + }, + "aux3": { + "nullable": true, + "description": "Auxlliary monitor 1, either laser temperature or additional supply voltage.", + "allOf": [ + { + "$ref": "#/components/schemas/Aux3Monitor" + } + ] + }, + "custom": { + "nullable": true, + "description": "A custom monitor. The value here is entirely vendor- and part-specific, so the part's data sheet must be consulted. The value may be either a signed or unsigned 16-bit integer, and so is included as raw bytes.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "BackplaneCableLeg": { + "description": "The leg of the backplane cable.\n\nThis describes the leg on the actual backplane cable that connects the Sidecar chassis connector to a cubby endpoint.", + "type": "string", + "enum": [ + "A", + "B", + "C", + "D" + ] + }, + "BackplaneLink": { + "description": "A single point-to-point connection on the cabled backplane.\n\nThis describes a single link from the Sidecar switch to a cubby, via the cabled backplane. It ultimately maps the Tofino ASIC pins to the cubby at which that link terminates. This path follows the Sidecar internal cable; the Sidecar chassis connector; and the backplane cable itself. This is used to map the Tofino driver's \"connector\" number (an index in its possible pinouts) through the backplane to our logical cubby numbering.", + "type": "object", + "properties": { + "backplane_leg": { + "$ref": "#/components/schemas/BackplaneCableLeg" + }, + "cubby": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "sidecar_connector": { + "$ref": "#/components/schemas/SidecarConnector" + }, + "sidecar_leg": { + "$ref": "#/components/schemas/SidecarCableLeg" + }, + "tofino_connector": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "backplane_leg", + "cubby", + "sidecar_connector", + "sidecar_leg", + "tofino_connector" + ] + }, + "Ber": { + "description": "Reports the bit-error rate (BER) for a link.", + "type": "object", + "properties": { + "ber": { + "description": "Estimated BER per-lane.", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "symbol_errors": { + "description": "Counters of symbol errors per-lane.", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "total_ber": { + "description": "Aggregate BER on the link.", + "type": "number", + "format": "float" + } + }, + "required": [ + "ber", + "symbol_errors", + "total_ber" + ] + }, + "BuildInfo": { + "description": "Detailed build information about `dpd`.", + "type": "object", + "properties": { + "cargo_triple": { + "type": "string" + }, + "debug": { + "type": "boolean" + }, + "git_branch": { + "type": "string" + }, + "git_commit_timestamp": { + "type": "string" + }, + "git_sha": { + "type": "string" + }, + "opt_level": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "rustc_channel": { + "type": "string" + }, + "rustc_commit_sha": { + "type": "string" + }, + "rustc_host_triple": { + "type": "string" + }, + "rustc_semver": { + "type": "string" + }, + "sde_commit_sha": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "cargo_triple", + "debug", + "git_branch", + "git_commit_timestamp", + "git_sha", + "opt_level", + "rustc_channel", + "rustc_commit_sha", + "rustc_host_triple", + "rustc_semver", + "sde_commit_sha", + "version" + ] + }, + "CmisDatapath": { + "description": "A datapath in a CMIS module.\n\nIn contrast to SFF-8636, CMIS makes first-class the concept of a datapath: a set of lanes and all the associated machinery involved in the transfer of data. This includes:\n\n- The \"application descriptor\" which is the host and media interfaces, and the lanes on each side used to transfer data; - The state of the datapath in a well-defined finite state machine (see CMIS 5.0 section 6.3.3); - The flags indicating how the datapath components are operating, such as receiving an input Rx signal or whether the transmitter is disabled.", + "type": "object", + "properties": { + "application": { + "description": "The application descriptor for this datapath.", + "allOf": [ + { + "$ref": "#/components/schemas/ApplicationDescriptor" + } + ] + }, + "lane_status": { + "description": "The status bits for each lane in the datapath.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CmisLaneStatus" + } + } + }, + "required": [ + "application", + "lane_status" + ] + }, + "CmisLaneStatus": { + "description": "The status of a single CMIS lane.\n\nIf any particular control or status value is unsupported by a module, it is `None`.", + "type": "object", + "properties": { + "rx_auto_squelch_disable": { + "nullable": true, + "description": "Whether the host-side has disabled the Rx auto-squelch.\n\nThe module can implement automatic squelching of the Rx output, if the media-side input signal isn't valid. This indicates whether the host has disabled such a setting.", + "type": "boolean" + }, + "rx_lol": { + "nullable": true, + "description": "Media-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the media-side signal (usually optical).", + "type": "boolean" + }, + "rx_los": { + "nullable": true, + "description": "Media-side loss of signal flag.\n\nThis is true if there is no detected input signal from the media-side (usually optical).", + "type": "boolean" + }, + "rx_output_enabled": { + "nullable": true, + "description": "Whether the Rx output is enabled.\n\nThe host may control this to disable the electrical output from the module to the host.", + "type": "boolean" + }, + "rx_output_polarity": { + "nullable": true, + "description": "The Rx output polarity.\n\nThis indicates a host-side control that flips the polarity of the host-side output signal.", + "allOf": [ + { + "$ref": "#/components/schemas/LanePolarity" + } + ] + }, + "rx_output_status": { + "description": "Status of host-side Rx output.\n\nThis indicates whether the Rx output is sending a valid signal to the host. Note that this is `Invalid` if the output is either muted (such as squelched) or explicitly disabled.", + "allOf": [ + { + "$ref": "#/components/schemas/OutputStatus" + } + ] + }, + "state": { + "description": "The datapath state of this lane.\n\nSee CMIS 5.0 section 8.9.1 for details.", + "type": "string" + }, + "tx_adaptive_eq_fail": { + "nullable": true, + "description": "A failure in the Tx adaptive input equalization.", + "type": "boolean" + }, + "tx_auto_squelch_disable": { + "nullable": true, + "description": "Whether the host-side has disabled the Tx auto-squelch.\n\nThe module can implement automatic squelching of the Tx output, if the host-side input signal isn't valid. This indicates whether the host has disabled such a setting.", + "type": "boolean" + }, + "tx_failure": { + "nullable": true, + "description": "General Tx failure flag.\n\nThis indicates that an internal and unspecified malfunction has occurred on the Tx lane.", + "type": "boolean" + }, + "tx_force_squelch": { + "nullable": true, + "description": "Whether the host-side has force-squelched the Tx output.\n\nThis indicates that the host can _force_ squelching the output if the signal is not valid.", + "type": "boolean" + }, + "tx_input_polarity": { + "nullable": true, + "description": "The Tx input polarity.\n\nThis indicates a host-side control that flips the polarity of the host-side input signal.", + "allOf": [ + { + "$ref": "#/components/schemas/LanePolarity" + } + ] + }, + "tx_lol": { + "nullable": true, + "description": "Host-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the host-side electrical signal.", + "type": "boolean" + }, + "tx_los": { + "nullable": true, + "description": "Host-side loss of signal flag.\n\nThis is true if there is no detected electrical signal from the host-side serdes.", + "type": "boolean" + }, + "tx_output_enabled": { + "nullable": true, + "description": "Whether the Tx output is enabled.", + "type": "boolean" + }, + "tx_output_status": { + "description": "Status of media-side Tx output.\n\nThis indicates whether the Rx output is sending a valid signal to the media itself. Note that this is `Invalid` if the output is either muted (such as squelched) or explicitly disabled.", + "allOf": [ + { + "$ref": "#/components/schemas/OutputStatus" + } + ] + } + }, + "required": [ + "rx_output_status", + "state", + "tx_output_status" + ] + }, + "CounterData": { + "description": "For a counter, this contains the number of bytes, packets, or both that were counted. XXX: Ideally this would be a data-bearing enum, with variants for Pkts, Bytes, and PktsAndBytes. However OpenApi doesn't yet have the necessary support, so we're left with this clumsier representation.", + "type": "object", + "properties": { + "bytes": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pkts": { + "nullable": true, + "type": "integer", + "format": "uint64", + "minimum": 0 + } + } + }, + "Datapath": { + "description": "Information about a transceiver's datapath.\n\nThis includes state related to the low-level eletrical and optical path through which bits flow. This includes flags like loss-of-signal / loss-of-lock; transmitter enablement state; and equalization parameters.", + "oneOf": [ + { + "description": "A number of datapaths in a CMIS module.\n\nCMIS modules may have a large number of supported configurations of their various lanes, each called an \"application\". These are described by the `ApplicationDescriptor` type, which mirrors CMIS 5.0 table 8-18. Each descriptor is identified by an \"Application Selector Code\", which is just its index in the section of the memory map describing them.\n\nEach lane can be used in zero or more applications, however, it may exist in at most one application at a time. These active applications, of which there may be more than one, are keyed by their codes in the contained mapping.", + "type": "object", + "properties": { + "cmis": { + "type": "object", + "properties": { + "connector": { + "description": "The type of free-side connector", + "type": "string" + }, + "datapaths": { + "description": "Mapping from \"application selector\" ID to its datapath information.\n\nThe datapath inclues the lanes used; host electrical interface; media interface; and a lot more about the state of the path.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CmisDatapath" + } + }, + "supported_lanes": { + "description": "A bit mask with a 1 in bit `i` if the `i`th lane is supported.", + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "connector", + "datapaths", + "supported_lanes" + ] + } + }, + "required": [ + "cmis" + ], + "additionalProperties": false + }, + { + "description": "Datapath state about each lane in an SFF-8636 module.", + "type": "object", + "properties": { + "sff8636": { + "type": "object", + "properties": { + "connector": { + "description": "The type of a media-side connector.\n\nThese values come from SFF-8024 Rev 4.10 Table 4-3.", + "type": "string" + }, + "lanes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sff8636Datapath" + }, + "minItems": 4, + "maxItems": 4 + }, + "specification": { + "$ref": "#/components/schemas/SffComplianceCode" + } + }, + "required": [ + "connector", + "lanes", + "specification" + ] + } + }, + "required": [ + "sff8636" + ], + "additionalProperties": false + } + ] + }, + "DfeAdaptationState": { + "description": "Rx DFE adaptation information", + "type": "object", + "properties": { + "adapt_cnt": { + "description": "Total DFE attempts", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "adapt_done": { + "description": "DFE complete", + "type": "boolean" + }, + "link_lost_cnt": { + "description": "Times the signal was lost since the last read", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readapt_cnt": { + "description": "DFE attempts since the last read", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "adapt_cnt", + "adapt_done", + "link_lost_cnt", + "readapt_cnt" + ] + }, + "Direction": { + "description": "Direction a multicast group member is reached by.\n\n`External` group members must have any packet encapsulation removed before packet delivery.", + "type": "string", + "enum": [ + "Underlay", + "External" + ] + }, + "ElectricalMode": { + "description": "The electrical mode of a QSFP-capable port.\n\nQSFP ports can be broken out into one of several different electrical configurations or modes. This describes how the transmit/receive lanes are grouped into a single, logical link.\n\nNote that the electrical mode may only be changed if there are no links within the port, _and_ if the inserted QSFP module actually supports this mode.", + "oneOf": [ + { + "description": "All transmit/receive lanes are used for a single link.", + "type": "string", + "enum": [ + "Single" + ] + } + ] + }, + "EncSpeed": { + "description": "Signal speed and encoding for a single lane", + "type": "object", + "properties": { + "encoding": { + "$ref": "#/components/schemas/LaneEncoding" + }, + "gigabits": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "encoding", + "gigabits" + ] + }, + "Error": { + "description": "Error information from a response.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "message", + "request_id" + ] + }, + "ExternalForwarding": { + "description": "Represents the forwarding configuration for external multicast traffic.", + "type": "object", + "properties": { + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + } + }, + "Fault": { + "description": "A Fault represents a specific kind of failure, and carries some additional context. Currently Faults are only used to describe Link failures, but there is no reason they couldn't be used elsewhere.", + "oneOf": [ + { + "type": "object", + "properties": { + "LinkFlap": { + "type": "string" + } + }, + "required": [ + "LinkFlap" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Autoneg": { + "type": "string" + } + }, + "required": [ + "Autoneg" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Injected": { + "type": "string" + } + }, + "required": [ + "Injected" + ], + "additionalProperties": false + } + ] + }, + "FaultCondition": { + "description": "Represents a potential fault condtion on a link", + "type": "object", + "properties": { + "fault": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Fault" + } + ] + } + } + }, + "FaultReason": { + "description": "The cause of a fault on a transceiver.", + "oneOf": [ + { + "description": "An error occurred accessing the transceiver.", + "type": "string", + "enum": [ + "failed" + ] + }, + { + "description": "Power was enabled, but did not come up in the requisite time.", + "type": "string", + "enum": [ + "power_timeout" + ] + }, + { + "description": "Power was enabled and later lost.", + "type": "string", + "enum": [ + "power_lost" + ] + }, + { + "description": "The service processor disabled the transceiver.\n\nThe SP is responsible for monitoring the thermal data from the transceivers, and controlling the fans to compensate. If a module's thermal data cannot be read, the SP may completely disable the transceiver to ensure it cannot overheat the Sidecar.", + "type": "string", + "enum": [ + "disabled_by_sp" + ] + } + ] + }, + "FecRSCounters": { + "description": "Per-port RS FEC counters", + "type": "object", + "properties": { + "fec_align_status": { + "description": "All lanes synced and aligned", + "type": "boolean" + }, + "fec_corr_cnt": { + "description": "FEC corrected blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_0": { + "description": "FEC symbol errors on lane 0", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_1": { + "description": "FEC symbol errors on lane 1", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_2": { + "description": "FEC symbol errors on lane 2", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_3": { + "description": "FEC symbol errors on lane 3", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_4": { + "description": "FEC symbol errors on lane 4", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_5": { + "description": "FEC symbol errors on lane 5", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_6": { + "description": "FEC symbol errors on lane 6", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_ser_lane_7": { + "description": "FEC symbol errors on lane 7", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "fec_uncorr_cnt": { + "description": "FEC uncorrected blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "hi_ser": { + "description": "symbol errors exceeds threshhold", + "type": "boolean" + }, + "port": { + "description": "Port being tracked", + "type": "string" + } + }, + "required": [ + "fec_align_status", + "fec_corr_cnt", + "fec_ser_lane_0", + "fec_ser_lane_1", + "fec_ser_lane_2", + "fec_ser_lane_3", + "fec_ser_lane_4", + "fec_ser_lane_5", + "fec_ser_lane_6", + "fec_ser_lane_7", + "fec_uncorr_cnt", + "hi_ser", + "port" + ] + }, + "FreeChannels": { + "description": "Represents the free MAC channels on a single physical port.", + "type": "object", + "properties": { + "channels": { + "description": "The set of available channels (lanes) on this connector.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "connector": { + "description": "The Tofino connector for this port.\n\nThis describes the set of electrical connections representing this port object, which are defined by the pinout and board design of the Sidecar.", + "type": "string" + }, + "port_id": { + "description": "The switch port.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "channels", + "connector", + "port_id" + ] + }, + "InstanceTarget": { + "description": "represents an internal target for either NAT or an external subnet mapping", + "type": "object", + "properties": { + "inner_mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "internal_ip": { + "type": "string", + "format": "ipv6" + }, + "vni": { + "$ref": "#/components/schemas/Vni" + } + }, + "required": [ + "inner_mac", + "internal_ip", + "vni" + ] + }, + "InternalForwarding": { + "description": "Represents the NAT target for multicast traffic for internal/underlay forwarding.", + "type": "object", + "properties": { + "nat_target": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/NatTarget" + } + ] + } + } + }, + "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, + "oneOf": [ + { + "title": "v4", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + { + "title": "v6", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + } + ] + }, + "IpSrc": { + "description": "Source filter match key for multicast traffic.", + "oneOf": [ + { + "description": "Exact match for the source IP address.", + "type": "object", + "properties": { + "Exact": { + "type": "string", + "format": "ip" + } + }, + "required": [ + "Exact" + ], + "additionalProperties": false + }, + { + "description": "Subnet match for the source IP address.", + "type": "object", + "properties": { + "Subnet": { + "$ref": "#/components/schemas/Ipv4Net" + } + }, + "required": [ + "Subnet" + ], + "additionalProperties": false + } + ] + }, + "Ipv4Entry": { + "description": "An IPv4 address assigned to a link.", + "type": "object", + "properties": { + "addr": { + "description": "The IP address.", + "type": "string", + "format": "ipv4" + }, + "tag": { + "description": "Client-side tag for this object.", + "type": "string" + } + }, + "required": [ + "addr", + "tag" + ] + }, + "Ipv4EntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Entry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv4Nat": { + "description": "represents an IPv4 NAT reservation", + "type": "object", + "properties": { + "external": { + "type": "string", + "format": "ipv4" + }, + "high": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "low": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "target": { + "$ref": "#/components/schemas/NatTarget" + } + }, + "required": [ + "external", + "high", + "low", + "target" + ] + }, + "Ipv4NatResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Nat" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv4Net": { + "example": "192.168.1.0/24", + "title": "An IPv4 subnet", + "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])$" + }, + "Ipv4Route": { + "description": "A route for an IPv4 subnet.", + "type": "object", + "properties": { + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + }, + "tag": { + "type": "string" + }, + "tgt_ip": { + "type": "string", + "format": "ipv4" + }, + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "link_id", + "port_id", + "tag", + "tgt_ip" + ] + }, + "Ipv4RouteUpdate": { + "description": "Represents a new or replacement mapping of a subnet to a single IPv4 RouteTarget nexthop target.", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + "replace": { + "description": "Should this route replace any existing route? If a route exists and this parameter is false, then the call will fail.", + "type": "boolean" + }, + "target": { + "description": "A single Route associated with this CIDR", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Route" + } + ] + } + }, + "required": [ + "cidr", + "replace", + "target" + ] + }, + "Ipv4Routes": { + "description": "Represents all mappings of an IPv4 subnet to a its nexthop target(s).", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + "targets": { + "description": "All RouteTargets associated with this CIDR", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Route" + } + } + }, + "required": [ + "cidr", + "targets" + ] + }, + "Ipv4RoutesResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Routes" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Entry": { + "description": "An IPv6 address assigned to a link.", + "type": "object", + "properties": { + "addr": { + "description": "The IP address.", + "type": "string", + "format": "ipv6" + }, + "tag": { + "description": "Client-side tag for this object.", + "type": "string" + } + }, + "required": [ + "addr", + "tag" + ] + }, + "Ipv6EntryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Entry" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Nat": { + "description": "represents an IPv6 NAT reservation", + "type": "object", + "properties": { + "external": { + "type": "string", + "format": "ipv6" + }, + "high": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "low": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "target": { + "$ref": "#/components/schemas/NatTarget" + } + }, + "required": [ + "external", + "high", + "low", + "target" + ] + }, + "Ipv6NatResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Nat" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Ipv6Net": { + "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])$" + }, + "Ipv6Route": { + "description": "A route for an IPv6 subnet.", + "type": "object", + "properties": { + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + }, + "tag": { + "type": "string" + }, + "tgt_ip": { + "type": "string", + "format": "ipv6" + }, + "vlan_id": { + "nullable": true, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "link_id", + "port_id", + "tag", + "tgt_ip" + ] + }, + "Ipv6RouteUpdate": { + "description": "Represents a new or replacement mapping of a subnet to a single IPv6 RouteTarget nexthop target.", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + }, + "replace": { + "description": "Should this route replace any existing route? If a route exists and this parameter is false, then the call will fail.", + "type": "boolean" + }, + "target": { + "description": "A single RouteTarget associated with this CIDR", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Route" + } + ] + } + }, + "required": [ + "cidr", + "replace", + "target" + ] + }, + "Ipv6Routes": { + "description": "Represents all mappings of an IPv6 subnet to a its nexthop target(s).", + "type": "object", + "properties": { + "cidr": { + "description": "Traffic destined for any address within the CIDR block is routed using this information.", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + }, + "targets": { + "description": "All RouteTargets associated with this CIDR", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Route" + } + } + }, + "required": [ + "cidr", + "targets" + ] + }, + "Ipv6RoutesResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv6Routes" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "LaneEncoding": { + "description": "Signal encoding", + "oneOf": [ + { + "description": "Pulse Amplitude Modulation 4-level", + "type": "string", + "enum": [ + "Pam4" + ] + }, + { + "description": "Non-Return-to-Zero encoding", + "type": "string", + "enum": [ + "Nrz" + ] + }, + { + "description": "No encoding selected", + "type": "string", + "enum": [ + "None" + ] + } + ] + }, + "LaneMap": { + "description": "Mapping of the logical lanes in a link to their physical instantiation in the MAC/serdes interface.", + "type": "object", + "properties": { + "logical_lane": { + "description": "logical lane within the mac block for each lane", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "mac_block": { + "description": "MAC block in the tofino ASIC", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "rx_phys": { + "description": "Rx logical->physical mapping", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "rx_polarity": { + "description": "Rx polarity", + "type": "array", + "items": { + "$ref": "#/components/schemas/Polarity" + } + }, + "tx_phys": { + "description": "Tx logical->physical mapping", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "tx_polarity": { + "description": "Tx polarity", + "type": "array", + "items": { + "$ref": "#/components/schemas/Polarity" + } + } + }, + "required": [ + "logical_lane", + "mac_block", + "rx_phys", + "rx_polarity", + "tx_phys", + "tx_polarity" + ] + }, + "LanePolarity": { + "description": "The polarity of a transceiver lane.", + "type": "string", + "enum": [ + "normal", + "flipped" + ] + }, + "LaneStatus": { + "description": "The combined status of a lane, with respect to the autonegotiation / link-training process.", + "type": "object", + "properties": { + "lane_an_status": { + "description": "Detailed autonegotiation status", + "allOf": [ + { + "$ref": "#/components/schemas/AnStatus" + } + ] + }, + "lane_done": { + "description": "Has a lane successfully completed autoneg and link training?", + "type": "boolean" + }, + "lane_lt_status": { + "description": "Detailed link-training status", + "allOf": [ + { + "$ref": "#/components/schemas/LtStatus" + } + ] + } + }, + "required": [ + "lane_an_status", + "lane_done", + "lane_lt_status" + ] + }, + "Led": { + "description": "Information about a QSFP port's LED.", + "type": "object", + "properties": { + "policy": { + "description": "The policy by which the LED is controlled.", + "allOf": [ + { + "$ref": "#/components/schemas/LedPolicy" + } + ] + }, + "state": { + "description": "The state of the LED.", + "allOf": [ + { + "$ref": "#/components/schemas/LedState" + } + ] + } + }, + "required": [ + "policy", + "state" + ] + }, + "LedPolicy": { + "description": "The policy by which a port's LED is controlled.", + "oneOf": [ + { + "description": "The default policy is for the LED to reflect the port's state itself.\n\nIf the port is operating normally, the LED will be solid on. Without a transceiver, the LED will be solid off. A blinking LED is used to indicate an unsupported module or other failure on that port.", + "type": "string", + "enum": [ + "automatic" + ] + }, + { + "description": "The LED is explicitly overridden by client requests.", + "type": "string", + "enum": [ + "override" + ] + } + ] + }, + "LedState": { + "description": "The state of a module's attention LED, on the Sidecar front IO panel.", + "oneOf": [ + { + "description": "The LED is off.\n\nThis indicates that the port is disabled or not working at all.", + "type": "string", + "enum": [ + "off" + ] + }, + { + "description": "The LED is solid on.\n\nThis indicates that the port is working as expected and enabled.", + "type": "string", + "enum": [ + "on" + ] + }, + { + "description": "The LED is blinking.\n\nThis is used to draw attention to the port, such as to indicate a fault or to locate a port for servicing.", + "type": "string", + "enum": [ + "blink" + ] + } + ] + }, + "Link": { + "description": "An Ethernet-capable link within a switch port.", + "type": "object", + "properties": { + "address": { + "description": "The MAC address for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "asic_id": { + "description": "The lower-level ASIC ID used to refer to this object in the switch driver software.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "autoneg": { + "description": "True if this link is configured to autonegotiate with its peer.", + "type": "boolean" + }, + "enabled": { + "description": "True if this link is enabled.", + "type": "boolean" + }, + "fec": { + "nullable": true, + "description": "The error-correction scheme for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "fsm_state": { + "description": "Current state in the autonegotiation/link-training finite state machine", + "type": "string" + }, + "ipv6_enabled": { + "description": "The link is configured for IPv6 use", + "type": "boolean" + }, + "kr": { + "description": "True if this link is in KR mode, i.e., is on a cabled backplane.", + "type": "boolean" + }, + "link_id": { + "description": "The `LinkId` within the switch port for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "link_state": { + "description": "The state of the Ethernet link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkState" + } + ] + }, + "media": { + "description": "The physical media underlying this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortMedia" + } + ] + }, + "port_id": { + "description": "The switch port on which this link exists.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + }, + "prbs": { + "description": "The PRBS mode.", + "allOf": [ + { + "$ref": "#/components/schemas/PortPrbsMode" + } + ] + }, + "presence": { + "description": "True if the transceiver module has detected a media presence.", + "type": "boolean" + }, + "speed": { + "description": "The speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + }, + "tofino_connector": { + "description": "The Tofino connector number associated with this link.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "address", + "asic_id", + "autoneg", + "enabled", + "fsm_state", + "ipv6_enabled", + "kr", + "link_id", + "link_state", + "media", + "port_id", + "prbs", + "presence", + "speed", + "tofino_connector" + ] + }, + "LinkCreate": { + "description": "Parameters used to create a link on a switch port.", + "type": "object", + "properties": { + "autoneg": { + "description": "Whether the link is configured to autonegotiate with its peer during link training.\n\nThis is generally only true for backplane links, and defaults to `false`.", + "default": false, + "type": "boolean" + }, + "fec": { + "nullable": true, + "description": "The requested forward-error correction method. If this is None, the standard FEC for the underlying media will be applied if it can be determined.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "kr": { + "description": "Whether the link is configured in KR mode, an electrical specification generally only true for backplane link.\n\nThis defaults to `false`.", + "default": false, + "type": "boolean" + }, + "lane": { + "nullable": true, + "description": "The first lane of the port to use for the new link", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "speed": { + "description": "The requested speed of the link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + }, + "tx_eq": { + "nullable": true, + "description": "Transceiver equalization adjustment parameters. This defaults to `None`.", + "default": null, + "allOf": [ + { + "$ref": "#/components/schemas/TxEq" + } + ] + } + }, + "required": [ + "speed" + ] + }, + "LinkEvent": { + "type": "object", + "properties": { + "channel": { + "nullable": true, + "description": "Channel ID for sub-link-level events", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "class": { + "description": "Event class", + "type": "string" + }, + "details": { + "nullable": true, + "description": "Optionally, additional details about the event", + "type": "string" + }, + "subclass": { + "description": "Event subclass", + "type": "string" + }, + "timestamp": { + "description": "Time the event occurred. The time is represented in milliseconds, starting at an undefined time in the past. This means that timestamps can be used to measure the time between events, but not to determine the wall-clock time at which the event occurred.", + "type": "integer", + "format": "int64" + } + }, + "required": [ + "class", + "subclass", + "timestamp" + ] + }, + "LinkFecRSCounters": { + "description": "The FEC counters for a specific link, including its link ID.", + "type": "object", + "properties": { + "counters": { + "description": "The FEC counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/FecRSCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkFsmCounter": { + "description": "Reports how many times a given autoneg/link-training state has been entered", + "type": "object", + "properties": { + "current": { + "description": "Times entered since the link was last enabled", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "state_name": { + "description": "FSM state being counted", + "type": "string" + }, + "total": { + "description": "Times entered since the link was created", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "current", + "state_name", + "total" + ] + }, + "LinkFsmCounters": { + "description": "Reports all the autoneg/link-training states a link has transitioned into.", + "type": "object", + "properties": { + "counters": { + "description": "All the states this link has entered, along with counts of how many times each state was entered.", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkFsmCounter" + } + }, + "link_path": { + "description": "Link being reported", + "type": "string" + } + }, + "required": [ + "counters", + "link_path" + ] + }, + "LinkHistory": { + "type": "object", + "properties": { + "events": { + "description": "The set of historical events recorded", + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkEvent" + } + }, + "timestamp": { + "description": "The timestamp in milliseconds at which this history was collected.", + "type": "integer", + "format": "int64" + } + }, + "required": [ + "events", + "timestamp" + ] + }, + "LinkId": { + "description": "An identifier for a link within a switch port.\n\nA switch port identified by a [`PortId`] may have multiple links within it, each identified by a `LinkId`. These are unique within a switch port only.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "LinkPcsCounters": { + "description": "The Physical Coding Sublayer (PCS) counters for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The PCS counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/PcsCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkRMonCounters": { + "description": "The RMON counters (traffic counters) for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The RMON counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/RMonCounters" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkRMonCountersAll": { + "description": "The complete RMON counters (traffic counters) for a specific link.", + "type": "object", + "properties": { + "counters": { + "description": "The RMON counter data.", + "allOf": [ + { + "$ref": "#/components/schemas/RMonCountersAll" + } + ] + }, + "link_id": { + "description": "The link ID.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "port_id": { + "description": "The switch port ID.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "counters", + "link_id", + "port_id" + ] + }, + "LinkSettings": { + "description": "An object with link settings used in concert with [`PortSettings`].", + "type": "object", + "properties": { + "addrs": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + }, + "uniqueItems": true + }, + "params": { + "$ref": "#/components/schemas/LinkCreate" + } + }, + "required": [ + "addrs", + "params" + ] + }, + "LinkState": { + "description": "The state of a data link with a peer.", + "oneOf": [ + { + "description": "An error was encountered while trying to configure the link in the switch hardware.", + "type": "object", + "properties": { + "config_error": { + "type": "string" + } + }, + "required": [ + "config_error" + ], + "additionalProperties": false + }, + { + "description": "The link is up.", + "type": "string", + "enum": [ + "up" + ] + }, + { + "description": "The link is down.", + "type": "string", + "enum": [ + "down" + ] + }, + { + "description": "The Link is offline due to a fault", + "type": "object", + "properties": { + "faulted": { + "$ref": "#/components/schemas/Fault" + } + }, + "required": [ + "faulted" + ], + "additionalProperties": false + }, + { + "description": "The link's state is not known.", + "type": "string", + "enum": [ + "unknown" + ] + } + ] + }, + "LinkUpCounter": { + "description": "Reports how many times a link has transitioned from Down to Up.", + "type": "object", + "properties": { + "current": { + "description": "LinkUp transitions since the link was last enabled", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "link_path": { + "description": "Link being reported", + "type": "string" + }, + "total": { + "description": "LinkUp transitions since the link was created", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "current", + "link_path", + "total" + ] + }, + "LpPages": { + "description": "Set of AN pages sent by our link partner", + "type": "object", + "properties": { + "base_page": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "next_page1": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "next_page2": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "base_page", + "next_page1", + "next_page2" + ] + }, + "LtStatus": { + "description": "Link-training status for a single lane", + "type": "object", + "properties": { + "frame_lock": { + "description": "Frame lock state", + "type": "boolean" + }, + "readout_state": { + "description": "Readout for frame lock state", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readout_training_state": { + "description": "Training state readout", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "readout_txstate": { + "description": "State machine readout for training arbiter", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "rx_trained": { + "description": "Local training finished", + "type": "boolean" + }, + "sig_det": { + "description": "Signal detect for PCS", + "type": "boolean" + }, + "training_failure": { + "description": "Link training failed", + "type": "boolean" + }, + "tx_training_data_en": { + "description": "TX control to send training pattern", + "type": "boolean" + } + }, + "required": [ + "frame_lock", + "readout_state", + "readout_training_state", + "readout_txstate", + "rx_trained", + "sig_det", + "training_failure", + "tx_training_data_en" + ] + }, + "MacAddr": { + "description": "An EUI-48 MAC address, used for layer-2 addressing.", + "type": "object", + "properties": { + "a": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 6, + "maxItems": 6 + } + }, + "required": [ + "a" + ] + }, + "ManagementMode": { + "description": "How a switch port is managed.\n\nThe free-side devices in QSFP ports are complex devices, whose operation usually involves coordinated steps through one or more state machines. For example, when bringing up an optical link, a signal from the peer link must be detected; then a signal recovered; equalizer gains set; etc. In `Automatic` mode, all these kinds of steps are managed autonomously by switch driver software. In `Manual` mode, none of these will occur -- a switch port will only change in response to explicit requests from the operator or Oxide control plane.", + "oneOf": [ + { + "description": "A port is managed manually, by either the Oxide control plane or an operator.", + "type": "string", + "enum": [ + "manual" + ] + }, + { + "description": "A port is managed automatically by the switch software.", + "type": "string", + "enum": [ + "automatic" + ] + } + ] + }, + "MediaInterfaceId": { + "oneOf": [ + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for multi-mode fiber media.\n\nSee SFF-8024 Table 4-6.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mmf" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for single-mode fiber.\n\nSee SFF-8024 Table 4-7.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "smf" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for passive copper cables.\n\nSee SFF-8024 Table 4-8.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "passive_copper" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for active cable assemblies.\n\nSee SFF-8024 Table 4-9.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "active_cable" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "description": "Media interface ID for BASE-T.\n\nSee SFF-8024 Table 4-10.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "base_t" + ] + } + }, + "required": [ + "id", + "type" + ] + } + ] + }, + "Monitors": { + "description": "Free-side device monitoring information.\n\nNote that all values are optional, as some specifications do not require that modules implement monitoring of those values.", + "type": "object", + "properties": { + "aux_monitors": { + "nullable": true, + "description": "Auxiliary monitoring values.\n\nThese are only available on CMIS-compatible transceivers, e.g., QSFP-DD.", + "allOf": [ + { + "$ref": "#/components/schemas/AuxMonitors" + } + ] + }, + "receiver_power": { + "nullable": true, + "description": "The measured input optical power (milliwatts);\n\nNote that due to a limitation in the SFF-8636 specification, it's possible for receiver power to be zero. See [`ReceiverPower`] for details.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiverPower" + } + }, + "supply_voltage": { + "nullable": true, + "description": "The measured input supply voltage (Volts).", + "type": "number", + "format": "float" + }, + "temperature": { + "nullable": true, + "description": "The measured cage temperature (degrees C);", + "type": "number", + "format": "float" + }, + "transmitter_bias_current": { + "nullable": true, + "description": "The output laser bias current (milliamps).", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "transmitter_power": { + "nullable": true, + "description": "The measured output optical power (milliwatts).", + "type": "array", + "items": { + "type": "number", + "format": "float" + } + } + } + }, + "MulticastGroupCreateExternalEntry": { + "description": "A multicast group configuration for POST requests for external (to the rack) groups.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "external_forwarding", + "group_ip", + "internal_forwarding" + ] + }, + "MulticastGroupCreateUnderlayEntry": { + "description": "A multicast group configuration for POST requests for internal (to the rack) groups.", + "type": "object", + "properties": { + "group_ip": { + "$ref": "#/components/schemas/AdminScopedIpv6" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "group_ip", + "members" + ] + }, + "MulticastGroupExternalResponse": { + "description": "Response structure for external multicast group operations. These groups handle IPv4 and non-admin IPv6 multicast via NAT targets.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "external_forwarding", + "external_group_id", + "group_ip", + "internal_forwarding" + ] + }, + "MulticastGroupMember": { + "description": "Represents a member of a multicast group.", + "type": "object", + "properties": { + "direction": { + "$ref": "#/components/schemas/Direction" + }, + "link_id": { + "$ref": "#/components/schemas/LinkId" + }, + "port_id": { + "$ref": "#/components/schemas/PortId" + } + }, + "required": [ + "direction", + "link_id", + "port_id" + ] + }, + "MulticastGroupResponse": { + "description": "Unified response type for operations that return mixed group types.", + "oneOf": [ + { + "description": "Response structure for underlay/internal multicast group operations. These groups handle admin-scoped IPv6 multicast with full replication.", + "type": "object", + "properties": { + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "$ref": "#/components/schemas/AdminScopedIpv6" + }, + "kind": { + "type": "string", + "enum": [ + "underlay" + ] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "nullable": true, + "type": "string" + }, + "underlay_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "external_group_id", + "group_ip", + "kind", + "members", + "underlay_group_id" + ] + }, + { + "description": "Response structure for external multicast group operations. These groups handle IPv4 and non-admin IPv6 multicast via NAT targets.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "type": "string", + "format": "ip" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "kind": { + "type": "string", + "enum": [ + "external" + ] + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "external_forwarding", + "external_group_id", + "group_ip", + "internal_forwarding", + "kind" + ] + } + ] + }, + "MulticastGroupResponseResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupResponse" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "MulticastGroupUnderlayResponse": { + "description": "Response structure for underlay/internal multicast group operations. These groups handle admin-scoped IPv6 multicast with full replication.", + "type": "object", + "properties": { + "external_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "group_ip": { + "$ref": "#/components/schemas/AdminScopedIpv6" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "nullable": true, + "type": "string" + }, + "underlay_group_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "external_group_id", + "group_ip", + "members", + "underlay_group_id" + ] + }, + "MulticastGroupUpdateExternalEntry": { + "description": "A multicast group update entry for PUT requests for external (to the rack) groups.", + "type": "object", + "properties": { + "external_forwarding": { + "$ref": "#/components/schemas/ExternalForwarding" + }, + "internal_forwarding": { + "$ref": "#/components/schemas/InternalForwarding" + }, + "sources": { + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/IpSrc" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "external_forwarding", + "internal_forwarding" + ] + }, + "MulticastGroupUpdateUnderlayEntry": { + "description": "Represents a multicast replication entry for PUT requests for internal (to the rack) groups.", + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MulticastGroupMember" + } + }, + "tag": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "members" + ] + }, + "NatTarget": { + "description": "represents an internal NAT target", + "type": "object", + "properties": { + "inner_mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "internal_ip": { + "type": "string", + "format": "ipv6" + }, + "vni": { + "$ref": "#/components/schemas/Vni" + } + }, + "required": [ + "inner_mac", + "internal_ip", + "vni" + ] + }, + "Oui": { + "description": "An Organization Unique Identifier.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "minItems": 3, + "maxItems": 3 + }, + "OutputStatus": { + "type": "string", + "enum": [ + "valid", + "invalid" + ] + }, + "PcsCounters": { + "description": "Per-port PCS counters", + "type": "object", + "properties": { + "bad_sync_headers": { + "description": "Count of bad sync headers", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "bip_errors_per_pcs_lane": { + "description": "Bit Inteleaved Parity errors (per lane)", + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "block_lock_loss": { + "description": "Count of block-lock loss detections", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "errored_blocks": { + "description": "Count of errored blocks", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "hi_ber": { + "description": "Count of high bit error rate events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "invalid_errors": { + "description": "Count of invalid error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "port": { + "description": "Port being tracked", + "type": "string" + }, + "sync_loss": { + "description": "Count of sync loss detections", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "unknown_errors": { + "description": "Count of unknown error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "valid_errors": { + "description": "Count of valid error events", + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "bad_sync_headers", + "bip_errors_per_pcs_lane", + "block_lock_loss", + "errored_blocks", + "hi_ber", + "invalid_errors", + "port", + "sync_loss", + "unknown_errors", + "valid_errors" + ] + }, + "Polarity": { + "type": "string", + "enum": [ + "Normal", + "Inverted" + ] + }, + "PortFec": { + "type": "string", + "enum": [ + "None", + "Firecode", + "RS" + ] + }, + "PortId": { + "example": "qsfp0", + "title": "PortId", + "description": "Physical switch port identifier", + "oneOf": [ + { + "title": "internal", + "type": "string", + "pattern": "(^[iI][nN][tT]0$)" + }, + { + "title": "rear", + "type": "string", + "pattern": "(^[rR][eE][aA][rR](([0-9])|([1-2][0-9])|(3[0-1]))$)" + }, + { + "title": "qsfp", + "type": "string", + "pattern": "(^[qQ][sS][fF][pP](([0-9])|([1-2][0-9])|(3[0-1]))$)" + } + ] + }, + "PortMedia": { + "type": "string", + "enum": [ + "Copper", + "Optical", + "CPU", + "None", + "Unknown" + ] + }, + "PortPrbsMode": { + "description": "Legal PRBS modes", + "type": "string", + "enum": [ + "Mode31", + "Mode23", + "Mode15", + "Mode13", + "Mode11", + "Mode9", + "Mode7", + "Mission" + ] + }, + "PortSettings": { + "description": "A port settings transaction object. When posted to the `/port-settings/{port_id}` API endpoint, these settings will be applied holistically, and to the extent possible atomically to a given port.", + "type": "object", + "properties": { + "links": { + "description": "The link settings to apply to the port on a per-link basis. Any links not in this map that are resident on the switch port will be removed. Any links that are in this map that are not resident on the switch port will be added. Any links that are resident on the switch port and in this map, and are different, will be modified. Links are indexed by spatial index within the port.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/LinkSettings" + } + } + }, + "required": [ + "links" + ] + }, + "PortSpeed": { + "description": "Speeds with which a single port may be configured", + "type": "string", + "enum": [ + "Speed0G", + "Speed1G", + "Speed10G", + "Speed25G", + "Speed40G", + "Speed50G", + "Speed100G", + "Speed200G", + "Speed400G" + ] + }, + "PowerMode": { + "description": "The power mode of a module.", + "type": "object", + "properties": { + "software_override": { + "nullable": true, + "description": "Whether the module is configured for software override of power control.\n\nIf the module is in `PowerState::Off`, this can't be determined, and `None` is returned.", + "type": "boolean" + }, + "state": { + "description": "The actual power state.", + "allOf": [ + { + "$ref": "#/components/schemas/PowerState" + } + ] + } + }, + "required": [ + "state" + ] + }, + "PowerState": { + "description": "An allowed power state for the module.", + "oneOf": [ + { + "description": "A module is entirely powered off, using the EFuse.", + "type": "string", + "enum": [ + "off" + ] + }, + { + "description": "Power is enabled to the module, but module remains in low-power mode.\n\nIn this state, modules will not establish a link or transmit traffic, but they may be managed and queried for information through their memory maps.", + "type": "string", + "enum": [ + "low" + ] + }, + { + "description": "The module is in high-power mode.\n\nNote that additional configuration may be required to correctly configure the module, such as described in SFF-8636 rev 2.10a table 6-10, and that the _host side_ is responsible for ensuring that the relevant configuration is applied.", + "type": "string", + "enum": [ + "high" + ] + } + ] + }, + "RMonCounters": { + "description": "High level subset of the RMon counters maintained by the Tofino ASIC", + "type": "object", + "properties": { + "crc_error_stomped": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "fragments_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frame_too_long": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_dropped_buffer_full": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_with_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_with_any_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx_in_good_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_total": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_without_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "port": { + "type": "string" + } + }, + "required": [ + "crc_error_stomped", + "fragments_rx", + "frame_too_long", + "frames_dropped_buffer_full", + "frames_rx_all", + "frames_rx_ok", + "frames_tx_all", + "frames_tx_ok", + "frames_tx_with_error", + "frames_with_any_error", + "octets_rx", + "octets_rx_in_good_frames", + "octets_tx_total", + "octets_tx_without_error", + "port" + ] + }, + "RMonCountersAll": { + "description": "All of the RMon counters maintained by the Tofino ASIC", + "type": "object", + "properties": { + "crc_error_stomped": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "fragments_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frame_too_long": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_dropped_buffer_full": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_indersized": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_1024_1518": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_128_255": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_1519_2047": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_2048_4095": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_256_511": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_4096_8191": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_512_1023": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_65_127": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_8192_9215": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_9216": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_eq_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_length_lt_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_oftype_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_oversized": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_any_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_broadcast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_fcs_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_length_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_multicast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_rx_with_unicast_addresses": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_truncated": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_all": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_broadcast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_1024_1518": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_128_255": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_1519_2047": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_2048_4095": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_256_511": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_4096_8191": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_512_1023": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_65_127": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_8192_9215": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_9216": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_eq_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_length_lt_64": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_multicast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_ok": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_pri_pause": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_unicast": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_vlan": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "frames_tx_with_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "jabber_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_rx_in_good_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_total": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "octets_tx_without_error": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "port": { + "type": "string" + }, + "pri0_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri0_framex_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri1_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri1_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri2_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri2_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri3_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri3_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri4_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri4_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri5_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri5_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri6_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri6_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri7_frames_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "pri7_frames_tx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "priority_pause_frames": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri0_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri1_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri2_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri3_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri4_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri5_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri6_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_pri7_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_standard_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "rx_vlan_frames_good": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri0_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri1_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri2_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri3_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri4_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri5_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri6_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "tx_pri7_pause_1us_count": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "crc_error_stomped", + "fragments_rx", + "frame_too_long", + "frames_dropped_buffer_full", + "frames_rx_all", + "frames_rx_indersized", + "frames_rx_length_1024_1518", + "frames_rx_length_128_255", + "frames_rx_length_1519_2047", + "frames_rx_length_2048_4095", + "frames_rx_length_256_511", + "frames_rx_length_4096_8191", + "frames_rx_length_512_1023", + "frames_rx_length_65_127", + "frames_rx_length_8192_9215", + "frames_rx_length_9216", + "frames_rx_length_eq_64", + "frames_rx_length_lt_64", + "frames_rx_oftype_pause", + "frames_rx_ok", + "frames_rx_oversized", + "frames_rx_with_any_error", + "frames_rx_with_broadcast_addresses", + "frames_rx_with_fcs_error", + "frames_rx_with_length_error", + "frames_rx_with_multicast_addresses", + "frames_rx_with_unicast_addresses", + "frames_truncated", + "frames_tx_all", + "frames_tx_broadcast", + "frames_tx_length_1024_1518", + "frames_tx_length_128_255", + "frames_tx_length_1519_2047", + "frames_tx_length_2048_4095", + "frames_tx_length_256_511", + "frames_tx_length_4096_8191", + "frames_tx_length_512_1023", + "frames_tx_length_65_127", + "frames_tx_length_8192_9215", + "frames_tx_length_9216", + "frames_tx_length_eq_64", + "frames_tx_length_lt_64", + "frames_tx_multicast", + "frames_tx_ok", + "frames_tx_pause", + "frames_tx_pri_pause", + "frames_tx_unicast", + "frames_tx_vlan", + "frames_tx_with_error", + "jabber_rx", + "octets_rx", + "octets_rx_in_good_frames", + "octets_tx_total", + "octets_tx_without_error", + "port", + "pri0_frames_rx", + "pri0_framex_tx", + "pri1_frames_rx", + "pri1_frames_tx", + "pri2_frames_rx", + "pri2_frames_tx", + "pri3_frames_rx", + "pri3_frames_tx", + "pri4_frames_rx", + "pri4_frames_tx", + "pri5_frames_rx", + "pri5_frames_tx", + "pri6_frames_rx", + "pri6_frames_tx", + "pri7_frames_rx", + "pri7_frames_tx", + "priority_pause_frames", + "rx_pri0_pause_1us_count", + "rx_pri1_pause_1us_count", + "rx_pri2_pause_1us_count", + "rx_pri3_pause_1us_count", + "rx_pri4_pause_1us_count", + "rx_pri5_pause_1us_count", + "rx_pri6_pause_1us_count", + "rx_pri7_pause_1us_count", + "rx_standard_pause_1us_count", + "rx_vlan_frames_good", + "tx_pri0_pause_1us_count", + "tx_pri1_pause_1us_count", + "tx_pri2_pause_1us_count", + "tx_pri3_pause_1us_count", + "tx_pri4_pause_1us_count", + "tx_pri5_pause_1us_count", + "tx_pri6_pause_1us_count", + "tx_pri7_pause_1us_count" + ] + }, + "ReceiverPower": { + "description": "Measured receiver optical power.\n\nThe SFF specifications allow for devices to monitor input optical power in several ways. It may either be an average power, over some unspecified time, or a peak-to-peak power. The latter is often abbreviated OMA, for Optical Modulation Amplitude. Again the time interval for peak-to-peak measurments are not specified.\n\nDetails -------\n\nThe SFF-8636 specification has an unfortunate limitation. There is no separate advertisement for whether a module supports measurements of receiver power. Instead, the _kind_ of measurement is advertised. The _same bit value_ could mean that either a peak-to-peak measurement is supported, or the measurements are not supported at all. Thus values of `PeakToPeak(0.0)` may mean that power measurements are not supported.", + "oneOf": [ + { + "description": "The measurement is represents average optical power, in mW.", + "type": "object", + "properties": { + "average": { + "type": "number", + "format": "float" + } + }, + "required": [ + "average" + ], + "additionalProperties": false + }, + { + "description": "The measurement represents a peak-to-peak, in mW.", + "type": "object", + "properties": { + "peak_to_peak": { + "type": "number", + "format": "float" + } + }, + "required": [ + "peak_to_peak" + ], + "additionalProperties": false + } + ] + }, + "RxSigInfo": { + "description": "Per-lane Rx signal information", + "type": "object", + "properties": { + "phy_ready": { + "description": "CDR lock achieved", + "type": "boolean" + }, + "ppm": { + "description": "Apparent PPM difference between local and remote", + "type": "integer", + "format": "int32" + }, + "sig_detect": { + "description": "Rx signal detected", + "type": "boolean" + } + }, + "required": [ + "phy_ready", + "ppm", + "sig_detect" + ] + }, + "SerdesEye": { + "description": "Eye height(s) for a single lane in mv", + "oneOf": [ + { + "type": "object", + "properties": { + "Nrz": { + "type": "number", + "format": "float" + } + }, + "required": [ + "Nrz" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Pam4": { + "type": "object", + "properties": { + "eye1": { + "type": "number", + "format": "float" + }, + "eye2": { + "type": "number", + "format": "float" + }, + "eye3": { + "type": "number", + "format": "float" + } + }, + "required": [ + "eye1", + "eye2", + "eye3" + ] + } + }, + "required": [ + "Pam4" + ], + "additionalProperties": false + } + ] + }, + "Sff8636Datapath": { + "description": "The datapath of an SFF-8636 module.\n\nThis describes the state of a single lane in an SFF module. It includes information about input and output signals, faults, and controls.", + "type": "object", + "properties": { + "rx_cdr_enabled": { + "description": "Media-side transmit Clock and Data Recovery (CDR) enable status.\n\nCDR is the process by which the module enages an internal retimer function, through which the module attempts to recovery a clock signal directly from the input bitstream.", + "type": "boolean" + }, + "rx_lol": { + "description": "Media-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the media-side signal (usually optical).", + "type": "boolean" + }, + "rx_los": { + "description": "Media-side loss of signal flag.\n\nThis is true if there is no detected input signal from the media-side (usually optical).", + "type": "boolean" + }, + "tx_adaptive_eq_fault": { + "description": "Flag indicating a fault in adaptive transmit equalization.", + "type": "boolean" + }, + "tx_cdr_enabled": { + "description": "Host-side transmit Clock and Data Recovery (CDR) enable status.\n\nCDR is the process by which the module enages an internal retimer function, through which the module attempts to recovery a clock signal directly from the input bitstream.", + "type": "boolean" + }, + "tx_enabled": { + "description": "Software control of output transmitter.", + "type": "boolean" + }, + "tx_fault": { + "description": "Flag indicating a fault in the transmitter and/or laser.", + "type": "boolean" + }, + "tx_lol": { + "description": "Host-side loss of lock flag.\n\nThis is true if the module is not able to extract a clock signal from the host-side electrical signal.", + "type": "boolean" + }, + "tx_los": { + "description": "Host-side loss of signal flag.\n\nThis is true if there is no detected electrical signal from the host-side serdes.", + "type": "boolean" + } + }, + "required": [ + "rx_cdr_enabled", + "rx_lol", + "rx_los", + "tx_adaptive_eq_fault", + "tx_cdr_enabled", + "tx_enabled", + "tx_fault", + "tx_lol", + "tx_los" + ] + }, + "SffComplianceCode": { + "description": "The compliance code for an SFF-8636 module.\n\nThese values record a specification compliance code, from SFF-8636 Table 6-17, or an extended specification compliance code, from SFF-8024 Table 4-4.", + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "description": "Extended electrical or optical interface codes", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "extended" + ] + } + }, + "required": [ + "code", + "type" + ] + }, + { + "type": "object", + "properties": { + "code": { + "description": "The Ethernet specification implemented by a module.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ethernet" + ] + } + }, + "required": [ + "code", + "type" + ] + } + ] + }, + "SidecarCableLeg": { + "description": "The leg of the Sidecar-internal cable.\n\nThis describes the leg on the cabling that connects the pins on the Tofino ASIC to the Sidecar chassis connector.", + "type": "string", + "enum": [ + "A", + "C" + ] + }, + "SidecarConnector": { + "description": "The Sidecar chassis connector mating the backplane and internal cabling.\n\nThis describes the \"group\" of backplane links that all terminate in one connector on the Sidecar itself. This is the connection point between a cable on the backplane itself and the Sidecar chassis.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "SwitchIdentifiers": { + "description": "Identifiers for a switch.", + "type": "object", + "properties": { + "asic_backend": { + "description": "Asic backend (compiler target) responsible for these identifiers.", + "type": "string" + }, + "fab": { + "nullable": true, + "description": "Fabrication plant identifier.", + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "lot": { + "nullable": true, + "description": "Lot identifier.", + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "model": { + "description": "The model number of the switch being managed.", + "type": "string" + }, + "revision": { + "description": "The revision number of the switch being managed.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial": { + "description": "The serial number of the switch being managed.", + "type": "string" + }, + "sidecar_id": { + "description": "Unique identifier for the chip.", + "type": "string", + "format": "uuid" + }, + "slot": { + "description": "The slot number of the switch being managed.\n\nMGS uses u16 for this internally.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "wafer": { + "nullable": true, + "description": "Wafer number within the lot.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "wafer_loc": { + "nullable": true, + "description": "The wafer location as (x, y) coordinates on the wafer, represented as an array due to the lack of tuple support in OpenAPI.", + "type": "array", + "items": { + "type": "integer", + "format": "int16" + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "asic_backend", + "model", + "revision", + "serial", + "sidecar_id", + "slot" + ] + }, + "SwitchPort": { + "description": "A physical port on the Sidecar switch.", + "type": "object", + "properties": { + "management_mode": { + "description": "How the QSFP device is managed.\n\nSee `ManagementMode` for details.", + "allOf": [ + { + "$ref": "#/components/schemas/ManagementMode" + } + ] + }, + "port_id": { + "description": "The identifier for the switch port.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + }, + "transceiver": { + "nullable": true, + "description": "Details about a transceiver module inserted into the switch port.\n\nIf there is no transceiver at all, this will be `None`.", + "allOf": [ + { + "$ref": "#/components/schemas/Transceiver" + } + ] + } + }, + "required": [ + "port_id" + ] + }, + "Table": { + "description": "Represents the contents of a P4 table", + "type": "object", + "properties": { + "entries": { + "description": "There will be an entry for each populated slot in the table", + "type": "array", + "items": { + "$ref": "#/components/schemas/TableEntry" + } + }, + "name": { + "description": "A user-friendly name for the table", + "type": "string" + }, + "size": { + "description": "The maximum number of entries the table can hold", + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "entries", + "name", + "size" + ] + }, + "TableCounterEntry": { + "type": "object", + "properties": { + "data": { + "description": "Counter values", + "allOf": [ + { + "$ref": "#/components/schemas/CounterData" + } + ] + }, + "keys": { + "description": "Names and values of each of the key fields.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "data", + "keys" + ] + }, + "TableEntry": { + "description": "Each entry in a P4 table is addressed by matching against a set of key values. If an entry is found, an action is taken with an action-specific set of arguments.\n\nNote: each entry will have the same key fields and each instance of any given action will have the same argument names, so a vector of TableEntry structs will contain a signficant amount of redundant data. We could consider tightening this up by including a schema of sorts in the \"struct Table\".", + "type": "object", + "properties": { + "action": { + "description": "Name of the action to take on a match", + "type": "string" + }, + "action_args": { + "description": "Names and values for the arguments to the action implementation.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "keys": { + "description": "Names and values of each of the key fields.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "action", + "action_args", + "keys" + ] + }, + "TfportData": { + "description": "The per-link data consumed by tfportd", + "type": "object", + "properties": { + "asic_id": { + "description": "The lower-level ASIC ID used to refer to this object in the switch driver software.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "ipv6_enabled": { + "description": "Is ipv6 enabled for this link", + "type": "boolean" + }, + "link_id": { + "description": "The link ID for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/LinkId" + } + ] + }, + "link_local": { + "nullable": true, + "description": "The IPv6 link-local address of the link, if it exists.", + "type": "string", + "format": "ipv6" + }, + "mac": { + "description": "The MAC address for the link.", + "allOf": [ + { + "$ref": "#/components/schemas/MacAddr" + } + ] + }, + "port_id": { + "description": "The switch port ID for this link.", + "allOf": [ + { + "$ref": "#/components/schemas/PortId" + } + ] + } + }, + "required": [ + "asic_id", + "ipv6_enabled", + "link_id", + "mac", + "port_id" + ] + }, + "Transceiver": { + "description": "The state of a transceiver in a QSFP switch port.", + "oneOf": [ + { + "description": "The transceiver could not be managed due to a power fault.", + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/FaultReason" + }, + "state": { + "type": "string", + "enum": [ + "faulted" + ] + } + }, + "required": [ + "info", + "state" + ] + }, + { + "description": "A transceiver was present, but unsupported and automatically disabled.", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "unsupported" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "A transceiver is present and supported.", + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/TransceiverInfo" + }, + "state": { + "type": "string", + "enum": [ + "supported" + ] + } + }, + "required": [ + "info", + "state" + ] + } + ] + }, + "TransceiverInfo": { + "description": "Information about a QSFP transceiver.\n\nThis stores the most relevant information about a transceiver module, such as vendor info or power. Each field may be missing, indicating it could not be determined.", + "type": "object", + "properties": { + "electrical_mode": { + "description": "The electrical mode of the transceiver.\n\nSee [`ElectricalMode`] for details.", + "allOf": [ + { + "$ref": "#/components/schemas/ElectricalMode" + } + ] + }, + "in_reset": { + "nullable": true, + "description": "True if the module is currently in reset.", + "type": "boolean" + }, + "interrupt_pending": { + "nullable": true, + "description": "True if there is a pending interrupt on the module.", + "type": "boolean" + }, + "power_mode": { + "nullable": true, + "description": "The power mode of the transceiver.", + "allOf": [ + { + "$ref": "#/components/schemas/PowerMode" + } + ] + }, + "vendor_info": { + "nullable": true, + "description": "Vendor and part identifying information.\n\nThe information will not be populated if it could not be read.", + "allOf": [ + { + "$ref": "#/components/schemas/VendorInfo" + } + ] + } + }, + "required": [ + "electrical_mode" + ] + }, + "TxEq": { + "description": "Parameters to adjust the transceiver equalization settings for a link on a switch. These parameters match those available on a tofino-based sidecar, and may need to be adapted when we move to a new switch ASIC.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "type": "integer", + "format": "int32" + } + } + }, + "TxEqSwHw": { + "description": "This represents the software-determined equalization value initially assigned to the transceiver and the value actually being used by the hardware. The values may differ on transceivers that are capable of tuning their own settings at run time.", + "type": "object", + "properties": { + "hw": { + "$ref": "#/components/schemas/TxEq" + }, + "sw": { + "$ref": "#/components/schemas/TxEq" + } + }, + "required": [ + "hw", + "sw" + ] + }, + "Vendor": { + "description": "Vendor-specific information about a transceiver module.", + "type": "object", + "properties": { + "date": { + "nullable": true, + "type": "string" + }, + "name": { + "type": "string" + }, + "oui": { + "$ref": "#/components/schemas/Oui" + }, + "part": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "serial": { + "type": "string" + } + }, + "required": [ + "name", + "oui", + "part", + "revision", + "serial" + ] + }, + "VendorInfo": { + "description": "The vendor information for a transceiver module.", + "type": "object", + "properties": { + "identifier": { + "description": "The SFF-8024 identifier.", + "type": "string" + }, + "vendor": { + "description": "The vendor information.", + "allOf": [ + { + "$ref": "#/components/schemas/Vendor" + } + ] + } + }, + "required": [ + "identifier", + "vendor" + ] + }, + "Vni": { + "description": "A Geneve Virtual Network Identifier.\n\nA Geneve VNI is a 24-bit value used to identify virtual networks encapsulated using the Generic Network Virtualization Encapsulation (Geneve) protocol (RFC 8926).", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "ipv4ResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "type": "string", + "format": "ipv4" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ipv6ResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "type": "string", + "format": "ipv6" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + } + }, + "responses": { + "Error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } +} diff --git a/openapi/dpd/dpd-latest.json b/openapi/dpd/dpd-latest.json index a8d9fb4..283a93c 120000 --- a/openapi/dpd/dpd-latest.json +++ b/openapi/dpd/dpd-latest.json @@ -1 +1 @@ -dpd-2.0.0-74a45c.json \ No newline at end of file +dpd-3.0.0-b02a88.json \ No newline at end of file diff --git a/swadm/src/attached.rs b/swadm/src/attached.rs new file mode 100644 index 0000000..5aa27fb --- /dev/null +++ b/swadm/src/attached.rs @@ -0,0 +1,161 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::convert::TryFrom; +use std::io::{Write, stdout}; +use std::net::Ipv6Addr; + +use anyhow::Context; +use clap::Subcommand; +use colored::*; +use futures::stream::TryStreamExt; +use oxnet::IpNet; +use tabwriter::TabWriter; + +use common::network::{MacAddr, Vni}; +use dpd_client::Client; +use dpd_client::types; + +#[derive(Debug, Subcommand)] +/// manage attached subnet mappings +pub enum AttachedSubnet { + /// list all attached subnet mappings + #[clap(visible_alias = "ls")] + List { + /// list v4 subnets + #[clap(short = '4')] + v4: bool, + /// list v6 subnets + #[clap(short = '6')] + v6: bool, + }, + /// get a single attached subnet mapping + Get { + /// attached subnet + #[clap(short = 'e')] + attsub: IpNet, + }, + /// add a new attached subnet mapping + Add { + /// attached subnet + #[clap(short = 'e')] + attsub: IpNet, + /// internal IP address + #[clap(short = 'i')] + internal: Ipv6Addr, + /// inner MAC address + #[clap(short = 'm')] + inner: MacAddr, + /// Geneve VNI + #[clap(short = 'v')] + vni: Vni, + }, + /// delete a single attached subnet mapping + Del { + /// attached subnet + attsub: IpNet, + }, +} + +async fn attsub_list( + client: &Client, + mut v4: bool, + mut v6: bool, +) -> anyhow::Result<()> { + if !v4 && !v6 { + v4 = true; + v6 = true; + } + let mappings: Vec = client + .attached_subnet_list_stream(None) + .try_collect() + .await + .context("failed to list attached subnets")?; + + let mut tw = TabWriter::new(stdout()); + writeln!( + &mut tw, + "{}\t{}\t{}\t{}", + "Attached Subnet".underline(), + "Internal IP".underline(), + "Inner MAC".underline(), + "VNI".underline() + )?; + + for m in mappings.iter().filter(|m| match m.subnet { + IpNet::V4(_) => v4, + IpNet::V6(_) => v6, + }) { + writeln!( + &mut tw, + "{}\t{}\t{}\t{}", + m.subnet, + m.tgt.internal_ip, + MacAddr::from(m.tgt.inner_mac.clone()), + m.tgt.vni.0, + )?; + } + tw.flush()?; + + Ok(()) +} + +async fn attsub_get(client: &Client, attsub: IpNet) -> anyhow::Result<()> { + let target = client + .attached_subnet_get(&attsub) + .await + .map(|r| { + common::network::InstanceTarget::try_from(r.into_inner()) + .expect("Invalid internal target from server") + }) + .context("failed to get IPv4 NAT mapping")?; + println!("target: {target}"); + Ok(()) +} + +async fn attsub_add( + client: &Client, + attsub: IpNet, + internal_ip: Ipv6Addr, + inner_mac: MacAddr, + vni: Vni, +) -> anyhow::Result<()> { + let tgt = types::InstanceTarget { + internal_ip, + inner_mac: inner_mac.into(), + vni: types::Vni::from(vni), + }; + client + .attached_subnet_create(&attsub, &tgt) + .await + .context("failed to create attached subnet mapping") + .map(|_| ()) +} + +async fn attsub_del(client: &Client, attsub: IpNet) -> anyhow::Result<()> { + client + .attached_subnet_delete(&attsub) + .await + .context("failed to delete attached subnet mapping") + .map(|_| ()) +} + +pub async fn attsub_cmd( + client: &Client, + e: AttachedSubnet, +) -> anyhow::Result<()> { + match e { + AttachedSubnet::List { v4, v6 } => attsub_list(client, v4, v6).await, + AttachedSubnet::Get { attsub } => attsub_get(client, attsub).await, + AttachedSubnet::Add { + attsub, + internal, + inner, + vni, + } => attsub_add(client, attsub, internal, inner, vni).await, + AttachedSubnet::Del { attsub } => attsub_del(client, attsub).await, + } +} diff --git a/swadm/src/main.rs b/swadm/src/main.rs index 957fa23..a9e3b91 100644 --- a/swadm/src/main.rs +++ b/swadm/src/main.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryFrom; use std::io; @@ -19,6 +19,7 @@ use dpd_client::types; mod addr; mod arp; +mod attached; mod compliance; mod counters; mod link; @@ -64,6 +65,11 @@ enum Commands { #[command(subcommand)] cmd: nat::Nat, }, + #[clap(visible_alias = "attsub")] + AttachedSubnet { + #[command(subcommand)] + cmd: attached::AttachedSubnet, + }, Counters { #[command(subcommand)] cmd: counters::P4Counters, @@ -216,6 +222,9 @@ async fn main_impl() -> anyhow::Result<()> { Commands::Arp { cmd: a } => arp::arp_cmd(&client, a).await, Commands::Route { cmd: r } => route::route_cmd(&client, r).await, Commands::Addr { cmd: p } => addr::addr_cmd(&client, p).await, + Commands::AttachedSubnet { cmd: p } => { + attached::attsub_cmd(&client, p).await + } Commands::Nat { cmd: p } => nat::nat_cmd(&client, p).await, Commands::Counters { cmd: c } => counters::ctrs_cmd(&client, c).await, Commands::SwitchPort { cmd: p } => { diff --git a/swadm/src/nat.rs b/swadm/src/nat.rs index de38faa..574caee 100644 --- a/swadm/src/nat.rs +++ b/swadm/src/nat.rs @@ -2,7 +2,7 @@ // 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/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryFrom; use std::io::{Write, stdout}; @@ -14,8 +14,7 @@ use colored::*; use futures::stream::TryStreamExt; use tabwriter::TabWriter; -use common::nat; -use common::network::MacAddr; +use common::network::{MacAddr, Vni}; use dpd_client::Client; use dpd_client::types; @@ -57,7 +56,7 @@ pub enum Nat { inner: MacAddr, /// Geneve VNI #[clap(short = 'v')] - vni: nat::Vni, + vni: Vni, }, /// delete a single NAT reservation Del { @@ -155,7 +154,7 @@ async fn nat_get( .nat_ipv4_get(&ipv4, port) .await .map(|r| { - nat::NatTarget::try_from(r.into_inner()) + common::network::NatTarget::try_from(r.into_inner()) .expect("Invalid NAT target from server") }) .context("failed to get IPv4 NAT mapping")?; @@ -166,7 +165,7 @@ async fn nat_get( .nat_ipv6_get(&ipv6, port) .await .map(|r| { - nat::NatTarget::try_from(r.into_inner()) + common::network::NatTarget::try_from(r.into_inner()) .expect("Invalid NAT target from server") }) .context("failed to get IPv6 NAT mapping")?; @@ -183,7 +182,7 @@ async fn nat_add( high_port: u16, internal_ip: Ipv6Addr, inner_mac: MacAddr, - vni: nat::Vni, + vni: Vni, ) -> anyhow::Result<()> { let tgt = types::NatTarget { internal_ip,