diff --git a/README.md b/README.md index b3ae0cf..7c4e37e 100644 --- a/README.md +++ b/README.md @@ -229,20 +229,42 @@ der aktiv weiterentwickelte Nachfolger und bildet „globaler Wert, pro Client - **Installation & Dienststeuerung**: eigene, jeweils einzeln bestätigte Buttons — „kea-dhcp4-server installieren“ (`apt-get install -y`), „Aktivieren & (neu) starten“ (`systemctl enable` + `restart`) und - „Stoppen“. Bewusst **nicht** automatisch an „Konfiguration speichern“ - oder „In Datei schreiben“ gekoppelt — ein unerwartet aktiver oder - fehlkonfigurierter DHCP-Server kann ein ganzes Netzsegment stören, jede - dieser Aktionen soll ein bewusster, separater Klick bleiben. -- **Netzwerk-Kopplung**: Subnet, Netzmaske und Gateway werden **nie manuell + „Stoppen“ (`systemctl stop` **+ `disable`**, damit ein bewusst gestoppter + Dienst nach einem Host-Neustart nicht trotzdem automatisch wieder + hochfährt — ein Host-Neustart übernimmt so genau den zuletzt bewusst + gewählten Zustand, an oder aus). Bewusst **nicht** automatisch an + „Konfiguration speichern“ oder „In Datei schreiben“ gekoppelt — ein + unerwartet aktiver oder fehlkonfigurierter DHCP-Server kann ein ganzes + Netzsegment stören, jede dieser Aktionen soll ein bewusster, separater + Klick bleiben. +- **Mehrere Subnetze gleichzeitig**: ein Host kann mehrere IPs/Interfaces + mit jeweils eigenem Netz haben, für die alle DHCP angeboten werden soll + (`dhcp_subnets`-Tabelle statt einer einzelnen globalen Range) — jedes + Subnetz hat eigene Interface/Range sowie optional eigenes Gateway/DNS, + generiert wird ein eigener `subnet4`-Block je Subnetz in der Kea-Config. + Domain, Lease-Zeiten und Ausgabepfad bleiben global (Kea-weit gültig). +- **Netzwerk-Kopplung**: Subnet und Netzmaske werden **nie manuell eingegeben**, sondern bei jeder Anzeige/Generierung live aus der - System-Netzwerkkonfiguration des gewählten Interfaces gelesen - (`_detect_interface_network()`: `ip -4 addr show dev ` + - `ip route show default`). Die generierte Kea-Konfiguration ist dadurch - immer an das Netz gekoppelt, in dem der Host tatsächlich hängt, auch wenn - sich dessen IP/Subnet mal ändert — nur das Interface selbst (Dropdown, - echte vorhandene Interfaces aus `/sys/class/net`), die Lease-Range, - DNS-Server, Domain, Lease-Zeiten und der Ausgabepfad sind konfigurierbar - (`dhcp_*`-Schlüssel in der `settings`-Tabelle). + System-Netzwerkkonfiguration des jeweiligen Interfaces gelesen + (`_detect_interface_network()`/`_detect_interface_networks()`: + `ip -4 addr show dev ` + `ip route show default`) — berücksichtigt + dabei ALLE IPv4-Adressen eines Interfaces, nicht nur die erste (ein + Interface kann mehrere IPs/Subnetze gleichzeitig tragen, z.B. Alias-IPs). + Die generierte Kea-Konfiguration ist dadurch immer an das Netz gekoppelt, + in dem der Host tatsächlich hängt, auch wenn sich dessen IP/Subnet mal + ändert. +- **Range ist Pflicht und muss zu einem echten Netz passen**: kein + vorausgefüllter Default für Range/DNS (nur Platzhaltertext) — ein + Subnetz ohne zu einem tatsächlich vorhandenen Netz passende Range wird + beim Anlegen/Bearbeiten abgelehnt (`_dhcp_matching_network()`), da Kea + ohne eine echte, physisch vorhandene IP im gewünschten Bereich keine + gültige Subnet-CIDR zugeordnet werden kann. Dieselbe Prüfung läuft + zusätzlich bei „In Datei schreiben“ (Subnetze, die inzwischen zu keinem + echten Netz mehr passen — z.B. nach einem Netzwechsel des Hosts — werden + übersprungen und in der Erfolgsmeldung benannt, statt die ganze Aktion + abzubrechen) und bei „Aktivieren & (neu) starten“ (verweigert den Start, + falls kein einziges gültiges Subnetz mehr existiert). Live auf einer + echten Fehlkonfiguration reproduziert und verifiziert. - **Eigene Options** (`dhcp_option_defs`/`dhcp_option_values`, analog den „Vordefinierten Optionen und Werten“ eines Windows-DHCP-Servers): Code, Name, Kea-Datentyp und Beschreibung frei definierbar (z.B. herstellerspezifische @@ -252,33 +274,25 @@ der aktiv weiterentwickelte Nachfolger und bildet „globaler Wert, pro Client (technisch: globale Werte landen im Top-Level `option-data`, Overrides im `option-data` der jeweiligen Kea-Reservierung, was Kea automatisch pro Client Vorrang geben lässt). -- **Reservierungen**: für jedes aktive Gerät mit gültiger MAC + IP **innerhalb - des erkannten Subnets** wird eine Kea-Reservierung erzeugt; der +- **Reservierungen**: für jedes aktive Gerät mit gültiger MAC + IP wird eine + Kea-Reservierung im **passenden Subnetz** erzeugt (die IP entscheidet, zu + welchem der ggf. mehreren konfigurierten Subnetze ein Gerät gehört); der Hostname wird aus dem Gerätenamen abgeleitet (nur `a-z0-9-`, Kollisionen - bekommen automatisch ein `-2`/`-3`-Suffix). Geräte ohne MAC/IP oder mit - einer IP außerhalb des Subnets werden mit Hinweis übersprungen — Kea lehnt - eine Reservierung außerhalb ihres Subnets als Konfigurationsfehler ab - (mit einer echten Kea-Instanz verifiziert). + innerhalb desselben Subnetzes bekommen automatisch ein `-2`/`-3`-Suffix). + Geräte ohne MAC/IP oder mit einer IP außerhalb aller konfigurierten + Subnetze werden mit Hinweis übersprungen — Kea lehnt eine Reservierung + außerhalb ihres Subnets als Konfigurationsfehler ab (mit einer echten + Kea-Instanz verifiziert). +- **Aktive Leases**: eigene Karte, liest die tatsächlich vergebenen Adressen + direkt aus Keas Lease-Datenbank (`kea-leases4.csv`, memfile-Backend — + kein Control-Agent nötig, reiner Lesezugriff auf die Datei). Zeigt damit + auch Clients **ohne eigene Reservierung**, die sich einfach eine freie IP + aus dem Pool genommen haben — deutlich als „Ohne Reservierung“ markiert, + statt nur die (unvollständige) Reservierungsliste zu zeigen. - **Schreiben**: „In Datei schreiben“ (nur mit `settings_dhcp.edit`) schreibt die vollständige, generierte Kea-JSON-Konfiguration an den konfigurierten Ausgabepfad. Wirksam wird sie erst nach einem Dienst-Neustart über den separaten Button. -- **Range ist Pflicht und muss zu einem echten Netz passen**: kein - vorausgefüllter Default für Range/DNS mehr (nur Platzhaltertext) — ohne - eingetragene Range schreibt „In Datei schreiben“ nichts und „Aktivieren & - (neu) starten“ verweigert den Start. Zusätzlich wird die Range beim - Speichern, Schreiben UND Starten gegen die tatsächlich am gewählten - Interface vorhandenen IPv4-Netze geprüft (ein Interface kann mehrere IPs/ - Subnetze gleichzeitig tragen, z.B. Alias-IPs — alle werden berücksichtigt). - Passt die Range zu keinem davon (z.B. weil sich das Netz des Hosts seit - dem letzten Speichern geändert hat), wird die Aktion mit einer genauen - Fehlermeldung abgelehnt, statt den Dienst mit einer zum echten Netz nicht - passenden Range laufen zu lassen — live auf einer echten Fehlkonfiguration - reproduziert und verifiziert. Gateway ist separat und optional - überschreibbar (Default: automatisch erkanntes Gateway des Interfaces). - (Mehrere DHCP-Ranges gleichzeitig — z.B. auf unterschiedlichen Interfaces - — sind aktuell nicht unterstützt, die Konfiguration ist auf eine globale - Range/ein Interface ausgelegt; als nächster Ausbauschritt vorgesehen.) - **Ampel im Topbar**: neben dem Prüfintervall-Timer zeigt ein grüner/roter Punkt, ob der Kea-Dienst läuft — nur sichtbar mit `settings_dhcp.view`. - Reservierungen mit aktiven eigenen Options zeigen in der Tabelle, welche @@ -290,6 +304,23 @@ Installation, generierte Konfiguration besteht `kea-dhcp4 -t`, Dienststart sowie Client-spezifischer Override erscheinen korrekt getrennt in der Konfiguration. +Die Mehrfach-Subnetz-Unterstützung selbst (Anlegen/Bearbeiten/Löschen samt +Validierung, mehrere generierte `subnet4`-Blöcke mit korrekten Pools/ +Interfaces, Migration einer bestehenden Einzel-Range) ist live auf einem +echten Host verifiziert. Zwei tatsächlich **unabhängige** Netze (z.B. über +zwei physische NICs) standen beim Test nicht zur Verfügung — verifiziert +wurde daher zusätzlich zwei Ranges innerhalb desselben real vorhandenen +Netzes (identischer `subnet`-CIDR, unterschiedliche Pools); ob Kea zwei +`subnet4`-Blöcke mit identischem `subnet`-Wert akzeptiert oder als Fehler +ablehnt, wurde nicht gegen eine echte Kea-Instanz geprüft — bei tatsächlich +unterschiedlichen Netzen (der eigentliche Anwendungsfall) besteht dieses +Risiko nicht. Die Leases-Anzeige wurde gegen eine echte (leere) +`kea-leases4.csv` verifiziert (korrektes Parsen, keine Endlos-/Absturz- +Fälle) — eine tatsächlich unreservierte Lease eines fremden Clients live +zu erzeugen und die „Ohne Reservierung“-Markierung damit End-to-End zu +zeigen, wurde nicht getestet (hätte einen echten, unkontrollierten +DHCP-Request auf dem gemeinsam genutzten Testnetz erfordert). + ## Netzwerkeinstellungen des Hosts (Systemeinstellungen) Eigene, klar getrennte Funktion von obigem DHCP-*Server*: hier geht es um @@ -326,13 +357,16 @@ da eine Änderung nicht die Erreichbarkeit kappt). erreichbar ist), rollt die App automatisch auf die gesicherte Konfiguration zurück. Verhindert, dass ein Tippfehler bei IP/Gateway den Host von der Web-UI/SSH abschneidet. -- **DHCP-Server wird vorsorglich gestoppt**: eine vorher passende Range - (siehe DHCP-Konfiguration oben) kann nach einer IP-/Subnetzänderung - plötzlich zum falschen Netz gehören. Läuft Kea beim Anwenden oder beim - Auto-Rollback, wird der Dienst deshalb gestoppt (nicht automatisch neu - gestartet — die Range muss der Admin aktiv auf der DHCP-Seite prüfen und - den Dienst dort bewusst wieder starten, statt dass die App mit einer - möglicherweise falschen Konfiguration weiterläuft oder blind rät). +- **DHCP-Server wird vorsorglich gestoppt UND deaktiviert**: eine vorher + passende Range (siehe DHCP-Konfiguration oben) kann nach einer + IP-/Subnetzänderung plötzlich zum falschen Netz gehören. Läuft Kea beim + Anwenden oder beim Auto-Rollback, wird der Dienst deshalb gestoppt und + deaktiviert (nicht automatisch neu gestartet — die Range muss der Admin + aktiv auf der DHCP-Seite prüfen und den Dienst dort bewusst wieder + starten). Das zusätzliche Deaktivieren verhindert, dass ein Host-Neustart + vor dieser Prüfung den Dienst trotzdem automatisch mit der ggf. falschen + Range wieder hochfährt, statt dass die App mit einer möglicherweise + falschen Konfiguration weiterläuft oder blind rät. - Live gegen eine echte Ubuntu-24.04-VM verifiziert (Standard-Installation, Netz per DHCP über netplan/systemd-networkd bezogen) — sowohl die Erkennung/Statusanzeige als auch der komplette Anwenden-Pfad inkl. der diff --git a/srv/poe_manager/templates/settings_dhcp.html b/srv/poe_manager/templates/settings_dhcp.html index 127d6f5..e0d2873 100644 --- a/srv/poe_manager/templates/settings_dhcp.html +++ b/srv/poe_manager/templates/settings_dhcp.html @@ -34,7 +34,7 @@ {% if status.active is sameas true %} -
+
@@ -58,50 +58,13 @@
-

