Files
alknet/docs/architecture/decisions/003-iroh-stream-join.md
glm-5.1 dad8224686 Add architecture specification for wraith SSH tunnel tool
Docs:
- README.md: index with doc table, ADR table, lifecycle definitions
- overview.md: purpose, exports, dependencies, constraints
- transport.md: Transport trait, TCP/TLS/iroh implementations, stream join
- client.md: SOCKS5 server, port forwarding, channel manager, reconnection
- server.md: auth, channel handling, stealth mode, outbound proxy
- tun-shim.md: separate privileged process, virtual DNS, --unshare mode
- napi-and-pubsub.md: NAPI wrapper, pubsub event target adapter

ADRs:
- 001: Pluggable transport via AsyncRead+AsyncWrite trait
- 002: TUN shim as separate process
- 003: iroh stream via tokio::io::join
- 004: SSH runs over transport, not alongside
- 005: SOCKS5 as primary interface, TUN as add-on
- 006(007): NAPI exposes single duplex stream

Open questions: 11 items covering TLS certs, iroh relay defaults,
Windows TUN, auth expansion, NAPI surface, TCP reconstruction
2026-06-01 15:01:45 +00:00

31 lines
1.9 KiB
Markdown

# ADR-003: iroh Stream via tokio::io::join
## Status
Accepted
## Context
iroh's QUIC implementation provides separate `RecvStream` (implements `AsyncRead`) and `SendStream` (implements `AsyncWrite`) for each bidirectional channel opened via `open_bi()` / `accept_bi()`. russh's `connect_stream()` and `run_stream()` require a single type implementing both `AsyncRead` and `AsyncWrite`.
Options considered:
1. `tokio::io::join(recv, send)` — Combines the two halves into `Join<RecvStream, SendStream>` which implements both traits.
2. Custom `IrohStream` wrapper — A struct with `recv` and `send` fields that delegates `AsyncRead` to `recv` and `AsyncWrite` to `send`.
3. Using iroh's `Connection` directly — Opening a new `open_bi()` for each SSH channel instead of running SSH over a single stream.
## Decision
Use `tokio::io::join(recv_stream, send_stream)` (Option 1).
One line of code, correct trait implementations, no custom types needed. The `Join<A, B>` type implements `AsyncRead` using `A` and `AsyncWrite` using `B`, which maps directly to iroh's split stream model.
If profiling later shows overhead (unlikely — it's just method dispatch), we can switch to a custom wrapper. But YAGNI until demonstrated.
Option 3 was rejected because it would require modifying russh to understand iroh connections. The whole point of the transport trait is that SSH doesn't know about iroh.
## Consequences
- **Positive**: Minimal code. One line to bridge iroh and russh.
- **Positive**: No custom types to maintain.
- **Positive**: Correct `AsyncRead` + `AsyncWrite` behavior — `Poll::Pending` on one half doesn't affect the other.
- **Negative**: None identified. The `Join` type is a standard tokio combinator with well-tested semantics.
## References
- [transport.md](../transport.md)
- [Feasibility assessment §11](../../../../conversations/research/ssh-tunnel-vpn-alternative-feasibility.md)