Files
Onyscribe-Admin/app/services/netcheck.py

61 lines
2.5 KiB
Python
Raw 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.
"""HTTP-GET mit DNS-Rückfall für Health-Checks.
Der Resolver des Admin-Servers (Coolify/Docker) cacht „Name nicht gefunden“ für neue Hostnamen oft lange – obwohl der Name öffentlich
längst existiert. Schlägt die Auflösung fehl, fragen wir die Adresse per DNS-over-HTTPS (Cloudflare, dann Google) ab und verbinden uns
direkt zur IP (Host-Header + SNI = Hostname, Zertifikat wird normal geprüft).
"""
from __future__ import annotations
import socket
from urllib.parse import urlsplit
import httpx
def _is_dns_error(exc: BaseException) -> bool:
seen = set()
while exc is not None and id(exc) not in seen:
seen.add(id(exc))
if isinstance(exc, socket.gaierror):
return True
exc = exc.__cause__ or exc.__context__
return False
def is_dns_failure_text(msg: str) -> bool:
m = msg.lower()
return "name or service not known" in m or "temporary failure in name resolution" in m or "nodename nor servname" in m or "gaierror" in m
def doh_resolve(host: str, timeout: float = 5.0) -> list[str]:
for url in ("https://cloudflare-dns.com/dns-query", "https://dns.google/resolve"):
try:
r = httpx.get(url, params={"name": host, "type": "A"}, headers={"accept": "application/dns-json"}, timeout=timeout)
ips = [a["data"] for a in r.json().get("Answer", []) if a.get("type") == 1]
if ips:
return ips
except Exception: # noqa: BLE001
continue
return []
def get_json(url: str, timeout: float = 8.0) -> tuple[int, dict]:
"""GET mit DNS-Rückfall → (status, json-dict oder {}). Bei Rückfall enthält das Dict `_dns_fallback: true`."""
def parse(r):
return r.status_code, (r.json() if r.headers.get("content-type", "").startswith("application/json") else {})
try:
return parse(httpx.get(url, timeout=timeout, follow_redirects=True))
except httpx.ConnectError as exc:
if not _is_dns_error(exc):
raise
parts = urlsplit(url)
for ip in doh_resolve(parts.hostname or ""):
try:
r = httpx.get(f"{parts.scheme}://{ip}{parts.path or '/'}", headers={"Host": parts.hostname}, extensions={"sni_hostname": parts.hostname},
timeout=timeout, follow_redirects=True)
status, body = parse(r)
return status, {**body, "_dns_fallback": True}
except Exception: # noqa: BLE001
continue
raise