PacketRelayDocs

Flags & environment#

PacketRelay is configured entirely at startup. Eighteen command-line flags and two environment variables are the whole surface — there is no configuration file, no reload signal, and no runtime tuning endpoint. This page lists every flag with its type, default, valid range and effect, then the two environment variables and the constraints that hold between flags.

The flag table is in source order — the order the fields are declared in the Args struct at main.rs:143-208, which is also the order --help prints them. Alphabetical order would separate --max-connections from --max-connections-per-ip, which are validated against each other.

Flags#

Every default below is the shipped value: all eighteen flags use default_value_t, so running the binary with no arguments produces exactly this configuration. Defaults are shown in bold for that reason.

Flag Type Default Valid range Effect
-p, --port u16 8080 Any u16 TCP listen port. The bind address is always 0.0.0.0:{port}main.rs:143-144, main.rs:317
--session-ttl u64 seconds 300 30–86400 Maximum age of a stale session before the reaper removes it — main.rs:147-148, session.rs:454
--max-connections usize 128 1–10000 Global limiter cap and the size of the legacy-route semaphore — main.rs:151-152, main.rs:297
--max-connections-per-ip usize 80 1 to --max-connections Concurrent connections permitted from one peer IP — main.rs:155-156, rate_limit.rs:92-125
--max-connect-rate-per-ip usize 240 1–1000000 New connection attempts per IP per connection-rate window. Rejected attempts count — main.rs:159-160, rate_limit.rs:111-112
--connect-rate-window u64 seconds 60 1–3600 Width of the sliding connection-rate window — main.rs:163-164
--max-sessions usize 256 1–100000 Global ceiling on the session map, enforced under the creation write lock — main.rs:167-168, session.rs:130-132
--max-id-bytes usize 64 8–256 Maximum length in ASCII bytes of session_id and member_idmain.rs:171-172, connection.rs:472-478
--hello-timeout u64 seconds 5 1–60 Deadline for the HTTP request head and for the first application hello — main.rs:175-176, main.rs:389
--max-message-bytes usize 65536 (64 KiB) 1024–65536 Tungstenite max_message_size and max_frame_size. 64 KiB is a hard security ceiling — main.rs:179-180, main.rs:39
--max-messages-per-window usize 3000 1–1000000 Per-connection text-frame count budget. Breach sends "message rate exceeded" and closes — main.rs:183-184, rate_limit.rs:220-225
--max-bytes-per-window usize 67108864 (64 MiB) --max-message-bytes to 1 GiB Per-connection payload byte budget. Breach sends "message byte rate exceeded" and closes — main.rs:187-188
--message-rate-window u64 seconds 60 1–3600 Width of the fixed message-budget window. Counters reset wholesale — main.rs:191-192, rate_limit.rs:214-219
--outbound-queue usize 16 1–256 Depth of the bounded outbound mpsc channel held per connection — main.rs:195-196, connection.rs:167
--max-room-members usize 32 2–256 Members permitted in one room. Breach gives "room at capacity"main.rs:199-200, session.rs:227-256
--max-spectators usize 64 1–1000 Spectators permitted in one broadcast. Breach gives "spectator cap reached"main.rs:203-204, session.rs:208-214
--reaper-interval u64 seconds 30 1 to --session-ttl Cadence of the session and limiter cleanup task — main.rs:207-208, session.rs:454-476

--port is the one flag with no documented range beyond its type: any u16 parses, including 0, which asks the operating system for an ephemeral port. Nothing in Args::validate inspects it — main.rs:213-266.

-p is the only short flag#

--port is declared #[arg(short, long, ...)]; the other seventeen are declared #[arg(long, ...)]main.rs:143, main.rs:147-208. There is no -c for --max-connections, no -t for --session-ttl, and no bundling. This is worth knowing before writing a start command, because clap rejects an unknown short flag rather than ignoring it.

Invalid values exit at startup#

validated_args parses, then calls Args::validate, and on any violation raises a clap ErrorKind::ValueValidation error and exits — main.rs:212-266, main.rs:269-276. The process never starts with an out-of-range configuration and never clamps a value into range. The error message names the flag and its bounds, for example:

--max-message-bytes must be between 1024 and 65536

Type errors are caught earlier, by clap itself: --port banana or --max-connections -1 fails at parse time before validate runs.

Three flags are validated against another flag#

Fourteen flags carry fixed bounds and --port carries none. The remaining three are bounded by the value of a second flag, which is what makes them easy to trip when raising a limit in isolation.

