mirror of
https://github.com/isc-projects/bind9.git
synced 2026-04-25 08:07:12 -04:00
As we are in overmem state we want to free more memory than we are adding so we need to add in an allowance for the rbtnodes that may have been added and the names stored with them. There is the node for the owner name and a possible ENT node if there was a node split.
1718 lines
46 KiB
C
1718 lines
46 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <isc/ascii.h>
|
|
#include <isc/async.h>
|
|
#include <isc/atomic.h>
|
|
#include <isc/crc64.h>
|
|
#include <isc/file.h>
|
|
#include <isc/hash.h>
|
|
#include <isc/hashmap.h>
|
|
#include <isc/heap.h>
|
|
#include <isc/hex.h>
|
|
#include <isc/loop.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/mutex.h>
|
|
#include <isc/once.h>
|
|
#include <isc/random.h>
|
|
#include <isc/refcount.h>
|
|
#include <isc/result.h>
|
|
#include <isc/rwlock.h>
|
|
#include <isc/serial.h>
|
|
#include <isc/stdio.h>
|
|
#include <isc/string.h>
|
|
#include <isc/time.h>
|
|
#include <isc/urcu.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/callbacks.h>
|
|
#include <dns/db.h>
|
|
#include <dns/dbiterator.h>
|
|
#include <dns/fixedname.h>
|
|
#include <dns/log.h>
|
|
#include <dns/masterdump.h>
|
|
#include <dns/nsec.h>
|
|
#include <dns/nsec3.h>
|
|
#include <dns/rbt.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatasetiter.h>
|
|
#include <dns/rdataslab.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/stats.h>
|
|
#include <dns/time.h>
|
|
#include <dns/view.h>
|
|
#include <dns/zone.h>
|
|
#include <dns/zonekey.h>
|
|
|
|
#include "rbtdb_p.h"
|
|
|
|
#define CHECK(op) \
|
|
do { \
|
|
result = (op); \
|
|
if (result != ISC_R_SUCCESS) \
|
|
goto failure; \
|
|
} while (0)
|
|
|
|
/*%
|
|
* Whether to rate-limit updating the LRU to avoid possible thread contention.
|
|
* Updating LRU requires write locking, so we don't do it every time the
|
|
* record is touched - only after some time passes.
|
|
*/
|
|
#ifndef DNS_RBTDB_LIMITLRUUPDATE
|
|
#define DNS_RBTDB_LIMITLRUUPDATE 1
|
|
#endif
|
|
|
|
/*% Time after which we update LRU for glue records, 5 minutes */
|
|
#define DNS_RBTDB_LRUUPDATE_GLUE 300
|
|
/*% Time after which we update LRU for all other records, 10 minutes */
|
|
#define DNS_RBTDB_LRUUPDATE_REGULAR 600
|
|
|
|
#define EXISTS(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_NONEXISTENT) == 0)
|
|
#define NONEXISTENT(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_NONEXISTENT) != 0)
|
|
#define NXDOMAIN(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_NXDOMAIN) != 0)
|
|
#define STALE(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_STALE) != 0)
|
|
#define NEGATIVE(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_NEGATIVE) != 0)
|
|
#define ZEROTTL(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_ZEROTTL) != 0)
|
|
#define ANCIENT(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_ANCIENT) != 0)
|
|
#define STATCOUNT(header) \
|
|
((atomic_load_acquire(&(header)->attributes) & \
|
|
DNS_SLABHEADERATTR_STATCOUNT) != 0)
|
|
|
|
#define STALE_TTL(header, rbtdb) \
|
|
(NXDOMAIN(header) ? 0 : rbtdb->common.serve_stale_ttl)
|
|
|
|
#define ACTIVE(header, now) \
|
|
(((header)->ttl > (now)) || ((header)->ttl == (now) && ZEROTTL(header)))
|
|
|
|
#define KEEPSTALE(rbtdb) ((rbtdb)->common.serve_stale_ttl > 0)
|
|
|
|
/*%
|
|
* Routines for LRU-based cache management.
|
|
*/
|
|
|
|
/*%
|
|
* See if a given cache entry that is being reused needs to be updated
|
|
* in the LRU-list. From the LRU management point of view, this function is
|
|
* expected to return true for almost all cases. When used with threads,
|
|
* however, this may cause a non-negligible performance penalty because a
|
|
* writer lock will have to be acquired before updating the list.
|
|
* If DNS_RBTDB_LIMITLRUUPDATE is defined to be non 0 at compilation time, this
|
|
* function returns true if the entry has not been updated for some period of
|
|
* time. We differentiate the NS or glue address case and the others since
|
|
* experiments have shown that the former tends to be accessed relatively
|
|
* infrequently and the cost of cache miss is higher (e.g., a missing NS records
|
|
* may cause external queries at a higher level zone, involving more
|
|
* transactions).
|
|
*
|
|
* Caller must hold the node (read or write) lock.
|
|
*/
|
|
static bool
|
|
need_headerupdate(dns_slabheader_t *header, isc_stdtime_t now) {
|
|
if (DNS_SLABHEADER_GETATTR(header, (DNS_SLABHEADERATTR_NONEXISTENT |
|
|
DNS_SLABHEADERATTR_ANCIENT |
|
|
DNS_SLABHEADERATTR_ZEROTTL)) != 0)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
#if DNS_RBTDB_LIMITLRUUPDATE
|
|
if (header->type == dns_rdatatype_ns ||
|
|
(header->trust == dns_trust_glue &&
|
|
(header->type == dns_rdatatype_a ||
|
|
header->type == dns_rdatatype_aaaa)))
|
|
{
|
|
/*
|
|
* Glue records are updated if at least DNS_RBTDB_LRUUPDATE_GLUE
|
|
* seconds have passed since the previous update time.
|
|
*/
|
|
return (header->last_used + DNS_RBTDB_LRUUPDATE_GLUE <= now);
|
|
}
|
|
|
|
/*
|
|
* Other records are updated if DNS_RBTDB_LRUUPDATE_REGULAR seconds
|
|
* have passed.
|
|
*/
|
|
return (header->last_used + DNS_RBTDB_LRUUPDATE_REGULAR <= now);
|
|
#else
|
|
UNUSED(now);
|
|
|
|
return (true);
|
|
#endif /* if DNS_RBTDB_LIMITLRUUPDATE */
|
|
}
|
|
|
|
/*%
|
|
* Update the timestamp of a given cache entry and move it to the head
|
|
* of the corresponding LRU list.
|
|
*
|
|
* Caller must hold the node (write) lock.
|
|
*
|
|
* Note that the we do NOT touch the heap here, as the TTL has not changed.
|
|
*/
|
|
static void
|
|
update_header(dns_rbtdb_t *rbtdb, dns_slabheader_t *header, isc_stdtime_t now) {
|
|
INSIST(IS_CACHE(rbtdb));
|
|
|
|
/* To be checked: can we really assume this? XXXMLG */
|
|
INSIST(ISC_LINK_LINKED(header, link));
|
|
|
|
ISC_LIST_UNLINK(rbtdb->lru[HEADER_NODE(header)->locknum], header, link);
|
|
header->last_used = now;
|
|
ISC_LIST_PREPEND(rbtdb->lru[HEADER_NODE(header)->locknum], header,
|
|
link);
|
|
}
|
|
|
|
/*
|
|
* Locking
|
|
*
|
|
* If a routine is going to lock more than one lock in this module, then
|
|
* the locking must be done in the following order:
|
|
*
|
|
* Tree Lock
|
|
*
|
|
* Node Lock (Only one from the set may be locked at one time by
|
|
* any caller)
|
|
*
|
|
* Database Lock
|
|
*
|
|
* Failure to follow this hierarchy can result in deadlock.
|
|
*/
|
|
|
|
/*
|
|
* Deleting Nodes
|
|
*
|
|
* For zone databases the node for the origin of the zone MUST NOT be deleted.
|
|
*/
|
|
|
|
/*
|
|
* DB Routines
|
|
*/
|
|
|
|
static void
|
|
update_cachestats(dns_rbtdb_t *rbtdb, isc_result_t result) {
|
|
INSIST(IS_CACHE(rbtdb));
|
|
|
|
if (rbtdb->cachestats == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (result) {
|
|
case DNS_R_COVERINGNSEC:
|
|
isc_stats_increment(rbtdb->cachestats,
|
|
dns_cachestatscounter_coveringnsec);
|
|
FALLTHROUGH;
|
|
case ISC_R_SUCCESS:
|
|
case DNS_R_CNAME:
|
|
case DNS_R_DNAME:
|
|
case DNS_R_DELEGATION:
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_NCACHENXRRSET:
|
|
isc_stats_increment(rbtdb->cachestats,
|
|
dns_cachestatscounter_hits);
|
|
break;
|
|
default:
|
|
isc_stats_increment(rbtdb->cachestats,
|
|
dns_cachestatscounter_misses);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clean_stale_headers(dns_slabheader_t *top) {
|
|
dns_slabheader_t *d = NULL, *down_next = NULL;
|
|
|
|
for (d = top->down; d != NULL; d = down_next) {
|
|
down_next = d->down;
|
|
dns_slabheader_destroy(&d);
|
|
}
|
|
top->down = NULL;
|
|
}
|
|
|
|
static isc_result_t
|
|
setup_delegation(rbtdb_search_t *search, dns_dbnode_t **nodep,
|
|
dns_name_t *foundname, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
dns_name_t *zcname = NULL;
|
|
dns_typepair_t type;
|
|
dns_rbtnode_t *node = NULL;
|
|
|
|
REQUIRE(search != NULL);
|
|
REQUIRE(search->zonecut != NULL);
|
|
REQUIRE(search->zonecut_header != NULL);
|
|
|
|
/*
|
|
* The caller MUST NOT be holding any node locks.
|
|
*/
|
|
|
|
node = search->zonecut;
|
|
type = search->zonecut_header->type;
|
|
|
|
/*
|
|
* If we have to set foundname, we do it before anything else.
|
|
* If we were to set foundname after we had set nodep or bound the
|
|
* rdataset, then we'd have to undo that work if dns_name_copy()
|
|
* failed. By setting foundname first, there's nothing to undo if
|
|
* we have trouble.
|
|
*/
|
|
if (foundname != NULL && search->copy_name) {
|
|
zcname = dns_fixedname_name(&search->zonecut_name);
|
|
dns_name_copy(zcname, foundname);
|
|
}
|
|
if (nodep != NULL) {
|
|
/*
|
|
* Note that we don't have to increment the node's reference
|
|
* count here because we're going to use the reference we
|
|
* already have in the search block.
|
|
*/
|
|
*nodep = node;
|
|
search->need_cleanup = false;
|
|
}
|
|
if (rdataset != NULL) {
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
NODE_RDLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
|
|
&nlocktype);
|
|
dns__rbtdb_bindrdataset(search->rbtdb, node,
|
|
search->zonecut_header, search->now,
|
|
isc_rwlocktype_read,
|
|
rdataset DNS__DB_FLARG_PASS);
|
|
if (sigrdataset != NULL && search->zonecut_sigheader != NULL) {
|
|
dns__rbtdb_bindrdataset(
|
|
search->rbtdb, node, search->zonecut_sigheader,
|
|
search->now, isc_rwlocktype_read,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
}
|
|
NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock),
|
|
&nlocktype);
|
|
}
|
|
|
|
if (type == dns_rdatatype_dname) {
|
|
return (DNS_R_DNAME);
|
|
}
|
|
return (DNS_R_DELEGATION);
|
|
}
|
|
|
|
static bool
|
|
check_stale_header(dns_rbtnode_t *node, dns_slabheader_t *header,
|
|
isc_rwlocktype_t *nlocktypep, isc_rwlock_t *lock,
|
|
rbtdb_search_t *search, dns_slabheader_t **header_prev) {
|
|
if (!ACTIVE(header, search->now)) {
|
|
dns_ttl_t stale = header->ttl +
|
|
STALE_TTL(header, search->rbtdb);
|
|
/*
|
|
* If this data is in the stale window keep it and if
|
|
* DNS_DBFIND_STALEOK is not set we tell the caller to
|
|
* skip this record. We skip the records with ZEROTTL
|
|
* (these records should not be cached anyway).
|
|
*/
|
|
|
|
DNS_SLABHEADER_CLRATTR(header, DNS_SLABHEADERATTR_STALE_WINDOW);
|
|
if (!ZEROTTL(header) && KEEPSTALE(search->rbtdb) &&
|
|
stale > search->now)
|
|
{
|
|
dns__rbtdb_mark(header, DNS_SLABHEADERATTR_STALE);
|
|
*header_prev = header;
|
|
/*
|
|
* If DNS_DBFIND_STALESTART is set then it means we
|
|
* failed to resolve the name during recursion, in
|
|
* this case we mark the time in which the refresh
|
|
* failed.
|
|
*/
|
|
if ((search->options & DNS_DBFIND_STALESTART) != 0) {
|
|
atomic_store_release(
|
|
&header->last_refresh_fail_ts,
|
|
search->now);
|
|
} else if ((search->options &
|
|
DNS_DBFIND_STALEENABLED) != 0 &&
|
|
search->now <
|
|
(atomic_load_acquire(
|
|
&header->last_refresh_fail_ts) +
|
|
search->rbtdb->serve_stale_refresh))
|
|
{
|
|
/*
|
|
* If we are within interval between last
|
|
* refresh failure time + 'stale-refresh-time',
|
|
* then don't skip this stale entry but use it
|
|
* instead.
|
|
*/
|
|
DNS_SLABHEADER_SETATTR(
|
|
header,
|
|
DNS_SLABHEADERATTR_STALE_WINDOW);
|
|
return (false);
|
|
} else if ((search->options &
|
|
DNS_DBFIND_STALETIMEOUT) != 0)
|
|
{
|
|
/*
|
|
* We want stale RRset due to timeout, so we
|
|
* don't skip it.
|
|
*/
|
|
return (false);
|
|
}
|
|
return ((search->options & DNS_DBFIND_STALEOK) == 0);
|
|
}
|
|
|
|
/*
|
|
* This rdataset is stale. If no one else is using the
|
|
* node, we can clean it up right now, otherwise we mark
|
|
* it as ancient, and the node as dirty, so it will get
|
|
* cleaned up later.
|
|
*/
|
|
if ((header->ttl < search->now - RBTDB_VIRTUAL) &&
|
|
(*nlocktypep == isc_rwlocktype_write ||
|
|
NODE_TRYUPGRADE(lock, nlocktypep) == ISC_R_SUCCESS))
|
|
{
|
|
/*
|
|
* We update the node's status only when we can
|
|
* get write access; otherwise, we leave others
|
|
* to this work. Periodical cleaning will
|
|
* eventually take the job as the last resort.
|
|
* We won't downgrade the lock, since other
|
|
* rdatasets are probably stale, too.
|
|
*/
|
|
|
|
if (isc_refcount_current(&node->references) == 0) {
|
|
/*
|
|
* header->down can be non-NULL if the
|
|
* refcount has just decremented to 0
|
|
* but dns__rbtdb_decref() has not
|
|
* performed clean_cache_node(), in
|
|
* which case we need to purge the stale
|
|
* headers first.
|
|
*/
|
|
clean_stale_headers(header);
|
|
if (*header_prev != NULL) {
|
|
(*header_prev)->next = header->next;
|
|
} else {
|
|
node->data = header->next;
|
|
}
|
|
dns_slabheader_destroy(&header);
|
|
} else {
|
|
dns__rbtdb_mark(header,
|
|
DNS_SLABHEADERATTR_ANCIENT);
|
|
HEADER_NODE(header)->dirty = 1;
|
|
*header_prev = header;
|
|
}
|
|
} else {
|
|
*header_prev = header;
|
|
}
|
|
return (true);
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name,
|
|
void *arg DNS__DB_FLARG) {
|
|
rbtdb_search_t *search = arg;
|
|
dns_slabheader_t *header = NULL;
|
|
dns_slabheader_t *header_prev = NULL, *header_next = NULL;
|
|
dns_slabheader_t *dname_header = NULL, *sigdname_header = NULL;
|
|
isc_result_t result;
|
|
isc_rwlock_t *lock = NULL;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
|
|
REQUIRE(search->zonecut == NULL);
|
|
|
|
/*
|
|
* Keep compiler silent.
|
|
*/
|
|
UNUSED(name);
|
|
|
|
lock = &(search->rbtdb->node_locks[node->locknum].lock);
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
|
|
/*
|
|
* Look for a DNAME or RRSIG DNAME rdataset.
|
|
*/
|
|
for (header = node->data; header != NULL; header = header_next) {
|
|
header_next = header->next;
|
|
if (check_stale_header(node, header, &nlocktype, lock, search,
|
|
&header_prev))
|
|
{
|
|
/* Do nothing. */
|
|
} else if (header->type == dns_rdatatype_dname &&
|
|
EXISTS(header) && !ANCIENT(header))
|
|
{
|
|
dname_header = header;
|
|
header_prev = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_SIGDNAME &&
|
|
EXISTS(header) && !ANCIENT(header))
|
|
{
|
|
sigdname_header = header;
|
|
header_prev = header;
|
|
} else {
|
|
header_prev = header;
|
|
}
|
|
}
|
|
|
|
if (dname_header != NULL &&
|
|
(!DNS_TRUST_PENDING(dname_header->trust) ||
|
|
(search->options & DNS_DBFIND_PENDINGOK) != 0))
|
|
{
|
|
/*
|
|
* We increment the reference count on node to ensure that
|
|
* search->zonecut_header will still be valid later.
|
|
*/
|
|
dns__rbtdb_newref(search->rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
search->zonecut = node;
|
|
search->zonecut_header = dname_header;
|
|
search->zonecut_sigheader = sigdname_header;
|
|
search->need_cleanup = true;
|
|
result = DNS_R_PARTIALMATCH;
|
|
} else {
|
|
result = DNS_R_CONTINUE;
|
|
}
|
|
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
find_deepest_zonecut(rbtdb_search_t *search, dns_rbtnode_t *node,
|
|
dns_dbnode_t **nodep, dns_name_t *foundname,
|
|
dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
unsigned int i;
|
|
isc_result_t result = ISC_R_NOTFOUND;
|
|
dns_name_t name;
|
|
dns_rbtdb_t *rbtdb = NULL;
|
|
bool done;
|
|
|
|
/*
|
|
* Caller must be holding the tree lock.
|
|
*/
|
|
|
|
rbtdb = search->rbtdb;
|
|
i = search->chain.level_matches;
|
|
done = false;
|
|
do {
|
|
dns_slabheader_t *header = NULL;
|
|
dns_slabheader_t *header_prev = NULL, *header_next = NULL;
|
|
dns_slabheader_t *found = NULL, *foundsig = NULL;
|
|
isc_rwlock_t *lock = &rbtdb->node_locks[node->locknum].lock;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
|
|
/*
|
|
* Look for NS and RRSIG NS rdatasets.
|
|
*/
|
|
for (header = node->data; header != NULL; header = header_next)
|
|
{
|
|
header_next = header->next;
|
|
if (check_stale_header(node, header, &nlocktype, lock,
|
|
search, &header_prev))
|
|
{
|
|
/* Do nothing. */
|
|
} else if (EXISTS(header) && !ANCIENT(header)) {
|
|
/*
|
|
* We've found an extant rdataset. See if
|
|
* we're interested in it.
|
|
*/
|
|
if (header->type == dns_rdatatype_ns) {
|
|
found = header;
|
|
if (foundsig != NULL) {
|
|
break;
|
|
}
|
|
} else if (header->type ==
|
|
RBTDB_RDATATYPE_SIGNS)
|
|
{
|
|
foundsig = header;
|
|
if (found != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
header_prev = header;
|
|
} else {
|
|
header_prev = header;
|
|
}
|
|
}
|
|
|
|
if (found != NULL) {
|
|
/*
|
|
* If we have to set foundname, we do it before
|
|
* anything else. If we were to set foundname after
|
|
* we had set nodep or bound the rdataset, then we'd
|
|
* have to undo that work if dns_name_concatenate()
|
|
* failed. By setting foundname first, there's
|
|
* nothing to undo if we have trouble.
|
|
*/
|
|
if (foundname != NULL) {
|
|
dns_name_init(&name, NULL);
|
|
dns_rbt_namefromnode(node, &name);
|
|
dns_name_copy(&name, foundname);
|
|
while (i > 0) {
|
|
dns_rbtnode_t *level_node =
|
|
search->chain.levels[--i];
|
|
dns_name_init(&name, NULL);
|
|
dns_rbt_namefromnode(level_node, &name);
|
|
result = dns_name_concatenate(
|
|
foundname, &name, foundname,
|
|
NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (nodep != NULL) {
|
|
*nodep = NULL;
|
|
}
|
|
goto node_exit;
|
|
}
|
|
}
|
|
}
|
|
result = DNS_R_DELEGATION;
|
|
if (nodep != NULL) {
|
|
dns__rbtdb_newref(search->rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
*nodep = node;
|
|
}
|
|
dns__rbtdb_bindrdataset(search->rbtdb, node, found,
|
|
search->now, nlocktype,
|
|
rdataset DNS__DB_FLARG_PASS);
|
|
if (foundsig != NULL) {
|
|
dns__rbtdb_bindrdataset(
|
|
search->rbtdb, node, foundsig,
|
|
search->now, nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
}
|
|
if (need_headerupdate(found, search->now) ||
|
|
(foundsig != NULL &&
|
|
need_headerupdate(foundsig, search->now)))
|
|
{
|
|
if (nlocktype != isc_rwlocktype_write) {
|
|
NODE_FORCEUPGRADE(lock, &nlocktype);
|
|
POST(nlocktype);
|
|
}
|
|
if (need_headerupdate(found, search->now)) {
|
|
update_header(search->rbtdb, found,
|
|
search->now);
|
|
}
|
|
if (foundsig != NULL &&
|
|
need_headerupdate(foundsig, search->now))
|
|
{
|
|
update_header(search->rbtdb, foundsig,
|
|
search->now);
|
|
}
|
|
}
|
|
}
|
|
|
|
node_exit:
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
|
|
if (found == NULL && i > 0) {
|
|
i--;
|
|
node = search->chain.levels[i];
|
|
} else {
|
|
done = true;
|
|
}
|
|
} while (!done);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Look for a potentially covering NSEC in the cache where `name`
|
|
* is known not to exist. This uses the auxiliary NSEC tree to find
|
|
* the potential NSEC owner. If found, we update 'foundname', 'nodep',
|
|
* 'rdataset' and 'sigrdataset', and return DNS_R_COVERINGNSEC.
|
|
* Otherwise, return ISC_R_NOTFOUND.
|
|
*/
|
|
static isc_result_t
|
|
find_coveringnsec(rbtdb_search_t *search, const dns_name_t *name,
|
|
dns_dbnode_t **nodep, isc_stdtime_t now,
|
|
dns_name_t *foundname, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
dns_fixedname_t fprefix, forigin, ftarget, fixed;
|
|
dns_name_t *prefix = NULL, *origin = NULL;
|
|
dns_name_t *target = NULL, *fname = NULL;
|
|
dns_rbtnode_t *node = NULL;
|
|
dns_rbtnodechain_t chain;
|
|
isc_result_t result;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
isc_rwlock_t *lock = NULL;
|
|
dns_typepair_t matchtype, sigmatchtype;
|
|
dns_slabheader_t *found = NULL, *foundsig = NULL;
|
|
dns_slabheader_t *header = NULL;
|
|
dns_slabheader_t *header_next = NULL, *header_prev = NULL;
|
|
|
|
/*
|
|
* Look for the node in the auxilary tree.
|
|
*/
|
|
dns_rbtnodechain_init(&chain);
|
|
target = dns_fixedname_initname(&ftarget);
|
|
result = dns_rbt_findnode(search->rbtdb->nsec, name, target, &node,
|
|
&chain, DNS_RBTFIND_EMPTYDATA, NULL, NULL);
|
|
if (result != DNS_R_PARTIALMATCH) {
|
|
dns_rbtnodechain_reset(&chain);
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
prefix = dns_fixedname_initname(&fprefix);
|
|
origin = dns_fixedname_initname(&forigin);
|
|
target = dns_fixedname_initname(&ftarget);
|
|
fname = dns_fixedname_initname(&fixed);
|
|
|
|
matchtype = DNS_TYPEPAIR_VALUE(dns_rdatatype_nsec, 0);
|
|
sigmatchtype = DNS_TYPEPAIR_VALUE(dns_rdatatype_rrsig,
|
|
dns_rdatatype_nsec);
|
|
|
|
/*
|
|
* Extract predecessor from chain.
|
|
*/
|
|
result = dns_rbtnodechain_current(&chain, prefix, origin, NULL);
|
|
dns_rbtnodechain_reset(&chain);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
result = dns_name_concatenate(prefix, origin, target, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
/*
|
|
* Lookup the predecessor in the main tree.
|
|
*/
|
|
node = NULL;
|
|
result = dns_rbt_findnode(search->rbtdb->tree, target, fname, &node,
|
|
NULL, DNS_RBTFIND_EMPTYDATA, NULL, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
lock = &(search->rbtdb->node_locks[node->locknum].lock);
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
for (header = node->data; header != NULL; header = header_next) {
|
|
header_next = header->next;
|
|
if (check_stale_header(node, header, &nlocktype, lock, search,
|
|
&header_prev))
|
|
{
|
|
continue;
|
|
}
|
|
if (NONEXISTENT(header) || DNS_TYPEPAIR_TYPE(header->type) == 0)
|
|
{
|
|
header_prev = header;
|
|
continue;
|
|
}
|
|
if (header->type == matchtype) {
|
|
found = header;
|
|
if (foundsig != NULL) {
|
|
break;
|
|
}
|
|
} else if (header->type == sigmatchtype) {
|
|
foundsig = header;
|
|
if (found != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
header_prev = header;
|
|
}
|
|
if (found != NULL) {
|
|
dns__rbtdb_bindrdataset(search->rbtdb, node, found, now,
|
|
nlocktype, rdataset DNS__DB_FLARG_PASS);
|
|
if (foundsig != NULL) {
|
|
dns__rbtdb_bindrdataset(search->rbtdb, node, foundsig,
|
|
now, nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
}
|
|
dns__rbtdb_newref(search->rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
|
|
dns_name_copy(fname, foundname);
|
|
|
|
*nodep = node;
|
|
result = DNS_R_COVERINGNSEC;
|
|
} else {
|
|
result = ISC_R_NOTFOUND;
|
|
}
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
|
|
dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
|
|
dns_dbnode_t **nodep, dns_name_t *foundname,
|
|
dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
dns_rbtnode_t *node = NULL;
|
|
isc_result_t result;
|
|
rbtdb_search_t search;
|
|
bool cname_ok = true;
|
|
bool found_noqname = false;
|
|
bool all_negative = true;
|
|
bool empty_node;
|
|
isc_rwlock_t *lock = NULL;
|
|
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
dns_slabheader_t *header = NULL;
|
|
dns_slabheader_t *header_prev = NULL, *header_next = NULL;
|
|
dns_slabheader_t *found = NULL, *nsheader = NULL;
|
|
dns_slabheader_t *foundsig = NULL, *nssig = NULL, *cnamesig = NULL;
|
|
dns_slabheader_t *update = NULL, *updatesig = NULL;
|
|
dns_slabheader_t *nsecheader = NULL, *nsecsig = NULL;
|
|
dns_typepair_t sigtype, negtype;
|
|
|
|
UNUSED(version);
|
|
|
|
REQUIRE(VALID_RBTDB((dns_rbtdb_t *)db));
|
|
REQUIRE(version == NULL);
|
|
|
|
if (now == 0) {
|
|
now = isc_stdtime_now();
|
|
}
|
|
|
|
search = (rbtdb_search_t){
|
|
.rbtdb = (dns_rbtdb_t *)db,
|
|
.serial = 1,
|
|
.options = options,
|
|
.now = now,
|
|
};
|
|
dns_fixedname_init(&search.zonecut_name);
|
|
dns_rbtnodechain_init(&search.chain);
|
|
|
|
TREE_RDLOCK(&search.rbtdb->tree_lock, &tlocktype);
|
|
|
|
/*
|
|
* Search down from the root of the tree. If, while going down, we
|
|
* encounter a callback node, cache_zonecut_callback() will search the
|
|
* rdatasets at the zone cut for a DNAME rdataset.
|
|
*/
|
|
result = dns_rbt_findnode(search.rbtdb->tree, name, foundname, &node,
|
|
&search.chain, DNS_RBTFIND_EMPTYDATA,
|
|
cache_zonecut_callback, &search);
|
|
|
|
if (result == DNS_R_PARTIALMATCH) {
|
|
/*
|
|
* If dns_rbt_findnode discovered a covering DNAME skip
|
|
* looking for a covering NSEC.
|
|
*/
|
|
if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
|
|
(search.zonecut_header == NULL ||
|
|
search.zonecut_header->type != dns_rdatatype_dname))
|
|
{
|
|
result = find_coveringnsec(
|
|
&search, name, nodep, now, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (result == DNS_R_COVERINGNSEC) {
|
|
goto tree_exit;
|
|
}
|
|
}
|
|
if (search.zonecut != NULL) {
|
|
result = setup_delegation(
|
|
&search, nodep, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
goto tree_exit;
|
|
} else {
|
|
find_ns:
|
|
result = find_deepest_zonecut(
|
|
&search, node, nodep, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
goto tree_exit;
|
|
}
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
goto tree_exit;
|
|
}
|
|
|
|
/*
|
|
* Certain DNSSEC types are not subject to CNAME matching
|
|
* (RFC4035, section 2.5 and RFC3007).
|
|
*
|
|
* We don't check for RRSIG, because we don't store RRSIG records
|
|
* directly.
|
|
*/
|
|
if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) {
|
|
cname_ok = false;
|
|
}
|
|
|
|
/*
|
|
* We now go looking for rdata...
|
|
*/
|
|
|
|
lock = &(search.rbtdb->node_locks[node->locknum].lock);
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
|
|
/*
|
|
* These pointers need to be reset here in case we did
|
|
* 'goto find_ns' from somewhere below.
|
|
*/
|
|
found = NULL;
|
|
foundsig = NULL;
|
|
sigtype = DNS_TYPEPAIR_VALUE(dns_rdatatype_rrsig, type);
|
|
negtype = DNS_TYPEPAIR_VALUE(0, type);
|
|
nsheader = NULL;
|
|
nsecheader = NULL;
|
|
nssig = NULL;
|
|
nsecsig = NULL;
|
|
cnamesig = NULL;
|
|
empty_node = true;
|
|
header_prev = NULL;
|
|
for (header = node->data; header != NULL; header = header_next) {
|
|
header_next = header->next;
|
|
if (check_stale_header(node, header, &nlocktype, lock, &search,
|
|
&header_prev))
|
|
{
|
|
/* Do nothing. */
|
|
} else if (EXISTS(header) && !ANCIENT(header)) {
|
|
/*
|
|
* We now know that there is at least one active
|
|
* non-stale rdataset at this node.
|
|
*/
|
|
empty_node = false;
|
|
if (header->noqname != NULL &&
|
|
header->trust == dns_trust_secure)
|
|
{
|
|
found_noqname = true;
|
|
}
|
|
if (!NEGATIVE(header)) {
|
|
all_negative = false;
|
|
}
|
|
|
|
/*
|
|
* If we found a type we were looking for, remember
|
|
* it.
|
|
*/
|
|
if (header->type == type ||
|
|
(type == dns_rdatatype_any &&
|
|
DNS_TYPEPAIR_TYPE(header->type) != 0) ||
|
|
(cname_ok && header->type == dns_rdatatype_cname))
|
|
{
|
|
/*
|
|
* We've found the answer.
|
|
*/
|
|
found = header;
|
|
if (header->type == dns_rdatatype_cname &&
|
|
cname_ok && cnamesig != NULL)
|
|
{
|
|
/*
|
|
* If we've already got the
|
|
* CNAME RRSIG, use it.
|
|
*/
|
|
foundsig = cnamesig;
|
|
}
|
|
} else if (header->type == sigtype) {
|
|
/*
|
|
* We've found the RRSIG rdataset for our
|
|
* target type. Remember it.
|
|
*/
|
|
foundsig = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
|
|
header->type == negtype)
|
|
{
|
|
/*
|
|
* We've found a negative cache entry.
|
|
*/
|
|
found = header;
|
|
} else if (header->type == dns_rdatatype_ns) {
|
|
/*
|
|
* Remember a NS rdataset even if we're
|
|
* not specifically looking for it, because
|
|
* we might need it later.
|
|
*/
|
|
nsheader = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_SIGNS) {
|
|
/*
|
|
* If we need the NS rdataset, we'll also
|
|
* need its signature.
|
|
*/
|
|
nssig = header;
|
|
} else if (header->type == dns_rdatatype_nsec) {
|
|
nsecheader = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_SIGNSEC) {
|
|
nsecsig = header;
|
|
} else if (cname_ok &&
|
|
header->type == RBTDB_RDATATYPE_SIGCNAME)
|
|
{
|
|
/*
|
|
* If we get a CNAME match, we'll also need
|
|
* its signature.
|
|
*/
|
|
cnamesig = header;
|
|
}
|
|
header_prev = header;
|
|
} else {
|
|
header_prev = header;
|
|
}
|
|
}
|
|
|
|
if (empty_node) {
|
|
/*
|
|
* We have an exact match for the name, but there are no
|
|
* extant rdatasets. That means that this node doesn't
|
|
* meaningfully exist, and that we really have a partial match.
|
|
*/
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0) {
|
|
result = find_coveringnsec(
|
|
&search, name, nodep, now, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (result == DNS_R_COVERINGNSEC) {
|
|
goto tree_exit;
|
|
}
|
|
}
|
|
goto find_ns;
|
|
}
|
|
|
|
/*
|
|
* If we didn't find what we were looking for...
|
|
*/
|
|
if (found == NULL ||
|
|
(DNS_TRUST_ADDITIONAL(found->trust) &&
|
|
((options & DNS_DBFIND_ADDITIONALOK) == 0)) ||
|
|
(found->trust == dns_trust_glue &&
|
|
((options & DNS_DBFIND_GLUEOK) == 0)) ||
|
|
(DNS_TRUST_PENDING(found->trust) &&
|
|
((options & DNS_DBFIND_PENDINGOK) == 0)))
|
|
{
|
|
/*
|
|
* Return covering NODATA NSEC record.
|
|
*/
|
|
if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
|
|
nsecheader != NULL)
|
|
{
|
|
if (nodep != NULL) {
|
|
dns__rbtdb_newref(search.rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
*nodep = node;
|
|
}
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, nsecheader,
|
|
search.now, nlocktype,
|
|
rdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(nsecheader, search.now)) {
|
|
update = nsecheader;
|
|
}
|
|
if (nsecsig != NULL) {
|
|
dns__rbtdb_bindrdataset(
|
|
search.rbtdb, node, nsecsig, search.now,
|
|
nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(nsecsig, search.now)) {
|
|
updatesig = nsecsig;
|
|
}
|
|
}
|
|
result = DNS_R_COVERINGNSEC;
|
|
goto node_exit;
|
|
}
|
|
|
|
/*
|
|
* This name was from a wild card. Look for a covering NSEC.
|
|
*/
|
|
if (found == NULL && (found_noqname || all_negative) &&
|
|
(search.options & DNS_DBFIND_COVERINGNSEC) != 0)
|
|
{
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
result = find_coveringnsec(
|
|
&search, name, nodep, now, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (result == DNS_R_COVERINGNSEC) {
|
|
goto tree_exit;
|
|
}
|
|
goto find_ns;
|
|
}
|
|
|
|
/*
|
|
* If there is an NS rdataset at this node, then this is the
|
|
* deepest zone cut.
|
|
*/
|
|
if (nsheader != NULL) {
|
|
if (nodep != NULL) {
|
|
dns__rbtdb_newref(search.rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
*nodep = node;
|
|
}
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, nsheader,
|
|
search.now, nlocktype,
|
|
rdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(nsheader, search.now)) {
|
|
update = nsheader;
|
|
}
|
|
if (nssig != NULL) {
|
|
dns__rbtdb_bindrdataset(
|
|
search.rbtdb, node, nssig, search.now,
|
|
nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(nssig, search.now)) {
|
|
updatesig = nssig;
|
|
}
|
|
}
|
|
result = DNS_R_DELEGATION;
|
|
goto node_exit;
|
|
}
|
|
|
|
/*
|
|
* Go find the deepest zone cut.
|
|
*/
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
goto find_ns;
|
|
}
|
|
|
|
/*
|
|
* We found what we were looking for, or we found a CNAME.
|
|
*/
|
|
|
|
if (nodep != NULL) {
|
|
dns__rbtdb_newref(search.rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
*nodep = node;
|
|
}
|
|
|
|
if (NEGATIVE(found)) {
|
|
/*
|
|
* We found a negative cache entry.
|
|
*/
|
|
if (NXDOMAIN(found)) {
|
|
result = DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
result = DNS_R_NCACHENXRRSET;
|
|
}
|
|
} else if (type != found->type && type != dns_rdatatype_any &&
|
|
found->type == dns_rdatatype_cname)
|
|
{
|
|
/*
|
|
* We weren't doing an ANY query and we found a CNAME instead
|
|
* of the type we were looking for, so we need to indicate
|
|
* that result to the caller.
|
|
*/
|
|
result = DNS_R_CNAME;
|
|
} else {
|
|
/*
|
|
* An ordinary successful query!
|
|
*/
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (type != dns_rdatatype_any || result == DNS_R_NCACHENXDOMAIN ||
|
|
result == DNS_R_NCACHENXRRSET)
|
|
{
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, found, search.now,
|
|
nlocktype, rdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(found, search.now)) {
|
|
update = found;
|
|
}
|
|
if (!NEGATIVE(found) && foundsig != NULL) {
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, foundsig,
|
|
search.now, nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
if (need_headerupdate(foundsig, search.now)) {
|
|
updatesig = foundsig;
|
|
}
|
|
}
|
|
}
|
|
|
|
node_exit:
|
|
if ((update != NULL || updatesig != NULL) &&
|
|
nlocktype != isc_rwlocktype_write)
|
|
{
|
|
NODE_FORCEUPGRADE(lock, &nlocktype);
|
|
POST(nlocktype);
|
|
}
|
|
if (update != NULL && need_headerupdate(update, search.now)) {
|
|
update_header(search.rbtdb, update, search.now);
|
|
}
|
|
if (updatesig != NULL && need_headerupdate(updatesig, search.now)) {
|
|
update_header(search.rbtdb, updatesig, search.now);
|
|
}
|
|
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
|
|
tree_exit:
|
|
TREE_UNLOCK(&search.rbtdb->tree_lock, &tlocktype);
|
|
|
|
/*
|
|
* If we found a zonecut but aren't going to use it, we have to
|
|
* let go of it.
|
|
*/
|
|
if (search.need_cleanup) {
|
|
node = search.zonecut;
|
|
INSIST(node != NULL);
|
|
lock = &(search.rbtdb->node_locks[node->locknum].lock);
|
|
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
dns__rbtdb_decref(search.rbtdb, node, 0, &nlocktype, &tlocktype,
|
|
true, false DNS__DB_FLARG_PASS);
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
INSIST(tlocktype == isc_rwlocktype_none);
|
|
}
|
|
|
|
dns_rbtnodechain_reset(&search.chain);
|
|
|
|
update_cachestats(search.rbtdb, result);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
|
|
isc_stdtime_t now, dns_dbnode_t **nodep,
|
|
dns_name_t *foundname, dns_name_t *dcname,
|
|
dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
dns_rbtnode_t *node = NULL;
|
|
isc_rwlock_t *lock = NULL;
|
|
isc_result_t result;
|
|
rbtdb_search_t search;
|
|
dns_slabheader_t *header = NULL;
|
|
dns_slabheader_t *header_prev = NULL, *header_next = NULL;
|
|
dns_slabheader_t *found = NULL, *foundsig = NULL;
|
|
unsigned int rbtoptions = DNS_RBTFIND_EMPTYDATA;
|
|
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
bool dcnull = (dcname == NULL);
|
|
|
|
REQUIRE(VALID_RBTDB((dns_rbtdb_t *)db));
|
|
|
|
if (now == 0) {
|
|
now = isc_stdtime_now();
|
|
}
|
|
|
|
search = (rbtdb_search_t){
|
|
.rbtdb = (dns_rbtdb_t *)db,
|
|
.serial = 1,
|
|
.options = options,
|
|
.now = now,
|
|
};
|
|
dns_fixedname_init(&search.zonecut_name);
|
|
dns_rbtnodechain_init(&search.chain);
|
|
|
|
if (dcnull) {
|
|
dcname = foundname;
|
|
}
|
|
|
|
if ((options & DNS_DBFIND_NOEXACT) != 0) {
|
|
rbtoptions |= DNS_RBTFIND_NOEXACT;
|
|
}
|
|
|
|
TREE_RDLOCK(&search.rbtdb->tree_lock, &tlocktype);
|
|
|
|
/*
|
|
* Search down from the root of the tree.
|
|
*/
|
|
result = dns_rbt_findnode(search.rbtdb->tree, name, dcname, &node,
|
|
&search.chain, rbtoptions, NULL, &search);
|
|
|
|
if (result == DNS_R_PARTIALMATCH) {
|
|
result = find_deepest_zonecut(&search, node, nodep, foundname,
|
|
rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
goto tree_exit;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
goto tree_exit;
|
|
} else if (!dcnull) {
|
|
dns_name_copy(dcname, foundname);
|
|
}
|
|
|
|
/*
|
|
* We now go looking for an NS rdataset at the node.
|
|
*/
|
|
|
|
lock = &(search.rbtdb->node_locks[node->locknum].lock);
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
|
|
for (header = node->data; header != NULL; header = header_next) {
|
|
header_next = header->next;
|
|
if (check_stale_header(node, header, &nlocktype, lock, &search,
|
|
&header_prev))
|
|
{
|
|
/*
|
|
* The function dns_rbt_findnode found us the a matching
|
|
* node for 'name' and stored the result in 'dcname'.
|
|
* This is the deepest known zonecut in our database.
|
|
* However, this node may be stale and if serve-stale
|
|
* is not enabled (in other words 'stale-answer-enable'
|
|
* is set to no), this node may not be used as a
|
|
* zonecut we know about. If so, find the deepest
|
|
* zonecut from this node up and return that instead.
|
|
*/
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
result = find_deepest_zonecut(
|
|
&search, node, nodep, foundname, rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
dns_name_copy(foundname, dcname);
|
|
goto tree_exit;
|
|
} else if (EXISTS(header) && !ANCIENT(header)) {
|
|
/*
|
|
* If we found a type we were looking for, remember
|
|
* it.
|
|
*/
|
|
if (header->type == dns_rdatatype_ns) {
|
|
/*
|
|
* Remember a NS rdataset even if we're
|
|
* not specifically looking for it, because
|
|
* we might need it later.
|
|
*/
|
|
found = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_SIGNS) {
|
|
/*
|
|
* If we need the NS rdataset, we'll also
|
|
* need its signature.
|
|
*/
|
|
foundsig = header;
|
|
}
|
|
header_prev = header;
|
|
} else {
|
|
header_prev = header;
|
|
}
|
|
}
|
|
|
|
if (found == NULL) {
|
|
/*
|
|
* No NS records here.
|
|
*/
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
result = find_deepest_zonecut(&search, node, nodep, foundname,
|
|
rdataset,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
goto tree_exit;
|
|
}
|
|
|
|
if (nodep != NULL) {
|
|
dns__rbtdb_newref(search.rbtdb, node,
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
*nodep = node;
|
|
}
|
|
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, found, search.now,
|
|
nlocktype, rdataset DNS__DB_FLARG_PASS);
|
|
if (foundsig != NULL) {
|
|
dns__rbtdb_bindrdataset(search.rbtdb, node, foundsig,
|
|
search.now, nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
}
|
|
|
|
if (need_headerupdate(found, search.now) ||
|
|
(foundsig != NULL && need_headerupdate(foundsig, search.now)))
|
|
{
|
|
if (nlocktype != isc_rwlocktype_write) {
|
|
NODE_FORCEUPGRADE(lock, &nlocktype);
|
|
POST(nlocktype);
|
|
}
|
|
if (need_headerupdate(found, search.now)) {
|
|
update_header(search.rbtdb, found, search.now);
|
|
}
|
|
if (foundsig != NULL && need_headerupdate(foundsig, search.now))
|
|
{
|
|
update_header(search.rbtdb, foundsig, search.now);
|
|
}
|
|
}
|
|
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
|
|
tree_exit:
|
|
TREE_UNLOCK(&search.rbtdb->tree_lock, &tlocktype);
|
|
|
|
INSIST(!search.need_cleanup);
|
|
|
|
dns_rbtnodechain_reset(&search.chain);
|
|
|
|
if (result == DNS_R_DELEGATION) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
|
dns_rdatatype_t type, dns_rdatatype_t covers,
|
|
isc_stdtime_t now, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
|
|
dns_slabheader_t *header = NULL, *header_next = NULL;
|
|
dns_slabheader_t *found = NULL, *foundsig = NULL;
|
|
dns_typepair_t matchtype, sigmatchtype, negtype;
|
|
isc_result_t result;
|
|
isc_rwlock_t *lock = NULL;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(type != dns_rdatatype_any);
|
|
|
|
UNUSED(version);
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
if (now == 0) {
|
|
now = isc_stdtime_now();
|
|
}
|
|
|
|
lock = &rbtdb->node_locks[rbtnode->locknum].lock;
|
|
NODE_RDLOCK(lock, &nlocktype);
|
|
|
|
matchtype = DNS_TYPEPAIR_VALUE(type, covers);
|
|
negtype = DNS_TYPEPAIR_VALUE(0, type);
|
|
if (covers == 0) {
|
|
sigmatchtype = DNS_TYPEPAIR_VALUE(dns_rdatatype_rrsig, type);
|
|
} else {
|
|
sigmatchtype = 0;
|
|
}
|
|
|
|
for (header = rbtnode->data; header != NULL; header = header_next) {
|
|
header_next = header->next;
|
|
if (!ACTIVE(header, now)) {
|
|
if ((header->ttl + STALE_TTL(header, rbtdb) <
|
|
now - RBTDB_VIRTUAL) &&
|
|
(nlocktype == isc_rwlocktype_write ||
|
|
NODE_TRYUPGRADE(lock, &nlocktype) ==
|
|
ISC_R_SUCCESS))
|
|
{
|
|
/*
|
|
* We update the node's status only when we
|
|
* can get write access.
|
|
*
|
|
* We don't check if refcurrent(rbtnode) == 0
|
|
* and try to free like we do in cache_find(),
|
|
* because refcurrent(rbtnode) must be
|
|
* non-zero. This is so because 'node' is an
|
|
* argument to the function.
|
|
*/
|
|
dns__rbtdb_mark(header,
|
|
DNS_SLABHEADERATTR_ANCIENT);
|
|
HEADER_NODE(header)->dirty = 1;
|
|
}
|
|
} else if (EXISTS(header) && !ANCIENT(header)) {
|
|
if (header->type == matchtype) {
|
|
found = header;
|
|
} else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
|
|
header->type == negtype)
|
|
{
|
|
found = header;
|
|
} else if (header->type == sigmatchtype) {
|
|
foundsig = header;
|
|
}
|
|
}
|
|
}
|
|
if (found != NULL) {
|
|
dns__rbtdb_bindrdataset(rbtdb, rbtnode, found, now, nlocktype,
|
|
rdataset DNS__DB_FLARG_PASS);
|
|
if (!NEGATIVE(found) && foundsig != NULL) {
|
|
dns__rbtdb_bindrdataset(rbtdb, rbtnode, foundsig, now,
|
|
nlocktype,
|
|
sigrdataset DNS__DB_FLARG_PASS);
|
|
}
|
|
}
|
|
|
|
NODE_UNLOCK(lock, &nlocktype);
|
|
|
|
if (found == NULL) {
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
if (NEGATIVE(found)) {
|
|
/*
|
|
* We found a negative cache entry.
|
|
*/
|
|
if (NXDOMAIN(found)) {
|
|
result = DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
result = DNS_R_NCACHENXRRSET;
|
|
}
|
|
}
|
|
|
|
update_cachestats(rbtdb, result);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static size_t
|
|
hashsize(dns_db_t *db) {
|
|
dns_rbtdb_t *rbtdb = NULL;
|
|
size_t size;
|
|
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
|
|
|
|
rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
|
|
TREE_RDLOCK(&rbtdb->tree_lock, &tlocktype);
|
|
size = dns_rbt_hashsize(rbtdb->tree);
|
|
TREE_UNLOCK(&rbtdb->tree_lock, &tlocktype);
|
|
|
|
return (size);
|
|
}
|
|
|
|
static isc_result_t
|
|
setcachestats(dns_db_t *db, isc_stats_t *stats) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
|
|
REQUIRE(stats != NULL);
|
|
|
|
isc_stats_attach(stats, &rbtdb->cachestats);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static dns_stats_t *
|
|
getrrsetstats(dns_db_t *db) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb)); /* current restriction */
|
|
|
|
return (rbtdb->rrsetstats);
|
|
}
|
|
|
|
static isc_result_t
|
|
setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb));
|
|
|
|
/* currently no bounds checking. 0 means disable. */
|
|
rbtdb->common.serve_stale_ttl = ttl;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb));
|
|
|
|
*ttl = rbtdb->common.serve_stale_ttl;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
setservestalerefresh(dns_db_t *db, uint32_t interval) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb));
|
|
|
|
/* currently no bounds checking. 0 means disable. */
|
|
rbtdb->serve_stale_refresh = interval;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
getservestalerefresh(dns_db_t *db, uint32_t *interval) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
|
|
REQUIRE(VALID_RBTDB(rbtdb));
|
|
REQUIRE(IS_CACHE(rbtdb));
|
|
|
|
*interval = rbtdb->serve_stale_refresh;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
expiredata(dns_db_t *db, dns_dbnode_t *node, void *data) {
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
|
|
dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node;
|
|
dns_slabheader_t *header = data;
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
|
|
|
|
NODE_WRLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, &nlocktype);
|
|
dns__cachedb_expireheader(header, &tlocktype,
|
|
dns_expire_flush DNS__DB_FLARG_PASS);
|
|
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, &nlocktype);
|
|
INSIST(tlocktype == isc_rwlocktype_none);
|
|
}
|
|
|
|
dns_dbmethods_t dns__rbtdb_cachemethods = {
|
|
.destroy = dns__rbtdb_destroy,
|
|
.currentversion = dns__rbtdb_currentversion,
|
|
.newversion = dns__rbtdb_newversion,
|
|
.attachversion = dns__rbtdb_attachversion,
|
|
.closeversion = dns__rbtdb_closeversion,
|
|
.findnode = dns__rbtdb_findnode,
|
|
.find = cache_find,
|
|
.findzonecut = cache_findzonecut,
|
|
.attachnode = dns__rbtdb_attachnode,
|
|
.detachnode = dns__rbtdb_detachnode,
|
|
.createiterator = dns__rbtdb_createiterator,
|
|
.findrdataset = cache_findrdataset,
|
|
.allrdatasets = dns__rbtdb_allrdatasets,
|
|
.addrdataset = dns__rbtdb_addrdataset,
|
|
.subtractrdataset = dns__rbtdb_subtractrdataset,
|
|
.deleterdataset = dns__rbtdb_deleterdataset,
|
|
.nodecount = dns__rbtdb_nodecount,
|
|
.setloop = dns__rbtdb_setloop,
|
|
.getoriginnode = dns__rbtdb_getoriginnode,
|
|
.getrrsetstats = getrrsetstats,
|
|
.setcachestats = setcachestats,
|
|
.hashsize = hashsize,
|
|
.setservestalettl = setservestalettl,
|
|
.getservestalettl = getservestalettl,
|
|
.setservestalerefresh = setservestalerefresh,
|
|
.getservestalerefresh = getservestalerefresh,
|
|
.locknode = dns__rbtdb_locknode,
|
|
.unlocknode = dns__rbtdb_unlocknode,
|
|
.expiredata = expiredata,
|
|
.deletedata = dns__rbtdb_deletedata,
|
|
};
|
|
|
|
/*
|
|
* Caller must hold the node (write) lock.
|
|
*/
|
|
void
|
|
dns__cachedb_expireheader(dns_slabheader_t *header,
|
|
isc_rwlocktype_t *tlocktypep,
|
|
dns_expire_t reason DNS__DB_FLARG) {
|
|
dns__rbtdb_setttl(header, 0);
|
|
dns__rbtdb_mark(header, DNS_SLABHEADERATTR_ANCIENT);
|
|
HEADER_NODE(header)->dirty = 1;
|
|
|
|
if (isc_refcount_current(&HEADER_NODE(header)->references) == 0) {
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_write;
|
|
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)header->db;
|
|
|
|
/*
|
|
* If no one else is using the node, we can clean it up now.
|
|
* We first need to gain a new reference to the node to meet a
|
|
* requirement of dns__rbtdb_decref().
|
|
*/
|
|
dns__rbtdb_newref(rbtdb, HEADER_NODE(header),
|
|
nlocktype DNS__DB_FLARG_PASS);
|
|
dns__rbtdb_decref(rbtdb, HEADER_NODE(header), 0, &nlocktype,
|
|
tlocktypep, true, false DNS__DB_FLARG_PASS);
|
|
|
|
if (rbtdb->cachestats == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (reason) {
|
|
case dns_expire_ttl:
|
|
isc_stats_increment(rbtdb->cachestats,
|
|
dns_cachestatscounter_deletettl);
|
|
break;
|
|
case dns_expire_lru:
|
|
isc_stats_increment(rbtdb->cachestats,
|
|
dns_cachestatscounter_deletelru);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
rdataset_size(dns_slabheader_t *header) {
|
|
if (!NONEXISTENT(header)) {
|
|
return (dns_rdataslab_size((unsigned char *)header,
|
|
sizeof(*header)));
|
|
}
|
|
|
|
return (sizeof(*header));
|
|
}
|
|
|
|
static size_t
|
|
expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum,
|
|
isc_rwlocktype_t *tlocktypep,
|
|
size_t purgesize DNS__DB_FLARG) {
|
|
dns_slabheader_t *header = NULL, *header_prev = NULL;
|
|
size_t purged = 0;
|
|
|
|
for (header = ISC_LIST_TAIL(rbtdb->lru[locknum]);
|
|
header != NULL && header->last_used <= rbtdb->last_used &&
|
|
purged <= purgesize;
|
|
header = header_prev)
|
|
{
|
|
size_t header_size = rdataset_size(header);
|
|
header_prev = ISC_LIST_PREV(header, link);
|
|
|
|
/*
|
|
* Unlink the entry at this point to avoid checking it
|
|
* again even if it's currently used someone else and
|
|
* cannot be purged at this moment. This entry won't be
|
|
* referenced any more (so unlinking is safe) since the
|
|
* TTL was reset to 0.
|
|
*/
|
|
ISC_LIST_UNLINK(rbtdb->lru[locknum], header, link);
|
|
dns__cachedb_expireheader(header, tlocktypep,
|
|
dns_expire_lru DNS__DB_FLARG_PASS);
|
|
purged += header_size;
|
|
}
|
|
|
|
return (purged);
|
|
}
|
|
|
|
/*%
|
|
* Purge some expired and/or stale (i.e. unused for some period) cache entries
|
|
* due to an overmem condition. To recover from this condition quickly,
|
|
* we clean up entries up to the size of newly added rdata that triggered
|
|
* the overmem; this is accessible via newheader.
|
|
*
|
|
* The LRU lists tails are processed in LRU order to the nearest second.
|
|
*
|
|
* A write lock on the tree must be held.
|
|
*/
|
|
void
|
|
dns__cachedb_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader,
|
|
isc_rwlocktype_t *tlocktypep DNS__DB_FLARG) {
|
|
uint32_t locknum_start = rbtdb->lru_sweep++ % rbtdb->node_lock_count;
|
|
uint32_t locknum = locknum_start;
|
|
/* Size of added data, possible node and possible ENT node. */
|
|
size_t purgesize = rdataset_size(newheader) +
|
|
2 * dns__rbtnode_getsize(HEADER_NODE(newheader));
|
|
size_t purged = 0;
|
|
isc_stdtime_t min_last_used = 0;
|
|
size_t max_passes = 8;
|
|
|
|
again:
|
|
do {
|
|
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
|
|
NODE_WRLOCK(&rbtdb->node_locks[locknum].lock, &nlocktype);
|
|
|
|
purged += expire_lru_headers(rbtdb, locknum, tlocktypep,
|
|
purgesize -
|
|
purged DNS__DB_FLARG_PASS);
|
|
|
|
/*
|
|
* Work out the oldest remaining last_used values of the list
|
|
* tails as we walk across the array of lru lists.
|
|
*/
|
|
dns_slabheader_t *header = ISC_LIST_TAIL(rbtdb->lru[locknum]);
|
|
if (header != NULL &&
|
|
(min_last_used == 0 || header->last_used < min_last_used))
|
|
{
|
|
min_last_used = header->last_used;
|
|
}
|
|
NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, &nlocktype);
|
|
locknum = (locknum + 1) % rbtdb->node_lock_count;
|
|
} while (locknum != locknum_start && purged <= purgesize);
|
|
|
|
/*
|
|
* Update rbtdb->last_used if we have walked all the list tails and have
|
|
* not freed the required amount of memory.
|
|
*/
|
|
if (purged < purgesize) {
|
|
if (min_last_used != 0) {
|
|
rbtdb->last_used = min_last_used;
|
|
if (max_passes-- > 0) {
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
}
|