Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c0be29b
[review] address PR review comments: UnderlayMulticastIpv6 newtype, V…
zeeshanlakhani Mar 23, 2026
2a7568c
[review] UnicastAddr types, Vni hook-in, remove underlay_nexthops + m…
zeeshanlakhani Mar 31, 2026
65c4619
[review] error type cleanup variant
zeeshanlakhani Apr 2, 2026
6234a9d
[doc] fix
zeeshanlakhani Apr 7, 2026
f7b738a
merge main
zeeshanlakhani Apr 9, 2026
babdda4
merge main / update opte
zeeshanlakhani Apr 16, 2026
526b5ca
bump oxide-tokio-rt to 0.1.4, tokio to 1.52.1
zeeshanlakhani Apr 18, 2026
e49df46
[deps] bump opte to bae0440c (API 40)
zeeshanlakhani Apr 21, 2026
4b2eed7
merge main
zeeshanlakhani May 6, 2026
d1a07f6
merge main
zeeshanlakhani May 20, 2026
c11b796
[merge] merge main again (cleanup, lock maint, happy maps)
zeeshanlakhani May 20, 2026
49584d7
merge main
zeeshanlakhani May 22, 2026
03edf70
[merge] main into zl/mrib
zeeshanlakhani May 27, 2026
93fc895
[deps] revert opte to 3d1fe6ad to match main
zeeshanlakhani May 27, 2026
f2c390c
merge main
zeeshanlakhani Jun 2, 2026
0613da6
[merge] merge main and API update(s)
zeeshanlakhani Jun 9, 2026
6180e7e
[mg-api-types] keep v11 multicast types omicron-free
zeeshanlakhani Jun 9, 2026
b42a019
[merge] Merge main: renumber MULTICAST_SUPPORT to v12 over PREFIX_TO_…
zeeshanlakhani Jun 14, 2026
d2bf04d
[merge] merge main
zeeshanlakhani Jul 13, 2026
22503d6
[rdb] rework RPF cache rebuilds around a bounded worker
zeeshanlakhani Jul 14, 2026
b6b6c63
[merge] merge main
zeeshanlakhani Jul 28, 2026
2466950
[merge] merge main
zeeshanlakhani Aug 4, 2026
486debe
[api-types] align multicast validation with dendrite/omicron policy
zeeshanlakhani Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dropshot-api-manager = "0.7.2"
dropshot-api-manager-types = "0.7.2"
expectorate = "1.2.0"
schemars = { version = "0.8.22", features = [ "uuid1", "chrono" ] }
tokio = { version = "1.49", features = ["full"] }
tokio = { version = "1.52.1", features = ["full"] }
serde_repr = "0.1"
anyhow = "1.0.104"
port-file = "0.1.0"
Expand Down Expand Up @@ -132,6 +132,7 @@ oximeter = { git = "https://github.com/oxidecomputer/omicron", branch = "main"}
oximeter-producer = { git = "https://github.com/oxidecomputer/omicron", branch = "main"}
oxnet = { version = "0.1.6", default-features = false, features = ["schemars", "serde"] }
omicron-common = { git = "https://github.com/oxidecomputer/omicron", branch = "main"}
poptrie = { git = "https://github.com/oxidecomputer/poptrie", branch = "main" }
gateway-client = { git = "https://github.com/oxidecomputer/omicron", branch = "main" }
uuid = { version = "1.21", features = ["serde", "v4"] }
smf = { git = "https://github.com/illumos/smf-rs", branch = "main" }
Expand Down
6 changes: 6 additions & 0 deletions client-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@
name = "client-common"
version = "0.1.0"
edition = "2024"

[dependencies]
oxnet.workspace = true

[dev-dependencies]
omicron-common.workspace = true
195 changes: 195 additions & 0 deletions client-common/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// 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

//! Multicast addressing constants shared across the routing suite.
//!
//! These mirror the canonical definitions in `omicron_common::address`.
//! They are duplicated here so the client and API-types crates consumed by
//! Omicron remain free of an Omicron dependency, which would otherwise form a
//! dependency cycle. These constants must be reachable at compile time from the
//! Omicron-free API-types crate because its newtypes validate addresses at
//! deserialization via `#[serde(try_from)]`.
//!
//! References: [RFC 4291] (IPv6 addressing), [RFC 4607] (SSM),
//! [RFC 5771] (IPv4 multicast), [RFC 7346] (IPv6 multicast scopes).
//!
//! [RFC 4291]: https://www.rfc-editor.org/rfc/rfc4291
//! [RFC 4607]: https://www.rfc-editor.org/rfc/rfc4607
//! [RFC 5771]: https://www.rfc-editor.org/rfc/rfc5771
//! [RFC 7346]: https://www.rfc-editor.org/rfc/rfc7346

