DDoS FlowSpec Mitigation: Fine-Grained BGP-Based Traffic Filtering

Last winter we watched a 1.4 Tbps UDP carpet-bomb roll across three of our PoPs at 2:17 AM. The upstream's first instinct was to blackhole the entire /24 — fast, effective, and completely brutal to the legitimate traffic sharing that prefix. We've all been there, and if you're still relying on destination-based Remote Triggered Black Hole as your primary response, you're dropping good customer traffic with the bad. That's exactly where BGP FlowSpec changes the conversation.

Why Destination Blackholing Is Too Blunt for Modern Attacks

For years, RTBH was the hammer we all kept in the toolbox. Under attack? Advertise a /32 with next-hop 192.0.2.1, let your edge routers discard everything to that destination, and wait it out. It works against massive DDoS Volumetric Attack Patterns: Identifying and Absorbing Bandwidth Floods when you just need to keep your backbone alive, but it doesn't distinguish between a 60-byte SYN flood on port 443 and a legitimate customer's API calls hitting the same IP. I learned this the hard way in 2019 when we blackholed a shared anycast IP to stop a DNS reflection flood and accidentally took down a major e-commerce checkout for 11 minutes. The attack was on UDP/53, but we killed TCP/443 too.

FlowSpec, originally defined in RFC 5575 and updated in RFC 8955, gives us a scalpel instead. Instead of saying "drop everything to 203.0.113.50," you can say "drop UDP packets from any source to 203.0.113.50 with source port 19 and packet length > 1400 bytes, at 0 rate." That granularity is what makes DDoS DNS Amplification Defense: Stopping Reflected Attacks at CDN Scale actually sustainable without collateral damage. You aren't filtering an IP; you're filtering a behavior. And because it's carried in BGP, you get the same rapid propagation you loved about RTBH, but with 12 different match components instead of just one destination prefix.

We've pushed this to the point where our mitigation controller can surgically strip 97% of attack traffic while leaving clean traffic untouched on the very same host. The trick is understanding that FlowSpec isn't just an ACL pushed over BGP — it's a BGP NLRI type itself, which means it inherits all of BGP's loop prevention, policy control, and validation mechanics.

# Juniper MX-style FlowSpec rule: Drop chargen reflection to victim
flow {
    route chargen-drop-01 {
        match {
            destination 203.0.113.50/32;
            protocol udp;
            source-port 19;
            packet-length 1400-9000;
            fragment dont-fragment;
        }
        then {
            discard;
            community 65535:666; # FlowSpec discard community
        }
    }
    # More surgical: rate-limit HTTP flood instead of full drop
    route http-flood-limit {
        match {
            destination 203.0.113.0/24;
            protocol tcp;
            destination-port 80,443;
            tcp-flags syn;
            packet-length 60-120;
        }
        then {
            policer 50m; # Rate-limit to 50 Mbps vs discard
            sample;
        }
    }
}

How FlowSpec Rules Actually Propagate as BGP NLRI

Here’s where a lot of teams get tripped up. FlowSpec doesn't use a new protocol — it uses BGP's existing machinery with a new Address Family. You're advertising NLRI with AFI 1 (IPv4) or AFI 2 (IPv6) and SAFI 133 for FlowSpec. Your FlowSpec controller — ours is a cluster of ExaBGP instances — establishes iBGP sessions to route reflectors just like any other BGP speaker. When our detection pipeline flags an attack, the controller composes a FlowSpec NLRI and advertises it. Within seconds, every edge router with that family enabled installs a hardware filter. No SSH, no config push, no waiting for Ansible to converge.

The part you can't skip is validation. Per RFC 8955, a router will only install a FlowSpec rule if the originator is considered authoritative for the destination prefix it covers. That means the best path for the destination prefix must have come from the same neighbor that sent you the FlowSpec rule, or you need to explicitly configure validation bypass — which I don't recommend in production. I've seen networks where a misconfigured route reflector advertised a FlowSpec rule for 0.0.0.0/0 and every router rejected it because the validation check failed. It's frustrating to debug at 3 AM, but it's what prevents a compromised peer from injecting drop rules for prefixes it doesn't own. For more on protocol-level validation behaviors, the HTTP Working Group Specifications are a useful reference for how strict validation should be implemented, even though FlowSpec itself lives in the routing layer.

Propagation delay is the metric we watch closest. In our last measurement window, median time from controller advertisement to filter installation on Broadcom Jericho2 line cards was 1.8 seconds, with 95th percentile at 4.2 seconds. That's fast enough to beat most booter services that ramp over 30-60 seconds. Compare that to manually pushing ACLs via NETCONF, which we've clocked at 45-90 seconds across a 40-PoP footprint. When you're dealing with DDoS Application Layer Attack Techniques: Slowloris, RUDY, and HTTP Floods, those seconds matter — the attack doesn't need to be huge to be effective, it just needs to exhaust conntrack tables before you react.

