Rename all crates, CLI commands, constants, type names, doc comments, and documentation from wraith to alknet. Includes wire-protocol changes: ALPN wraith-ssh -> alknet-ssh, reserved destination prefix wraith- -> alknet-, SSH auth username wraith -> alknet.
3.6 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |||
|---|---|---|---|---|---|---|---|---|---|---|
| client/channel-manager | Implement ChannelManager — SSH session management, channel opens, reconnection | done |
|
moderate | high | component | implementation |
Description
Implement the ChannelManager that owns the Arc<client::Handle<ClientHandler>> and provides the core client methods:
open_direct_tcpip(host, port)— open a tunnel channel to a remote hostopen_streamlocal(socket_path)— open a tunnel to a Unix socket (stub for now)request_tcpip_forward(addr, port)— request remote listeningcancel_tcpip_forward(addr, port)— cancel remote listening
Most importantly, the channel manager handles reconnection on transport failure:
- Detect via
handle.is_closed()or transport read error - Exponential backoff reconnect (1s, 2s, 4s, ... max 30s)
- Re-establish transport connection (call
transport.connect()again) - Re-authenticate SSH session
- Notify SOCKS5 server and port forwards (in-flight connections fail, new connections work)
Reconnection is always enabled. The backoff caps at 30 seconds and continues indefinitely.
Acceptance Criteria
crates/alknet-core/src/client/channel_manager.rsexportsChannelManagerChannelManagerholds:Arc<Transport>,Arc<ClientAuthConfig>,Arc<client::Handle<ClientHandler>>(behind RwLock for reconnection)ChannelManager::new()establishes initial transport connection, authenticates, returns manageropen_direct_tcpip(host, port)— opens SSH channel to targetrequest_tcpip_forward(addr, port)— sendstcpip_forwardrequestcancel_tcpip_forward(addr, port)— sendscancel_tcpip_forwardrequest- Reconnection detection: monitors
handle.is_closed(), triggers reconnect on failure - Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (cap), continues indefinitely
- Full reconnect: new transport stream, new SSH session over it (ADR-004)
- After reconnect: port forward listeners (
-L,-R) re-registered with new session - In-flight connections on old session fail gracefully (channel errors, not session-wide)
- Unit tests: channel open, reconnection trigger, backoff timing, forward re-registration
References
- docs/architecture/client.md — Channel Manager section, Reconnection section
- docs/architecture/decisions/004-ssh-over-transport.md — full reconnect, not "SSH reconnects over same transport"
Notes
- Converted
client.rs(single file) to directory module:client/mod.rs+client/channel_manager.rs - Used
russh::keys::PrivateKeyandrussh::keys::PublicKey(not the nonexistentrussh::key::KeyPair) - Reconnection monitor runs as a spawned tokio task that polls
handle.is_closed()every 1s - On reconnect: creates new transport stream + new SSH session (ADR-004 full reconnect)
ForwardRequeststruct tracks registered port forwards for re-registration after reconnect- In-flight channels on old session naturally fail with
ChannelError::ChannelClosedsince the handle is replaced
Summary
Implemented ChannelManager in crates/alknet-core/src/client/channel_manager.rs with SSH session management, channel opens (open_direct_tcpip), port forward requests (request_tcpip_forward/cancel_tcpip_forward), and automatic reconnection with exponential backoff (1s→30s cap). Full reconnect per ADR-004 creates new transport stream + new SSH session. Port forwards are re-registered after successful reconnect. 8 unit tests covering backoff timing, forward tracking, transport failure, and reconnection detection.