use oxnet::{Ipv4Net, Ipv6Net};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

// TODO: Consolidate these constants and the `omicron_common::address`
// originals into `oxnet`, the cycle-free leaf crate that maghemite, dendrite,
// and omicron already share, so the duplication can be removed.

/// IPv4 Source-Specific Multicast (SSM) subnet (232.0.0.0/8) per RFC 4607 §1.
pub const IPV4_SSM_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(232, 0, 0, 0), 8);

/// Reserved IPv4 SSM subnet (232.0.0.0/24).
///
/// RFC 4607 §4.3 reserves 232.0.0.0 (must not be assigned to any
/// application) and notes that IANA holds 232.0.0.1 through 232.0.0.255
/// in reserve, so the entire first /24 is excluded from allocation.
pub const IPV4_SSM_RESERVED_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(232, 0, 0, 0), 24);

const fn ipv6_ssm_subnet(scope: u16) -> Ipv6Net {
Ipv6Net::new_unchecked(
Ipv6Addr::new(0xff30 | scope, 0, 0, 0, 0, 0, 0, 0),
32,
)
}

/// IPv6 Source-Specific Multicast (SSM) subnets, one per scope field value.
///
/// RFC 4607 §1 specifies "ff3x::/32 for each scope x", meaning one /32
/// block per scope (ff30::/32, ff31::/32, ..., ff3f::/32).
///
/// These blocks cannot be represented by one CIDR: the scope nibble precedes
/// the 16 zero bits that complete each /32. In particular, ff3e:1:: is outside
/// ff3e::/32 even though it is inside the broader ff30::/12 prefix.
pub const IPV6_SSM_SUBNETS: [Ipv6Net; 16] = [
ipv6_ssm_subnet(0x0),
ipv6_ssm_subnet(0x1),
ipv6_ssm_subnet(0x2),
ipv6_ssm_subnet(0x3),
ipv6_ssm_subnet(0x4),
ipv6_ssm_subnet(0x5),
ipv6_ssm_subnet(0x6),
ipv6_ssm_subnet(0x7),
ipv6_ssm_subnet(0x8),
ipv6_ssm_subnet(0x9),
ipv6_ssm_subnet(0xa),
ipv6_ssm_subnet(0xb),
ipv6_ssm_subnet(0xc),
ipv6_ssm_subnet(0xd),
ipv6_ssm_subnet(0xe),
ipv6_ssm_subnet(0xf),
];

/// Check if an IP is in the SSM (Source-Specific Multicast) range.
///
/// SSM ranges per RFC 4607 §1:
/// - IPv4: 232.0.0.0/8
/// - IPv6: ff3x::/32 (all SSM scopes)
///
/// The IPv6 check matches the exact per-scope /32 blocks, not ff30::/12.
/// A /12 match would also classify RFC 3306 unicast-prefix-based addresses
/// with a nonzero network prefix as SSM.
pub fn is_ssm_address(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(addr) => IPV4_SSM_SUBNET.contains(addr),
IpAddr::V6(addr) => {
IPV6_SSM_SUBNETS.iter().any(|subnet| subnet.contains(addr))
}
}
}

/// IPv4 multicast address range (224.0.0.0/4) per RFC 5771.
pub const IPV4_MULTICAST_RANGE: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(224, 0, 0, 0), 4);

/// IPv4 link-local multicast subnet (224.0.0.0/24) per RFC 5771 §4.
///
/// Reserved for local network control protocols and not routed beyond the
/// local link.
pub const IPV4_LINK_LOCAL_MULTICAST_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(224, 0, 0, 0), 24);

/// IPv6 multicast address range (ff00::/8) per RFC 4291.
pub const IPV6_MULTICAST_RANGE: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0), 8);

/// IPv6 multicast prefix (ff00::/8) value for scope checking per RFC 4291 §2.7.
pub const IPV6_MULTICAST_PREFIX: u16 = 0xff00;

/// Admin-local IPv6 multicast prefix (ff04::/16) as a u16 for address
/// construction and normalization of underlay multicast addresses.
///
/// See RFC 4291 §2.7 and RFC 7346 for the multicast address format and scope
/// definitions.
pub const IPV6_ADMIN_SCOPED_MULTICAST_PREFIX: u16 = 0xff04;

/// Fixed underlay admin-local IPv6 multicast subnet (ff04::/64).
///
/// Admin-local scope (4) is the smallest scope that must be administratively
/// configured per RFC 7346. The Oxide rack maps overlay multicast groups 1:1
/// into this /64.
pub const UNDERLAY_MULTICAST_SUBNET: Ipv6Net = Ipv6Net::new_unchecked(
Ipv6Addr::new(IPV6_ADMIN_SCOPED_MULTICAST_PREFIX, 0, 0, 0, 0, 0, 0, 0),
64,
);

