]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Force manual DNAME handling to be acknowledged
authorMichał Kępień <michal@isc.org>
Fri, 30 May 2025 16:08:54 +0000 (18:08 +0200)
committerMichał Kępień <michal@isc.org>
Fri, 30 May 2025 16:08:54 +0000 (18:08 +0200)
Adding proper DNAME support to AsyncDnsServer would add complexity to
its code for little gain: DNAME use in custom system test servers is
limited to crafting responses that attempt to trigger bugs in named.

This fact will not be obvious to AsyncDnsServer users as it
automatically loads all zone files it finds and handles CNAME records
like a normal authoritative DNS server would.

Therefore, to prevent surprises:

  - raise an exception whenever DNAME records are found in any of the
    zone files loaded by AsyncDnsServer,

  - add a new optional argument to the AsyncDnsServer constructor that
    enables suppressing this new behavior, enabling zones with DNAME
    records to be loaded anyway.

This enables response handlers to use the DNAME records present in zone
files in arbitrary ways without complicating the "base" code.

bin/tests/system/isctest/asyncserver.py

index 522537b9d4c2ec4676f1d429e0c8aefa1169a02e..2341f0311048d797e67eb09cac538a4d111db2ec 100644 (file)
@@ -533,11 +533,12 @@ class AsyncDnsServer(AsyncServer):
     response from scratch, without using zone data at all.
     """
 
-    def __init__(self):
+    def __init__(self, acknowledge_manual_dname_handling: bool = False) -> None:
         super().__init__(self._handle_udp, self._handle_tcp, "ans.pid")
 
         self._zone_tree: _ZoneTree = _ZoneTree()
         self._response_handlers: List[ResponseHandler] = []
+        self._acknowledge_manual_dname_handling = acknowledge_manual_dname_handling
 
         self._load_zones()
 
@@ -572,11 +573,31 @@ class AsyncDnsServer(AsyncServer):
             entry_path = pathlib.Path(entry.path)
             if entry_path.suffix != ".db":
                 continue
-            origin = dns.name.from_text(entry_path.stem)
-            logging.info("Loading zone file %s", entry_path)
-            zone = dns.zone.from_file(entry.path, origin, relativize=False)
+            zone = self._load_zone(entry_path)
             self._zone_tree.add(zone)
 
+    def _load_zone(self, zone_file_path: pathlib.Path) -> dns.zone.Zone:
+        origin = dns.name.from_text(zone_file_path.stem)
+        logging.info("Loading zone file %s", zone_file_path)
+        with open(zone_file_path, encoding="utf-8") as zone_file:
+            zone = dns.zone.from_file(zone_file, origin, relativize=False)
+        self._abort_if_dname_found_unless_acknowledged(zone)
+        return zone
+
+    def _abort_if_dname_found_unless_acknowledged(self, zone: dns.zone.Zone) -> None:
+        if self._acknowledge_manual_dname_handling:
+            return
+
+        error = f'DNAME records found in zone "{zone.origin}"; '
+        error += "this server does not handle DNAME in a standards-compliant way; "
+        error += "pass `acknowledge_manual_dname_handling=True` to the "
+        error += "AsyncDnsServer constructor to acknowledge this and load zone anyway"
+
+        for node in zone.nodes.values():
+            for rdataset in node:
+                if rdataset.rdtype == dns.rdatatype.DNAME:
+                    raise ValueError(error)
+
     async def _handle_udp(
         self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport
     ) -> None: