diff --git a/bind-9.20-stale-cname-tests.patch b/bind-9.20-stale-cname-tests.patch new file mode 100644 index 0000000..6280b77 --- /dev/null +++ b/bind-9.20-stale-cname-tests.patch @@ -0,0 +1,94 @@ +diff --git a/bin/tests/system/serve-stale/ns1/stale.test.db b/bin/tests/system/serve-stale/ns1/stale.test.db +index d389e7c6a6..128fb25a10 100644 +--- a/bin/tests/system/serve-stale/ns1/stale.test.db ++++ b/bin/tests/system/serve-stale/ns1/stale.test.db +@@ -17,3 +17,11 @@ cname1.stale.test. 1 CNAME a1.stale.test. + a1.stale.test. 1 A 192.0.2.1 + cname2.stale.test. 1 CNAME a2.stale.test. + a2.stale.test. 300 A 192.0.2.2 ++ ++cname-a1 1 CNAME cname-a2 ++cname-a2 300 CNAME cname-a3 ++cname-a3 300 A 192.0.2.1 ++ ++cname-b1 300 CNAME cname-b2 ++cname-b2 1 CNAME cname-b3 ++cname-b3 1 A 192.0.2.2 +diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh +index 96cd26505f..a30896b6a4 100755 +--- a/bin/tests/system/serve-stale/tests.sh ++++ b/bin/tests/system/serve-stale/tests.sh +@@ -2256,6 +2256,73 @@ if [ $ret != 0 ]; then + fi + status=$((status + ret)) + ++# New CNAME scenario (GL #5243) ++n=$((n + 1)) ++echo_i "prime cache cname-a1.stale.test A (stale-answer-client-timeout 0) ($n)" ++ret=0 ++$DIG -p ${PORT} @10.53.0.3 cname-a1.stale.test A >dig.out.test$n || ret=1 ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-a1\.stale\.test\..*1.*IN.*CNAME.*cname-a2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a2\.stale\.test\..*300.*IN.*CNAME.*cname-a3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a3\.stale\.test\..*300.*IN.*A.*192\.0\.2\.1" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++n=$((n + 1)) ++echo_i "prime cache cname-b1.stale.test A (stale-answer-client-timeout 0) ($n)" ++ret=0 ++$DIG -p ${PORT} @10.53.0.3 cname-b1.stale.test A >dig.out.test$n || ret=1 ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-b1\.stale\.test\..*300.*IN.*CNAME.*cname-b2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b2\.stale\.test\..*1.*IN.*CNAME.*cname-b3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b3\.stale\.test\..*1.*IN.*A.*192\.0\.2\.2" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++# Allow RRset to become stale. ++sleep 1 ++ ++n=$((n + 1)) ++ret=0 ++echo_i "check stale cname-a1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" ++nextpart ns3/named.run >/dev/null ++$DIG -p ${PORT} @10.53.0.3 cname-a1.stale.test A >dig.out.test$n || ret=1 ++wait_for_log 5 "cname-a1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++# Other records in chain are still good, so do not attempt a refresh ++grep "cname-a2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++grep "cname-a3.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++# Check answer ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-a1\.stale\.test\..*3.*IN.*CNAME.*cname-a2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a2\.stale\.test\..*29[0-9].*IN.*CNAME.*cname-a3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a3\.stale\.test\..*29[0-9].*IN.*A.*192\.0\.2\.1" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++n=$((n + 1)) ++ret=0 ++echo_i "check stale cname-b1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" ++nextpart ns3/named.run >/dev/null ++$DIG -p ${PORT} @10.53.0.3 cname-b1.stale.test A >dig.out.test$n || ret=1 ++wait_for_log 5 "cname-b2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++# The next one in the chain (cname-b3.stale.test) is likely not logged because ++# there is already a refresh in progress. And the first record in the chain is ++# still good, so do not attempt a refresh. ++grep "cname-b1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++# Check answer ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-b1\.stale\.test\..*29[0-9].*IN.*CNAME.*cname-b2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b2\.stale\.test\..*3.*IN.*CNAME.*cname-b3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b3\.stale\.test\..*3.*IN.*A.*192\.0\.2\.2" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ + #################################################################### + # Test for stale-answer-client-timeout 0 and stale-refresh-time 4. # + #################################################################### diff --git a/bind-9.20-stale-cname.patch b/bind-9.20-stale-cname.patch new file mode 100644 index 0000000..3796380 --- /dev/null +++ b/bind-9.20-stale-cname.patch @@ -0,0 +1,709 @@ +diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh +index c001e7a071..96cd26505f 100755 +--- a/bin/tests/system/serve-stale/tests.sh ++++ b/bin/tests/system/serve-stale/tests.sh +@@ -2053,7 +2053,7 @@ ret=0 + echo_i "check stale nodata.example TXT comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 nodata.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "nodata.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "nodata.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 0," dig.out.test$n >/dev/null || ret=1 +@@ -2066,7 +2066,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2196,7 +2196,7 @@ ret=0 + echo_i "check stale cname1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 cname1.stale.test A >dig.out.test$n || ret=1 +-wait_for_log 5 "cname1.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "cname1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +@@ -2235,7 +2235,7 @@ ret=0 + echo_i "check stale cname2.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 cname2.stale.test A >dig.out.test$n || ret=1 +-wait_for_log 5 "cname2.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "cname2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +@@ -2312,7 +2312,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2356,7 +2356,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2368,7 +2368,7 @@ status=$((status + ret)) + n=$((n + 1)) + ret=0 + echo_i "wait until resolver query times out, activating stale-refresh-time" +-wait_for_log 15 "data.example resolver failure, stale answer used" ns3/named.run || ret=1 ++wait_for_log 15 "data.example/TXT stale refresh failed: timed out" ns3/named.run || ret=1 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + +@@ -2419,7 +2419,7 @@ n=$((n + 1)) + ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h +index 3a85226836..b04e65afc7 100644 +--- a/lib/dns/include/dns/rdataset.h ++++ b/lib/dns/include/dns/rdataset.h +@@ -201,7 +201,7 @@ struct dns_rdataset { + #define DNS_RDATASETATTR_STALE 0x01000000 + #define DNS_RDATASETATTR_ANCIENT 0x02000000 + #define DNS_RDATASETATTR_STALE_WINDOW 0x04000000 +-#define DNS_RDATASETATTR_STALE_ADDED 0x08000000 ++#define DNS_RDATASETATTR_KEEPCASE 0x10000000 + #define DNS_RDATASETATTR_STATICSTUB 0x20000000 + + /*% +diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h +index ea2d83e079..db17f01d79 100644 +--- a/lib/ns/include/ns/client.h ++++ b/lib/ns/include/ns/client.h +@@ -184,6 +184,7 @@ struct ns_client { + (query, update, notify) */ + isc_nmhandle_t *fetchhandle; /* Waiting for recursive fetch */ + isc_nmhandle_t *prefetchhandle; /* Waiting for prefetch / rpzfetch */ ++ isc_nmhandle_t *stalerefreshhandle; + isc_nmhandle_t *updatehandle; /* Waiting for update callback */ + unsigned char *tcpbuf; + size_t tcpbuf_size; +diff --git a/lib/ns/include/ns/query.h b/lib/ns/include/ns/query.h +index c090754526..10a5931dca 100644 +--- a/lib/ns/include/ns/query.h ++++ b/lib/ns/include/ns/query.h +@@ -69,6 +69,7 @@ struct ns_query { + isc_mutex_t fetchlock; + dns_fetch_t *fetch; + dns_fetch_t *prefetch; ++ dns_fetch_t *stalerefresh; + ns_hookasync_t *hookactx; + dns_rpz_st_t *rpz_st; + isc_bufferlist_t namebufs; +@@ -120,7 +121,6 @@ struct ns_query { + #define NS_QUERYATTR_RRL_CHECKED 0x010000 + #define NS_QUERYATTR_REDIRECT 0x020000 + #define NS_QUERYATTR_ANSWERED 0x040000 +-#define NS_QUERYATTR_STALEOK 0x080000 + #define NS_QUERYATTR_STALEPENDING 0x100000 + + typedef struct query_ctx query_ctx_t; +@@ -149,7 +149,6 @@ struct query_ctx { + bool authoritative; /* authoritative query? */ + bool want_restart; /* CNAME chain or other + * restart needed */ +- bool refresh_rrset; /* stale RRset refresh needed */ + bool need_wildcardproof; /* wildcard proof needed */ + bool nxrewrite; /* negative answer from RPZ */ + bool findcoveringnsec; /* lookup covering NSEC */ +diff --git a/lib/ns/query.c b/lib/ns/query.c +index 11d2520c61..114cb0bf6b 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -140,9 +140,6 @@ + #define QUERY_STALEPENDING(q) \ + (((q)->attributes & NS_QUERYATTR_STALEPENDING) != 0) + +-/*% Does the query allow stale data in the response? */ +-#define QUERY_STALEOK(q) (((q)->attributes & NS_QUERYATTR_STALEOK) != 0) +- + /*% Does the query wants to check for stale RRset due to a timeout? */ + #define QUERY_STALETIMEOUT(q) (((q)->dboptions & DNS_DBFIND_STALETIMEOUT) != 0) + +@@ -249,6 +246,15 @@ query_addanswer(query_ctx_t *qctx); + static isc_result_t + query_prepare_delegation_response(query_ctx_t *qctx); + ++static isc_result_t ++qctx_prepare_buffers(query_ctx_t *qctx, isc_buffer_t *buffer); ++ ++static void ++qctx_freedata(query_ctx_t *qctx); ++ ++static void ++qctx_destroy(query_ctx_t *qctx); ++ + /* + * Return the hooktable in use with 'qctx', or if there isn't one + * set, return the default hooktable. +@@ -504,9 +510,6 @@ query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata); + static void + query_addauth(query_ctx_t *qctx); + +-static void +-query_clear_stale(ns_client_t *client); +- + /* + * Increment query statistics counters. + */ +@@ -815,6 +818,7 @@ ns_query_init(ns_client_t *client) { + + client->query.fetch = NULL; + client->query.prefetch = NULL; ++ client->query.stalerefresh = NULL; + client->query.authdb = NULL; + client->query.authzone = NULL; + client->query.authdbset = false; +@@ -2247,10 +2251,6 @@ query_addrrset(query_ctx_t *qctx, dns_name_t **namep, + if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) { + mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + } +- if ((rdataset->attributes & DNS_RDATASETATTR_STALE_ADDED) != 0) +- { +- mrdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; +- } + return; + } else if (result == DNS_R_NXDOMAIN) { + /* +@@ -2538,6 +2538,88 @@ free_devent(ns_client_t *client, isc_event_t **eventp, + isc_event_free(eventp); + } + ++static void ++stale_refresh_aftermath(ns_client_t *client, isc_result_t result) { ++ dns_db_t *db = NULL; ++ unsigned int dboptions; ++ isc_buffer_t buffer; ++ query_ctx_t qctx; ++ dns_clientinfomethods_t cm; ++ dns_clientinfo_t ci; ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ char typebuf[DNS_RDATATYPE_FORMATSIZE]; ++ ++ /* ++ * If refreshing a stale RRset failed, we need to set the ++ * stale-refresh-time window, so that on future requests for this ++ * RRset the stale entry may be used immediately. ++ */ ++ switch (result) { ++ case ISC_R_SUCCESS: ++ case DNS_R_GLUE: ++ case DNS_R_ZONECUT: ++ case ISC_R_NOTFOUND: ++ case DNS_R_DELEGATION: ++ case DNS_R_EMPTYNAME: ++ case DNS_R_NXRRSET: ++ case DNS_R_EMPTYWILD: ++ case DNS_R_NXDOMAIN: ++ case DNS_R_COVERINGNSEC: ++ case DNS_R_NCACHENXDOMAIN: ++ case DNS_R_NCACHENXRRSET: ++ case DNS_R_CNAME: ++ case DNS_R_DNAME: ++ break; ++ default: ++ dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); ++ dns_rdatatype_format(client->query.qtype, typebuf, ++ sizeof(typebuf)); ++ ns_client_log(client, NS_LOGCATEGORY_SERVE_STALE, ++ NS_LOGMODULE_QUERY, ISC_LOG_NOTICE, ++ "%s/%s stale refresh failed: timed out", namebuf, ++ typebuf); ++ ++ /* ++ * Set up a short lived query context, solely to set the ++ * last refresh failure time on the RRset in the cache ++ * database, starting the stale-refresh-time window for it. ++ * This is a condensed form of query_lookup(). ++ */ ++ isc_stdtime_get(&client->now); ++ client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; ++ qctx_init(client, NULL, 0, &qctx); ++ ++ dns_clientinfomethods_init(&cm, ns_client_sourceip); ++ dns_clientinfo_init(&ci, qctx.client, NULL); ++ if (HAVEECS(qctx.client)) { ++ dns_clientinfo_setecs(&ci, &qctx.client->ecs); ++ } ++ ++ result = qctx_prepare_buffers(&qctx, &buffer); ++ if (result != ISC_R_SUCCESS) { ++ goto cleanup; ++ } ++ ++ dboptions = qctx.client->query.dboptions; ++ dboptions |= DNS_DBFIND_STALEOK; ++ dboptions |= DNS_DBFIND_STALESTART; ++ ++ dns_db_attach(qctx.client->view->cachedb, &db); ++ (void)dns_db_findext(db, qctx.client->query.qname, NULL, ++ qctx.client->query.qtype, dboptions, ++ qctx.client->now, &qctx.node, qctx.fname, ++ &cm, &ci, qctx.rdataset, qctx.sigrdataset); ++ if (qctx.node != NULL) { ++ dns_db_detachnode(db, &qctx.node); ++ } ++ dns_db_detach(&db); ++ ++ cleanup: ++ qctx_freedata(&qctx); ++ qctx_destroy(&qctx); ++ } ++} ++ + static void + prefetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; +@@ -2572,6 +2654,133 @@ prefetch_done(isc_task_t *task, isc_event_t *event) { + isc_nmhandle_detach(&client->prefetchhandle); + } + ++static void ++refresh_done(isc_task_t *task, isc_event_t *event) { ++ dns_fetchevent_t *devent = (dns_fetchevent_t *)event; ++ ns_client_t *client; ++ ++ UNUSED(task); ++ ++ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); ++ client = devent->ev_arg; ++ REQUIRE(NS_CLIENT_VALID(client)); ++ REQUIRE(task == client->task); ++ ++ CTRACE(ISC_LOG_DEBUG(3), "refresh_done"); ++ ++ LOCK(&client->query.fetchlock); ++ if (client->query.stalerefresh != NULL) { ++ INSIST(devent->fetch == client->query.stalerefresh); ++ client->query.stalerefresh = NULL; ++ } ++ UNLOCK(&client->query.fetchlock); ++ ++ stale_refresh_aftermath(client, devent->result); ++ ++ if (client->recursionquota != NULL) { ++ isc_quota_detach(&client->recursionquota); ++ ns_stats_decrement(client->sctx->nsstats, ++ ns_statscounter_recursclients); ++ } ++ ++ free_devent(client, &event, &devent); ++ isc_nmhandle_detach(&client->stalerefreshhandle); ++} ++ ++static void ++query_stale_refresh(ns_client_t *client, dns_name_t *qname, ++ dns_rdataset_t *rdataset) { ++ isc_result_t result; ++ isc_sockaddr_t *peeraddr; ++ dns_rdataset_t *tmprdataset; ++ unsigned int options; ++ ++ CTRACE(ISC_LOG_DEBUG(3), "query_stale_refresh"); ++ ++ bool stale_refresh_window = false; ++ bool stale_rrset = true; ++ ++ if (rdataset != NULL) { ++ stale_refresh_window = ++ (STALE_WINDOW(rdataset) && ++ (client->query.dboptions & DNS_DBFIND_STALEENABLED) != 0); ++ stale_rrset = STALE(rdataset); ++ } ++ ++ if (client->query.stalerefresh != NULL || ++ (client->query.dboptions & DNS_DBFIND_STALETIMEOUT) == 0 || ++ !stale_rrset || stale_refresh_window) ++ { ++ return; ++ } ++ ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ char typebuf[DNS_RDATATYPE_FORMATSIZE]; ++ dns_name_format(qname, namebuf, sizeof(namebuf)); ++ dns_rdatatype_format(client->query.qtype, typebuf, sizeof(typebuf)); ++ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ++ ISC_LOG_INFO, ++ "%s %s stale answer used, an attempt " ++ "to refresh the RRset will still be " ++ "made", ++ namebuf, typebuf); ++ ++ client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | ++ DNS_DBFIND_STALEOK | ++ DNS_DBFIND_STALEENABLED); ++ ++ if (client->recursionquota == NULL) { ++ result = isc_quota_attach(&client->sctx->recursionquota, ++ &client->recursionquota); ++ switch (result) { ++ case ISC_R_SUCCESS: ++ ns_stats_increment(client->sctx->nsstats, ++ ns_statscounter_recursclients); ++ break; ++ case ISC_R_SOFTQUOTA: ++ isc_quota_detach(&client->recursionquota); ++ FALLTHROUGH; ++ default: ++ return; ++ } ++ } ++ ++ tmprdataset = ns_client_newrdataset(client); ++ if (tmprdataset == NULL) { ++ return; ++ } ++ ++ if (!TCP(client)) { ++ peeraddr = &client->peeraddr; ++ } else { ++ peeraddr = NULL; ++ } ++ ++ isc_nmhandle_attach(client->handle, &client->stalerefreshhandle); ++ options = client->query.fetchoptions; ++ result = dns_resolver_createfetch( ++ client->view->resolver, qname, client->query.qtype, NULL, NULL, NULL, ++ peeraddr, client->message->id, options, 0, NULL, NULL, ++ client->task, refresh_done, client, tmprdataset, NULL, ++ &client->query.stalerefresh); ++ if (result != ISC_R_SUCCESS) { ++ ns_client_putrdataset(client, &tmprdataset); ++ isc_nmhandle_detach(&client->stalerefreshhandle); ++ } ++} ++ ++static void ++query_stale_refresh_ncache(ns_client_t *client) { ++ dns_name_t *qname; ++ ++ if (client->query.origqname != NULL) { ++ qname = client->query.origqname; ++ } else { ++ qname = client->query.qname; ++ } ++ query_stale_refresh(client, qname, NULL); ++} ++ + static void + query_prefetch(ns_client_t *client, dns_name_t *qname, + dns_rdataset_t *rdataset) { +@@ -2587,6 +2796,7 @@ query_prefetch(ns_client_t *client, dns_name_t *qname, + rdataset->ttl > client->view->prefetch_trigger || + (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0) + { ++ query_stale_refresh(client, qname, rdataset); + return; + } + +@@ -2638,6 +2848,7 @@ query_prefetch(ns_client_t *client, dns_name_t *qname, + + dns_rdataset_clearprefetch(rdataset); + ns_stats_increment(client->sctx->nsstats, ns_statscounter_prefetch); ++ return; + } + + static void +@@ -5824,55 +6035,6 @@ error: + return ISC_R_NOMEMORY; + } + +-/* +- * Setup a new query context for resolving a query. +- * +- * This function is only called if both these conditions are met: +- * 1. BIND is configured with stale-answer-client-timeout 0. +- * 2. A stale RRset is found in cache during initial query +- * database lookup. +- * +- * We continue with this function for refreshing/resolving an RRset +- * after answering a client with stale data. +- */ +-static void +-query_refresh_rrset(query_ctx_t *orig_qctx) { +- isc_buffer_t buffer; +- query_ctx_t qctx; +- +- REQUIRE(orig_qctx != NULL); +- REQUIRE(orig_qctx->client != NULL); +- +- qctx_copy(orig_qctx, &qctx); +- qctx.client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | +- DNS_DBFIND_STALEOK | +- DNS_DBFIND_STALEENABLED); +- qctx.client->nodetach = false; +- +- /* +- * We'll need some resources... +- */ +- if (qctx_prepare_buffers(&qctx, &buffer) != ISC_R_SUCCESS) { +- dns_db_detach(&qctx.db); +- qctx_destroy(&qctx); +- return; +- } +- +- /* +- * Pretend we didn't find anything in cache. +- */ +- (void)query_gotanswer(&qctx, ISC_R_NOTFOUND); +- +- if (qctx.fname != NULL) { +- ns_client_releasename(qctx.client, &qctx.fname); +- } +- if (qctx.rdataset != NULL) { +- ns_client_putrdataset(qctx.client, &qctx.rdataset); +- } +- +- qctx_destroy(&qctx); +-} +- + /*% + * Depending on the db lookup result, we can respond to the + * client this stale answer. +@@ -5944,7 +6106,7 @@ query_lookup(query_ctx_t *qctx) { + rpzqname = qctx->client->query.qname; + } + +- if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) { ++ if ((qctx->options & DNS_GETDB_STALEFIRST) != 0 && !qctx->is_zone) { + /* + * If DNS_GETDB_STALEFIRST is set, it means that a stale + * RRset may be returned as part of this lookup. An attempt +@@ -5954,19 +6116,19 @@ query_lookup(query_ctx_t *qctx) { + qctx->client->query.dboptions |= DNS_DBFIND_STALETIMEOUT; + } + +- dboptions = qctx->client->query.dboptions; +- if (!qctx->is_zone && qctx->findcoveringnsec && +- (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname))) +- { +- dboptions |= DNS_DBFIND_COVERINGNSEC; +- } +- + (void)dns_db_getservestalerefresh(qctx->client->view->cachedb, + &stale_refresh); + if (stale_refresh > 0 && + dns_view_staleanswerenabled(qctx->client->view)) + { +- dboptions |= DNS_DBFIND_STALEENABLED; ++ qctx->client->query.dboptions |= DNS_DBFIND_STALEENABLED; ++ } ++ ++ dboptions = qctx->client->query.dboptions; ++ if (!qctx->is_zone && qctx->findcoveringnsec && ++ (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname))) ++ { ++ dboptions |= DNS_DBFIND_COVERINGNSEC; + } + + result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type, +@@ -6115,19 +6277,7 @@ query_lookup(query_ctx_t *qctx) { + * Immediately return the stale answer, start a + * resolver fetch to refresh the data in cache. + */ +- isc_log_write( +- ns_lctx, NS_LOGCATEGORY_SERVE_STALE, +- NS_LOGMODULE_QUERY, ISC_LOG_INFO, +- "%s stale answer used, an attempt to " +- "refresh the RRset will still be made", +- namebuf); +- +- qctx->refresh_rrset = STALE(qctx->rdataset); +- /* +- * If we are refreshing the RRSet, we must not +- * detach from the client in query_send(). +- */ +- qctx->client->nodetach = qctx->refresh_rrset; ++ qctx->client->nodetach = false; + + if (stale_found) { + ns_client_extendederror( +@@ -6167,76 +6317,12 @@ query_lookup(query_ctx_t *qctx) { + } + } + +- if (stale_timeout && (answer_found || stale_found)) { +- /* +- * Mark RRsets that we are adding to the client message on a +- * lookup during 'stale-answer-client-timeout', so we can +- * clean it up if needed when we resume from recursion. +- */ +- qctx->client->query.attributes |= NS_QUERYATTR_STALEOK; +- qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; +- } +- + result = query_gotanswer(qctx, result); + + cleanup: + return result; + } + +-/* +- * Clear all rdatasets from the message that are in the given section and +- * that have the 'attr' attribute set. +- */ +-static void +-message_clearrdataset(dns_message_t *msg, unsigned int attr) { +- unsigned int i; +- dns_name_t *name, *next_name; +- dns_rdataset_t *rds, *next_rds; +- +- /* +- * Clean up name lists by calling the rdataset disassociate function. +- */ +- for (i = DNS_SECTION_ANSWER; i < DNS_SECTION_MAX; i++) { +- name = ISC_LIST_HEAD(msg->sections[i]); +- while (name != NULL) { +- next_name = ISC_LIST_NEXT(name, link); +- +- rds = ISC_LIST_HEAD(name->list); +- while (rds != NULL) { +- next_rds = ISC_LIST_NEXT(rds, link); +- if ((rds->attributes & attr) != attr) { +- rds = next_rds; +- continue; +- } +- ISC_LIST_UNLINK(name->list, rds, link); +- INSIST(dns_rdataset_isassociated(rds)); +- dns_rdataset_disassociate(rds); +- isc_mempool_put(msg->rdspool, rds); +- rds = next_rds; +- } +- +- if (ISC_LIST_EMPTY(name->list)) { +- ISC_LIST_UNLINK(msg->sections[i], name, link); +- if (dns_name_dynamic(name)) { +- dns_name_free(name, msg->mctx); +- } +- isc_mempool_put(msg->namepool, name); +- } +- +- name = next_name; +- } +- } +-} +- +-/* +- * Clear any rdatasets from the client's message that were added on a lookup +- * due to a client timeout. +- */ +-static void +-query_clear_stale(ns_client_t *client) { +- message_clearrdataset(client->message, DNS_RDATASETATTR_STALE_ADDED); +-} +- + /* + * Create a new query context with the sole intent of looking up for a stale + * RRset in cache. If an entry is found, we mark the original query as +@@ -6316,6 +6402,7 @@ fetch_callback(isc_task_t *task, isc_event_t *event) { + } + client->query.fetchoptions &= ~DNS_FETCHOPT_TRYSTALE_ONTIMEOUT; + client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT; ++ client->query.dboptions &= ~DNS_DBFIND_STALEENABLED; + client->nodetach = false; + + LOCK(&client->query.fetchlock); +@@ -7709,14 +7796,6 @@ query_usestale(query_ctx_t *qctx, isc_result_t result) { + return false; + } + +- if (qctx->refresh_rrset) { +- /* +- * This is a refreshing query, we have already prioritized +- * stale data, so don't enable serve-stale again. +- */ +- return false; +- } +- + if (result == DNS_R_DUPLICATE || result == DNS_R_DROP || + result == ISC_R_ALREADYRUNNING) + { +@@ -8256,24 +8335,6 @@ query_addanswer(query_ctx_t *qctx) { + + CALL_HOOK(NS_QUERY_ADDANSWER_BEGIN, qctx); + +- /* +- * On normal lookups, clear any rdatasets that were added on a +- * lookup due to stale-answer-client-timeout. Do not clear if we +- * are going to refresh the RRset, because the stale contents are +- * prioritized. +- */ +- if (QUERY_STALEOK(&qctx->client->query) && +- !QUERY_STALETIMEOUT(&qctx->client->query) && !qctx->refresh_rrset) +- { +- CCTRACE(ISC_LOG_DEBUG(3), "query_clear_stale"); +- query_clear_stale(qctx->client); +- /* +- * We can clear the attribute to prevent redundant clearing +- * in subsequent lookups. +- */ +- qctx->client->query.attributes &= ~NS_QUERYATTR_STALEOK; +- } +- + if (qctx->dns64) { + result = query_dns64(qctx); + qctx->noqname = NULL; +@@ -8307,9 +8368,7 @@ query_addanswer(query_ctx_t *qctx) { + query_filter64(qctx); + ns_client_putrdataset(qctx->client, &qctx->rdataset); + } else { +- if (!qctx->is_zone && RECURSIONOK(qctx->client) && +- !QUERY_STALETIMEOUT(&qctx->client->query)) +- { ++ if (!qctx->is_zone && RECURSIONOK(qctx->client)) { + query_prefetch(qctx->client, qctx->fname, + qctx->rdataset); + } +@@ -10591,6 +10650,10 @@ query_ncache(query_ctx_t *qctx, isc_result_t result) { + } + } + ++ if (!qctx->is_zone && RECURSIONOK(qctx->client)) { ++ query_stale_refresh_ncache(qctx->client); ++ } ++ + return query_nodata(qctx, result); + + cleanup: +@@ -12093,20 +12156,6 @@ ns_query_done(query_ctx_t *qctx) { + nodetach = qctx->client->nodetach; + query_send(qctx->client); + +- if (qctx->refresh_rrset) { +- /* +- * If we reached this point then it means that we have found a +- * stale RRset entry in cache and BIND is configured to allow +- * queries to be answered with stale data if no active RRset +- * is available, i.e. "stale-anwer-client-timeout 0". But, we +- * still need to refresh the RRset. To prevent adding duplicate +- * RRsets, clear the RRsets from the message before doing the +- * refresh. +- */ +- message_clearrdataset(qctx->client->message, 0); +- query_refresh_rrset(qctx); +- } +- + if (!nodetach) { + qctx->detach_client = true; + } diff --git a/bind.spec b/bind.spec index ade1f2d..6855476 100644 --- a/bind.spec +++ b/bind.spec @@ -80,7 +80,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause # Before rebasing bind, ensure bind-dyndb-ldap is ready to be rebuild and use side-tag with it. # Updating just bind will cause freeipa-dns-server package to be uninstallable. Version: 9.18.33 -Release: 16%{?dist} +Release: 17%{?dist} Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -160,6 +160,9 @@ Patch227: bind-9.20-CVE-2025-8677-dual-signing-test.patch # https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11329 Patch228: bind-9.20-robust-key-rollovers.patch Patch229: bind-9.20-robust-key-rollovers-tests.patch +# https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/10767 +Patch230: bind-9.20-stale-cname.patch +Patch231: bind-9.20-stale-cname-tests.patch %{?systemd_ordering} # https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers @@ -961,6 +964,9 @@ fi; %endif %changelog +* Thu Feb 26 2026 Fedor Vorobev - 32:9.18.33-17 +- Backport fixes for stale CNAME chains. (RHEL-142289) + * Wed Jan 28 2026 Fedor Vorobev - 32:9.18.33-16 - Backport fix for manual DNSSEC key rolllovers. (RHEL-144421)