- ProducerDoor (feature local, src/local/producer_door.rs): a real TcpListener accept loop handing each accepted connection to the landed Socks5Producer::serve_socks5_over — the NoAuth in-stream RFC conversation with the local seam providers dialing/egressing/accepting; no new protocol-layer code. - The accept path owns ADR-014's two knobs: the idle-first-byte peek (non-consuming) and the max-concurrent-sessions cap (try_acquire — an accept beyond the bound is refused door-side). - ProducerDoorGuard: the listener's lifetime is the caller's — graceful close() awaits the loop's end; Drop aborts best-effort (the alktunnels TunnelListener teardown posture). In-flight sessions are never force-closed. - Per-connection failures are logged + isolated (the loop continues). - The BIND gate on this path is the builder's bind_policy (no open-op identity here; the pump handler's identity-derived gate does not apply) — pinned by the 0x02-refusal test. - tests/local-backend-producer.rs (9): vanilla CONNECT round-trip on real sockets, faithful-RFC BIND (real reply#1/reply#2 addresses), UDP ASSOCIATE through the real egress, domain resolution at the seam, idle timeout, session cap, failure isolation, bind_policy(false). Verification: cargo test + --all-features green; clippy clean (default, all-features, wasm32 targets); cargo fmt --check clean; wasm checks green (backend is local-gated).
7.0 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| implementation/local-backend-producer | Producer-side local backend — the producer's front door (vanilla clients → a locally-served SOCKS5 resource) | completed |
|
narrow | low | component | implementation |
Description
The producer-side local backend (feature local): the same door
shape in the other direction — the producer serves from a real
listener, so vanilla clients reach a locally-served SOCKS5 resource
(producer.md §Producer-Side Local Backend). This is the shape where
the SOCKS5 service rides a real TcpListener instead of (or alongside)
channels: each accepted TCP connection is handed to the landed
substrate-free entry Socks5Producer::serve_socks5_over directly —
the RFC conversation runs on the accepted socket, the seams dial
locally.
Deliverables:
src/local/additions (featurelocal) — the producer-side listener shape:- A
serve_local-shaped constructor on the local module (exact name at implementation; producer.md names no symbol — record the choice in Notes): takes aSocks5Producer(the landed builder type), a bind address, and the door-policy knobs (the idle-first-byte timeout + max-concurrent-sessions cap — the same two knobs the consumer door carries, ADR-014's accept-path defense; an accepted socket is not a channel). - The accept loop: bind the listener, per accepted connection
spawn
producer.serve_socks5_over(stream); per-connection failures are logged + isolated (the loop continues). The listener's lifetime is the caller's (aclose/Drop shape — the alktunnelsTunnelListenerteardown posture). - This is a front door for the producer side: the door
surface (NoAuth raw pipe vs RFC 1929 cred-bearing, ADR-005) is
the consumer door's code shape. For the producer-side backend
the NoAuth shape is the primary one (the producer IS the RFC
conversation here —
serve_socks5_overrunsaccept_no_authin-stream); a cred-bearing variant would wrapaccept_password_authbeforeserve_socks5_over— land the NoAuth shape; the cred-bearing producer-side door is a documented follow-up unless a test drives it (record the decision in Notes). - The BIND command gate on this path is the builder's
bind_policy(the landedSocks5ProducerBuilder::bind_policy— the substrate-free entry's gate); document that the per-caller gate (the pump handler's identity-derived one) does not apply here — this path has no open-op identity.
- A
- Tests (
tests/local-backend-producer.rs): a vanilla RFC 1928 client against the locally-served producer — CONNECT round-trip throughlocal::tcp_dial()seams (the whole stack on real sockets); BIND through the local backend (the faithful-RFC variant: real reply#1/reply#2 addresses); the idle-first-byte timeout; the session cap; per-connection failure isolation; thebind_policy(false)gate refuses BIND RFC-faithfully (0x02).
Acceptance Criteria
cargo testpasses;cargo test --all-featurespassescargo clippy --all-targets -- -D warningsclean;cargo fmt --checkclean- Default wasm checks stay green (backend code is
local-gated) - The backend serves via
Socks5Producer::serve_socks5_over(the landed substrate-free entry) — no new protocol-layer code - The accept path owns the idle defense + session cap (the two knobs) — pinned by tests
- Per-connection failure isolation — pinned by test
- The
bind_policygate is honored on this path — pinned by test
References
- docs/architecture/producer.md §Producer-Side Local Backend, §Wiring
- docs/architecture/decisions/005-auth-split-by-door.md (the door surface), 014-idle-timeout-posture.md, 006 (the bind gate)
- Landed surfaces:
src/producer.rs(Socks5Producer/Socks5ProducerBuilder::bind_policy/serve_socks5_over),src/local/(the providers from local-providers) - POC reference:
/workspace/alksocks-connect-poc/src/producer.rs(theserve_socks5_overshape the POC validated)
Notes
- Symbol choice: the constructor is
ProducerDoor(+ProducerDoorBuilder/ProducerDoorGuard) insrc/local/producer_door.rs— the consumer door's sibling name (FrontDoor/UdpFrontDoor), notserve_local: the surface is a builder + accept-loop object (serve()binds,serve_on(listener)is the test/assembly seam over an existing listener), matching the consumer door's shape rather than a free function. - Door surface decision: the NoAuth shape landed; the cred-bearing
producer-side door is a documented follow-up (no test drives it). On this
path the producer IS the RFC conversation (
serve_socks5_overrunsaccept_no_authin-stream) — there is no door-side protocol half to re-originate; a cred-bearing variant would wrapaccept_password_authbefore handing the stream toserve_socks5_over. - Guard shape (the alktunnels
TunnelListenerteardown posture):serve/serve_onreturn aProducerDoorGuard;close()ends the accept loop gracefully and awaits the loop task; a Drop aborts it best-effort (sync, never awaits). In-flight sessions are never force-closed by the guard — they own their sockets (per-connection isolation cuts both ways). - BIND gate documentation: the per-caller gate (the pump handler's
identity-derived one) does not apply here — this path has no open-op
identity; the builder's
bind_policyis the whole gate, pinned by thebind_policy(false)test (0x02 refusal, CONNECT still serves). - No protocol-layer code: the accept loop rides
Socks5Producer::serve_socks5_overdirectly; the only additions are accept-loop assembly (peekidle gate,Semaphoresession cap,try_acquire_owneddoor-side refusal) and thewatch-signaled guard.
Summary
Landed the producer-side local backend (feature local): ProducerDoor
(src/local/producer_door.rs, exported from the crate root) — a real
TcpListener front door handing every accepted connection to the landed
Socks5Producer::serve_socks5_over (NoAuth in-stream RFC conversation;
local::tcp_dial() / udp_egress() / bind_accept() dial locally). The
accept path owns ADR-014's two knobs (idle-first-byte peek + session-cap
semaphore, door-side refusals); per-connection failures are logged +
isolated; the ProducerDoorGuard carries the listener's lifetime
(graceful close() / best-effort abort Drop). Pinned by
tests/local-backend-producer.rs (9 tests): vanilla CONNECT round-trip on
real sockets, faithful-RFC BIND (real reply#1/reply#2 addresses), UDP
ASSOCIATE through the real egress, domain-target resolution at the seam,
the idle timeout, the session cap (refusal while live sessions serve),
failure isolation, and the bind_policy(false) gate. Verified: cargo test + --all-features green, clippy clean (default/all/wasm targets),
cargo fmt --check clean, wasm checks stay green.