Everything built so far in this unit reads a capture after the fact. This article is different: the same logic, pointed at a live interface instead of a saved file, becomes an early warning system, because it watches the rate of traffic, not the total.
Analogy: The turnstile
Catching an attack while it is still happening, rather than reconstructing it later, means watching the rate of traffic rather than its total. Picture a turnstile that only ever counts the last few minutes: as new packets arrive they are added, and as old ones age past the window they are dropped, so the count always reflects a recent rate, not everything that has ever passed through.
The algorithm
Python concept: Sliding-window flood detection
Input: a stream of packets, a window length, and an alert threshold. Output: an alert whenever one destination is flooded.
- For each packet, read its destination address and arrival time.
- Add this arrival time to that destination's window.
- Remove from the window every time older than the window length.
- Count the times remaining in the window: this is the recent packet rate.
- If the count exceeds the threshold, raise an alert for that destination.
- Continue with the next packet.
Building the detector
detect_in_progress.py
# detect_in_progress.py: a sliding-window flood detector
from collections import defaultdict, deque
import dpkt, socket
from open_capture import read_packets
WINDOW_SECONDS = 10
THRESHOLD = 500
packet_times = defaultdict(deque)
def record_and_check(dst_ip, timestamp):
window = packet_times[dst_ip]
window.append(timestamp)
while window and window[0] < timestamp - WINDOW_SECONDS:
window.popleft()
if len(window) > THRESHOLD:
print(f"ALERT: {dst_ip} received {len(window)} packets "
f"in the last {WINDOW_SECONDS} seconds")
for ts, raw in read_packets("capture.pcap"):
try:
ip = dpkt.ethernet.Ethernet(raw).data
record_and_check(socket.inet_ntoa(ip.dst), ts)
except AttributeError:
continue
Run against the same flood capture from two articles ago:
Sample output (first few lines of many)
ALERT: 10.0.0.5 received 501 packets in the last 10 seconds
ALERT: 10.0.0.5 received 502 packets in the last 10 seconds
ALERT: 10.0.0.5 received 503 packets in the last 10 seconds
...
ALERT: 10.0.0.5 received 1805 packets in the last 10 seconds
Python concept: deque as a sliding window
A deque (double-ended queue) can add and remove from either end cheaply. Here it holds only the packet timestamps inside the last ten seconds: new ones are appended on the right, and popleft() trims old ones from the left. The length of the deque is therefore always the recent packet rate, which is what the threshold is compared against.
An alert that never stops is a problem of its own
The output above keeps climbing: 501, then 502, then every single packet after that for as long as the flood lasts, because the condition len(window) > THRESHOLD is true for every one of them. A real deployment of this detector would drown an operator in alerts within seconds.
Caution: A deployed detector needs to rate-limit itself, not just the window
The fix is not to the window logic, which is correct; it is to add a second, separate rule about alerting, such as firing once when the threshold is first crossed and staying quiet until the count drops back below it, or sending at most one alert per destination per minute. This is a common, easy-to-miss step: a detection rule and an alerting policy are two different decisions, and conflating them is how a genuinely useful detector becomes one everyone learns to ignore.
Because the detector watches a ten-second window rather than a running total, each alert reflects the rate right now, which is exactly what a real defence acts on when it decides to route the traffic away.
Real incident: The GitHub attack, 2018: 1.35 terabits in one spike
On 28 February 2018, the software-hosting site GitHub was hit by what was then the largest denial-of-service attack ever recorded, peaking at 1.35 terabits per second. The attackers used a technique called memcached amplification: they sent small forged requests to thousands of misconfigured memcached servers, each of which replied to the victim with a response many times larger, multiplying the attackers' bandwidth. The spike was detected almost immediately by exactly the kind of rate monitoring this article builds, traffic to GitHub crossing a threshold no legitimate load could explain, and mitigation routed the flood away within about ten minutes.
What this teaches: the GitHub attack was survived not because anyone read every packet, which at 1.35 terabits per second is impossible, but because a rate-based detector noticed the window count cross a threshold and acted. The sliding-window detector in this article is a scaled-down version of the real defence, and it teaches the central lesson of DDoS response: watch the rate, not the total, and act on the slope.
Try it yourself: From a file to a live feed
Point this same logic at a live interface instead of a saved file, by swapping dpkt.pcap.Reader for a live capture source, and it becomes an early warning system watching traffic in real time. The record_and_check() function itself would not need to change at all; only where ts and raw come from does.
Check your understanding
Question 1: Using the turnstile analogy, explain why a sliding window detects an attack in progress better than a counter that only ever increases. Relate your answer to how the GitHub attack was mitigated.
A plain counter that only ever grows would eventually cross any threshold on normal traffic too, given enough time, and tells you nothing about whether traffic is high right now versus simply having accumulated over a long period. A sliding window only counts the last few seconds, so it directly reflects the current rate, which is what crossed GitHub's threshold and triggered mitigation within minutes rather than after the fact.
Question 2: Why does detect_in_progress.py keep alerting on almost every packet once the flood starts, and what is the correct fix?
Because the condition len(window) > THRESHOLD stays true for every packet arriving while the flood continues, so each one triggers its own alert. The fix is not to change the detection logic, which is correctly measuring the rate, but to add a separate alerting policy, such as firing once per crossing or rate-limiting alerts per destination, so the detection and the notification are treated as two different decisions.
Question 3: What would need to change to turn detect_in_progress.py from an offline analysis tool into a live early-warning system?
Only the source of ts and raw: instead of dpkt.pcap.Reader reading a saved .pcap file, a live capture source feeding packets as they arrive on an interface. The record_and_check() function, which does the actual sliding-window detection, needs no change at all.
Key takeaway: Key takeaway
A sliding window, built from a deque, turns a stream of packets into a rate that always reflects right now, not the whole history. That rate is what caught the largest DDoS attack ever recorded within minutes, and the same logic, pointed at a live feed instead of a file, is an early warning system.

