modified: catalog_zone_manager.py

modified:   management/commands/dns-export-zone.py
modified:   management/commands/dns-export-zones.py
modified:   management/commands/dns-transfer-endpoint.py
modified:   notify_handler.py
modified:   request_handler.py
Guard all db requests against broken database connections.
This commit is contained in:
Sven Luethi committed 2026-07-10 17:10:19 +02:00
1 parent d18e0fc262
commit 5747b044b6
6 files changed
+178 -148

No files matched your search

+116 -76
View File
@@ -7,7 +7,7 @@ from netbox_dns.models import Zone
from netbox_dns_bridge.models import IntegerKeyValueSetting, CatalogZoneMemberIdentifier from netbox_dns_bridge.models import IntegerKeyValueSetting, CatalogZoneMemberIdentifier
from uuid import uuid4 from uuid import uuid4
from base64 import b32encode from base64 import b32encode
from django.db import transaction from django.db import transaction, close_old_connections, OperationalError
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -17,19 +17,9 @@ def init() -> None:
_create_missing_member_identifiers() _create_missing_member_identifiers()
def _init_soa_serial() -> None:
try:
IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial")
logger.info("Catalog zone SOA serial number loaded from database")
except IntegerKeyValueSetting.DoesNotExist:
IntegerKeyValueSetting.objects.create(key="catalog-zone-soa-serial", value=1)
logger.debug(
"Catalog zone SOA serial number was not set in the database. Set to 1"
)
def increment_soa_serial() -> int: def increment_soa_serial() -> int:
with transaction.atomic(): with transaction.atomic():
try:
soa_serial = IntegerKeyValueSetting.objects.select_for_update().get( soa_serial = IntegerKeyValueSetting.objects.select_for_update().get(
key="catalog-zone-soa-serial" key="catalog-zone-soa-serial"
) )
@@ -46,37 +36,14 @@ def increment_soa_serial() -> int:
logger.debug(f"Catalog zone SOA serial number is now {soa_serial.value}") logger.debug(f"Catalog zone SOA serial number is now {soa_serial.value}")
return soa_serial.value return soa_serial.value
except OperationalError as e:
logger.error(
def _create_missing_member_identifiers() -> None: f"ERROR: Unable to increment the Catalog Zone's SOA serial: {e}"
existing_zone_ids = CatalogZoneMemberIdentifier.objects.values_list(
"zone_id", flat=True
)
missing_zones = Zone.objects.exclude(id__in=existing_zone_ids)
new_objects = [
CatalogZoneMemberIdentifier(
zone=zone,
name=_generate_member_identifier(),
)
for zone in missing_zones
]
for identifier in new_objects:
logger.debug(
f"Zone {identifier.zone} missing catz member identifier. Creating..."
)
CatalogZoneMemberIdentifier.objects.bulk_create(
new_objects,
ignore_conflicts=False,
) )
close_old_connections()
def create_zone(name, view_name) -> dns.zone.Zone: def create_zone(name, view_name) -> dns.zone.Zone:
soa_serial = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial").value
# Zone origin # Zone origin
origin = dns.name.from_text(name, dns.name.root) origin = dns.name.from_text(name, dns.name.root)
@@ -84,6 +51,7 @@ def create_zone(name, view_name) -> dns.zone.Zone:
zone = dns.zone.Zone(origin) zone = dns.zone.Zone(origin)
zone.rdclass = dns.rdataclass.IN zone.rdclass = dns.rdataclass.IN
try:
# get zones from netbox # get zones from netbox
nb_zones = Zone.objects.filter( nb_zones = Zone.objects.filter(
view__name=view_name, active=True view__name=view_name, active=True
@@ -120,6 +88,95 @@ def create_zone(name, view_name) -> dns.zone.Zone:
rdataset = zone.find_rdataset(rid, dns.rdatatype.TXT, create=True) rdataset = zone.find_rdataset(rid, dns.rdatatype.TXT, create=True)
rdataset.add(rdata, ttl) rdataset.add(rdata, ttl)
zone_root_node = zone.find_node(origin, create=True)
# SOA record
soa_rdataset = _create_soa_rdataset()
zone_root_node.rdatasets.append(soa_rdataset)
# NS record
ns_rdataset = _create_ns_rdataset()
zone_root_node.rdatasets.append(ns_rdataset)
# version.catz. record
version_rdataset = _create_version_rdataset()
zone_version_node = zone.find_node(
dns.name.from_text("version", origin), create=True
)
zone_version_node.rdatasets.append(version_rdataset)
return zone
except OperationalError as e:
logger.error(f"ERROR: Unable to create Catalog Zone: {e}")
close_old_connections()
return None
def _init_soa_serial() -> None:
try:
IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial")
logger.info("Catalog zone SOA serial number loaded from database")
except IntegerKeyValueSetting.DoesNotExist:
IntegerKeyValueSetting.objects.create(key="catalog-zone-soa-serial", value=1)
logger.debug(
"Catalog zone SOA serial number was not set in the database. Set to 1"
)
except OperationalError as e:
logger.error(f"ERROR: Unable to initialize the Catalog Zone's SOA serial: {e}")
close_old_connections()
def _generate_member_identifier() -> str:
return b32encode(uuid4().bytes)[0:26].lower().decode("UTF-8")
def update_member_identifier(zone: Zone) -> None:
try:
CatalogZoneMemberIdentifier.objects.update_or_create(
zone=zone,
defaults={"name": _generate_member_identifier()},
)
except OperationalError as e:
logger.error(f"ERROR: Unable to update Catalog Zone member identifier: {e}")
close_old_connections()
def _create_missing_member_identifiers() -> None:
try:
existing_zone_ids = CatalogZoneMemberIdentifier.objects.values_list(
"zone_id", flat=True
)
missing_zones = Zone.objects.exclude(id__in=existing_zone_ids)
new_objects = [
CatalogZoneMemberIdentifier(
zone=zone,
name=_generate_member_identifier(),
)
for zone in missing_zones
]
for identifier in new_objects:
logger.debug(
f"Zone {identifier.zone} missing catz member identifier. Creating..."
)
CatalogZoneMemberIdentifier.objects.bulk_create(
new_objects,
ignore_conflicts=False,
)
except OperationalError as e:
logger.error(f"ERROR: Could not create catz member identifier: {e}")
close_old_connections()
def _create_soa_rdataset() -> dns.rdataset:
try:
serial = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial").value
# SOA Record components # SOA Record components
ttl = 0 ttl = 0
rclass = dns.rdataclass.IN rclass = dns.rdataclass.IN
@@ -132,50 +189,33 @@ def create_zone(name, view_name) -> dns.zone.Zone:
minimum = 0 minimum = 0
# Create SOA rdata object # Create SOA rdata object
soa_rdata = dns.rdata.from_text( rdata = dns.rdata.from_text(
rclass, rclass,
rtype, rtype,
f"{mname} {rname} {soa_serial} {refresh} {retry} {expire} {minimum}", f"{mname} {rname} {serial} {refresh} {retry} {expire} {minimum}",
) )
# Create Rdataset and add the RDATA to it # Create Rdataset and add the RDATA to it
soa_rdataset = dns.rdataset.Rdataset(rclass, rtype) rdataset = dns.rdataset.Rdataset(rclass, rtype)
soa_rdataset.add(soa_rdata, ttl) rdataset.add(rdata, ttl)
return rdataset
# Add to the origin node in the zone except OperationalError as e:
node = zone.find_node(origin, create=True) logger.error(f"ERROR: Unable to access catz SOA record in database: {e}")
node.rdatasets.append(soa_rdataset) close_old_connections()
def _create_ns_rdataset() -> dns.rdataset:
# NS record for catz. # NS record for catz.
ns_name = dns.name.from_text("invalid", dns.name.root) name = dns.name.from_text("invalid", dns.name.root)
ns_rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, str(ns_name)) rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, str(name))
ns_rdataset = dns.rdataset.Rdataset(dns.rdataclass.IN, dns.rdatatype.NS) rdataset = dns.rdataset.Rdataset(dns.rdataclass.IN, dns.rdatatype.NS)
ns_rdataset.add(ns_rdata, 0) rdataset.add(rdata, 0)
return rdataset
# Add to node (catz. is the origin)
ns_node = zone.find_node(origin, create=True)
ns_node.rdatasets.append(ns_rdataset)
# TXT record for version.catz.
version_name = dns.name.from_text("version", origin)
txt_rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.TXT, '"2"')
txt_rdataset = dns.rdataset.Rdataset(dns.rdataclass.IN, dns.rdatatype.TXT)
txt_rdataset.add(txt_rdata, 0)
# Add to node version.catz.
txt_node = zone.find_node(version_name, create=True)
txt_node.rdatasets.append(txt_rdataset)
return zone
def _generate_member_identifier() -> str: def _create_version_rdataset() -> dns.rdataset:
return b32encode(uuid4().bytes)[0:26].lower().decode("UTF-8") rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.TXT, '"2"')
rdataset = dns.rdataset.Rdataset(dns.rdataclass.IN, dns.rdatatype.TXT)
rdataset.add(rdata, 0)
def update_member_identifier(zone: Zone) -> None: return rdataset
CatalogZoneMemberIdentifier.objects.update_or_create(
zone=zone,
defaults={"name": _generate_member_identifier()},
)
@@ -1,8 +1,3 @@
import os
import dns.zone
import dns.rdatatype
import dns.rdataclass
import dns.exception
import netbox_dns.models import netbox_dns.models
from netbox_dns_bridge.utils import export_bind_zone_file from netbox_dns_bridge.utils import export_bind_zone_file
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@@ -24,10 +19,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
# if len(options) < 2:
# print("export-zone <zone name> <file path>")
# sys.exit(1)
# Load parameters # Load parameters
view_name = options["view"] view_name = options["view"]
zone_name = options["zone"] zone_name = options["zone"]
@@ -48,13 +39,11 @@ class Command(BaseCommand):
if nb_zone: if nb_zone:
export_bind_zone_file(nb_zone, file_path=file_path) export_bind_zone_file(nb_zone, file_path=file_path)
else:
print("Zone not found in Netbox. Aborting")
sys.exit(1)
except Exception as e:
raise CommandError(f"Failed to export zone: {e}")
self.stdout.write( self.stdout.write(
self.style.SUCCESS(f"Zone '{zone_name}' exported to '{file_path}'") self.style.SUCCESS(f"Zone '{zone_name}' exported to '{file_path}'")
) )
else:
self.stderr.write("Zone not found in Netbox. Aborting")
except Exception as e:
raise CommandError(f"Failed to export zone: {e}")
@@ -1,8 +1,4 @@
import os import os
import dns.zone
import dns.rdatatype
import dns.rdataclass
import dns.exception
import netbox_dns.models import netbox_dns.models
from netbox_dns_bridge.utils import export_bind_zone_file from netbox_dns_bridge.utils import export_bind_zone_file
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@@ -9,36 +9,24 @@ import dns.rdataclass
import dns.rdtypes import dns.rdtypes
import dns.exception import dns.exception
import dns.renderer import dns.renderer
import logging
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.db import close_old_connections, OperationalError
from netbox_dns.models import View from netbox_dns.models import View
from netbox_dns_bridge import catalog_zone_manager as catzm from netbox_dns_bridge import catalog_zone_manager as catzm
from netbox_dns_bridge.request_handler import UDPRequestHandler, TCPRequestHandler from netbox_dns_bridge.request_handler import UDPRequestHandler, TCPRequestHandler
from netbox_dns_bridge.dns_server import UDPDNSServer, TCPDNSServer from netbox_dns_bridge.dns_server import UDPDNSServer, TCPDNSServer
from netbox_dns_bridge.models import IntegerKeyValueSetting
from netbox_dns_bridge.logger import get_logger from netbox_dns_bridge.logger import get_logger
logger = get_logger(__name__) LOGGER = get_logger(__name__)
SETTINGS = settings.PLUGINS_CONFIG.get("netbox_dns_bridge", {})
class Command(BaseCommand): class Command(BaseCommand):
help = "Run a minimal AXFR DNS server using data from NetBox DNS plugin" help = "Run a minimal AXFR DNS server using data from NetBox DNS plugin"
def load_settings(self): def _load_tsig_key_settings(self):
self.settings = settings.PLUGINS_CONFIG.get("netbox_dns_bridge", None)
if not self.settings:
raise RuntimeError(
"Command failed to initialize due to missing settings. Terminating Netbox."
)
self.tsig_keys = self.settings.get("tsig_keys", None)
if not self.tsig_keys:
raise RuntimeError("tsig_keys variable not set in plugin settings.")
# Load TSIG keys and map them to views
def load_tsig_key_settings(self):
self.keyring = {} self.keyring = {}
self.tsig_view_map = {} self.tsig_view_map = {}
@@ -48,7 +36,7 @@ class Command(BaseCommand):
algorithm_str = data.get("algorithm", "hmac-sha256") algorithm_str = data.get("algorithm", "hmac-sha256")
if not raw_key_name or not secret: if not raw_key_name or not secret:
logger.error( LOGGER.error(
f"Skipping TSIG key for view {view_name}: missing keyname or secret." f"Skipping TSIG key for view {view_name}: missing keyname or secret."
) )
continue continue
@@ -56,10 +44,12 @@ class Command(BaseCommand):
try: try:
nb_view = View.objects.get(name=view_name) nb_view = View.objects.get(name=view_name)
except View.DoesNotExist: except View.DoesNotExist:
logger.error( LOGGER.error(
f"Skipping TSIG key {raw_key_name}: view '{view_name}' not found." f"Skipping TSIG key {raw_key_name}: view '{view_name}' not found."
) )
continue continue
except OperationalError:
close_old_connections()
# Normalize key name to absolute DNS name # Normalize key name to absolute DNS name
key_name_obj = dns.name.from_text(raw_key_name, origin=None).canonicalize() key_name_obj = dns.name.from_text(raw_key_name, origin=None).canonicalize()
@@ -71,11 +61,11 @@ class Command(BaseCommand):
name=key_name_obj, secret=secret, algorithm=algorithm_str name=key_name_obj, secret=secret, algorithm=algorithm_str
) )
self.tsig_view_map[key_name_str] = nb_view self.tsig_view_map[key_name_str] = nb_view
logger.info(f"Loaded TSIG key {key_name_str} for view {nb_view.name}") LOGGER.info(f"Loaded TSIG key {key_name_str} for view {nb_view.name}")
if not self.keyring: if not self.keyring:
msg = "No TSIG keys found in database." msg = "No TSIG keys found in database."
logger.critical(msg) LOGGER.critical(msg)
raise RuntimeError(msg) raise RuntimeError(msg)
def add_arguments(self, parser): def add_arguments(self, parser):
@@ -92,9 +82,11 @@ class Command(BaseCommand):
address = options["address"] address = options["address"]
catzm.init() catzm.init()
# Initialize settings self.tsig_keys = SETTINGS.get("tsig_keys", None)
self.load_settings() if not self.tsig_keys:
self.load_tsig_key_settings() raise RuntimeError("tsig_keys variable not set in plugin settings.")
self._load_tsig_key_settings()
udp_server = UDPDNSServer( udp_server = UDPDNSServer(
(address, port), UDPRequestHandler, self.keyring, self.tsig_view_map (address, port), UDPRequestHandler, self.keyring, self.tsig_view_map
@@ -104,15 +96,15 @@ class Command(BaseCommand):
(address, port), TCPRequestHandler, self.keyring, self.tsig_view_map (address, port), TCPRequestHandler, self.keyring, self.tsig_view_map
) )
def run_udp_server(server): def _run_udp_server(server):
logger.info(f"Query endpoint listening on {address} udp/{port}") LOGGER.info(f"Query endpoint listening on {address} udp/{port}")
server.serve_forever() server.serve_forever()
udp_thread = threading.Thread( udp_thread = threading.Thread(
target=run_udp_server, args=(udp_server,), daemon=True target=_run_udp_server, args=(udp_server,), daemon=True
) )
udp_thread.start() udp_thread.start()
logger.info(f"Query endpoint listening on {address} tcp/{port}") LOGGER.info(f"Query endpoint listening on {address} tcp/{port}")
tcp_server.serve_forever() tcp_server.serve_forever()
+6 -2
View File
@@ -6,6 +6,7 @@ import dns.rdatatype
import dns.tsig import dns.tsig
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.db import close_old_connections, OperationalError
from netbox.jobs import JobRunner from netbox.jobs import JobRunner
from netbox_dns.models import View, Zone from netbox_dns.models import View, Zone
from .logger import get_logger from .logger import get_logger
@@ -48,10 +49,10 @@ class SendDNSNotify(JobRunner):
view_name = kwargs["view_name"] view_name = kwargs["view_name"]
zone_name = kwargs["zone_name"] zone_name = kwargs["zone_name"]
try:
view = View.objects.get(name=view_name) view = View.objects.get(name=view_name)
tsig_key = self._load_tsig_key(view_name) tsig_key = self._load_tsig_key(view_name)
cutoff_hours = SETTINGS.get("notify_client_alive_threshold_hours", 24)
cutoff_hours = SETTINGS.get("notify_client_dead_after_hours", 24)
seen_cutoff = timezone.now() - timezone.timedelta(hours=cutoff_hours) seen_cutoff = timezone.now() - timezone.timedelta(hours=cutoff_hours)
clients = SeenTransferClients.objects.filter( clients = SeenTransferClients.objects.filter(
view=view, last_seen__gte=seen_cutoff view=view, last_seen__gte=seen_cutoff
@@ -73,6 +74,9 @@ class SendDNSNotify(JobRunner):
self.logger.error( self.logger.error(
f"NOTIFY failed for {client.source_ip} zone {name}: {e}" f"NOTIFY failed for {client.source_ip} zone {name}: {e}"
) )
except OperationalError as e:
self.logger.error(f"NOTIFY failed due to unexpected error: {e}")
close_old_connections()
def schedule(zone: Zone): def schedule(zone: Zone):
+32 -23
View File
@@ -12,13 +12,16 @@ import dns.exception
import dns.renderer import dns.renderer
from .logger import get_logger from .logger import get_logger
from django.utils import timezone from django.utils import timezone
from django.db import close_old_connections, OperationalError
from django.conf import settings
from netbox_dns.models import Zone, Record, View from netbox_dns.models import Zone, Record, View
from netbox_dns_bridge import catalog_zone_manager as catzm from netbox_dns_bridge import catalog_zone_manager as catzm
from .models import SeenTransferClients from .models import SeenTransferClients
logger = get_logger(__name__) LOGGER = get_logger(__name__)
SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"] SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"]
class DNSBaseRequestHandler(socketserver.BaseRequestHandler): class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
def __init__(self, request, client_address, server) -> None: def __init__(self, request, client_address, server) -> None:
self.MAX_WIRE = 65535 self.MAX_WIRE = 65535
@@ -34,16 +37,11 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
view__name=view_name, view__name=view_name,
active=True, active=True,
) )
except Zone.DoesNotExist:
return None
# Build DNS zone # Build DNS zone
zone = dns.zone.Zone(zone_name, dns.name.root) zone = dns.zone.Zone(zone_name, dns.name.root)
zone.rdclass = dns.rdataclass.IN zone.rdclass = dns.rdataclass.IN
nb_records = Record.objects.filter( nb_records = Record.objects.filter(zone=nb_zone, active=True)
zone=nb_zone, active=True
)
rdatasets_dict = {} rdatasets_dict = {}
@@ -109,13 +107,19 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# Check if the rdataset has any rdata before creating an RRset # Check if the rdataset has any rdata before creating an RRset
if not rdataset: if not rdataset:
logger.debug(f"Skipping empty rdataset for {name} {rdtype}") LOGGER.debug(f"Skipping empty rdataset for {name} {rdtype}")
continue # Skip empty rdataset continue # Skip empty rdataset
# Replace the rdataset for the given name and type # Replace the rdataset for the given name and type
zone.replace_rdataset(name, rdataset) zone.replace_rdataset(name, rdataset)
return zone return zone
except Zone.DoesNotExist:
return None
except OperationalError as e:
LOGGER.error(f"DB ERROR: {e}")
close_old_connections()
def _deny_request_bad_tsig(self, wire, tsig_error: dns.rcode) -> None: def _deny_request_bad_tsig(self, wire, tsig_error: dns.rcode) -> None:
# Use empty keyring to parse TSIG without validating # Use empty keyring to parse TSIG without validating
query = dns.message.from_wire( query = dns.message.from_wire(
@@ -146,12 +150,17 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
def _track_seen_client(self, source_ip: str, view: View) -> None: def _track_seen_client(self, source_ip: str, view: View) -> None:
if SETTINGS.get("notify_clients", False): if SETTINGS.get("notify_clients", False):
try:
SeenTransferClients.objects.update_or_create( SeenTransferClients.objects.update_or_create(
source_ip=source_ip, source_ip=source_ip,
view=view, view=view,
defaults={"last_seen": timezone.now()}, defaults={"last_seen": timezone.now()},
) )
except OperationalError as e:
LOGGER.error(f"DB ERROR: {e}")
close_old_connections()
def _handle_soa_request(self, query, soa_rrset, zone, peer, nb_view, dname) -> None: def _handle_soa_request(self, query, soa_rrset, zone, peer, nb_view, dname) -> None:
# We assume that the SOA rdataset has at least one record (it usually does). # We assume that the SOA rdataset has at least one record (it usually does).
soa_rdata = soa_rrset[0] # Get the first SOA record soa_rdata = soa_rrset[0] # Get the first SOA record
@@ -185,11 +194,11 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
data = response.to_wire(max_size=512) data = response.to_wire(max_size=512)
self._send_response(data) self._send_response(data)
logger.info(f"{peer} SOA {nb_view.name}/{dname}") LOGGER.info(f"{peer} SOA {nb_view.name}/{dname}")
def _handle_axfr_request(self, query, zone, peer, nb_view, dname) -> None: def _handle_axfr_request(self, query, zone, peer, nb_view, dname) -> None:
if query.keyname not in self.server.keyring: if query.keyname not in self.server.keyring:
logger.error(f"AXFR aborted: keyname {query.keyname} not in keyring") LOGGER.error(f"AXFR aborted: keyname {query.keyname} not in keyring")
self._deny_request(query) self._deny_request(query)
return return
@@ -262,7 +271,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
try: try:
r.add_rrset(dns.renderer.ANSWER, rrset) r.add_rrset(dns.renderer.ANSWER, rrset)
except dns.exception.TooBig: except dns.exception.TooBig:
logger.error( LOGGER.error(
f"RRset {rrset.name}/{dns.rdatatype.to_text(rrset.rdtype)} " f"RRset {rrset.name}/{dns.rdatatype.to_text(rrset.rdtype)} "
f"exceeds MAX_WIRE ({self.MAX_WIRE}) and cannot be sent; " f"exceeds MAX_WIRE ({self.MAX_WIRE}) and cannot be sent; "
f"skipping." f"skipping."
@@ -283,7 +292,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
) )
wire = r.get_wire() wire = r.get_wire()
self._send_response(wire) self._send_response(wire)
logger.info(f"{peer} AXFR {nb_view.name}/{dname}") LOGGER.info(f"{peer} AXFR {nb_view.name}/{dname}")
def _handle_dns_query(self, wire) -> None: def _handle_dns_query(self, wire) -> None:
peer = self.client_address[0] peer = self.client_address[0]
@@ -296,17 +305,17 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
) )
except dns.tsig.BadSignature as e: except dns.tsig.BadSignature as e:
logger.warning(f"Request denied from {peer} failed TSIG verification: {e}") LOGGER.warning(f"Request denied from {peer} failed TSIG verification: {e}")
self._deny_request_bad_tsig(wire, dns.rcode.BADSIG) self._deny_request_bad_tsig(wire, dns.rcode.BADSIG)
return return
except (dns.message.UnknownTSIGKey, dns.tsig.BadAlgorithm) as e: except (dns.message.UnknownTSIGKey, dns.tsig.BadAlgorithm) as e:
logger.warning(f"Request denied from {peer} with bad TSIG key: {e}") LOGGER.warning(f"Request denied from {peer} with bad TSIG key: {e}")
self._deny_request_bad_tsig(wire, dns.rcode.BADKEY) self._deny_request_bad_tsig(wire, dns.rcode.BADKEY)
return return
except Exception as e: except Exception as e:
logger.error("Error parsing query: ", e) LOGGER.error("Error parsing query: ", e)
return return
# If there was no question in the query, refuse # If there was no question in the query, refuse
@@ -321,7 +330,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# Only process AXFR/SOA queris # Only process AXFR/SOA queris
if qtype not in (dns.rdatatype.AXFR, dns.rdatatype.SOA): if qtype not in (dns.rdatatype.AXFR, dns.rdatatype.SOA):
logger.warning( LOGGER.warning(
f"Request denied from {peer}: Request was not AXFR or SOA (Type: {qtype})" f"Request denied from {peer}: Request was not AXFR or SOA (Type: {qtype})"
) )
self._deny_request(query) self._deny_request(query)
@@ -329,7 +338,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# Identify TSIG key used # Identify TSIG key used
if not query.had_tsig: if not query.had_tsig:
logger.warning(f"Request denied from {peer}: No TSIG key used") LOGGER.warning(f"Request denied from {peer}: No TSIG key used")
self._deny_request(query) self._deny_request(query)
return return
@@ -338,7 +347,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# Check if the key matches a view # Check if the key matches a view
nb_view = self.server.tsig_view_map.get(key_name) nb_view = self.server.tsig_view_map.get(key_name)
if not nb_view: if not nb_view:
logger.warning( LOGGER.warning(
f"Request denied from {peer}: {key_name} does not match a view" f"Request denied from {peer}: {key_name} does not match a view"
) )
self._deny_request(query) self._deny_request(query)
@@ -351,14 +360,14 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
zone = self._getZoneFromNB(dname, nb_view.name) zone = self._getZoneFromNB(dname, nb_view.name)
# When zone was not found, let client know # When zone was not found, let client know
if not zone: if not zone:
logger.warning(f"Zone {dname} not found in view {nb_view.name}") LOGGER.warning(f"Zone {dname} not found in view {nb_view.name}")
self._deny_request(query) self._deny_request(query)
return return
# Retrieve the existing SOA record from the Zone # Retrieve the existing SOA record from the Zone
soa_rrset = zone.get_rdataset(zone.origin, dns.rdatatype.SOA) soa_rrset = zone.get_rdataset(zone.origin, dns.rdatatype.SOA)
if soa_rrset is None: if soa_rrset is None:
logger.error(f"Zone {dname} has no SOA — aborting") LOGGER.error(f"Zone {dname} has no SOA — aborting")
return return
if qtype == dns.rdatatype.SOA: if qtype == dns.rdatatype.SOA:
@@ -383,7 +392,7 @@ class UDPRequestHandler(DNSBaseRequestHandler):
try: try:
self._handle_dns_query(data) self._handle_dns_query(data)
except Exception as e: except Exception as e:
logger.error(f"Error handling request from {peer}: {e}") LOGGER.error(f"Error handling request from {peer}: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@@ -425,9 +434,9 @@ class TCPRequestHandler(DNSBaseRequestHandler):
self._handle_dns_query(wire) self._handle_dns_query(wire)
except socket.timeout: except socket.timeout:
logger.debug(f"Connection from {peer} timed out") LOGGER.debug(f"Connection from {peer} timed out")
except Exception as e: except Exception as e:
logger.error(f"Error handling request from {peer}: {e}") LOGGER.error(f"Error handling request from {peer}: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()