Netzwerk-Konfiguration

-
Subnet/Netzmaske/Gateway werden live vom gewählten Interface übernommen, nicht manuell gepflegt.
+

Globale DHCP-Einstellungen

+
Gelten für den gesamten Dienst, unabhängig von den Subnetzen unten.
- - {% if net_info.ok %} -
- Erkannt - {{ net_info.ip }}/{{ net_info.prefix }} (Netz {{ net_info.network }}) - {% if net_info.gateway %}Gateway {{ net_info.gateway }}{% endif %} -
- {% else %} -
- Nicht erkannt - {{ net_info.error }} -
- {% endif %} - {% if can_edit %}
-
- -
Subnet/Netzmaske/Gateway oben werden für das hier ausgewählte Interface erkannt.
-
-
- -
-
- -
Pflichtfeld, muss im oben erkannten Netz liegen — ohne Range startet der Dienst nicht.
-
-
- -
Nur bei abweichendem Router für die Clients nötig — leer lassen für das oben erkannte Gateway.
-
-
- -
Kommagetrennt, falls mehrere.
-
@@ -125,6 +88,71 @@ {% endif %}
+
+
+
+

Subnetze

+
+ Ein Host kann mehrere IPs/Interfaces mit jeweils eigenem Netz bedienen — je Subnetz eigene Range, + optional eigenes Gateway/DNS. Subnet/Netzmaske werden live vom Interface übernommen, nicht manuell gepflegt. +
+
+ {% if can_edit %} + + {% endif %} +
+ + {% if subnets %} +
+
+ + + + {% for s in subnets %} + {% set st = subnet_status.get(s.id) %} + + + + + + + + + {% endfor %} + +
InterfaceRangeGatewayDNSErkanntes NetzAktionen
{{ s.interface }}{{ s.range_start }} – {{ s.range_end }}{{ s.gateway or '(automatisch)' }}{{ s.dns or '—' }} + {% if st and st.ok %} + Erkannt + {{ st.network }} + {% else %} + Nicht erkannt + {% if st %}{{ st.error }}{% endif %} + {% endif %} + + {% if can_edit %} +
+ + + + + +
+ {% endif %} +
+
+
+ {% else %} +

