Back in late 2021, I was on call when a customer's e-commerce origin went dark under a 600 Gbps UDP flood that came out of nowhere on a Tuesday afternoon. Their upstream provider blackholed the /24 before we could even get a packet capture, and that 15 minutes of downtime cost them six figures. If you've run CDN edge infrastructure for any length of time, you've had a version of that call — and it teaches you fast that volumetric DDoS isn't about clever exploits, it's about raw, pipe-clogging bandwidth that doesn't care how good your application code is.
How Bandwidth Floods Saturate Links Before Your Filters Fire
Most engineers picture a volumetric DDoS as a wall of garbage traffic, but what actually kills you is link saturation at the interconnect. Your routers and switches have a fixed packet-per-second budget, and once the ingress interface is full, legitimate SYN-ACKs, HTTP responses, and even BGP keepalives start dropping. I've seen 40G peering ports hit 98% utilization in under 30 seconds from a simple NTP reflection burst, long before any WAF rule had time to evaluate the payload. That's why DDoS NTP Reflection Mitigation: Blocking Monlist-Based Amplification Attacks matters even if you've already patched your own NTP servers — you aren't absorbing your own reflection, you're absorbing everyone else's.
The pattern is brutally simple compared to application-layer stuff. An attacker spoofs your IP, sends a small query to thousands of open reflectors, and each one replies with a response that's 20x to 500x larger. With DNS, that's a 60-byte query turning into a 3,000-byte response. With NTP monlist, it's even worse. The flood that hits you isn't from the attacker directly; it's from legitimate infrastructure that has no idea it's participating. We learned this the hard way when a media customer kept blaming our cache for packet loss, but the loss was actually at their transit provider's border two hops before us.
What makes this different from an DDoS Application Layer Attack Techniques: Slowloris, RUDY, and HTTP Floods is that you can't just scale your origin or add a few more app servers. You've got to absorb traffic at the edge, upstream of the bottleneck. At our CDN, we do this with Anycast and massive overprovisioning, but even that only works if you can detect the spike fast enough. Our old NetFlow-based alerting had a 2-minute delay, which meant we were already dropping customer traffic by the time PagerDuty fired. Now we rely on sampled sFlow on the line cards and push that into a real-time aggregator that trips in under 5 seconds.
#!/bin/bash
# fast volumetric spike detector - runs on edge router mirror
# compares pps on ingress vs 5-minute baseline
INTERFACE="eth0"
BASELINE_PPS=800000
THRESHOLD_MULTIPLIER=3
while true; do
CURRENT_PPS=$(cat /sys/class/net/$INTERFACE/statistics/rx_packets | awk '{print $1}')
sleep 1
NEXT_PPS=$(cat /sys/class/net/$INTERFACE/statistics/rx_packets | awk '{print $1}')
DELTA=$(( NEXT_PPS - CURRENT_PPS ))
if [ "$DELTA" -gt $(( BASELINE_PPS * THRESHOLD_MULTIPLIER )) ]; then
echo "[ALERT] $(date) - Volumetric spike: ${DELTA} pps on $INTERFACE"
# trigger BGP Flowspec or XDP drop - don't wait for human
/usr/sbin/flowspec-announce --action discard --proto udp --port 19,53,123,1900
fi
done
Fingerprinting Volumetric DDoS Patterns in Real Traffic
Not all bandwidth floods look the same, and if you treat every UDP spike as identical you'll end up dropping good traffic. Over the years I've learned to fingerprint the pattern in the first few thousand packets. A classic DNS amplification flood has a very distinct signature: huge UDP packets, source port 53, often with the same query name repeated and a payload size right around 4096 bytes. A pure UDP random-port flood, by contrast, is messy — small packets, random sources, no consistent payload length, just trying to exhaust your packet-processing capacity rather than your byte capacity. Knowing the difference tells you whether to filter by packet size or by source port.
We had a case last year where a gaming customer was hit with what looked like a 400 Gbps flood, but the bits-per-second wasn't the problem — it was 120 million packets per second of 64-byte UDP. Their upstream's 100G links had plenty of bandwidth headroom, but their routers' PPS capacity was blown. We switched our mitigation from byte-based rate limiting to PPS-based policing and advertised a more specific Flowspec rule that matched tiny packets. Within 90 seconds the packet rate collapsed and legitimate players reconnected without us dropping the whole /24. If we'd only looked at gigabits, we would have missed the real constraint entirely.
The other tell I always check is the distribution of source ASNs. A botnet-driven flood will show traffic from tens of thousands of residential ASNs with no clear center of mass. A reflected amplification attack, on the other hand, clusters heavily around hosting providers and networks that host open reflectors. I keep a live top-talkers view for any prefix under attack, and if the top 10 source ASNs account for more than 60% of the traffic, I'm almost certain it's reflection and not direct botnet fire. That distinction drives what you do next — for reflection, you can be aggressive with source-port filters, but for a botnet you can't.
# Python - quick fingerprint for volumetric patterns from sFlow sample
import collections
def fingerprint(sample_packets):
sizes = [p['len'] for p in sample_packets]
ports = [p['src_port'] for p in sample_packets]
asns = [p['src_asn'] for p in sample_packets]
avg_size = sum(sizes) / len(sizes)
top_ports = collections.Counter(ports).most_common(3)
top_asns = collections.Counter(asns).most_common(10)
asn_concentration = sum(c for _, c in top_asns) / len(asns)
print(f"Avg pkt size: {avg_size:.0f} bytes")
print(f"Top src ports: {top_ports}")
print(f"ASN concentration (top10): {asn_concentration:.1%}")
if avg_size > 1000 and 53 in [p for p,_ in top_ports]:
return "Likely DNS amplification"
if avg_size < 200 and asn_concentration < 0.3:
return "Likely direct UDP botnet - high PPS"
if 123 in [p for p,_ in top_ports] and avg_size > 400:
return "Likely NTP reflection"
return "Mixed or unknown volumetric pattern"
# Feed this with 5k sampled packets during an alert window
Traffic Absorption Tactics That Keep the Origin Alive
Absorbing a bandwidth flood isn't about being clever with one edge node; it's about spreading the pain so thin that no single link melts. That's the whole point of Anycast for us. When we announce a customer's prefix from 280+ PoPs, a 1 Tbps flood that would crush a single data center becomes 3-4 Gbps per PoP, which our line cards don't even notice. I've watched attacks that peaked at 1.4 Tbps get absorbed without a single origin request timing out because the flood never converged on one place. Without Anycast, you're just hoping your scrubbing center has a bigger pipe than the attacker, and these days that bet often loses.
But Anycast alone doesn't save you if you've got a stateful device in the path. The biggest mistake I see intermediate teams make is putting a stateful firewall or load balancer in front of a property they want to protect from volumetric attacks. Those boxes keep connection tables, and a flood of 50 million pps will fill that table in seconds and then they fail closed. We moved to stateless XDP/eBPF filters on the edge years ago for this reason. They don't track flows, they just match on header patterns and drop at line rate before the kernel even allocates an skb. Per the HTTP Working Group Specifications, modern HTTP semantics assume the transport can absorb loss, but that assumption falls apart if your edge is dropping everything because a firewall choked.
You also need to coordinate with your transits before you need them. We've got Flowspec peers with our top four transit providers, and we test those sessions monthly. When a customer's prefix is under a 500+ Gbps flood, we don't just absorb locally; we push a Flowspec rule upstream that tells the transit to discard UDP/19, UDP/123, or UDP/53 with payloads over 1200 bytes before it ever reaches our peering. The web.dev Articles on network resilience reminded my team last quarter that performance budgets start at the network layer, not the browser. For volumetric defense, that means you define what "clean" traffic should look like — TCP 80/443, QUIC, maybe a little whitelisted UDP — and you discard the rest upstream during an active attack. Check our detailed guide on DDoS DNS Amplification Defense: Stopping Reflected Attacks at CDN Scale if you're dealing specifically with DNS reflectors, since the filter tuning there is quite different from NTP or SSDP.
| Attack Vector | Typical Peak Bandwidth | Avg Packet Size | Amplification Factor | Primary Absorption Method |
|---|---|---|---|---|
| DNS Amplification (port 53) | 300 - 800 Gbps | 1,400 - 3,900 bytes | 30x - 54x | Anycast + upstream Flowspec drop on large UDP/53 |
| NTP Monlist Reflection (port 123) | 400 - 1,200 Gbps | 440 - 520 bytes | 180x - 556x | Stateless XDP filter on src port 123 + transit discard |
| SSDP Reflection (port 1900) | 150 - 400 Gbps | 1,200 - 1,800 bytes | 28x - 35x | Edge ACL + rate-limit UDP/1900 to zero during event |
| Direct UDP Random Flood | 100 - 600 Gbps / 50-150 Mpps | 64 - 256 bytes | None (botnet) | PPS policing + BGP blackhole for targeted /32 only |
| Memcached Reflection (port 11211) | 500 - 1,700 Gbps | 8,000 - 100,000 bytes | 10,000x - 51,000x | Strict src port 11211 drop at all peering edges |
Deploying Volumetric Absorption: Anycast, Scrubbing, and Edge Filtering in Practice
You don't absorb a 1.3 Tbps flood at your origin firewall — you've already lost by then. The link's saturated three hops upstream. Practical absorption has to happen before traffic ever hits your transit. For us, that means Anycast plus regional scrubbing. We announce the same /24 from 12 PoPs across NA and EU, so a 800 Gbps UDP flood gets naturally shattered into 60-90 Gbps chunks per PoP. No single pipe melts, and our detection has breathing room. Pair that with BGP Flowspec for surgical drops and you've got automated mitigation in under 4 seconds, not the 45 seconds it takes for a human to blackhole a /32.
Here's the workflow we've run in production for the last two years: sFlow/IPFIX telemetry sampled at 1:4096 feeds a Go-based detector. If it sees >15 Mpps of UDP with average packet size < 250 bytes and entropy < 0.3 (classic amplification), it pushes a Flowspec rule to all edge routers. We don't wait for perfect classification. The rule rate-limits the suspect src-port to 5 Gbps per PoP and sends the rest to our scrubbing VLAN where XDP does the heavy lifting. That scrubbing layer is stateless — it'll handle 40 Mpps on a single Xeon Gold 6338 without conntrack falling over, unlike iptables which we've seen collapse at ~1.2 Mpps.
The last mile is edge filtering before your L7 stack even wakes up. Don't let NGINX or your WAF touch this junk. Drop it in XDP or DPDK. We keep a pinned BPF map of known amplifier signatures — memcached, NTP monlist, CLDAP — and update it via a control plane every 60 seconds. If you're still running reflection-prone services, you'll want to read our deep dive on DDoS Memcached Amplification Defense: Neutralizing 51,000x Amplification Factor because a single exposed UDP/11211 can turn your own infrastructure into a 500 Gbps cannon. Filter it at the edge, not the origin.
# XDP + nftables / Flowspec hybrid for volumetric UDP absorption
# 1. Flowspec rule pushed via ExaBGP (rate-limit NTP amp to scrubbing)
flow route 10.0.0.0/24
match destination 203.0.113.0/24
protocol udp
destination-port 123
packet-length 468-600
then discard
then community 64999:666 # scrubbing center redirect
# 2. XDP program on edge (xdp_volumetric.c) - stateless drop
SEC("xdp")
int xdp_drop_amp(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if (eth + 1 > data_end) return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *ip = data + sizeof(*eth);
if (ip + 1 > data_end) return XDP_PASS;
if (ip->protocol != IPPROTO_UDP) return XDP_PASS;
struct udphdr *udp = (void *)ip + sizeof(*ip);
if (udp + 1 > data_end) return XDP_PASS;
// Drop NTP monlist (468 bytes) and memcached reflection
if (ntohs(udp->source) == 123 && ntohs(udp->len) > 440)
return XDP_DROP;
if (ntohs(udp->source) == 11211)
return XDP_DROP;
return XDP_PASS;
}
# Load: ip link set dev eth0 xdp obj xdp_volumetric.o sec xdp
Advanced Evasion Tactics and Troubleshooting Leaky Mitigation
If your graphs show mitigation working but origin latency is still spiking to 800ms, you're probably not failing on volume — you're failing on distribution. The newest playbook isn't a single 600 Gbps spike to one IP, it's 40 Gbps spread across every IP in your /24. We saw this last quarter: 18,000 destinations, each getting 2-3 Gbps of randomized UDP. No threshold tripped, but the aggregate smoked our 100G interconnect. That's classic carpet bombing, and your per-IP policers won't catch it. You need per-prefix aggregate counters and subnet-level Flowspec. We've documented the full strategy in DDoS Carpet Bombing Defense: When Attackers Spread Traffic Across Your Entire Subnet — the key is triggering on /24 total bps, not /32.
The other nightmare is the pulse wave. Attackers know your auto-mitigation takes 30-45 seconds to spin up and 120 seconds to withdraw. So they pulse: 15 seconds on at 900 Gbps, 45 seconds off. Your scrubbing center scales up just in time for the attack to stop, then you withdraw and get hammered again. Your 95th percentile looks clean, but users see a stuttering outage every minute. If you're seeing clean traffic for 40 seconds then a sudden 10M pps burst that disappears before your alerts fire, don't tune your thresholds tighter — you'll just flap. Instead, implement sticky mitigation with a 300-second hold timer and burst detection on 5-second bins. Our writeup on DDoS Pulse Wave Detection: Identifying Intermittent Attack Bursts covers the exact dampening logic we use to keep the scrubbing path hot.
When troubleshooting, start with what your filters *aren't* seeing. Run tcpdump -i any udp and greater 400 on a mirror port during an event — if you still see 30% UDP after mitigation, your BPF map is stale. Check for fragmented packets too; attackers fragment CLDAP to dodge port-based drops, and your XDP program that checks udp->source will miss it because L4 headers aren't in the first fragment. We've had to add an early fragment drop: if ip->frag_off & IP_MF, drop it outright if the 5-minute fragment rate exceeds 50k pps. Legit traffic almost never fragments at that rate anymore.
Frequently Asked Questions
How much bandwidth do I actually need to absorb a modern volumetric attack?
Don't size for average — size for the tail. In 2023-2024, our median volumetric event was 45 Gbps, but the 95th percentile hit 650 Gbps and we logged three events over 1.4 Tbps. If you're buying 20 Gbps of scrubbing capacity, you're covered for 70% of attacks and dead for the ones that make headlines. We recommend at least 500 Gbps of Anycast-absorbed capacity with burst to 1 Tbps via a provider, plus 100G per PoP locally. It's cheaper than 2 hours of downtime.
Why does link saturation happen before my WAF or firewall triggers?
Because your WAF lives at L7 and your firewall's conntrack lives at L4, but the flood saturates L1/L2. A 300 Gbps flood will fill your 100G transit pipe 3x over before a single packet is evaluated by iptables or Cloudflare rules. Routers upstream start tail-dropping, including legitimate SYNs. That's why you've got to push filtering to the network edge with Flowspec, RTBH, or XDP — you need to discard traffic on the wire, not in userspace.
Should I use RTBH or Flowspec for volumetric drops?
RTBH (/32 blackhole) is a sledgehammer — it drops *everything* to the victim IP, including good traffic. Use it only when the origin is already offline. Flowspec is a scalpel: you can say "drop UDP/123 with len >400 to 203.0.113.5" while keeping HTTPS alive. The downside is router support — not all transit honors Flowspec. We use Flowspec internally and RTBH as an upstream community (64999:0) as a last resort. Test both with your transit providers *before* an attack.
How do I tell a volumetric flood from a flash crowd?
Look at three ratios: packet size variance, source ASN entropy, and SYN-to-ACK ratio. A flash crowd has mixed packet sizes (400-1400 bytes), high ASN diversity (1000+ ASNs for a viral event), and bidirectional flows. A volumetric UDP flood has uniform size (e.g., 468 bytes for NTP), low ASN diversity (often <50 reflectors), and 99.9% inbound. If you're seeing 20 Mpps inbound with almost zero outbound, that's not users — that's amplification.