# ExaBGP controller announcing a fine-grained FlowSpec rule
# Propagates in <2s to all iBGP peers with family ipv4 flow
neighbor 192.0.2.10 {
    router-id 192.0.2.1;
    local-address 192.0.2.1;
    local-as 65000;
    peer-as 65000;
    family {
        ipv4 flow;
        ipv4 unicast;
    }
}
flow route 203.0.113.50-udp-flood {
    # NLRI match components - ordered per RFC 8955 Type 1-12
    rd 65000:100;
    route-distinguisher 65000:100;
    match {
        destination 203.0.113.50/32;
        source 0.0.0.0/0;
        protocol = udp;
        source-port >= 1024;
        destination-port = 389; # CLDAP reflection
        packet-length > 1000;
        dscp = 0;
    }
    then {
        discard;
        # Alternative actions: rate-limit 9600, redirect 65000:999, mark 8
    }
    community [ 65000:4400 no-export ];
}

Match Fields, Actions, and What Line-Rate Filtering Really Costs

FlowSpec v1 gives you 12 match types, and you'll use about seven of them constantly. Destination prefix, source prefix, IP protocol, port(s), destination port, source port, ICMP type/code, TCP flags, packet length, DSCP, and fragment encoding. You can combine them with AND logic — all components in a single rule must match. If you need OR logic, you advertise multiple rules. Don't try to pack every variation into one NLRI; routers compile each rule into a TCAM entry, and overly complex rules actually fragment TCAM usage worse than several simpler ones. We've found that keeping rules to 3-5 match components gives the best hit rate without blowing up filter resources.

Actions are where you get creative beyond just discard. Discard is obvious, but we've gotten more mileage from rate-limit (using the traffic-rate extended community with 0x80000000 flag for discard vs. rate in bytes), redirect to VRF (traffic-redirect to scrubbing center via RT), and remark DSCP + sample for analysis. For example, during a multi-vector attack last quarter, we redirected all UDP/389 CLDAP traffic to our scrubbing VRF, rate-limited SYN floods to 20k pps per edge, and discarded only the chargen vector. That tiered approach kept latency for clean traffic under 15ms instead of the 300ms+ we'd see with coarse filtering. Guidance on handling and sampling remarked traffic efficiently aligns well with best practices from web.dev Articles on performance-aware traffic handling.

Hardware matters more than the RFC lets on. Not every platform supports every match. Older Trident2 chips won't match on packet length reliably, and some Junos versions ignore fragment type unless you reboot the FPC. We maintain a capability matrix per PoP and our controller automatically downgrades rules for PoPs that can't enforce them — e.g., stripping packet-length on those nodes. Here's what we've actually measured in production across our mixed fleet when deploying fine-grained FlowSpec rules versus traditional filters:

Mitigation Action Match Specificity Avg Install Time Attack Traffic Dropped Collateral Drop (Clean Traffic)
RTBH /32 Blackhole Destination IP only 1.2 sec 100% (all traffic) 100% to that IP
FlowSpec Discard (UDP+port) Dst IP + Proto + Dst Port 1.8 sec 99.2% 0.04%
FlowSpec Rate-Limit 50 Mbps Dst IP + TCP Flags + Pkt Len 2.1 sec 94.7% (flood limited) 0.8% (throttled)
FlowSpec Redirect to Scrubbing VRF Dst /24 + Proto + Port Range 2.4 sec 98.5% (scrubbed & returned) 0.12% (latency +8ms)
ACL via NETCONF Push Same as FlowSpec 68 sec 99.1% 0.06%

Deploying FlowSpec in Production: From Controller to Line Card

We've seen too many teams try to inject FlowSpec directly from their border routers and wonder why validation fails. Don't do that. In production, you want a dedicated FlowSpec controller — usually ExaBGP, GoBGP, or FastNetMon — peering via iBGP to your route reflectors. Your routers should be configured to only accept FlowSpec NLRI from that controller's IP, with strict RPKI-style validation enabled (flowspec validation on IOS-XR, family flow validation on Junos). Without that, any compromised eBGP peer could inject a discard rule and blackhole your own traffic. We typically see validation and propagation hit line cards in 5-12 seconds across a 40-router PoP, far faster than any manual ACL push.

The trick is matching precisely without nuking legitimate traffic. If you're getting hammered by 300 Gbps of NTP reflection, you don't want to block all UDP — you want to drop only UDP/123 with packet lengths over 200 bytes destined for your victim prefix. That's where FlowSpec shines compared to blunt RTBH. On our edge, a single rule like that drops 1.4M pps at line rate with zero CPU punt, because the filter is programmed directly into the ASIC's TCAM. For context on why port-specific filtering matters, our writeup on DDoS NTP Reflection Mitigation: Blocking Monlist-Based Amplification Attacks breaks down why monlist responses are so fingerprintable — and why a 5-tuple FlowSpec match is cleaner than upstream ACLs.

