Warmup Guide — Availability and Denial-of-Service Engineering from First Principles

Zero-to-expert primer for the Availability/DoS module. It builds the "A" in CIA as a discipline: why availability is security, the DoS/DDoS taxonomy (volumetric, protocol, application), amplification/ reflection, the layered mitigation stack, the rate-limiting algorithms (token bucket, leaky bucket, sliding window), and graceful degradation / denial of wallet. Builds on Phase 02 (network) and Phase 06/07 (isolation, cloud). Offline — analysis over attack descriptors and a simulated limiter; no traffic generation (Phase 00).

Table of Contents


Chapter 1: Availability Is Security

Zero background. The classic security triad is C-I-A — Confidentiality, Integrity, Availability. A Denial of Service (DoS) attack targets the third: it doesn't steal or alter data, it makes the system unavailable to legitimate users. An attacker who can't read your database but can keep your service offline during a product launch, an election, or a trading window has inflicted real damage — sometimes more than a data leak. Distributed DoS (DDoS) is the same attack from many sources (a botnet — often compromised IoT devices, Chapter 8 of the Wireless module — or a booter/stresser service rented for dollars), which makes it both more powerful and harder to filter by source.

Why it's an engineering discipline, not just "buy DDoS protection." Different attacks exhaust different resources (bandwidth, connection state, CPU, database, money), and each requires a different mitigation at a different layer (Chapter 5). Throwing a CDN at an application-logic DoS, or rate-limiting a 400 Gbps volumetric flood, fails. The skill is diagnosing which resource is being exhausted and applying the matching control.

The asymmetry that defines DoS. The attacker wins when their cost to send is far less than your cost to process. Amplification (Chapter 3) and asymmetric work (Chapter 4) are both ways of widening that gap. Good availability engineering narrows it — make rejecting bad traffic cheap and make expensive work require proven legitimacy.

Misconception to kill now. "DoS isn't a real security issue — nothing gets stolen." Availability is one third of the security model; a successful DoS can cause more business harm than a breach, and DoS is frequently used as a smokescreen to distract responders during a real intrusion. It is squarely a security concern.

Chapter 2: The DoS / DDoS Taxonomy

Three classes by which resource they exhaust — memorize this, because it dictates the mitigation:

  • Volumetric (L3/L4). Saturate bandwidth or packets-per-second with sheer volume — UDP floods, ICMP floods, amplified reflection (Chapter 3). Measured in Gbps/Mpps; the largest hit terabits per second. Your link/edge is full before traffic even reaches your app. Only upstream capacity (a scrubbing provider, anycast, a CDN) can absorb it — you cannot fix it on your origin server.
  • Protocol / state-exhaustion (L3/L4). Exhaust a finite state table rather than bandwidth. The classic is the SYN flood: the attacker sends TCP SYNs but never completes the handshake, filling the server's half-open-connection table so legitimate handshakes are refused. Also: ACK floods, connection-table and load-balancer exhaustion. Cheap in bandwidth, deadly to state.
  • Application-layer (L7). Make the application do expensive work with few, valid-looking requests — an HTTP flood of real requests, Slowloris (hold many connections open by sending headers one byte at a time), or hitting an expensive endpoint (a heavy search, an unbounded export, a costly regex — ReDoS). These blend with legitimate traffic, so volume-based filtering misses them; you need application awareness (Chapter 4).

Misconception to kill now. "DDoS means a huge traffic flood." Only the volumetric class is about volume. A Slowloris or an expensive-query L7 attack can take down a server with trivial bandwidth from a single host — low and slow, not big and loud.

Chapter 3: Amplification and Reflection

The mechanism. Reflection spoofs the victim's IP as the source of requests to third-party servers, so the responses flood the victim (who never sent anything). Amplification picks protocols where the response is much larger than the request, multiplying the attacker's bandwidth.

The amplification factor is response-size ÷ request-size. The notorious reflectors:

  • DNS (~28–54×), NTP monlist (~556×), memcached (up to ~51,000× — the 1.3 Tbps GitHub 2018 attack), SSDP, CLDAP, SNMP. A small attacker with a spoofable upstream becomes a terabit cannon.

