chore: improve documentation and README
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 33s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 23s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Successful in 33s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m7s
Rust CI / cargo doc (push) Successful in 27s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Successful in 1m21s
Fuzz Smoke / cargo fuzz smoke (push) Successful in 1m40s
Netem Interop / netem interop (push) Successful in 1m18s
Supply chain / cargo-deny advisories (push) Successful in 18s
Supply chain / actionlint (push) Failing after 4s
Supply chain / cargo-deny bans licenses sources (push) Successful in 17s
Build binaries / Build (arm-musleabi) (push) Successful in 4m58s
Build binaries / Build (aarch64-musl) (push) Successful in 5m6s
Build binaries / Build (arm-musleabihf) (push) Successful in 4m38s
Build binaries / Build (armv7-musleabi) (push) Successful in 4m33s
Build binaries / Build (armv7-musleabihf) (push) Successful in 4m57s
Build binaries / Build (i686-musl) (push) Successful in 5m1s
Build binaries / Build (mips64-muslabi64) (push) Successful in 4m35s
Build binaries / Build (mips-musl) (push) Successful in 4m42s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 4m54s
Build binaries / Build (mipsel-musl) (push) Successful in 4m54s
Build binaries / Build (riscv64gc-musl) (push) Successful in 4m36s
Build binaries / Build (x86_64-musl) (push) Successful in 4m40s

This commit is contained in:
Nils Andreas Svee committed 2026-10-28 12:17:48 +07:00
1 parent f02f9c0714
commit 97bd903769
6 files changed
+534 -466

No files matched your search

