Technical Documentation: rathole-org/rathole
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
rathole
A secure, stable and high-performance reverse proxy for NAT traversal, written in Rust
rathole, like frp and ngrok, can help to expose the service on the device behind the NAT to the Internet, via a server with a public IP.
Features
- High Performance Much higher throughput can be achieved than frp, and more stable when handling a large volume of connections. See Benchmark
- Low Resource Consumption Consumes much fewer memory than similar tools. See Benchmark. The binary can be as small as ~500KiB to fit the constraints of devices, like embedded devices as routers.
- Security Tokens of services are mandatory and service-wise. The server and clients are responsible for their own configs. With the optional Noise Protocol, encryption can be configured at ease. No need to create a self-signed certificate! TLS is also supported.
- Hot Reload Services can be added or removed dynamically by hot-reloading the configuration file. HTTP API is WIP.
Quickstart
A full-powered rathole can be obtained from the release page. Or build from source for other platforms and minimizing the binary. A Docker image is also available.
The usage of rathole is very similar to frp. If you have experience with the latter, then the configuration is very easy for you. The only difference is that configuration of a service is split into the client side and the server side, and a token is mandatory.
To use rathole, you need a server with a public IP, and a device behind the NAT, where some services that need to be exposed to the Internet.
Assuming you have a NAS at home behind the NAT, and want to expose its ssh service to the Internet:
- On the server which has a public IP
Create server.toml with the following content and accommodate it to your needs.
# server.toml
[server]
bind_addr = "0.0.0.0:2333" # `2333` specifies the port that rathole listens for clients
[server.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to an arbitrary value.
bind_addr = "0.0.0.0:5202" # `5202` specifies the port that exposes `my_nas_ssh` to the InternetThen run:
./rathole server.toml- On the host which is behind the NAT (your NAS)
Create client.toml with the following content and accommodate it to your needs.
# client.toml
[client]
remote_addr = "myserver.com:2333" # The address of the server. The port must be the same with the port in `server.bind_addr`
[client.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know" # Must be the same with the server to pass the validation
local_addr = "127.0.0.1:22" # The address of the service that needs to be forwardedThen run:
./rathole client.toml- Now the client will try to connect to the server
myserver.comon port2333, and any traffic tomyserver.com:5202will be forwarded to the client's port22.
So you can ssh myserver.com:5202 to ssh to your NAS.
To run rathole run as a background service on Linux, checkout the systemd examples.
Configuration
rathole can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of [server] and [client] block is present, like the example in Quickstart.
But the [client] and [server] block can also be put in one file. Then on the server side, run rathole --server config.toml and on the client side, run rathole --client config.toml to explicitly tell rathole the running mode.
Before heading to the full configuration specification, it's recommend to skim the configuration examples to get a feeling of the configuration format.
See Transport for more details about encryption and the transport block.
Here is the full configuration specification:
/* Detailed source-code truncated for AI context efficiency. */Logging
rathole, like many other Rust programs, use environment variables to control the logging level. info, warn, error, debug, trace are available.
RUST_LOG=error ./rathole config.tomlwill run rathole with only error level logging.
If RUST_LOG is not present, the default logging level is info.
Tuning
From v0.4.7, rathole enables TCP_NODELAY by default, which should benefit the latency and interactive applications like rdp, Minecraft servers. However, it slightly decreases the bandwidth.
If the bandwidth is more important, TCP_NODELAY can be opted out with nodelay = false.
Benchmark
rathole has similar latency to frp, but can handle a more connections, provide larger bandwidth, with less memory usage.
For more details, see the separate page Benchmark.
However, don't take it from here that rathole can magically make your forwarded service faster several times than before. The benchmark is done on local loopback, indicating the performance when the task is cpu-bounded. One can gain quite a improvement if the network is not the bottleneck. Unfortunately, that's not true for many users. In that case, the main benefit is lower resource consumption, while the bandwidth and the latency may not improved significantly.
Planning
- HTTP APIs for configuration
Out of Scope lists features that are not planned to be implemented and why.
Overview (`examples/systemd/README.md`)
Systemd Unit Examples
The directory lists some systemd unit files for example, which can be used to run rathole as a service on Linux.
The @ symbol in the name of unit files such as[email protected] facilitates the management of multiple instances of rathole.
For the naming of the example, ratholes stands for rathole --server, and ratholec stands for rathole --client, rathole is just rathole.
For security, it is suggested to store configuration files with permission 600, that is, only the owner can read the file, preventing arbitrary users on the system from accessing the secret tokens.
With root privilege
Assuming that rathole is installed in /usr/bin/rathole, and the configuration file is in /etc/rathole/app1.toml, the following steps show how to run an instance of rathole --server with root.
- Create a service file.
sudo cp [email protected] /etc/systemd/system/- Create the configuration file
app1.toml.
sudo mkdir -p /etc/rathole
# And create the configuration file named `app1.toml` inside /etc/rathole- Enable and start the service.
sudo systemctl daemon-reload # Make sure systemd find the new unit
sudo systemctl enable ratholes@app1 --nowWithout root privilege
Assuming that rathole is installed in ~/.local/bin/rathole, and the configuration file is in ~/.local/etc/rathole/app1.toml, the following steps show how to run an instance of rathole --server without root.
- Edit the example service file as...
# with root
# ExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml
# without root
ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/%i.toml- Create a service file.
mkdir -p ~/.config/systemd/user
cp [email protected] ~/.config/systemd/user/- Create the configuration file
app1.toml.
mkdir -p ~/.local/etc/rathole
# And create the configuration file named `app1.toml` inside ~/.local/etc/rathole- Enable and start the service.
systemctl --user daemon-reload # Make sure systemd find the new unit
systemctl --user enable ratholes@app1 --nowRun multiple services
To run multiple services at once, simply add another configuration, say app2.toml under /etc/rathole (~/.local/etc/rathole for non-root), then run sudo systemctl enable ratholes@app2 --now (systemctl --user enable ratholes@app2 --now for non-root) to start an instance for that configuration.
The same applies to [email protected] for rathole --client and [email protected] for rathole.
Benchmark (`docs/benchmark.md`)
Benchmark
Date: 2021/12/28
Version: commit 1180c7e538564efd69742f22e77453a1b74a5ed2
Arch Linux with 5.15.11-arch2-1 kernel
Intel Xeon CPU E5-2620 @ 2.00GHz *2
16GB RAM
Bandwidth
rathole with the following configuration:
[client]
remote_addr = "localhost:2333"
default_token = "123"
[client.services.bench-tcp]
local_addr = "127.0.0.1:80"
[client.services.bench-udp]
type = "udp"
local_addr = "127.0.0.1:80"
[server]
bind_addr = "0.0.0.0:2333"
default_token = "123"
[server.services.bench-tcp]
bind_addr = "0.0.0.0:5202"
[server.services.bench-udp]
type = "udp"
bind_addr = "0.0.0.0:5202"frp 0.38.0 with the following configuration:
[common]
bind_port = 7000
authentication_method = token
token = 1233# frpc.ini
[common]
server_addr = 127.0.0.1
server_port = 7000
authentication_method = token
token = 1233
[bench-tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 80
remote_port = 5203
[bench-udp]
type = udp
local_ip = 127.0.0.1
local_port = 80
remote_port = 5203$ iperf3 -v
iperf 3.10.1 (cJSON 1.7.13)
Linux sig 5.15.7-arch1-1 #1 SMP PREEMPT Wed, 08 Dec 2021 14:33:16 +0000 x86_64
Optional features available: CPU affinity setting, IPv6 flow label, TCP congestion algorithm setting, sendfile / zerocopy, socket pacing, authentication, bind to device, support IPv4 don't fragment
$ sudo iperf3 -s -p 80For rathole benchmark:
$ iperf3 -c 127.0.0.1 -p 5202For frp benchmark:
$ iperf3 -c 127.0.0.1 -p 5203HTTP
nginx/1.20.2 listens on port 80, with the default test page.
frp and rathole configuration is same with the previous section.
vegeta is used to generate HTTP load.
HTTP Throughput
The following commands are used to benchmark rathole and frp. Note that if you want to do a benchmark yourself, -max-workers should be adjusted to get the accurate results for your machine.
echo 'GET http://127.0.0.1:5203' | vegeta attack -rate 0 -duration 30s -max-workers 48
echo 'GET http://127.0.0.1:5202' | vegeta attack -rate 0 -duration 30s -max-workers 48HTTP Latency
rathole has very similar latency to frp, but can handle more connections
Here's a table, latency is in ms
| QPS | latency(rathole) | latency(frp) |
|---|---|---|
| 1 | 2.113 | 2.55 |
| 1000 | 1.723 | 1.742 |
| 2000 | 1.845 | 1.749 |
| 3000 | 2.064 | 2.011 |
| 4000 | 2.569 | 7907 |
As you can see, for QPS from 1 to 3000, rathole and frp have nearly identical latency.
But with QPS of 4000, frp starts reporting lots of errors and the latency grows to even seconds. This kind of reflects the throughput in the previous section.
Thus, in terms of latency, rathole and frp are nearly the same. But rathole can handle more connections.
Memory Usage
The graph shows the memory usage of frp and rathole when vegeta attack -duration 30s -rate 1000 is executed.
rathole uses much less memory than frp.
Build Guide (`docs/build-guide.md`)
Build Guide
This is for those who want to build rathole themselves, possibly because the need of latest features or the minimal binary size.
Build
To use default build settings, run:
cargo build --releaseYou may need to pre-install openssl dependencies in Unix-like systems.
Customize the Build
rathole comes with lots of crate features that determine whether a certain feature will be compiled or not. Supported features can be checked out in [features] of Cargo.toml.
For example, to build rathole with the client and noise feature:
cargo build --release --no-default-features --features client,noiseRustls Support
rathole provides optional rustls support. It's an almost drop-in replacement of native-tls support. (See Transport for more information.)
To enable this, disable the default features and enable rustls feature. And for websocket feature, enable websocket-rustls feature as well.
You can also use command line option for this. For example, to replace all default features with rustls:
cargo build --release --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reloadFeature rustls and websocket-rustls cannot be enabled with native-tls and websocket-native-tls at the same time, as they are mutually exclusive. Enabling both will result in a compile error.
(Note that default features contains native-tls and websocket-native-tls.)
Minimalize the binary
- Build with the
minimalprofile
The release build profile optimize for the program running time, not the binary size.
However, the minimal profile enables lots of optimization for the binary size to produce a much smaller binary.
For example, to build rathole with client feature with the minimal profile:
cargo build --profile minimal --no-default-features --features clientstripandupx
The binary that step 1 produces can be even smaller, by using strip and upx to remove the symbols and compress the binary.
Like:
strip rathole
upx --best --lzma ratholeAt the time of writting the build guide, the produced binary for x86_64-unknown-linux-glibc has the size of 574 KiB, while frpc has the size of ~10 MiB, which is much larger.
Internals (`docs/internals.md`)
Internals
Conceptions
Service
The entity whose traffic needs to be forwarded
Server
The host that runs rathole in the server mode
Client
The host behind the NAT that runs rathole in the client mode. It has some services that need to be forwarded.
Visitor
Who visists a service, via the server
Control Channel
A control channel is a TCP connection between the server and the client that only carries rathole control commands for one service.
Data Channel
A data channel is a TCP connection between the server and the client that only carries the encapsulated data that needs forwarding for one service.
The Process
TODO: Add more details about the protocol
When rathole starts in the client mode, it creates connections to server.common.bind_addr for each service. These connection acts as control channels.
When a control channel starts, the server challenge the client by a nonce, the client is required to authenticate as the service it wants to represent. Then the forwarding of that service is set up.
When the server accepts a connection on a service's bind_port, it sends a control command to the client via the corresponding control channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency.
Out Of Scope (`docs/out-of-scope.md`)
Out of Scope
rathole focuses on the forwarding for the NAT traversal, rather than being a all-in-one development tool or a load balancer or a gateway. It's designed to be used with them, not replace them.
But that doesn't mean it's not useful for other purposes. In the future, more configuration APIs will be added and rathole can be used with an external dashboard.
Make each program do one thing well.
Domain based forwarding for HTTP
Introducing these kind of features into
ratholeitself ultimately reinvent a nginx. Use nginx to do this and setratholeas the upstream. This method achieves better performance as well as flexibility.HTTP Request Logging
ratholedoesn't interference with the application layer traffic. A right place for this kind of stuff is the web server, and a network capture tool.frp's STCP or other setup that requires visitors' side configurationIf that kind of setup is possible, then there are a lot more tools available. You may want to consider secure tunnels like wireguard or zerotier.
ratholeprimarily focuses on NAT traversal by forwarding, which doesn't require any setup for visitors.Caching
local_ip's DNS recordsAs responded in issue #183,
local_ipcache is not feasible because we have no reliable way to detect ip change. Handle DNS TTL and so on should be done with a DNS server, not a client. Caching ip is generally dangerous for clients. If you care about thelocal_ipquery you can set up a local DNS server and enable caching. Then the local lookup should be trivial.
Transport (`docs/transport.md`)
Security
By default, rathole forwards traffic as it is. Different options can be enabled to secure the traffic.
TLS
Checkout the example
Client
Normally, a self-signed certificate is used. In this case, the client needs to trust the CA. trusted_root is the path to the root CA's certificate PEM file.hostname is the hostname that the client used to validate aginst the certificate that the server presents. Note that it does not have to be the same with the remote_addr in [client].
[client.transport.tls]
trusted_root = "example/tls/rootCA.crt"
hostname = "localhost"Server
PKCS#12 archives are needed to run the server.
It can be created using openssl like:
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crtAruguments are:
-inkey: Server Private Key-in: Server Certificate-certfile: CA Certificate
Creating self-signed certificate with one's own CA is a non-trival task. However, a script is provided under tls example folder for reference.
Rustls Support
rathole provides optional rustls support. Build Guide demostrated this.
One difference is that, the crate we use for loading PKCS#12 archives can only handle limited types of PBE algorithms. We only support PKCS#12 archives that they (crate p12) support. So we need to specify the legacy format (openssl 1.x format) when creating the PKCS#12 archive.
In short, the command used with openssl 3 to create the PKCS#12 archive with rustls support is:
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt -legacyNoise Protocol
Quickstart for the Noise Protocl
In one word, the Noise Protocol is a lightweigt, easy to configure and drop-in replacement of TLS. No need to create a self-sign certificate to secure the connection.
rathole comes with a reasonable default configuration for noise protocol. You can a glimpse of the minimal example for how it will look like.
The default noise protocol that rathole uses, which is Noise_NK_25519_ChaChaPoly_BLAKE2s, providing the authentication of the server, just like TLS with properly configured certificates. So MITM is no more a problem.
To use it, a X25519 keypair is needed.
Generate a Keypair
- Run
rathole --genkey, which will generate a keypair using the default X25519 algorithm.
It emits:
$ rathole --genkey
Private Key:
cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=
Public Key:
GQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE=(WARNING: Don't use the keypair from the Internet, including this one)
- The server should keep the private key to identify itself. And the client should keep the public key, which is used to verify whether the peer is the authentic server.
So relevant snippets of configuration are:
# Client Side Configuration
[client.transport]
type = "noise"
[client.transport.noise]
remote_public_key = "GQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE="
# Server Side Configuration
[server.transport]
type = "noise"
[server.transport.noise]
local_private_key = "cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q="Then rathole will run under the protection of the Noise Protocol.
Specifying the Pattern of Noise Protocol
The default configuration of Noise Protocol that comes with rathole satifies most use cases, which is described above. But there're other patterns that can be useful.
No Authentication
This configuration provides encryption of the traffic but provides no authentication, which means it's vulnerable to MITM attack, but is resistent to the sniffing and replay attack. If MITM attack is not one of the concerns, this is more convenient to use.
# Server Side Configuration
[server.transport.noise]
pattern = "Noise_XX_25519_ChaChaPoly_BLAKE2s"
# Client Side Configuration
[client.transport.noise]
pattern = "Noise_XX_25519_ChaChaPoly_BLAKE2s"Bidirectional Authentication
# Server Side Configuration
[server.transport.noise]
pattern = "Noise_KK_25519_ChaChaPoly_BLAKE2s"
local_private_key = "server-priv-key-here"
remote_public_key = "client-pub-key-here"
# Client Side Configuration
[client.transport.noise]
pattern = "Noise_KK_25519_ChaChaPoly_BLAKE2s"
local_private_key = "client-priv-key-here"
remote_public_key = "server-pub-key-here"Other Patterns
To find out which pattern to use, refer to:
Note that PSKs are not supported currently. Free to open an issue if you need it.
--- METRICS ---
- Files Extracted: 7
- Estimated Token Budget: ~5658 tokens
- Canonical Reference: https://codewiki.google/github.com/rathole-org/rathole