From: Ondřej Surý Date: Thu, 30 Jul 2026 15:42:26 +0000 (+0200) Subject: Store negative cache entries under their natural type X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b4f36cbc25cfade17a0adf053ff90b17dfe55bea;p=thirdparty%2Fbind9.git Store negative cache entries under their natural type Negative cache entries were exposed through dns_rdataset_t in an inverted shape (type=none, covers=) while the cache internally stored them under (, none) with a NEGATIVE flag, so every consumer had to convert between the two representations, and several places relied on type==0 as an implicit negativity test. Expose negative entries in their natural shape instead: type holds the RR type whose nonexistence is cached (dns_rdatatype_any for NXDOMAIN and NODATA(QTYPE=ANY) proofs), covers stays none, and the negative attribute marks the entry. The implicit type==0 tests become explicit attribute checks, and the cache database now rejects meta-types other than ANY, which remains valid as a lookup type and as a negative entry. --- diff --git a/lib/dns/client.c b/lib/dns/client.c index c50126895e4..2f373076781 100644 --- a/lib/dns/client.c +++ b/lib/dns/client.c @@ -677,7 +677,7 @@ client_resfind(resctx_t *rctx, dns_fetchresponse_t *resp) { while (tresult == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, rctx->rdataset); - if (rctx->rdataset->type != 0) { + if (!rctx->rdataset->attributes.negative) { ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); n++; diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index f97eb3dd8c6..66c501d3323 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -1360,10 +1360,9 @@ dns__db_deleterdataset(dns_db_t *db, dns_dbnode_t *node, * Notes: * * \li In a cache database, a negative cache entry is stored under the - * type it covers. Passing 'type' == dns_rdatatype_none with 'covers' - * set (the shape in which rdataset iterators return negative entries) - * deletes the cache entry for the covered type, whether it is positive - * or negative. + * type whose nonexistence it proves (dns_rdatatype_any for NXDOMAIN + * and NODATA(QTYPE=ANY) entries), so deleting 'type' removes the + * cache entry for that type whether it is positive or negative. * * Requires: * diff --git a/lib/dns/include/dns/ncache.h b/lib/dns/include/dns/ncache.h index ae187668c8f..e6bc6be8f75 100644 --- a/lib/dns/include/dns/ncache.h +++ b/lib/dns/include/dns/ncache.h @@ -53,7 +53,7 @@ isc_result_t dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, - dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_rdatatype_t rdtype, isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl, bool optout, bool secure, dns_rdataset_t *addedrdataset); /*%< @@ -67,7 +67,7 @@ dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, * \li If 'secure' is false, the negative cache entry's trust level * will be capped at answer. * - * The 'covers' argument is the RR type whose nonexistence we are caching, + * The 'rdtype' argument is the RR type whose nonexistence we are caching, * or dns_rdatatype_any when caching a NXDOMAIN response. * * 'optout' parameter indicates if 'optout' attribute should be set. This only diff --git a/lib/dns/include/dns/rdatatype.h b/lib/dns/include/dns/rdatatype.h index 1822daef072..7623ad135cf 100644 --- a/lib/dns/include/dns/rdatatype.h +++ b/lib/dns/include/dns/rdatatype.h @@ -18,9 +18,8 @@ #include #if DNS_TYPEPAIR_CHECK -#define DNS__TYPEPAIR_CHECK(base, covers) \ - INSIST((dns_rdatatype_issig(base) && covers != dns_rdatatype_none) || \ - (base == dns_rdatatype_none && covers != dns_rdatatype_none) || \ +#define DNS__TYPEPAIR_CHECK(base, covers) \ + INSIST((dns_rdatatype_issig(base) && covers != dns_rdatatype_none) || \ (base != dns_rdatatype_none && covers == dns_rdatatype_none)) #else #define DNS__TYPEPAIR_CHECK(base, covers) (void)(base), (void)(covers) diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c index 2040a832d03..5ad7c7c894d 100644 --- a/lib/dns/masterdump.c +++ b/lib/dns/masterdump.c @@ -571,7 +571,6 @@ rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, bool first = true; uint32_t current_ttl; bool current_ttl_valid; - dns_rdatatype_t type; unsigned int type_start; dns_fixedname_t fixed; dns_name_t *name = NULL; @@ -697,19 +696,12 @@ rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, /* * Type. */ - - if (rdataset->attributes.negative) { - type = rdataset->covers; - } else { - type = rdataset->type; - } - INDENT_TO(type_column); type_start = target->used; if (rdataset->attributes.negative) { RETERR(str_totext("\\-", target)); } - switch (type) { + switch (rdataset->type) { case dns_rdatatype_keydata: #define KEYDATA "KEYDATA" if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) { @@ -726,10 +718,11 @@ rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) { - result = dns_rdatatype_tounknowntext(type, - target); + result = dns_rdatatype_tounknowntext( + rdataset->type, target); } else { - result = dns_rdatatype_totext(type, target); + result = dns_rdatatype_totext(rdataset->type, + target); } if (result != ISC_R_SUCCESS) { return result; diff --git a/lib/dns/ncache.c b/lib/dns/ncache.c index 942df43a7f2..344435974fd 100644 --- a/lib/dns/ncache.c +++ b/lib/dns/ncache.c @@ -92,7 +92,7 @@ copy_rdataset(dns_rdataset_t *rdataset, isc_buffer_t *buffer) { isc_result_t dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, - dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_rdatatype_t rdtype, isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl, bool optout, bool secure, dns_rdataset_t *addedrdataset) { isc_buffer_t buffer; @@ -126,7 +126,8 @@ dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, */ dns_rdatalist_init(&ncrdatalist); ncrdatalist.rdclass = dns_db_class(cache); - ncrdatalist.covers = covers; + ncrdatalist.type = rdtype; + ncrdatalist.covers = dns_rdatatype_none; ncrdatalist.ttl = maxttl; /* @@ -257,7 +258,8 @@ dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx, */ REQUIRE(rdataset != NULL); - REQUIRE(rdataset->type == dns_rdatatype_none); + REQUIRE(rdataset->type != dns_rdatatype_none); + REQUIRE(rdataset->covers == dns_rdatatype_none); REQUIRE(rdataset->attributes.negative); savedbuffer = *target; @@ -474,7 +476,8 @@ dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, REQUIRE(ncacherdataset != NULL); REQUIRE(DNS_RDATASET_VALID(ncacherdataset)); - REQUIRE(ncacherdataset->type == dns_rdatatype_none); + REQUIRE(ncacherdataset->type != dns_rdatatype_none); + REQUIRE(ncacherdataset->covers == dns_rdatatype_none); REQUIRE(ncacherdataset->attributes.negative); REQUIRE(name != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); @@ -540,7 +543,8 @@ dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, unsigned int count; REQUIRE(ncacherdataset != NULL); - REQUIRE(ncacherdataset->type == dns_rdatatype_none); + REQUIRE(ncacherdataset->type != dns_rdatatype_none); + REQUIRE(ncacherdataset->covers == dns_rdatatype_none); REQUIRE(ncacherdataset->attributes.negative); REQUIRE(name != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); @@ -625,7 +629,8 @@ dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found, unsigned char *raw; REQUIRE(ncacherdataset != NULL); - REQUIRE(ncacherdataset->type == dns_rdatatype_none); + REQUIRE(ncacherdataset->type != dns_rdatatype_none); + REQUIRE(ncacherdataset->covers == dns_rdatatype_none); REQUIRE(ncacherdataset->attributes.negative); REQUIRE(found != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c index c84039da891..a1431ac3538 100644 --- a/lib/dns/qpcache.c +++ b/lib/dns/qpcache.c @@ -920,19 +920,8 @@ bindrdataset(qpcache_t *qpdb, qpcnode_t *node, dns_slabheader_t *header, rdataset->methods = &dns_rdataslab_rdatasetmethods; rdataset->rdclass = qpdb->common.rdclass; - if (NEGATIVE(header)) { - /* - * Convert the internal represetation of a negative record - * to the rdataset representation (type=0, covers=type). - */ - rdataset->type = dns_rdatatype_none; - rdataset->covers = DNS_TYPEPAIR_TYPE(header->typepair); - INSIST(DNS_TYPEPAIR_COVERS(header->typepair) == - dns_rdatatype_none); - } else { - rdataset->type = DNS_TYPEPAIR_TYPE(header->typepair); - rdataset->covers = DNS_TYPEPAIR_COVERS(header->typepair); - } + rdataset->type = DNS_TYPEPAIR_TYPE(header->typepair); + rdataset->covers = DNS_TYPEPAIR_COVERS(header->typepair); rdataset->ttl = !ZEROTTL(header) ? header->expire - now : 0; rdataset->trust = header_trust(header); rdataset->resign = 0; @@ -1433,8 +1422,14 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, dns_slabheader_t *nsecheader = NULL, *nsecsig = NULL; dns_typepair_t typepair = DNS_TYPEPAIR(type); - if (type == dns_rdatatype_none) { - /* We can't search negative cache directly */ + /* + * Meta-types can't exist in the cache, with the sole exception + * of ANY, which matches any type at the node (both ANY and + * RRSIG queries are looked up as ANY). + */ + if (type == dns_rdatatype_none || + (dns_rdatatype_ismeta(type) && type != dns_rdatatype_any)) + { return ISC_R_NOTFOUND; } @@ -1739,7 +1734,10 @@ qpcache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, qpcache_t *qpdb = (qpcache_t *)db; qpcnode_t *qpnode = (qpcnode_t *)node; dns_slabheader_t *found = NULL, *foundsig = NULL; - dns_typepair_t typepair, sigpair; + dns_typepair_t typepair = DNS_TYPEPAIR_VALUE(type, covers); + dns_typepair_t sigpair = !dns_rdatatype_issig(type) + ? DNS_SIGTYPEPAIR(type) + : dns_typepair_none; isc_result_t result = ISC_R_SUCCESS; isc_rwlock_t *nlock = NULL; isc_rwlocktype_t nlocktype = isc_rwlocktype_none; @@ -1752,18 +1750,19 @@ qpcache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, REQUIRE(version == NULL); REQUIRE(type != dns_rdatatype_any); - if (type == dns_rdatatype_none) { - /* We can't search negative cache directly */ + /* + * Meta-types can't exist in the cache, with the sole + * exception of ANY, which records the nonexistence of all + * types at the node (NXDOMAIN or NODATA(QTYPE=ANY) proof), + * but can't be looked up using this function. + */ + if (type == dns_rdatatype_none || dns_rdatatype_ismeta(type)) { return ISC_R_NOTFOUND; } nlock = &qpdb->buckets[qpnode->locknum].lock; NODE_RDLOCK(nlock, &nlocktype); - typepair = DNS_TYPEPAIR_VALUE(type, covers); - sigpair = (type != dns_rdatatype_rrsig) ? DNS_SIGTYPEPAIR(type) - : dns_typepair_none; - DNS_SLABHEADER_FOREACH(tmp, &qpnode->headers) { dns_slabheader_t *header = NULL, *sigheader = NULL; @@ -2649,6 +2648,20 @@ qpcache_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, REQUIRE(VALID_QPDB(qpdb)); REQUIRE(version == NULL); + /* + * Meta-types can't be added to the cache, with the sole + * exception of a negative ANY entry, which records the + * nonexistence of all types at the node (NXDOMAIN or + * NODATA(QTYPE=ANY) proof). + */ + if (rdataset->type == dns_rdatatype_none || + (dns_rdatatype_ismeta(rdataset->type) && + !(rdataset->type == dns_rdatatype_any && + rdataset->attributes.negative))) + { + return ISC_R_NOTIMPLEMENTED; + } + result = dns_rdataslab_fromrdataset(rdataset, qpnode->mctx, ®ion, qpdb->maxrrperset); if (result != ISC_R_SUCCESS) { @@ -2774,19 +2787,15 @@ qpcache_deleterdataset(dns_db_t *db, dns_dbnode_t *node, REQUIRE(VALID_QPDB(qpdb)); REQUIRE(version == NULL); - /* Positive ANY type can't be in the cache. */ - if (type == dns_rdatatype_any) { - return ISC_R_NOTIMPLEMENTED; - } - /* - * Convert the rdataset representation of a negative record - * (type=0, covers=type) to the internal representation (the - * other way around). + * Type none can't exist in the cache; note that type ANY is + * a valid argument here because it matches the negative cache + * entry left behind by an NXDOMAIN or NODATA(QTYPE=ANY) response. */ - if (type == dns_rdatatype_none && covers != dns_rdatatype_none) { - type = covers; - covers = dns_rdatatype_none; + if (type == dns_rdatatype_none || + (dns_rdatatype_ismeta(type) && type != dns_rdatatype_any)) + { + return ISC_R_NOTIMPLEMENTED; } typepair = DNS_TYPEPAIR_VALUE(type, covers); diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c index 107a1df4e50..cf8302525a6 100644 --- a/lib/dns/rdataslab.c +++ b/lib/dns/rdataslab.c @@ -196,11 +196,12 @@ makeslab(dns_rdataset_t *rdataset, isc_mem_t *mctx, isc_region_t *region, /* * If there are no rdata then we just need to allocate a header - * with a zero record count. + * with a zero record count. Only a negative cache entry (e.g. + * an uncacheable NODATA proof) may be empty. */ nitems = dns_rdataset_count(rdataset); if (nitems == 0) { - if (rdataset->type != 0) { + if (!rdataset->attributes.negative) { return ISC_R_FAILURE; } (void)newslab(rdataset, mctx, region, 0, buflen, func, file, @@ -301,9 +302,13 @@ makeslab(dns_rdataset_t *rdataset, isc_mem_t *mctx, isc_region_t *region, } /* - * Ensure that singleton types are actually singletons. + * Ensure that singleton types are actually singletons. The check + * doesn't apply to a negative cache entry: it stores ncache-encoded + * records rather than RRs of 'rdataset->type'. */ - if (nitems > 1 && dns_rdatatype_issingleton(rdataset->type)) { + if (nitems > 1 && !rdataset->attributes.negative && + dns_rdatatype_issingleton(rdataset->type)) + { /* * We have a singleton type, but there's more than one * RR in the rdataset. @@ -370,18 +375,10 @@ dns_rdataslab__fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, } dns_slabheader_t *header = (dns_slabheader_t *)region->base; - if (rdataset->attributes.negative) { - INSIST(rdataset->type == dns_rdatatype_none); - INSIST(rdataset->covers != dns_rdatatype_none); - header->typepair = DNS_TYPEPAIR_VALUE(rdataset->covers, - dns_rdatatype_none); - } else { - INSIST(rdataset->type != dns_rdatatype_none); - INSIST(dns_rdatatype_issig(rdataset->type) || - rdataset->covers == dns_rdatatype_none); - header->typepair = DNS_TYPEPAIR_VALUE(rdataset->type, - rdataset->covers); - } + INSIST(rdataset->type != dns_rdatatype_none); + INSIST(dns_rdatatype_issig(rdataset->type) || + rdataset->covers == dns_rdatatype_none); + header->typepair = DNS_TYPEPAIR_VALUE(rdataset->type, rdataset->covers); return ISC_R_SUCCESS; } diff --git a/lib/dns/rdatavec.c b/lib/dns/rdatavec.c index 7436af85738..335737520d6 100644 --- a/lib/dns/rdatavec.c +++ b/lib/dns/rdatavec.c @@ -352,27 +352,19 @@ dns_rdatavec_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, result = makevec(rdataset, mctx, region, maxrrperset); if (result == ISC_R_SUCCESS) { dns_vecheader_t *new = (dns_vecheader_t *)region->base; - dns_typepair_t typepair; - if (rdataset->attributes.negative) { - INSIST(rdataset->type == dns_rdatatype_none); - INSIST(rdataset->covers != dns_rdatatype_none); - typepair = DNS_TYPEPAIR_VALUE(rdataset->covers, - dns_rdatatype_none); - } else { - INSIST(rdataset->type != dns_rdatatype_none); - INSIST(dns_rdatatype_issig(rdataset->type) || - rdataset->covers == dns_rdatatype_none); - typepair = DNS_TYPEPAIR_VALUE(rdataset->type, - rdataset->covers); - } + INSIST(!rdataset->attributes.negative); + INSIST(rdataset->type != dns_rdatatype_none); + INSIST(dns_rdatatype_issig(rdataset->type) || + rdataset->covers == dns_rdatatype_none); /* * Reset the vecheader content, but keep the refcount and mctx. */ *new = (dns_vecheader_t){ .next_header = ISC_SLINK_INITIALIZER, - .typepair = typepair, + .typepair = DNS_TYPEPAIR_VALUE(rdataset->type, + rdataset->covers), .trust = rdataset->trust, .ttl = rdataset->ttl, .references = atomic_load_acquire(&new->references), diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 96c83002fa0..9b0ac0ce48d 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -6546,7 +6546,7 @@ negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name, isc_result_t result; dns_ttl_t minttl = fctx->res->view->minncachettl; dns_ttl_t maxttl = fctx->res->view->maxncachettl; - dns_rdatatype_t covers = fctx->type; + dns_rdatatype_t rdtype = fctx->type; dns_db_t *cache = fctx->cache; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset = DNS_RDATASET_INIT; @@ -6562,7 +6562,7 @@ negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name, if (message->rcode == dns_rcode_nxdomain && fctx->type != dns_rdatatype_ds) { - covers = dns_rdatatype_any; + rdtype = dns_rdatatype_any; } /* @@ -6570,7 +6570,7 @@ negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name, * to zero to facilitate locating the containing zone of * an arbitrary zone. */ - if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any && + if (fctx->type == dns_rdatatype_soa && rdtype == dns_rdatatype_any && fctx->res->zero_no_soa_ttl) { maxttl = 0; @@ -6592,7 +6592,7 @@ negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name, */ RETERR(dns_db_findnode(fctx->cache, name, true, &node)); - result = dns_ncache_add(message, cache, node, covers, now, minttl, + result = dns_ncache_add(message, cache, node, rdtype, now, minttl, maxttl, optout, secure, added); /* @@ -6605,7 +6605,7 @@ negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name, * We got the same negative type that we were adding, everything * is fine, continue. */ - if (NEGATIVE(added) && added->covers == covers) { + if (NEGATIVE(added) && added->type == rdtype) { result = ISC_R_SUCCESS; } else { dns_rdataset_disassociate(added); diff --git a/lib/dns/validator.c b/lib/dns/validator.c index e13c8850a09..213a681af24 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -902,21 +902,21 @@ validator_callback_ds(void *arg) { } switch (result) { - case ISC_R_SUCCESS: + case ISC_R_SUCCESS: { + bool have_dsset = !NEGATIVE(&val->frdataset) && + val->frdataset.type == dns_rdatatype_ds; + dns_name_t *name = dns_fixedname_name(&val->fname); + validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", - val->frdataset.type == dns_rdatatype_ds - ? "dsset" - : "ds non-existence", + have_dsset ? "dsset" : "ds non-existence", dns_trust_totext(val->frdataset.trust)); - bool have_dsset = (val->frdataset.type == dns_rdatatype_ds); - dns_name_t *name = dns_fixedname_name(&val->fname); if ((val->attributes & VALATTR_INSECURITY) != 0) { bool crossed = false; bool insecure = false; - if (val->frdataset.covers == dns_rdatatype_ds && - NEGATIVE(&val->frdataset)) + if (NEGATIVE(&val->frdataset) && + val->frdataset.type == dns_rdatatype_ds) { insecure = is_insecure_referral( val, name, &val->frdataset, @@ -942,6 +942,7 @@ validator_callback_ds(void *arg) { result = validate_async_run(val, validate_dnskey); } break; + } case ISC_R_CANCELED: /* Validation was canceled */ case ISC_R_SHUTTINGDOWN: /* Server shutting down */ case ISC_R_QUOTA: /* Validation fails quota reached */ @@ -3931,7 +3932,7 @@ validator_start(void *arg) { default: UNREACHABLE(); } - } else if (val->rdataset != NULL && val->rdataset->type != 0) { + } else if (val->rdataset != NULL && !NEGATIVE(val->rdataset)) { /* * This is either an unsecure subdomain or a response * from a broken server. diff --git a/lib/ns/query.c b/lib/ns/query.c index a39a9a4c6bb..53da43f9d27 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -7367,7 +7367,7 @@ query_respond_any(query_ctx_t *qctx) { dns_rdataset_disassociate(qctx->rdataset); } else if ((qctx->qtype == dns_rdatatype_any || qctx->rdataset->type == qctx->qtype) && - qctx->rdataset->type != 0) + !qctx->rdataset->attributes.negative) { if (qctx->rdataset->attributes.noqname && qctx->client->inner.wantdnssec)