GitHub

Escalation ladders

A single mitigation method fires the same response the instant an attack is detected. An escalation ladder instead steps the response up the longer an attack persists — declaratively, with no callback scripts.

Most deployments are fine with a single mitigation: method. Reach for a ladder only when you want a gentler response first (e.g. alert, then surgical filtering) before resorting to a full blackhole.

escalation:                         # supersedes `mitigation` when present
  - { after_seconds: 0,   action: none }       # alert only at first
  - { after_seconds: 30,  action: flowspec }   # still under attack after 30s → surgical drop
  - { after_seconds: 90,  action: divert }     # still under attack after 90s → scrub
  - { after_seconds: 300, action: blackhole }  # still under attack after 300s → blackhole
flowspec:
  action: discard
scrubbing:
  next_hop: "192.0.2.100"   # scrubbing center; see Traffic diversion
  community: "65000:200"

A bgp block is required for any rung to announce; the flowspec and scrubbing blocks above are needed only for the flowspec and divert rungs.

How rungs apply

Each rung's after_seconds is measured from the attack's start. A rung applies once that much time has elapsed and the ban is still active. (A ban is only withdrawn after traffic has stayed below threshold for the configured cool-down period, ban.unban_hysteresis_seconds — see Configuration.) The rung clock keeps running from the attack's start throughout.

  • The first rung must be at 0 seconds.
  • A ladder may have at most 5 rungs — this is a hard limit; the daemon refuses to start with a longer ladder.
  • action is none (alert only), flowspec, divert (scrubbing), or blackhole.
  • A ladder may only hold or strengthen the response: none < flowspec < divert < blackhole. De-escalating between rungs is a config error.

Make-before-break

Climbing to a rung is make-before-break: the new rung is announced first, and the previous one is withdrawn only after that succeeds — so the victim is never momentarily unprotected mid-switch. If the announce fails, the ban holds the working rung and retries on the next tick. If several rungs come due at once (a long-running attack, or the daemon catching up after a pause), the ban jumps straight to the highest due rung and never announces the rungs it skips.

A flowspec or divert rung whose announce the upstream peer rejects falls back to blackhole (unless you set ban.fallback: none in Configuration); the API and notifications surface this via the fell_back_from field. Blackhole and alert-only rungs have no fallback.

The one exception to the announce-then-withdraw dance is divert → blackhole: both announce the same host route (the victim's /32 or /128), so the blackhole announcement atomically replaces the divert route — no separate withdraw, no gap. Cross-family switches (FlowSpec rules vs. a host route) follow the make-before-break case above.

Lifecycle and compatibility

The ladder is per-hostgroup overridable and shares the rest of the ban lifecycle: TTL auto-withdrawal, the max_active_bans cap, the blast-radius caps (max_banned_fraction, max_bans_per_window), the absolute whitelist, and dry-run (which advances the ladder and logs each rung but never announces).

When no escalation block is set, the single mitigation method behaves exactly as a one-rung ladder at 0 seconds — full backward compatibility. Each ban in /api/v1/bans exposes the full configured ladder (escalation), the index of the current rung (escalation_step), and the active rung's method (method).