/// IPv6 interface-local multicast subnet (ff01::/16) per RFC 4291 §2.7.
///
/// Not routable.
pub const IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0), 16);

/// IPv6 link-local multicast subnet (ff02::/16) per RFC 4291 §2.7.
///
/// Not routable beyond the local link.
pub const IPV6_LINK_LOCAL_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16);

/// IPv6 reserved-scope multicast subnet (ff00::/16) per RFC 4291 §2.7.
///
/// Scope 0 is reserved. Packets with this scope must not be originated and
/// must be silently dropped if received.
pub const IPV6_RESERVED_SCOPE_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0), 16);

#[cfg(test)]
mod tests {
use omicron_common::address as canonical;

use super::*;

/// Assert each local constant equals its `omicron_common::address`
/// original so the copies cannot drift from the source of truth.
///
/// `omicron_common` is a dev-dependency only, so it does not appear in the
/// normal dependency tree the no-omicron CI check inspects.
#[test]
fn constants_match_canonical_values() {
assert_eq!(IPV4_SSM_SUBNET, canonical::IPV4_SSM_SUBNET);
// TODO: Compare IPV4_SSM_RESERVED_SUBNET, IPV6_SSM_SUBNETS, and
// is_ssm_address against their canonical originals once the Omicron
// change replacing the ff30::/12 IPV6_SSM_SUBNET with per-scope /32
// blocks lands on main and the pinned revision picks it up.
assert_eq!(IPV4_MULTICAST_RANGE, canonical::IPV4_MULTICAST_RANGE);
assert_eq!(
IPV4_LINK_LOCAL_MULTICAST_SUBNET,
canonical::IPV4_LINK_LOCAL_MULTICAST_SUBNET
);
assert_eq!(IPV6_MULTICAST_RANGE, canonical::IPV6_MULTICAST_RANGE);
assert_eq!(IPV6_MULTICAST_PREFIX, canonical::IPV6_MULTICAST_PREFIX);
assert_eq!(
IPV6_ADMIN_SCOPED_MULTICAST_PREFIX,
canonical::IPV6_ADMIN_SCOPED_MULTICAST_PREFIX
);
assert_eq!(
UNDERLAY_MULTICAST_SUBNET,
canonical::UNDERLAY_MULTICAST_SUBNET
);
assert_eq!(
IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET,
canonical::IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET
);
assert_eq!(
IPV6_LINK_LOCAL_MULTICAST_SUBNET,
canonical::IPV6_LINK_LOCAL_MULTICAST_SUBNET
);
assert_eq!(
IPV6_RESERVED_SCOPE_MULTICAST_SUBNET,
canonical::IPV6_RESERVED_SCOPE_MULTICAST_SUBNET
);
}
}
2 changes: 2 additions & 0 deletions client-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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/.

pub mod address;

/// Like `println!`, but silently exits on broken pipe (EPIPE) instead of
/// panicking. Other I/O errors still panic.
#[macro_export]
Expand Down
1 change: 1 addition & 0 deletions mg-api-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ mg-api-types-versions.workspace = true

[features]
clap = ["mg-api-types-versions/clap"]
proptest = ["mg-api-types-versions/proptest"]
1 change: 1 addition & 0 deletions mg-api-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

pub mod bfd;
pub mod bgp;
pub mod mrib;
pub mod ndp;
pub mod rdb;
pub mod rib;
Expand Down
8 changes: 8 additions & 0 deletions mg-api-types/src/mrib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// 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/.

pub use mg_api_types_versions::latest::mrib::*;

#[cfg(feature = "proptest")]
pub use mg_api_types_versions::proptest::mrib::*;
5 changes: 5 additions & 0 deletions mg-api-types/versions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ edition = "2024"
[dependencies]
chrono.workspace = true
clap = { workspace = true, optional = true }
client-common.workspace = true
nom.workspace = true
num_enum.workspace = true
oxnet.workspace = true
proptest = { workspace = true, optional = true }
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
slog.workspace = true
thiserror.workspace = true
uuid.workspace = true

[dev-dependencies]
omicron-common.workspace = true
serde_json.workspace = true

[features]
clap = ["dep:clap"]
proptest = ["dep:proptest"]
3 changes: 3 additions & 0 deletions mg-api-types/versions/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@

pub(crate) mod bfd;
pub(crate) mod bgp;
#[cfg(feature = "proptest")]
pub mod mrib;
pub(crate) mod mrib_db;
pub(crate) mod rdb;
Loading