Commit 793a4fc65e for bind
commit 793a4fc65ea1889236a9d5dbf5f3adfd41889c95
Author: Štěpán Balážik <stepan@isc.org>
Date: Tue Sep 8 19:36:10 2026 +0200
Match only the first matching query with a matcher
Once declares that a handler answers only the first query its matcher
matches, which filters/ans6 previously did with a match() method that
spent itself on the first query evaluated, whatever it was.
Assisted-by: Claude:claude-fable-5
diff --git a/bin/tests/system/filters/ans6/ans.py b/bin/tests/system/filters/ans6/ans.py
index ef77810bfe..5a682f36d6 100644
--- a/bin/tests/system/filters/ans6/ans.py
+++ b/bin/tests/system/filters/ans6/ans.py
@@ -14,9 +14,9 @@ import dns.rdataclass
import dns.rdatatype
import dns.rrset
-from isctest.asyncserver import AsyncDnsServer, QueryContext
+from isctest.asyncserver import AsyncDnsServer
from isctest.asyncserver.handlers import StaticResponseHandler
-from isctest.asyncserver.matchers import Qname, Qtype
+from isctest.asyncserver.matchers import Once, Qname, Qtype
DNS64_TRIGGER = "nodata.test."
@@ -44,19 +44,9 @@ class NodataOnceHandler(StaticResponseHandler):
AAAA (answered by AaaaHandler), triggering the bug.
"""
- matcher = Qname(DNS64_TRIGGER) & Qtype(dns.rdatatype.AAAA)
+ matcher = Once(Qname(DNS64_TRIGGER) & Qtype(dns.rdatatype.AAAA))
authority = [soa()]
- def __init__(self) -> None:
- super().__init__()
- self._answered = False
-
- def match(self, qctx: QueryContext) -> bool:
- if self._answered:
- return False
- self._answered = True
- return super().match(qctx)
-
class AaaaHandler(StaticResponseHandler):
matcher = Qname(DNS64_TRIGGER) & Qtype(dns.rdatatype.AAAA)
diff --git a/bin/tests/system/isctest/asyncserver/matchers.py b/bin/tests/system/isctest/asyncserver/matchers.py
index dcdb37dba3..cc577331c9 100644
--- a/bin/tests/system/isctest/asyncserver/matchers.py
+++ b/bin/tests/system/isctest/asyncserver/matchers.py
@@ -309,3 +309,22 @@ class LabelCount(Matcher):
def __str__(self) -> str:
return f"QNAME of {self._count} labels"
+
+
+class Once(Matcher):
+ """
+ Match the first query the given matcher matches, and only that one.
+ """
+
+ def __init__(self, matcher: Matcher) -> None:
+ self._matcher = matcher
+ self._done = False
+
+ def match(self, qctx: QueryContext) -> bool:
+ if self._done or not self._matcher.match(qctx):
+ return False
+ self._done = True
+ return True
+
+ def __str__(self) -> str:
+ return f"once, {self._matcher}"