Noch kein Subnetz konfiguriert — der Dienst kann so nicht gestartet werden.

+ {% endif %} +
+
@@ -184,14 +212,61 @@ {% endif %}
+
+
+
+

Aktive Leases

+
+ Direkt aus Kea gelesen (nicht aus den Reservierungen) — zeigt auch Clients OHNE eigene Reservierung, + die sich einfach eine freie IP aus dem Pool genommen haben. +
+
+
+ + {% if leases is none %} +

+ Keine Lease-Datei gefunden — der Dienst wurde vermutlich noch nie gestartet oder hat noch keine Adresse vergeben. +

+ {% elif leases %} +
+
+ + + + {% for l in leases %} + + + + + + + + {% endfor %} + +
IP-AdresseMACHostnameLäuft abStatus
{{ l.ip }}{{ l.mac }}{{ l.hostname or '—' }}{{ l.expires_at or '—' }} + {% if l.reserved %} + Reserviert + {% else %} + + ⚠ Ohne Reservierung + + {% endif %} +
+
+
+ {% else %} +

Aktuell keine aktiven Leases vergeben.

+ {% endif %} +
+

Reservierungen aus den Clients

- {{ reservations|length }} aktive{{ ' Clients' if reservations|length != 1 else 'r Client' }} mit gültiger MAC + IP im konfigurierten Subnet. + {{ reservations|length }} aktive{{ ' Clients' if reservations|length != 1 else 'r Client' }} mit gültiger MAC + IP in einem der konfigurierten Subnetze. {% if skipped_count %}{{ skipped_count }} aktive{{ ' Clients' if skipped_count != 1 else 'r Client' }} ohne MAC/IP übersprungen.{% endif %} - {% if out_of_subnet_count %}{{ out_of_subnet_count }} aktive{{ ' Clients außerhalb' if out_of_subnet_count != 1 else 'r Client außerhalb' }} des erkannten Netzes übersprungen.{% endif %} + {% if out_of_subnet_count %}{{ out_of_subnet_count }} aktive{{ ' Clients außerhalb' if out_of_subnet_count != 1 else 'r Client außerhalb' }} aller konfigurierten Subnetze übersprungen.{% endif %}
{% if can_edit %} @@ -209,7 +284,7 @@
- + {% for r in reservations %} @@ -217,6 +292,7 @@ +
HostnameMACIP-AdresseClient-NameOptionenAktionen
HostnameMACIP-AdresseClient-NameSubnetzOptionenAktionen
{{ r.mac }} {{ r.ip }} {{ r.name }}{{ r.subnet_label }} {% for d in option_defs %} {% set override = option_values.get(d.id, {}).get(r.mac) %} @@ -323,6 +399,90 @@ + + +{% endif %} + + +{% if can_edit %} +{% for s in subnets %} + +{% endfor %} {% endif %} {% endblock %}