- Rewrote em dash prose across the whole tree outside docs/reviews/ (the historical reviews stay verbatim, matching the alktunnels and alktty precedent): src doc comments and inline comments (587), docs/architecture (319), docs/research + sdd_process (484), tests (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77), AGENTS.md (85), and the .opencode/agents prompts (109) - Fenced content follows the precedent: comments, ASCII-diagram label separators, and template placeholders rewritten; wire data, JSON/BAST documents, byte diagrams, and transcripts survive verbatim; assert, expect, panic, error-display, and format message literals rewritten as prose; no code, fixture, or test data change - src/rfc1928/LICENSE: the two dashes in alksocks' own provenance header rewritten; the MIT legal text untouched - AGENTS.md adds Project Convention 18: no em dashes in prose; varied restructure, no spaced-hyphen substitutes, fenced prose rule, en dash numeric ranges exempt, docs/reviews/ exempt from the greps - tasks/*.md frontmatter: the name values rewritten by the sweep are now quoted (a raw colon inside an unquoted YAML scalar parsed as null); taskgraph validate reports 37 tasks, no issues Verification: cargo test; cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; fuzz corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/ and the bast.md BAST JSON data fence
191 lines
10 KiB
Markdown
191 lines
10 KiB
Markdown
---
|
|
id: implementation/composed-accept
|
|
name: "Composed: the bind-first accept (the listener on a listen resource)"
|
|
status: completed
|
|
depends_on: [implementation/composed-egress]
|
|
scope: moderate
|
|
risk: medium
|
|
impact: component
|
|
level: implementation
|
|
---
|
|
|
|
## Description
|
|
|
|
The `composed` feature's accept provider (ADR-008's composed
|
|
variants; ADR-007 variant 2): the BIND accept whose listener lives on
|
|
an alktunnels 0.2.0 bind-first listen resource
|
|
(`register_tunnel_bindfirst_openable` + `BindFirstInjections`),
|
|
per-listener ACL for free, hub-relayable. **Vanilla-fidelity with no
|
|
stand-ins since the graduation** (ADR-007): reply#1 advertises the
|
|
real OS-chosen `bound` (the open-op reply's `bound` field, alkcall
|
|
0.8.0 reply projection); reply#2 carries the real peer (the forwarded
|
|
op's `peer` param).
|
|
|
|
Deliverables:
|
|
|
|
1. **`src/composed/` additions** (feature `composed`): the composed
|
|
accept, named per producer.md §Wiring:
|
|
- `bindfirst_accept(...)` → `BindAcceptFactory`: the POC #5
|
|
`composed_bind_accept` shape **updated to the graduation**:
|
|
the POC's pinned workaround (reply#1 first, the tunnel open
|
|
rides the accept future; the factory carries an expected
|
|
`bind_addr`) is **retired** (ADR-007 §Consequences). The landed
|
|
shape: the factory opens the bind-first listen channel
|
|
(`TunnelListener::open`; the reply projection surfaces
|
|
`bound`), reply#1 advertises the real `bound`, and the accept
|
|
future correlates the forwarded sessions (the `listen` token →
|
|
the adopted forwarded sessions the assembly correlates; the
|
|
forwarded op's `peer` param is reply#2's peer).
|
|
- **The correlation shape is the design center** (the POC's
|
|
upstream-ask ledger retired by the graduation): the factory
|
|
returns `BindAccept` whose accept future waits for the ONE
|
|
forwarded session matching the listener's token. The
|
|
correlation registry is assembly-side bookkeeping (the
|
|
alktunnels `TunnelListener` docs: "forwarded sessions arrive as
|
|
ordinary adopted sessions on the manager; the listener does not
|
|
own the receipt queue"). The provider owns: the
|
|
`register_tunnel_forwarded_openable` serving side (the opener
|
|
half: the SOCKS5 producer's connection must serve the
|
|
forwarded op), the token→waiter map, and the per-forwarded
|
|
pump. Design the wait shape against the landed alkcall 0.8.0
|
|
adopt surface (`manager.adopt_channel`) and record it in Notes.
|
|
- **The Sync-bound wrinkle** (shared with composed-egress): the
|
|
accepted halves must be `Box<dyn AsyncRead + Send + Sync +
|
|
Unpin>` (`BindTargetHandle`'s payloads); alktunnels' `Taken
|
|
Halves` yields `Send + Unpin` only. The owner-wrapper rides
|
|
**inside the handle payloads** (egress.rs's documented posture):
|
|
the wrapper owns the adopted channel session so dropping the
|
|
handle drops everything (the teardown-at-birth discipline,
|
|
compiler-enforced). This is the load-bearing wrapper: design it
|
|
once (with composed-egress's needs), pin it with a test.
|
|
- **Peer-address fidelity**: the forwarded op's `peer` param is
|
|
reply#2's real peer (the graduation's win); the wrapper-aware
|
|
sentinel (`0.0.0.0:0`) is the in-band default's shape, not this
|
|
variant's.
|
|
- **The establishment bound covers only the open-op round trip**
|
|
(ADR-007): the accept wait lives in the teardown-governed pump
|
|
phase: the quiet wait's `select!` drop of the accept future IS
|
|
the client-gone teardown; the listener (inside the future)
|
|
closes with it. The forwarded-op correlation must survive a
|
|
client-gone drop without leaking the listener (the accept
|
|
future owns it end-to-end, which is the seam contract).
|
|
- Module doc: names the ADRs (007/008), the graduation's
|
|
vanilla-fidelity win, the correlation shape, the owner-wrapper.
|
|
2. **Tests** (`tests/composed-accept.rs`): the full composed BIND
|
|
story over channels: a bind-first tunnel producer
|
|
(`register_tunnel_bindfirst_openable` + injections), the SOCKS5
|
|
producer with `composed::bindfirst_accept(...)`, a consumer BIND
|
|
session: reply#1 carries the real `bound`, an app server dials
|
|
the far listener, reply#2 carries the real peer, the pass-through
|
|
data plane round-trips; the client-gone teardown releases the
|
|
listener (drop-witness); two concurrent BIND sessions get
|
|
distinct tokens; a forwarded-open failure is per-accept isolated.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `cargo test` passes; `cargo test --all-features` passes
|
|
- [ ] `cargo check --features composed --target wasm32-unknown-
|
|
unknown` clean (hard gate)
|
|
- [ ] `cargo clippy --all-targets --all-features -- -D warnings`
|
|
clean; `cargo fmt --check` clean
|
|
- [ ] The graduation's shape lands (reply#1 = the real `bound`;
|
|
reply#2 = the forwarded op's real `peer`, no expected-addr
|
|
stand-in), pinned by test
|
|
- [ ] The owner-wrapper rides inside the handle payloads (Sync
|
|
halves; teardown structural), pinned by test
|
|
- [ ] The client-gone teardown releases the listener (the accept
|
|
future owns it end-to-end), pinned by drop-witness test
|
|
- [ ] The forwarded-op serving side + correlation land; per-accept
|
|
isolation, pinned by tests
|
|
|
|
## References
|
|
|
|
- docs/architecture/decisions/007-bind-in-base.md (variant 2 + the
|
|
graduation), 008-provider-defaults.md, 004 (the accept seam's
|
|
contracts), 006 (the command gate)
|
|
- docs/architecture/producer.md §Wiring
|
|
(`composed::bindfirst_accept`), §The Three Seams
|
|
- POC reference: `/workspace/alksocks-bind-poc/src/accept.rs`
|
|
(`composed_bind_accept`, the superseded-in-shape workaround, the
|
|
owner-wrapper's first witness)
|
|
- Landed surfaces: `src/egress.rs` (`BindAccept`/`BindTargetHandle`,
|
|
the contracts), alktunnels 0.2.0 (`register_tunnel_bindfirst_
|
|
openable`, `BindFirstInjections`, `TunnelListener`,
|
|
`register_tunnel_forwarded_openable`, `ForwardedHandler`)
|
|
|
|
## Notes
|
|
|
|
- **The correlation shape** (the design center): a
|
|
`CorrelationRegistry` (`Arc<tokio::sync::Mutex<HashMap<u32,
|
|
oneshot::Sender<ForwardedSession>>>>`) keyed on the `listen` token.
|
|
The factory inserts the token → oneshot right after
|
|
`TunnelListener::open`; the provider's shared forwarded handler
|
|
(`make_forwarded_handler`, registered via
|
|
`register_forwarded_serving` on the SOCKS5 producer's connection)
|
|
removes the entry on the arriving forwarded open and delivers
|
|
`ForwardedSession { far duplex halves, peer }`. A dropped receiver
|
|
(client-gone) or a miss makes the handler drop the forwarded
|
|
channel stream: teardown-at-birth, R-02 (the inline `pump_bidi`
|
|
await in `register_openable`'s wrapper then reaps the entry).
|
|
- **The Sync-bound wrinkle**: alktunnels' halves are `Send + Unpin`
|
|
only; the landed `BindTargetHandle` payloads carry `+ Sync`. Two
|
|
adapters pin the no-unsafe Sync proof (`Mutex<T: Send>` is `Sync`;
|
|
contention never occurs, since only one pump task runs at a time):
|
|
`SyncHalves`
|
|
(read half) and `OwnedWrite` (write half + the owner inside).
|
|
- **The owner-wrapper rides inside the payloads**
|
|
(`ForwardedOwner`): the write payload owns the `TunnelListener`;
|
|
dropping the handle drops the listener → the listen channel EOFs →
|
|
the far teardown cascades (teardown structural,
|
|
compiler-enforced). The edition-2021 disjoint-capture trap (an
|
|
earlier draft captured `listener.channel_id`, dropping the
|
|
listener at the factory's return, that is EOF at birth) is why the whole
|
|
struct is rebound into the accept future.
|
|
- **The three-party bridge pump**: the forwarded handler bridges the
|
|
forwarded `BiStream` to a local duplex pair whose far end travels
|
|
into the accept future's payloads. The bridge completes on EITHER
|
|
direction's EOF (a `pump_bidi`-then-drop, not a per-direction
|
|
shutdown cascade; a three-party half-close cycle would deadlock:
|
|
each pump's completion-shutdown needs the next hop's EOF, and the
|
|
chain closes on itself). Dropping everything EOFs the SOCKS5
|
|
producer-side pump, ends the per-accept pump, and tears the
|
|
forwarded channel down.
|
|
- **Test concession (producer-side adopted forwarded entry)**: the
|
|
producer's `open_forwarded` caller adopts the opener-allocated
|
|
forwarded channel id on the producer-side manager; that entry's
|
|
reap rides the per-adopter contract + transport EOF, NOT the
|
|
listener's teardown (alktunnels ADR-005; upstream's own bind-first
|
|
suites make the same concession: `producer_bindfirst.rs`
|
|
`no_stray_channels` documents the listen channel, and the
|
|
forwarded entries are adopted sessions owned by their adopter).
|
|
`assert_only_channels` therefore treats odd producer-side ids as
|
|
the adopted-forwarded shape.
|
|
- **Forwarded-open failure semantics**: a FORBIDDEN forwarded open is
|
|
per-accept isolated AT THE TUNNEL LAYER (the producer's accept loop
|
|
drops that connection and keeps serving; the alktunnels
|
|
producer-suite pin). The SOCKS5 accept future stays parked; the
|
|
SOCKS5 producer never learns of the tunnel-layer failure, so no
|
|
reply#2 arrives; the quiet wait is unaffected. Pinned by test.
|
|
|
|
## Summary
|
|
|
|
Landed `composed::bindfirst_accept` + `composed::register_forwarded_serving`
|
|
(the composed accept, ADR-007 variant 2 / ADR-008) on the alktunnels 0.2.0
|
|
bind-first listen resource, at the graduation's vanilla fidelity: reply#1
|
|
advertises the open-op reply's real `bound` (the alkcall 0.8.0 reply
|
|
projection; no expected-addr stand-in), reply#2 carries the forwarded op's
|
|
real `peer` param. The correlation registry (token → oneshot) is assembly
|
|
bookkeeping; the provider owns the forwarded-op serving half, the token→waiter
|
|
map, and the per-forwarded middle pump (either-EOF completion shape). The
|
|
Sync-bound wrinkle is met by `SyncHalves`/`OwnedWrite` (mutex Sync-proof, no
|
|
unsafe) with the owner (`ForwardedOwner`, the `TunnelListener`) riding inside
|
|
the write payload: dropping the handle drops everything (teardown structural,
|
|
compiler-enforced). `tests/composed-accept.rs` pins: the full story (real
|
|
`bound`, real `peer`, raw pass-through round trip), the client-gone teardown
|
|
(drop-witness: the accept future owns the listener end-to-end; no leaked
|
|
listen channel), two concurrent BIND sessions' distinct tokens, per-accept
|
|
isolation of a FORBIDDEN forwarded open, and the late-forwarded correlation
|
|
miss dropping the channel at birth. Verified: `cargo test`, `cargo test
|
|
--all-features`, `cargo check/clippy --target wasm32-unknown-unknown` (hard
|
|
gate), `cargo clippy --all-targets --all-features -- -D warnings`,
|
|
`cargo fmt --check`: all clean. |