- `bind_tcp_first` + `TcpListenerHalves::accept_pair`: the real-socket BindFn source (port 0 resolved via local_addr — the `bound` reply field's data) and the per-accept (peer, halves) pair (the peer rides accept()'s return — the forwarded op's `peer` carrier) - unix bind-first parity shipped (OQ-TN-14): `bind_unix` + `UnixListenerHalves` (local_addr / queue-feeding accept_loop / bind-first accept_pair; an unnamed peer fails loud — the forwarded `peer` param requires an address) + `bind_unix_first` - assembly conveniences: `local_bind_first()` (tcp/unix dispatch; udp fails loud — no accept on a UDP socket, the shape is not defined there, never a fake listener) + `local_bindfirst_accept_loop()` (downcasts the opaque listener; a foreign type fails loud) - direct-op DialFn reuse verified + pinned: `local_dial` shared unchanged (the UDP framed wrap composes inside the closure); `direct_udp_through_local_dial_round_trips_framed` drives a REAL udp echo through the direct op's establisher path - tests/local_bindfirst.rs: tcp e2e over real sockets (bound fidelity — the test dials the reported address; two clients → two forwarded channels pumping independently, real distinct peers), unix parity (real path in `bound`, named-peer client via socket2, real peer path on the forwarded open), udp refusal, downcast-miss probe - accept-first shapes (accept_loop + AcceptQueue) untouched Verified: cargo test (default) + --all-features green (45 lib; 151 integration), clippy -D warnings clean (native all-features + wasm32), fmt clean, wasm32 check passes (default crate stays wasm-clean), cargo doc clean (no new warnings). Tasks: tunnels/local-bind-halves (status completed; Notes + Summary filled). CHANGELOG: Unreleased → Added.
9.3 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tunnels/local-bind-halves | local feature — TCP bind halves + the bind-first assembly pieces | completed |
|
narrow | low | component | implementation |
|
Description
Extend the local feature (non-wasm, tokio/net-gated —
tunnels/local-socket-halves established the pattern) with the
bind-first shape's substrate half: a real TCP bind function
(BindFn-compatible) and an accept loop producing
(peer, TargetHandle) pairs, plus the direct-op convenience dial
closure composition (the DialFn the direct op shares with the base
op — likely already satisfied by local_dial; verify, don't
duplicate).
The bind half
/// The real-socket BindFn source (the `local` feature's shape 3
/// backing): bind `addr` (port 0 = OS-chosen), report the REAL
/// bound address (the `bound` reply field's source — ADR-008 §2),
/// expose the accept loop that feeds the dispatcher.
pub async fn bind_tcp_first(addr: &str)
-> Result<BoundListener, TunnelEstablishError>;
pub fn local_bind_first() -> BindFn; // renders via bind_tcp_first
BoundListener.listenercarries theTcpListener(type-erased in the plan —tunnels/bindfirst-producer's shape note); the accept loop is assembly code composed ON it — the protocol crate stays socket-blind, so the loop lives here (local= substrate code, ADR-004):TcpListenerHalvesalready has an accept loop (accept_loopfeeding anAcceptQueue— the accept-first shape). The bind-first shape needs a DIFFERENT loop: per-accept it must produce(peer_addr, TargetHandle)for the accept dispatcher (which opens forwarded channels per accept), not feed a queue for the establisher to pop. Add a sibling method/struct (e.g.TcpListenerHalves::accept_pairsor a newBindFirstAcceptLoopadapter) — decide the honest shape, document in Notes. The peer address rides fromaccept()'s return value (reply#2 fidelity's source — ADR-008 §3'speercarrier).- The direct-op
DialFn:local_dial()already renders(Substrate, backing)→ halves with the UDP framed adapter — the direct establisher calls the SAME closure with the rendered target string. Verify the UDP wrap happens INSIDE the closure (it does — review 001 U-1) so direct-udp gets the framed adapter free; add a test pinning direct-udp-through-local_dialframing if missing.
Tests (tests/, real sockets — the local suite's posture)
- Bind port 0 →
boundreports the REAL OS-chosen address (the reply#1 fidelity pin). - Two clients dial the bound address → two accepts → two forwarded channels (via the bind-first harness) → both pump independently (peer fidelity: each forwarded open carries the REAL accepted peer).
- Unix-domain bind (OQ-TN-14 parity):
bind_unix_first-equivalent — decide whether unix bind-first ships in this task or is deferred with a note (the wire enum already carries unix; the local dial path exists — the bind path is the same shape; prefer shipping both, matching dial parity).
Acceptance Criteria
bind_tcp_first+ the accept-pairs loop (peer + halves per accept) behindlocal- The bound address is the REAL OS-chosen one (port 0 resolved — pinned by test)
- Direct-op
DialFnreuse verified (no duplicate dial code; UDP framed-adapter wrap composes) - Unix bind-first parity decided + shipped or explicitly deferred with rationale
cargo test --all-featuresgreen; default-crate wasm32 checks still clean (the feature gate holds)- Clippy/fmt clean (both feature sets)
References
- docs/architecture/decisions/008-listen-metadata-forwarded-op.md (§1 shape 3 — the bind-first establisher; §3 the peer carrier)
- docs/architecture/producer.md §The Registration API (
BindFnshape) - tasks/tunnels/local-socket-halves.md (the
localpattern + the existingbind_tcp/TcpListenerHalves/AcceptQueueaccept-first shapes) - tasks/tunnels/bindfirst-producer.md (the
BindFn/BindFirstAcceptLoopcontracts this implements) - OQ-TN-14 (unix ships with
localv1; stdio out — alktty's)
Notes
- The task sketch's
bind_tcp_first(addr: &str)signature held — the committed [BindFn] contract takes(substrate, backing); the substrate dispatch lives inside [local_bind_first] (tcp → [bind_tcp_first], unix → [bind_unix_first], udp → fails loud), matching the type-erased closure precedent (the protocol crate stays socket-blind; the closure is the assembly side). - The accept-pairs shape: a
*_pairmethod, not a sibling loop struct — [TcpListenerHalves::accept_pair] (one accept per call →(peer, halves)). The task floatedaccept_pairsor aBindFirstAcceptLoopadapter struct; one method is the honest shape: the DISPATCHER owns the loop pacing (the committedBindFirstAcceptLoopcontract is one closure call = one accept), so the substrate half is just the single-accept step with the peer attached.local_bindfirst_accept_loop()is the adapter — it wraps the*_pairmethods with the downcast (the assembly side of the type-erased injection; a foreign listener type fails loud ashandler_error— pinned by test). The accept-FIRST [TcpListenerHalves::accept_loop] (queue-feeding) is untouched. - UDP bind-first is NOT here — refused loud (a decision the task
left open): a UDP socket has no accept, so the bind-first shape is
not DEFINED for udp; [
local_bind_first] mapsSubstrate::Udpto ahandler_errorrather than faking a listener (pinned byudp_bind_first_fails_loud). The per-peer associate shape (an accept-analogue for UDP) would be a wire/protocol design decision — an ADR, if it ever ships. The direct/forward udp dial shapes cover the substrate. - Unix bind-first SHIPPED (the task's preferred posture — OQ-TN-14
dial parity):
bind_unix+UnixListenerHalves(a sibling of [TcpListenerHalves]:local_addr, the queue-feedingaccept_loop, and the bind-firstaccept_pair) + [bind_unix_first]. The bind-firstaccept_pair's peer is the ACCEPTED client's bound path; an unnamed (autobind) client fails loud — the forwarded op'speerparam is required on the wire, an addressless peer cannot be represented (ADR-008 §3). The unix parity test binds the CLIENT to a named path first (socket2 — std has noUnixStream::bind), making the peer representable. bind_unixmaps bind failure toResourceShortage(matching [bind_tcp]'s posture — the held-resource class), notDialFailed: the bind IS the dial for the establisher's error table (shape-3: unbindable/held →dial_failed-class rides the establisher's passthrough), but the substrate function's own class follows the sibling bind functions.- Direct-op
DialFnreuse: verified, no duplication — [local_dial] is unchanged and shared; the UDP wrap happens INSIDE the closure (review 001 U-1). The missing pin was added:direct_udp_through_local_dial_round_trips_framed(tests/ local_halves.rs) drivesTunnelSession::open_direct+Substrate::Udpthrough [local_dial] against a REAL udp echo socket — datagram boundary preserved through the direct op's establisher path.
Summary
Implemented the local feature's bind-first substrate half (shape 3,
ADR-008 as amended) per the task: src/local/mod.rs — bind_tcp_first
(the real-socket [BindFn] source: port 0 resolved via local_addr,
the bound reply field's data), TcpListenerHalves::accept_pair (one
accept → (peer, halves) — the peer rides accept()'s return, the
forwarded op's peer carrier), bind_unix + UnixListenerHalves +
bind_unix_first (unix bind-first parity SHIPPED — OQ-TN-14; unnamed
peer fails loud, the forwarded peer param requires an address),
local_bind_first() (the [BindFn] convenience: tcp/unix dispatch,
udp fails loud — no accept on a UDP socket, the shape is not defined
there, never a fake listener) and local_bindfirst_accept_loop() (the
[BindFirstAcceptLoop] convenience: downcasts the plan's opaque
listener to the module's wrappers, one pair per call; a foreign
listener fails loud). The accept-FIRST shapes (accept_loop +
AcceptQueue) are untouched.
Tests: tests/local_bindfirst.rs (4, #![cfg(feature = "local")]) —
the tcp e2e over REAL sockets (bind port 0 → the reply's bound is
the REAL OS-chosen address the test then DIALS — a fake bound would
strand the dial; two clients → two accepts → two forwarded channels →
independent interleaved pumps, each forwarded open carrying the REAL
accepted peer, distinct ports), unix parity (real socket path in
bound; a named-path client connects; the forwarded open carries the
REAL peer path), the udp refusal, and the downcast-miss probe. Plus
direct_udp_through_local_dial_round_trips_framed in tests/
local_halves.rs — the direct-op DialFn reuse pin (the framed udp
adapter composes through the direct op; review 001 U-1).
Verified: cargo test (default) and cargo test --all-features green
(46 lib + 151 integration total; the local suites 20 + 4), clippy
--all-targets --all-features -D warnings clean, fmt clean,
wasm32-unknown-unknown check passes (the default crate stays
wasm-clean; the bind-first half lives behind local, non-wasm by
design).