From: Ondřej Surý Date: Mon, 20 Jul 2026 05:52:03 +0000 (+0200) Subject: Test that an expired RRset does not block negative caching X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=70a2ba16b18f4469a842e708be377f8939f8eb5d;p=thirdparty%2Fbind9.git Test that an expired RRset does not block negative caching Cover the inverse of the CD=1 NXDOMAIN guard: a validated RRset that has passed its TTL, but has not yet been cleaned from the cache, must not block the unvalidated negative entry. Before the fix the CD=1 query failed with SERVFAIL until the expired header was reaped. Assisted-by: Claude:claude-fable-5 --- diff --git a/bin/tests/system/cdnxdomain/tests_cdnxdomain.py b/bin/tests/system/cdnxdomain/tests_cdnxdomain.py index 6448dd7b515..bd53e9841d1 100644 --- a/bin/tests/system/cdnxdomain/tests_cdnxdomain.py +++ b/bin/tests/system/cdnxdomain/tests_cdnxdomain.py @@ -19,10 +19,15 @@ prime the cache with a validated a.example/A (trust=secure), flip ns2 to a 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 @@ -31,6 +36,7 @@ import isctest RESOLVER = "10.53.0.1" AUTH = "10.53.0.2" A_RDATA = "10.53.0.99" +SHORT_TTL = 3 def bootstrap(): @@ -57,7 +63,7 @@ ns2 A 10.53.0.2 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( @@ -151,3 +157,30 @@ def test_cd1_nxdomain_uncached_type_answer(ns1, ns2, system_test_dir): 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)