+104 -104
View File
@@ -1,22 +1,22 @@
# irtt-server
## NAME
## Name
irtt-server - IRTT-compatible UDP server
## SYNOPSIS
## Synopsis
`irtt-server` [`--bind` *ADDR*]... [*OPTIONS*]
`irtt-rs server` [`--bind` *ADDR*]... [*OPTIONS*]
## DESCRIPTION
## Description
`irtt-server` answers IRTT echo requests over UDP. It is thin orchestration
over the reusable `irtt-server` library: one current-thread Tokio runtime,
one or more listeners, and the library's own session/policy logic.
`irtt-server` answers IRTT echo requests over UDP. It uses the reusable
`irtt-server` library for sessions and policy, with one current-thread
Tokio runtime running one or more listeners.
Run with no arguments for the ordinary case:
To start a server with the defaults, run:
```sh
irtt-server
@@ -28,34 +28,31 @@ wildcard address for both address families, in this fixed order:
1. `[::]:2112`
2. `0.0.0.0:2112`
This is available wherever the platform supports safe wildcard reply-source
selection (see [Wildcard binds](#wildcard-binds) below). Any explicit
`--bind` **replaces** this default pair entirely rather than adding to it —
`irtt-server --bind 127.0.0.1:2112` binds only that one address.
The defaults work on platforms that support wildcard reply-source
selection (see [Wildcard binds](#wildcard-binds)). If you supply any
`--bind`, it replaces both defaults. For example,
`irtt-server --bind 127.0.0.1:2112` listens only on that address.
If one of the two default addresses cannot be bound because its address
family itself has no local support at all — IPv6 disabled on an otherwise
IPv4-capable host, for example — the server falls back to serving just the
other one instead of failing outright, and says so on startup. Any other
reason a default listener fails — the port already in use, permission
denied, no safe wildcard reply-source path — is not a family-support
problem and is not silently worked around; the invocation fails as it would
for an explicit `--bind`. This fallback applies only to the implicit default
pair: an explicit `--bind` list always keeps the ordinary all-or-nothing
behavior described below.
If the host has no support for one address family, for example because IPv6
is disabled, the server uses the other default listener and tells you on
startup. Other bind failures still stop startup. This includes a port
already in use, permission denied, or a platform without safe wildcard
reply-source selection.
## OPTIONS
The address-family fallback only applies to the default pair. An explicit
`--bind` list always uses the all-or-nothing behavior described below.
## Options
### `--bind ADDR`
Local UDP address to bind, as `ADDR:PORT`, for example `127.0.0.1:2112` or
`[::1]:2112`. Host names are not resolved. Repeatable.
Use `ADDR:PORT` to choose a local UDP address, for example `127.0.0.1:2112`
or `[::1]:2112`. Hostnames are not resolved.
Repeat the option to serve several addresses from one process, in the order
given. Every listener applies the same policy options, but each keeps its
own sessions and tokens, so a session belongs to the address it was opened
on. Binding is all-or-nothing: if any address cannot be bound, none are
served.
Repeat `--bind` to serve several addresses from one process, in the order
given. Every listener uses the same policy options, but keeps its own
sessions and tokens. A session belongs to the address it was opened on.
Binding is all-or-nothing: if any address cannot be bound, none are served.
A port of `0` selects an unused port per listener, so two such binds get two
different ports.
@@ -63,71 +60,75 @@ different ports.
With no `--bind` at all, see [Description](#description) above for the
default wildcard pair and the address-family fallback it allows.
When reply DSCP/traffic-class marking for IPv4 peers matters, do not configure
one explicit dual-stack `[::]:PORT` listener by itself. On Linux, IPv4 peers
served through that socket are IPv4-mapped and their replies cannot retain the
negotiated marking. Configure the ordinary paired `[::]:PORT` and
`0.0.0.0:PORT` listeners instead; the IPv6 listener is made IPv6-only for that
same-port pair, so each family uses the correct socket option. This is a
configuration workaround, not a reason to force `IPV6_V6ONLY` for every lone
IPv6 listener.
If you need DSCP/traffic-class marking on replies to IPv4 peers, use care
with a lone dual-stack `[::]:PORT` listener. On Linux, those peers use
IPv4-mapped addresses and their replies cannot retain the negotiated
marking. Use paired `[::]:PORT` and `0.0.0.0:PORT` listeners on the same
port. The server makes the IPv6 listener IPv6-only for that pair, so each
family gets the correct socket option. A lone IPv6 listener keeps its usual
behavior; the server does not force `IPV6_V6ONLY` on it.
### `--hmac KEY`
Accepts a plain-text key or a hexadecimal key prefixed with `0x`. For example, `secret` and `0x736563726574` are equivalent. The `IRTT_SERVER_HMAC` environment variable uses the same syntax.
You can use a plain-text key or a hexadecimal key prefixed with `0x`.
For example, `secret` and `0x736563726574` are equivalent. The
`IRTT_SERVER_HMAC` environment variable accepts the same syntax.
With a key configured, every request must carry a valid MAC and every reply is authenticated;
without one, authenticated requests are dropped. The key is visible in the
process arguments.
With a key configured, every request must carry a valid MAC and the server
authenticates every reply. Without a key, the server drops authenticated
requests. A key passed on the command line is visible in the process
arguments.
### `--max-sessions COUNT`
Maximum number of simultaneously live sessions **per listener**, not per
process. Once a listener's table is full, a session-creating open to it is
dropped silently; nothing is evicted. Zero refuses every session-creating
open.
Set the maximum number of live sessions per listener. This limit applies
to each listener separately, so the process can have more sessions in total.
Once a listener's table is full, it silently drops opens that would create
a session and keeps the existing sessions. Zero refuses every open that
would create a session.
### `--max-packet-length BYTES`
Maximum echo datagram size a session may negotiate, in bytes. A longer
requested length is reduced during negotiation, an open whose mandatory
field block would not fit is refused, and inbound echo requests are admitted
against the same limit. This is a resource bound, not an MTU.
Set the maximum echo datagram size a session may negotiate, in bytes. The
server reduces longer requests during negotiation and refuses an open if
its mandatory fields would not fit. Incoming echo requests must fit the
same limit. Use this to bound resource use; it does not set the network MTU.
### `--min-interval DURATION`
Floor on the send interval a session may negotiate. The negotiated interval
is still capped at a quarter of the idle timeout afterwards, so a
`--min-interval` above that cap is not the value actually negotiated.
Set the minimum send interval a session may negotiate. The server then caps
the negotiated interval at a quarter of the idle timeout, so a
`--min-interval` above that cap will be reduced.
A session's reply allowance refills at the shorter of this configured floor
and the interval it actually negotiated. Ordinarily that is the configured
floor itself, since the negotiated interval is normally at least as long —
a 10ms `--min-interval` against a 1s negotiated interval refills every
10ms. The two differ only when the idle-timeout cap above pulled the
negotiated interval below the configured floor, in which case the shorter,
negotiated value is what the allowance actually refills at: a server must
not enforce a cadence it did not advertise to the client. Use `0` for no
time-based throttling.
The session's reply allowance refills at the shorter of the configured
minimum and the negotiated interval. Usually this is the configured
minimum: a 10ms `--min-interval` with a 1s negotiated interval refills every
10ms.
If the idle-timeout cap reduces the negotiated interval below the configured
minimum, the allowance refills at that shorter interval too. This keeps the
server from enforcing a slower cadence than it told the client to use.
Use `0` for no time-based throttling.
### `--burst COUNT`
Echo replies one session may have answered before its allowance has to
refill. Use `0` for no allowance at all, which rate-limits every echo
request whatever the interval is.
Set how many echo replies a session can send before its allowance needs to
refill. Use `0` for no allowance, which rate-limits every
echo request regardless of the interval.
### `--idle-timeout DURATION`
Release a session after this long without a served or rate-limited echo
request. The deadline runs from the open, and release is silent. Use `0` to
expire a session at the next evaluation; it is not a way to disable expiry.
request. The timeout starts at open, and the server releases the session
without sending a close. If you use `0`, the session expires at the next
evaluation; zero does not disable expiry.
### `--max-duration DURATION`
Maximum test duration a session may negotiate. A longer request is reduced
to it, and a continuous request is answered with it. Omit this flag for no
maximum; a maximum of zero cannot be expressed, because a negotiated
duration of zero means continuous.
Set the maximum test duration a session may negotiate. The server reduces
longer requests to this value and uses it for continuous requests too.
Leave the flag out if you don't want a maximum. You cannot set a maximum of
zero, since a negotiated duration of zero means continuous.
### `--timestamp-allowance MODE`
@@ -147,13 +148,12 @@ and they are sent unmarked. The session is not refused.
Durations everywhere in this command accept an integer followed by a unit:
`ms`, `s`, or `m`.
## ENVIRONMENT
## Environment
Every option above can also be supplied through an environment variable, for
deployments that configure by environment rather than by command line
(containers, orchestrators, unit files). A flag given on the command line
overrides the corresponding variable entirely; an unset variable leaves the
built-in default in place.
You can also set each option through an environment variable, which is
useful for containers, orchestrators, and unit files. A command-line flag
overrides the corresponding variable entirely. If neither is set, the
server uses its built-in default.
| Variable | Option |
| --- | --- |
@@ -176,10 +176,10 @@ more than one address needs repeated `--bind` options.
an empty value leave traffic-class marking enabled; any other value disables
it, and an unset variable leaves it enabled.
## OUTPUT
## Output
Each bound endpoint is printed on startup, once every listener is up, which
also resolves a port of `0`:
Once every listener is up, the server prints each bound endpoint. If you
requested port `0`, this shows which port was assigned:
```
irtt-server: listening on [::]:2112
@@ -188,21 +188,22 @@ irtt-server: listening on 0.0.0.0:2112
`Ctrl-C` stops the server gracefully.
## MULTIPLE LISTENERS
## Multiple listeners
Every invocation runs through the same multi-listener path, so one address —
whether from the default pair or a single explicit `--bind` — is an ordinary
set of one rather than a separate mode. The policy options above apply to
every listener, but listeners are otherwise independent: each has its own
sessions and tokens, and `--max-sessions` bounds each listener rather than
the process. If any listener fails while running, the others are shut down
with it rather than leaving a service configured for two families answering
on one.
The server manages listeners the same way whether you have one or several,
including when only one address from the default pair is available or you
supply a single `--bind`. Each listener uses the same policy options, but
keeps its own sessions and tokens. The `--max-sessions` limit applies to
each listener separately.
If any listener fails while running, the server shuts down the others too.
So a service configured for both address families won't carry on answering
on just one after a listener failure.
Wildcard IPv4 and IPv6 listeners may share one port, as the default pair
does.
## SERVER FILL
## Server fill
Echo replies fill their payload according to the negotiated `ServerFill`
mode, which a client requests with `irtt-client`'s `--sfill` (see
@@ -212,11 +213,11 @@ mode, which a client requests with `irtt-client`'s `--sfill` (see
- `rand`: random bytes.
- `pattern:HEX`: the given repeating hexadecimal pattern.
Every valid descriptor is honored. A descriptor the server cannot parse, and
a client that expresses no fill preference at all, both get the default
`pattern:69727474`, i.e. the repeating bytes `69 72 74 74` (`irtt`).
The server honors every valid descriptor. If it cannot parse the descriptor,
or the client sends no fill preference, it uses `pattern:69727474`: the
repeating bytes `69 72 74 74` (`irtt`).
## WILDCARD BINDS
## Wildcard binds
An explicit interface address works on every supported system. A wildcard
bind such as `0.0.0.0:2112` or `[::]:2112` reads each request's local
@@ -224,14 +225,13 @@ destination from packet metadata and sends that request's reply from the
same address, so a client on a multi-homed host is answered from the
endpoint it actually contacted.
That reply-source selection is implemented on **Linux, macOS, and FreeBSD**.
Elsewhere, a wildcard bind is refused at construction rather than served
from a routing-table-selected source address a client would discard — this
also applies to the zero-argument default, which will fail with a clear
error asking for an explicit `--bind` on such a platform. This correctness
rule is deliberate and is not weakened by the zero-argument default.
Reply-source selection is supported on Linux, macOS, and FreeBSD. On other
platforms, the server refuses wildcard binds because the routing table
could select a source address the client would discard. This also means
running without arguments fails on those platforms, with an error asking
you to supply an explicit `--bind`.
## EXAMPLES
## Examples
Run with the default wildcard listeners on the standard port:
@@ -283,19 +283,19 @@ Require authentication:
irtt-server --bind 192.0.2.10:2112 --hmac secret
```
## EXIT STATUS
## Exit status
`0` on a clean shutdown (`Ctrl-C`), nonzero if binding fails or a listener
fails while running.
## LIMITATIONS
## Limitations
- No hostname resolution, interface expansion, daemonization, or
configuration file — binds are explicit addresses only.
configuration file. Binds must use explicit addresses.
- Wildcard reply-source selection is unavailable outside Linux, macOS, and
FreeBSD; such platforms must bind explicit addresses.
## SEE ALSO
## See also
`irtt-client(1)`, `irtt-tui(1)`, `irtt-rs(1)`, and `irtt-server --help` for
the full option list.