WIP handoff (do NOT merge): candidate dial-back fix for scopeless link-local IPv6 — #61 - #62
WIP handoff (do NOT merge): candidate dial-back fix for scopeless link-local IPv6 — #61#62hartsock wants to merge 1 commit into
Conversation
…v6 (issue #61, UNVERIFIED — do not merge)
hartsock
left a comment
There was a problem hiding this comment.
This "feels" right. And it's probably worth noting my intention with using a "mesh" instead of direct dial or TCP here is that "identity floats around" as I've seen when developing other "moving" identities in multi-cloud environments. The whole point here is to allow conversational contexts to "float" like a "cloud container" does between "pods" the individual harnesses become "containers" for the conversational context that "floats over" the top of them all.
Provided we ratchet the design properly this will eventually enable "commodity intelligence" or something buzzwordy ... some marketing person will come up with a name I'm sure.
Anyway. It's my old play book. Crawl into a container. Walk over to a new computer. Run in the cloud. I think I've been doing the exact same strategy for 20 years at this point.
|
Building on Shawn's review — the design steer for whoever lands the real fix: The bug is a location leak in an identity system. #21 made the reply dial the request's source socket address — pinning a conversation to a location, which is exactly what the floating-identity thesis says must never be load-bearing. The empirical results in #61 line up perfectly with that reading:
So the fix shape that matches the thesis: reply-routing resolves the sender's fingerprint (identity), and socket addresses are only ever candidates — the recv path's source (with its That also makes this draft's loopback-augment an honest stepping stone (more candidates) rather than the destination (identity-resolved reply routing). Handing the destination to the transport owner per #61 — this framing is just so the ratchet points the right way. |
|
Real fix is up: #64 (stacked on |
#61) (#64) Retire the single-address reply dial-back introduced in #21: `dial_reply_peer` dialed the request's one source `SocketAddr`, so a scopeless link-local IPv6 (`fe80::` with no scope id — what mDNS surfaces on CI and most Linux hosts) became an undialable single point of failure. QUIC `sendmsg` returned `InvalidInput`; a quiet-bind (`announce:false`) peer, being mDNS-invisible, had dial-back as its only route, so its reply was lost. Per the floating-identity doctrine (docs/decisions/floating_identity.md): a location is only ever a candidate, never load-bearing. - `reply_dial_candidates`: the source path (its recvmsg scope id already intact — verified preserved through iroh's noq-udp fork) plus same-host loopback at the source port. Never one address, never empty. - `is_scopeless_link_local`: hand-rolled `fe80::/10` + `scope_id == 0` test, because std's `is_unicast_link_local` is unstable on MSRV 1.75. Applied to BOTH dial sites — the reply path and the mDNS `dial_peer` candidate set — so a scopeless `fe80::` can no longer `sendmsg`-abort the iroh dial race (the racy full-suite failure seen while diagnosing the bug). Regression tests are pure and deterministic, so they gate every PR; the real dial-back-over-iroh proof stays #[ignore]d on the real-LAN tier. Supersedes the #62 WIP handoff. Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ad-bearing (#63) * docs: floating-identity doctrine — locations are candidates, never load-bearing Elevates Shawn's PR #62 review from thread prose to binding design law: identity (fingerprint) is the routing key; socket addresses are only ever candidate paths; a quiet peer must never resolve to zero paths; conversations outlive locations. Includes the #61 dial-back case study (adding candidates fixed, removing the only path regressed — the bug is the SINGULAR address, so dial_reply_peer(pubkey, [one addr]) is the shape to retire) and the reviewer checklist + ratchet direction (harnesses fungible, conversational context durable — the container/cloud playbook one layer up). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bus): dial replies over a candidate set, drop scopeless link-local (#61) (#64) Retire the single-address reply dial-back introduced in #21: `dial_reply_peer` dialed the request's one source `SocketAddr`, so a scopeless link-local IPv6 (`fe80::` with no scope id — what mDNS surfaces on CI and most Linux hosts) became an undialable single point of failure. QUIC `sendmsg` returned `InvalidInput`; a quiet-bind (`announce:false`) peer, being mDNS-invisible, had dial-back as its only route, so its reply was lost. Per the floating-identity doctrine (docs/decisions/floating_identity.md): a location is only ever a candidate, never load-bearing. - `reply_dial_candidates`: the source path (its recvmsg scope id already intact — verified preserved through iroh's noq-udp fork) plus same-host loopback at the source port. Never one address, never empty. - `is_scopeless_link_local`: hand-rolled `fe80::/10` + `scope_id == 0` test, because std's `is_unicast_link_local` is unstable on MSRV 1.75. Applied to BOTH dial sites — the reply path and the mDNS `dial_peer` candidate set — so a scopeless `fe80::` can no longer `sendmsg`-abort the iroh dial race (the racy full-suite failure seen while diagnosing the bug). Regression tests are pure and deterministic, so they gate every PR; the real dial-back-over-iroh proof stays #[ignore]d on the real-LAN tier. Supersedes the #62 WIP handoff. Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * docs(floating-identity): trim thesis prose, tighten throughout Cut the 20-year-playbook framing + blockquote and the "commodity intelligence" flourish; compress each section to its load-bearing point. The four design laws are unchanged in substance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(floating-identity): law 5 — first contact is a ceremony; cite prior art Add the one gap the Zero-Trust research pass found: keyless first contact is a TOFU leap of faith (RFC 7401's own admission). The pin decision is exposed as data and rendered by the consumer (#65). Add a terse prior-art section (Saltzer RFC 1498, HIP, NIST 800-207, SPIFFE, iroh) so reviewers cite the canon instead of rearguing it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bug (diagnosed — #61)
dial_reply_peer(from #21) dials the request's single sourceaddr. When that's a scopeless link-local IPv6 (fe80::…, what mDNS surfaces on CI / most Linux hosts), QUICsendmsgfails (InvalidInput). A quiet-bind (announce:false) client is mDNS-invisible → dial-back is its only route → reply lost. Red on newt-agent's nightly Mesh-integration; blocks unrelated newt-agent PRs that trip thenewt-core/**path filter.Candidate here (UNVERIFIED)
Augment
dial_reply_peerwith loopback at the source port (mirrorsdial_peer).quiet_client_gets_replies_via_dial_back.fe80::addr stays in the iroh race, can abort the dial).fe80::instead regressed the isolated test → iroh seems to need/reuse the source path, so the real fix is likely upstream: preserve therecvmsgscope_id on the sourceSocketAddrV6, not adial_reply_peerpatch.Ask
Take it from here (scope_id is probably right); close this draft when the real fix lands. Shawn authorized me + is looping you in. No overlap with #58/#59/#60.