DEV Community

Cover image for Defending Against Automated Botnet Floods Without Degrading Container CPU Footprints
qyleron-dev
qyleron-dev

Posted on Originally published at qyleron.com

Defending Against Automated Botnet Floods Without Degrading Container CPU Footprints

A botnet-driven connection flood doesn't just threaten availability, it can quietly double or triple your container CPU bill before anyone notices, depending entirely on how your listener is built. This guide compares how synchronous blocking engines and asynchronous event loops handle the same flood at the kernel level, and why that difference determines whether your infrastructure degrades gracefully or falls over.

When people talk about surviving a connection flood, the conversation usually jumps straight to rate limiting and network-layer mitigation. Those matter, but they don't explain why two services under the exact same flood, same packet rate, same source distribution, can show wildly different CPU graphs. That difference comes down to a decision made long before the flood ever starts: how the listener itself is architected to handle concurrent connections.

This matters directly to SREs and cloud architects because CPU footprint under load determines your autoscaling behavior, your bin-packing density, and ultimately your infrastructure cost during exactly the kind of event you can't schedule around.

1. How synchronous blocking engines spend kernel CPU

Synchronous, thread-per-connection listeners spend rising kernel CPU on context-switching as connection count grows, independent of how much real traffic those connections carry. A synchronous, thread-per-connection (or process-per-connection) architecture handles concurrency by handing each incoming connection its own thread, which then blocks on I/O until that connection has data to process. This model is simple to reason about, but it has a structural cost that only becomes visible under load: every thread the kernel schedules carries real overhead, independent of whether that thread is doing useful work.

  • Context switching dominates under high concurrency. Once thread count climbs into the thousands, the kernel spends a growing share of every CPU cycle just deciding which thread runs next, rather than executing application code. This overhead scales with connection count, not with actual traffic volume.
  • Each thread carries a fixed memory and scheduling cost. A blocked thread waiting on a slow or intentionally slow-drip connection, exactly what a flood does, still occupies a kernel scheduling slot and its full stack allocation, even while doing nothing.
  • Lock contention compounds under pressure. Many synchronous frameworks serialize access to shared state, connection pools, logging buffers, rate-limit counters, behind a lock. As thread count rises, contention on that lock rises with it, and CPU that should be doing I/O work goes into spinning or waiting on the lock instead.

The practical result: CPU usage under a synchronous architecture rises non-linearly as connection count increases, because each additional connection adds fixed kernel-level overhead on top of whatever actual work it represents. A flood of mostly-idle, slow-drip connections, the kind a botnet is cheap to generate, is close to a worst case for this model, since it maximizes thread count while minimizing actual useful throughput per thread.

A useful diagnostic: if CPU usage during an incident tracks connection count more closely than it tracks request throughput, that's a strong sign the bottleneck is architectural, kernel scheduling and context-switch overhead, rather than application logic actually working harder.

2. How async event loops absorb the same flood

Async event-loop architectures decouple CPU cost from connection count, since a small fixed pool of threads multiplexes across thousands of connections via non-blocking I/O. An asynchronous, event-loop-based architecture handles concurrency differently: a small, fixed number of OS threads, often just one per CPU core, cooperatively multiplexes across thousands of connections using non-blocking I/O and an event notification mechanism like epoll on Linux.

  • Connection count no longer drives thread count. Whether the event loop is juggling 100 connections or 100,000, it's still running on the same small, fixed set of OS threads, so the kernel-level context-switching cost that scales with connection count in the synchronous model simply doesn't apply here.
  • Idle connections cost almost nothing. A connection with no data to process just sits in the kernel's I/O readiness table until it has something to report. It doesn't hold a thread, a stack, or a scheduling slot the way a blocked synchronous thread does.
  • CPU usage tracks actual work, not connection count. Because the event loop only spends cycles on connections that have real I/O ready to process, CPU usage under an async architecture scales much more closely with actual request throughput than with the raw number of open connections.

This is precisely why a slow-drip flood, designed to maximize open connection count while minimizing real work per connection, is far less effective against an async architecture: the attack's core mechanism, exhausting per-connection kernel resources, doesn't have the same leverage when connections aren't consuming dedicated threads in the first place.

3. What this means for container CPU footprints specifically

A synchronous listener's container CPU limit must be sized for worst-case connection count, while an async listener's CPU footprint stays close to its normal-load baseline even under a flood. In a containerized environment, this architectural difference has a direct, measurable cost implication. A container running a synchronous, thread-per-connection listener needs its CPU limit sized for worst-case concurrent connection count, not average throughput, because that's what drives its actual CPU consumption under load. Undersize it, and a flood causes CPU throttling that degrades every other workload sharing that node; oversize it defensively, and you're paying for headroom that sits idle outside of incident windows.

An async architecture decouples those two numbers. Its CPU footprint under a connection flood stays much closer to its footprint under normal load, because the mechanism driving synchronous CPU spikes, kernel scheduling overhead scaling with thread count, isn't in play. This makes capacity planning materially simpler: you size for expected request throughput, not for a worst-case connection count that a botnet can generate for free.

4. Operational signals worth tracking

A synchronous listener's CPU usage tracks connection count under a flood, while an async listener's CPU usage stays tied to actual request throughput.

  • Ratio of open connections to CPU usage. A synchronous listener will show this ratio climbing during a flood; an async one should stay comparatively flat, since connection count and CPU cost are decoupled by design.
  • Kernel context-switch rate (visible via vmstat or /proc/stat on the host) during a suspected flood. A sharp rise here, disproportionate to actual request volume, points directly at thread-per-connection overhead rather than application-layer load.
  • Container throttling events correlated with connection count rather than request rate. If your orchestrator's CPU throttling metric spikes in lockstep with concurrent connections rather than requests per second, the listener architecture is very likely the root cause, not insufficient CPU limits.
  • Thread count inside the container during an incident. A synchronous architecture will show thread count scaling directly with connection count; an async one will show a flat, small thread count regardless of load.

This same asymmetry is what makes a lightweight async decoy service a useful tool for absorbing and studying flood traffic directly: because it doesn't spend a thread per connection, it can safely accept far more concurrent flood traffic on the same CPU footprint as a normal service, generating detailed telemetry on the attack instead of falling over from it. This is the same architectural principle Echidra OSS is built on.

The floods this architecture absorbs most often originate from the same botnet infrastructure covered in Analyzing SSH Brute-Force Patterns, the credential-spraying stage there and the connection-flood stage here are frequently run from overlapping pools of compromised hosts.

See it in action

We ran a live SYN flood against a deployed Echidra OSS honeypot and measured what actually happened to its CPU and memory in real time.

Result: CPU usage rose by only 0.4% during the flood, and memory (RSS) stayed below its pre-flood baseline throughout. No container restarts, no degraded response times.

See Deception-Based Detection in Action

Echidra OSS is an open-source cyber deception engine (AGPLv3). Deploy a lightweight decoy alongside your real infrastructure and map unauthorized access attempts to MITRE ATT&CK in real time.

  • Lightweight: 83MB RAM floor vs 180MB for legacy honeypots.
  • Scales cleanly: <20% CPU under a 5,000-packet flood.

View the Deployment Guide ยท Star it on GitHub

Top comments (0)