rulay runs in one of two modes:
transmitter— accepts connections from clients and receivers, verifies REALITY auth, proxies trafficreceiver— connects to the transmitter and bridges it to the target upstream
Both modes accept:
--upstream-server--upstream-port--downstream-server--downstream-port
Transmitter mode also accepts:
--server-priv— base64url (no-pad) encoded 32-byte X25519 server private key for REALITY auth verification--redirect-server—host:portto redirect non-REALITY clients to (e.g. a cover website)
If any parameter is omitted, mode-specific defaults are used.
Build:
cargo build --releaseRun transmitter:
cargo run -- \
--mode transmitter \
--upstream-server 0.0.0.0 \
--upstream-port 8444 \
--downstream-server 0.0.0.0 \
--downstream-port 443 \
--server-priv 12345ABCD \
--redirect-server example.com:443Run receiver:
cargo run -- \
--mode receiver \
--upstream-server 0.0.0.0 \
--upstream-port 8443 \
--downstream-server 0.0.0.0 \
--downstream-port 8444The Docker image targets linux/amd64. To build the binary on macOS without Docker:
brew install zig
cargo install cargo-zigbuild
rustup target add x86_64-unknown-linux-gnu
cargo zigbuild --release --target x86_64-unknown-linux-gnuThe binary is placed at target/x86_64-unknown-linux-gnu/release/rulay and is picked up by the Dockerfile directly.
Build the image (uses the pre-compiled binary from target/x86_64-unknown-linux-gnu/release/rulay):
./install.sh --build-onlyOr directly:
docker build -t rulay .Run transmitter:
./install.sh \
--mode transmitter \
--upstream-server 0.0.0.0 \
--upstream-port 8554 \
--downstream-server 0.0.0.0 \
--downstream-port 443 \
--server-priv ABCD123123EF \
--redirect-server example:443Run receiver:
./install.sh \
--mode receiver \
--upstream-server host.docker.internal \
--upstream-port 8553 \
--downstream-server 0.0.0.0 \
--downstream-port 8554Additional options:
--image-name <name>— Docker image tag (default:rulay)--container-name <name>— container name (default:rulay)--build-only— build the image and exit without starting a container
install.sh publishes both configured ports from the container to the host using the same port numbers.
The binary includes tokio-console instrumentation. The console gRPC server listens on port 6669 inside the container (install.sh publishes it automatically).
Install the CLI:
cargo install tokio-consoleConnect to a remote server via SSH tunnel:
ssh -L 6669:localhost:6669 root@<server-ip>Then in another terminal:
tokio-consoleThis opens an interactive TUI showing all named async tasks, their poll times, waker counts, etc.
Named tasks:
| Name | Location |
|---|---|
upstream-listener |
accepts receiver connections |
ping-loop |
pings receiver connections in the pool |
downstream-client |
handles an incoming client |
copy-bidir-client |
bidirectional copy client <-> receiver |
copy-bidir-redirect |
bidirectional copy for redirected (non-REALITY) clients |
receiver-connect |
establishes connection to transmitter |
receiver-ping-loop |
responds to pings / waits for data |
docker run --rm \
--add-host host.docker.internal:host-gateway \
-e MODE=transmitter \
-e UPSTREAM_SERVER=0.0.0.0 \
-e UPSTREAM_PORT=8444 \
-e DOWNSTREAM_SERVER=0.0.0.0 \
-e DOWNSTREAM_PORT=443 \
-e SERVER_PRIV=ABCD123123EF \
-e REDIRECT_SERVER=example:443 \
-p 8444:8444 \
-p 443:443 \
rulay