Python · Network Security

NetSentinel — Real-Time Network IDS

A Python-based network intrusion detection system that catches ARP spoofing, port scans, DNS hijacking, and ICMP floods as they happen — not after the fact

← Back to projects

NetSentinel — Real-Time Network IDS

Because I got tired of not knowing what was happening on my homelab network until it was too late.


The Goal

I wanted a tool that would watch live network traffic and fire alerts the moment something suspicious showed up — not aggregate logs for review later, not rely on a cloud service, and not require a $40k SIEM license. Just a Python process with root and a network interface, doing real detection against real attack patterns I actually care about.

The four threats I focused on are ones that show up constantly in lab environments and real networks alike: ARP spoofing (the foundation of most LAN MITM attacks), port scans (recon), ICMP floods (DoS), and DNS anomalies — both outright hijacking and the subtler signal of high-entropy subdomains that suggest DNS tunneling for C2 or exfil.


How It Works

NetSentinel runs Scapy in packet-sniffing mode, feeding live frames through a set of independent monitor modules. Each monitor maintains its own state — an ARP table, a sliding window of SYN timestamps, a DNS resolution cache — and calls a shared alerter when a threshold or anomaly condition is met. Alerts go to stdout and to a newline-delimited JSON log file for later analysis.

Detection Methods

Threat Method Severity
ARP Spoofing Tracks IP→MAC mappings; alerts the moment a reply claims a different MAC for a known IP CRITICAL
Port Scan Counts SYN packets per source in a sliding time window; alerts when N ports are hit in M seconds HIGH
ICMP Flood Counts echo requests per source in a sliding window; alerts past threshold HIGH
DNS Hijacking Caches domain→IP mappings from observed DNS responses; alerts when a domain suddenly resolves differently HIGH
DNS Tunneling Calculates Shannon entropy of subdomain labels; high entropy flags likely C2 or exfil traffic MEDIUM

The ARP monitor only acts on ARP replies (op=2), which cuts down noise considerably — requests are normal, replies with mismatched MACs are the actual attack signal. The DNS tunneling detection uses Shannon entropy on subdomain labels rather than on the full domain, which avoids false-positives from CDN hostnames with long random identifiers in the TLD portion.

MITRE ATT&CK Mapping

Every alert maps to a specific technique, which was a deliberate design choice. I wanted the log to be useful for writing detections in other contexts, not just for reading.

Detection Tactic Technique
ARP Spoofing Credential Access / Collection T1557.002 — ARP Cache Poisoning
Port Scan Discovery T1046 — Network Service Discovery
ICMP Flood Impact T1499.003 — Application Exhaustion Flood
DNS Hijacking Command and Control T1071.004 — DNS
DNS Tunneling Exfiltration / C2 T1041, T1071.004

Configuration

Thresholds are tunable in config.yaml without touching code — scan window size, ICMP count ceiling, entropy threshold for tunneling, and a whitelist for IPs that should never trigger alerts (like your own router). The entropy threshold in particular needed tuning: too low and you get noise from legitimate CDN queries, too high and real C2 beaconing slips past.

Log Format

Every event writes a structured JSON record:

{
  "timestamp": "2024-06-14T14:32:01.123456",
  "severity": "CRITICAL",
  "event_type": "arp_spoofing",
  "source_ip": "192.168.1.55",
  "destination": "LAN",
  "detail": "ARP table poisoning: 192.168.1.1 was aa:bb:cc:dd:ee:ff, now claiming 11:22:33:44:55:66"
}

A --summary flag parses the log and prints a breakdown by severity, event type, and top source IPs — no root required for that part.


Tech & Tools

  • Python 3.8+ — main runtime
  • Scapy — packet capture and layer parsing (ARP, IP, DNS, ICMP, TCP)
  • PyYAML — configuration loading
  • Shannon entropy — implemented directly in dns_monitor.py, no external dependency
  • Modular monitor architecture — each detector is an independent class with a process(pkt) method

What I Learned

The ARP table state problem is interesting. The monitor only has visibility into what it sees on the wire — it builds its IP→MAC table from scratch each run. That means if NetSentinel starts after a spoofing attack is already underway, it won't catch the transition. It's a fundamental limitation of passive detection without a trusted baseline, and it's the same problem enterprise NDR tools deal with. Solving it properly would require either persisting the table between runs or importing an authoritative baseline from DHCP leases.

DNS tunneling detection via entropy works, but the threshold matters a lot. A value of 3.5 catches the obvious cases without drowning in false positives, but it's not a substitute for volume analysis or behavioral baselining. A real C2 channel that uses short, low-entropy subdomains would slide past entirely.

Scapy's layer traversal for DNS answers took more care than expected. DNS responses can chain multiple resource records in a linked structure, and iterating them correctly without crashing on malformed packets required explicit exception handling around the payload traversal loop.

Writing structured JSON events from the start pays off immediately. The --summary mode took almost no effort to add because the log was already machine-readable. That's not a novel observation, but it was a good reminder that "we'll parse it later" is a lot easier when you didn't have to think about parsing.


What's Next

  • Slack or Discord webhook for real-time alerting
  • PCAP capture triggered on alert (so you get the evidence, not just the notification)
  • Passive OS fingerprinting from observed traffic
  • A lightweight Flask dashboard for live event viewing
  • systemd service file for always-on monitoring without a persistent terminal session