Constraint Failure message Citation
--max-connections-per-ip must be at least 1 and no greater than --max-connections --max-connections-per-ip must be between 1 and --max-connections main.rs:216-218
--max-bytes-per-window must be at least --max-message-bytes, and at most 1 GiB --max-bytes-per-window must be at least one max message and at most 1 GiB main.rs:240-247
--reaper-interval must be at least 1 and no greater than --session-ttl --reaper-interval must be between 1 and --session-ttl main.rs:262-264
Important

Lowering --max-connections below the current --max-connections-per-ip is the common way to hit the first constraint. --max-connections 40 alone fails, because the per-IP default of 80 then exceeds it; both flags have to move together.

The byte-budget constraint exists because a budget smaller than one message would reject every frame the first time it was charged — the connection would be admitted and then closed on its first text frame, which looks like a relay fault rather than a configuration error.

Environment variables#

Exactly two environment variables are read anywhere in the binary.

Variable Type Default Effect
PORT u16 Unset; falls back to --port Overrides --port when present and parseable. A present-but-unparseable value silently falls back to --portmain.rs:313-316
RUST_LOG tracing-subscriber filter directive packet_relay=info Sets the log filter. On absence or parse failure the filter is packet_relay=infomain.rs:281-286

Both failure paths are silent by design and by consequence. PORT=http does not stop startup and does not warn — the relay binds --port instead, so a platform that exports a non-numeric port produces a listener on the wrong port with a clean log. RUST_LOG=nonsense likewise leaves the default filter in place, because EnvFilter::try_from_default_env() is followed by unwrap_or_else rather than an expect.

Warning

PORT takes precedence over the flag, not the other way round. Passing --port 9000 on a host that already exports PORT=8080 yields a listener on 8080. The env var is checked after argument parsing and wins — main.rs:313-316.

No flag reads an environment variable#

The clap env feature is not enabled — the dependency is declared with the derive feature only (Cargo.toml:18), so no #[arg(env = ...)] attribute exists and none would work. PORT is honoured by an explicit std::env::var("PORT") call in main, not by clap, and it is the only flag value that has such a call. There is no PACKET_RELAY_MAX_CONNECTIONS, no RELAY_SESSION_TTL, and no prefix convention waiting to be discovered.

This matters most in a container, where flags are awkward to pass and environment variables are the native idiom. Every limit other than the port has to be supplied on the command line.

The shipped container passes no flags#

The Dockerfile sets ENV PORT=8080, EXPOSE 8080, USER 65532:65532 and ENTRYPOINT ["packet-relay"], with no CMDDockerfile:1-26. The image therefore runs on every default in the table above, and railway.json adds no startCommand (railway.json:1-11). Changing a limit in a deployed container means overriding the command, not setting a variable.

packet-relay --max-connections 512 --max-connections-per-ip 64    # together
cargo run --locked -- --port 8080    # from source, all other defaults

What no flag configures#

The eighteen flags above are the complete set. Several things an operator would reasonably look for a flag for have no flag, because the behaviour they would control does not exist in the binary at all.

Looked-for control Actual state Citation
TLS certificate, key or listener The relay speaks plain HTTP and WebSocket only. TLS termination is the hosting edge's job, so clients connect to the edge with wss:// main.rs:317
Origin allowlist No Origin header check exists anywhere in the binary BACKLOG.md:151-158
Health-check path /health, /healthz, /ready and /readyz are fixed paths in the route table and are not configurable main.rs:474-486
Post-hello idle timeout None exists. --hello-timeout covers the request head and the first hello only connection.rs:333-409
Server-initiated keepalive interval None exists. The relay echoes Ping as Pong and never sends a Ping connection.rs:397-399
Graceful shutdown or drain period None exists. The accept loop is unconditional and no signal is handled main.rs:357-381
Persistence, database URL or state directory Sessions, TOFU pins, nonce high-water marks and limiter state are process memory. A restart loses all of it session.rs:93, room_auth.rs:141
Bind address Fixed at 0.0.0.0. Only the port is configurable main.rs:317

--product-connection-reserve was removed#

The flag was deleted on 2026-08-28 together with the /v1/product-route handler, its reserved connection pool and the RequestRoute::Product variant — CHANGELOG.md:10-28. Removed.

Note

The project README.md still lists --product-connection-reserve with a default of 32, and still describes product-route capacity as additive and isolated — README.md:160, README.md:177-178. Those rows are stale. Args has no such field, RouteCapacity holds only the legacy semaphore, and passing the flag is an unknown-argument error at startup. The code is authoritative here.

  • Run the relay — building, the container, and the Railway target.
  • Limits & capacity — what each cap does under load, and where backpressure bites.
  • HTTP endpoints — the health and readiness paths these limits are applied ahead of.
  • Message reference — the envelopes the message budget is charged against.
  • Security model — which of these limits are the security boundary and which are not.
  • Build & test — the toolchain and the gates that pin these defaults.