irtt_server/lib.rs
1//! IRTT-compatible server behavior.
2//!
3//! This crate shares wire semantics with the client through `irtt-proto` and
4//! does not depend on `irtt-client`.
5//!
6//! # Structure
7//!
8//! [`ServerCore`] is the deterministic protocol and session engine: packet
9//! admission, authentication policy, open negotiation, the session table, echo
10//! receive state, rate and lifetime policy, resource decisions and reply
11//! construction. It performs no I/O; the clock its timestamps and deadlines
12//! come from is a private injected seam, not a runtime abstraction, so the
13//! engine stays testable. Each reply it produces is an [`OutboundDatagram`]:
14//! the packet and the raw traffic class it must be sent with. [`Server`] owns
15//! one Tokio UDP listener and one core, and provides sequential receive, reply,
16//! scheduled expiry maintenance and caller-controlled shutdown. [`ServerSet`]
17//! owns one or more `Server`s as a single service: it binds them all or none,
18//! runs each in its own task, fans one shutdown out to them and fails the group
19//! if any listener does. The crate is intentionally Tokio-native; there is no
20//! blocking or alternate-runtime counterpart and no transport abstraction.
21//!
22//! # One listener or several
23//!
24//! `Server` is the primitive and stays directly usable. `ServerSet` is the
25//! service around it, and a set of one is an ordinary set rather than a special
26//! case — the server CLI runs every invocation, single-bind included, through
27//! one.
28//!
29//! Listeners in a set share nothing but the configuration they were cloned
30//! from. Each has its own socket, core, session table and tokens, so a token
31//! issued by one is unknown at the others, and every bound in
32//! [`ServerConfig`] — `max_sessions` included — applies **per listener**, not
33//! per process.
34//!
35//! # Rejection is silence
36//!
37//! The protocol defines no error reply, no reset and no NACK. Malformed,
38//! unauthenticated and policy-refused datagrams are discarded without a
39//! response and without disturbing any live session, and a client tells
40//! "rejected" from "lost" only by timing out. [`ServerError`] is therefore
41//! reserved for internal failures on the server's own side.
42//!
43//! # Resource policy
44//!
45//! Total session state is bounded (see [`DEFAULT_MAX_SESSIONS`]), and so is the
46//! echo datagram a single session can negotiate (see
47//! [`DEFAULT_MAX_PACKET_LENGTH`]), the rate one session is answered at (see
48//! [`DEFAULT_MIN_SEND_INTERVAL`] and [`DEFAULT_BURST_ALLOWANCE`]) and how long
49//! one lives without traffic (see [`DEFAULT_IDLE_TIMEOUT`]). A single
50//! unauthenticated datagram creates a session and opens are never deduplicated,
51//! so neither an unbounded table nor a remotely chosen buffer size is a
52//! compatibility feature. Upstream's observed lack of session bounds, its
53//! unlimited default packet length, and its never expiring a session that has
54//! not carried an echo request are explicitly not compatibility targets: an
55//! `irtt-rs` session ages from the moment it is opened.
56//!
57//! # Reply source address
58//!
59//! A reply leaves from the address its request was sent to. An
60//! explicit-address listener gets that from the bind and uses ordinary
61//! receive/send. A wildcard listener (`0.0.0.0` or `[::]`) instead reads each
62//! request's local destination from packet metadata and sends that request's
63//! reply from it, so a client on the host's second address is answered from the
64//! endpoint it contacted rather than from whichever one the routing table would
65//! have chosen.
66//!
67//! That path is implemented for Linux, macOS and FreeBSD. On other targets a
68//! wildcard bind is refused by [`Server::from_socket`] — and so by
69//! [`Server::bind`] — rather than served incorrectly; explicit-address
70//! listeners work everywhere they did before.
71//!
72//! # Current scope
73//!
74//! Open handling and negotiation, session creation, normal echo processing,
75//! per-session rate limiting, idle expiry, the maximum-duration
76//! server-initiated close, the negotiated per-session reply traffic class,
77//! server fill, the optional timestamp-allowance and DSCP capability
78//! restrictions, wildcard reply-source selection, Tokio UDP orchestration and
79//! multi-listener supervision through [`ServerSet`].
80//!
81//! # Capability restrictions
82//!
83//! Two optional negotiation policies restrict what a session may ask this server
84//! to provide, and both are off by default, so a configuration that sets neither
85//! negotiates exactly what it always did.
86//!
87//! [`TimestampAllowance`] reduces the requested timestamp placement: `Single`
88//! provides at most one timestamp instant, answering a request for both instants
89//! with the midpoint, and `None` provides no timestamps at all. The requested
90//! clock is never rewritten, so a single placement still reports one field per
91//! negotiated clock domain. [`ServerConfig::with_dscp_allowed`] refuses to provide
92//! traffic-class marking, negotiating any requested DSCP value to zero, after
93//! which the session's replies leave unmarked like any other zero-DSCP session.
94//!
95//! # Server fill
96//!
97//! An echo reply's payload region is filled from the session's negotiated
98//! ServerFill descriptor: `none`, `rand`, or `pattern:` followed by a repeating
99//! hexadecimal pattern. Every valid descriptor is accepted — there is no
100//! allow-list to configure — and one this server cannot parse is answered with
101//! its default, `pattern:69727474`, which is also what a client expressing no
102//! preference is served without any change to the negotiated parameters.
103//!
104//! A `none` payload is zeroes. Payload bytes carry no protocol meaning, and a
105//! server must never emit residue from another request or client. Request
106//! payload bytes never reach a reply under any mode.
107#![forbid(unsafe_code)]
108
109mod clock;
110mod config;
111mod core;
112mod error;
113mod fill;
114mod negotiate;
115mod runtime;
116mod session;
117mod set;
118mod socket_io;
119mod socket_options;
120mod token;
121
122#[cfg(test)]
123mod tests;
124
125pub use config::{
126 ServerConfig, TimestampAllowance, DEFAULT_BURST_ALLOWANCE, DEFAULT_IDLE_TIMEOUT,
127 DEFAULT_MAX_PACKET_LENGTH, DEFAULT_MAX_SESSIONS, DEFAULT_MIN_SEND_INTERVAL,
128};
129pub use core::{OutboundDatagram, ServerCore};
130pub use error::ServerError;
131pub use runtime::{Server, ServerRuntimeError};
132pub use set::{ServerSet, ServerSetError};