← Blog

Auto-blocking attackers with BGP — an IDS that announces black-hole routes

DEENESRUUK

The cleanest place to drop an attacker isn't the server — it's the edge of the network, before the packets cost you anything. Suri Defender does exactly that: it turns "this IP is attacking us" into a BGP announcement that black-holes the source at the borders and the MikroTik, while the core router never carries a single blackhole route.

The idea: detection → a route

Blocking with iptables works on one box. But when you run an autonomous system (AS 48756 here), the right primitive is RTBH — Remote-Triggered Black Hole. You announce the offending /32 over iBGP tagged with a black-hole community (48756:666); every border router that hears it drops traffic to that destination. One announcement, dropped everywhere, at line rate.

So the whole design is a pipeline that ends in a BGP announce:

SSH / nginx / syslog / FortiGate logs
        │                    │
   CrowdSec scenarios    fail2ban jails
        │ decisions          │ file-only action
        ▼                    ▼
   source-of-truth DB  +  prefix files
        │  union − whitelist
        ▼
   ExaBGP route-injector  →  FRR (bgpd)  →  borders / MikroTik
        announce /32 · community 48756:666
  • CrowdSec parses the logs and runs detection scenarios, exposing a decision stream.
  • fail2ban handles its own jails (SSH, VPN, nginx, mail) and appends banned /32s to a flat file — a deliberately dumb, file-only action, no vtysh.
  • A small FastAPI + SQLite service holds the source of truth, applies TTLs, and regenerates the prefix file.
  • ExaBGP reads the union of those files (minus a whitelist) and emits announce/withdraw to FRR, which reflects the routes onward.

Why a route-injector beats a reconciler

The first version kept blackholes as static routes plus a prefix-list and redistribute static, with a reconciler trying to keep them in sync. Under FRR 10.6 that fought the management daemon constantly: prefix-list trie desyncs, phantom routes, datastore-lock contention, a 2.6 MB frr.conf.

Moving to an ExaBGP route-injector removed the local routing state entirely — the core holds no blackhole routes now. "Blocked" and "announced" became the same fact by construction, not two states a reconciler has to chase. That's the kind of invariant that makes a system stop surprising you at 3 a.m.

Lighter, too

It replaced a heavy Suricata + OpenSearch + Dashboards + Filebeat + EveBox stack — about 2.7 GB of RAM — with a handful of small processes totalling a few hundred megabytes. Same job, an order of magnitude less weight, and a single web UI to see what's currently black-holed and why.

Detection is a solved-ish problem; the interesting part is the response — and for a network, the most elegant response is a route.

Transparency: most posts here are drafted with AI assistance, and the non-English versions are machine-translated by a local LLM and then reviewed. Spotted an error? Please let me know.