Files
2026-04-28 11:21:39 +02:00

17 KiB
Raw Permalink Blame History

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

  1. Schnellstart
  2. Architektur
  3. Konfiguration (.env)
  4. Funktionen
  5. Web-Dashboard
  6. REST-API
  7. Datenbank-Schema
  8. Deployment
  9. 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_PASSWORD lä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_THRESHOLD aufeinanderfolgenden 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:

  1. ARP-Scan des gesamten Netzwerks via scapy → erkennt alle aktiven Geräte
  2. Fallback auf nmap wenn scapy nicht verfügbar/keine Rechte
  3. FritzBox-Abgleich → Gerätenamen und Verbindungstyp (LAN/WLAN/Gast)
  4. Reverse-DNS → Hostname-Auflösung
  5. 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_iddevices, 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_ROWS Einträ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_HOST prüfen, Passwort prüfen
  • Falsches Interface: MONITOR_INTERFACE=auto oder 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_RANGE manuell auf das lokale Subnetz setzen: z.B. 192.168.1.0/24
  • Container muss mit network_mode: host laufen

Kurze Ausfälle werden nicht erkannt

  • OUTAGE_CHECK_INTERVAL verringern (z.B. auf 5)
  • OUTAGE_THRESHOLD=1 setzen (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.