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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

168 changes: 130 additions & 38 deletions common/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,70 @@ pub const SLED_PREFIX: u8 = 64;

// Multicast constants

/// IPv4 Source-Specific Multicast (SSM) subnet as defined in RFC 4607:
/// <https://tools.ietf.org/html/rfc4607>.
/// IPv4 Source-Specific Multicast (SSM) subnet.
///
/// RFC 4607 Section 3 allocates 232.0.0.0/8 as the IPv4 SSM address range.
/// See [RFC 4607 §3] for the IPv4 SSM address range allocation (232.0.0.0/8).
/// This is a single contiguous block, unlike IPv6 which has per-scope ranges.
pub const IPV4_SSM_SUBNET: oxnet::Ipv4Net =
oxnet::Ipv4Net::new_unchecked(Ipv4Addr::new(232, 0, 0, 0), 8);
///
/// [RFC 4607 §3]: https://www.rfc-editor.org/rfc/rfc4607#section-3
pub const IPV4_SSM_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(232, 0, 0, 0), 8);

/// IPv6 Source-Specific Multicast (SSM) subnet as defined in RFC 4607:
/// <https://tools.ietf.org/html/rfc4607>.
/// IPv6 Source-Specific Multicast (SSM) subnet.
///
/// RFC 4607 Section 3 specifies "FF3x::/32 for each scope x" - meaning one
/// /32 block per scope (FF30::/32, FF31::/32, ..., FF3F::/32).
/// See [RFC 4607 §3] for SSM scope allocation. The RFC specifies "ff3x::/32
/// for each scope x" - meaning one /32 block per scope (ff30::/32, ff31::/32,
/// ..., ff3f::/32).
///
/// We use /12 as an implementation convenience to match all these blocks with
/// a single subnet. This works because all SSM addresses share the same first
/// 12 bits:
/// - Bits 0-7: 11111111 (0xFF, multicast prefix)
/// - Bits 0-7: 11111111 (0xff, multicast prefix)
/// - Bits 8-11: 0011 (flag field = 3, indicating SSM)
/// - Bits 12-15: xxxx (scope field, any value 0-F)
/// - Bits 12-15: xxxx (scope field, any value 0-f)
///
/// Thus FF30::/12 efficiently matches FF30:: through FF3F:FFFF:...:FFFF,
/// Thus ff30::/12 efficiently matches ff30:: through ff3f:ffff:...:ffff,
/// covering all SSM scopes.
pub const IPV6_SSM_SUBNET: oxnet::Ipv6Net = oxnet::Ipv6Net::new_unchecked(
Ipv6Addr::new(0xff30, 0, 0, 0, 0, 0, 0, 0),
12,
);
///
/// This superset is used only for contains-based classification and validation
/// (e.g., `contains()` checks). It is not an allocation boundary.
///
/// [RFC 4607 §3]: https://www.rfc-editor.org/rfc/rfc4607#section-3
pub const IPV6_SSM_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff30, 0, 0, 0, 0, 0, 0, 0), 12);

/// Maximum source IPs per SSM group member (per [RFC 3376] IGMPv3).
///
/// [RFC 3376]: https://www.rfc-editor.org/rfc/rfc3376
pub const MAX_SSM_SOURCE_IPS: usize = 64;

/// IPv4 multicast address range (224.0.0.0/4).
/// See RFC 5771 (IPv4 Multicast Address Assignments):
/// <https://www.rfc-editor.org/rfc/rfc5771>
///
/// See [RFC 5771] for IPv4 multicast address assignments.
///
/// [RFC 5771]: https://www.rfc-editor.org/rfc/rfc5771
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).
///
/// This range is reserved for local network control protocols and should not
/// be routed beyond the local link. Includes addresses for protocols like
/// OSPF (224.0.0.5), RIPv2 (224.0.0.9), and other local routing protocols.
/// See RFC 5771 Section 4:
/// <https://www.rfc-editor.org/rfc/rfc5771#section-4>
///
/// See [RFC 5771 §4] for link-local multicast address assignments. The IANA
/// IPv4 Multicast Address Space registry is the canonical source for
/// assignments.
///
/// [RFC 5771 §4]: https://www.rfc-editor.org/rfc/rfc5771#section-4
pub const IPV4_LINK_LOCAL_MULTICAST_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(224, 0, 0, 0), 24);

/// IPv6 multicast address range (ff00::/8).
/// See RFC 4291 (IPv6 Addressing Architecture):
/// <https://www.rfc-editor.org/rfc/rfc4291>
///
/// See [RFC 4291] for IPv6 addressing architecture.
///
/// [RFC 4291]: https://www.rfc-editor.org/rfc/rfc4291
pub const IPV6_MULTICAST_RANGE: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0), 8);

Expand All @@ -82,25 +100,98 @@ pub const IPV6_MULTICAST_PREFIX: u16 = 0xff00;
pub const IPV6_ADMIN_SCOPED_MULTICAST_PREFIX: u16 = 0xff04;

