This repository has been archived on 2026-10-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
karldns.de/karldns/dns/rfc2136.py
T

114 lines
3.3 KiB
Python
Raw Normal View History

2026-05-20 23:55:27 +02:00
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Protocol
2026-05-21 17:23:08 +02:00
from ..models import UpdateResult
from ..settings import DdnsSettings
2026-05-20 23:55:27 +02:00
from .base import DnsProviderError
class _RcodeResponse(Protocol):
def rcode(self) -> int: ...
@dataclass(frozen=True, slots=True)
class _RecordUpdate:
ttl: int
record_type: str
value: str | None
enabled: bool
2026-05-20 23:55:27 +02:00
def publish_rfc2136(result: UpdateResult, settings: DdnsSettings) -> None:
if not settings.rfc2136_server:
return
update, relative = _rfc2136_update(result.hostname, settings)
_apply_update_records(update, relative, result, settings)
_send_rfc2136_update(update, settings.rfc2136_server, "update")
2026-05-20 23:55:27 +02:00
def delete_rfc2136_records(hostname: str, settings: DdnsSettings) -> None:
if not settings.rfc2136_server:
return
update, relative = _rfc2136_update(hostname, settings)
update.delete(relative, "A")
update.delete(relative, "AAAA")
_send_rfc2136_update(update, settings.rfc2136_server, "delete")
def _rfc2136_update(hostname: str, settings: DdnsSettings) -> tuple[Any, str]:
import dns.update
zone = _rfc2136_zone(hostname, settings)
update = dns.update.Update(zone, keyring=_rfc2136_keyring(settings))
return update, _rfc2136_relative_name(hostname, zone)
def _rfc2136_zone(hostname: str, settings: DdnsSettings) -> str:
return settings.rfc2136_zone or ".".join(hostname.split(".")[1:])
def _rfc2136_relative_name(hostname: str, zone: str) -> str:
hostname = hostname.rstrip(".")
zone = zone.rstrip(".")
if hostname == zone:
return "@"
return hostname.removesuffix("." + zone).rstrip(".") or "@"
def _rfc2136_keyring(settings: DdnsSettings) -> Any | None:
if not settings.rfc2136_key_name or not settings.rfc2136_key_secret:
return None
import dns.tsigkeyring
return dns.tsigkeyring.from_text({settings.rfc2136_key_name: settings.rfc2136_key_secret})
def _apply_update_records(update: Any, relative: str, result: UpdateResult, settings: DdnsSettings) -> None:
_apply_record_update(
update,
relative,
_RecordUpdate(ttl=settings.ttl, record_type="A", value=result.ipv4, enabled=result.update_ipv4),
)
_apply_record_update(
update,
relative,
_RecordUpdate(ttl=settings.ttl, record_type="AAAA", value=result.ipv6, enabled=result.update_ipv6),
)
def _apply_record_update(
update: Any,
relative: str,
record: _RecordUpdate,
) -> None:
if not record.enabled:
return
if record.value is None:
update.delete(relative, record.record_type)
return
update.replace(relative, record.ttl, record.record_type, record.value)
def _send_rfc2136_update(update: Any, server: str, operation: str) -> None:
2026-05-20 23:55:27 +02:00
import dns.exception
import dns.query
import dns.rcode
try:
response = dns.query.tcp(update, server, timeout=5)
2026-05-20 23:55:27 +02:00
except dns.exception.DNSException as exc:
raise DnsProviderError(f"RFC2136 DNS {operation} failed") from exc
_raise_for_rfc2136_response(response, operation)
def _raise_for_rfc2136_response(response: _RcodeResponse, operation: str) -> None:
import dns.rcode
2026-05-20 23:55:27 +02:00
rcode = response.rcode()
if rcode != 0:
raise DnsProviderError(f"RFC2136 {operation} failed: {dns.rcode.to_text(dns.rcode.Rcode(rcode))}")