# 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](#1-schnellstart) 2. [Architektur](#2-architektur) 3. [Konfiguration (.env)](#3-konfiguration-env) 4. [Funktionen](#4-funktionen) 5. [Web-Dashboard](#5-web-dashboard) 6. [REST-API](#6-rest-api) 7. [Datenbank-Schema](#7-datenbank-schema) 8. [Deployment](#8-deployment) 9. [Troubleshooting](#9-troubleshooting) --- ## 1. Schnellstart ```bash 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://: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 ```env 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 ```env 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) ```env 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 ```env 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 ```env 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`:** ```bash # 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 ```env 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://: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://:5000/api` ### GET /api/status Aktueller Gesamtstatus. ```json { "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` ```json { "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. ```json [ { "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. ```json [ { "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/\/ports Offene Ports eines Geräts. ```json [ {"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=\&limit=200&filter=\ Live-Paketmitschnitt. `since` = UNIX-Timestamp (float), nur neuere Pakete werden zurückgegeben. ```json [ { "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. ```json { "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. ```json { "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`). ```sql -- 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_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) ```yaml # 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 ``` ```bash # 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) ```bash cd /root/netmon cp .env.example .env && nano .env sudo ./start.sh # sudo wegen raw sockets ``` ### Daten sichern ```bash # Datenbank sichern cp ./data/netmon.db ./data/netmon_backup_$(date +%Y%m%d).db ``` ### Auf anderem System deployen ```bash # 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 ```bash 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 ```bash # 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 ```bash # 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 ```env # In .env WEB_PORT=8080 ``` --- *NetMon – gebaut für 24/7-Betrieb auf Linux mit Docker.*