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

621 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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://<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
```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://<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.
```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/\<id\>/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=\<unix_ts\>&limit=200&filter=\<text\>
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_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)
```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.*