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. - Rate
Limiter - 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. SeeWINDOWfor 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_LENGTHshares. - 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:
keyitself, or the sharedOVER_LONG_KEYwhen it is longer thanMAX_KEY_LENGTH. - caller_
key - Names the caller a
RateLimitershould 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.