Skip to content

Commit dfcb0b4

Browse files
ccie18643claude
andcommitted
docs(backlog): resumable R4 P2-P5 implementation map
Derived by reading the v4 source-filter machinery: exact locations of the handler mc_* methods / _Ip4GroupMembership contributor / _mc_recompute (to mirror as mc6_* / _Ip6GroupMembership under _lock__multicast, duplicated L2/L3), the v4-only membership API to parallel, the protocol-independent MCAST_JOIN_SOURCE_GROUP family + group_source_req glibc layout for the v6 setsockopt, and the RX ip_mc_sf_allow gate for UDP + RAW. Keeps the large safety-critical feature resumable without re-deriving. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ddd2846 commit dfcb0b4

1 file changed

Lines changed: 73 additions & 2 deletions

File tree

docs/refactor/host_refinements_backlog.md

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,76 @@ self._signal_readable()
188188
- **Effort:** large (multi-phase, mirrors a whole shipped track). **Risk:**
189189
medium. **Value:** highest — real v4/v6 parity.
190190

191+
#### R4 — resumable implementation map (derived 2026-07-19 by reading the v4 machinery)
192+
193+
The v4 source-filter machinery to mirror, with exact locations, so P2-P5
194+
can resume without re-deriving:
195+
196+
- **Handler (`runtime/packet_handler/__init__.py`) — the deep, no-GIL-locked
197+
core, duplicated across the L2 and L3 handler classes:**
198+
- `_Ip4GroupMembership` (line ~156): `operator: bool` + `socket_filters:
199+
dict[int, Ip4MulticastFilter]` + `contributors()`. Mirror as
200+
`_Ip6GroupMembership`.
201+
- handler attrs (line ~325/451): `_ip4_multicast_filters:
202+
dict[Ip4Address, Ip4MulticastFilter]` + `_ip4_multicast_refs:
203+
dict[Ip4Address, _Ip4GroupMembership]`. Mirror `_ip6_*`, init in the
204+
same `__init__`, snapshot/restore in the harness.
205+
- `mc_is_joined` / `_mc_recompute` / `mc_ref_acquire` / `mc_ref_release`
206+
/ `mc_set_socket_filter` / `mc_clear_socket_filter` (lines ~863-980),
207+
all under `self._lock__multicast`. Mirror as `mc6_*`. `_mc_recompute`
208+
is the heart: merge contributors → reception edge calls
209+
`_assign_ip4_multicast` (join), loss calls `_remove_ip4_multicast`
210+
(leave), mid-membership filter delta calls `_send_igmp_state_change`.
211+
- `_ip4_multicast_filter_for` (line ~2586) + the filter-materializing
212+
`_assign_ip4_multicast` / `_remove_ip4_multicast` (L2 ~3785, L3 ~4014;
213+
note the v6 `assign_ip6_multicast`/`remove_ip6_multicast` at L2 ~3752 /
214+
L3 ~3976 are the ANY-SOURCE public path — the filtered path is a new
215+
private sibling). ff02::1 is the v6 permanent-group exemption (v4:
216+
224.0.0.1 `IP4__MULTICAST__ALL_SYSTEMS`).
217+
- **Membership API (`stack/membership.py`, 232 lines, entirely v4):** build
218+
a parallel v6 surface (mirror, not genericize) — `join` / `leave` /
219+
`set_socket_filter` / `clear_socket_filter` / `list_memberships`
220+
delegating to the `mc6_*` handler methods. Cap via
221+
`igmp.max_memberships`'s v6 analogue (no separate MLD cap today — reuse
222+
or add `mld.max_memberships`).
223+
- **Socket (`runtime/socket/__init__.py`):**
224+
- v4 opts at lines ~881-979 (`_ipproto_ip_source_membership` +
225+
`_apply_source_op`), 12-byte `ip_mreq_source`. For v6, Linux uses the
226+
**protocol-independent `MCAST_JOIN_SOURCE_GROUP`(46) /
227+
`MCAST_LEAVE_SOURCE_GROUP`(47) / `MCAST_BLOCK_SOURCE`(43) /
228+
`MCAST_UNBLOCK_SOURCE`(44)** at IPPROTO_IPV6 level with a
229+
`group_source_req` struct — there is NO `IPV6_ADD_SOURCE_MEMBERSHIP`.
230+
Add these as a `McastOption` IntEnum + bare aliases (enums.md §2.2).
231+
- `group_source_req` glibc layout (native): `gsr_interface` uint32 at
232+
offset 0; 4 bytes pad; `gsr_group` sockaddr_storage at offset 8
233+
(sockaddr_in6 → sin6_addr at +8, so group addr = bytes[16:32]);
234+
`gsr_source` sockaddr_storage at offset 136 (source addr =
235+
bytes[144:160]). Validate `ss_family == AF_INET6` (10 on Linux) in
236+
each sockaddr. Total 264 bytes.
237+
- Per-socket `_ip6_source_filters: dict[(ifindex, Ip6Address),
238+
Ip6MulticastFilter]` + a v6 `_apply_source_op` (mirror v4's INCLUDE/
239+
EXCLUDE mode-conflict + EADDRNOTAVAIL errno surface). Push to the v6
240+
membership API. Also migrate the existing simple `IPV6_JOIN_GROUP` /
241+
`IPV6_LEAVE_GROUP` path (line ~1029/1040, the non-refcounted
242+
`_ip6_memberships` set) onto the new API so per-socket refcounting is
243+
correct (the existing leave-removes-for-all bug the code comments flag).
244+
- Socket `close()` must `clear_socket_filter` every held v6 filter
245+
(mirror the v4 close path).
246+
- **RX delivery (`Ip6MulticastFilter.allows`):** gate per-socket delivery
247+
in the IPv6 UDP + RAW RX paths (mirror the v4 `ip_mc_sf_allow` gate; RAW
248+
needed the `RawMetadata.socket_ids` wildcard-combo enumeration — check
249+
the v6 RAW metadata has the same).
250+
- **Tests:** unit (value type — DONE P1; `_apply_source_op` v6 transitions;
251+
`group_source_req` parse) + integration (`tests/integration/protocols/
252+
icmp6/` — setsockopt drives the interface merge + emits the right MLDv2
253+
ALLOW/BLOCK/CHANGE_TO_* record on the wire; RX source-delivery gating for
254+
UDP + RAW). Mirror the shipped IGMP SSM tests under
255+
`tests/integration/protocols/igmp/`.
256+
- **Safety note:** every `mc6_*` mutation runs under `_lock__multicast`
257+
(the no-GIL standing invariant); the L2/L3 duplication must stay in
258+
sync. This is the stack's most safety-critical machinery — do each phase
259+
tests-first and run the full multicast integration suite before commit.
260+
191261
### R5 — CLI polish: JSON output for `address` / `ss` / `route` / `neighbor` — SHIPPED (see "Shipped" above)
192262

193263
Full `-j`/`--json` parity across every observation command is now closed
@@ -335,5 +405,6 @@ None block a 3.0.8 release. Everything here can equally slip to 3.0.9.
335405
- Branch `PyTCP_3_0_8`. Pushed through `b9794191` (UDP + RAW SO_RCVBUF +
336406
this backlog plan).
337407
- Pushed through `c2a76a60` (R1 / R5 / R5-followon / R10 knob / R11).
338-
- **Unpushed:** the R7 DF-probe test-lock commit + the R6 HyStart++
339-
end-to-end test-strengthening commit. Hold until the user says "push".
408+
- **Unpushed:** R7 DF-probe test-lock (`1793fb45`), R6 HyStart++ end-to-end
409+
tests (`6cc2702a`), R4 P1 `Ip6MulticastFilter` (`d2054fe8`) + its doc
410+
notes, and this resumable R4 P2-P5 map. Hold until the user says "push".

0 commit comments

Comments
 (0)