Start in monitor mode. Announce your rule with a traffic-rate 0 or sample action first, check your NetFlow/sFlow for hit counts, then flip it to discard. We've rolled back more than one rule that looked perfect in the lab but started catching fragmented DNS because someone forgot to specify fragment dont-fragment. Here's what a battle-tested ExaBGP announcement looks like for that NTP case:

# ExaBGP FlowSpec announcement - validated, rate-limited to controller
announce flow route 100:ntp-monlist-drop {
  match {
    destination 203.0.113.0/24;
    source 0.0.0.0/0;
    protocol udp;
    destination-port 123;
    packet-length > 200 & < 1500;
    fragment dont-fragment is-fragment;
  }
  then {
    discard;
    # use traffic-rate 0; for Cisco, or sample for monitoring
  }
  # NLRI expires in 3600s - auto-withdraw if controller dies
  community [ 8000:100 ];
}

Advanced Patterns and Why Your FlowSpec Rule Silently Fails

Once you've got basic discard working, the real power comes from actions beyond drop. For large volumetric attacks, we often use redirect vrf to steer scrubbing-only traffic to a cleaning cluster instead of dropping it outright — think 800 Gbps of mixed DDoS Memcached Amplification Defense: Neutralizing 51,000x Amplification Factor traffic on UDP/11211 where you still want to inspect for legitimate cache queries. You can also use traffic-marking (DSCP 0x2e) to deprioritize suspicious flows and let policers handle them downstream. On Juniper MX and Cisco ASR 9K, we've validated that redirect and marking actions still forward at 100G line rate, adding only ~2-3 microseconds of latency per packet.

The nasty case is DDoS Carpet Bombing Defense: When Attackers Spread Traffic Across Your Entire Subnet. A single /32 FlowSpec rule won't help when the attacker sprays 50k pps across your entire /20, with each IP getting just enough to evade thresholds. Here we use wildcard FlowSpec with source-port and packet-length matching, not destination. For example: match destination 198.51.100.0/20 + protocol udp + source-port 389 + packet-length 800-1400 catches carpet-bombing CLDAP reflection without touching your /20's real traffic. Pair it with BGP Flowspec v2 (draft-ietf-idr-flowspec-v2) if your gear supports it — it lets you match on TCP flags and TTL which is gold for SYN floods.

If your rule isn't hitting, check these three things in order — they account for 90% of our troubleshooting tickets. First, RFC 5575 validation: the originator must have a covering unicast route for the destination prefix, otherwise Junos/Cisco silently ignores it. Second, filter hardware exhaustion: most Broadcom Trident3/Jericho2 line cards cap at ~3,000-5,000 FlowSpec rules; check show bgp flow-spec detail | inc active and show pbr statistics. Third, your interface is missing flowspec under the address-family. We've seen PoPs where the controller announced correctly for 45 minutes but no filtering happened because the edge interfaces weren't enrolled in the FlowSpec policy. Always verify with show flowspec ipv4 detail and confirm packet counters are incrementing before you declare victory.

Frequently Asked Questions

How is FlowSpec different from Remote Triggered Black Holing (RTBH)?

RTBH drops *all* traffic to a /32 via a next-hop to null0 — it's a sledgehammer. FlowSpec lets you define a 5-12 tuple filter (IP, proto, ports, packet length, TCP flags, fragment) and apply granular actions like discard, rate-limit to 10 Mbps, sample, or redirect to a VRF for scrubbing. You can mitigate a 500 Gbps attack on a single service without taking the host offline.

Can FlowSpec stop memcached or NTP amplification without blocking legitimate services?

Yes, if you match precisely. For memcached, match protocol udp + source-port 11211 + packet-length > 1000 and discard — legitimate memcached should never traverse your edge over the public internet anyway. For NTP, match destination-port 123 + packet-length > 400 to catch monlist responses while allowing normal time sync (90-120 byte packets). That's far more surgical than blocking UDP entirely.

How fast do FlowSpec rules propagate and how long should they stay?

In a well-designed iBGP setup with route reflectors, propagation is 5-15 seconds to all edge routers — we've measured 7.2 seconds average across 60 Cisco ASR 9000s. Always set an automatic expiry. We announce with a community that triggers auto-withdraw after 1-4 hours. Without expiry, stale rules linger and fill TCAM, and you'll forget why port 19 suddenly drops six months later.

What if my upstream or hardware doesn't support FlowSpec?

Not all transit providers or older line cards support it. Check your vendor's docs — Juniper MX/T-series (15.1+), Cisco ASR9K/NCS55K (6.0.2+), and Arista 7500R3 all support it at line rate. If your upstream doesn't, you can still use FlowSpec internally to filter at your edge and use RTBH or BGP communities to signal the upstream to drop. Fall back to traditional ACLs or destination-based RTBH, but push your provider for FlowSpec — it's been an IETF standard since RFC 5575.

Related Articles

References & Standards: HTTP Working Group Specifications · web.dev Articles · IETF Datatracker