Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
NetMon – Netzwerk Monitor
Vollständiges Netzwerk-Monitoring-System mit Live-Paketmitschnitt, Ausfallserkennung, Geräteverfolgung und FritzBox-Integration. Läuft 24/7 als Docker-Container und speichert alle Daten in einer SQLite-Datenbank. Auswertung über ein Web-Dashboard.
Inhaltsverzeichnis
- Schnellstart
- Architektur
- Konfiguration (.env)
- Funktionen
- Web-Dashboard
- REST-API
- Datenbank-Schema
- Deployment
- Troubleshooting
1. Schnellstart
cd /root/netmon
# 1. Konfiguration anlegen
cp .env.example .env
nano .env # mindestens FRITZ_PASSWORD setzen
# 2. Starten
docker compose up -d --build
# 3. Dashboard öffnen
# http://<server-ip>:5000
2. Architektur
┌─────────────────────────────────────────────────────────┐
│ main.py (Einstiegspunkt) │
│ .env laden → DB initialisieren → Threads starten │
│ → Flask-Webserver starten │
└────────────────────────┬────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌────▼─────┐ ┌────▼────┐ ┌────▼────┐
│ monitor │ │ api │ │database │
│ 7 Threads│ │ Flask │ │ SQLite │
└────┬─────┘ └────┬────┘ └─────────┘
│ │
│ ┌──────────┴──────────┐
│ │ REST-API-Endpunkte │
│ │ Statische Dateien │
│ └─────────────────────┘
│
┌────┴──────────────────────────────────┐
│ Thread 1 │ Traffic-Loop (60s) │
│ Thread 2 │ Outage-Loop (5s) │
│ Thread 3 │ Device-Loop (300s) │
│ Thread 4 │ Port-Loop (3600s) │
│ Thread 5 │ FritzBox-Loop (60s) │
│ Thread 6 │ Cleanup-Loop (24h) │
│ Thread 7 │ Capture-Loop (dauerhaft)│
└───────────────────────────────────────┘
Komponenten
| Datei | Aufgabe |
|---|---|
src/main.py |
Einstiegspunkt, startet alles |
src/monitor.py |
Alle 7 Monitoring-Threads |
src/database.py |
SQLite-Zugriff, alle DB-Funktionen |
src/fritz.py |
FritzBox TR-064 Client |
src/scanner.py |
ARP-Scan, nmap Port-Scan, Vendor-Lookup |
src/api.py |
Flask REST-API + statische Dateien |
src/web/ |
HTML/CSS/JS Frontend |
Technologie-Stack
| Bereich | Technologie |
|---|---|
| Backend | Python 3.11, Flask 3.0 |
| Datenbank | SQLite (WAL-Mode, thread-safe) |
| Paketmitschnitt | scapy |
| Port-Scan | python-nmap |
| Traffic-Messung | psutil |
| Router-Integration | fritzconnection (TR-064) |
| Frontend | Bootstrap 5.3 (Dark Theme), Chart.js 4 |
| Container | Docker, host-network, privileged |
3. Konfiguration (.env)
Alle Einstellungen erfolgen ausschließlich über die .env-Datei im Projektordner.
FritzBox
FRITZ_HOST=192.168.178.1 # IP der FritzBox (Standard AVM: 192.168.178.1)
FRITZ_USER= # Benutzername (leer = kein Auth)
FRITZ_PASSWORD=dein_passwort # Passwort – PFLICHTFELD für FritzBox-Integration
Ohne
FRITZ_PASSWORDläuft das System weiter, FritzBox-Daten fehlen aber im Dashboard.
Netzwerk
MONITOR_INTERFACE=auto # "auto" = primäres Interface, oder z.B. "eth0", "ens18"
NETWORK_RANGE=auto # "auto" = wird aus Interface berechnet, oder "192.168.1.0/24"
Monitoring-Intervalle (Sekunden)
TRAFFIC_INTERVAL=60 # Bandbreiten-Messung alle 60s
OUTAGE_CHECK_INTERVAL=5 # Ping-Prüfung alle 5s
DEVICE_SCAN_INTERVAL=300 # Geräte-Scan alle 5 Minuten
PORT_SCAN_INTERVAL=3600 # Port-Scan alle 60 Minuten
FRITZ_POLL_INTERVAL=60 # FritzBox-Abfrage alle 60s
OUTAGE_THRESHOLD=2 # Aufeinanderfolgende Fehlschläge bis Ausfall erkannt wird
Ausfalls-Erkennung
PING_TARGETS=8.8.8.8,1.1.1.1,9.9.9.9 # Kommaseparierte Ping-Ziele
# Gateway wird automatisch hinzugefügt
Der Default-Gateway wird immer automatisch erkannt und als erstes Ziel hinzugefügt.
Paketmitschnitt
CAPTURE_ENABLED=true # Mitschnitt ein/aus
# Protokoll-Filter (true = herausfiltern, false = mitschneiden)
CAPTURE_FILTER_ICMP=true # Ping/ICMP ausblenden
CAPTURE_FILTER_ARP=true # ARP ausblenden
CAPTURE_FILTER_ZABBIX=true # Zabbix (Port 10050/10051) ausblenden
CAPTURE_FILTER_RUSTDESK=true # RustDesk (Port 21115-21119) ausblenden
# Eigener BPF-Filter (wird mit AND angehängt)
CAPTURE_FILTER_EXTRA= # z.B.: not port 5353 and not port 1900
CAPTURE_MAX_ROWS=50000 # Max. Einträge in DB (Rolling Window)
BPF-Filter Beispiele für CAPTURE_FILTER_EXTRA:
# mDNS und SSDP rausfiltern
CAPTURE_FILTER_EXTRA=not port 5353 and not port 1900
# Nur lokalen Traffic anzeigen
CAPTURE_FILTER_EXTRA=dst net 192.168.1.0/24
# Bestimmten Host ausblenden
CAPTURE_FILTER_EXTRA=not host 192.168.1.50
Datenbank & Web
DB_PATH=/data/netmon.db # Pfad zur SQLite-Datenbank im Container
WEB_HOST=0.0.0.0 # Bind-Adresse (0.0.0.0 = alle Interfaces)
WEB_PORT=5000 # HTTP-Port
LOG_LEVEL=INFO # DEBUG / INFO / WARNING / ERROR
4. Funktionen
Traffic-Messung
Misst alle 60 Sekunden die Bandbreite auf dem überwachten Interface via psutil. Gespeichert werden:
- Bytes/s (Upload + Download)
- Paketanzahl
- Delta-Bytes seit letzter Messung
Daten werden 35 Tage aufbewahrt und täglich automatisch bereinigt.
Ausfalls-Erkennung
Alle 5 Sekunden werden alle Ping-Ziele gleichzeitig (parallel) angepingt:
- Timeout: 1 Sekunde pro Ping
- Erst wenn alle Ziele nicht antworten, wird ein Ausfall registriert
- Nach
OUTAGE_THRESHOLDaufeinanderfolgenden Fehlschlägen → Ausfall-Eintrag in DB - Jeder einzelne Ping-Versuch wird geloggt (auch kurze Blips unter der Ausfall-Schwelle sichtbar)
- Der Default-Gateway wird automatisch zu den Zielen hinzugefügt
Warum 2-fache Schwelle? Verhindert Fehlalarme durch einzelne Paketverluste im Netz.
Geräte-Erkennung
Alle 5 Minuten:
- ARP-Scan des gesamten Netzwerks via scapy → erkennt alle aktiven Geräte
- Fallback auf nmap wenn scapy nicht verfügbar/keine Rechte
- FritzBox-Abgleich → Gerätenamen und Verbindungstyp (LAN/WLAN/Gast)
- Reverse-DNS → Hostname-Auflösung
- MAC-Vendor-Lookup → Hersteller aus MAC-Präfix (Apple, Samsung, Raspberry Pi, etc.)
Geräte die nicht mehr gesehen werden, bekommen is_active=0.
Port-Scan
Jede Stunde wird für alle aktiven Geräte ein nmap-Quick-Scan durchgeführt:
Gescannte Ports: 21-23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 548, 993, 995, 1080, 1194, 1433, 1883, 2049, 3306, 3389, 4000, 5000, 5432, 5900, 6379, 8080, 8443, 8883, 9100, 27017
Ergebnisse werden pro Gerät gespeichert und im Dashboard angezeigt.
FritzBox-Integration
Alle 60 Sekunden werden via TR-064 API abgefragt:
- Verbindungsstatus (online/offline)
- Verbindungszeit (Uptime seit letztem Reconnect)
- Maximale Up-/Downloadrate (vom Provider)
- Gesamte übertragene Datenmenge
- Aktuelle externe IP-Adresse
- Liste aller bekannten Geräte (Name, IP, MAC, Interface-Typ)
Bei Verbindungsverlust zur FritzBox wird automatisch alle 10 Minuten ein Reconnect versucht.
Live-Paketmitschnitt
Dauerhafter Mitschnitt via scapy auf dem konfigurierten Interface. Gespeichert wird nur Metadaten (kein Payload):
| Feld | Inhalt |
|---|---|
| Zeitstempel | Millisekunden-Präzision |
| Quelle | IP:Port |
| Ziel | IP:Port |
| Protokoll | TCP/UDP/DNS/HTTP/HTTPS/SSH/SMB/... |
| Länge | Bytes |
| Flags | TCP-Flags (SYN/ACK/FIN/RST/PSH) |
| Info | Lesbare Zusammenfassung |
Erkannte Protokolle: TCP, UDP, HTTP, HTTPS, SSH, FTP, SMTP, DNS, DHCP, NTP, mDNS, SSDP, SMB, RDP, VNC, MySQL, PostgreSQL, Redis, MongoDB, MQTT, AFP
Rolling Window: Älteste Einträge werden gelöscht wenn CAPTURE_MAX_ROWS erreicht.
5. Web-Dashboard
Erreichbar unter http://<server-ip>:5000
Tab: Dashboard
- Status-Badge (Navbar): Internet Online/Offline + aktuelle Geschwindigkeit
- Stat-Karten: Internet-Status · Aktive Geräte · Ausfälle heute · Ausfallzeit heute
- Traffic-Chart: Zeitreihe Upload/Download in kbps (Zeitbereiche: 1h / 6h / 24h / 7d / 30d)
- Letzte Ausfälle: Tabelle der 10 letzten Ausfälle mit Dauer
Tab: Geräte
- Vollständige Geräteliste mit Status, IP, MAC, Hersteller, Verbindungstyp
- Suchfeld (filtert nach Name, IP, MAC, Hersteller)
- Toggle für inaktive Geräte
- Port-Modal: Klick auf Port-Anzahl → alle offenen Ports mit Service-Name
Tab: Ausfälle
- Ping-Statistik pro Ziel: Checks · Erfolgsrate · Ausfallrate · Latenz (Ø/Min/Max)
- Zeitbereich wählbar (1h / 6h / 24h / 7d)
- Ausfall-Protokoll: Komplette History mit Start, Ende, Dauer, Status
Tab: FritzBox
- Verbindungsstatus · Externe IP · Max. Download/Upload · Verbindungszeit
- Gesamter Datenverbrauch (seit letztem Router-Reset)
- Verbindungsverlauf (letzte 24h) als Chart
Tab: Mitschnitt
- Live-Paketliste (aktualisiert alle 2 Sekunden)
- Protokoll-Farben (wie Wireshark):
- Blau = TCP · Grün = HTTP/HTTPS/SSH · Lila = UDP · Orange = DNS/DHCP · Rot = SMB/RDP
- Filterfeld: Echtzeit-Filter nach IP, Port, Protokoll
- Anzeige-Limit wählbar (100 / 200 / 500 / 1000 Pakete)
- Live/Pause-Toggle
- Auto-Scroll (optional)
6. REST-API
Basis-URL: http://<server-ip>:5000/api
GET /api/status
Aktueller Gesamtstatus.
{
"internet_up": true,
"bps_up": 125000,
"bps_down": 890000,
"active_devices": 12,
"total_devices": 18,
"outages_today": 2,
"total_outage_seconds_today": 37,
"current_outage_since": null,
"fritz": {
"available": true,
"is_connected": 1,
"external_ip": "93.x.x.x",
"uptime_s": 86400,
"max_up_bps": 10000000,
"max_down_bps": 50000000
}
}
GET /api/traffic?range=1h
Traffic-Zeitreihe. range: 1h | 6h | 24h | 7d | 30d
{
"labels": ["12:00", "12:01", "12:02"],
"upload": [12.5, 8.3, 45.1],
"download": [89.2, 102.4, 78.9]
}
GET /api/outages?limit=100&since=1700000000
Ausfall-History.
[
{
"id": 42,
"start_ts": 1700012345,
"end_ts": 1700012382,
"duration_s": 37,
"duration_human": "37s",
"start_human": "15.11.2024 09:12:25",
"end_human": "15.11.2024 09:13:02"
}
]
GET /api/devices
Alle Geräte.
[
{
"id": 1,
"mac": "aa:bb:cc:dd:ee:ff",
"ip": "192.168.1.42",
"name": "Mein Laptop",
"hostname": "laptop.local",
"fritz_name": "Mein Laptop",
"vendor": "Apple",
"iface_type": "802.11 ax",
"is_active": true,
"first_seen": 1700000000,
"last_seen": 1700012345,
"last_seen_human": "15.11.2024 09:12:25",
"port_count": 3
}
]
GET /api/devices/<id>/ports
Offene Ports eines Geräts.
[
{"port": 22, "protocol": "tcp", "service": "ssh", "state": "open"},
{"port": 80, "protocol": "tcp", "service": "http", "state": "open"},
{"port": 443, "protocol": "tcp", "service": "https", "state": "open"}
]
GET /api/packets?since=<unix_ts>&limit=200&filter=<text>
Live-Paketmitschnitt. since = UNIX-Timestamp (float), nur neuere Pakete werden zurückgegeben.
[
{
"ts": 1700012345.123,
"iface": "ens18",
"src_ip": "192.168.1.42",
"dst_ip": "1.1.1.1",
"src_port": 54321,
"dst_port": 443,
"protocol": "HTTPS",
"length": 128,
"flags": "SYN",
"info": "54321→443 [SYN] Seq=0"
}
]
GET /api/ping_log?minutes=60
Ping-Log der letzten N Minuten + Statistik pro Ziel.
{
"log": [
{"ts": 1700012345, "target": "8.8.8.8", "success": 1, "latency_ms": 12.4}
],
"stats": [
{
"target": "8.8.8.8",
"total": 720,
"ok": 718,
"avg_ms": 14.2,
"min_ms": 9.1,
"max_ms": 89.3
}
]
}
GET /api/fritz?hours=24
FritzBox-Status und Verbindungsverlauf.
{
"latest": {
"ts": 1700012345,
"is_connected": 1,
"uptime_s": 86400,
"max_up_bps": 10000000,
"max_down_bps": 50000000,
"total_bytes_sent": 15000000000,
"total_bytes_recv": 120000000000,
"external_ip": "93.x.x.x"
},
"history": [
{"bucket": 1700010000, "connected": 1.0}
]
}
7. Datenbank-Schema
SQLite-Datenbank unter ./data/netmon.db (im Container: /data/netmon.db).
-- Bandbreiten-Messungen (alle 60s)
traffic (id, ts, iface, bytes_sent, bytes_recv, pkts_sent, pkts_recv, bps_sent, bps_recv)
-- Internetausfälle
outages (id, start_ts, end_ts, duration_s)
-- Jeder einzelne Ping-Versuch
ping_log (id, ts, target, success, latency_ms)
-- Paketmitschnitt-Metadaten (Rolling Window)
packets (id, ts, iface, src_ip, dst_ip, src_port, dst_port, protocol, length, flags, info)
-- Erkannte Geräte
devices (id, mac, ip, hostname, vendor, fritz_name, iface_type, first_seen, last_seen, is_active)
-- Offene Ports pro Gerät
ports (id, device_id→devices, port, protocol, service, state, ts)
-- FritzBox-Verbindungsdaten (alle 60s)
fritz_stats (id, ts, is_connected, uptime_s, max_up_bps, max_down_bps, total_bytes_sent, total_bytes_recv, external_ip)
Alle Zeitstempel sind UNIX-Timestamps (Integer). Die packets-Tabelle verwendet REAL für Millisekunden-Präzision.
Datenhaltung:
- Traffic, Ping-Log, FritzBox-Stats: automatisch nach 35 Tagen gelöscht
- Pakete: Rolling Window, maximal
CAPTURE_MAX_ROWSEinträge
8. Deployment
Voraussetzungen
- Docker + Docker Compose
- Linux-Host (für raw sockets / packet capture)
- Root-Rechte (für ARP-Scan, nmap, scapy)
Docker Compose (empfohlen)
# docker-compose.yml
services:
netmon:
build: .
container_name: netmon
restart: unless-stopped
network_mode: host # Pflicht für echte Interface-Daten
privileged: true # Pflicht für scapy/nmap raw sockets
env_file: .env
volumes:
- ./data:/data # DB persistent auf dem Host
# Starten
docker compose up -d --build
# Logs anschauen
docker logs netmon -f
# Neu deployen nach .env-Änderung
docker compose restart
# Neu bauen nach Code-Änderung
docker compose up -d --build
# Stoppen
docker compose down
Ohne Docker (direkt auf dem Host)
cd /root/netmon
cp .env.example .env && nano .env
sudo ./start.sh # sudo wegen raw sockets
Daten sichern
# Datenbank sichern
cp ./data/netmon.db ./data/netmon_backup_$(date +%Y%m%d).db
Auf anderem System deployen
# Projekt kopieren
scp -r /root/netmon user@neuer-server:/opt/netmon
# Auf neuem Server
cd /opt/netmon
cp .env.example .env
nano .env # IP/Passwort anpassen
docker compose up -d --build
9. Troubleshooting
Dashboard zeigt keine Daten
docker logs netmon --tail=50
Häufige Ursachen:
- FritzBox nicht erreichbar:
FRITZ_HOSTprüfen, Passwort prüfen - Falsches Interface:
MONITOR_INTERFACE=autooder manuell setzen (z.B.eth0)
Paketmitschnitt leer
# Interface prüfen
docker exec netmon python3 -c "import scapy.all as s; print(s.get_if_list())"
# Privileged-Mode aktiv?
docker inspect netmon | grep Privileged
Muss true sein. Falls nicht: docker compose down && docker compose up -d.
Geräte werden nicht gefunden
NETWORK_RANGEmanuell auf das lokale Subnetz setzen: z.B.192.168.1.0/24- Container muss mit
network_mode: hostlaufen
Kurze Ausfälle werden nicht erkannt
OUTAGE_CHECK_INTERVALverringern (z.B. auf5)OUTAGE_THRESHOLD=1setzen (sofort beim ersten Fehlschlag)- Ping-Log unter Ausfälle → Ping-Statistik zeigt jeden einzelnen Check
DB wächst zu schnell
# Aktuelle Größe
du -sh ./data/netmon.db
# Weniger Pakete cachen
# In .env: CAPTURE_MAX_ROWS=10000
# Daten-Retention verkürzen (in database.py cleanup_old_data aufrufen mit weniger Tagen)
Port 5000 bereits belegt
# In .env
WEB_PORT=8080
NetMon – gebaut für 24/7-Betrieb auf Linux mit Docker.