Skip to content

Commit 2f8914a

Browse files
ccie18643claude
andcommitted
feat(mld): IPv6 multicast source-filter handler core (R4 P3)
Adds the 'mc6_*' reception-state machinery to the packet handler — the IPv6 (MLDv2) analogue of the shipped IGMPv3 source-filter core, mirroring the fully-evolved v4 machinery rather than the intermediate Phase-1 form. New '_Ip6GroupMembership' (operator hold + token-keyed per-socket 'socket_filters' + 'contributors()'). The handler now keeps a materialized '_ip6_multicast_filters: dict[Ip6Address, Ip6MulticastFilter]' as the per-interface reception source of truth (including the permanent all-nodes group ff02::1 and the solicited-node groups); '_ip6_multicast' becomes a derived read-only property over its keys, so the RX accept / TX source / MLD report paths are unchanged. '_ip6_multicast_refs' holds the contributors; '_ip6_multicast_filter_for' merges them (RFC 3810 §4.2), or returns the EXCLUDE{} any-source default for a directly-assigned group. The six 'mc6_*' methods (mc6_is_joined / _mc6_recompute / mc6_ref_acquire / mc6_ref_release / mc6_set_socket_filter / mc6_clear_socket_filter) drive the join / leave edge off the merged filter's reception state, exempting the permanent all-nodes group ff02::1 (the v6 analogue of 224.0.0.1). The IPv6 multicast reception state moved from '_lock__addr_config' to '_lock__multicast' (one lock domain, matching v4), so 'L2/L3.assign/remove_ip6_multicast' now materialize the filter map under '_lock__multicast' and the address-config callers take '_lock__addr_config' THEN '_lock__multicast'. The move keeps '_mc6_recompute' (which holds the multicast lock) from having to reach for the address-config lock on its join edge — the no-GIL lock-ordering hazard the single-domain design avoids. P3 is behaviour-preserving: every contributor is still EXCLUDE{} any-source, so the same MLD Reports fire on join / leave. The '_mc6_recompute' mid-membership filter-delta branch is a '# P4:' placeholder (current-state Report) until '_send_mld_state_change' emits the MLDv2 ALLOW / BLOCK / CHANGE_TO_* source records. The socket 'MCAST_JOIN_SOURCE_GROUP' options (P2), the source-delta TX records (P4), and the RX source-delivery gate (P5) land next; the RFC 3810 §4.2 / §5.2 adherence flip waits for those (as the v4 §3.2 flip waited for Phase 5). Tests-first: 'test__icmp6__mld__source_filter_model.py' drives the model end-to-end through the handler (materialization, derived view, INCLUDE join, merge-holds-reception-until-last-ref, operator hold, all-nodes exemption). The NetworkTestCase harness seeds '_ip6_multicast_filters' (dict) instead of the list; the addr-config thread-safety test now tracks '_lock__multicast'; the ICMPv6 RX/TX and ND-DAD tests seed the filter map directly. Lint clean, 13506 passing, 0 skipped. Reference: RFC 3810 §4.2 (deriving per-interface state from per-socket state). Reference: RFC 3810 §4.1 (per-socket filter mode + source list). Reference: RFC 3810 §6 (the all-nodes group ff02::1 is never MLD-managed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dfcb0b4 commit 2f8914a

12 files changed

Lines changed: 657 additions & 76 deletions

docs/refactor/host_refinements_backlog.md

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ until the user says "push".
7676
flipped the stale "TCP probe path deferred (Phase 3c)" rows in
7777
`rfc8899__dplpmtud` + `rfc4821__plpmtud` adherence records to met. No
7878
production change required.
79+
- [x] **IPv6 SSM handler core (R4 P3)** — the `mc6_*` reception-state
80+
machinery on the packet handler, a faithful mirror of the fully-evolved
81+
IGMPv3 v4 core: `_Ip6GroupMembership`, `_ip6_multicast_filters` (now the
82+
per-interface source of truth, with `_ip6_multicast` a derived read-only
83+
property), `_ip6_multicast_refs`, `_ip6_multicast_filter_for`, and
84+
`mc6_is_joined` / `_mc6_recompute` / `mc6_ref_acquire` / `mc6_ref_release`
85+
/ `mc6_set_socket_filter` / `mc6_clear_socket_filter`. The IPv6 multicast
86+
reception state moved from `_lock__addr_config` to `_lock__multicast`
87+
(one lock domain, matching v4); `L2/L3.assign/remove_ip6_multicast`
88+
materialize the filter map there. Behaviour-preserving (every P3
89+
contributor is EXCLUDE{} any-source; the `_mc6_recompute` mid-delta
90+
branch is a `# P4:` placeholder). Tests-first:
91+
`test__icmp6__mld__source_filter_model.py` (7 wire-driven model tests) +
92+
harness / thread-safety / ND-seed updates. RFC 3810 §4.2/§5.2 adherence
93+
flip waits for P4/P5. See the R4 section for the remaining P2/P4/P5 map.
7994

