Server admin API + ctl, then session-hello wire refactor (v0.7.0 + v0.8.0) - #39
Merged
Conversation
Phase-1 of the long-tracked admin observability item: the server now
exposes a read-only HTTP API over a unix domain socket so operators can
list connected clients, the tunnels each client declared, the live conns
going through every tunnel, and per-tunnel / per-conn byte counters.
A new `rusnel ctl` subcommand wraps the API for the shell.
State model is three layers, each with one meaning: a *client* is one
connected `rusnel client` daemon; a *tunnel* is the remote declaration
that client established (deduped per client by spec, atomically via a
DashMap entry index); a *conn* is one proxied network connection going
through a tunnel (one accepted local TCP, one per-source UDP flow, one
SOCKS5 CONNECT, one SOCKS5 UDP target). Tunnels accumulate cumulative
counters as conns close; conns expose live counters plus a free-form
`peer` label.
Admin socket defaults to `~/.rusnel/admin.sock`, mode 0600. Override
with `--admin-socket <path>`; disable with `--no-admin-socket`. Routes:
`GET /api/v1/{server,clients,clients/:id,clients/:id/{tunnels,conns},
tunnels,tunnels/:id,tunnels/:id/conns,conns,conns/:id,history}`.
`rusnel ctl` subcommands mirror the routes: `server`, `clients`,
`client <id>`, `client-conns <id>`, `tunnels`, `tunnel <id>`,
`tunnel-conns <id>`, `conns`, `history`. Tab-aligned tables by default,
`--json` for the raw payload, `--socket <path>` to override.
Implementation:
- `src/server/state.rs` — `ServerState` (DashMaps of clients/tunnels/
conns, ring-buffered disconnect history), `TunnelHandle` for reverse
handlers, `ConnGuard` RAII for per-conn registration, DTOs.
- `src/server/admin.rs` — axum router served over hyper http1 on a
`tokio::net::UnixListener`; bind tightens permissions to 0600.
- `src/common/counted.rs` — `TunnelCounters` (atomic in/out) and
`CountedReader` to bump bytes_in zero-copy on the TCP path; UDP /
SOCKS5 paths bump per datagram.
- `src/ctl/mod.rs` — minimal HTTP/1 client + table renderer (no
dependency on a full HTTP client).
- Reverse handlers (`tunnel_tcp_client`, `tunnel_udp_client`,
`tunnel_socks_client`) take `TunnelHandleOpt` and register a conn
per accept / per-source / per-CONNECT.
- Forward handlers stay session-shaped (one bi-stream = one conn).
Phase-2 follow-ups (kick client, kill conn, Prometheus `/metrics`,
embedded web UI) are tracked in the README TODO section.
Tests: `tests/admin.rs` covers socket perms (0600), tunnel/conn dedup
under concurrency, and the cumulative-vs-active counter split. Existing
tests updated for the new tunnel-handler signatures.
Co-authored-by: Cursor <cursoragent@cursor.com>
The 0.7 wire protocol re-sent a full `RemoteRequest` on every
data-plane bi-stream and used an `announce_only` band-aid to make
forward tunnels visible in admin state before traffic flowed. Replace
both with chisel's session-config model: one `SessionHello` carrying
every declared remote at connect time, the server validates the whole
batch against `--allow-reverse` / `--allow-socks` and assigns one
`tunnel_id` per declaration, then every subsequent bi-stream opens
with a tiny `OpenConn { tunnel_id, dynamic }` frame. SOCKS5 dynamic
targets ride along as `OpenConn::dynamic` under their parent SOCKS
tunnel — no more `from_socks` flag.
This is a breaking wire change (0.8 client cannot talk to a 0.7
server). Forward SOCKS5 with `--allow-socks=false` is now rejected at
hello time rather than per-CONNECT, so the client's local SOCKS
listener no longer binds in that configuration; test updated.
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related commits on this branch:
066a9ee— Add server admin API +rusnel ctl(read-only, v0.7.0). Phase-1 of the long-tracked operability item: a read-only HTTP API on a unix socket (~/.rusnel/admin.sock, mode 0600) exposing live clients, tunnels, conns, per-tunnel byte counters, and a bounded disconnect-history ring buffer. Newrusnel ctlsubcommand wraps the API for the shell. Three-layer client / tunnel / conn data model inServerState+ DTOs.6c80f95— Replace per-streamRemoteRequestwith session hello +OpenConn(v0.8.0). Wire-protocol overhaul that follows chisel's session-config model. The 0.7 protocol re-sent a fullRemoteRequeston every data-plane bi-stream and used anannounce_onlyband-aid for forward-tunnel admin visibility; both are gone. Clients now declare every tunnel up front inSessionHello, server validates the batch and assignstunnel_ids, every subsequent bi-stream opens with a tinyOpenConn { tunnel_id, dynamic }frame. SOCKS5 dynamic targets ride along asOpenConn::dynamicunder the parent SOCKS tunnel, removing thefrom_socksflag.Breaking
RemoteRequest::announce_only/from_socksanddynamic_tcp/dynamic_udpconstructors removed.ServerConfiggains anadmin_socket: Option<PathBuf>field (0.7).tunnel_id: u64argument (0.8) and aCountersargument (0.7).--allow-socks=falsenow fails the entire session at hello time instead of binding a local listener and rejecting per-CONNECT.Highlights
/api/v1/{server,clients,clients/:id,clients/:id/{tunnels,conns},tunnels,tunnels/:id,tunnels/:id/conns,conns,conns/:id,history}endpoints.rusnel ctl {server,clients,client,client-conns,tunnels,tunnel,tunnel-conns,conns,history}with table or--jsonoutput.--no-admin-socketflag to opt out;--admin-socket <path>to override.find_or_create_tunnel+tunnel_indexdedup hack collapsed into a simple bulkregister_tunnels— the protocol no longer needs runtime dedup.Test plan
cargo build --releasecargo fmt --all -- --checkcargo clippy --all -- -D warningscargo test --all— full suite passes (admin, auth, combinations, concurrent, disconnect_cleanup, edge_cases, ipv6, large_transfer, proxy, reconnect, tunnels)rusnel server --admin-socket /tmp/r.sock+rusnel client --remote 1080:google.com:80+rusnel ctl --socket /tmp/r.sock tunnelsshows the tunnel before and after pushing trafficMade with Cursor