rusnel 0.9.1

Rusnel is a fast TCP/UDP tunnel, transported over and encrypted using QUIC protocol. Single executable including both client and server
Documentation
# Rusnel

[![Crates.io](https://img.shields.io/crates/v/rusnel.svg)](https://crates.io/crates/rusnel)

## Description
Rusnel is a fast TCP/UDP tunnel, transported over and encrypted using QUIC protocol. Single executable including both client and server. Written in Rust.


## Features
-   Easy to use
-   Single executable including both client and server.
-   Uses QUIC protocol for fast and multiplexed communication.
-   Encrypted connections using the QUIC protocol (TLS 1.3).
-   Static forward tunneling (TCP, UDP)
-   Static reverse tunneling (TCP, UDP)
-   Dynamic tunneling (socks5, including UDP ASSOCIATE)
-   Dynamic reverse tunneling (reverse socks5, including UDP ASSOCIATE)
-   Layered peer authentication: insecure, fingerprint pinning, or full mTLS
    (see [Authentication]#authentication).



## Install
```bash
cargo install rusnel
```

### or

Clone the repository and build the project:
```bash
git clone https://github.com/guyte149/Rusnel.git
cd rusnel
cargo build --release
```

## Usage
```bash
$ rusnel --help
A fast tcp/udp tunnel

Usage: rusnel <COMMAND>

Commands:
  server  run Rusnel in server mode
  client  run Rusnel in client mode
  cert    generate certificates for use with --tls-* flags
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version
```

```bash
$ rusnel server --help
run Rusnel in server mode

Usage: rusnel server [OPTIONS]

Options:
      --host <HOST>          defines Rusnel listening host [default: 0.0.0.0]
  -p, --port <PORT>          defines Rusnel listening port [default: 8080]
      --allow-reverse        Allow clients to specify reverse port forwarding remotes
      --allow-socks          Allow clients to specify SOCKS5 remotes. `R:socks`
                             additionally requires `--allow-reverse`.
      --insecure             Disable all TLS authentication (testing only)
      --tls-self-signed      Persisted self-signed cert under --tls-state-dir
      --tls-state-dir <DIR>  Directory for persisted self-signed cert/key (default: ~/.rusnel)
      --tls-cert <PATH>      Server PEM cert (paired with --tls-key)
      --tls-key  <PATH>      Server PEM key  (paired with --tls-cert)
      --tls-ca   <PATH>      Enable mTLS: require client certs signed by this CA
      --congestion <CC>      QUIC congestion controller: cubic (default) or bbr.
                             cubic wins on loopback / clean LANs; bbr wins on
                             high-BDP / lossy WAN links (≳25ms RTT or any loss).
  -v, --verbose              enable verbose logging
      --debug                enable debug logging
  -h, --help                 Print help
```

```bash
$ rusnel client --help
run Rusnel in client mode

Usage: rusnel client [OPTIONS] <SERVER> <remote>...

Arguments:
  <SERVER>     defines the Rusnel server address (in form of host:port)
  <remote>...
               <remote>s are remote connections tunneled through the server, each which come in the form:

                   <local-host>:<local-port>:<remote-host>:<remote-port>/<protocol>

                   ■ local-host defaults to 0.0.0.0 (all interfaces).
                   ■ local-port defaults to remote-port.
                   ■ remote-port is required*.
                   ■ remote-host defaults to 0.0.0.0 (server localhost).
                   ■ protocol defaults to tcp.

               which shares <remote-host>:<remote-port> from the server to the client as <local-host>:<local-port>, or:

                   R:<local-host>:<local-port>:<remote-host>:<remote-port>/<protocol>

               which does reverse port forwarding,
               sharing <remote-host>:<remote-port> from the client to the server\'s <local-host>:<local-port>.

                   example remotes

                       1337
                       example.com:1337
                       1337:google.com:80
                       192.168.1.14:5000:google.com:80
                       socks
                       5000:socks
                       R:2222:localhost:22
                       R:socks
                       R:5000:socks
                       1.1.1.1:53/udp
                       [::1]:80
                       [::1]:5000:[2001:db8::1]:80
                       R:[::1]:2222:[::1]:22
                       stdio:example.com:22

                   IPv6 literals must be wrapped in [brackets] (same
                   convention as URLs and ssh -L).

                   When the Rusnel server has --allow-reverse enabled, remotes can be prefixed with R to denote that they are reversed.

                   Remotes can specify "socks" in place of remote-host and remote-port.
                   The default local host and port for a "socks" remote is 127.0.0.1:1080.

                   Remotes can specify "stdio" in place of <local-host>:<local-port>
                   to pipe the client process's stdin/stdout to/from the tunnel
                   instead of binding a local listener. Stdio remotes are
                   forward-only.


Options:
      --insecure                  Skip server cert verification (testing only)
      --tls-fingerprint <SHA256>  Pin server cert by SHA-256 fingerprint
      --tls-ca <PATH>             Verify server cert against this CA bundle
      --tls-cert <PATH>           Client PEM cert (mTLS; paired with --tls-key + --tls-ca)
      --tls-key  <PATH>           Client PEM key  (mTLS; paired with --tls-cert + --tls-ca)
      --tls-server-name <NAME>    Override SNI / verification name
      --congestion <CC>           QUIC congestion controller: cubic (default) or
                                  bbr. cubic wins on loopback / clean LANs; bbr
                                  wins on high-BDP / lossy WAN links.
      --max-retry-count <N>       Reconnect attempts after a disconnect or
                                  failed connect. -1 = retry forever (default);
                                  counter resets on every successful connect.
      --max-retry-interval <S>    Cap on the exponential reconnect backoff
                                  (default 300s; starts at 200 ms and doubles).
      --proxy <URL>               Route the QUIC connection through a SOCKS5
                                  proxy via UDP ASSOCIATE.
                                  Form: socks5://[user:pass@]host:port.
  -v, --verbose                   enable verbose logging
      --debug                     enable debug logging
  -h, --help                      Print help
```

The client survives both transient network drops and full server restarts
out of the box: on disconnect it logs the close reason (e.g.
`closed by peer: server received ^C (code 0)`), backs off, and tries every
resolved address in parallel using **RFC 8305 Happy Eyeballs** so v4-only
servers reachable via a v6-preferring resolver still connect within
~250 ms. Server-side resources (including reverse-tunnel listeners) are
released the moment the QUIC connection drops.

## Authentication

Both the server and the client require an explicit TLS-mode flag — there is
no silent insecure default. Three modes:

| Mode               | Server                                            | Client                                                     |
|--------------------|---------------------------------------------------|------------------------------------------------------------|
| Insecure           | `--insecure`                                      | `--insecure`                                               |
| Fingerprint pin    | `--tls-self-signed` (or `--tls-cert`/`--tls-key`) | `--tls-fingerprint sha256:...`                             |
| Full mTLS          | `--tls-cert ... --tls-key ... --tls-ca ...`       | `--tls-ca ... --tls-cert ... --tls-key ... [--tls-server-name ...]` |

Quickest path for a private/single-user setup — the server logs its
fingerprint at startup, the client pins it:

```bash
rusnel server --tls-self-signed
# server cert fingerprint: sha256:abcd...
rusnel client --tls-fingerprint sha256:abcd... 1.2.3.4:8080 1337
```

For full mTLS, generate a CA + server + client cert (no `openssl` required):

```bash
scripts/gen-certs.sh ./pki 1.2.3.4
rusnel server --tls-ca   ./pki/ca.pem --tls-cert ./pki/server.pem --tls-key ./pki/server.key
rusnel client --tls-ca   ./pki/ca.pem --tls-cert ./pki/client.pem --tls-key ./pki/client.key \
              --tls-server-name 1.2.3.4 1.2.3.4:8080 1337
```

`rusnel cert --help` lists the underlying subcommands (`ca`, `server`,
`client`, `fingerprint`) for finer control.

### Embedded credentials (drop-and-run binaries)

Rusnel can bake credentials and the default invocation into the binary
at **build time** via `RUSNEL_EMBED_*` env vars. The resulting binary
connects, authenticates, and starts forwarding the moment it's run —
no flags, no subcommand, no config file. CLI flags still override
embedded values, so `--help` and ad-hoc subcommands keep working.

```bash
RUSNEL_EMBED_CA=./pki/ca.pem \
RUSNEL_EMBED_CLIENT_CERT=./pki/client.pem \
RUSNEL_EMBED_CLIENT_KEY=./pki/client.key \
RUSNEL_EMBED_SERVER_NAME=1.2.3.4 \
RUSNEL_EMBED_ARGS='client 1.2.3.4:8080 R:2222:localhost:22' \
cargo build --release
./target/release/rusnel               # connects + opens reverse tunnel
```

Recognised vars: `RUSNEL_EMBED_ARGS`, `RUSNEL_EMBED_SERVER_ADDR`,
`RUSNEL_EMBED_FINGERPRINT`, `RUSNEL_EMBED_SERVER_NAME`,
`RUSNEL_EMBED_CA`, `RUSNEL_EMBED_SERVER_CERT`, `RUSNEL_EMBED_SERVER_KEY`,
`RUSNEL_EMBED_CLIENT_CERT`, `RUSNEL_EMBED_CLIENT_KEY`. See
[`build.rs`](build.rs) for the full mapping.

## Logging

Rusnel uses [`tracing`](https://docs.rs/tracing) end-to-end with structured
fields and stable span hierarchy.

**Verbosity** (mutually exclusive):

```bash
rusnel server ...                  # INFO  (default)
rusnel server -v ...               # DEBUG for rusnel modules
rusnel server --debug ...          # TRACE for rusnel modules
rusnel server -q / --quiet ...     # WARN-and-above only
```

For finer control set `RUST_LOG` directly — it overrides the flags:

```bash
RUST_LOG=rusnel=debug,quinn=info rusnel server ...
RUST_LOG=rusnel::common::tcp=trace rusnel client ...
```

**Format**: human-readable compact (default) or one JSON object per line for
log aggregators:

```bash
rusnel server --log-format json ...
```

Logs go to **stderr** (so forward `stdio:` tunnels can use stdout cleanly).
Timestamps are ISO-8601 UTC with millisecond precision; ANSI colours are
auto-detected from the stderr TTY.

Every event is wrapped in spans whose field names match the
`rusnel ctl` / admin-API ID schema (`client_id`, `tunnel_id`,
`conn_id`, …), so logs grep cleanly against API output. Every conn
emits a `conn opened` event on entry and a `conn closed` event on exit
carrying `bytes_in`, `bytes_out`, and `dur_ms`.

Example session (compact format, ANSI stripped):

```
2026-05-05T11:03:55.022Z  INFO client: connected client_id=1 peer=127.0.0.1:62178
2026-05-05T11:03:55.027Z  INFO client: session established count=1
2026-05-05T11:03:55.027Z  INFO client: tunnel registered tunnel_id=1 dir="forward" spec=19999=>127.0.0.1:9999/tcp
2026-05-05T11:03:55.670Z  INFO conn: conn opened conn_id=1 tunnel_id=1 peer="127.0.0.1:9999"
2026-05-05T11:03:55.671Z  INFO conn: conn closed bytes_in=12 bytes_out=17 dur_ms=1
```

## Server admin API & `rusnel ctl`

The server exposes a read-only admin HTTP API on a unix domain socket
**by default** at `~/.rusnel/admin.sock` (mode `0600`). Filesystem
permissions are the only auth — only the user that started the server
can connect. Pass `--admin-socket <path>` to override the path or
`--no-admin-socket` to disable.

### Terminology: client, tunnel, conn

State is modelled in three layers, each with one meaning:

- A **client** is one connected `rusnel client` daemon. Lives for the
  lifetime of the QUIC connection.
- A **tunnel** is the *remote declaration* a client established with
  the server (`R:5000=>socks`, `1080=>1.1.1.1:53/udp`, …).
  Deduplicated per client by spec; lives as long as the client.
- A **conn** is a single proxied network connection going through a
  tunnel — one accepted TCP connection, one per-source UDP flow, one
  SOCKS5 CONNECT, one SOCKS5 UDP target.

Query the API with `rusnel ctl` (or `curl --unix-socket`):

```bash
rusnel ctl clients                       # tab-aligned table by default
rusnel ctl client 3                      # client detail incl. its tunnels
rusnel ctl client-conns 3                # active conns across all of client 3's tunnels
rusnel ctl tunnels                       # every tunnel across every client
rusnel ctl tunnel 7                      # tunnel detail + its active conns
rusnel ctl tunnel-conns 7                # just the conns on tunnel 7
rusnel ctl conns --json                  # every active conn, raw JSON
rusnel ctl history --limit 20            # recent client disconnects
```

`ctl` defaults to the same socket path the server uses, so the
zero-flag pairing just works.

The available endpoints (`GET` only in this release):

| Path                                  | Purpose                                                       |
|---------------------------------------|---------------------------------------------------------------|
| `/api/v1/server`                      | version, listen addr, uptime, client/tunnel/conn counts       |
| `/api/v1/clients`                     | one row per connected client with rolled-up totals            |
| `/api/v1/clients/:id`                 | client detail, with embedded tunnel summaries                 |
| `/api/v1/clients/:id/tunnels`         | tunnels owned by one client                                   |
| `/api/v1/clients/:id/conns`           | active conns across all of one client's tunnels               |
| `/api/v1/tunnels`                     | every tunnel across every client                              |
| `/api/v1/tunnels/:id`                 | tunnel detail with its active conns embedded                  |
| `/api/v1/tunnels/:id/conns`           | just the active conns on one tunnel                           |
| `/api/v1/conns`                       | every active conn globally                                    |
| `/api/v1/conns/:id`                   | one conn by id                                                |
| `/api/v1/history?limit=N`             | bounded ring buffer (256) of recent disconnects               |

Write operations (kick client, kill conn), Prometheus `/metrics`, and
an embedded web UI are tracked as phase-2 follow-ups in the TODO
section.

## Performance

Rusnel (QUIC) vs [Chisel](https://github.com/jpillora/chisel) (SSH-over-WebSocket)
on loopback. Throughput is iperf3 over a tunneled TCP forward
(100 MB × 5 runs + warmup, median); latency is the round-trip time of a
64 B echo across the tunnel.

![Throughput](benchmark/iperf/results/loopback/throughput.png)
![Latency](benchmark/iperf/results/loopback/latency.png)

End-to-end HTTP request times through the tunnel across payload sizes
(median of 5 runs, error bars show min/max):

![HTTP through tunnel](benchmark/chisel-bench/results/loopback/chisel-bench.png)

The benchmark harness also includes a `wan` profile that applies
`tc qdisc netem delay 25ms` to the loopback interface to approximate a
50 ms-RTT WAN. Reproduce everything with `./benchmark/run.sh`
(requires Docker; needs `--cap-add=NET_ADMIN` for netem profiles, which
the script adds for you). See [`benchmark/`](benchmark/) for tunables.

## TODO

### Reliability & UX
- [x] client reconnect with exponential backoff (configurable via `--max-retry-count` / `--max-retry-interval`)
- [x] proxy support for client: `--proxy socks5://[user:pass@]host:port` routes the QUIC connection through a SOCKS5 proxy via UDP ASSOCIATE (RFC 1928 §4). HTTP CONNECT is intentionally not supported in this release because it cannot carry UDP — see the WebSocket-fallback transport item under `Security & access control` for the path that would unlock HTTP/SOCKS-CONNECT proxies.
- [x] **production-grade structured logging**: `tracing-subscriber` with `EnvFilter` (`RUST_LOG=rusnel=debug,quinn=info`), `--quiet` / `-q`, `--log-format compact|json`, ISO-8601 UTC timestamps, ANSI colours auto-detected. Stable span hierarchy `client{client_id,peer}``tunnel{tunnel_id,dir,spec}``conn{conn_id,tunnel_id,peer}` (matching the `rusnel ctl` ID schema), and every conn emits a structured close summary with `bytes_in`, `bytes_out`, `dur_ms`. See "Logging" below.

### Protocol features
- [ ] add fake-backend http/3 feature to server (real HTTP/3 facade for active probes that open streams)
- [ ] skip the 1-RTT control handshake on static forwards (cache the parsed `RemoteRequest` server-side; saves ~1 RTT per accepted TCP connection on WAN)
- [ ] enable QUIC 0-RTT connection resumption (session-ticket cache; cuts the first request after `rusnel client` startup from ~3 RTT to ~1 RTT)
- [ ] UDP hole-punching / NAT traversal mode: introduce a `rusnel broker` role that observes each peer's reflexive address (optionally cross-checked against public STUN servers to detect symmetric NAT) and brokers a direct QUIC connection between two NATed peers à la libp2p DCUtR / Tailscale DERP, with relay fallback when punching fails. Lets two devices behind NAT talk without anyone running a publicly-reachable data-plane server.

### Security & access control
- [ ] server-side remote ACLs: `--allow` / `--deny` flags (and config-file equivalents) accepting wildcarded `RemoteRequest` patterns, e.g. `--allow socks`, `--allow R:2222:localhost:22`, `--deny tcp:*:*:169.254.169.254:*`. Default-deny dangerous targets like cloud instance-metadata endpoints. With mTLS, bind ACLs to the client cert subject / fingerprint so a contractor cert can be scoped to one tunnel.
- [ ] SSO / OIDC client auth via a `rusnel-issuer` daemon: client runs `rusnel client --sso https://issuer.corp.example`, completes a device-code flow against the org's IdP (Okta/Google/Auth0), and the issuer mints a short-lived (~8h) mTLS client cert with the user's email/groups in the SAN. Rusnel server only needs to trust the issuer's CA — no IdP knowledge in the data path. ACLs from the bullet above match on cert subject/groups.

### Operability
- [x] **server admin API (read-only) + CLI**: typed `ServerState` (DashMap of clients/tunnels/conns with cumulative + per-conn byte counters), HTTP admin API on a unix socket gated by filesystem perms (mode 0600), three-layer client/tunnel/conn model exposed via `rusnel ctl clients|client|client-conns|tunnels|tunnel|tunnel-conns|conns|history|server`. See "Server admin API & `rusnel ctl`" above.
- [ ] **server admin API — phase 2**: `DELETE /clients/:id` (kick), `DELETE /tunnels/:id` (kill tunnel), `GET /metrics` (Prometheus exporter), optional TCP+mTLS transport so the API is reachable over the network with the same PKI as the tunnel control plane.
- [ ] **embedded web UI**: tiny `include_str!`'d HTML file (no JS framework) with a client/tunnel dashboard and bandwidth sparklines off `/metrics`.

### Testing & CI
- [ ] run `./benchmark/run.sh` on a self-hosted runner per release tag and commit the result PNGs back, so perf regressions surface in PRs