fix: usr: Fix CNAME resolution failure caused by a cached SERVFAIL response

Under certain circumstances, a cached SERVFAIL response could incorrectly prevent successful resolution of a CNAME target. This could cause resolution failures to persist until the cached SERVFAIL entry expired, even when the CNAME target itself was otherwise resolvable. This issue has been fixed.

Closes #5983

Merge branch '5983-servfail-cache-cname' into 'main'

See merge request isc-projects/bind9!12158
This commit is contained in:
Colin Vidal 2026-06-17 11:24:32 +02:00
commit e0e1208dbb
5 changed files with 82 additions and 2 deletions

View file

@ -0,0 +1,16 @@
options {
query-source address @ns.ip@;
notify-source @ns.ip@;
transfer-source @ns.ip@;
port @PORT@;
pid-file "named.pid";
listen-on { @ns.ip@; };
listen-on-v6 { none; };
recursion no;
dnssec-validation no;
};
zone "." {
type primary;
file "root.db";
};

View file

@ -0,0 +1,6 @@
$TTL 300
. IN SOA ns. hostmaster. 1 600 600 1200 600
. NS ns
ns A 10.53.0.1
foo.tld1. CNAME tld2.
tld2. A 1.2.3.4

View file

@ -0,0 +1,22 @@
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;
};
zone "tld1." {
type static-stub;
server-addresses { 10.53.0.1; };
};
zone "tld2." {
type static-stub;
server-addresses { 10.53.0.1; };
};

View file

@ -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.
#
# ns1 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)

View file

@ -1030,6 +1030,9 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
isc_time_t expire;
isc_interval_t i;
uint32_t flags = 0;
dns_name_t *qname = client->query.origqname != NULL
? client->query.origqname
: client->query.qname;
if ((message->flags & DNS_MESSAGEFLAG_CD) != 0) {
flags = NS_FAILCACHE_CD;
@ -1038,8 +1041,7 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
isc_interval_set(&i, client->inner.view->fail_ttl, 0);
result = isc_time_nowplusinterval(&expire, &i);
if (result == ISC_R_SUCCESS) {
dns_badcache_add(client->inner.view->failcache,
client->query.qname,
dns_badcache_add(client->inner.view->failcache, qname,
client->query.qtype, flags,
isc_time_seconds(&expire));
}