From 4275c84a6c2eeba9cd6f2463a98ec03a054aa240 Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Sun, 13 Sep 2026 09:55:36 +0200 Subject: [PATCH] Version 2.8.10: der Name gilt nur, wenn ihn jemand aufloesen kann --- apps/tesm/pyproject.toml | 2 +- apps/tesm/src/tesm/services/vnc.py | 47 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/apps/tesm/pyproject.toml b/apps/tesm/pyproject.toml index 6276915..540910b 100644 --- a/apps/tesm/pyproject.toml +++ b/apps/tesm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm" -version = "2.8.9" +version = "2.8.10" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" requires-python = ">=3.11" dependencies = [ diff --git a/apps/tesm/src/tesm/services/vnc.py b/apps/tesm/src/tesm/services/vnc.py index e385118..3118062 100644 --- a/apps/tesm/src/tesm/services/vnc.py +++ b/apps/tesm/src/tesm/services/vnc.py @@ -24,6 +24,8 @@ niemand muss ihn pflegen, und er zeigt nie ins Leere. from __future__ import annotations import re +import socket +import time from dataclasses import dataclass from typing import Any, Mapping @@ -64,16 +66,51 @@ class Zugang: return f"vnc://{self.ziel}:{self.port}" +#: Aufloesungen, die wir schon einmal versucht haben: ``name -> (Zeit, Ergebnis)``. +#: +#: Nicht wegen der Geschwindigkeit -- ein arbeitender Resolver antwortet in +#: Bruchteilen einer Millisekunde --, sondern wegen des Gegenteils: faellt der +#: Nameserver aus, laeuft jede Abfrage in sein eigenes Zeitlimit. Ohne diesen +#: Puffer haette das der Seitenaufbau ausgebadet, und zwar bei jedem Aufruf. +_AUFLOESUNG: dict[str, tuple[float, bool]] = {} + +#: Fuenf Minuten. Lang genug, damit ein kaputter Resolver nicht jede Seite +#: aufhaelt; kurz genug, dass ein neuer DNS-Eintrag nicht bis zum Neustart +#: wartet. +AUFLOESUNG_GILT = 300.0 + + +def aufloesbar(name: str) -> bool: + """Kennt der Resolver diesen Namen?""" + jetzt = time.monotonic() + gemerkt = _AUFLOESUNG.get(name) + if gemerkt is not None and jetzt - gemerkt[0] < AUFLOESUNG_GILT: + return gemerkt[1] + try: + socket.getaddrinfo(name, None) + ergebnis = True + except OSError: + ergebnis = False + _AUFLOESUNG[name] = (jetzt, ergebnis) + return ergebnis + + def ziel(device: Mapping[str, Any]) -> str: """Der Name des Clients, sonst seine IP-Adresse. - Der Name ist die bessere Wahl, wo er aufloest: er ueberlebt einen - Adresswechsel. Taugt er nicht als Hostname -- weil er ein Leerzeichen - enthaelt oder ein Umlaut darin steht --, waere ein Link darauf sicher - falsch; dann ist die Adresse richtig. + Der Name ist die bessere Wahl, wo er gilt: er ueberlebt einen + Adresswechsel, und genau so stehen die gewachsenen Verbindungen im Viewer + (``Host=hap02``). Zwei Dinge muessen dafuer stimmen -- er muss als Hostname + **taugen** ("Kamera Eingang" tut es nicht) und er muss sich **aufloesen** + lassen. Sonst zeigte die Datei auf einen Namen, den der Arbeitsplatz nicht + kennt, und niemand saehe, woran es liegt; dann ist die Adresse richtig, die + ohnehin im Datensatz steht. + + Loest der Name auf eine **andere** Adresse auf als die gespeicherte, gewinnt + trotzdem der Name: dann ist der Eintrag im Bestand veraltet, nicht das DNS. """ name = str(device.get("name") or "").strip() - if name and HOSTNAME_RE.match(name): + if name and HOSTNAME_RE.match(name) and aufloesbar(name): return name.lower() return str(device.get("ip") or "").strip()