/// IPv6 interface-local multicast subnet (ff01::/16).
///
/// These addresses are not routable and should not be added to IP pools.
/// See RFC 4291 Section 2.7 (multicast scope field):
/// <https://www.rfc-editor.org/rfc/rfc4291#section-2.7>
pub const IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET: oxnet::Ipv6Net =
oxnet::Ipv6Net::new_unchecked(
Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0),
16,
);
///
/// See [RFC 4291 §2.7] for multicast scope field definitions.
///
/// [RFC 4291 §2.7]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7
pub const IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0), 16);

/// Last address in the IPv6 interface-local multicast subnet.
pub const IPV6_INTERFACE_LOCAL_MULTICAST_LAST: Ipv6Addr = Ipv6Addr::new(
0xff01, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
);

/// IPv6 link-local multicast subnet (ff02::/16).
///
/// These addresses are not routable beyond the local link and should not be
/// added to IP pools.
/// See RFC 4291 Section 2.7 (multicast scope field):
/// <https://www.rfc-editor.org/rfc/rfc4291#section-2.7>
pub const IPV6_LINK_LOCAL_MULTICAST_SUBNET: oxnet::Ipv6Net =
oxnet::Ipv6Net::new_unchecked(
Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0),
16,
);
///
/// See [RFC 4291 §2.7] for multicast scope field definitions.
///
/// [RFC 4291 §2.7]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7
pub const IPV6_LINK_LOCAL_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16);

/// Last address in the IPv6 link-local multicast subnet.
pub const IPV6_LINK_LOCAL_MULTICAST_LAST: Ipv6Addr = Ipv6Addr::new(
0xff02, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
);

/// IPv6 reserved-scope multicast subnet (ff00::/16).
///
/// Scope 0 is reserved - packets with this scope must not be originated and
/// must be silently dropped if received. These addresses should not be added
/// to IP pools.
///
/// See [RFC 4291 §2.7] for multicast scope field definitions.
///
/// [RFC 4291 §2.7]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7
pub const IPV6_RESERVED_SCOPE_MULTICAST_SUBNET: Ipv6Net =
Ipv6Net::new_unchecked(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0), 16);

/// Last address in the IPv6 reserved-scope multicast subnet.
pub const IPV6_RESERVED_SCOPE_MULTICAST_LAST: Ipv6Addr = Ipv6Addr::new(
0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
);

/// IPv4 GLOP addressing block (233.0.0.0/8).
///
/// This range is reserved for GLOP addressing and should not be allocated from
/// IP pools for general multicast use.
///
/// See [RFC 3180] for GLOP address allocation.
///
/// [RFC 3180]: https://www.rfc-editor.org/rfc/rfc3180
pub const IPV4_GLOP_MULTICAST_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(233, 0, 0, 0), 8);

/// IPv4 administratively scoped multicast subnet (239.0.0.0/8).
///
/// This range is reserved for organization-local administrative scoping and
/// should not be allocated from IP pools for general multicast use.
///
/// See [RFC 2365] for administratively scoped IP multicast.
///
/// [RFC 2365]: https://www.rfc-editor.org/rfc/rfc2365
pub const IPV4_ADMIN_SCOPED_MULTICAST_SUBNET: Ipv4Net =
Ipv4Net::new_unchecked(Ipv4Addr::new(239, 0, 0, 0), 8);

/// Specifically reserved IPv4 multicast addresses.
///
/// These addresses are reserved for specific protocols and should not be
/// allocated from IP pools. They fall outside the link-local range
/// (224.0.0.0/24) but are still reserved.
///
/// - 224.0.1.1: NTP (Network Time Protocol, RFC 5905)
/// - 224.0.1.39: Cisco Auto-RP-Announce
/// - 224.0.1.40: Cisco Auto-RP-Discovery
/// - 224.0.1.129-132: PTP (Precision Time Protocol, IEEE 1588)
///
/// See [IANA IPv4 Multicast Address Space Registry] for complete assignments.
///
/// [IANA IPv4 Multicast Address Space Registry]: https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml
pub const IPV4_SPECIFIC_RESERVED_MULTICAST_ADDRS: [Ipv4Addr; 7] = [
Ipv4Addr::new(224, 0, 1, 1), // NTP
Ipv4Addr::new(224, 0, 1, 39), // Cisco Auto-RP-Announce
Ipv4Addr::new(224, 0, 1, 40), // Cisco Auto-RP-Discovery
Ipv4Addr::new(224, 0, 1, 129), // PTP-primary
Ipv4Addr::new(224, 0, 1, 130), // PTP-alternate1
Ipv4Addr::new(224, 0, 1, 131), // PTP-alternate2
Ipv4Addr::new(224, 0, 1, 132), // PTP-alternate3
];

/// maximum possible value for a tcp or udp port
pub const MAX_PORT: u16 = u16::MAX;
Expand Down Expand Up @@ -254,8 +345,9 @@ pub static NTP_OPTE_IPV6_SUBNET: LazyLock<Ipv6Net> = LazyLock::new(|| {
// Anycast is a mechanism in which a single IP address is shared by multiple
// devices, and the destination is located based on routing distance.
//
// This is covered by RFC 4291 in much more detail:
// <https://datatracker.ietf.org/doc/html/rfc4291#section-2.6>
// See [RFC 4291 §2.6] for anycast address allocation.
//
// [RFC 4291 §2.6]: https://www.rfc-editor.org/rfc/rfc4291#section-2.6
//
// Anycast addresses are always the "zeroeth" address within a subnet. We
// always explicitly skip these addresses within our network.
Expand Down
4 changes: 2 additions & 2 deletions dev-tools/omdb/tests/successes.out
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ task: "multicast_reconciler"
configured period: every <REDACTED_DURATION>m
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "empty_groups_marked": Number(0), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})

