Phase 3 of the crate extraction (per findings.md): create the alknet-client crate — the native client dial seam, client-side analogue of AlknetEndpoint. Three dial methods (dial_quic, dial_tcp_tls, dial_iroh) unified on &ConnectionCredentials (ADR-091), pre-built transports via builder methods, optional SOCKS5 proxy support (ADR-090). 9 tasks, 7 generations, no cycles: - client/crate-init: Cargo.toml, feature flags, module skeleton - client/error-type: ClientDialError enum (5 variants) - client/client-core: AlknetClient struct + builder methods - client/dial-quic: QUIC dial via quinn - client/dial-tcp-tls: TCP+TLS dial via tokio-rustls - client/dial-iroh: Iroh dial (key-not-config) - client/socks5-proxy: Socks5ProxyConfig, Socks5UdpSocket, proxy integration - client/tests: Unit tests + integration test - client/review-client: Review checkpoint Depends on: tls/review-tls, endpoint/review-endpoint (both completed). Purely additive — old CallClient::connect stays until Phase 5 prune.
7.4 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | ||
|---|---|---|---|---|---|---|---|---|---|
| client/crate-init | Initialize alknet-client crate with Cargo.toml, dependencies, and module skeleton | pending |
|
moderate | low | project | implementation |
Description
Phase 3, Task 1 of the crate extraction (per docs/research/alknet-crate-extraction/findings.md).
Initialize the alknet-client crate from scratch. This crate provides the native client dial
seam — AlknetClient, the client-side analogue of AlknetEndpoint. A multi-transport dialer
that takes pre-built transport handles (quinn, TCP+TLS, iroh), dials a remote AlknetEndpoint
on a chosen ALPN, and produces a Connection for the protocol take-overs to consume.
The dial is the client-side mirror of the server-side accept loop (ADR-083): one type that
takes pre-built transport handles and produces a Connection, with the transport choice as a
parameter. The protocol take-overs (CallClient::spawn_dispatch, ChannelClient::from_connection)
are unchanged — they consume the Connection and do not know AlknetClient produced it.
Crate setup
Create crates/alknet-client/ with:
Cargo.toml— package metadata, dependencies, feature flagssrc/lib.rs— crate root with module declarations and re-exports- Module skeleton files for:
src/error.rs—ClientDialErrorenum (5 variants:TlsConfig,Connect,Handshake,NoTransport,Proxy)src/client.rs—AlknetClientstruct,new, builder methods (with_quinn,with_tcp_tls,with_iroh,with_socks5_proxy)src/dial/quinn.rs—dial_quicimplementation (feature-gated onquinn)src/dial/tcp_tls.rs—dial_tcp_tlsimplementation (feature-gated ontcp)src/dial/iroh.rs—dial_irohimplementation (feature-gated oniroh)src/dial/mod.rs— dial module declarationssrc/socks5.rs—Socks5ProxyConfig,Socks5Credentials,Socks5UdpSocket, HTTP-to-SOCKS5 bridge (feature-gated onsocks5)
Dependencies
Per the findings (Phase 3) and the architecture spec:
| Crate | Purpose |
|---|---|
alknet-core |
Connection, ConnectionCredentials, RemoteIdentity, Ed25519SecretKey, types (workspace path) |
alknet-tls |
TlsClientConfig — for quinn + tcp dials (workspace path) |
quinn 0.11 |
QUIC transport (optional, feature-gated) |
tokio-rustls 0.26 |
TCP+TLS transport (optional, feature-gated) |
iroh 1.0 |
Iroh transport (optional, feature-gated) |
fast-socks5 1 |
SOCKS5 client (optional, feature-gated — ADR-090) |
tokio 1 (full) |
Async runtime, TcpStream, spawn |
thiserror 2 |
ClientDialError |
alknet-client depends on alknet-tls (for TlsClientConfig) and alknet-core (for
Connection, ConnectionCredentials, RemoteIdentity, and types). It does not depend
on alknet-call or alknet-channels-call — the dial is below the protocol.
Feature flags
[features]
default = []
quinn = ["dep:quinn", "alknet-tls/quinn", "alknet-core/quinn"] # dial_quic
tcp = ["dep:tokio-rustls", "alknet-tls/tcp"] # dial_tcp_tls
iroh = ["dep:iroh", "alknet-core/iroh"] # dial_iroh
socks5 = ["dep:fast-socks5"] # proxied dial paths (ADR-090)
The quinn and tcp features pull the corresponding features on alknet-tls (for
TlsClientConfig::for_quinn / for_tcp_tls). The quinn and iroh features also pull
the corresponding features on alknet-core — dial_quic produces a Connection via
Connection::from_quinn_with_alpn and dial_iroh via Connection::from_iroh, both of
which live in alknet-core's types.rs behind core's quinn / iroh features.
The socks5 feature (ADR-090) is independent of the transport features — it enables the
proxy code path that dial_quic (UDP ASSOCIATE) and dial_tcp_tls (CONNECT) use when a
proxy is configured. The fast-socks5 dep is behind socks5, so deployments that don't
use a proxy don't pay the dep.
Workspace Cargo.toml
Add crates/alknet-client to the workspace members list in the root Cargo.toml.
Module skeleton
// src/lib.rs
//! alknet-client: Native client dial seam — multi-transport dialer that
//! produces `Connection`s for protocol take-overs.
//!
//! `AlknetClient` is the client-side analogue of `AlknetEndpoint`: a
//! multi-transport dialer that takes pre-built transport handles (quinn,
//! TCP+TLS, iroh), dials a remote `AlknetEndpoint` on a chosen ALPN, and
//! produces a `Connection`. The protocol take-overs
//! (`CallClient::spawn_dispatch`, `ChannelClient::from_connection`)
//! consume the `Connection` — the dial is below the protocol.
//!
//! An optional SOCKS5 proxy (ADR-090) routes the dials through a proxy
//! to hide the client's real IP from the hub.
pub mod client;
pub mod dial;
pub mod error;
#[cfg(feature = "socks5")]
pub mod socks5;
// Re-exports (filled in by subsequent tasks)
Each module file gets a doc comment and // TODO: implement marker.
Acceptance Criteria
crates/alknet-client/Cargo.tomlexists with all dependencies and feature flagscrates/alknet-client/src/lib.rsexists with module declarations- Module skeleton files exist:
error.rs,client.rs,dial/mod.rs,dial/quinn.rs,dial/tcp_tls.rs,dial/iroh.rs,socks5.rs - Root
Cargo.tomlmemberslist includescrates/alknet-client cargo check -p alknet-clientsucceedscargo clippy -p alknet-clientsucceeds with no warnings- Dual licensing:
MIT OR Apache-2.0(workspace-inherited) alknet-coredependency uses workspace path (path = "../alknet-core")alknet-tlsdependency uses workspace path (path = "../alknet-tls")- No dependency on
alknet-call(dial is below the protocol) - Feature flags:
quinn,tcp,iroh,socks5(all optional, default off) quinnfeature pullsalknet-tls/quinn+alknet-core/quinntcpfeature pullsalknet-tls/tcpirohfeature pullsalknet-core/irohsocks5feature pullsdep:fast-socks5only (no alknet feature deps)cargo build --workspacestill succeeds (old code untouched)
References
- docs/research/alknet-crate-extraction/findings.md — Phase 3
- docs/architecture/crates/client/README.md — full architecture spec
- docs/architecture/decisions/089-alknetclient-native-dial-seam.md — ADR-089
- docs/architecture/decisions/090-client-dial-socks5-proxy-seam.md — ADR-090
- docs/architecture/decisions/091-connectioncredentials-decouple-dial-from-call.md — ADR-091
- crates/alknet-tls/Cargo.toml — reference for dep versions and feature flag pattern
- crates/alknet-endpoint/Cargo.toml — reference for builder-method pattern
- crates/alknet-call/src/client/call_client.rs — reference
connect()implementation (lines 142-168)
Notes
This is the foundational setup task for alknet-client. All subsequent client/* tasks depend on this one. The crate depends on
alknet-tls(forTlsClientConfig) andalknet-core(forConnection,ConnectionCredentials,RemoteIdentity). It does NOT depend onalknet-call— the dial is below the protocol. The oldCallClient::connectinalknet-callis intentionally still present (duplicated) — the prune happens in Phase 5. Thesocks5feature andfast-socks5dep are opt-in; deployments that don't use a proxy pay nothing.
Summary
To be filled on completion