tunnelctl
Concepts

The FRP transport

How the embedded FRP client carries traffic, and why tunnelctl forks frp.

Under the hood, tunnelctl moves traffic with FRP (fast reverse proxy). FRP carries all three tunnel protocols — HTTP/HTTPS via its http/https proxies (routed by hostname) and raw TCP/UDP via its tcp/udp proxies (a dedicated edge port). What's unusual is that the FRP client is embedded in the CLI — there's no separate frpc process to install or configure, and no .toml file is written to disk. The CLI builds the client config in memory and runs the FRP service in-process.

The path of a request

The embedded FRP client opens an encrypted control channel to the tunnel edge (FRP server) — by default over wss on port 443.
It registers the proxy using the tunnel's connection token — an HTTP proxy bound to <slug>.intunnel.eu, or a TCP/UDP proxy bound to a remote port (tcp.intunnel.eu:<port>).
A server-side plugin validates the token with the control plane (signature, serial, protocol + slug/port) before allowing the proxy.
Public traffic to that endpoint — HTTP requests by hostname, or raw TCP/UDP to the remote port — is routed down the control channel and forwarded to your local host:port.

Tuning

A couple of up http flags map onto FRP behaviour (HTTP tunnels only):

  • --pool-count pre-allocates work connections so the first requests don't pay a connection-setup cost.
  • --host-header-rewrite rewrites the outgoing Host header, which matters for local apps that route by virtual host.

Why a fork

Upstream frpc exposes no signal for control-channel connect/disconnect events. tunnelctl maintains a small fork that adds a login-state callback, so the per-tunnel daemon can detect when the control channel drops and transition to Reconnecting without polling. The fork tracks upstream closely and is published under the git.nubeo.eu/microservices/frp module path.

Daemon state from FRP

The connect/disconnect callback is what drives the ConnectedReconnecting states you see in tunnelctl status.

On this page