CLAUDE.md · joaoh82/rustunnel
CLAUDE.md from joaoh82/rustunnel: 251 lines covering What is rustunnel, Parent project, Common Commands, Workspace Structure and more.
Template by joaoh82
Install
Works with Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/joaoh82/rustunnel/main/CLAUDE.md -o CLAUDE.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: code-generation
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
A CLAUDE.md file: project memory Claude Code loads at the start of every session.
- For
- Claude Code
- Length
- 251 lines
- File
CLAUDE.md
Sections
- What is rustunnel
- Parent project
- Common Commands
- Workspace Structure
- Architecture
- Databases
- Configuration
- Observability
- Repository Conventions
- CI
- Knowledge Base
First lines (shown under the repo's AGPL-3.0 license)
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What is rustunnel
rustunnel is a self-hosted secure tunnel server written in Rust (similar to ngrok). It exposes local services through a public server via encrypted WebSocket connections with TLS termination, HTTP/TCP/UDP proxying, P2P direct mode (NAT hole punching via STUN/QUIC), a live dashboard, Prometheus metrics, and audit logging.
## Parent project
**Parent project:** [rustunnel](/Users/joaoh82/projects/rustunnel/CLAUDE.md) — read for shared dev commands, env vars, cross-service architecture, **and the global working guidelines** (never-touch-main, task management, pull-request registration, reserved ports, engineering defaults). Those guideline sections live only in the parent `CLAUDE.md` — this file does not duplicate them.
### How I fit in
This crate (`rustunnel/`) is the **core tunnel server + CLI client + MCP server**. Siblings in the parent meta-repo:
| Sibling | Stack | Dev port | Role |
|---------|-------|----------|------|
| `rustunnel-web/` | Rust (Axum) + Next.js 15 | 3001 / 3000 | Platform API + public website + marketing pages (registration, JWT, billing) |
| `rustunnel-admin-dashboard/` | Next.js 14 | 3002 | Internal admin UI |
| `rustunnel-private/` | Markdown / Bash / Ansible | — | Ops docs, provisioning playbooks |
| `docs/` | Mintlify (MDX) | 3000 | Public documentation |
| `homebrew-rustunnelcli/` | Ruby | — | Homebrew tap formula |
| `rustunnel-status/` | Upptime (YAML + Actions) | — | `status.rustunnel.com`, probes every edge |
This server shares its **PostgreSQL database** with `rustunnel-web/platform-api` (the `tokens` table is the auth boundary). Edge servers (`eu`, `us`, `ap`) all run this binary; `platform-api` runs on a separate VPS at `api.rustunnel.com`.
## Common Commands
```bash
# Build
cargo build --workspace
make build
make build-full # rebuild dashboard UI (Next.js export) + server crate (use after UI changes)
# Release build (optimized binaries: rustunnel-server, rustunnel-client, rustunnel-mcp)
make release
# Test (requires PostgreSQL — start first)
make db-start
make test
# Or manually:
TEST_DATABASE_URL=postgres://rustunnel:test@localhost:5432/rustunnel_test cargo test --workspace
# Stop test DB
make db-stop
# Lint & format
make fmt # cargo fmt --all
make lint # cargo clippy --workspace --all-targets -- -D warnings
make check # fmt check + clippy (mirrors CI)
# Run a single test
TEST_DATABASE_URL=postgres://rustunnel:test@localhost:5432/rustunnel_test cargo test <test_name> -p <crate_name>
# Install pre-push hooks (run once)
make install-hooks
```
### Local Development
```bash
# One-time TLS cert in /tmp/rustunnel-dev (re-run after reboot — tmpfs is wiped)
make dev-setup
# Run server (uses deploy/local/server.toml: self-signed cert, require_auth = false)
cargo run -p rustunnel-server -- --config deploy/local/server.toml
# Run client — HTTP, TCP, UDP
cargo run -p rustunnel-client -- http 3000 --server localhost:4040 --token dev-secret-change-me --insecure
cargo run -p rustunnel-client -- tcp 5432 --server localhost:4040 --token dev-secret-change-me --insecure
cargo run -p rustunnel-client -- udp 27015 --server localhost:4040 --token dev-secret-change-me --insecure
# Dashboard UI (Next.js, dev mode — proxies API to localhost:8443)
make ui-install # first time
make ui-dev
```
## Workspace Structure
Four crates in `crates/`, plus integration tests and a Next.js dashboard (`dashboard-ui/`) whose static export is embedded into the server binary:
| Crate | Purpose |
|-------|---------|
| `rustunnel-protocol` | Shared `ControlFrame` enum + `TunnelProtocol` (Http/Https/Tcp/Udp/P2p) and serialization |
| `rustunnel-server` | Server: control plane, HTTP/TCP/UDP edges, dashboard, TLS, ACME |
| `rustunnel-client` | CLI client with auto-reconnect, local port forwarding, P2P/STUN, region auto-select, Ratatui terminal UI (`src/tui/`) and a loopback request inspector (`src/inspect/`) |
| `rustunnel-mcp` | MCP server exposing rustunnel as tools to AI agents |
## Architecture
### High-Level Data Flow
```
Internet → HTTP/TCP/UDP Edge → TunnelCore (router) → Control Plane → yamux stream → Local service
↘ P2P direct (QUIC, NAT-punched) ↗
```
The server spawns concurrent tasks for each subsystem:
- **Control Plane** (`:4040`) — WebSocket server for client connections; handles auth, tunnel registration, `NewConnection` delivery, P2P signaling
- **HTTP/HTTPS Edge** (`:80` / `:443`) — TLS-terminated reverse proxy; routes by subdomain
- **TCP Edge** (`:20000+`) — Raw TCP proxy; routes by assigned port
- **UDP Edge** (configurable port range) — UDP proxy; routes by assigned port
- **Dashboard** (`:8443`) — REST API + embedded Next.js SPA + per-instance SQLite request capture
- **Prometheus Metrics** (`:9090`)
- **ACME Renewal** (background, optional — Let's Encrypt + Cloudflare DNS-01)
### Connection Handoff (Core Mechanism)
1. HTTP/TCP/UDP edge receives inbound connection → registers a `pending_conn` (oneshot channel) in `TunnelCore` with a `conn_id`
2. Control plane sends `NewConnection { conn_id, protocol }` to the relevant client over WebSocket
3. Client opens a new yamux stream for that `conn_id`
4. Server resolves the oneshot, unblocking the edge task
5. Edge task and client stream data bidirectionally through yamux
### P2P Direct Mode
For peer-to-peer tunnels (`TunnelProtocol::P2p`), the server acts as a rendezvous/signaling broker only — bytes do not flow through it once the punch succeeds:
1. Publisher registers a named tunnel with a `secret_hash`; reports its NAT type and STUN-mapped addresses (`P2pNatInfo`)
2. Subscriber sends `P2pConnect { target_tunnel_name, secret_hash }`
3. Server uses `classify_nat_pair` to pick a hole-punching strategy and exchanges peer addresses via P2P frames
4. Both peers establish a direct **QUIC** connection (via `quinn`) — server is out of the data path
5. Falls back to relayed mode when NAT classification rules out direct connectivity
Client implementation: `crates/rustunnel-client/src/p2p_direct.rs`, `stun.rs`. Server-side state: `P2pPublisher` in `core/router.rs`.
### TunnelCore (`crates/rustunnel-server/src/core/router.rs`)
Central shared state, `Arc`-wrapped and passed to all subsystems:
```rust
pub struct TunnelCore {
http_routes: DashMap<String, Arc<TunnelGroup>>, // subdomain → group of members
tcp_routes: DashMap<u16, Arc<TunnelGroup>>, // port → group of members
udp_routes: DashMap<u16, Arc<TunnelGroup>>, // port → group of members
p2p_tunnels: DashMap<String, P2pPublisher>, // name → P2P publisher
sessions: DashMap<Uuid, SessionInfo>, // connected clients
available_tcp_ports: Mutex<Vec<u16>>, // free TCP port pool
available_udp_ports: Mutex<Vec<u16>>, // free UDP port pool
tunnel_index: DashMap<Uuid, TunnelKey>, // tunnel_id → key (O(1) removal)
pending_conns: DashMap<Uuid, oneshot::Sender>, // conn_id → yamux resolver
tcp_events: broadcast::Sender<TcpTunnelEvent>,
udp_events: broadcast::Sender<UdpTunnelEvent>,
rate_limiter: Arc<RateLimiter>, // per-tunnel token bucket
ip_limiter: Arc<IpRateLimiter>, // per-source-IP sliding window
}
```
Each route key resolves to a `TunnelGroup` of one or more `GroupMember`s
(see `core/tunnel.rs`). Today every group has exactly one member — the route
shape is in place ahead of TUNNEL-7 group-based load balancing (`see
../rustunnel-private/docs/development/load-balancing-and-health-checks.md`).
`resolve_http` / `resolve_tcp` / `resolve_udp` pick a healthy member uniformly
at random; ungrouped registrations stay healthy for life. The
`[load_balancing] enabled` flag in `server.toml` is the kill switch for
honouring multi-member groups (defaults to `false`; flipped on per region
during the EU → US → AP rollout).
### Protocol (`crates/rustunnel-protocol/src/frame.rs`)Signals
- Stars
- 657
- Forks
- 48
- Open issues
- 0
- Contributors
- 4
- Last commit (this folder)
- Jul 31, 2026 (2 months ago)
- Last release
- Sep 3, 2026 (1 month ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- AGPL-3.0
- Repository
- github.com/joaoh82/rustunnel
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from joaoh82
Cite this page
CLAUDE.md · joaoh82/rustunnel. Agent Mart. https://agentmart.dev/templates/joaoh82-rustunnel-claude-md (last updated 2026-10-24)