A deep dive into building a Python-based network surveillance tool with Scapy, automatic router detection, and a two-phase watch system.
Most people never think about what's actually on their home network. The router has a device list somewhere behind three login screens and a UI that looks like it was designed in 2009. You check it once, forget about it, and move on.
That's not good enough.
I wanted to know my network — not by logging into the router every time, but through automation. A tool that runs, learns what's supposed to be there, and then tells me when something isn't. So I built CERBERUS.
This post covers what it does, how every component works, the engineering decisions behind it, and what I actually learned building it — including the parts that were genuinely difficult.
What CERBERUS Does
CERBERUS is a Python-based network sentinel. It scans your local network using ARP, learns which devices are trusted, and runs a continuous surveillance loop that alerts you when something unknown shows up.
Feature list:
- Automatic router and subnet detection (no manual config)
- ARP-based device discovery with wake-up broadcast
- Two-phase operation: Learning Mode → Surveillance Mode
- Unknown device alerts with CRITICAL log entries
- Structured file + console logging (reusable module)
- Auto-generated
known_devices.jsonandcerberus.log - Windows Npcap auto-installer
- Cross-platform: Linux, macOS, Windows
What it does NOT do (...yet):
- No port scanning (that's CERBERUS v2)
- Won't detect devices that block ARP responses entirely
- No persistent background daemon — runs in terminal
- No push/email alerts, just logged output
Architecture: Four Modules, One Purpose
cerberus/
├── cerberus_scan.py # Main orchestrator
├── cerberus_logger.py # Reusable logging setup
├── router_detector.py # Network auto-configuration
├── npcap_installer.py # Windows Npcap helper
├── known_devices.json # Auto-generated trusted list
└── cerberus.log # Auto-generated activity log
The design is intentionally modular. Each file has one job. cerberus_scan.py orchestrates everything but doesn't do the heavy lifting itself — it delegates to the other modules and handles the main execution flow.
The two auto-generated files (known_devices.json and cerberus.log) are created at runtime. You don't set them up. CERBERUS creates them when it first runs.
Security note:
known_devices.jsonstores MAC addresses of your trusted devices.
Deep Dive: How Each Component Works
1. Router Detection — router_detector.py
Before scanning anything, CERBERUS needs to know what network to scan. This is where most tools either require manual configuration or make a dumb assumption. CERBERUS tries to be smarter.
The RouterDetector class uses Python's netifaces library to query the system's default gateway:
gateways = netifaces.gateways()
gateway_ip, interface = gateways['default'][netifaces.AF_INET][:2]
Once it has the interface, it fetches the actual subnet mask:
addrs = netifaces.ifaddresses(interface)
netmask = addrs[netifaces.AF_INET][0]['netmask']
cidr = sum(bin(int(x)).count('1') for x in netmask.split('.'))
network_address = f"{network_addr}/{cidr}"
This gives you the correct CIDR notation — say, 192.168.1.0/24 or 10.0.0.0/22 — based on your actual network, not a guess.
The fallback: If subnet mask detection fails (some systems don't return this reliably), CERBERUS falls back to a /24 assumption:
network_base = '.'.join(router_ip.split('.')[:-1]) + '.0/24'
TARGET_NETWORK = network_base
logger.warning(f"Using default /24 network assumption: {TARGET_NETWORK}")
This covers the vast majority of home networks. If your network is different, you'll see the warning and can hardcode TARGET_NETWORK in the config section of cerberus_scan.py. It's a practical trade-off — automatic detection works 95% of the time, and the fallback handles the rest without crashing.
2. ARP Scanning — The Core of CERBERUS
ARP (Address Resolution Protocol) is how devices on a network map IP addresses to MAC addresses. It's stateless, has no authentication, and every device on your network responds to it. That makes it ideal for discovery.
CERBERUS builds a broadcast ARP request using Scapy:
arp_request = ARP(pdst=TARGET_NETWORK)
ether_frame = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = ether_frame / arp_request
answered_list = srp(packet, timeout=3, verbose=0)[0]
clients = []
for sent, received in answered_list:
clients.append({"ip": received.psrc, "mac": received.hwsrc})
ARP(pdst=TARGET_NETWORK) creates an ARP request asking "who has every IP in this range?"
Ether(dst="ff:ff:ff:ff:ff:ff") wraps it in an Ethernet broadcast frame — every device on the network receives this.
srp() (send/receive at Layer 2) sends the packet and listens for responses. Whatever responds gets added to the clients list with their IP and MAC.
The wake-up call:
Before the ARP scan, CERBERUS sends a broadcast ICMP ping:
broadcast_ip = '.'.join(TARGET_NETWORK.split('/')[0].split('.')[:3]) + '.255'
send(IP(dst=broadcast_ip)/ICMP(), verbose=0)
time.sleep(1)
Some devices go into a low-power state and take a moment to respond to ARP. The broadcast ping nudges them awake before the scan. It doesn't work on every device — anything that ignores pings or blocks ICMP stays invisible — but it improves discovery rates on typical home networks.
3. Learning Mode vs Surveillance Mode
This two-phase design is the core logic of CERBERUS.
Phase 1 — Learning Mode (first run only):
def learn_network_mode():
current_devices = scan_network()
known_macs = [device["mac"] for device in current_devices]
save_known_devices(known_macs)
return known_macs
On first run, there's no known_devices.json. CERBERUS detects this, scans the network, saves every discovered MAC as trusted, and creates the file. No alerts. Everything found in learning mode is considered legitimate.
Phase 2 — Surveillance Mode (every run after):
def surveillance_mode(known_macs):
while True:
current_devices = scan_network()
for device in current_devices:
if device["mac"] not in known_macs:
logger.warning(f"Unknown: {device['ip']} - {device['mac']}")
if unknown_devices:
logger.critical(f"ALERT: {len(unknown_devices)} intruder(s) detected!")
time.sleep(SCAN_INTERVAL)
Every 60 seconds (configurable), CERBERUS scans, loads the trusted MAC list, and compares. Any MAC not in the trusted list triggers a WARNING. If unknown devices are found, CRITICAL level alerts fire for each one.
The loop runs until Ctrl+C. No scan limit. CERBERUS watches until you tell it to stop.
Why MAC addresses and not IPs?
IP addresses change. Devices get new IPs via DHCP on every reconnect. MAC addresses are hardware identifiers — they're stable. Tracking by MAC means a device can reconnect on a different IP and CERBERUS still recognizes it.
4. The Logging Module — cerberus_logger.py
def setup_logging(log_file="cerberus.log", level="INFO", silent_mode=False):
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H-%M-%S"
)
# Always writes to file
file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
# Console output optional
if not silent_mode:
console_handler = logging.StreamHandler(sys.stdout)
I built this as a separate module for a reason: I'll use it in future projects. Same interface, drop it in, configure the log file name and level. No rewriting.
Key behaviors:
- Always appends to file — you don't lose previous scan history
- Console output is optional via
silent_mode=True - Five log levels available: DEBUG, INFO, WARNING, ERROR, CRITICAL
- Millisecond-precision timestamps on every entry
CERBERUS uses WARNING for unknown devices and CRITICAL for confirmed intruder alerts. The distinction matters when parsing logs programmatically.
5. Windows Npcap Auto-Installer — npcap_installer.py
Windows doesn't give raw socket access out of the box. Scapy needs Npcap (a packet capture driver) to function on Windows. Without it, nothing works.
Most tools say "install Npcap manually" and leave you to figure it out. I didn't want that. The auto-installer handles it:
- Check if already installed — registry lookup + file system check + live Scapy test
- If not found — offer options: auto-install, manual install, or skip
- Auto-install path — downloads installer, runs silent install with these flags:
/S # Silent mode, no GUI
winpcap_mode=yes # WinPcap API compatibility
loopback_support=yes # Capture localhost traffic
admin_only=no # Allow non-admin capture
The version is currently hardcoded to Npcap 1.79. It'll work fine for the foreseeable future — Npcap doesn't have breaking releases frequently — but it's something to revisit in v2.
This module ended up being more code than the scanner itself. Building it taught me more about Windows internals than I expected: registry access with winreg, process management with subprocess, installer flags, and the gap between "it works on my machine" and "it works on someone else's machine."
Sample Output
Learning Mode (first run):
==================================================
PROJECT CERBERUS: The Network Sentinel
==================================================
✔️ Network detected: 192.168.1.0/24
Router: 192.168.1.1
Your IP: 192.168.1.x
Interface: wlan0
--------------------------------------------------
No known devices file found. Starting learning mode...
Scanning 192.168.1.0/24...
Found 5 devices.
Learned 5 devices. They are now trusted.
Surveillance Mode — clean scan:
------------------------- Scan 1 -------------------------
Scanning 192.168.1.0/24...
Found 5 devices.
All devices are trusted.
Surveillance Mode — intruder detected:
------------------------- Scan 4 -------------------------
Scanning 192.168.1.0/24...
Found 6 devices.
WARNING - Unknown: 192.168.1.108 - xx:xx:xx:xx:xx:xx
CRITICAL - ALERT: 1 intruder(s) device detected!
CRITICAL - INTRUDER: 192.168.1.108 - xx:xx:xx:xx:xx:xx
For security reasons i'm not putting the original terminal outputs here.
Key Engineering Decisions
Why Scapy over subprocess + system arp?
The alternative was calling arp -a via subprocess and parsing text output. That breaks when output format changes across OS versions (and it does). Scapy gives you structured data directly, cross-platform control, and fine-grained timeout/retry configuration. The learning curve is steeper, but you're not writing a brittle text parser.
Why JSON for device storage?
No database overhead for a list of MAC addresses that'll never exceed 50 entries on a home network. JSON is human-readable, directly editable, and parseable without any dependencies. If you want to add a device manually or remove one, open the file and edit it. Simple.
Why 60-second scan intervals?
30 seconds felt aggressive for passive home network monitoring — unnecessary traffic and CPU overhead. 120 seconds means an intruder goes unnoticed for two minutes. 60 is the balance. It's also directly configurable: change SCAN_INTERVAL in cerberus_scan.py.
Why separate the logging module?
Reusability. cerberus_logger.py has no CERBERUS-specific logic. It's a clean logging setup function. I'll drop it into the next project without touching it.
What I Actually Learned
1. Scapy's documentation is scattered
Most tutorials cover sending a single ping. Understanding srp() (Layer 2 send/receive) versus sr() (Layer 3) took actual time — reading source code, not just docs. There's no single good resource that covers it end-to-end. You have to piece it together.
2. ARP is stateless. That's the attack surface.
Building this made the theory real. ARP has no authentication. Any device can respond to any ARP request, including one it didn't ask for — that's how ARP spoofing works. I understood this conceptually before. After building CERBERUS, I understood it practically.
3. Device discovery is harder than it looks
Not all devices respond to ARP every time. Sleeping devices, aggressively firewalled devices, some IoT hardware — they don't always reply. The wake-up broadcast ping helps, but it's not a complete solution. This is the core limitation that's driving CERBERUS v2: multiple discovery methods, not just ARP.
4. Windows is a different world for networking
Linux gives you raw socket access with sudo. Windows gates it behind a kernel driver (Npcap). Building the auto-installer taught me about Windows registry interaction, silent installer flags, and the difference between making a tool that works on your development machine versus one that someone else can actually run.
5. AI helped with syntax. Debugging was still mine.
I used AI assistance for Scapy syntax and parts of the Npcap implementation. But when something failed — wrong subnet detection, missed devices, logging output inconsistencies — figuring out why required reading logs, testing across configurations, and sometimes reading library source code. AI accelerates. It doesn't replace the diagnostic work.
Limitations and CERBERUS v2
What v1 doesn't solve:
- Devices that don't respond to ARP stay invisible
- No port/service fingerprinting — you see a device, not what it's running
- No persistent daemon — closes when terminal closes
- No alerting beyond log output (no email, no webhook, no SMS)
- Npcap version hardcoded on Windows
What CERBERUS v2 is adding:
- Docker containerization — runs anywhere, including Raspberry Pi
- Nmap integration for port scanning and service detection
- Multiple discovery methods (not just ARP)
- SQLite for persistent device history
- REST API for status queries
- Webhook/email notifications
- Smarter intruder classification
v2 is in active development.
Quick Start
Prerequisites:
- Python 3.7+
- Linux/macOS: run with
sudo(raw packet access requires root) - Windows: run as Administrator (auto-installer handles Npcap)
git clone https://github.com/Ranvir2028/Cerberus-The-Network-Sentinel.git
cd Cerberus-The-Network-Sentinel
pip install -r requirements.txt
Run:
# Linux/macOS
sudo python3 cerberus_scan.py
# Windows (Administrator terminal)
python cerberus_scan.py
First run enters learning mode automatically. Every run after that is surveillance.
Dependencies (from requirements.txt):
scapy==2.6.1
netifaces-plus==0.12.5
requests==2.32.5
colorama==0.4.6
Security Note
Only use CERBERUS on networks you own or have explicit permission to monitor. ARP scanning sends packets to every IP in your network range. On a network you don't own, that's unauthorized network activity.
What's Next
CERBERUS v2 is being built now. The goal is a containerized network sentinel that runs continuously on a home server or Raspberry Pi, scans with multiple discovery methods, and notifies you without you having to watch a terminal.
The v1 code is on GitHub named Cerberus: The Network Sentinel. Read it, break it, use it to understand ARP scanning.
GitHub: github.com/Ranvir2028/Cerberus-The-Network-Sentinel
LinkedIn: linkedin.com/in/harshwardhan-s-ranvir-20a328378
Top comments (4)
Thoughtful work👍
wonderful work 🙏🔥
It's very interesting tool u have developed
Great work 👍👍really helpful