8095
**The canonical SO_RCVBUF guard pattern** (mirror for any new datagram socket):
8196

@@ -176,7 +191,26 @@ self._signal_readable()
176191
(and/or the protocol-independent `MCAST_JOIN_SOURCE_GROUP` family) as
177192
`IpV6Option` enum members + bare aliases (see `.claude/rules/enums.md`
178193
§2.2). Wire the setsockopt cases.
179-
- P3: `_ip6_multicast_filters` dict on the handler + `_ip6_multicast_filter_for`.
194+
- **P3 — SHIPPED:** the handler `mc6_*` reception-state core, a
195+
faithful mirror of the fully-evolved v4 machinery. Added
196+
`_Ip6GroupMembership`, the `_ip6_multicast_filters` dict as the
197+
per-interface reception source of truth (with `_ip6_multicast` now a
198+
derived read-only property over its keys), `_ip6_multicast_refs`,
199+
`_ip6_multicast_filter_for`, and the six `mc6_*` methods
200+
(`mc6_is_joined` / `_mc6_recompute` / `mc6_ref_acquire` /
201+
`mc6_ref_release` / `mc6_set_socket_filter` /
202+
`mc6_clear_socket_filter`). The IPv6 multicast reception state moved
203+
from `_lock__addr_config` to `_lock__multicast` (matching v4, one lock
204+
domain), so `L2/L3.assign/remove_ip6_multicast` now materialize the
205+
filter map under `_lock__multicast` and the address-config callers
206+
take `_lock__addr_config` THEN `_lock__multicast`. Behaviour-preserving
207+
(every P3 contributor is still EXCLUDE{} any-source, so the same MLD
208+
Reports fire); the `_mc6_recompute` mid-membership filter-delta branch
209+
is a P4 placeholder (current-state Report, marked `# P4:`). Tests-first:
210+
`test__icmp6__mld__source_filter_model.py` (7 wire-driven model tests)
211+
+ the harness / thread-safety / ND-seed test updates. The RFC 3810
212+
§4.2 / §5.2 source-record adherence flip waits for P4/P5 (as the v4
213+
§3.2 flip waited for Phase 5).
180214
- P4: MLDv2 source records on TX — `Icmp6Mld2MulticastAddressRecordType`
181215
already has `ALLOW_NEW_SOURCES` (5) / `BLOCK_OLD_SOURCES` (6) /
182216
`CHANGE_TO_INCLUDE` (3) / `CHANGE_TO_EXCLUDE` (4). Emit the state-change
@@ -191,10 +225,17 @@ self._signal_readable()
191225
#### R4 — resumable implementation map (derived 2026-07-19 by reading the v4 machinery)
192226

193227
The v4 source-filter machinery to mirror, with exact locations, so P2-P5
194-
can resume without re-deriving:
228+
can resume without re-deriving. **Status: P3 (the handler core below) is
229+
SHIPPED** — the `_Ip6GroupMembership` / `_ip6_multicast_filters` /
230+
`_ip6_multicast_refs` / `mc6_*` / `_ip6_multicast_filter_for` machinery all
231+
landed under `_lock__multicast`, and `assign/remove_ip6_multicast` now
232+
materialize the filter map. **Remaining: P2** (Membership API + Socket
233+
`MCAST_*` setsockopt + `group_source_req`), **P4** (`_send_mld_state_change`
234+
MLDv2 source records, replacing the `# P4:` placeholder in `_mc6_recompute`),
235+
**P5** (RX `Ip6MulticastFilter.allows` gate for UDP + RAW).
195236

196237
- **Handler (`runtime/packet_handler/__init__.py`) — the deep, no-GIL-locked
197-
core, duplicated across the L2 and L3 handler classes:**
238+
core, duplicated across the L2 and L3 handler classes — SHIPPED (P3):**
198239
- `_Ip4GroupMembership` (line ~156): `operator: bool` + `socket_filters:
199240
dict[int, Ip4MulticastFilter]` + `contributors()`. Mirror as
200241
`_Ip6GroupMembership`.

0 commit comments

Comments
 (0)