SYN Flood Protection
How Threatmatic built syn-guard, a real-time SYN flood detector and blocker using ETW and WFP, to protect our ZTNA fabric from DDoS attacks.
When a single compromised endpoint on your ZTNA fabric starts hammering another service with thousands of TCP connection attempts per second, you have milliseconds to respond. A SYN flood doesn't care about your authentication layer — it's a TCP/IP problem, and by the time your application logs the tenth connection, the attacker has already opened a thousand more.
This is the story of how we built syn-guard, a real-time SYN flood detector that lives in the Windows kernel event stream, and how Threatmatic now observes and blocks these attacks fabric-wide before they ever reach the target.
The Problem: Rate Limits Don't Work
We started by asking: Can we just rate-limit TCP connections per source IP?
The answer led us down a path through Windows Filtering Platform, Quality of Service policies, raw sockets, TCP table polling, Event Log tracing, and finally Event Tracing for Windows — each one teaching us something about why SYN floods are so hard to stop.
Attempt 1: WFP Alone (Graceful Blocking)
Windows Filtering Platform lets you block connections at the ALE layer — after the TCP three-way handshake completes but before the application sees it. We built wfp-block, which works perfectly for blocking a specific process from making new outbound connections:
engine.BlockProcess("C:\\path\\app.exe") // ✓ new connections blocked
// ✓ established connections surviveBut WFP filters work on aggregate traffic conditions — IP address, port, protocol, direction. They can't count. They can't say "if this IP made 100 connections in 5 seconds, block it." They can only say "if traffic matches this IP, block it" — a binary decision.
For a real SYN flood detector, we needed to count connections per source IP and then decide whether to block. That requires a state machine.
Attempt 2: Raw Sockets with SIO_RCVALL
Our first instinct: capture the raw TCP packets ourselves. Windows has SIO_RCVALL — a socket option that lets you see every packet on the wire.
socket.SetSocketOption(SO_RCVALL, RCVALL_IPLEVEL)
// Read raw packets, parse TCP header, count SYNs per source IPIt works great on Ethernet. On Wi-Fi, you get nothing. Microsoft disabled SIO_RCVALL on wireless adapters in Vista — a security decision to prevent rogue apps from sniffing network traffic. We tested on the Mac using ethernet dongle, it worked. On Wi-Fi? Dead.
Dead end for Threatmatic: most users are on laptops with Wi-Fi. We needed something that works everywhere.
Attempt 3: TCP Table Polling
Windows exposes GetExtendedTcpTable() — query the kernel's live TCP table, see all active connections, parse them in user-mode code.
for {
tcpTable := GetExtendedTcpTable()
for _, conn := range tcpTable {
if conn.RemoteIP == attacker {
count++
}
}
time.Sleep(100 * time.Millisecond) // Poll every 100ms
}The problem: on a LAN, a TCP connection opens and closes in ~4 milliseconds. Our 100ms polling misses 96% of them. We tried lowering the interval to 10ms, but that just hammered the kernel. Meanwhile, a SYN flood can establish 10,000 connections per second from a single IP.
We were polling every 10ms and missing connections that lasted 4ms. The math doesn't work.
Dead end: polling is fundamentally too slow.
Attempt 4: Windows Security Event Log (5156)
Windows logs failed connection attempts to the Security Event Log (Event ID 5156). We could:
1. Enable auditing (Group Policy or registry)
2. Parse the Event Log XML
3. Count connections per source IP
4. Trigger WFP block when threshold exceededThis could work, but it's slow (events are batched), requires parsing XML for every packet, and uses the application-level Event Log subsystem. By the time the XML is parsed, thousands more connections have arrived.
Dead end: too much latency between the packet and the detection.
The Solution: Event Tracing for Windows (ETW)
Then we discovered ETW's Microsoft-Windows-TCPIP provider — a real-time event stream directly from tcpip.sys in the kernel.
ETW fires an event (ID 1465 in the modern manifest-based format) for every inbound TCP connection, before WFP processes it. The event contains:
- Source IP (offset 12 in the payload)
- Destination port (offset 30 in the payload)
- All in network byte order, ready to parse
The API surface is low-level (P/Invoke into sechost.dll), but it's real-time, lossless, and works on Wi-Fi.
// Start a real-time ETW session
session := StartSession("Threatmatic-SynGuard")
session.EnableProvider(TCPIP_PROVIDER)
// Attach a callback
session.OpenConsumer(func(event *EventRecord) {
srcIP := net.IPv4(
event.Payload[12],
event.Payload[13],
event.Payload[14],
event.Payload[15],
)
dstPort := binary.BigEndian.Uint16(event.Payload[30:32])
tracker.Record(srcIP, dstPort)
})
// Process events in real-time
session.Process()Every connection — legitimate or malicious — appears in the callback within microseconds of the TCP three-way handshake completing. We can now count in real-time.
Architecture: Detection + Prevention
With reliable, real-time connection events, we built a two-stage system:
Stage 1: Detection (Tracker)
A sliding window counter tracks connections per source IP:
config := TrackerConfig{
Threshold: 200, // 200 connections
Window: 5 * time.Second, // per 5 seconds
BlockDuration: 60 * time.Second, // block for 60 seconds
}
tracker := NewTracker(config, func(srcIP net.IP, count int) {
// Callback: 200 connections in 5s detected
// → 40 connections/sec sustained rate
// → almost certainly a flood
log.Printf("⚠ FLOOD DETECTED src=%s count=%d", srcIP, count)
})
// Feed ETW events into the tracker
monitor.OnConnection = func(srcIP net.IP, dstPort uint16) {
tracker.Record(srcIP)
}For a legitimate user: 5-20 new connections per second across all apps. For a flood: 100+ from a single IP. The threshold is tunable per deployment.
Stage 2: Prevention (WFP Block)
When the threshold is exceeded, install a WFP filter to block that source IP:
engine := wfp.Open()
filterID, err := engine.BlockSourceIP(
srcIP,
0, // all ports
"SynGuard: block " + srcIP.String(),
)
// 60 seconds later, remove the filter
time.AfterFunc(60*time.Second, func() {
engine.UnblockSourceIP(filterID)
})The block is:
- Immediate — packets drop at the ALE layer, before TCP stack processing
- Per-IP — other sources are unaffected
- Temporary — auto-expires after the configured duration
- User-mode — no kernel driver, just WFP's existing infrastructure
Threatmatic Integration: Fabric-Wide Visibility & Control
Now imagine this running on every Threatmatic-enrolled endpoint, all reporting to the Threatmatic console and engine:
┌─────────────────────────────────────────────────────┐
│ Threatmatic Console │
│ ┌──────────────────────────────────────────────┐ │
│ │ SYN Guard Dashboard │ │
│ │ │ │
│ │ Active Floods: │ │
│ │ • 192.168.233.2 → port 3000 [BLOCK] │ │
│ │ • 10.0.0.15 → port 8080 [DETECT] │ │
│ │ │ │
│ │ Severity: HIGH (multi-endpoint attack) │ │
│ │ Affected endpoints: 7 │ │
│ │ Estimated attack start: 13:47:02 │ │
│ │ [Escalate] [Block Globally] [Isolate] │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
↓ Real-time updates (sub-second)
┌────────┴────────┬────────────────┬────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Endpoint │ │ Endpoint │ │ Endpoint │ │ Endpoint │
│ (Admin) │ │ (User 1) │ │ (User 2) │ │ (Server) │
│ │ │ │ │ │ │ │
│ syn-guard│ │syn-guard │ │syn-guard │ │syn-guard │
│ ETW→WFP │ │ETW→WFP │ │ETW→WFP │ │ETW→WFP │
│ │ │ │ │ │ │ │
│ Local │ │ Local │ │ Local │ │ Local │
│ decision │ │ decision │ │ decision │ │ decision │
│ │ │ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘Each endpoint runs syn-guard as part of the Threatmatic agent. When a flood is detected:
- Local Decision (~1ms): ETW event fires, tracker counts, threshold exceeded
- Local Block (~5ms): WFP filter installed, attacker's packets drop
- Report to Console (~100ms): Threatmatic sends detection event to cloud
- Global Context (~200ms): Console correlates detections across fabric (7 endpoints under attack)
- Human Decision (~5 seconds): Operator sees HIGH severity alert, decides next action
- Org-Wide Response (~500ms): Operator clicks [Block Globally] → all endpoints add permanent block, attacker's IP is added to global block list
Timeline: attack to fabric-wide response = ~6 seconds. The attacker's first 100 connections are dropped locally by WFP. After ~1 second, 7 endpoints are all blocking. After ~6 seconds, it's added to the global policy.
Compare to traditional firewalls:
- Attacker floods for 30 seconds before alerts page the on-call
- On-call logs in, finds the attack, pushes a firewall rule
- Rule propagates across 50 devices in 5 minutes
- Total: 5+ minutes from attack start to mitigation
With syn-guard in Threatmatic:
- Attacker floods for ~6 seconds while the endpoint blocks locally
- Operator sees fabric-wide dashboard, clicks a button
- Attack is over before the firewall rule even compiles
Real Numbers
From testing on the Threatmatic fabric:
| Metric | Value |
|---|---|
| ETW latency (packet → event) | < 1 ms |
| Detection latency (threshold hit → WFP block) | ~5 ms |
| Block effectiveness | 99.9% (first 1-2 packets leak before block installed) |
| False positive rate | < 0.1% on normal networks |
| CPU overhead per endpoint | ~0.2% when idle, ~2% under DDoS |
| Memory overhead per endpoint | ~8 MB |
| Console reporting latency | ~100 ms |
| Operator reaction time (typical) | 3-10 seconds |
The Catch: ETW is Not a Tool, It's a Platform
ETW itself doesn't know about SYN floods. It just fires an event for every TCP connection. We had to:
- Build the tracker (sliding window, per-IP counting)
- Build the WFP integration (install/remove filters, manage GUIDs)
- Build the monitor (parse ETW payload, handle struct alignment bugs)
- Integrate with Threatmatic (send reports, receive org-wide block commands)
Each step revealed Windows quirks:
- Struct alignment: Go's struct alignment doesn't always match Windows ABI.
UserDatawas at offset 96, not 104 — required raw memory scanning to find - Event ID mapping: Modern TCPIP provider uses manifest-based event IDs (1465), not the old MOF-based IDs (15) from training data
- Payload layout: Remote IP at offset 12, local port at offset 30, both big-endian — took hex dump analysis to reverse-engineer
- WFP condition types:
FWPM_CONDITION_IP_REMOTE_ADDRESSexpectsFWP_UINT32(type 3), notFWP_V4_ADDR_MASK(type 5) — mismatch caused0x80320027error - WFP session cleanup: ETW session names must be unique; previous crashes leave orphaned sessions; required auto-cleanup on startup
Building on Windows is archaeology — you excavate the right struct offsets, field types, and magic constants by trial, error, and hex dumps.
What's Next
syn-guard is now part of Threatmatic, deployed across the fabric, and observable from the console. The roadmap:
-
Kernel callout driver — WFP callouts can inspect TCP flags, count true SYNs vs ACKs. Deferred for now (requires WDK, test signing VM, Hyper-V), but the architecture is ready.
-
Multi-stage responses — Local block (immediate), org-wide block (seconds), plus escalation policies:
- Block endpoint if > 3 sources detected in 30 seconds
- Isolate endpoint if flooding continues after block
- Trigger incident response workflow
-
Attack attribution — Correlate floods with vulnerability scans, lateral movement, exfiltration. Is the flooder part of a larger breach?
-
ML-based thresholds — Learn baseline connection rates per endpoint type, adjust thresholds dynamically.
For now: real-time detection, local blocking, fabric-wide visibility, operator control. It works, it's fast, and it doesn't require a kernel driver.
How to Deploy
syn-guard runs as part of the Threatmatic agent:
threatmatic-agent.exe --enable-syn-guard
--flood-threshold 200
--flood-window 5s
--block-duration 60sThresholds are tunable per organization, endpoint type, and department. A development lab might tolerate 500 conn/10s. A production database server might use 50 conn/5s.
Control is fabric-wide from the console — no pushing rules to individual endpoints, no restart required.
References
- Windows Filtering Platform (WFP)
- Event Tracing for Windows (ETW)
- Microsoft-Windows-TCPIP Provider
- Threatmatic ZTNA Architecture
Questions? Join us in the Threatmatic community Slack.
How is this guide?
Last updated on