Skip to main content

Module rate_limit

Module rate_limit 

Source
Expand description

A minimal fixed-window rate limiter, shared by every anonymous route that does real work before it knows who is calling.

bot.did.createAccount and POST /oauth/par are the motivating cases: the first mints a signing key and a hostname, the second fetches a document from a host the caller names, and both do it for a caller this server has not authenticated yet. This limiter is what stands in front of that cost.

A small fixed-window counter: one HashMap, one lock, no background task. The per-account budget on atproto-proxy is a governor token bucket instead; see crate::proxy.

Fixed-window rather than a token bucket: it under-approximates a true rate at the window boundary (a caller can burst up to limit twice, once at the end of one window and once at the start of the next), which is an acceptable trade for a limiter this small.

§A caller’s name

caller_key defaults to the TCP peer address — the socket’s own SocketAddr, delivered through axum’s ConnectInfo extractor, wired at serve_router and serve_tls — because that is the one address a caller cannot lie about. didbot-tls terminates TLS itself with no reverse proxy in front of it, so X-Forwarded-For and X-Real-IP are, by default, ordinary caller-supplied header values: trusting them unconditionally would let a caller mint a fresh budget on every request just by sending a different header, which is not a degraded limiter but a bypassed one. caller_key only reads those headers when trust_forwarded_headers is true — an explicit, off-by-default setting an operator turns on after putting a real reverse proxy in front of this server. Nothing here infers that posture from a header’s mere presence.

Turned on, the rightmost X-Forwarded-For element is read: a proxy appends its own observation to whatever the client sent, so the right-hand end is the proxy’s and the left-hand end is the client’s. This holds for the single trusted proxy a deployment puts in front of one server; a chain of several needs a setting naming how far to walk, and there is none.

A caller reached with no [ConnectInfo] at all — a test harness driving a Router directly, or a deployment that has not wired serve_router or serve_tls — shares the same "unknown" bucket every caller got before this address was available, rather than failing the request.

Structs§

Bucket 🔒
Peer
A handler’s own extraction of the caller’s TCP peer address, for caller_key.
RateLimiter
Counts requests per key (typically a caller’s address, from caller_key) within a fixed window, and says whether the next one is still allowed.
State 🔒
The buckets, and when they were last swept for elapsed windows.

Constants§

DEFAULT_LIMIT
The default number of requests one key may make per WINDOW, for a limiter that does not pick its own. See WINDOW for why this is not a key today.
MAX_KEY_LENGTH
The longest key that gets a bucket of its own.
OVER_LONG_KEY
The single bucket every key over MAX_KEY_LENGTH shares.
WINDOW
The default window a caller’s request count is measured over.

Functions§

address_key 🔒
The budget key for one address: see caller_key.
bucket_key 🔒
The key a bucket is actually filed under: key itself, or the shared OVER_LONG_KEY when it is longer than MAX_KEY_LENGTH.
caller_key
Names the caller a RateLimiter should key its budget on.
forwarded_caller 🔒
The address the reverse proxy in front of this server saw the request arrive from, for a deployment that has opted into trusting it.