task: "phantom_disks"
configured period: every <REDACTED_DURATION>s
Expand Down Expand Up @@ -1281,7 +1281,7 @@ task: "multicast_reconciler"
configured period: every <REDACTED_DURATION>m
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "empty_groups_marked": Number(0), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})

task: "phantom_disks"
configured period: every <REDACTED_DURATION>s
Expand Down
2 changes: 1 addition & 1 deletion illumos-utils/src/opte/port_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ impl PortManager {
///
/// TODO: Once OPTE kernel module supports multicast group APIs, this
/// method should be updated to configure OPTE port-level multicast
/// group membership. Note: multicast groups are fleet-wide and can span
/// group membership. Note: multicast groups are fleet-scoped and can span
/// across VPCs.
pub fn multicast_groups_ensure(
&self,
Expand Down
48 changes: 16 additions & 32 deletions nexus/auth/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! accept these `authz` types.
//!
//! The `authz` types can be passed to
//! [`crate::context::OpContext::authorize()`] to do an authorization check --
//! [`OpContext::authorize()`] to do an authorization check --
//! is the caller allowed to perform some action on the resource? This is the
//! primary way of doing authz checks in Nexus.
//!
Expand Down Expand Up @@ -153,7 +153,7 @@ where
/// Fleets.
///
/// This object is used for authorization checks on a Fleet by passing it as the
/// `resource` argument to [`crate::context::OpContext::authorize()`]. You
/// `resource` argument to [`OpContext::authorize()`]. You
/// don't construct a `Fleet` yourself -- use the global [`FLEET`].
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Fleet;
Expand Down Expand Up @@ -475,18 +475,16 @@ impl AuthorizedResource for IpPoolList {
/// collection.
///
/// **Authorization Model:**
/// - Multicast groups are fleet-wide resources (similar to IP pools).
/// - Any authenticated user within a silo in the fleet can create, list, read,
/// and modify groups. This includes project collaborators, silo collaborators,
/// and silo admins.
/// - Cross-silo multicast communication is enabled by fleet-wide access.
/// - Multicast groups are fleet-scoped resources.
/// - Groups are created when the first instance joins and deleted when the last
/// member leaves (implicit lifecycle).
/// - **List**: Any authenticated user in the fleet (for discovery).
///
/// The fleet-level collection endpoint (`/v1/multicast-groups`) allows:
/// - Any authenticated user within the fleet's silos to create and list groups.
/// - Instances from different projects and silos can join the same multicast groups.
/// - Fleet-wide listing for all authenticated users (discovery).
/// - Instances from different projects and silos can join the same groups.
///
/// See `omicron.polar` for the detailed policy rules that grant fleet-wide
/// access to authenticated silo users for multicast group operations.
/// See `omicron.polar` for the detailed policy rules.
#[derive(Clone, Copy, Debug)]
pub struct MulticastGroupList;

Expand Down Expand Up @@ -1393,35 +1391,21 @@ authz_resource! {

// MulticastGroup Authorization
//
// MulticastGroups are **fleet-scoped resources** (parent = "Fleet"), similar to
// IP pools, to enable efficient cross-project and cross-silo multicast
// communication.
// MulticastGroups are **fleet-scoped resources** with an implicit lifecycle:
// created when the first instance joins and deleted when the last member leaves.
//
// Authorization rules:
// - Creating/modifying groups: Any authenticated user within a silo in the fleet.
// This includes project collaborators, silo collaborators, and silo admins.
// - Listing groups: Any authenticated user within a silo in the fleet
// - Viewing individual groups: Any authenticated user within a silo in the fleet
// - Attaching instances to groups: only requires Instance::Modify permission
// (users can attach their own instances to any fleet-scoped group)
// - List/Read: Any authenticated user in their fleet
// - Attach/detach: Instance::Modify permission on the instance being attached
//
// Fleet::Admin role can also perform all operations via the parent Fleet relation.
//
// See omicron.polar for the special `has_permission` rules that grant create/modify/
// list/read access to authenticated silo users (including project collaborators),
// enabling cross-project and cross-silo multicast communication without requiring
// Fleet::Admin or Fleet::Viewer roles.
//
// Member management: `MulticastGroup` member attachments/detachments (instances
// joining/leaving groups) use the existing `MulticastGroup` and `Instance`
// authz resources rather than creating a separate `MulticastGroupMember` authz
// resource.
// See omicron.polar for the custom authorization rules.

authz_resource! {
name = "MulticastGroup",
parent = "Fleet",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = FleetChild,
polar_snippet = Custom,
}

// Customer network integration resources nested below "Fleet"
Expand Down
Loading
Loading