Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1c9d172
[multicast] implicit group lifecycle with IP pool integration
zeeshanlakhani Nov 29, 2025
f7b87b4
[follow-up] link gateway should be unicast default pool only
zeeshanlakhani Dec 1, 2025
1e3b137
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 2, 2025
4b62ccb
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 3, 2025
655e602
[update] auto-select appropriate SSM/ASM pool when allocating multica…
zeeshanlakhani Dec 3, 2025
7def773
[fix] auth ordering
zeeshanlakhani Dec 5, 2025
f28aa9c
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 5, 2025
bad9f11
[api] version multicast endpoints for implicit group lifecycle
zeeshanlakhani Dec 5, 2025
7b673a5
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 6, 2025
c8242bc
[merge] external api clarity and udpates
zeeshanlakhani Dec 6, 2025
bc472b3
[fmt] ..
zeeshanlakhani Dec 6, 2025
b12e8c1
[fix] minor updates and delegation for API
zeeshanlakhani Dec 6, 2025
709c568
[multicast] Relax multicast address restrictions for flexibility
zeeshanlakhani Dec 9, 2025
39a016a
[dep] oxnet update
zeeshanlakhani Dec 9, 2025
07a36a1
[nit] expose vni
zeeshanlakhani Dec 9, 2025
d940ab3
[review] source IPs per member + xor with salt underlay mapping
zeeshanlakhani Dec 16, 2025
3d4230c
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 16, 2025
dc791eb
[hakari] ..
zeeshanlakhani Dec 16, 2025
57fc06e
[fix] endpoint
zeeshanlakhani Dec 16, 2025
0e0737e
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 17, 2025
52a3d65
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Dec 17, 2025
9794876
[multicast] put_upsert test helper, pool selection tests, and ASM sou…
zeeshanlakhani Dec 17, 2025
68a141c
[minor] missing wait
zeeshanlakhani Dec 17, 2025
f7f3c39
[minor] test fixup
zeeshanlakhani Dec 17, 2025
c11aae0
[ip-pools] Allow multiple default IP pools per silo
zeeshanlakhani Dec 23, 2025
c0e3fba
Merge remote-tracking branch 'origin/zl/multiple-default-pools' into …
zeeshanlakhani Dec 28, 2025
f7ef672
[multicast] Fix SSM source filtering to be more flexible/ASM
zeeshanlakhani Dec 28, 2025
93460f3
Merge remote-tracking branch 'origin/main' into zl/multiple-default-p…
zeeshanlakhani Dec 30, 2025
0fa862d
[review] check ip_version against pool + sql migration split
zeeshanlakhani Dec 30, 2025
24b435c
[review] doc comments
zeeshanlakhani Dec 30, 2025
cc4bca7
[openapi] regen docs
zeeshanlakhani Dec 31, 2025
9865c3f
Merge remote-tracking branch 'origin/zl/multiple-default-pools' into …
zeeshanlakhani Dec 31, 2025
bdfe893
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Jan 4, 2026
21e6b5d
[multicast] Add ip_version to join API for pool disambiguation+cleanup
zeeshanlakhani Jan 4, 2026
cf28577
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Jan 4, 2026
27e7f53
[multicast] Implicit lifecycle implementation and cleanup
zeeshanlakhani Jan 5, 2026
af93ec2
[doc-fix]
zeeshanlakhani Jan 5, 2026
76a9555
[db] constraint change migration
zeeshanlakhani Jan 6, 2026
b257fc0
[dupe] rm dupe code from merge
zeeshanlakhani Jan 6, 2026
b0cccf8
[minor] paging for instance groups
zeeshanlakhani Jan 7, 2026
ac1bcae
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Jan 9, 2026
0b26c5a
Merge remote-tracking branch 'origin/main' into zl/mcast-implicit-lif…
zeeshanlakhani Jan 9, 2026
bc47c1e
[post-merge] fix
zeeshanlakhani Jan 9, 2026
cbd97e4
[post-merge] fix
zeeshanlakhani Jan 9, 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
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