]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
System test covering SERVFAIL cache and CNAME
authorColin Vidal <colin@isc.org>
Mon, 1 Jun 2026 15:38:18 +0000 (17:38 +0200)
committerOndřej Surý <ondrej@sury.org>
Wed, 17 Jun 2026 04:42:16 +0000 (06:42 +0200)
Add a system test for the case where resolution SERVFAILs because the
fetch context reaches the `max-query-count` threshold while following a
CNAME.

Resolving the CNAME target independently should still work, because the
SERVFAIL cache stores the original query name rather than the target.

bin/tests/system/sfcache_cname/ans1/ans.py [new file with mode: 0644]
bin/tests/system/sfcache_cname/ns2/named.conf.j2 [new file with mode: 0644]
bin/tests/system/sfcache_cname/tests_sfcache_cname.py [new file with mode: 0644]

diff --git a/bin/tests/system/sfcache_cname/ans1/ans.py b/bin/tests/system/sfcache_cname/ans1/ans.py
new file mode 100644 (file)
index 0000000..049a2d2
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+
+import dns.name
+import dns.rcode
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import AsyncDnsServer, QnameQtypeHandler, StaticResponseHandler
+
+
+def rrset(
+    qname: dns.name.Name | str,
+    rtype: dns.rdatatype.RdataType,
+    rdata: str,
+    ttl: int = 300,
+) -> dns.rrset.RRset:
+    return dns.rrset.from_text(qname, ttl, dns.rdataclass.IN, rtype, rdata)
+
+
+class Tld1Handler(QnameQtypeHandler, StaticResponseHandler):
+    qnames = ["foo.tld1."]
+    qtypes = [dns.rdatatype.A]
+    answer = [rrset("foo.tld1.", dns.rdatatype.CNAME, "tld2.")]
+
+
+class Tld2Handler(QnameQtypeHandler, StaticResponseHandler):
+    qnames = ["tld2."]
+    qtypes = [dns.rdatatype.A]
+    answer = [rrset("tld2.", dns.rdatatype.A, "1.2.3.4")]
+
+
+def main() -> None:
+    server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
+    server.install_response_handlers(
+        Tld1Handler(),
+        Tld2Handler(),
+    )
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/sfcache_cname/ns2/named.conf.j2 b/bin/tests/system/sfcache_cname/ns2/named.conf.j2
new file mode 100644 (file)
index 0000000..c501ad6
--- /dev/null
@@ -0,0 +1,26 @@
+options {
+       query-source address @ns.ip@;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { @ns.ip@; };
+       listen-on-v6 { none; };
+       recursion yes;
+       dnssec-validation no;
+       qname-minimization off;
+       servfail-ttl 5;
+       max-query-count 2;
+};
+
+{% include "_common/controls.conf.j2" %}
+
+zone "tld1." {
+       type forward;
+       forward only;
+       forwarders { 10.53.0.1; };
+};
+
+zone "tld2." {
+       type forward;
+       forward only;
+       forwarders { 10.53.0.1; };
+};
diff --git a/bin/tests/system/sfcache_cname/tests_sfcache_cname.py b/bin/tests/system/sfcache_cname/tests_sfcache_cname.py
new file mode 100644 (file)
index 0000000..df838dd
--- /dev/null
@@ -0,0 +1,34 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+
+import dns.message
+
+import isctest
+
+
+# A SERVFAIL produced while following a CNAME must be cached against the
+# original query name, not the CNAME target.
+#
+# ans1 serves "foo.tld1 CNAME tld2" and "tld2 A 1.2.3.4"; ns2 forwards
+# both zones to it with "max-query-count 2". Resolving "foo.tld1/A"
+# follows the CNAME to "tld2" and then exhausts the query budget, so the
+# client gets SERVFAIL. That failure must be cached under the original
+# name ("foo.tld1"), so a subsequent direct query for the CNAME target
+# ("tld2") is not blocked by the SERVFAIL cache and resolves normally.
+def test_sfcache_cname(ns2):
+    msg = dns.message.make_query("foo.tld1.", "A")
+    res = isctest.query.udp(msg, ns2.ip)
+    isctest.check.servfail(res)
+
+    msg = dns.message.make_query("tld2.", "A")
+    res = isctest.query.udp(msg, ns2.ip)
+    isctest.check.noerror(res)