CHAN.RUN

Restunnel

Architecture

Restunnel uses a star topology. Each hub sits at the center with exit nodes connecting inbound. Hubs are independent — they don't know about each other.

Server A (Hetzner)                    Server B (AWS)
┌────────────────────────┐     ┌────────────────────────┐
│  Your Software         │     │  Your Crawler          │
│       │                │     │       │                │
│       ▼                │     │       ▼                │
│  Restunnel Hub A       │     │  Restunnel Hub B       │
│  socks5://localhost:1080│     │  socks5://localhost:1080│
└───────┬────────────────┘     └──────────┬─────────────┘
        │                                 │
        │    Encrypted tunnels            │
        │    (exit nodes connect out)     │
        │                                 │
   ┌────┴─────────────────────────────────┴────┐
   │                                            │
   ▼                                            ▼
📱 Phone (LTE)                          💻 Laptop (Home WiFi)
   Connected to: Hub A + Hub B              Connected to: Hub A

Components

Hub — a single binary on your server with three responsibilities:

  • Tunnel endpoint (:9000) — listens for exit node connections via Noise protocol. Silently drops unauthenticated connections. Multiplexes many TCP streams over a single tunnel per node.
  • Local proxy (:1080 SOCKS5, :1081 HTTP CONNECT) — binds to localhost only. Routes requests through connected exit nodes.
  • Dashboard (:8080) — binds to localhost only. Manage nodes and generate QR codes for pairing.

Exit nodes — apps on your personal devices. They connect outbound to hubs — they never listen on any port. Zero inbound attack surface.

Request Flow

Your Software              Hub                     Phone (Exit Node)
     │                      │                           │
     │ SOCKS5 CONNECT       │                           │
     │ example.com:443      │                           │
     │─────────────────────►│                           │
     │                      │  CONNECT example.com:443  │
     │                      │──────────────────────────►│
     │                      │                           │
     │                      │                           │ TCP connect
     │                      │                           │ to example.com
     │                      │                           │
     │                      │       CONNECT_OK          │
     │                      │◄──────────────────────────│
     │   SOCKS5 OK          │                           │
     │◄─────────────────────│                           │
     │                      │                           │
     │ TLS ClientHello      │         DATA              │
     │─────────────────────►│──────────────────────────►│──► example.com
     │                      │                           │
     │                      │         DATA              │
     │◄─────────────────────│◄──────────────────────────│◄── example.com
     │ TLS ServerHello      │                           │

The hub and exit node relay encrypted TLS bytes — they never see plaintext. The target website sees the phone's residential IP.

Pairing

  1. Hub generates a one-time enrollment token (valid 15 minutes)
  2. Token is shown as a QR code on the dashboard
  3. Exit node app scans the QR code
  4. Noise protocol handshake establishes mutual authentication
  5. Hub stores the node's public key — future reconnections use this key directly

After enrollment, the token is burned. Reconnections authenticate via cryptographic keypair. No passwords, no bearer tokens on the wire.

Protocol

Restunnel uses the Noise Protocol Framework (Noise_IK with X25519) instead of TLS. The key difference: TLS responds to any client's handshake (fingerprintable), while Noise requires the client to prove identity in the first message. Unauthorized clients get no response — the port appears closed.

Enrollment handshake: Node initiates Noise_IK → sends ENROLL with one-time token → hub validates, stores key, burns token → sends ENROLL_OK.

Reconnection: Node initiates Noise_IK with stored keys → hub checks authorized list → tunnel established (or silent drop if unknown).

Message Format

After the handshake, the encrypted channel carries binary-framed messages:

┌──────────┬──────────┬───────────┬──────────────┐
│ Type (1B)│ ID (4B)  │ Len (4B)  │ Payload      │
└──────────┴──────────┴───────────┴──────────────┘
TypeNameDirectionPurpose
0x01ENROLLNode → HubOne-time enrollment
0x02ENROLL_OKHub → NodeEnrollment accepted
0x03/0x04PING/PONGBothKeepalive (30s interval, 10s timeout)
0x10CONNECTHub → NodeOpen TCP connection to target
0x11/0x12CONNECT_OK/FAILNode → HubConnection result
0x20DATABothRaw TCP data for a stream
0x21CLOSEBothClose a stream
0x30STATUSNode → HubPeriodic status (IP, battery, bandwidth)

The ID field multiplexes many TCP streams over a single tunnel — one exit node can handle hundreds of concurrent connections.

Security

The security model assumes the hub will be installed and forgotten for months.

Hub is invisible — unauthenticated connections are silently dropped. No error response, no banner. Port scanners see nothing. Even if a vulnerability exists, it's only exploitable by someone with a valid device key.

No open relay — every proxy connection requires an authenticated exit node. No node connected = no traffic flows.

Exit node protection:

  • Outbound only — never listens on any port
  • Verifies hub identity cryptographically
  • Blocks private IP ranges (RFC1918, RFC6598, loopback, link-local) — the hub cannot reach devices on the exit node's local network. DNS resolution happens on the exit node, preventing hostname tricks.

Hub protection:

  • All services (proxy, dashboard) bind to 127.0.0.1
  • No configuration option to expose them without explicit override

Authentication:

  • Enrollment tokens: 256-bit random, 15-minute TTL, single-use
  • Post-enrollment: Noise_IK handshake with X25519 static keys — no bearer tokens
  • Revocation: delete the key, immediate effect, no propagation delay

Key storage:

PlatformStorageHardware-Backed
AndroidAndroid Keystore (AES-256-GCM wrapped)Yes
macOSKeychainYes (Apple Silicon / T2)
Linux<data-dir>/node_key.bin (chmod 600)No