Später · 21 Neue Geräte im Heimnetz (Nachbartabelle des Proxmox-Hosts statt AdGuard)
AdGuard sieht in diesem Netz fast nur die Fritzbox, die die Anfragen der meisten Geräte weiterreicht (nachgesehen 26.09.). Deshalb: - Ausführer: alle 30 min Ping an jede Adresse des /24 der Brücke vmbr0, dann ip neigh (MAC, IP, Rückwärtsname); nur lesend, im Bericht als host.netz (größer als /24 wird nicht abgesucht) - services/homelab/netzgeraete.py: Register netz-bekannt.json; erster Abgleich = alles bekannt, Gäste des Hosts und neue MACs mit bekanntem Namen (Handys mit zufälliger MAC) gelten als bekannt; Neues einmal gemeldet - Wächter: gelber Hinweis „Neues Gerät im Netz“ mit „Ist bekannt“; GET /api/homelab/netz Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
241e91c03f
commit
5325544d89
@@ -0,0 +1,114 @@
|
||||
"""Neue Geräte im Heimnetz (Ausbauplan „Später“, Punkt 21, 26.09.2026): Der Ausführer liest die Nachbartabelle des
|
||||
Proxmox-Hosts, der Homelab-Teil führt ein Register und meldet Neues einmal; der Wächter zeigt es, bis „Ist bekannt“."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from kern import einstellungen as einstellungen_mod
|
||||
from services.homelab import netzgeraete
|
||||
|
||||
AUSFUEHRER = Path(__file__).resolve().parents[2] / "deploy" / "homelab" / "ausfuehrer.py"
|
||||
T0 = 1_790_400_000.0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def a():
|
||||
spec = importlib.util.spec_from_file_location("ausfuehrer_netz", AUSFUEHRER)
|
||||
modul = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(modul)
|
||||
return modul
|
||||
|
||||
|
||||
def test_netz_der_bruecke(a):
|
||||
addr = json.dumps([{"ifname": "vmbr0", "addr_info": [{"family": "inet", "local": "192.168.178.108", "prefixlen": 24}]}])
|
||||
assert str(a.netz_der_bruecke(addr)) == "192.168.178.0/24"
|
||||
gross = json.dumps([{"addr_info": [{"family": "inet", "local": "10.0.0.5", "prefixlen": 16}]}])
|
||||
assert a.netz_der_bruecke(gross) is None # größer als /24 wird nicht abgesucht
|
||||
assert a.netz_der_bruecke("kaputt") is None
|
||||
|
||||
|
||||
def test_nachbarn(a):
|
||||
import ipaddress
|
||||
neigh = json.dumps([
|
||||
{"dst": "192.168.178.1", "lladdr": "74:42:7F:00:00:01", "state": ["REACHABLE"]},
|
||||
{"dst": "192.168.178.23", "lladdr": "6e:81:03:00:00:02", "state": ["STALE"]},
|
||||
{"dst": "192.168.178.40", "state": ["FAILED"]},
|
||||
{"dst": "192.168.178.41", "lladdr": "aa:bb:cc:00:00:03", "state": ["INCOMPLETE"]},
|
||||
{"dst": "fe80::1", "lladdr": "74:42:7f:00:00:01", "state": ["REACHABLE"]},
|
||||
{"dst": "10.1.1.1", "lladdr": "11:22:33:00:00:04", "state": ["REACHABLE"]},
|
||||
])
|
||||
assert a.nachbarn(neigh, ipaddress.ip_network("192.168.178.0/24")) == [
|
||||
{"ip": "192.168.178.1", "mac": "74:42:7f:00:00:01"}, {"ip": "192.168.178.23", "mac": "6e:81:03:00:00:02"}]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def daten(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("MC_DATEN_DIR", str(tmp_path))
|
||||
einstellungen_mod.einstellungen.cache_clear()
|
||||
gemeldet: list[str] = []
|
||||
monkeypatch.setattr(netzgeraete.announce, "notify_telegram", lambda betreff, text, **k: gemeldet.append(text))
|
||||
yield gemeldet
|
||||
einstellungen_mod.einstellungen.cache_clear()
|
||||
|
||||
|
||||
def _bericht(*geraete: tuple[str, str, str | None], gaeste: tuple[str, ...] = ()) -> dict:
|
||||
return {"host": {"netz": {"zeit": T0, "netz": "192.168.178.0/24",
|
||||
"geraete": [{"ip": ip, "mac": mac, "name": name} for ip, mac, name in geraete]}},
|
||||
"gaeste": [{"vmid": 100 + i, "ip": ip} for i, ip in enumerate(gaeste)]}
|
||||
|
||||
|
||||
FRITZ = ("192.168.178.1", "74:42:7f:00:00:01", "fritz.box")
|
||||
PC = ("192.168.178.22", "34:5a:60:00:00:05", "TobisNicerPC.fritz.box")
|
||||
|
||||
|
||||
def test_erstes_mal_ist_alles_bekannt_dann_meldet_es_neues(daten):
|
||||
assert netzgeraete.abgleichen(_bericht(FRITZ, PC), jetzt=T0) == []
|
||||
assert daten == [] and netzgeraete.pruefe_netzgeraete() == []
|
||||
handy = ("192.168.178.23", "6e:81:03:00:00:02", None)
|
||||
neu = netzgeraete.abgleichen(_bericht(FRITZ, PC, handy), jetzt=T0 + 1800)
|
||||
assert [g["mac"] for g in neu] == ["6e:81:03:00:00:02"]
|
||||
assert daten == [("Ein neues Gerät ist im Heimnetz aufgetaucht:\n• ohne Namen (192.168.178.23 · MAC "
|
||||
"6e:81:03:00:00:02 · zufällige MAC, oft ein Handy oder Tablet)\nKennst du es, drück im "
|
||||
"Homelab-Cockpit „Ist bekannt“.")]
|
||||
(befund,) = netzgeraete.pruefe_netzgeraete()
|
||||
assert (befund.id, befund.stufe, befund.titel) == ("netz:6e:81:03:00:00:02", "gelb", "Neues Gerät im Netz: 192.168.178.23")
|
||||
assert befund.aktionen == [{"id": "bekannt", "label": "Ist bekannt", "mac": "6e:81:03:00:00:02"}] and befund.sofort
|
||||
# Beim nächsten Bericht kein zweites Mal.
|
||||
assert netzgeraete.abgleichen(_bericht(FRITZ, PC, handy), jetzt=T0 + 3600) == [] and len(daten) == 1
|
||||
assert netzgeraete.bestaetigen("6E:81:03:00:00:02")["ok"]
|
||||
assert netzgeraete.pruefe_netzgeraete() == []
|
||||
assert not netzgeraete.bestaetigen("00:00:00:00:00:00")["ok"]
|
||||
|
||||
|
||||
def test_gaeste_und_neue_mac_mit_bekanntem_namen_sind_nicht_neu(daten):
|
||||
netzgeraete.abgleichen(_bericht(FRITZ, PC), jetzt=T0)
|
||||
gast = ("192.168.178.160", "bc:24:11:00:00:09", None)
|
||||
pc_neue_mac = ("192.168.178.22", "36:5a:60:00:00:99", "TobisNicerPC.fritz.box")
|
||||
assert netzgeraete.abgleichen(_bericht(FRITZ, gast, pc_neue_mac, gaeste=("192.168.178.160",)), jetzt=T0 + 60) == []
|
||||
lage = netzgeraete.lage()
|
||||
pc = next(g for g in lage["geraete"] if g["mac"] == "36:5a:60:00:00:99")
|
||||
assert pc["wie"] == "34:5a:60:00:00:05" and pc["name"] == "TobisNicerPC" and pc["bestaetigt"]
|
||||
|
||||
|
||||
def test_ohne_netz_im_bericht_passiert_nichts(daten):
|
||||
assert netzgeraete.abgleichen({"host": {}}, jetzt=T0) == []
|
||||
assert not (Path(einstellungen_mod.einstellungen().daten_dir) / "netz-bekannt.json").exists()
|
||||
|
||||
|
||||
def test_schnittstelle_und_knopf_des_hinweises(daten, monkeypatch):
|
||||
from routers import homelab
|
||||
from services import waechter
|
||||
netzgeraete.abgleichen(_bericht(FRITZ), jetzt=T0)
|
||||
netzgeraete.abgleichen(_bericht(FRITZ, PC), jetzt=T0 + 60)
|
||||
app = FastAPI()
|
||||
app.include_router(homelab.router)
|
||||
lage = TestClient(app).get("/api/homelab/netz").json()
|
||||
assert lage["netz"] == "192.168.178.0/24" and [g["bestaetigt"] for g in lage["geraete"]] == [False, True]
|
||||
(befund,) = netzgeraete.pruefe_netzgeraete()
|
||||
monkeypatch.setattr(waechter, "lese_stand", lambda: {"hinweise": [{"id": befund.id, "aktionen": befund.aktionen}]})
|
||||
ergebnis = waechter.fuehre_aktion_aus(befund.id, "bekannt")
|
||||
assert ergebnis["ok"] and ergebnis["text"].startswith("Gemerkt: TobisNicerPC ist bekannt.")
|
||||
Reference in New Issue
Block a user