-
Notifications
You must be signed in to change notification settings - Fork 63
[multicast] implicit group lifecycle with IP pool integration #9450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This PR also addresses permission models, object deletion, and error handling questions related to reserved addresses presented in @askfongjojo's testing Google Doc (default IP Pools are covered in a follow-up, stacked PR). In thinking through the *Groups* API, permission scopes, and flexibility, @rcgoodfellow mentioned this consideration: > Do we need an explicit notion of a group object at all? Or can > instances simply allocate/deallocate group IPs from pools, and there is > no explicit management of group objects. With Fleet admins having access control to create pools and link silos to a pool, we arrived at the idea of replacing the current explicit multicast group CRUD with an implicit lifecycle, where groups are created upon the first member join and deleted when the last member leaves. **Note**: Most of the PR's changes are test-related due to moving away from the explicit multicast group(s) lifecycle. Auth Model: - Discovery (fleet-scoped): - Read/list groups and list members: any authenticated user in the same fleet. - Membership (project-scoped): - Join/leave requires Instance::Modify on the specific instance. - Creation control: - Implicit group creation only when the s silo is linked to a suitable multicast pool (by name or by explicit IP in that pool). Behavior: - Implicit lifecycle: - Create on first join (idempotent); delete when last member leaves (atomic mark-for-removal, reconciler schedules cleanup). - Addressing and validation: - Implicit allocation from the s linked multicast pools. - SSM/ASM semantics enforced: - IPv4 SSM 232/8 and IPv6 ff3x::/32 - Error handling: - Reserved/invalid multicast ranges rejected at pool/range add time. API: - Primary flows: - Group-centric member management: POST/DELETE /v1/multicast-groups/{group}/members - Instance-centric join/leave: PUT/DELETE /v1/instances/{instance}/multicast-groups/{group} - Discovery endpoints remain for list/view; there is no explicit group create/update/delete. - This is a *breaking* change, but multicast is not yet enabled or available in production Key changes: - Implicit group model; groups exist while they have members. - IP pool integration for multicast allocation with silo link gating. - Simplified API centered on join/leave flows. - Add multicast_ip to the member table for responses. - For consistency, move to `Instant` type over `SystemTime` for mcast-related caches Follow-ups (stacked PRs) - [ ] Remove MVLAN from group data model. - [ ] Default IP pool support (IPv4/IPv6 Followrequire unicast/multicast). - [ ] Dendrite: use omicron-common constants for validation.
e44bc27 to
1c9d172
Compare
Previously, internal multicast groups accepted admin-scoped addresses
including admin-local (ff04), site-local (ff05), and org-local (ff08).
This narrows the scope to only admin-local (ff04::/16), which is what
Omicron *now* dictates.
- [ ] This should be merged after
oxidecomputer/omicron#9450 is reviewed
and merged into Omicron. We now make Dendrite/Dpd match Omicron
consistently for validation.
Key changes:
- Remove IPV6_SITE_LOCAL_PATTERN and IPV6_ORG_SCOPE_PATTERN from P4
- Update P4 table entries to only match admin-local (size 4→2)
- Add ADMIN_LOCAL_PREFIX const to dpd-types with RFC doc links
- Update validation to use `is_admin_local_multicast()` from oxnet v0.1.4
- Bump to API version 2 for doc changes (only)
- Update README with OpenAPI generation instructions
- Use new multicast subnet constants from `omicron-common` for validation
Previously, internal multicast groups accepted admin-scoped addresses
including admin-local (ff04), site-local (ff05), and org-local (ff08).
This narrows the scope to only admin-local (ff04::/16), which is what
Omicron *now* dictates.
- [ ] This should be merged after
oxidecomputer/omicron#9450 is reviewed
and merged into Omicron. We now make Dendrite/Dpd match Omicron
consistently for validation.
Key changes:
- Remove IPV6_SITE_LOCAL_PATTERN and IPV6_ORG_SCOPE_PATTERN from P4
- Update P4 table entries to only match admin-local (size 4→2)
- Add ADMIN_LOCAL_PREFIX const to dpd-types with RFC doc links
- Update validation to use `is_admin_local_multicast()` from oxnet v0.1.4
- Bump to API version 2 for doc changes (only)
- Update README with OpenAPI generation instructions
- Use new multicast subnet constants from `omicron-common` for validation
|
@zeeshanlakhani - The revised authn model and implicit creation/deletion logic make sense to me. I'll make another pass tomorrow to ensure I didn't miss anything but the mental model of multicast group lifecycle seems straightforward enough. |
This PR adds omdb commands to inspect multicast state:
- `omdb db multicast groups` - list multicast groups with optional
state and pool name filters
- `omdb db multicast members` - list group members with filters for group-id,
group-name, group-ip, state, and sled-id
- `omdb db multicast info` - show detailed info for a specific group
- `omdb db multicast pools` - list multicast IP pools
We also include:
- Background task status display for multicast_reconciler
- Integration tests for all multicast omdb commands
Follows the multicast lifecycle work in
#9450.
Introduce API version `VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES`
(v2025120500) to support the transition from explicit to implicit
multicast group lifecycle management.
Changes in new API version:
- Groups are created implicitly when first member joins
- Groups are deleted implicitly when last member leaves
- Instance create/update accept `MulticastGroupIdentifier` (name, UUID,
or multicast IP address) instead of just `NameOrId`
- MulticastGroupMemberAdd now has optional `source_ips` for SSM
Backward compatibility (v20251120):
- Add `v20251120` module with compatibility types using `NameOrId`
- Explicit group create/update/delete endpoints marked deprecated
- Proper base64 validation for user_data via shared UserData serde helper
Also includes:
- Add version_policy to techport server for omdb compatibility
nexus/src/app/ip_pool.rs
Outdated
| /// - IPv4: Rejects link-local (224.0.0.0/24), GLOP (233.0.0.0/8), | ||
| /// admin-scoped (239.0.0.0/8), specific reserved addresses (NTP, Cisco | ||
| /// Auto-RP, PTP), and prevents ASM/SSM boundary spanning | ||
| /// - IPv6: Rejects reserved-scope (ff00::/16), interface-local (ff01::/16), | ||
| /// link-local (ff02::/16), and prevents ASM/SSM boundary spanning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation against ASM/SSM boundary spanning is not part of this PR but reading this comment causes me to wonder how to use IP pools with ASM vs SSM IP ranges. Let's say a silo has two distinct types of workloads that have different source IP filtering requirements, how would the operator configure the silo's default IP pool such that users have the control over getting an ASM vs SSM address for a new multicast group (since they can't specify the IP pool to use in a join request)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We handle this in allocate_external_multicast_group:
// Determine if this is an SSM request (source_ips provided) or an
// implicit ASM request (no sources, no explicit pool/IP)
let sources_empty = params.source_ips.as_ref().map(|v| v.is_empty()).unwrap_or(true);
let needs_ssm_pool = !sources_empty && params.pool.is_none() && params.ip.is_none();
let needs_asm_pool = sources_empty && params.pool.is_none() && params.ip.is_none();
// Select the appropriate pool:
// - If `source_ips` provided without explicit pool/IP, find an SSM pool.
// - If no `source_ips` and no explicit pool/IP, find an ASM pool.
let authz_pool = if needs_ssm_pool {
let (authz_pool, _) = self
.ip_pools_fetch_ssm_multicast(opctx)
.await
...
} else if needs_asm_pool {
let (authz_pool, _) = self
.ip_pools_fetch_asm_multicast(opctx)
.await
...If the user provides source_ips in the join request body, we'll select an SSM pool automatically; if a user doesn't provide source_ips, we'll select an ASM pool
automatically.
The operator setup involves creating a multicast pool with SSM ranges (232.x.x.x for IPv4, ff3x::/32 for IPv6) and/or another multicast pool with ASM ranges (other
multicast addresses), then linking both to the silo. One can be marked as the default, but the SSM/ASM selection criteria scans all linked pools by their IP ranges.
It's really all about source IPs (or not).
Includes: - Remove GLOP (233/8), admin-scoped (239/8), and specific reserved address (NTP, Cisco Auto-RP, PTP) restrictions from IP pool validation - Only link-local multicast (224.0.0.0/24) is now rejected (not routable) - Add ASM pool fallback when join-by-name with source_ips finds no SSM pool linked - Allow source filtering on ASM addresses (IGMPv3/MLDv2 supports this) - SSM addresses still require sources per RFC 4607 The previous restrictions were overly conservative. Customers may have legitimate use cases for GLOP (AS-based allocations), admin-scoped (organization-local multicast), and protocol-specific addresses.
rcgoodfellow
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @zeeshanlakhani for getting the implicit group changes coded up. Questions/comments follow.
| tags = ["experimental"], | ||
| versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., | ||
| }] | ||
| async fn multicast_group_member_add( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still want/need this API endpoint? What does this give us beyond the instance join endpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Seems useless now. Will update.
| /// (important given implicit create/delete semantics). Both external and | ||
| /// underlay groups use the same tag for pairing. | ||
| pub fn dpd_tag(&self) -> String { | ||
| self.id().to_string() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the multicast group address in the tag would probably be nice for someone looking at dpd state and trying to figure out where things are coming from and/or what this tag actually means.
| operation_id = "multicast_group_list", | ||
| versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, | ||
| }] | ||
| async fn v2025120300_multicast_group_list( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to exist? AFAICT it's identical in signature and implementation to multicast_group_list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I was right, it's due to the Path type changes, as Path<T> from dropshot is a struct with a private inner field version. But, I was pointing to the wrong version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can clean this up a bit.
| tags = ["experimental"], | ||
| versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., | ||
| }] | ||
| async fn multicast_group_member_remove( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as above, do we need this when we have the instance leave endpoint?
| pub multicast_ip: IpAddr, | ||
| /// Source IP addresses for Source-Specific Multicast (SSM). | ||
| /// Empty array means any source is allowed. | ||
| pub source_ips: Vec<IpAddr>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think SSM source IPs are more a property of an individual members join than of the group itself. Or is this the union of source addresses from all joins for a given group?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean for SSM, sources are typically tied to a group. (S, G); however, you make a good point. Sources should be specific to the member, and this should be a union of those.
This PR also addresses permission models, object deletion, and error handling questions related to reserved addresses presented in @askfongjojo's testing Google Doc (default IP Pools are covered in a follow-up, stacked PR).
In thinking through the Groups API, permission scopes, and flexibility, @rcgoodfellow mentioned this consideration:
With Fleet admins having access control to create pools and link silos to a pool, we arrived at the idea of replacing the current explicit multicast group CRUD with an implicit lifecycle, where groups are created upon the first member join and deleted when the last member leaves.
Note: Most of the PR's changes are test-related due to moving away from the explicit multicast group(s) lifecycle.
Auth Model:
Behavior:
API:
Key changes:
Instanttype overSystemTimefor mcast-related cachesFollow‑ups (stacked [and related] PRs)
omdbadditions for multicast introspection -> [multicast] add omdb mcast commands for groups, members, pools #9464