1ad8d9d489e143e777b450ec5e176ef55ef29377
44
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1ad8d9d489 |
core: the cross-cutting integration suite + docs sync
The batch-1 closing task: nine cross-cutting integration tests
(tests/core-integration.rs) pinning the whole protocol core end to
end over the channels harness, plus the architecture-sync docs pass.
Tests:
- One registration, all three commands: CONNECT + ASSOCIATE + BIND
interleaved live on the one channels/socks5/sub op; a teardown
ends only its own session (mux/demux isolation) — the POC #2/#5
regression guard, complete.
- Concurrency: nine concurrent mixed-command sessions (raw topology)
and six through the typed consumer sessions (consumer-side
topology) — the mux/demux pinned in both channel-id allocation
directions; fresh round-trips after the burst; no channel leaks.
- Teardown matrix (wire.md §Sentinels and lifecycle): CONNECT
half-close, ASSOCIATE EOF, BIND client-gone (a parked accept so
the quiet-wait exit is deterministic) — each followed by fresh
sessions of every command (no connection poisoning).
- Identity/scope matrix (ADR-006 end to end): open-only (CONNECT +
ASSOCIATE serve, BIND refused 0x02 at the command gate), bind-only
fail-closed at the open gate for all commands (the spec drift the
task's phrasing carried, reconciled — ADR-006's open gate requires
socks5:open unconditionally), both-scopes (everything, granted
explicitly); the CF-006 witness asserted per row.
- The readiness contract re-pinned under concurrent load: a
hot-Ok(None) egress across concurrent associations does not starve
the datagram stage's channel branch.
Docs sync: README Current State ("the protocol core is implemented"
+ the landed inventory; the decomposition residue gone); consumer.md
§Wiring reconciled to the landed session signatures; ADR-006
§Consequences states the grant asymmetry explicitly; lib.rs crate
docs describe the landed surface.
Verification: cargo test 302 green; --all-features green; clippy
--all-targets -D warnings clean; fmt --check clean; both wasm checks
clean; cargo doc --no-deps clean. Batch boundary holds: no
local/composed code, the default crate is protocol-only.
|
||
|
|
c57451545d |
consumer: AssociateSession — the framed datagram stage + e2e suite
The UDP ASSOCIATE consumer session (POC #2's shape, consumer.md §The associate session, ADR-003): - `AssociateSession::associate` — open → no-auth → ASSOCIATE → reply; the sentinel BND.ADDR is received (`relay_addr()`, RFC fidelity) and never used for addressing (the handle is the channel). The ASSOCIATE DST.ADDR is the vanilla-mirror unspecified address. - `send_datagram` — frames exactly once via the crate's wire `frame()` (the framing-once mirror; the u16 joint bound enforced at frame time). Takes `impl ToTargetAddr` (ADR-012 posture); a `ToTargetAddr for &TargetAddr` impl was added to the forked codec so a `TargetAddr` value survives across sends. - `recv_datagram` — the incremental `DatagramReader` + batched- datagram pending queue, state persisting across calls (the POC's reader-state finding); `Ok(None)` on channel EOF; fail-loud on a stream end mid-frame and on undecodable frames (`WireError` → the forked `AddrError`/`UdpHeaderError` RFC classes — the consumer mirror of the producer stage's `UndecodableFrame` exit). - `shutdown` — the half-close; the mux's implicit-EOF sentinel reaches the producer's stage as EOF (association lifetime = channel lifetime). `close()`/`Drop` own the teardown (sync reap + structural EOF, the CONNECT/BIND posture); teardown-at-birth on in-stream refusals; no `Clone` (compile-fail asserted). - Typed error surface: open failures are `SocksSessionError::Open` (ADR-049); the missing-egress 0x07 and factory-failure codes surface from `associate` as `Rfc`. E2E suite (`tests/consumer-associate.rs`, 8 tests over the harness): round-trips (multiple in-order datagrams, one association, sentinel relay address asserted), the split-datagram persistence test (a 60 KiB datagram reassembling across the call boundary + the pending- queue drain — both halves of the persistence invariant), the empty- payload round-trip (F-2, never EOF), shutdown → a fresh associate succeeds immediately (the POC's test 4), Drop teardown + connection health, the missing-egress refusal (typed 0x07, no phantom channel), the egress-factory failure mapping, and the CONNECT+ASSOCIATE regression guard (interleaved on one registration). Unit tests (4) pin the reader internals over a duplex (`skip_auth`'s handshake-free path). Verification: cargo test 305 (7 suites, all green), clippy --all-targets -D warnings, fmt, cargo doc --no-deps, and both wasm32-unknown-unknown checks clean. |
||
|
|
776844e2fb |
consumer: BindSession — the reply#2 reader + e2e suite
- BindSession::bind (open → no-auth → BIND → reply#1, capturing BND.ADDR) + reply1() (the BindReply1 handle) + await_inbound (the POC #5 read_reply2 — hand-read with the forked read_address codec, no second codec, ADR-011) returning BindDataPlane (into_inner / get_socket_mut + AsyncRead/AsyncWrite forwards) - Teardown ownership (the CONNECT session's posture): Drop sync-reap + structural EOF, close() graceful, teardown-at-birth on in-stream refusals, no Clone (compile_fail-asserted) - Typed error surface: open failures ride SocksSessionError::Open (ADR-049 reasons); the command gate's 0x02 and factory-failure reply#1 codes surface from bind as Rfc; accept-stage reply#2 codes surface at await_inbound - tests/consumer-bind.rs: 9 e2e tests over the harness (round-trip, data-plane accessors, gate refusal, both failure mappings, quiet-wait garbage, drop teardown + health, the CONNECT+BIND regression guard, the Send probe) Verification: cargo test (259 incl. the doctest), clippy -D warnings (native + wasm), fmt, both wasm checks, cargo doc clean. |
||
|
|
76717f3944 |
consumer: Socks5ChannelSession (CONNECT) — teardown ownership + e2e suite
- Socks5ChannelSession::connect: open_channel(OP_SOCKS5_OPEN, {},
SOCKS5_ALPN) then the forked client typestate's use_stream path
over the channel BiStream (POC #1's fit); post-reply the session
is the raw byte pipe (0 B framing)
- Target construction via ToTargetAddr: IP-string forms parse to IP
targets, domain form passes through unresolved (ADR-012)
- split() borrowed halves (SplitPipe) / into_inner() owned pipe
(TakenInner — the session rides along as the teardown owner);
channel_id() for correlation
- Teardown ownership (alktunnels ADR-005 posture): Drop sync-reaps
the adopted entry (teardown_channel is sync; Drop cannot await) +
the pipe drop EOFs the producer pump structurally; close() is the
graceful path (write-half shutdown, then reap); the in-stream
refusal tears the channel down in connect (teardown-at-birth —
no phantom channel); no Clone (compile_fail doctest)
- Error surface: open failures are the typed SocksOpenError reasons
(ADR-049 §4); a refused target is the RFC reply code through
SocksSessionError::Rfc (consumer.md §Error surface)
- Harness: the consumer-side topology (wire_consumer_side[_with] —
the consumer dials with a real ChannelClient; the producer accepts
with the real registration) + witnessing_echo_dial; the
allow(dead_code) attr rides the alktunnels harness posture
- tests/consumer-connect.rs: 9 e2e tests — echo round-trip + CF-006
witness, target forms at the dial seam (ADR-012), refused dial →
typed RFC error + no phantom channel, FORBIDDEN open → typed open
error + no session, drop teardown + connection health, half-close
EOF semantics, into_inner pairing, Send shape
Verified: cargo test (219 green incl. the compile_fail doctest),
clippy --all-targets -D warnings, fmt --check,
check/clippy --target wasm32-unknown-unknown
|
||
|
|
df772ccbeb |
producer: the UDP ASSOCIATE datagram stage — the select!-driven relay (ADR-003)
- serve_udp_associate: sentinel reply (127.0.0.1:0 — the handle-swap surface) then the datagram stage; missing egress refuses RFC-faithfully (CommandNotSupported, ADR-008); egress-factory failure maps to reply#1's code - run_datagram_stage: select! over channel reads ∥ egress replies (the concurrent full-duplex shape), typed StageExit diagnostics (EOF = the clean exit; undecodable frames fail loud; the egress readiness contract tolerated as spurious wakeups) - wire: frame_inner — the reply-path half of framing-once (identical wire to frame(), pinned); egress inner bytes never re-prefixed - tests: the readiness-contract test (instant-Ok(None) egress does not starve the stage), empty-payload datagram legal (never EOF), undecodable-frame loud fail, EOF teardown + fresh associate, the egress drop witness (stage owns it), missing-egress refusal + CONNECT guard — in-process (duplex) and over channels (harness); the three-command regression guard drives all commands serving Verified: cargo test 174/174; clippy --all-targets -D warnings; fmt --check; wasm check + clippy (wasm32-unknown-unknown); doc. |
||
|
|
048493fefa |
producer: the BIND phase model + the socks5:bind command gate (ADR-006/ADR-007)
The TCP BIND dispatch arm replaces its refusal with the full phase model (POC #5-validated scope), plus the command gate at dispatch: - The command gate: TCP BIND requires socks5:bind from the per-call opener identity (CF-005/CF-006) — the pump handler resolves it from the open-op AuthContext; serve_socks5_over<T> takes the resolved can_bind boolean explicitly. A scope-less identity gets the RFC-faithful ConnectionNotAllowed (0x02) with no factory call (the gate precedes the seam) while CONNECT still serves it. - The phase model (wire.md §BIND): the BindAcceptFactory at the command-read interception point → reply#1 carrying BND.ADDR (the factory's bind_addr) → the quiet wait (single-shot select!, exactly two exits: the one accept, or the control stream's EOF/garbage/ error; any byte = protocol garbage → session ends, no data plane; the accept future's drop IS the client-gone teardown) → reply#2 carrying the peer address (via the fork's published new_reply) → pump_bidi inline (the CONNECT shape, R-02). - Two-stage RFC-faithful failure mapping: factory failure → reply#1's mapped code; accept-stage failure → reply#2's mapped code, no data plane. No phantom listeners, no phantom data planes. - The no-factory default is the wrapper-aware in-band variant (ADR-007 variant 3, documented): sentinel reply#1 + sentinel reply#2 (0.0.0.0:0), the full phase model preserved — the channel is the connection. - The accept wait lives in the teardown-governed pump phase, never under the establishment bound (ADR-007). Tests: 16 unit tests (raw RFC client over duplex) + 7 harness integration tests over channels — happy path, the gate (0x02 while CONNECT serves, both scopes), quiet-wait garbage, client-gone teardown (owner-flag witness), both failure stages over channels, no-factory variant 3, and the three-command regression guard (CONNECT serves, BIND serves under its gate, ASSOCIATE still refuses). Verification: cargo test green (96 lib + 26 harness + 37 differential); clippy -D warnings clean (all targets, native + wasm32-unknown-unknown); cargo fmt clean; wasm check clean. Task: tasks/implementation/core-producer-bind.md (status → completed) |
||
|
|
cfacc8a968 |
producer: the session-gate establisher + the command-dispatch pump + CONNECT
The crate's core task (implementation/core-producer-connect): the producer half of src/producer.rs per producer.md, adapted to the crate's seam types and the alkcall 0.8.0 establishment surface. - The session-gate establisher (`socks5_session_establisher`): validates/records the opener identity (the CF-005/CF-006 seam, ADR-005) via the optional `identity_witness` probe (the alktunnels posture); returns `Establishment::default()` — no plan payload (the dial happens post-establishment; POC #1's wire note). - The pump handler (`make_socks5_pump_handler`): `accept_bi()` → the forked typestate over the `BiStream` (`accept_no_auth → read_command`) → dispatch. CONNECT: the dial seam (`DialFn`, ADR-004) at the command-read interception point; success: `reply_success(sentinel)` then `pump_bidi` awaited inline (R-02 — the JoinHandle tracks the data-plane lifetime). Refusal: `reply_error` with the mapped RFC code — no phantom data plane. - The ASSOCIATE/BIND arms land as RFC-faithful refusals (`CommandNotSupported`) — the pre-vertical posture (ADR-008); the dispatch skeleton + registration signature carry the seam parameters from day one so the associate/bind tasks swap the arms without touching the registration shape. - `register_socks5_openable`: spec + establisher + pump via `register_openable_with_establisher` (`timeout: None` = the 10s default), scope-gated `socks5:open`. Missing callbacks = those commands refuse RFC-faithfully, never a panic. - `serve_socks5_over<T>`: the substrate-free protocol entry (the POC shape), directly testable without channels; the builder (`Socks5Producer::builder()`) is the spec'd assembly shape. - Tests: 11 in-module (substrate-free duplex-driven — round-trip + half-close teardown, refusal mapping per variant, missing-dial refusal, ASSOCIATE/BIND arms, dispatch guard, establisher witness, builder shape) + 5 harness integration (end-to-end CONNECT echo with the identity witness, dial refusal 0x05 with no phantom data plane, scope-class refusal 0x02, ASSOCIATE/BIND refusals with CONNECT still serving, repeated CONNECTs on one registration). The harness retargeted to the real producer (`RegisterMode:: Producer`); the minimal stand-in is a harness-internal helper. Verification: cargo test 87+19+37 green; clippy --all-targets -D warnings clean; fmt clean; wasm check + wasm clippy clean; cargo doc clean; --all-features green. |
||
|
|
f8773521b9 |
harness: the producer/consumer integration test harness over duplex
tests/harness.rs — the producer<->consumer wiring over one tokio::io::duplex carrying the channels 8-byte chunk format, the alkcall 0.8.0 establishment surface consumed for the first time by this crate (register_openable_with_establisher + the call/adopt dance) — the first-consumption risk retired. - Identities: consumer (socks5:open) / bind (socks5:bind only) / both-scopes (explicit, ADR-006) + TestIdProvider (token path). - In-process seams, no sockets, no `local`: echo_dial / refusing_dial (variant-preserving), echo_egress (readiness contract verified — next_reply parks Pending when idle) / refusing_egress, echo_accept / refusing_accept_factory / refusing_accept (two-stage BIND refusal shapes, mapped through to_reply_error()). - Producer-side registration helper: register_socks5_openable_minimal + socks5_session_establisher + RegisterMode — the single place the producer wiring lives; the CF-006 identity witness rides it and is asserted end-to-end. - RawRfcClient: the hand-rolled RFC 1928 client helper over any AsyncRead + AsyncWrite, round-tripped against a byte-level hand-rolled server (success + refusal replies). - Smoke test: harness up -> producer registered -> consumer open + adopt -> raw RFC CONNECT -> echo round-trip through pump_bidi. Fail-closed probe: scope-less identity -> FORBIDDEN, no channel anywhere (the load-bearing POC #1 set_identity wiring requirement, explicit in the install hook and tested). 14 harness tests. Verification: cargo test (127 green), clippy (all-targets + wasm32) clean, fmt clean, doc zero warnings, test --all-features green, both wasm checks clean (harness is dev-only). |
||
|
|
ac361f7610 |
seams: the three seam types — dial / egress / accept (ADR-004)
- egress.rs: the I/O-plane inversion point, function-not-trait at the three factories; Egress is the one trait (the per-association returned object) - dial: TargetHandle (boxed halves, +Sync per the ChannelPlan bound), DialFn over the typed TargetAddr (the POCs' JSON stand-in retired), SocksDialError with the five-variant RFC-faithful mapping - egress: Egress (send payload-only; next_reply returns inner bytes; the readiness contract — Pending when nothing is ready — documented on the trait), EgressFactory, EgressError, the egress_factory sync-lift constructor - accept: BindAccept (the accept future owns its resources end-to-end; the quiet wait's drop IS the client-gone teardown), BindTargetHandle (ownership structural; the composed keep-alive owner rides inside the halves provider-side), BindAcceptFactory, the two-stage error pair (factory → reply#1; accept-stage → reply#2) - no substrate type named anywhere in the module; ownership rides the handle structurally (no _ bindings) - 9 new tests: the four error→reply-code tables (all variants, byte-compared), the sync-lift Ok/Err paths, the closure compile shapes, the Send/Sync/Unpin assertions; lib.rs re-exports the surface Verified: cargo test (113 green), --all-features green, clippy -D warnings clean (native + wasm32), fmt clean, cargo doc zero warnings, wasm32 check clean. |
||
|
|
1ccd5bbe0c |
params/error: the wire-stable open-op surface + the typed open-error vocabulary
- params: the ADR-001/ADR-006 constants (`channels/socks5/sub`,
`alk/socks5`, `socks5:open`, `socks5:bind`), `socks5_open_spec()`
(Sub/External/channel-open marker; trivial empty-object input schema
with loud `additionalProperties: false`; reply
`{channel_id: integer, minimum 1}` required; `socks5:open` scope),
`Socks5Params {}` (deny_unknown_fields — empty stays valid forever),
the `establishment_reason` helper re-export.
- error: `SocksOpenError` (the ADR-049 §4 typed surface, shaped on
alktunnels' `TunnelOpenError`), the typed
`SocksEstablishmentReason` vocabulary (the five reason codes,
parse/as_str; unknown codes never aliased), `SocksOpenError::reason()`
as the typed view, and `SocksSessionError` (open vs in-stream `Rfc`
vs `Io` — a refused target is an RFC reply code through the session
type, consumer.md §Error surface). `SocksError`/`SocksServerError`/
`ReplyError` re-exported from the forked core.
- lib.rs re-exports the new public surface (convention 15).
- 9 new tests: spec shape, constants, serde round-trip +
unknown-field rejection, the reason mapping table, the
non-establishment classes (`FORBIDDEN`/`INVALID_INPUT`/
`too_many_channels`), future-reason non-aliasing, the three-plane
session surface.
- task `implementation/core-params-errors`: status → completed;
Notes + Summary filled.
Verification: cargo test (104 green), clippy --all-targets -D
warnings clean, fmt --check clean, doc --no-deps zero warnings,
both wasm checks (check + clippy on wasm32-unknown-unknown) clean.
|
||
|
|
d530eae5c4 |
wire: the ASSOCIATE datagram frame codec — frame + DatagramReader (ADR-003)
- frame(addr, payload): [len: u16 BE][addr + data] over TargetAddr::to_be_bytes() + payload; the joint bound enforced at frame time (checked_add; 65507 + 255-char domain = 65766 > 65535 → Oversize) with addr_encoding_len exposed (7 v4 / 19 v6 / 259 max domain) and MAX_INNER_LEN pinned - DatagramReader: incremental reassembly across chunk boundaries (split frames, batched frames, partial headers), infallible, returning per-frame Results; the addr sub-layout decoded by hand (ATYP-keyed, UTF-8-validated) with typed decode errors — BadAtyp / ShortAddress / DomainNotUtf8 / EmptyFrame — so the producer stage fails the association loud on protocol garbage (udp_undecodable_frame_ends_association, POC #2) - module docs state the framing-once invariant (the egress speaks inner bytes; this layer owns the single length prefix — the double-framing bug class POC #2 caught), the F-2 invariant at payload level (a zero-length payload is a legal empty datagram, never EOF; wire len == 0 is unreachable garbage), and the EOF layering (the channels-level sentinel, outside the codec) - doc-sync: wire.md + bast.md residue corrected — "len == 0 is a legal empty datagram" replaced with the payload-level F-2 statement (len == 0 unreachable since addr >= 7 bytes) - lib.rs re-exports frame, DatagramReader, addr_encoding_len, MAX_INNER_LEN, WireError (convention 15) Verification: cargo test (58 lib + 37 differential), clippy --all-targets -- -D warnings, fmt --check, doc --no-deps, and both wasm checks (check + clippy on wasm32-unknown-unknown) — all clean. |
||
|
|
ce8148d711 |
deps: the alkcall substrate pin + the batch-1 module skeleton
- Cargo.toml: alkcall = "0.8.0" (convention 11 — pinned, first consumption of the 0.8.0 surface), serde/serde_json, bytes, and the std-only futures subset (BoxFuture seam shape). tokio's wasm-clean subset and the empty local/composed features unchanged. - lib.rs: the six skeleton modules per overview.md §Module Map (params, wire, producer, consumer, egress, error) — stub files with module docs naming their filling tasks; no re-exports yet (each implementation task adds its own). Verified: cargo test (76 tests), clippy --all-targets -D warnings, fmt --check, cargo doc --no-deps, both wasm checks (check + clippy on wasm32-unknown-unknown), and the dep-graph spot-checks (no mio/socket2/tokio::net in the default normal graph; normal tokio features = bytes/io-util/macros/rt/sync/time only). alkcall 0.8.0 from crates.io confirmed wasm-clean via probe build before wiring. Task: tasks/implementation/core-crate-deps.md (completed) |
||
|
|
a2483e4a84 |
tasks: decompose the protocol-core group — thirteen dependency-ordered tasks
Batch 1 of the Phase 1 decomposition: the default, wasm-clean crate (producer + consumer + wire + seams + params/errors), per the architecture spec set (ADRs 001-013) and the three POCs' validated shapes. The assembly layer (local/composed providers, front doors) is batch 2, decomposed after review-protocol-core opens. - foundation: crate-deps (alkcall 0.8.0 first consumption), wire-codec (the ASSOCIATE frame + the joint bound), seam-types (the three ADR-004 seams), params-errors (the one-way-door surface) - harness: duplex transport + identities + in-process seams (the alkcall 0.8.0 establishment surface proven before the producer) - verticals: producer/consumer splits per command (CONNECT, ASSOCIATE, BIND) — each POC's scope, producer tasks test via the raw RFC client, consumer tasks e2e through the harness - closing: integration suite (three-command regression, teardown matrix, identity matrix, docs sync) + review-protocol-core (the batch gate) taskgraph: 8 generations, no cycles, risk path 2.55 (no high-risk tasks). Also records the wire.md/bast.md len=0 residue (payload-level F-2 is the operative invariant) as a doc-sync deliverable of the wire-codec task. Verification: cargo test (76 passed), fmt clean; taskgraph validate/cycles/parallel/critical clean. |
||
|
|
7bd5d6a7e7 |
review 001 remediation: the forked core's findings resolved
Batch 1 (F-1 + N-1 + S-2 + N-4): - reply_success/reply_error write replies with write_all (partial-write hazard closed; the err_when-chaining idiom suppresses clippy::unused_io_amount — reproduced fresh via a five-variant probe) - rfc1928.md's write_all bullet records both conversions and the real rule (replies are single-shot, all-or-nothing) + the lint blind spot - reject's doc names the 0xff vocabulary choice (RFC 1929 needs only non-0x00); Addr::Domain's deliberating comment pinned Batch 2 (coverage: L-1 + L-2 + L-3 + S-1): - client v6 request encoding pinned (22 bytes, ATYP=4, slice bounds) - Socks5Stream::shutdown() EOF/BrokenPipe propagation pinned - wait_on_tcp's io-error arm + SocksServerError::to_reply_error's full mapping table + read_command's command-phase version mismatch covered - S-1: read_address's 1-byte-field bound documented; decode-side 255/256 boundary pinned both faces Batch 3 (posture notes: C-1, C-2, N-2, N-3 — decided keep-with-note): - ConnectError: hand-port map's companion (cut if the producer layer lands without consuming it); StandardAuthentication: ADR-005 auth-split is the anticipated consumer; CheckResult same shape - tracing: the fork's declared logging seam, noted in Cargo.toml Coverage 92.63% → 93.09% regions / 95.40% → 96.07% lines; the named gaps closed, the remainder is accepted-uncovered with disposition. Review doc carries the re-verification + resolution record. Verification: cargo test (39 lib + 37 differential), clippy (all-targets + wasm32) -D warnings, fmt, doc, wasm check, MSRV 1.88, publish dry-run — all green. |
||
|
|
4bd25c093e |
review: rfc1928 post-fork full review — 001
Security + code-smell + coverage review of the targeted fork as owned code (the extraction review was narrow; this is the full pass). Findings: 1 major (F-1: reply_success/reply_error write replies with .write() — a partial-write hazard invisible to clippy's unused_io_amount under the err_when-chaining idiom, executable-probe verified; rfc1928.md's write_all rationale is false for these sites), 7 minors (2 security-posture notes, 2 extraction leftovers, 3 coverage gaps), 4 observations. No critical findings. Verification: 38+32 tests, clippy (all-targets + wasm32) -D warnings, fmt, doc, wasm check, publish dry-run, MSRV 1.88 native+wasm — all green. Coverage 92.63% regions / 95.40% lines (cargo-llvm-cov); gaps attributed per finding. Differential suite's 15 upstream citations spot-checked against the v1.0.0 reference checkout — all accurate. |
||
|
|
f8c34444fb |
rfc1928: the targeted fork — rename + convention pass (ADR-013)
The mechanical half of the posture change: src/vendor/ ->
src/rfc1928/ (module alksocks::rfc1928), tests/vendor-differential.rs
-> tests/rfc1928-differential.rs (git mv — history preserved).
- Fork point pinned: the tree as of
|
||
|
|
001aac1fd5 |
docs: the targeted fork — ADR-013, the RFC 1928 core is owned
ADR-013 supersedes ADR-011's maintenance posture (not its mechanics):
the extracted T-generic subset is fully owned code, the vendor
framing is retired. Module renamed src/vendor/ -> src/rfc1928/
(naming decided in the ADR: 'vendor' names an ownership relation
now gone; 'socks5' would stutter and collide with the future
producer/consumer layers). Fork point = the tree as of the
extraction-review commit (
|
||
|
|
5b18d4d292 |
review: vendor extraction — gate opens; F-1 panic arm fixed
The extraction review against vendor.md's cut table, ADR-011
§Amendment, and AGENTS.md conventions. All five file diffs vs the
fast-socks5 v1.0.0 reference checkout read hunk-by-hunk and
categorized (cut / carried changes / adjustments / provenance /
tests); wasm, clippy, fmt, doc all green; RFC 1928 spot-checks
(reply form, method order, UDP zero fallback, domain cap, FRAG
split) independently verified against the RFC text.
F-1 (critical, resolved here): ReplyError::from_u8 kept upstream's
unreachable!("ReplyError code unsupported.") arm while being
wire-reachable — Socks5Stream::read_request_reply feeds the raw
reply byte from the wire into it, and RFC 1928 §6 marks 0x09-0xFF
unassigned, so a misbehaving peer would panic the consumer task.
Unassigned codes now map to GeneralFailure (the RFC's catch-all),
pinned by a new test, and the panic-translation adjustment is
recorded in vendor.md's adjustment list (with the Config-Default
and write_all notes) in the same change.
Findings F-2..F-4 (dead commented code dropped with its region;
Cargo.toml feature-layout comments; the preserved resolve_dns error
string) reviewed and accepted. Review task file carries the full
record; the gate to the downstream task groups is open.
Verified: cargo test (38 + 32), cargo test --all-features, clippy
--all-targets and --target wasm32-unknown-unknown (-D warnings),
cargo check --target wasm32-unknown-unknown, cargo fmt --check,
cargo doc --no-deps.
|
||
|
|
d6fbd04ef3 |
vendor: the differential RFC edge-case suite — 32 duplex-only tests
tests/vendor-differential.rs pins the vendored extraction's RFC edge behavior as explicit bytes, per vendor.md §Maintenance Posture (the differential-testing contract; ADR-011): - negotiation: empty/zero method lists -> 0xff, server-order method preference, 0x04 greeting refusal before the methods byte, EOF mid-handshake typed error, password sub-protocol EmptyUsername/ EmptyPassword + the 0x01/0xff-then-error reject path - command reads: unknown command -> 0x07 reply, RSV != 0 tolerance (upstream reads it unchecked), ATYP 0x02/0x05 -> 0x08, the 255-byte domain wire maximum, truncated v4/domain address errors - reply forms (new_reply regression guard): 10/22-byte shapes, constant VER/RSV across codes, zero-addr error replies, caller sock_addr verbatim on success - client session: per-code ReplyError mapping (incl. the 0x06 double map), domain request encoding, UDP-ASSOCIATE zero-target fallback form, skip_auth emits no greeting - UDP codec: GarbageInReserved, FRAG passthrough (the parser returns it; refusing is the caller's job), v6 round-trip, payload split - transfer/wait_on_tcp: pump both directions to completion, EOF on clean close, UnexpectedUdpControlGarbage carries the byte Every non-obvious expectation names the upstream behavior it pins (file + line in /workspace/fast-socks5, v1.0.0); the reference is consulted, never linked — no build-time dependency. Duplex pairs only (no TcpListener/UdpSocket/tokio::net in test code). Verification: cargo test (37 unit + 32 differential), cargo test --all-features (identical), cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo check --target wasm32-unknown-unknown — all clean. Task: tasks/implementation/vendor-differential-tests.md (status -> completed). |
||
|
|
2ae489a205 |
vendor: the client session — Socks5Stream<S>::use_stream path
Extract the T-generic client session from fast-socks5 v1.0.0's client.rs into src/vendor/client.rs (ADR-011 §Amendment extraction, vendor.md cut table row 2): Socks5Stream<S> with use_stream (the greeting, method-choice read, password sub-handshake, request encoder, reply reader, socket accessors), the AsyncRead/AsyncWrite impls, and the pruned Config (skip_auth only). - typed MissingTargetAddr variant replaces upstream's anyhow::Error::msg(...).into() at client.rs:285 (the one SocksError addition; the client.rs:528 Other(anyhow!) site went with the cut Socks5Datagram) - use_password_auth's unreachable!() is an error return (upstream's None-arm message; convention 2), and the sub-negotiation reply's version byte is now checked (was only a dropped debug!) - write -> write_all in request_header (clippy unused_io_amount; byte-identical on a healthy stream) - cut (not copied): Socks5Datagram, the Socks5Stream<TcpStream> convenience impl (connect*/connect_raw), Config::connect_timeout - 14 first-party duplex tests vs raw-byte mock servers (uncorrelated from the vendored server typestate): both handshakes, 0xff refusal, version mismatch, domain/IP request forms, the full failure-code table, UDP-ASSOCIATE zero fallback, skip_auth, io-impl copy, socket accessors - mod.rs: doc touch-ups (client module now landed; fixed the pre-existing broken intra-doc link) Verification: cargo test (37 pass), --all-features, clippy --all-targets -D warnings (native + wasm32), cargo check --target wasm32-unknown-unknown, cargo fmt --check, cargo doc --no-deps. |
||
|
|
9ba63fd239 |
vendor: the server typestate — Socks5ServerProtocol + new_reply publication
Extract the explicit typestate from fast-socks5 v1.0.0's server.rs into src/vendor/server.rs (ADR-011 §Amendment extraction, vendor.md cut table row 1): the states module, Socks5ServerProtocol<T, S>, the auth machinery (AuthMethod/AuthMethodSuccessState, the NoAuthentication/PasswordAuthentication impls, auth_method_enums! + StandardAuthentication), negotiate_auth/read_command/reply_success/ reply_error, transfer/wait_on_tcp, try_notify!, ErrorContext<T>, SocksServerError (landed early by the wire-vocabulary task). - carried change #2: new_reply is pub (byte-identity tested — v4 10-byte / v6 22-byte forms + the reply-code table) - extraction adjustments: log calls dropped (unused bindings renamed), reply_error's parse().unwrap() replaced with a compile-time const, auth_method_enums! de-exported to pub(crate) use, no anyhow/async-trait - cut (not copied): the legacy Socks5Server/Incoming/Socks5Socket, Config<A>/Authentication layer, DnsResolveHelper, the run_* + udp kernel-net handlers, the net-only test module - 12 first-party duplex tests: full no-auth round-trip, version mismatch, 0xff method refusal, 0x07 unknown-command reply, both reply forms byte-exact, password accept/reject Verification: cargo test (25 pass), --all-features, clippy --all-targets -D warnings (native + wasm32), cargo check --target wasm32-unknown-unknown, cargo fmt --check, cargo doc. |
||
|
|
7497b79c05 |
vendor: the wire vocabulary — consts, commands, codecs, errors, macros
Extract fast-socks5 v1.0.0's non-net lib.rs region into src/vendor/mod.rs (scaffolding-pinned root; the task's lib.rs target name corrected — a second root file would conflict, see task Notes): consts, Socks5Command (as_u8/from_u8 made pub), AuthenticationMethod, SocksError pruned to the extracted region's consumers (no anyhow/ Redaction/InvalidHeader/ReplySocks4Error/ArgumentInputError), the Result alias, ReplyError codec, UdpHeaderError + new_udp_header/ parse_udp_request. util/target_addr.rs gains TargetAddr/AddrError (minus resolve_dns + its two variants)/ToTargetAddr/Addr/read_address; util/stream.rs gains the read_exact!/ready! macros (de-exported to pub(crate) use) + ConnectError + to_reply_error. SocksServerError lands verbatim in server.rs early — SocksError's #[from] needs it before the typestate task. 16 first-party codec tests (round-trips, mapping tables, garbage-RSV/unknown-ATYP); no sockets, no env_logger. Newer-clippy style lints allowed at module level in vendored files (verbatim diff posture, rationale recorded). Verification: cargo test (16 pass) + --all-features; clippy --all-targets -D warnings (native + wasm32); cargo check --target wasm32-unknown-unknown; cargo fmt --check; cargo doc. Negative sweeps: no anyhow/log/tokio::net/socket2/tokio-stream/ async-trait under src/vendor/; no #[macro_export]; resolve_dns and the pruned error variants absent. |
||
|
|
57b107bdf0 |
vendor: crate scaffolding — Cargo.toml, lib.rs, vendor skeleton, license notice
- Cargo.toml: alksocks 0.1.0 in the sibling posture (alktunnels-style exclude/keywords/description); empty `local` + `composed` features declared (ADR-009 §Amendment — populated by their modules' tasks); deps = tokio wasm-clean subset (rt/sync/io-util/macros/time), tracing, thiserror; full tokio dev-deps. No alkcall, no net-shaped deps (the producer/consumer tasks pin alkcall 0.8.0). - src/lib.rs: forbid(unsafe_code), crate docs (never-binds posture, vendor provenance, architecture pointer), `pub mod vendor;`. - src/vendor/: skeleton preserving upstream fast-socks5 v1.0.0 file names for mechanical per-file diffs — mod.rs (provenance doc + server/client/util declarations), five stubs for the extraction tasks; socks4/ cut per ADR-011 §Amendment. - src/vendor/LICENSE: upstream MIT text + v1.0.0 attribution header, referenced from the vendor module doc (ADR-011's license obligation; upstream has no per-file headers). - tasks/implementation/vendor-crate-scaffolding.md: completed (checkboxes, notes, summary). Verification: cargo test; clippy --all-targets -- -D warnings; fmt --check; cargo check + clippy --target wasm32-unknown-unknown with -D warnings — all clean; wasm gate green from birth. |
||
|
|
2cc010cf2b |
tasks: decompose the vendor group — six dependency-ordered tasks
The inline vendor (vendor.md, ADR-011 §Amendment) is the root of the crate's dependency graph; this decomposes it before any downstream group (params/wire/producer/consumer/doors). - vendor-crate-scaffolding: Cargo.toml (wasm-clean tokio subset, no alkcall yet), lib.rs, vendor/ skeleton, bundled MIT notice - vendor-wire-vocabulary: consts, codecs, errors, macros — with the extraction-boundary pins (error-variant pruning, pub command codecs, macro de-exporting) - vendor-server-typestate: Socks5ServerProtocol + the new_reply publication (carried change #2); BIND's driving is producer-group work, the vendor carries only the surface - vendor-client-session: Socks5Stream::use_stream path + the AsyncRead/AsyncWrite impls; Config pruned (connect_timeout cut); tests against a raw-byte mock server so it runs parallel with the typestate task - vendor-differential-tests: RFC edge-case suite over duplex streams, pinning upstream behavior per vendor.md's differential posture - review-vendor-extraction: the gate before downstream groups — file-by-file diff vs /workspace/fast-socks5 must contain only cut/carried-change/adjustment hunks Graph validated: all dependency ids resolve, no cycles; topological order scaffolding → wire → (typestate ∥ client) → tests → review. |
||
|
|
b0cfc26065 |
docs: pin the vendor scope as extraction — the net gate dissolves
Complete the ADR-011 §Amendment (extraction, not wholesale-copy-plus- gate) across every doc that stated the pre-amendment posture: - ADR-011: add the two extraction-recipe exclusions the amendment missed (the socks4 module — upstream's separate default-off cargo feature, out of RFC 1928 scope; the legacy Config/Authentication auth layer — ungated but consumed only by the cut region) and the dependency-surface adjustments (log -> tracing, anyhow dropped, async-trait dropped with its legacy-layer uses) - ADR-009: amend — the vendor's net gate dissolves; features collapse to local + composed; local carries tokio/net + socket2 itself - vendor.md: replace the contradictory old "four carried changes" section with the two-change reading + extraction-time adjustments; extend the cut table (socks4 row, legacy auth layer) - overview.md: dependency diagram + feature table drop the net feature; module map reflects two carried changes - README.md: principles 7/8 + doc/ADR tables updated - ADR-002/012, open-questions.md (OQ-SK-04), phase-0.md current-state sections, AGENTS.md conventions 4/17 + stale wasm-caveat sentence, POC findings posture notes: aligned with the extraction Verification: repo-wide grep for stale "four carried"/default-on net/ implies-net statements returns only amendment-context matches; extraction recipe re-verified against POC #4's gate map and the fast-socks5 v1.0.0 source (async-trait usage confined to the cut legacy layer; AuthMethod<T> traits are native async-fn-in-trait). |
||
|
|
9587670138 |
docs: Phase 1 architecture — specs, ADRs 001-012, open-questions tracker
- docs/architecture/: README index, overview, wire, producer, consumer, vendor, bast specs + open-questions.md (the OQ-SK-01..09 ledger promoted; new questions as OQ-SK2-06..) - ADRs 001-012: ALPN/op/params, protocol base, the handle swap, the dial-callback shape, auth split by the door, access control (open gate + BIND command gate), BIND in base, provider defaults, feature gates, channel capacity, the inline vendor, target policy - deferred OQs (SK2-06, SK2-07) with tracker tasks in tasks/architecture/ per the sdd_process deferred-OQ protocol - review pass applied: frame-length joint bound (65766 corner), accept_no_auth vs skip_auth corrected against fast-socks5 source, composed-feature wasm posture, cross-reference fixes Verified: cross-references (all ADR/OQ refs resolve), doc sizes under split threshold, Phase 0 fidelity spot-checks |
||
|
|
3044abe296 |
docs: consolidate phase-0 — current-state form, one statement per decision
The Phase 0 doc had accumulated stratified updates (each revision layered on top of the prior text), leaving duplicated statements, superseded reasoning presented as live, and a few internal conflicts. This rewrite is a consolidation only — no decisions changed: - Each OQ opens with a status line; resolved content stated as settled fact; superseded reasoning retained only where it carries Phase 1 ADR weight (e.g. OQ-SK-04's decision tree as the reasoning trail). - The inline-vendor decision now stated once (OQ-SK-04 + the Convergence Layering bullet) instead of ~10 restatements; stale 'file the upstream ask' residue removed (Convergence protocol-base bullet). - The component-3 transfer to alknet stated once in §Vision; the noq section is background-only; OQ-SK-06 compressed to its resolution. - OQ-SK-01/03's duplicated direct-op update merged into its OQ; OQ-SK-08's superseded sequencing consistently framed as the retired POC workaround against alktunnels 0.1.0. - Checklist: OQ-SK-03/06/07 and the POC box checked to match their own text; 'RFC-refused in both POCs' corrected (fast-socks5 refuses BIND; POC #5 implemented it). - Preserved: all external anchors cross-referenced by the findings files (§OQ-SK-07 register + numbering, §Prior art's run_udp_proxy_custom caveat, OQ-SK-04's decision record, the Convergence Layering bullet), every ADR/API/file:line reference, POC numbers, and measurement figures. Restored on audit: the 65507 u16-suffices rationale, skip_auth_this_is_not_rfc_compliant, lib.rs:91, the wasm-resolver interaction note. Verified: anchor audit against all cross-referencing docs (no dangling references), content-loss audit vs the prior version (no unsuperseded fact dropped), fast-socks5 v1.0.0 spot-checks (TCPBind refusal sites, wait_on_tcp:1195, private new_reply, typestate methods — all true). |
||
|
|
cced3b2b85 |
docs: correct fast-socks5 posture — third-party upstream, inline vendor decided
The ownership premise ('we own fast-socks5') was wrong — it is
third-party upstream and has not been forked. The corrected posture
resolves OQ-SK-04: inline vendor. The wrapper's needs exceed the wasm
gate regardless (BIND absent upstream — POC #5 proved the wrapper
drives it; the run_udp_proxy_custom bind seam; private new_reply), so
carrying the T-generic subset (~120 cfg lines, POC #4-verified) pays
for itself independent of the adapter story.
AGENTS.md:
- convention 17 rewritten: 'Upstream: alkcall is ours to shape;
fast-socks5 is inlined' — the ownership split stated plainly, the
inline vendor + its four carried changes named, the courtesy PR
kept optional/never-waited-on, the vendor point (v1.0.0) noted
- convention 4: wasm posture note updated to the resolved vendor
- prior-art entry: fast-socks5 marked third-party/NOT ours
phase-0.md:
- OQ-SK-04 RESOLVED (2026-09-20): resolution note headers the OQ (the
two settling facts: wrong ownership premise; needs exceed the gate);
decision tree retained as reasoning trail with both legs annotated
(Case 2's premise falsified by the BIND need); the root question
reframed as retrospective; checklist item checked
- §Prior art fast-socks5: header + run_udp_proxy_custom caveat
reworded from 'fileable ask' to in-tree change
- settled-foundations wasm caveat resolved; survey + checklist
prior-art entries marked; 'What Phase 0 could NOT answer' wasm item
struck as resolved; convergence Layering bullet now enumerates the
four in-tree carried changes; upstream-asks bullet: fast-socks5
items are no longer asks
poc-connect-wasm-findings.md / poc-bind-findings.md /
iroh-socks5-eval.md: 'we own it' ask sections corrected to
in-tree-vendor changes (dangling fragment from an earlier edit also
fixed); ledger item 3 annotated
Verified: fast-socks5 upstream still 1.0.0, no net feature, BIND
refused (lib.rs:384-386/server.rs:813-816) — the needs-are-real facts
stand. Docs-only change; no build surface affected.
|
||
|
|
cb553ea033 |
docs: record the upstream graduation landing (alkcall 0.8.0, alktunnels 0.2.0)
The ask ledger's two alktunnels items landed 2026-09-18/20 — superseded in shape, not implemented as posed. The graduation (alktunnels tunnels-graduation.md -> ADRs 007/008) adopted the ssh forwarded-tcpip-shaped alternative over the per-ask carriers. phase-0.md: - settled-foundations: establishment gains the reply-projection note (ADR-049 amendment 3); the relay/hub note records the in-tree ChannelRelay/HubLegTemplate (ADR-051); identity-seam note marked current at 0.8.0 - OQ-SK-01: Option A updated — the direct op (channels/tunnel/direct, ADR-007) is the dynamic-target channel; POC #5's scope-gate + ADR-007 rationale point the same way (separation lives at the ACL) - OQ-SK-03: composed egress's vehicle is open_direct + Substrate::Udp; the ~0.8 ms measurement carries over; the stale POC-candidate hunch paragraph updated - OQ-SK-08: the landed note — ask #1 dissolved (bind-first establisher; the real bound rides the open reply; POC #5's open-rides-the-accept workaround + the 10s-bound finding retire), ask #2 landed at the wire's native moment (forwarded op's {listen, substrate, peer} params), correlation token per ADR-008 Amendment 1 - convergence: egress seam composes against 0.2.0; the upstream-asks bullet records the landing per crate; the BIND hand-off bullet notes variant 2 is now fully vanilla-fidelity - checklist OQ-SK-08 item + POC #5 register entry point at the landed note poc-bind-findings.md: ledger statuses appended (superseded in shape; evidence stands as the alktunnels-0.1.0 record) AGENTS.md: alkcall pin 0.7.1 -> 0.8.0 (reply projection noted); architecture context: alktunnels v0.2.0 entry added, alkcall 0.8.0 surface listed Verified against the repos: alkcall 0.8.0 changelog (U-1/U-2/ADR-051), alktunnels 0.2.0 changelog + ADR-007/008 + exports (bindfirst_establisher producer.rs:641, register_tunnel_bindfirst_ openable :924), fast-socks5 still 1.0.0 with no net feature (the courtesy tail). Docs-only change; no build surface affected. |
||
|
|
cb4d58a2bf |
docs: POC #5 (channels-native SOCKS5 BIND) findings
POC #5 run 2026-09-15 (alksocks-bind-poc, standalone, 9 tests) — OQ-SK-08 POC-validated; the POC register (OQ-SK-07) is closed. - The BIND phase model extends end to end on one channel: request → reply#1 (BND.ADDR) → quiet wait → one inbound accept → reply#2 (peer) → pump_bidi pass-through (0 B overhead, like CONNECT) - Both accept variants implemented: local (real TcpListener, faithful RFC) + composed (alktunnels listen-tunnel — per-listener ACL for free, hub-relayable) - The address-first-vs-accept-first deadlock resolved in shape: reply#1 first, the tunnel open rides the accept future - The socks5:bind scope gate distinguishes ingress from egress (RFC-faithful ConnectionNotAllowed refusal; CONNECT unaffected) - Vanilla front door re-validated for the BIND conversation (raw pipe, zero translation); CONNECT + ASSOCIATE regression-guarded on the same registration — the RFC 1928 command surface is complete - fast-socks5: zero BIND ask (typestate-driven; reply_success carries reply#1); courtesy ask: publish the private new_reply encoder - alktunnels ask ledger pinned at two items for the one-version bump: (1) listen-addr inspection without consuming an accept, (2) accepted handles carrying peer metadata — both POC-evidenced, both additive Verification: cargo test (9/9), clippy --all-targets -D warnings, fmt --check clean in alksocks-bind-poc. |
||
|
|
aac63d17fc |
docs: phase-0 consistency pass — POC register + client-wrapper transfer residue
- OQ-SK-07: add a POC register table (one-place accounting of all five numbered POCs: #1/#2/#4 passed, #3 dropped, #5 pending); mark the priority order as historical; note numbering must not be renumbered - §Vision: fix the components 1+2 layout sentence now that component 3 transferred; keep the transfer reasoning - alknet-client ancestor section: replace the stale 'this crate's client half rehomes that work' sentence with a historical note (the rehoming is alknet's, per the 2026-09-14 scope decision) - noq prior-art section + OQ-SK-06: add transfer banners so ~25 lines of transferred research are not read as live scope - OQ-SK-02: spell out what the 'yes / planes-don't-compare / probably-not' shorthand maps to - OQ-SK-04: note the component-3 transfer does not change the wasm decision inputs - OQ-SK-07 #3: rewrite the ambiguous 'at the time of the decision' parenthetical as before/after - Survey list: mark noq and iroh entries as alknet-facing background - Convergence checklist: uncheck the POC item (BIND POC still pending) and explain the checkbox semantics Verified against the repo: findings files, POC crates (alksocks-connect-poc 8 tests, alksocks-udp-poc 11 tests, alksocks-fs5-wasm-poc exists), upstream line refs (fast-socks5 lib.rs:384-386 / server.rs:813-816 / 1195, noq runtime/mod.rs:44 / endpoint.rs:162, alktunnels producer.rs:207,392), op name channels/socks5/sub in both POC crates. Verification: docs-only change; no build/test surface affected. |
||
|
|
601afa3922 |
docs: BIND use case found (OQ-SK-08, POC #5) + capacity OQ-SK-09
Post-POC #2 findings review: a real BIND use case and mechanism surfaced, plus a channel-capacity analysis for proxy workloads. - OQ-SK-08: fast-socks5 parses but refuses TCPBind — the wrapper owns the whole shape (zero upstream ask). Phase-model extension: reply#1 (BND.ADDR) -> quiet wait -> one inbound accept -> reply#2 (peer) -> raw pass-through; iroh-socks5's channels-native BIND is convergent precedent. Accept variants mirror OQ-SK-03's egress variants: feature-gated local listener (faithful RFC) vs composed alktunnels listen-tunnel (needs a listen-addr-inspection ask) vs wrapper-aware only. Security note: BIND is inbound egress — SOCKS5_OPEN_SCOPE must distinguish it from CONNECT/ASSOCIATE. - OQ-SK-09: 256-channel default vs proxy fan-out; wire change rejected, assembly-layer config + documented proxy-workload recommendation is the mechanism; in-channel port multiplexing ([port:u16] per chunk) considered and rejected (re-opens OQ-SK-03's demux-vs-phase trade, breaks 0-B pass-through, AGENTS.md #10). - OQ-SK-05 addendum: virtual address space / identity-scoped subnets adoptable with no wire change — DST.ADDR/BND.ADDR translation via the dial callback; hub exposure via terminate-and-re-produce. - POC #5 (BIND, both accept variants, vanilla front-door test) added to OQ-SK-07; checklist + convergence updated; upstream-asks list pinned (fast-socks5: net gate + UDP bind seam; alktunnels: listen-addr inspection). Verification: docs-only change (phase-0.md). |
||
|
|
6c71c0e5d2 |
docs: Phase 0 converged — scope pinned to socks+channels, recommended approach written
- Scope resolution: the quinn/noq AsyncUdpSocket client wrapper (component 3) transfers to the alknet rewrite (its ancestor work — ADR-090, quinn-proxy POC — lives there; it consumes no channels and has no consumers planned here). OQ-SK-06 and would-be POC #3 go with it. alksocks = the producer/consumer pair only. - Convergence section added (Phase 0's output for the Architect): the validated approach pieces (fast-socks5 typestate driven directly for both commands, the handle-swap wire shape, DialFn + EgressFactory egress seam, "both" auth split by the door, alktty/alktunnels layering, vendored-subset fork posture) and the list of Phase 1 ADR decisions with their collected inputs (datagram framing, demux vs phase, params/ALPN, scope convention, door mapping, wasm posture). - Checklist: OQ-SK-06 resolved-by-transfer; POC list complete (#1/#2/#4 passed, #3 dropped); convergence checked off. |
||
|
|
c77c28c28e |
docs: POC #2 (channels-native UDP ASSOCIATE) findings
- alksocks-udp-poc (/workspace/alksocks-udp-poc/): 11 tests pass, clippy -D warnings + fmt clean; the handle-swap baseline validated end to end (one channel per association, [len][addr+data] frames in-band, RSV/FRAG only at front doors, channel EOF = association lifetime, per-datagram addressing without a flow table) - Both egress variants implemented behind the EgressFactory callback; composed per-destination alktunnels udp-tunnel open cost measured at ~0.8 ms mean (N=20, in-process loopback) — comfortable for DNS + browsing - Front-door RFC-codec composition confirmed (RFC UDP header codec at the vanilla boundary only); CONNECT regression-guarded on the same registration - Load-bearing API findings for Phase 1: the select!-driven stage's egress readiness contract (Pending-when-idle; readable() not writable(); closed queues end, never spin) and the length framing applied exactly once by the wire layer (egress returns inner bytes) Verification: cargo test (11 pass), cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo doc --no-deps (all clean in the POC crate); phase-0.md OQ-SK-03/07 statuses updated. |
||
|
|
921aaf3a9a |
docs: qualify the 256-channel figure — default policy knob, not a wire limit
channel_id is u32 on the wire (~4B ids, monotonic counter, ADR-040); 256 is the default per-connection max_channels (memory bound, 256x1MiB reassembly buffers) and the default per-identity DoS cap (ADR-041) — both configurable without a wire change. AGENTS.md convention 10 and the OQ-SK-03 composed-egress note now carry the qualifier. |
||
|
|
4730672f10 |
docs: OQ-SK-02 direction settled — "both" auth, split by the door
- endgame story makes both mechanisms real: consumer binds the produced SOCKS5 resource locally (optional front door, ssh -D shape without the forced bind) and points tun2proxy at it - producer side: identity seam only (CF-005/CF-006); a password there is moot against the ACL system — in-band conversation runs NoAuth - consumer front door: owns RFC 1929 where vanilla clients need it (tun2proxy/curl user:pass), maps accepted credentials to the channel identity it opens the producer with; the SOCKS password never crosses the channel as a SOCKS password - replace-or-second-gate sub-question dissolves: the mechanisms occupy different planes (door-local creds vs channel-level identity) - remaining for Phase 1: credential->identity mapping shape; producer- side local backend door surface; AuthMethod trait vs door-owned check |
||
|
|
c3ccba4d59 |
docs: OQ-SK-03 handle swap — the relay handle is the channel itself
- the ASSOCIATE reply's BND.ADDR/BND.PORT is a relay handle, not a
destination (per-datagram destinations ride the datagram header);
swapping "tell the client about a UDP port" for "tell the client
about the UDP tunnel" preserves every RFC property: one channel per
association, {addr, data} frames in-band, channel EOF = association
lifetime, optional front-door binds on either side
- sentinel-address hunch superseded: no virtualized relay address is
ever needed on the in-band path; BND.ADDR there is pure
RFC-compatibility surface
- producer egress has two variants, both dial-callback policy (OQ-SK-01
shape): local UdpSocket (no flow table, native-only) or composed
per-target alktunnels udp-tunnels (lazy destination->tunnel table;
flow table reborn as channel handles; per-target ACL for free —
dissolves most of OQ-SK-05 for the composed path)
- no upstream ask: alktunnels' connected-udp substrate is correct as-is
for per-target composition; an unconnected-egress resource would be
SOCKS5 again (circular)
- POC #2 baseline settled: sketch both egress variants behind the dial
callback, measure per-destination open cost of the composed variant
|
||
|
|
cf5432209a |
docs: iroh-socks5 evaluated — not the base, but OQ-SK-03 prior art
- new docs/research/iroh-socks5-eval.md: full source review of iroh-socks5 0.5.0 (github.com/mattgeddes/iroh-socks5, MIT OR Apache-2.0) — quality verified, structural sibling of the alksocks shape, but iroh-coupled (no generic-T seam) with an RFC subset, so fast-socks5 stays the protocol base (POC #1/#4 conclusions stand) - phase-0: prior-art section for iroh-socks5; OQ-SK-03 updated with its UDP design as the POC #2 baseline (framed {addr, data} in-band datagrams, RFC header codec at front doors only, no producer-side flow table, control-EOF association lifetime, FRAG != 0 refused) |
||
|
|
7c6bc65618 |
docs: POC #1 (channels-native CONNECT) + POC #4 (fs5 wasm fork) findings
- POC #1 passed (alksocks-connect-poc, 8 tests + curl front-door example): BiStream-as-T genericity, register_openable_with_ establisher fit, pump_bidi data plane, typed session refusal, RFC reply-code dial-refusal mapping, vanilla-client front door. Structural resolution: the dial belongs at the command-read interception point (target is unknowable at open time); the establisher is the session gate. OQ-SK-01/02 shapes resolved empirically. - POC #4 passed (fs5-wasm-poc, kept at /workspace/alksocks-fs5-wasm-poc): the fork is genuinely minimal — ~120 lines of #[cfg] insertions gate kernel-net behind a default-on 'net' feature; the wasm-clean subset compiles for wasm32-unknown-unknown (--no-default-features) and still completes a full RFC 1928 CONNECT conversation. OQ-SK-04 reduces to the adapter-story question. - Corrected the run_udp_proxy_custom prior-art claim: it binds a kernel socket unconditionally (relay closure + reply IP only); the channels path drives the typestate directly. Fileable ask. - Findings: docs/research/poc-connect-wasm-findings.md |
||
|
|
a628715106 |
docs: OQ-SK-04 restructured as the conditional decision tree it is
The root question is 'does wasm make sense for alksocks?', not 'how do we get wasm': Case 1 (wasm wanted) -> fork-first, PR as courtesy, carry regardless; Case 2 (wasm unwanted) -> plain dependency on the router.rs native path, no fork (the alkhttp precedent, strictly better when its premise holds). Decision inputs written: (a) is there a planned sandboxed (wasm) consumer of the SOCKS5 service, (b) is the fork genuinely minimal (POC #4 checks). Fork-first is a conditional preference, not a general one — do not carry a fork for an invariant's sake. AGENTS.md conventions 4/17 aligned. |
||
|
|
766e489f99 |
docs: three-component split; fork-first (vendor-and-vet) posture; consumer-side bind
- phase-0 vision: components split — (1) producer half (expose the SOCKS5 service over channels), (2) consumer half (consume it, optionally bind it locally — the ssh -D front door + tun2proxy path), (3) standalone SOCKS client wrapper for quinn/noq that must work with any RFC 1928 server (iroh relay/peer IP-leak motivation, alknet ADR-090 generalized); the earlier docs conflated the consumer-side bind with the producer-side local backend - principle 6: the vpn-like endgame — tunnel a produced SOCKS5 resource to a local port, point tun2proxy (--proxy socks5:// natively) at it; verified tun2proxy src/args.rs - OQ-SK-04 resolved as fork-first: 'fork' = vendor-and-vet inline in the crate (minimal diff, feature-gate native-net), PR upstream as courtesy, never wait on approval; wasm-drop (alkhttp precedent) is the escape hatch, not the plan - AGENTS.md conventions 4/9/17 aligned (fork-first, binds on either side, wasm posture) |
||
|
|
216a5e965e |
docs: phase-0 — UDP phase model vs alktty-style demux; fork-vs-reimplement collapses onto OQ-SK-04
- OQ-SK-03: RFC 1928 control/data never interleave (fast-socks5 wait_on_tcp treats post-reply control bytes as garbage), so the alktty 5-stream demux solves a problem SOCKS5 doesn't have; phase model (pass-through CONNECT, len-prefixed datagrams post-reply) subsumes it; the demux's real advantage is an additive in-band stream-type vocabulary — noted as the honest one-way-door fork - OQ-SK-04: no structural fork needed for the channels surface (router.rs interception shape covers CONNECT; run_udp_proxy_custom + reply_success + the pure UDP codec cover ASSOCIATE); the fork/wrap/reimplement decision is driven entirely by the wasm feature-gating outcome; full fork documented as least attractive |
||
|
|
4c641ea9c0 |
docs: repo init — gitignore, AGENTS.md, Phase 0 draft
- .gitignore (target/, node_modules/, .worktrees/, Cargo.lock) - AGENTS.md: project conventions adapted from alktunnels for the SOCKS5 wrapper crate (no-forced-binding posture, fast-socks5 genericity, producer/consumer vocabulary, upstream-asks rule, verification commands with the wasm caveat for OQ-SK-04) - docs/research/phase-0.md: Phase 0 draft — vision, prior art (fast-socks5 surface, alktunnels -D conclusion, alknet ADR-090 client, noq trait-shape divergence), open questions OQ-SK-01..07, POC candidates, convergence checklist - sdd_process.md: package name fixed (alkcall -> alksocks) |
||
|
|
169aa47790 | init |