composed: the bind-first accept — the listener on a listen resource
Lands the composed accept (ADR-007 variant 2 / ADR-008): the BIND accept whose listener lives on an alktunnels 0.2.0 bind-first listen resource, at the graduation's vanilla fidelity. - `composed::bindfirst_accept` — the BindAcceptFactory: opens the listen channel (reply#1 advertises the open-op reply's real `bound`, the 0.8.0 reply projection — no expected-addr stand-in), inserts the minted `listen` token into the correlation registry, and returns the accept future parked on the token's oneshot. - `composed::register_forwarded_serving` — the forwarded-op serving half on the SOCKS5 producer's connection: the shared handler correlates by token and owns the middle pump (either-EOF completion; a three-party per-direction shutdown cascade would deadlock). - The Sync-bound wrinkle: `SyncHalves`/`OwnedWrite` (mutex Sync-proof, no unsafe) over the forwarded halves; the owner (`ForwardedOwner`, the TunnelListener) rides inside the write payload — dropping the handle drops the session, halves, and listener together (teardown structural, compiler-enforced). - `tests/composed-accept.rs`: the full story (real bound, real peer, raw pass-through round trip), client-gone teardown drop-witness, distinct tokens for concurrent BINDs, per-accept isolation of a FORBIDDEN forwarded open, late-forwarded correlation miss. - The producer-side adopted forwarded entry rides the per-adopter contract (transport-EOF reap), as upstream's bind-first suites concede; the no-leak probe reflects that shape. Verification: cargo test (default + --all-features), wasm check/clippy (hard gate), clippy --all-targets --all-features -D warnings, fmt --check — all clean.
This commit is contained in:
1 parent
3128d2bd4f
commit
4e1673eefc
5 files changed
+1284
-24
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: implementation/composed-accept
|
||||
name: Composed — the bind-first accept (the listener on a listen resource)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [implementation/composed-egress]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
@@ -115,8 +115,76 @@ Deliverables:
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
- **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 — one pump task 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 — 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
|
||||
|
||||
> To be filled on completion
|
||||
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.
|
||||
Reference in new issue
Block a user