Skip to content

feat: LAN-direct — Locator seam + QUIC + mDNS (C1 + C2) + happy-eyeballs#43

Merged
frahlg merged 8 commits into
mainfrom
c1-c2-lan-direct
Jun 13, 2026
Merged

feat: LAN-direct — Locator seam + QUIC + mDNS (C1 + C2) + happy-eyeballs#43
frahlg merged 8 commits into
mainfrom
c1-c2-lan-direct

Conversation

@frahlg

@frahlg frahlg commented Jun 13, 2026

Copy link
Copy Markdown
Member

Implements docs/superpowers/specs/2026-06-13-c1-c2-lan-direct-locator-design.md.
Supersedes #42 (auto-closed when its stacked base b1.4-b1.5-wallet-wire merged via
#41); rebased onto main, same commits + the happy-eyeballs race. CLI-only, Go-only
the browser keeps using the relay; no web changes, no byte-identical crypto gate touched.

What this delivers

mir attach reaches a mir up node on the same LAN with no relay — zero-config mDNS
discovery + a direct QUIC transport — via a Locator seam under the unchanged Noise-KK
session. The relay/WAN path is untouched.

  • C1 — Locator seam (pure refactor). Attach composes locators and runs Noise-KK over
    the first that connects; today's relay path moves verbatim into relayLocator. Relay e2e
    stays green.
  • C2 — LAN-direct. mDNS (_miranda._udp) discovery + QUIC (internal/quicmsg,
    self-signed + skip-verify; real auth is Noise-KK + the wallet binding inside the
    stream). The binding is frame 0; the agent reuses ownerPubFromBinding
    serveAuthenticated (shared with the relay path).
  • Staggered happy-eyeballs. LAN starts immediately; the relay only after a ~200 ms head
    start; first live MsgConn wins, loser cancelled+cleaned. A successful LAN attach never
    contacts the relay
    (relay-free, no metadata); remote attaches pay only the head start.
    Race-tested (-race).
  • Escapes: mir up --no-lan, mir attach --relay-only.

Trust (unchanged) + new surface

Locate but never impersonate. mDNS spoofing / a rogue LAN host → at worst a failed handshake
(DoS). New exposure: the agent accepts inbound LAN connections (bounded by the pre-auth
limiter) + an mDNS advertisement of an opaque machine_id; both off with --no-lan.
Documented in SECURITY.md.

QUIC-over-WAN is the destination, not this step

WAN's hard part is NAT traversal (ICE), which WebRTC already ships; a future
QUICHolePunchLocator (DCUtR, north-star C4) drops into the same seam — stated, not built.

Tests / deps

go test ./... (incl. real-shell echo over QUIC with no relay, unpinned-binding rejection,
staggered-race under -race), go vet, gofmt clean. Real mir binary exposes the flags.
New deps: quic-go v0.60.0, grandcat/zeroconf v1.0.0. Follow-up: deploy/netsim LAN path.

🤖 Generated with Claude Code

frahlg and others added 8 commits June 13, 2026 14:11
Approved 2026-06-13. CLI-only, Go-only. A Locator seam (in package client)
turns a Machine into a live peer.MsgConn; Attach composes [LAN, Relay].
LAN uses QUIC (self-signed + skip-verify; real auth = Noise-KK + binding)
and mDNS discovery. Relay/WAN path unchanged. QUIC-over-WAN (DCUtR) is a
stated future locator, not built now.

Stacked on the B1.4+B1.5 branch (reuses ownerPubFromBinding, the cached
binding, and wallet owner_id).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a Locator interface (package client) that turns a Machine into a
live peer.MsgConn; Attach composes [relayLocator] and runs Noise-KK over
whatever connects. Today's relay path moves verbatim into relayLocator.
Attach now returns peer.MsgConn (was *peer.DataChannel; consumers use only
Send/Recv). Behavior-preserving: the relay e2e tests stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A length-framed QUIC bidi stream as a peer.MsgConn, shared by client (LAN
dial) and agent (LAN listen). QUIC TLS is dumb transport: the real auth is
Noise-KK + the wallet binding that runs inside, so ClientTLS skips
verification. ServerTLS uses an ephemeral self-signed cert. ALPN
miranda/lan/v1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A lanLocator that resolves a machine_id to a LAN address (mDNS in prod,
injectable resolver in tests), QUIC-dials it, and sends the wallet binding
as frame 0 before Noise-KK. Returns ErrUnreachable on miss / no-wallet so
Attach falls through to the relay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…accept

mir up can accept LAN-direct QUIC connections (advertised via mDNS as
_miranda._udp). The wallet binding arrives as frame 0; the agent checks
IsOwnerPinned + verifies the binding (reusing ownerPubFromBinding) and pins
binding.x25519, then runs the SAME authenticated PTY session as the relay
path via the extracted serveAuthenticated helper. admit() bounds pre-auth
handshakes. Not yet auto-started (wiring + --no-lan is next).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-only

mir attach now tries LAN-direct (mDNS+QUIC) first with a ~600ms budget,
then falls back to the relay; --relay-only skips LAN. mir up starts the
QUIC listener + mDNS advertisement (in Up, after the paired-owner guard)
unless --no-lan; LAN start failure is non-fatal — the relay path always
serves. attachLocators() makes the LAN-first-vs-relay-only decision unit-
testable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lve test

SECURITY.md gains an honest LAN-direct residual-exposure note (new inbound
listener surface + mDNS leak; same Noise-KK + binding trust, --no-lan to
disable). README documents LAN-direct + the flags. A skippable live-mDNS
test validates the prod resolver where multicast is available. Spec notes
happy-eyeballs as the future latency refinement and netsim as a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the sequential LAN-then-relay dial with dialStaggered: the LAN
locator starts immediately, the relay only after a ~200ms head start; the
first live MsgConn wins, the loser is cancelled and any late-connecting
loser is cleaned up. On the LAN, LAN-direct wins inside the head start so
the relay is never contacted (a successful LAN attach stays relay-free).
Remote attaches pay only the head start, not the full ~600ms LAN budget.
Staggered (not a naive simultaneous race) on purpose: it keeps the
relay-free property that is the point of LAN-direct. Race-tested (-race).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@frahlg frahlg merged commit b9fb511 into main Jun 13, 2026
2 checks passed
@frahlg frahlg deleted the c1-c2-lan-direct branch June 13, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant