Why not just use tcpdump?
tcpdump is excellent. The issue is its output format. Raw pcap or line-oriented text doesn't feed cleanly into a JSON-native SIEM like Wazuh + OpenSearch. You can use tshark's JSON mode, but it produces deeply nested objects with Wireshark's verbose field naming — writing decoders for it is painful.
The goal was a sniffer that outputs flat, typed JSON per packet, with exactly the fields Wazuh decoders expect: src_ip, dst_ip, protocol, src_port, dst_port, flags, payload_len. Nothing else. 200 lines of Python is a reasonable price for that precision.
The Scapy sniff loop
Scapy's sniff() function takes a callback that receives each captured packet. The callback extracts fields and writes a JSON line to a log file that Wazuh's log collector watches.
The store=False flag is important — without it, Scapy buffers every packet in memory and will OOM on a busy interface within minutes.
Protocol parsing (TCP / UDP / ICMP)
Wazuh integration via custom decoder
The sniffer writes NDJSON to /var/log/net_sniffer.json. Wazuh's localfile block watches it. A custom decoder maps the flat JSON fields to Wazuh's internal field names so rules can reference data.src_ip and data.dport directly.
Limitations to be honest about
- Encrypted traffic — TLS payloads show up as opaque bytes; you get metadata (src, dst, len) but not content. That's fine for detecting scans and DDoS patterns, not for DLP.
- High-throughput links — Scapy's Python overhead means you'll drop packets on a 1 Gbps link under heavy load. For production high-throughput capture, use AF_PACKET or DPDK.
- Privilege — raw socket capture requires root or CAP_NET_RAW. Run the sniffer in its own systemd service with minimal capabilities.