Why it works (the root cause). Two enablers: UDP (connectionless, so the source IP is trivially spoofed — no handshake to prove you are who you claim) and open, abusable services that answer unauthenticated queries with large responses. The defenses are systemic: BCP 38 / source-address validation (ingress filtering) by ISPs to stop spoofed packets leaving a network, and closing/ securing the reflector services (don't expose monlist, memcached, open resolvers to the internet). For the victim, only upstream scrubbing/anycast (Chapter 5) absorbs the resulting flood.

Misconception to kill now. "We just need to block the attacking IPs." In a reflection attack the source IPs are innocent third-party servers (and spoofed), not the attacker — blocking them blocks legitimate services and never reaches the real attacker. The fix is upstream absorption plus the internet-wide hygiene of anti-spoofing and closed reflectors.

Chapter 4: Application-Layer DoS and Asymmetric Work

The core idea: asymmetric work. L7 DoS exploits any place where a cheap request triggers expensive processing — the attacker spends a little, you spend a lot:

  • Slowloris / slow-read. Open many connections and trickle data so each ties up a worker thread indefinitely; a few thousand connections exhaust a thread-per-connection server with almost no bandwidth. Fix: connection timeouts, limits per IP, and async/event-driven servers.
  • Expensive endpoints. A search with no result cap, an export of the whole table, an unindexed query, a zip-bomb upload, image resizing, or crypto operations — one request burns seconds of CPU or a DB connection. Fix: pagination/limits, query budgets, caching, and authentication before expensive work.
  • ReDoS (regular-expression DoS). A "catastrophic backtracking" regex on attacker input takes exponential time ((a+)+$ against aaaa...!). Fix: linear-time regex engines (RE2), input length caps, timeouts.
  • Algorithmic complexity attacks. Inputs crafted to hit a data structure's worst case (hash collisions → O(n²); the reason languages randomize hash seeds).
  • Amplification within your app: one API call that fans out to many backend calls, or a cache-busting pattern that forces origin work (the cousin of web cache poisoning, P02).

The defining property: L7 attacks use valid-looking, low-volume requests, so source/volume filtering misses them. You need application awareness — rate-limit by user/endpoint, bound the work per request, require auth before the expensive path, and shed load (Chapter 7).

Misconception to kill now. "Our autoscaler will handle the load." Autoscaling against an L7 attack just scales your bill (denial of wallet, Chapter 7) and your database connections while the attacker spends pennies. Bound the expensive work and require legitimacy; don't buy your way through asymmetry.

Chapter 5: The Mitigation Stack — Match the Layer

The one rule: the mitigation must match the attack's layer. A control for one class does nothing for another.

Attack classWhat's exhaustedThe mitigation that works
Volumetric (L3/4)bandwidth / PPSupstream: anycast (spread load across many POPs), a scrubbing center, a CDN absorbing/filtering at the edge — cannot be fixed on the origin
Protocol (L3/4)connection/state tablesSYN cookies (stateless handshake — no half-open state), connection rate limits, stateless edge proxies, firewalls/IPS
Application (L7)CPU / DB / threads / moneyrate limiting (Chapter 6), WAF (bot/pattern filtering), caching/CDN for cacheable content, query/work budgets, auth-before-expense, CAPTCHA/proof-of-work for suspect clients, load shedding

Cross-cutting controls: anycast (the same IP announced from many locations so an attack is diluted and absorbed near its source), always-on vs on-demand DDoS protection, generous upstream capacity headroom, and an incident runbook (who declares, who engages the scrubbing provider, how you communicate). Architecturally, statelessness at the edge and isolation/segmentation (P06/P07) limit how far an attack propagates.

Misconception to kill now. "We have a WAF/CDN, so we're protected from DDoS." A CDN/WAF helps with L7 and cacheable volumetric, but a non-cacheable volumetric flood needs scrubbing/anycast capacity, and a protocol flood needs SYN cookies/state limits. There is no single box; you defend per layer.

Chapter 6: Rate-Limiting Algorithms

Rate limiting is the workhorse L7 control: cap how many requests a client (key) may make per unit time, rejecting or queuing the excess. The algorithms (and their tradeoffs):

  • Fixed window. Count requests per key per fixed interval (e.g. per minute); reset at the boundary. Simple, but allows a 2× burst at the boundary (max requests at the end of one window + the start of the next).
  • Sliding window. Smooths the boundary burst by weighting the previous window or tracking timestamps over a rolling interval. More accurate, slightly more state.
  • Token bucket (the most common). A bucket holds up to capacity tokens and refills at rate tokens/sec; each request consumes a token; if the bucket is empty, the request is limited. This allows controlled bursts (up to capacity) while bounding the sustained rate to rate — the behavior most APIs want. (Lab 02 implements this.)
  • Leaky bucket. Requests enter a queue that drains at a fixed rate; it smooths output to a constant rate (no bursts) — good for protecting a downstream that needs steady input.

The engineering details that matter: rate-limit per key (per user / API key / IP — and beware that IP is shared/spoofable and NAT groups many users), isolate keys so one abuser doesn't starve others, make the limiter distributed (a shared store) so it holds across many servers, and return 429 Too Many Requests with a Retry-After. Rate limiting is also a security control beyond DoS: it throttles credential stuffing, brute force, scraping, and enumeration.

Misconception to kill now. "A fixed per-minute counter is good enough." Fixed windows permit a near- 2× burst across the boundary, and a single global counter lets one client starve everyone. Token bucket (controlled bursts, sustained-rate bound) with per-key isolation is the usual right answer.

Chapter 7: Graceful Degradation, Load Shedding, and Denial of Wallet

Load shedding. When overloaded, deliberately drop or reject low-priority work to keep the core serving — return 503 fast for non-critical endpoints, prioritize authenticated/paying users, and fail fast rather than letting every request pile up and time out (which collapses everything). Shedding load is choosing who to disappoint instead of failing for everyone.

Backpressure and graceful degradation. Propagate "I'm full" upstream (bounded queues, timeouts, circuit breakers — P01/P11 bounded-concurrency lesson) so callers slow down instead of overwhelming a struggling dependency. Degrade gracefully: serve stale cache, disable expensive features, show a lightweight page — partial service beats total outage.

Denial of Wallet (the cloud-era DoS). In autoscaling/serverless, an attacker can't take you down (you scale) — instead they run up an enormous bill, or exhaust a quota, or drain a third-party API budget (the AI-agent "denial of wallet" of P11). The defenses are budgets and caps: max autoscale limits, per-tenant quotas, spend alerts, and — again — rate limiting and auth-before-expense.

The whole-picture lesson. Availability under attack is a system property: upstream capacity for volumetric, stateless edges for protocol, rate limits + work budgets + auth for L7, and load shedding + backpressure + budgets so that when you are overwhelmed you degrade predictably instead of collapsing — and you don't bankrupt yourself scaling against an attacker who spends nothing.

Misconception to kill now. "Scale up until the attack stops." Against asymmetric L7 or a volumetric flood, scaling converts a downtime problem into a bankruptcy problem (denial of wallet) and often just moves the bottleneck to the database. Caps, shedding, and matched-layer mitigation — not unlimited scaling — are the answer.

Lab Walkthrough Guidance

Three labs turn availability into runnable engineering (offline):

  1. Lab 01 — DDoS Classification & Mitigation Planner. From an attack descriptor (layer, vector, rate, amplification, spoofed, cacheable), classify it and recommend the correct mitigation layer — and reject mismatched ones (you can't rate-limit a volumetric flood). Chapters 2–5.
  2. Lab 02 — Rate-Limiter Simulator. Implement a deterministic token-bucket limiter (capacity + refill rate) with per-key isolation and a sliding-window variant; verify it bounds the sustained rate, allows bursts up to capacity, and isolates keys. Chapter 6.
  3. Lab 03 — Rate-Limiter Variants. Add the fixed-window and leaky-bucket algorithms and demonstrate the fixed-window boundary burst (≈2× the nominal rate) that a naive counter allows — the algorithm tradeoffs in code (Chapter 6).
LAB_MODULE=solution pytest -q   # reference (passes)
pytest -q                        # your implementation after the TODOs

Success Criteria

You have internalized this module when you can, without notes:

  1. Classify a DoS attack by layer and vector and explain the attacker's asymmetric advantage.
  2. Explain amplification/reflection, the amplification factor, and why blocking source IPs fails.
  3. Explain L7 / asymmetric-work attacks (Slowloris, expensive endpoints, ReDoS) and why volume filtering misses them.
  4. Match each attack class to the mitigation layer that actually works (and why a CDN ≠ full DDoS protection).
  5. Implement and compare token-bucket, leaky-bucket, fixed- and sliding-window rate limiting, with per-key isolation.
  6. Explain load shedding, backpressure, graceful degradation, and denial of wallet.

Common Mistakes

  • Treating DoS as "not real security," or ignoring that it's used as a smokescreen for intrusion.
  • Trying to fix a volumetric flood on the origin (it needs upstream scrubbing/anycast).
  • Blocking the source IPs of a reflection attack (they're innocent/spoofed third parties).
  • Assuming a CDN/WAF covers all DDoS (it doesn't cover non-cacheable volumetric or protocol floods).
  • Fixed-window rate limits (boundary burst) and a single global counter (one client starves all).
  • Autoscaling against an attack (denial of wallet) instead of caps, shedding, and matched mitigation.
  • No load shedding / backpressure, so overload collapses everything instead of degrading.

Interview Q&A

Q: Classify DDoS attacks and match each to a mitigation. A: Volumetric (L3/4 — saturate bandwidth/PPS, often amplified): only upstream anycast/scrubbing/CDN absorbs it — you can't fix it on the origin. Protocol (L3/4 — exhaust state, e.g. SYN flood): SYN cookies, connection limits, stateless edges. Application (L7 — asymmetric work, e.g. Slowloris, expensive queries): rate limiting, WAF, caching, work budgets, auth-before-expense, load shedding. The rule is match the mitigation to the layer; a CDN doesn't stop a protocol flood and SYN cookies don't stop an expensive-query L7 attack.

Q: How does amplification work and why can't you just block the attacking IPs? A: The attacker spoofs the victim's IP as the source of small queries to open UDP services (DNS, NTP monlist, memcached at ~51,000×), and the large responses flood the victim. The "sources" are innocent third-party reflectors (and spoofed), so blocking them blocks legitimate services and never reaches the attacker. Real fixes are systemic — BCP 38 source-address validation by ISPs and closing open reflectors — plus upstream scrubbing for the victim.

Q: Implement a token-bucket rate limiter and explain its advantage. A: A bucket holds up to capacity tokens, refilling at rate/sec; each request takes a token, and an empty bucket means limited. It bounds the sustained rate to rate while allowing controlled bursts up to capacity — what most APIs want — unlike a fixed window (which permits a ~2× boundary burst) or a leaky bucket (which forbids bursts entirely). I'd key it per user/API key, isolate keys, back it with a shared store for distributed enforcement, and return 429 with Retry-After.

Q: An autoscaling service is under an L7 attack — what do you do? A: Don't just scale (that's denial of wallet — you pay, the attacker doesn't, and the DB becomes the bottleneck). Rate-limit per key, require auth before expensive paths, cap autoscale and per-tenant quotas, shed low-priority load (503 fast), cache what's cacheable, put a WAF/bot filter in front, and engage scrubbing if there's a volumetric component. Bound the asymmetric work; don't buy through it.

References

  • Cloudflare/Akamai/AWS DDoS taxonomy and "Learning Center" articles; the GitHub 2018 memcached 1.3 Tbps and Dyn 2016 (Mirai) incidents.
  • BCP 38 / RFC 2827 (ingress filtering / source-address validation); SYN cookies (Bernstein).
  • Rate-limiting: token bucket / leaky bucket; the "sliding window" approaches; RE2 (linear-time regex).
  • Google SRE Book (load shedding, graceful degradation, cascading failures); Phase cross-refs: P02 (network/HTTP), P06/P07 (isolation, cloud, egress), P11 (denial of wallet).