--- /dev/null
+# 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()
--- /dev/null
+options {
+ query-source address 10.53.0.2;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ qname-minimization off;
+ servfail-ttl 5;
+ max-query-count 2;
+};
+
+zone "tld1." {
+ type forward;
+ forward only;
+ forwarders { 10.53.0.1; };
+};
+
+zone "tld2." {
+ type forward;
+ forward only;
+ forwarders { 10.53.0.1; };
+};
--- /dev/null
+# 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
+
+
+# This test verifies the query pattern when the upstream behaves badly.
+# In this scenario, the upstream server (ans3) always responds with a
+# BADCOOKIE error for queries within the "example" zone, even on TCP.
+# The resolver (ns4), should not resend the same queries over and over
+# again, up to the max-query-count threshold. Instead, the expected
+# pattern is:
+# 1. Priming query, getting the NS for .
+# 2. Getting the NS for example.
+# 3. Trying to resolve test.example.
+# 4. Trying again, but now with the server cookie.
+# 5. Trying again, now over TCP.
+#
+# This means we expect 5 recursion queries trying to resolve test.example.
+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)