Commit f809b1b6d5 for bind
commit f809b1b6d524f503462891815088e85474fb4c42
Author: Štěpán Balážik <stepan@isc.org>
Date: Tue Sep 8 19:34:52 2026 +0200
Replace DomainHandler with a matcher
DomainHandler existed only to match a query below one of a list of
domains, remembering which; its users now declare `Domain(...)`
themselves, and the matcher keeps the most specific domain matched
in `matched_domain` for the handler that needs it.
Assisted-by: Claude:claude-fable-5
diff --git a/bin/tests/system/README.md b/bin/tests/system/README.md
index d803b33400..ad0263dc03 100644
--- a/bin/tests/system/README.md
+++ b/bin/tests/system/README.md
@@ -441,16 +441,10 @@ Most importantly, avoid the temptation to define all DNS responses that a given
easier to follow for static DNS data. Splitting up static DNS data and custom
behavior also makes it easier to follow the idea behind each test.
-The most commonly subclassed handler classes are (ordered by descending
-specificity):
-
- - `QnameQtypeHandler`
- - `QnameHandler`
- - `DomainHandler`
-
-These handler classes require certain properties (e.g. `qnames`, `qtypes`,
-`domains`) to be defined by their subclasses. These properties define the set
-of queries that a given handler should be used for. Please see
+A handler declares the queries it handles in its `matcher`, built from the
+matchers in `isctest/asyncserver/matchers.py` (`Qname`, `Qtype`, `Domain`,
+...) combined with `&`, `|` and `~`; the first installed handler whose
+matcher matches a query handles it. Please see
`isctest/asyncserver/handlers.py` for up-to-date information on available handler classes
and existing `ans.py` files for how they can be used in practice. Consult the
log files (`ans.run`) in case a query is not matched by its intended handler.
@@ -472,15 +466,15 @@ from collections.abc import AsyncGenerator
import dns.flags
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
-class TruncateHandler(DomainHandler):
+class TruncateHandler(ResponseHandler):
"""Answer everything under broken.example. with TC=1."""
- domains = ["broken.example."]
+ matcher = Domain("broken.example.")
async def get_responses(
self, qctx: QueryContext
@@ -584,8 +578,8 @@ delegation pattern end to end.
The existing mock servers are the best reference. To find them, grep for
what you're about to use:
`git grep -l isctest.asyncserver -- '*/ans*/ans.py'` lists every python
-mock, and a grep for the base class
-(`DomainHandler`, `QnameHandler`, `ConnectionHandler`) or the response
+mock, and a grep for the matcher (`Domain`, `Qname`, ...), the base class
+(`StaticResponseHandler`, `ConnectionHandler`) or the response
action (`ResponseDrop`, `BytesResponseSend`, ...) you need usually turns
up a test already doing something similar. The full toolbox lives in
`isctest/asyncserver/` (query matching, TCP connection handling, TSIG
diff --git a/bin/tests/system/chain/ans3/ans.py b/bin/tests/system/chain/ans3/ans.py
index 4e06df3551..ba9645234d 100644
--- a/bin/tests/system/chain/ans3/ans.py
+++ b/bin/tests/system/chain/ans3/ans.py
@@ -18,9 +18,9 @@ import dns.rdatatype
import dns.rrset
import dns.zone
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
def get_dname_rrset_at_name(
@@ -35,13 +35,13 @@ def get_dname_rrset_at_name(
return rrset
-class CnameThenDnameHandler(DomainHandler):
+class CnameThenDnameHandler(ResponseHandler):
"""
For certain trigger QNAMEs, insert a DNAME RRset after the CNAME chain
prepared from zone data.
"""
- domains = ["example.broken."]
+ matcher = Domain("example.broken.")
async def get_responses(
self, qctx: QueryContext
@@ -60,7 +60,7 @@ class CnameThenDnameHandler(DomainHandler):
yield DnsResponseSend(qctx.response)
-class Cve202125215(DomainHandler):
+class Cve202125215(ResponseHandler):
"""
Attempt to trigger the resolver variant of CVE-2021-25215. A `named`
instance cannot be used for serving the DNAME records returned by this
@@ -68,7 +68,7 @@ class Cve202125215(DomainHandler):
crash while answering the queries sent by the tested resolver.
"""
- domains = ["example.dname."]
+ matcher = Domain("example.dname.")
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py
index b9771b03f8..8e0003cd1a 100644
--- a/bin/tests/system/chain/ans4/ans.py
+++ b/bin/tests/system/chain/ans4/ans.py
@@ -23,9 +23,14 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import ControlCommand, ControllableAsyncDnsServer, QueryContext
+from isctest.asyncserver import (
+ ControlCommand,
+ ControllableAsyncDnsServer,
+ QueryContext,
+ ResponseHandler,
+)
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
class ChainNameGenerator:
@@ -401,7 +406,7 @@ class ChainSetupCommand(ControlCommand):
return rrsets
-class ChainResponseHandler(DomainHandler):
+class ChainResponseHandler(ResponseHandler):
"""
For trigger queries (`test.domain.nil`), return a chained response
previously prepared by `ChainSetupCommand`.
@@ -409,7 +414,7 @@ class ChainResponseHandler(DomainHandler):
For any other query, return a non-chained response (a single A RRset).
"""
- domains = ["domain.nil."]
+ matcher = Domain("domain.nil.")
def __init__(self, answer_rrsets: list[dns.rrset.RRset]):
super().__init__()
diff --git a/bin/tests/system/digdelv/ans7/ans.py b/bin/tests/system/digdelv/ans7/ans.py
index 63c8f8ac4d..98f0262fde 100644
--- a/bin/tests/system/digdelv/ans7/ans.py
+++ b/bin/tests/system/digdelv/ans7/ans.py
@@ -13,25 +13,26 @@ from collections.abc import AsyncGenerator
import dns.rcode
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import CloseConnection, DnsResponseSend, ResponseDrop
-from isctest.asyncserver.handlers import DomainHandler, IgnoreAllQueries
+from isctest.asyncserver.handlers import IgnoreAllQueries
+from isctest.asyncserver.matchers import Domain
-class SilentHandler(DomainHandler, IgnoreAllQueries):
+class SilentHandler(IgnoreAllQueries):
"""
Handler that doesn't respond.
"""
- domains = ["silent.example"]
+ matcher = Domain("silent.example")
-class CloseHandler(DomainHandler):
+class CloseHandler(ResponseHandler):
"""
Handler that doesn't respond and closes TCP connection.
"""
- domains = ["close.example"]
+ matcher = Domain("close.example")
async def get_responses(
self, qctx: QueryContext
@@ -39,12 +40,12 @@ class CloseHandler(DomainHandler):
yield CloseConnection()
-class SilentThenServfailHandler(DomainHandler):
+class SilentThenServfailHandler(ResponseHandler):
"""
Handler that drops one query and response to the next one with SERVFAIL.
"""
- domains = ["silent-then-servfail.example"]
+ matcher = Domain("silent-then-servfail.example")
counter = 0
async def get_responses(
diff --git a/bin/tests/system/dnssec_cname_response/ans2/ans.py b/bin/tests/system/dnssec_cname_response/ans2/ans.py
index 11bbc207e5..bbfe05b133 100644
--- a/bin/tests/system/dnssec_cname_response/ans2/ans.py
+++ b/bin/tests/system/dnssec_cname_response/ans2/ans.py
@@ -21,7 +21,7 @@ import dns.zone
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain, Qtype
# 'example.' answers DNSKEY/NSEC/NSEC3/RRSIG queries with a CNAME (the
# meta-types whose CNAME answer the resolver and validator must cope with).
@@ -62,7 +62,7 @@ def rrset_with_rrsig(
return rrsets
-class LoneAHandler(DomainHandler):
+class LoneAHandler(ResponseHandler):
"""
Answer any query with a single unrelated A record (no RRSIG and no
alias). An RRSIG query is handled by the resolver as a subset of ANY,
@@ -70,7 +70,7 @@ class LoneAHandler(DomainHandler):
for a validator that was never started.
"""
- domains = ["lone-a.example."]
+ matcher = Domain("lone-a.example.")
async def get_responses(
self, qctx: QueryContext
@@ -94,40 +94,28 @@ class CnameHandler(ResponseHandler):
yield DnsResponseSend(qctx.response)
-class ExampleMetatypeCnameHandler(DomainHandler, CnameHandler):
- domains = ["example."]
- _qtypes = frozenset(
- {
- dns.rdatatype.DNSKEY,
- dns.rdatatype.NSEC,
- dns.rdatatype.NSEC3,
- dns.rdatatype.RRSIG,
- }
+class ExampleMetatypeCnameHandler(CnameHandler):
+ matcher = Domain("example.") & Qtype(
+ dns.rdatatype.DNSKEY,
+ dns.rdatatype.NSEC,
+ dns.rdatatype.NSEC3,
+ dns.rdatatype.RRSIG,
)
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qtype in self._qtypes and super().match(qctx)
-
-
-class SecureDsCnameHandler(DomainHandler, CnameHandler):
- domains = ["secure."]
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qtype == dns.rdatatype.DS and super().match(qctx)
+class SecureDsCnameHandler(CnameHandler):
+ matcher = Domain("secure.") & Qtype(dns.rdatatype.DS)
-class SignedZoneHandler(DomainHandler):
+class SignedZoneHandler(ResponseHandler):
"""
Serve a signed zone faithfully.
"""
def __init__(self, zone: dns.zone.Zone) -> None:
self._zone = zone
- super().__init__()
-
- @property
- def domains(self) -> list[str]:
- return [self._zone.origin.to_text()]
+ assert self._zone.origin
+ self.matcher = Domain(self._zone.origin)
def _soa(self) -> list[dns.rrset.RRset]:
return rrset_with_rrsig(self._zone, self._zone.origin, dns.rdatatype.SOA)
@@ -156,20 +144,17 @@ class SignedZoneHandler(DomainHandler):
yield DnsResponseSend(qctx.response)
-class StuffedNxdomainHandler(DomainHandler):
+class StuffedNxdomainHandler(ResponseHandler):
"""
Answer NXDOMAIN with every NSEC3 RRset from a signed zone.
"""
def __init__(self, zone: dns.zone.Zone) -> None:
self._zone = zone
- super().__init__()
+ assert self._zone.origin
+ self.matcher = Domain(self._zone.origin)
self._nsec3_authority = self._build_authority()
- @property
- def domains(self) -> list[str]:
- return [self._zone.origin.to_text()]
-
def _build_authority(self) -> list[dns.rrset.RRset]:
authority = rrset_with_rrsig(self._zone, self._zone.origin, dns.rdatatype.SOA)
for name, _ in self._zone.iterate_rdatasets(dns.rdatatype.NSEC3):
diff --git a/bin/tests/system/dnssec_nsec3/ans1/f025.py b/bin/tests/system/dnssec_nsec3/ans1/f025.py
index 7149172fde..953a5ad221 100644
--- a/bin/tests/system/dnssec_nsec3/ans1/f025.py
+++ b/bin/tests/system/dnssec_nsec3/ans1/f025.py
@@ -31,9 +31,9 @@ from dnssec_nsec3.ans1.common import (
rrset_from_rdata,
soa_rrset,
)
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
TTL = 300
PARENT = "f025.test."
@@ -116,8 +116,8 @@ def add_wildcard_answer(response: dns.message.Message, owner: str, child: Key) -
response.answer.append(wildcard_rrsig(owner, child))
-class F025Handler(DomainHandler):
- domains = [PARENT, CHILD]
+class F025Handler(ResponseHandler):
+ matcher = Domain(PARENT, CHILD)
def __init__(self, keys: dict[str, Key]) -> None:
super().__init__()
diff --git a/bin/tests/system/dnssec_nsec3/ans1/f055.py b/bin/tests/system/dnssec_nsec3/ans1/f055.py
index 2a79d84ea3..3aff055ace 100644
--- a/bin/tests/system/dnssec_nsec3/ans1/f055.py
+++ b/bin/tests/system/dnssec_nsec3/ans1/f055.py
@@ -34,9 +34,9 @@ from dnssec_nsec3.ans1.common import (
rrset_from_rdata,
soa_rrset,
)
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
TTL = 300
F055_ZONE = "f055.test."
@@ -145,8 +145,8 @@ def add_nsec3_nxdomain(
add_signed(response.authority, chain.cover_for(f"*.{closest}"), signer)
-class F055Handler(DomainHandler):
- domains = [F055_ZONE]
+class F055Handler(ResponseHandler):
+ matcher = Domain(F055_ZONE)
def __init__(self, keys: dict[str, Key]) -> None:
super().__init__()
diff --git a/bin/tests/system/dnssec_parent_rrsig/ans1/ans.py b/bin/tests/system/dnssec_parent_rrsig/ans1/ans.py
index 2912e436e7..750d4cae1e 100644
--- a/bin/tests/system/dnssec_parent_rrsig/ans1/ans.py
+++ b/bin/tests/system/dnssec_parent_rrsig/ans1/ans.py
@@ -29,6 +29,7 @@ import dns.rrset
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
+from isctest.asyncserver.matchers import Domain
TTL = 300
PARENT = "f044.test."
@@ -124,13 +125,11 @@ class AncestorAdditionalHandler(ResponseHandler):
def __init__(self, keys: dict[str, Key]) -> None:
self.keys = keys
self.parent = name(PARENT)
+ self.matcher = Domain(self.parent)
self.child = name(CHILD)
self.query = name(QUERY)
self.service = name(SERVICE)
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qname.is_subdomain(self.parent)
-
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
diff --git a/bin/tests/system/dnssec_py/ans4/delegationtrap_ans.py b/bin/tests/system/dnssec_py/ans4/delegationtrap_ans.py
index 95cd72ed0a..d3ce9a48ae 100644
--- a/bin/tests/system/dnssec_py/ans4/delegationtrap_ans.py
+++ b/bin/tests/system/dnssec_py/ans4/delegationtrap_ans.py
@@ -54,9 +54,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
ZONE_NAME = "attack.delegationtrap."
SERVER_IP = "10.53.0.4"
@@ -64,12 +64,12 @@ TTL = 300
PEM_PATH = Path("attack_delegationtrap.pem")
-class DelegationTrapHandler(DomainHandler):
+class DelegationTrapHandler(ResponseHandler):
"""
Serve every label under attack.delegationtrap. as a secure zone cut.
"""
- domains = [ZONE_NAME]
+ matcher = Domain(ZONE_NAME)
def __init__(self) -> None:
super().__init__()
diff --git a/bin/tests/system/dnssec_py/ans4/noqname_mismatch.py b/bin/tests/system/dnssec_py/ans4/noqname_mismatch.py
index e5c53f0277..0c6c3ce9b1 100644
--- a/bin/tests/system/dnssec_py/ans4/noqname_mismatch.py
+++ b/bin/tests/system/dnssec_py/ans4/noqname_mismatch.py
@@ -36,9 +36,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
TTL = 300
ZONE = "f217.test."
@@ -236,12 +236,12 @@ def add_both_attack_answer(response: dns.message.Message) -> None:
response.authority.append(nsec3_rrsig(NSEC3_OWNER))
-class RuntimeCheckHandler(DomainHandler):
+class RuntimeCheckHandler(ResponseHandler):
"""
Serve f217.test. and the forged wildcard answers below evil.f217.test.
"""
- domains = [ZONE]
+ matcher = Domain(ZONE)
def __init__(self) -> None:
super().__init__()
diff --git a/bin/tests/system/dnssec_py/ans4/rrsig_labels_signer_ans.py b/bin/tests/system/dnssec_py/ans4/rrsig_labels_signer_ans.py
index ae04cd8833..f4bcdac833 100644
--- a/bin/tests/system/dnssec_py/ans4/rrsig_labels_signer_ans.py
+++ b/bin/tests/system/dnssec_py/ans4/rrsig_labels_signer_ans.py
@@ -53,9 +53,9 @@ import dns.rdatatype
import dns.rdtypes.ANY.NSEC3
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
ZONE_NAME = "attacker.rrsig-labels-signer."
PARENT_ZONE_NAME = "rrsig-labels-signer."
@@ -109,12 +109,12 @@ def _type_bitmap(*types: int) -> bytes:
return bytes(bm)
-class AttackerZoneHandler(DomainHandler):
+class AttackerZoneHandler(ResponseHandler):
"""
Serve attacker.rrsig-labels-signer. with crafted wildcard RRSIG.
"""
- domains = [ZONE_NAME]
+ matcher = Domain(ZONE_NAME)
def __init__(self) -> None:
super().__init__()
diff --git a/bin/tests/system/dnssec_py/ans4/sibling_ds_ans.py b/bin/tests/system/dnssec_py/ans4/sibling_ds_ans.py
index b1e1348ee0..d6ca574580 100644
--- a/bin/tests/system/dnssec_py/ans4/sibling_ds_ans.py
+++ b/bin/tests/system/dnssec_py/ans4/sibling_ds_ans.py
@@ -23,17 +23,17 @@ from collections.abc import AsyncGenerator
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
-class SiblingDsInjectionHandler(DomainHandler):
+class SiblingDsInjectionHandler(ResponseHandler):
"""
Inject a DS record for sibling.sibling-ds into child.sibling-ds referrals.
"""
- domains = ["child.sibling-ds."]
+ matcher = Domain("child.sibling-ds.")
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/forward/ans11/ans.py b/bin/tests/system/forward/ans11/ans.py
index dec74dac7e..5c1a8325ee 100644
--- a/bin/tests/system/forward/ans11/ans.py
+++ b/bin/tests/system/forward/ans11/ans.py
@@ -14,18 +14,22 @@ from collections.abc import AsyncGenerator
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import ControllableAsyncDnsServer, QueryContext
+from isctest.asyncserver import (
+ ControllableAsyncDnsServer,
+ QueryContext,
+ ResponseHandler,
+)
from isctest.asyncserver.actions import DnsResponseSend
from isctest.asyncserver.commands import ToggleResponsesCommand
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
-class ExtraAnswersHandler(DomainHandler):
+class ExtraAnswersHandler(ResponseHandler):
"""
Answer from zone data, inserting extra RRsets into responses to A queries.
"""
- domains = ["attackSecureDomain.net3."]
+ matcher = Domain("attackSecureDomain.net3.")
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/forward/ans6/ans.py b/bin/tests/system/forward/ans6/ans.py
index 89c08d0018..bb42e9b4f2 100644
--- a/bin/tests/system/forward/ans6/ans.py
+++ b/bin/tests/system/forward/ans6/ans.py
@@ -24,8 +24,8 @@ from isctest.asyncserver import (
)
from isctest.asyncserver.actions import DnsResponseSend
from isctest.asyncserver.commands import ToggleResponsesCommand
-from isctest.asyncserver.handlers import DomainHandler, StaticResponseHandler
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.handlers import StaticResponseHandler
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
SLD = "sld.tld."
NS1 = f"ns1.{SLD}"
@@ -65,7 +65,7 @@ class Ns1AaaaHandler(StaticResponseHandler):
edns = None
-class SldDelegationHandler(DomainHandler):
+class SldDelegationHandler(ResponseHandler):
"""
Delegate every NS query at or below sld.tld. to ns1.sld.tld., copying the
owner name from the QNAME. Together with Ns1AHandler / Ns1AaaaHandler
@@ -73,10 +73,7 @@ class SldDelegationHandler(DomainHandler):
negative catch-all), this drives named's DS-chasing logic.
"""
- domains = [SLD]
-
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qtype == dns.rdatatype.NS and super().match(qctx)
+ matcher = Domain(SLD) & Qtype(dns.rdatatype.NS)
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/fwdfirst/ans2/ans.py b/bin/tests/system/fwdfirst/ans2/ans.py
index 497cac9618..99324b758e 100644
--- a/bin/tests/system/fwdfirst/ans2/ans.py
+++ b/bin/tests/system/fwdfirst/ans2/ans.py
@@ -15,11 +15,12 @@ import dns.rdatatype
import dns.rrset
from isctest.asyncserver import AsyncDnsServer
-from isctest.asyncserver.handlers import DomainHandler, StaticResponseHandler
+from isctest.asyncserver.handlers import StaticResponseHandler
+from isctest.asyncserver.matchers import Domain
-class PoisonReferralForwarder(DomainHandler, StaticResponseHandler):
- domains = ["fwd.hack."]
+class PoisonReferralForwarder(StaticResponseHandler):
+ matcher = Domain("fwd.hack.")
authority = [
dns.rrset.from_text(
"hack.", 300, dns.rdataclass.IN, dns.rdatatype.NS, "ns.fwd.hack."
diff --git a/bin/tests/system/isctest/asyncserver/handlers.py b/bin/tests/system/isctest/asyncserver/handlers.py
index 9f6f18a0d3..87dfd88c0f 100644
--- a/bin/tests/system/isctest/asyncserver/handlers.py
+++ b/bin/tests/system/isctest/asyncserver/handlers.py
@@ -19,7 +19,6 @@ import logging
import dns.exception
import dns.message
-import dns.name
import dns.rcode
import dns.rdatatype
import dns.rrset
@@ -271,52 +270,6 @@ class StaticResponseHandler(ResponseHandler):
)
-class DomainHandler(ResponseHandler):
- """
- Base class used for deriving custom domain handlers.
-
- The derived class must specify a list of `domains` that it wants to handle.
- Queries for any of these domains (and their subdomains) will then be passed
- to the `get_response()` method in the derived class.
-
- The most specific matching domain is stored in the `matched_domain` attribute.
- """
-
- @property
- @abc.abstractmethod
- def domains(self) -> list[str]:
- """
- A list of domain names handled by this class.
- """
- raise NotImplementedError
-
- def __init__(self) -> None:
- self._domains: list[dns.name.Name] = sorted(
- [dns.name.from_text(d) for d in self.domains], reverse=True
- )
- self._matched_domain: dns.name.Name | None = None
-
- @property
- def matched_domain(self) -> dns.name.Name:
- assert self._matched_domain is not None
- return self._matched_domain
-
- def __str__(self) -> str:
- return f"{self.__class__.__name__}(domains: {', '.join(self.domains)})"
-
- def match(self, qctx: QueryContext) -> bool:
- """
- Handle queries whose QNAME matches any of the domains handled by this
- class.
- """
- self._matched_domain = None
- for domain in self._domains:
- if qctx.qname.is_subdomain(domain):
- self._matched_domain = domain
- return True
- return False
-
-
class ForwarderHandler(ResponseHandler):
"""
A handler forwarding all received queries to another DNS server with an
diff --git a/bin/tests/system/isctest/asyncserver/matchers.py b/bin/tests/system/isctest/asyncserver/matchers.py
index 6e9c597a69..bc56dc6255 100644
--- a/bin/tests/system/isctest/asyncserver/matchers.py
+++ b/bin/tests/system/isctest/asyncserver/matchers.py
@@ -170,3 +170,37 @@ class Qtype(Matcher):
def __str__(self) -> str:
return f"QTYPE in [{', '.join(map(dns.rdatatype.to_text, self.qtypes))}]"
+
+
+class Domain(Matcher):
+ """
+ Match queries for one of the given domains or any of their subdomains.
+
+ The most specific domain matched is kept in `matched_domain` for the
+ handler to read.
+ """
+
+ def __init__(self, *domains: str | dns.name.Name) -> None:
+ self._domains = sorted(
+ (
+ name if isinstance(name, dns.name.Name) else dns.name.from_text(name)
+ for name in domains
+ ),
+ reverse=True,
+ )
+ self._matched_domain: dns.name.Name | None = None
+
+ @property
+ def matched_domain(self) -> dns.name.Name:
+ assert self._matched_domain is not None, f"{self} has not matched yet"
+ return self._matched_domain
+
+ def match(self, qctx: QueryContext) -> bool:
+ for domain in self._domains:
+ if qctx.qname.is_subdomain(domain):
+ self._matched_domain = domain
+ return True
+ return False
+
+ def __str__(self) -> str:
+ return f"QNAME under [{', '.join(str(name) for name in self._domains)}]"
diff --git a/bin/tests/system/nsec3_impersonation/ans1/ans.py b/bin/tests/system/nsec3_impersonation/ans1/ans.py
index d0b42bd6bb..80e721bed9 100644
--- a/bin/tests/system/nsec3_impersonation/ans1/ans.py
+++ b/bin/tests/system/nsec3_impersonation/ans1/ans.py
@@ -29,7 +29,7 @@ import dns.rrset
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
TTL = 300
TLD = "tld.test."
@@ -208,8 +208,7 @@ class AttackerZoneHandler(SignedResponseHandler):
the apex are answered with an NXDOMAIN with no NSEC or NSEC3 present.
"""
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qname.is_subdomain(name(ATTACKER))
+ matcher = Domain(ATTACKER)
def respond(self, qctx: QueryContext) -> None:
if qctx.qname == name(ATTACKER):
@@ -239,8 +238,7 @@ class TldZoneHandler(SignedResponseHandler):
The attack assumes that the adversary can inject these responses on-path.
"""
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qname.is_subdomain(name(TLD))
+ matcher = Domain(TLD)
def respond(self, qctx: QueryContext) -> None:
if qctx.qname == name(TLD):
diff --git a/bin/tests/system/nsec_grandparent/ans1/ans.py b/bin/tests/system/nsec_grandparent/ans1/ans.py
index 712b8577d0..fe9968aa4d 100644
--- a/bin/tests/system/nsec_grandparent/ans1/ans.py
+++ b/bin/tests/system/nsec_grandparent/ans1/ans.py
@@ -29,6 +29,7 @@ import dns.rrset
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
+from isctest.asyncserver.matchers import Domain
TTL = 300
PARENT = "p031.test."
@@ -305,6 +306,7 @@ class GrandparentNsecHandler(ResponseHandler):
self.child_key = keys[CHILD]
self.sibling_key = keys[SIBLING]
self.parent = name(PARENT)
+ self.matcher = Domain(self.parent)
self.child = name(CHILD)
self.sibling = name(SIBLING)
self.grandchild = name(GRANDCHILD)
@@ -324,9 +326,6 @@ class GrandparentNsecHandler(ResponseHandler):
self.grandchild_almost_too_many,
)
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qname.is_subdomain(self.parent)
-
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
diff --git a/bin/tests/system/nsec_piggyback/ans1/ans.py b/bin/tests/system/nsec_piggyback/ans1/ans.py
index f13518c017..1e3437eef6 100644
--- a/bin/tests/system/nsec_piggyback/ans1/ans.py
+++ b/bin/tests/system/nsec_piggyback/ans1/ans.py
@@ -30,6 +30,7 @@ import dns.rrset
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
+from isctest.asyncserver.matchers import Domain
TTL = 300
PARENT = "p22.hack."
@@ -173,14 +174,12 @@ class ParentHandler(ResponseHandler):
def __init__(self, keys: dict[str, Key]) -> None:
self.keys = keys
self.parent = name(PARENT)
+ self.matcher = Domain(self.parent)
self.child = name(CHILD)
self.victim = name(VICTIM)
self.aac = name(AAC)
self.prime_nx = name(PRIME_NX)
- def match(self, qctx: QueryContext) -> bool:
- return qctx.qname.is_subdomain(self.parent)
-
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
diff --git a/bin/tests/system/nsec_synthesis/ans1/f004.py b/bin/tests/system/nsec_synthesis/ans1/f004.py
index 497c745721..648730d15d 100644
--- a/bin/tests/system/nsec_synthesis/ans1/f004.py
+++ b/bin/tests/system/nsec_synthesis/ans1/f004.py
@@ -21,9 +21,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
from nsec_synthesis.ans1.common import (
Key,
add_signed,
@@ -77,8 +77,8 @@ def garbage_rrsig(covered: dns.rrset.RRset, signer: Key) -> dns.rrset.RRset:
return dns.rrset.from_rdata(covered.name, covered.ttl, rdata)
-class F004Handler(DomainHandler):
- domains = [F004_ZONE]
+class F004Handler(ResponseHandler):
+ matcher = Domain(F004_ZONE)
def __init__(self, keys: dict[str, Key]) -> None:
super().__init__()
diff --git a/bin/tests/system/nsec_synthesis/ans1/f023.py b/bin/tests/system/nsec_synthesis/ans1/f023.py
index af66ac875c..fe833034af 100644
--- a/bin/tests/system/nsec_synthesis/ans1/f023.py
+++ b/bin/tests/system/nsec_synthesis/ans1/f023.py
@@ -21,9 +21,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
from nsec_synthesis.ans1.common import (
Key,
add_signed,
@@ -81,8 +81,8 @@ def add_parent_denial(
add_signed(response.authority, nsec, signer)
-class F023Handler(DomainHandler):
- domains = [F023_ZONE, EVIL]
+class F023Handler(ResponseHandler):
+ matcher = Domain(F023_ZONE, EVIL)
def __init__(self, keys: dict[str, Key]) -> None:
super().__init__()
diff --git a/bin/tests/system/nsec_wildcard_wrong_zone/ans1/ans.py b/bin/tests/system/nsec_wildcard_wrong_zone/ans1/ans.py
index b7dc752c2d..60aaeff5c0 100644
--- a/bin/tests/system/nsec_wildcard_wrong_zone/ans1/ans.py
+++ b/bin/tests/system/nsec_wildcard_wrong_zone/ans1/ans.py
@@ -26,9 +26,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
TTL = 300
VICTIM = "victim.example."
@@ -125,8 +125,8 @@ def add_wildcard_answer(response, key: Key) -> None:
response.answer.append(dns.rrset.from_rdata(name(CONTROL), TTL, rrsig))
-class NsecWildcardWrongZoneHandler(DomainHandler):
- domains = [VICTIM, ATTACKER]
+class NsecWildcardWrongZoneHandler(ResponseHandler):
+ matcher = Domain(VICTIM, ATTACKER)
def __init__(self, keys: dict[str, Key]) -> None:
super().__init__()
diff --git a/bin/tests/system/qmin/ans2/ans.py b/bin/tests/system/qmin/ans2/ans.py
index b1a4fb90f0..7c0268047c 100644
--- a/bin/tests/system/qmin/ans2/ans.py
+++ b/bin/tests/system/qmin/ans2/ans.py
@@ -16,9 +16,9 @@ import dns.rcode
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
from ..qmin_ans import (
DelayedResponseHandler,
@@ -29,21 +29,21 @@ from ..qmin_ans import (
class QueryLogger(QueryLogHandler):
- domains = ["1.0.0.2.ip6.arpa.", "fwd.", "good."]
+ matcher = Domain("1.0.0.2.ip6.arpa.", "fwd.", "good.")
class BadHandler(EntRcodeChanger):
- domains = ["bad."]
+ matcher = Domain("bad.")
rcode = dns.rcode.NXDOMAIN
class UglyHandler(EntRcodeChanger):
- domains = ["ugly."]
+ matcher = Domain("ugly.")
rcode = dns.rcode.FORMERR
class SlowHandler(DelayedResponseHandler):
- domains = ["slow."]
+ matcher = Domain("slow.")
delay = 0.2
@@ -67,7 +67,7 @@ def send_delegation(
return DnsResponseSend(response, authoritative=False)
-class StaleHandler(DomainHandler):
+class StaleHandler(ResponseHandler):
"""
`a.b.stale` is a subdomain of `b.stale` and these two subdomains need to be
delegated to different name servers. Therefore, their delegations cannot
@@ -76,7 +76,7 @@ class StaleHandler(DomainHandler):
on the QNAME.
"""
- domains = ["stale."]
+ matcher = Domain("stale.")
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/qmin/ans3/ans.py b/bin/tests/system/qmin/ans3/ans.py
index 4a86fbae72..7700b0d541 100644
--- a/bin/tests/system/qmin/ans3/ans.py
+++ b/bin/tests/system/qmin/ans3/ans.py
@@ -12,26 +12,27 @@
import dns.rcode
from isctest.asyncserver import AsyncDnsServer
+from isctest.asyncserver.matchers import Domain
from ..qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler
class QueryLogger(QueryLogHandler):
- domains = ["8.2.6.0.1.0.0.2.ip6.arpa.", "a.b.stale.", "zoop.boing.good."]
+ matcher = Domain("8.2.6.0.1.0.0.2.ip6.arpa.", "a.b.stale.", "zoop.boing.good.")
class ZoopBoingBadHandler(EntRcodeChanger):
- domains = ["zoop.boing.bad."]
+ matcher = Domain("zoop.boing.bad.")
rcode = dns.rcode.NXDOMAIN
class ZoopBoingUglyHandler(EntRcodeChanger):
- domains = ["zoop.boing.ugly."]
+ matcher = Domain("zoop.boing.ugly.")
rcode = dns.rcode.FORMERR
class ZoopBoingSlowHandler(DelayedResponseHandler):
- domains = ["zoop.boing.slow."]
+ matcher = Domain("zoop.boing.slow.")
delay = 0.4
diff --git a/bin/tests/system/qmin/ans4/ans.py b/bin/tests/system/qmin/ans4/ans.py
index 024e22c3fe..245241e96e 100644
--- a/bin/tests/system/qmin/ans4/ans.py
+++ b/bin/tests/system/qmin/ans4/ans.py
@@ -14,9 +14,9 @@ from collections.abc import AsyncGenerator
import dns.rcode
import dns.rdatatype
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
+from isctest.asyncserver.matchers import Domain
from ..qmin_ans import (
DelayedResponseHandler,
@@ -27,13 +27,10 @@ from ..qmin_ans import (
class QueryLogger(QueryLogHandler):
- domains = [
- "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
- "icky.ptang.zoop.boing.good.",
- ]
+ matcher = Domain("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", "icky.ptang.zoop.boing.good.")
-class StaleHandler(DomainHandler):
+class StaleHandler(ResponseHandler):
"""
The test code relies on this server returning non-minimal (i.e. including
address records in the ADDITIONAL section) responses to NS queries for
@@ -44,7 +41,7 @@ class StaleHandler(DomainHandler):
handler implemented below.
"""
- domains = ["b.stale", "a.b.stale"]
+ matcher = Domain("b.stale", "a.b.stale")
async def get_responses(
self, qctx: QueryContext
@@ -69,17 +66,17 @@ class StaleHandler(DomainHandler):
class IckyPtangZoopBoingBadHandler(EntRcodeChanger):
- domains = ["icky.ptang.zoop.boing.bad."]
+ matcher = Domain("icky.ptang.zoop.boing.bad.")
rcode = dns.rcode.NXDOMAIN
class IckyPtangZoopBoingUglyHandler(EntRcodeChanger):
- domains = ["icky.ptang.zoop.boing.ugly."]
+ matcher = Domain("icky.ptang.zoop.boing.ugly.")
rcode = dns.rcode.FORMERR
class IckyPtangZoopBoingSlowHandler(DelayedResponseHandler):
- domains = ["icky.ptang.zoop.boing.slow."]
+ matcher = Domain("icky.ptang.zoop.boing.slow.")
delay = 0.4
diff --git a/bin/tests/system/qmin/qmin_ans.py b/bin/tests/system/qmin/qmin_ans.py
index 285578e332..44c33a9bd8 100644
--- a/bin/tests/system/qmin/qmin_ans.py
+++ b/bin/tests/system/qmin/qmin_ans.py
@@ -16,9 +16,8 @@ import abc
import dns.rcode
import dns.rdatatype
-from isctest.asyncserver import QueryContext
+from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
def log_query(qctx: QueryContext) -> None:
@@ -36,7 +35,7 @@ def log_query(qctx: QueryContext) -> None:
print(f"{qtype} {qname}", file=query_log)
-class QueryLogHandler(DomainHandler):
+class QueryLogHandler(ResponseHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses.
@@ -49,7 +48,7 @@ class QueryLogHandler(DomainHandler):
yield DnsResponseSend(qctx.response)
-class EntRcodeChanger(DomainHandler):
+class EntRcodeChanger(ResponseHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses, but override the RCODE returned for empty
@@ -80,7 +79,7 @@ class EntRcodeChanger(DomainHandler):
yield DnsResponseSend(qctx.response)
-class DelayedResponseHandler(DomainHandler):
+class DelayedResponseHandler(ResponseHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses, but delay sending every answer by the amount of time
diff --git a/bin/tests/system/resend_loop/ans3/ans.py b/bin/tests/system/resend_loop/ans3/ans.py
index 6dc750c644..20b74b4d53 100644
--- a/bin/tests/system/resend_loop/ans3/ans.py
+++ b/bin/tests/system/resend_loop/ans3/ans.py
@@ -18,10 +18,10 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler, StaticResponseHandler
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.handlers import StaticResponseHandler
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
def rrset(
@@ -45,8 +45,8 @@ class ExampleNsHandler(StaticResponseHandler):
additional = [rrset("ns.example.", dns.rdatatype.A, "10.53.0.3")]
-class ExampleCookieHandler(DomainHandler):
- domains = ["example."]
+class ExampleCookieHandler(ResponseHandler):
+ matcher = Domain("example.")
def _get_cookie(self, qctx: QueryContext) -> dns.edns.CookieOption | None:
for o in qctx.query.options:
diff --git a/bin/tests/system/resolver/ans2/ans.py b/bin/tests/system/resolver/ans2/ans.py
index 7f8e446b3f..29dd9e8a4a 100644
--- a/bin/tests/system/resolver/ans2/ans.py
+++ b/bin/tests/system/resolver/ans2/ans.py
@@ -19,12 +19,8 @@ import dns.rrset
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import (
- DomainHandler,
- IgnoreAllQueries,
- StaticResponseHandler,
-)
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.handlers import IgnoreAllQueries, StaticResponseHandler
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
from ..resolver_ans import (
DelegationHandler,
@@ -165,8 +161,8 @@ class Ns7Delegation(DelegationHandler):
server_number = 7
-class PartialFormerrHandler(DomainHandler, StaticResponseHandler):
- domains = ["partial-formerr."]
+class PartialFormerrHandler(StaticResponseHandler):
+ matcher = Domain("partial-formerr.")
authoritative = False
rcode = dns.rcode.FORMERR
diff --git a/bin/tests/system/resolver/ans3/ans.py b/bin/tests/system/resolver/ans3/ans.py
index e53ac71796..38eb5ff14f 100644
--- a/bin/tests/system/resolver/ans3/ans.py
+++ b/bin/tests/system/resolver/ans3/ans.py
@@ -17,12 +17,8 @@ import dns.rdatatype
from isctest.asyncserver import AsyncDnsServer, QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import (
- DomainHandler,
- IgnoreAllQueries,
- StaticResponseHandler,
-)
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.handlers import IgnoreAllQueries, StaticResponseHandler
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
from ..resolver_ans import (
DelegationHandler,
@@ -174,8 +170,8 @@ class OkSubHandler(ResponseHandler):
yield DnsResponseSend(qctx.response)
-class PartialFormerrHandler(DomainHandler):
- domains = ["partial-formerr."]
+class PartialFormerrHandler(ResponseHandler):
+ matcher = Domain("partial-formerr.")
async def get_responses(
self, qctx: QueryContext
diff --git a/bin/tests/system/resolver/ans8/ans.py b/bin/tests/system/resolver/ans8/ans.py
index b5506aedca..00ddfcdf91 100644
--- a/bin/tests/system/resolver/ans8/ans.py
+++ b/bin/tests/system/resolver/ans8/ans.py
@@ -26,8 +26,8 @@ from isctest.asyncserver import (
ResponseHandler,
)
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler, StaticResponseHandler
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.handlers import StaticResponseHandler
+from isctest.asyncserver.matchers import Domain, Qname, Qtype
from ..resolver_ans import rrset
@@ -77,8 +77,8 @@ class TcpFallbackHandler(ResponseHandler):
yield DnsResponseSend(qctx.response)
-class FormerrToAllHandler(DomainHandler, StaticResponseHandler):
- domains = ["formerr-to-all."]
+class FormerrToAllHandler(StaticResponseHandler):
+ matcher = Domain("formerr-to-all.")
rcode = dns.rcode.FORMERR
diff --git a/bin/tests/system/resolver/resolver_ans.py b/bin/tests/system/resolver/resolver_ans.py
index cd1d3b1d6a..ca8a925de5 100644
--- a/bin/tests/system/resolver/resolver_ans.py
+++ b/bin/tests/system/resolver/resolver_ans.py
@@ -21,8 +21,7 @@ import dns.rrset
from isctest.asyncserver import QueryContext, ResponseHandler
from isctest.asyncserver.actions import DnsResponseSend
-from isctest.asyncserver.handlers import DomainHandler
-from isctest.asyncserver.matchers import Qname
+from isctest.asyncserver.matchers import Domain, Qname
def rrset(
@@ -72,16 +71,25 @@ def setup_delegation(
qctx.response.additional.append(delegation.a_rrset)
-class DelegationHandler(DomainHandler):
+class DelegationHandler(ResponseHandler):
+ @property
+ @abc.abstractmethod
+ def domains(self) -> list[str]:
+ raise NotImplementedError
+
@property
@abc.abstractmethod
def server_number(self) -> int:
raise NotImplementedError
+ def __init__(self) -> None:
+ self.matcher = Domain(*self.domains)
+
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
- setup_delegation(qctx, self.matched_domain, self.server_number)
+ domain = self.matcher.of(Domain).matched_domain
+ setup_delegation(qctx, domain, self.server_number)
yield DnsResponseSend(qctx.response, authoritative=False)