version of the zone where a.example no longer exists, and send a CD=1 query
that makes the resolver cache an unvalidated NXDOMAIN for a.example. The
originally cached, more-trusted A record must survive.
+
+The last test covers the inverse: once the validated RRset has passed its
+TTL it must no longer block the negative entry, nor be served to the
+client in its place.
"""
import os
import shutil
+import time
import dns.zone
RESOLVER = "10.53.0.1"
AUTH = "10.53.0.2"
A_RDATA = "10.53.0.99"
+SHORT_TTL = 3
def bootstrap():
f"-P -x -O full -o {name} -f {name}-empty.db.signed {name}-empty.db", cwd="ns2"
)
- zonetext += "a A 10.53.0.99"
+ zonetext += f"a A 10.53.0.99\nshort {SHORT_TTL} A 10.53.0.99\n"
zone = dns.zone.from_text(zonetext, origin=name)
zone.to_file(f"ns2/{name}-full.db", sorted=True)
signer(
isctest.check.nxdomain(res)
isctest.check.adflag(res)
isctest.check.empty_answer(res)
+
+
+def test_cd1_nxdomain_expired_secure_does_not_block(ns1, ns2, system_test_dir):
+ # Prime the resolver with the short-TTL validated short.example/A.
+ _serve(ns2, system_test_dir, "full")
+ ns1.rndc("flush")
+ res = isctest.query.tcp(isctest.query.create("short.example", "A"), RESOLVER)
+ isctest.check.noerror(res)
+ isctest.check.adflag(res)
+ assert A_RDATA in str(res.answer), res.answer
+
+ # Flip the zone to the version without the name and wait out the TTL.
+ # Expired headers are only cleaned lazily, so the secure A record is
+ # still sitting in the cache when the negative entry arrives.
+ _serve(ns2, system_test_dir, "empty")
+ time.sleep(SHORT_TTL + 2)
+ direct = isctest.query.create("short.example", "A", dnssec=False)
+ isctest.check.nxdomain(isctest.query.tcp(direct, AUTH))
+
+ # The expired secure A record must neither block the unvalidated
+ # NXDOMAIN from being cached nor be handed back to the client in its
+ # place.
+ res = isctest.query.tcp(
+ isctest.query.create("short.example", "A", cd=True), RESOLVER
+ )
+ isctest.check.nxdomain(res)
+ isctest.check.empty_answer(res)