DEV Community

Cover image for CERBERUS: I Built a Network Sentinel Because My Router Wasn't Enough
Harshwardhan S. Ranvir
Harshwardhan S. Ranvir

Posted on

CERBERUS: I Built a Network Sentinel Because My Router Wasn't Enough

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.json and cerberus.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
Enter fullscreen mode Exit fullscreen mode

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.json stores 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]
Enter fullscreen mode Exit fullscreen mode

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}"
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

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})
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Check if already installed — registry lookup + file system check + live Scapy test
  2. If not found — offer options: auto-install, manual install, or skip
  3. 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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Surveillance Mode — clean scan:

------------------------- Scan 1 -------------------------
Scanning 192.168.1.0/24...
Found 5 devices.
All devices are trusted.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Run:

# Linux/macOS
sudo python3 cerberus_scan.py

# Windows (Administrator terminal)
python cerberus_scan.py
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
anmol_late_93db679c7c9017 profile image
Anmol Late

Thoughtful work👍

Collapse
 
null_ebd69fac0541d6fce8f8 profile image
Null

wonderful work 🙏🔥

Collapse
 
sahil_pagare_45c9d6a5c62b profile image
SAHIL PAGARE

It's very interesting tool u have developed

Collapse
 
gunjan_sagvekar_448d78de2 profile image
Gunjan Sagvekar

Great work 👍👍really helpful