DoS Lab 02 — Rate-Limiter Simulator
WARMUP: Chapter 6 (rate-limiting algorithms).
The problem
Rate limiting is the workhorse application-layer availability control — and a defense against credential stuffing, brute force, scraping, and enumeration. The failure modes are subtle: a fixed-window counter permits a ~2× boundary burst, and a single global counter lets one client starve everyone. This lab implements the algorithm most APIs actually want — a token bucket (bounds the sustained rate while allowing controlled bursts) — plus a sliding-window limiter, both with per-key isolation. Time is supplied, so behavior is deterministic and testable.
What you build (lab.py)
TokenBucketLimiter(capacity, refill_rate)—allow(key, now)andtokens(key, now): refill tonow, consume a token if available, cap atcapacity.SlidingWindowLimiter(limit, window)—allow(key, now): at mostlimitrequests in any trailingwindowseconds.simulate(limiter, events)— run(key, time)events and return the allow/deny decisions.
Attack cases the tests cover
- Token bucket: a full bucket allows a burst of
capacity, then denies; tokens refill over time; they cap atcapacity(not unbounded after idle); the sustained rate is bounded torefill_rate; one key's exhaustion doesn't affect another (per-key isolation). - Sliding window: limits within the window and frees requests once they age out; per-key.
Run
pip install -r requirements.txt
LAB_MODULE=solution pytest -q
pytest -q
Hardening / extensions
- Add fixed-window and leaky-bucket variants and compare boundary-burst behavior.
- Make it distributed (a shared store) and discuss atomicity (the P02 web-race lesson).
- Return
429+Retry-Aftersemantics and a per-tenant quota tier. - Add cost-weighted requests (an expensive endpoint consumes more tokens).
Interview / resume
"Implemented token-bucket and sliding-window rate limiters with per-key isolation — bounding the sustained rate while allowing controlled bursts — the core L7 availability and anti-abuse control, and explained why fixed-window/global counters fail."
Limitations: in-memory single-process simulation with supplied time (not a distributed, wall-clock implementation); the leaky-bucket/fixed-window variants are left as extensions.