mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 10:10:06 +00:00
The DNS Flag Day 2020 aims to remove the IP fragmentation problem from the UDP DNS communication. In this commit, we implement the required changes and simplify the logic for picking the EDNS Buffer Size. 1. The defaults for `edns-udp-size`, `max-udp-size` and `nocookie-udp-size` have been changed to `1232` (the value picked by DNS Flag Day 2020). 2. The probing heuristics that would try 512->4096->1432->1232 buffer sizes has been removed and the resolver will always use just the `edns-udp-size` value. 3. Instead of just disabling the PMTUD mechanism on the UDP sockets, we now set IP_DONTFRAG (IPV6_DONTFRAG) flag. That means that the UDP packets won't get ever fragmented. If the ICMP packets are lost the UDP will just timeout and eventually be retried over TCP.
4842 lines
120 KiB
C
4842 lines
120 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* 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
|
|
*
|
|
* \note
|
|
* In finds, if task == NULL, no events will be generated, and no events
|
|
* have been sent. If task != NULL but taskaction == NULL, an event has been
|
|
* posted but not yet freed. If neither are NULL, no event was posted.
|
|
*
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <isc/mutexblock.h>
|
|
#include <isc/netaddr.h>
|
|
#include <isc/print.h>
|
|
#include <isc/random.h>
|
|
#include <isc/stats.h>
|
|
#include <isc/string.h> /* Required for HP/UX (and others?) */
|
|
#include <isc/task.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/adb.h>
|
|
#include <dns/db.h>
|
|
#include <dns/events.h>
|
|
#include <dns/log.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/resolver.h>
|
|
#include <dns/result.h>
|
|
#include <dns/stats.h>
|
|
|
|
#define DNS_ADB_MAGIC ISC_MAGIC('D', 'a', 'd', 'b')
|
|
#define DNS_ADB_VALID(x) ISC_MAGIC_VALID(x, DNS_ADB_MAGIC)
|
|
#define DNS_ADBNAME_MAGIC ISC_MAGIC('a', 'd', 'b', 'N')
|
|
#define DNS_ADBNAME_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAME_MAGIC)
|
|
#define DNS_ADBNAMEHOOK_MAGIC ISC_MAGIC('a', 'd', 'N', 'H')
|
|
#define DNS_ADBNAMEHOOK_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAMEHOOK_MAGIC)
|
|
#define DNS_ADBLAMEINFO_MAGIC ISC_MAGIC('a', 'd', 'b', 'Z')
|
|
#define DNS_ADBLAMEINFO_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBLAMEINFO_MAGIC)
|
|
#define DNS_ADBENTRY_MAGIC ISC_MAGIC('a', 'd', 'b', 'E')
|
|
#define DNS_ADBENTRY_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBENTRY_MAGIC)
|
|
#define DNS_ADBFETCH_MAGIC ISC_MAGIC('a', 'd', 'F', '4')
|
|
#define DNS_ADBFETCH_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH_MAGIC)
|
|
#define DNS_ADBFETCH6_MAGIC ISC_MAGIC('a', 'd', 'F', '6')
|
|
#define DNS_ADBFETCH6_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH6_MAGIC)
|
|
|
|
/*!
|
|
* For type 3 negative cache entries, we will remember that the address is
|
|
* broken for this long. XXXMLG This is also used for actual addresses, too.
|
|
* The intent is to keep us from constantly asking about A/AAAA records
|
|
* if the zone has extremely low TTLs.
|
|
*/
|
|
#define ADB_CACHE_MINIMUM 10 /*%< seconds */
|
|
#define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */
|
|
#define ADB_ENTRY_WINDOW 1800 /*%< seconds */
|
|
|
|
/*%
|
|
* The period in seconds after which an ADB name entry is regarded as stale
|
|
* and forced to be cleaned up.
|
|
* TODO: This should probably be configurable at run-time.
|
|
*/
|
|
#ifndef ADB_STALE_MARGIN
|
|
#define ADB_STALE_MARGIN 1800
|
|
#endif /* ifndef ADB_STALE_MARGIN */
|
|
|
|
#define FREE_ITEMS 64 /*%< free count for memory pools */
|
|
#define FILL_COUNT 16 /*%< fill count for memory pools */
|
|
|
|
#define DNS_ADB_INVALIDBUCKET (-1) /*%< invalid bucket address */
|
|
|
|
#define DNS_ADB_MINADBSIZE (1024U * 1024U) /*%< 1 Megabyte */
|
|
|
|
typedef ISC_LIST(dns_adbname_t) dns_adbnamelist_t;
|
|
typedef struct dns_adbnamehook dns_adbnamehook_t;
|
|
typedef ISC_LIST(dns_adbnamehook_t) dns_adbnamehooklist_t;
|
|
typedef struct dns_adblameinfo dns_adblameinfo_t;
|
|
typedef ISC_LIST(dns_adbentry_t) dns_adbentrylist_t;
|
|
typedef struct dns_adbfetch dns_adbfetch_t;
|
|
typedef struct dns_adbfetch6 dns_adbfetch6_t;
|
|
|
|
/*% dns adb structure */
|
|
struct dns_adb {
|
|
unsigned int magic;
|
|
|
|
isc_mutex_t lock;
|
|
isc_mutex_t reflock; /*%< Covers irefcnt, erefcnt */
|
|
isc_mutex_t overmemlock; /*%< Covers overmem */
|
|
isc_mem_t *mctx;
|
|
dns_view_t *view;
|
|
|
|
isc_taskmgr_t *taskmgr;
|
|
isc_task_t *task;
|
|
isc_task_t *excl;
|
|
|
|
isc_interval_t tick_interval;
|
|
int next_cleanbucket;
|
|
|
|
unsigned int irefcnt;
|
|
unsigned int erefcnt;
|
|
|
|
isc_mutex_t mplock;
|
|
isc_mempool_t *nmp; /*%< dns_adbname_t */
|
|
isc_mempool_t *nhmp; /*%< dns_adbnamehook_t */
|
|
isc_mempool_t *limp; /*%< dns_adblameinfo_t */
|
|
isc_mempool_t *emp; /*%< dns_adbentry_t */
|
|
isc_mempool_t *ahmp; /*%< dns_adbfind_t */
|
|
isc_mempool_t *aimp; /*%< dns_adbaddrinfo_t */
|
|
isc_mempool_t *afmp; /*%< dns_adbfetch_t */
|
|
|
|
/*!
|
|
* Bucketized locks and lists for names.
|
|
*
|
|
* XXXRTH Have a per-bucket structure that contains all of these?
|
|
*/
|
|
unsigned int nnames;
|
|
isc_mutex_t namescntlock;
|
|
unsigned int namescnt;
|
|
dns_adbnamelist_t *names;
|
|
dns_adbnamelist_t *deadnames;
|
|
isc_mutex_t *namelocks;
|
|
bool *name_sd;
|
|
unsigned int *name_refcnt;
|
|
|
|
/*!
|
|
* Bucketized locks and lists for entries.
|
|
*
|
|
* XXXRTH Have a per-bucket structure that contains all of these?
|
|
*/
|
|
unsigned int nentries;
|
|
isc_mutex_t entriescntlock;
|
|
unsigned int entriescnt;
|
|
dns_adbentrylist_t *entries;
|
|
dns_adbentrylist_t *deadentries;
|
|
isc_mutex_t *entrylocks;
|
|
bool *entry_sd; /*%< shutting down */
|
|
unsigned int *entry_refcnt;
|
|
|
|
isc_event_t cevent;
|
|
bool cevent_out;
|
|
bool shutting_down;
|
|
isc_eventlist_t whenshutdown;
|
|
isc_event_t growentries;
|
|
bool growentries_sent;
|
|
isc_event_t grownames;
|
|
bool grownames_sent;
|
|
|
|
uint32_t quota;
|
|
uint32_t atr_freq;
|
|
double atr_low;
|
|
double atr_high;
|
|
double atr_discount;
|
|
};
|
|
|
|
/*
|
|
* XXXMLG Document these structures.
|
|
*/
|
|
|
|
/*% dns_adbname structure */
|
|
struct dns_adbname {
|
|
unsigned int magic;
|
|
dns_name_t name;
|
|
dns_adb_t *adb;
|
|
unsigned int partial_result;
|
|
unsigned int flags;
|
|
int lock_bucket;
|
|
dns_name_t target;
|
|
isc_stdtime_t expire_target;
|
|
isc_stdtime_t expire_v4;
|
|
isc_stdtime_t expire_v6;
|
|
unsigned int chains;
|
|
dns_adbnamehooklist_t v4;
|
|
dns_adbnamehooklist_t v6;
|
|
dns_adbfetch_t *fetch_a;
|
|
dns_adbfetch_t *fetch_aaaa;
|
|
unsigned int fetch_err;
|
|
unsigned int fetch6_err;
|
|
dns_adbfindlist_t finds;
|
|
/* for LRU-based management */
|
|
isc_stdtime_t last_used;
|
|
|
|
ISC_LINK(dns_adbname_t) plink;
|
|
};
|
|
|
|
/*% The adbfetch structure */
|
|
struct dns_adbfetch {
|
|
unsigned int magic;
|
|
dns_fetch_t *fetch;
|
|
dns_rdataset_t rdataset;
|
|
unsigned int depth;
|
|
};
|
|
|
|
/*%
|
|
* This is a small widget that dangles off a dns_adbname_t. It contains a
|
|
* pointer to the address information about this host, and a link to the next
|
|
* namehook that will contain the next address this host has.
|
|
*/
|
|
struct dns_adbnamehook {
|
|
unsigned int magic;
|
|
dns_adbentry_t *entry;
|
|
ISC_LINK(dns_adbnamehook_t) plink;
|
|
};
|
|
|
|
/*%
|
|
* This is a small widget that holds qname-specific information about an
|
|
* address. Currently limited to lameness, but could just as easily be
|
|
* extended to other types of information about zones.
|
|
*/
|
|
struct dns_adblameinfo {
|
|
unsigned int magic;
|
|
|
|
dns_name_t qname;
|
|
dns_rdatatype_t qtype;
|
|
isc_stdtime_t lame_timer;
|
|
|
|
ISC_LINK(dns_adblameinfo_t) plink;
|
|
};
|
|
|
|
/*%
|
|
* An address entry. It holds quite a bit of information about addresses,
|
|
* including edns state (in "flags"), rtt, and of course the address of
|
|
* the host.
|
|
*/
|
|
struct dns_adbentry {
|
|
unsigned int magic;
|
|
|
|
int lock_bucket;
|
|
unsigned int refcnt;
|
|
unsigned int nh;
|
|
|
|
unsigned int flags;
|
|
unsigned int srtt;
|
|
uint16_t udpsize;
|
|
unsigned int completed;
|
|
unsigned int timeouts;
|
|
unsigned char plain;
|
|
unsigned char plainto;
|
|
unsigned char edns;
|
|
unsigned char ednsto;
|
|
|
|
uint8_t mode;
|
|
atomic_uint_fast32_t quota;
|
|
atomic_uint_fast32_t active;
|
|
double atr;
|
|
|
|
isc_sockaddr_t sockaddr;
|
|
unsigned char *cookie;
|
|
uint16_t cookielen;
|
|
|
|
isc_stdtime_t expires;
|
|
isc_stdtime_t lastage;
|
|
/*%<
|
|
* A nonzero 'expires' field indicates that the entry should
|
|
* persist until that time. This allows entries found
|
|
* using dns_adb_findaddrinfo() to persist for a limited time
|
|
* even though they are not necessarily associated with a
|
|
* name.
|
|
*/
|
|
|
|
ISC_LIST(dns_adblameinfo_t) lameinfo;
|
|
ISC_LINK(dns_adbentry_t) plink;
|
|
};
|
|
|
|
/*
|
|
* Internal functions (and prototypes).
|
|
*/
|
|
static inline dns_adbname_t *
|
|
new_adbname(dns_adb_t *, const dns_name_t *);
|
|
static inline void
|
|
free_adbname(dns_adb_t *, dns_adbname_t **);
|
|
static inline dns_adbnamehook_t *
|
|
new_adbnamehook(dns_adb_t *, dns_adbentry_t *);
|
|
static inline void
|
|
free_adbnamehook(dns_adb_t *, dns_adbnamehook_t **);
|
|
static inline dns_adblameinfo_t *
|
|
new_adblameinfo(dns_adb_t *, const dns_name_t *, dns_rdatatype_t);
|
|
static inline void
|
|
free_adblameinfo(dns_adb_t *, dns_adblameinfo_t **);
|
|
static inline dns_adbentry_t *
|
|
new_adbentry(dns_adb_t *);
|
|
static inline void
|
|
free_adbentry(dns_adb_t *, dns_adbentry_t **);
|
|
static inline dns_adbfind_t *
|
|
new_adbfind(dns_adb_t *);
|
|
static inline bool
|
|
free_adbfind(dns_adb_t *, dns_adbfind_t **);
|
|
static inline dns_adbaddrinfo_t *
|
|
new_adbaddrinfo(dns_adb_t *, dns_adbentry_t *, in_port_t);
|
|
static inline dns_adbfetch_t *
|
|
new_adbfetch(dns_adb_t *);
|
|
static inline void
|
|
free_adbfetch(dns_adb_t *, dns_adbfetch_t **);
|
|
static inline dns_adbname_t *
|
|
find_name_and_lock(dns_adb_t *, const dns_name_t *, unsigned int, int *);
|
|
static inline dns_adbentry_t *
|
|
find_entry_and_lock(dns_adb_t *, const isc_sockaddr_t *, int *, isc_stdtime_t);
|
|
static void
|
|
dump_adb(dns_adb_t *, FILE *, bool debug, isc_stdtime_t);
|
|
static void
|
|
print_dns_name(FILE *, const dns_name_t *);
|
|
static void
|
|
print_namehook_list(FILE *, const char *legend, dns_adb_t *adb,
|
|
dns_adbnamehooklist_t *list, bool debug, isc_stdtime_t now);
|
|
static void
|
|
print_find_list(FILE *, dns_adbname_t *);
|
|
static void
|
|
print_fetch_list(FILE *, dns_adbname_t *);
|
|
static inline bool
|
|
dec_adb_irefcnt(dns_adb_t *);
|
|
static inline void
|
|
inc_adb_irefcnt(dns_adb_t *);
|
|
static inline void
|
|
inc_adb_erefcnt(dns_adb_t *);
|
|
static inline void
|
|
inc_entry_refcnt(dns_adb_t *, dns_adbentry_t *, bool);
|
|
static inline bool
|
|
dec_entry_refcnt(dns_adb_t *, bool, dns_adbentry_t *, bool);
|
|
static inline void
|
|
violate_locking_hierarchy(isc_mutex_t *, isc_mutex_t *);
|
|
static bool
|
|
clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *);
|
|
static void
|
|
clean_target(dns_adb_t *, dns_name_t *);
|
|
static void
|
|
clean_finds_at_name(dns_adbname_t *, isc_eventtype_t, unsigned int);
|
|
static bool
|
|
check_expire_namehooks(dns_adbname_t *, isc_stdtime_t);
|
|
static bool
|
|
check_expire_entry(dns_adb_t *, dns_adbentry_t **, isc_stdtime_t);
|
|
static void
|
|
cancel_fetches_at_name(dns_adbname_t *);
|
|
static isc_result_t
|
|
dbfind_name(dns_adbname_t *, isc_stdtime_t, dns_rdatatype_t);
|
|
static isc_result_t
|
|
fetch_name(dns_adbname_t *, bool, unsigned int, isc_counter_t *qc,
|
|
dns_rdatatype_t);
|
|
static inline void
|
|
check_exit(dns_adb_t *);
|
|
static void
|
|
destroy(dns_adb_t *);
|
|
static bool
|
|
shutdown_names(dns_adb_t *);
|
|
static bool
|
|
shutdown_entries(dns_adb_t *);
|
|
static inline void
|
|
link_name(dns_adb_t *, int, dns_adbname_t *);
|
|
static inline bool
|
|
unlink_name(dns_adb_t *, dns_adbname_t *);
|
|
static inline void
|
|
link_entry(dns_adb_t *, int, dns_adbentry_t *);
|
|
static inline bool
|
|
unlink_entry(dns_adb_t *, dns_adbentry_t *);
|
|
static bool
|
|
kill_name(dns_adbname_t **, isc_eventtype_t);
|
|
static void
|
|
water(void *, int);
|
|
static void
|
|
dump_entry(FILE *, dns_adb_t *, dns_adbentry_t *, bool, isc_stdtime_t);
|
|
static void
|
|
adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
|
|
isc_stdtime_t now);
|
|
static void
|
|
shutdown_task(isc_task_t *task, isc_event_t *ev);
|
|
static void
|
|
log_quota(dns_adbentry_t *entry, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
|
|
|
|
/*
|
|
* MUST NOT overlap DNS_ADBFIND_* flags!
|
|
*/
|
|
#define FIND_EVENT_SENT 0x40000000
|
|
#define FIND_EVENT_FREED 0x80000000
|
|
#define FIND_EVENTSENT(h) (((h)->flags & FIND_EVENT_SENT) != 0)
|
|
#define FIND_EVENTFREED(h) (((h)->flags & FIND_EVENT_FREED) != 0)
|
|
|
|
#define NAME_NEEDS_POKE 0x80000000
|
|
#define NAME_IS_DEAD 0x40000000
|
|
#define NAME_HINT_OK DNS_ADBFIND_HINTOK
|
|
#define NAME_GLUE_OK DNS_ADBFIND_GLUEOK
|
|
#define NAME_STARTATZONE DNS_ADBFIND_STARTATZONE
|
|
#define NAME_DEAD(n) (((n)->flags & NAME_IS_DEAD) != 0)
|
|
#define NAME_NEEDSPOKE(n) (((n)->flags & NAME_NEEDS_POKE) != 0)
|
|
#define NAME_GLUEOK(n) (((n)->flags & NAME_GLUE_OK) != 0)
|
|
#define NAME_HINTOK(n) (((n)->flags & NAME_HINT_OK) != 0)
|
|
|
|
/*
|
|
* Private flag(s) for entries.
|
|
* MUST NOT overlap FCTX_ADDRINFO_xxx and DNS_FETCHOPT_NOEDNS0.
|
|
*/
|
|
#define ENTRY_IS_DEAD 0x00400000
|
|
|
|
/*
|
|
* To the name, address classes are all that really exist. If it has a
|
|
* V6 address it doesn't care if it came from a AAAA query.
|
|
*/
|
|
#define NAME_HAS_V4(n) (!ISC_LIST_EMPTY((n)->v4))
|
|
#define NAME_HAS_V6(n) (!ISC_LIST_EMPTY((n)->v6))
|
|
#define NAME_HAS_ADDRS(n) (NAME_HAS_V4(n) || NAME_HAS_V6(n))
|
|
|
|
/*
|
|
* Fetches are broken out into A and AAAA types. In some cases,
|
|
* however, it makes more sense to test for a particular class of fetches,
|
|
* like V4 or V6 above.
|
|
* Note: since we have removed the support of A6 in adb, FETCH_A and FETCH_AAAA
|
|
* are now equal to FETCH_V4 and FETCH_V6, respectively.
|
|
*/
|
|
#define NAME_FETCH_A(n) ((n)->fetch_a != NULL)
|
|
#define NAME_FETCH_AAAA(n) ((n)->fetch_aaaa != NULL)
|
|
#define NAME_FETCH_V4(n) (NAME_FETCH_A(n))
|
|
#define NAME_FETCH_V6(n) (NAME_FETCH_AAAA(n))
|
|
#define NAME_FETCH(n) (NAME_FETCH_V4(n) || NAME_FETCH_V6(n))
|
|
|
|
/*
|
|
* Find options and tests to see if there are addresses on the list.
|
|
*/
|
|
#define FIND_WANTEVENT(fn) (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0)
|
|
#define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0)
|
|
#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0)
|
|
#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0)
|
|
#define FIND_HINTOK(fn) (((fn)->options & DNS_ADBFIND_HINTOK) != 0)
|
|
#define FIND_GLUEOK(fn) (((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
|
|
#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list))
|
|
#define FIND_RETURNLAME(fn) (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
|
|
#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0)
|
|
|
|
/*
|
|
* These are currently used on simple unsigned ints, so they are
|
|
* not really associated with any particular type.
|
|
*/
|
|
#define WANT_INET(x) (((x)&DNS_ADBFIND_INET) != 0)
|
|
#define WANT_INET6(x) (((x)&DNS_ADBFIND_INET6) != 0)
|
|
|
|
#define EXPIRE_OK(exp, now) ((exp == INT_MAX) || (exp < now))
|
|
|
|
/*
|
|
* Find out if the flags on a name (nf) indicate if it is a hint or
|
|
* glue, and compare this to the appropriate bits set in o, to see if
|
|
* this is ok.
|
|
*/
|
|
#define GLUE_OK(nf, o) (!NAME_GLUEOK(nf) || (((o)&DNS_ADBFIND_GLUEOK) != 0))
|
|
#define HINT_OK(nf, o) (!NAME_HINTOK(nf) || (((o)&DNS_ADBFIND_HINTOK) != 0))
|
|
#define GLUEHINT_OK(nf, o) (GLUE_OK(nf, o) || HINT_OK(nf, o))
|
|
#define STARTATZONE_MATCHES(nf, o) \
|
|
(((nf)->flags & NAME_STARTATZONE) == ((o)&DNS_ADBFIND_STARTATZONE))
|
|
|
|
#define ENTER_LEVEL ISC_LOG_DEBUG(50)
|
|
#define EXIT_LEVEL ENTER_LEVEL
|
|
#define CLEAN_LEVEL ISC_LOG_DEBUG(100)
|
|
#define DEF_LEVEL ISC_LOG_DEBUG(5)
|
|
#define NCACHE_LEVEL ISC_LOG_DEBUG(20)
|
|
|
|
#define NCACHE_RESULT(r) \
|
|
((r) == DNS_R_NCACHENXDOMAIN || (r) == DNS_R_NCACHENXRRSET)
|
|
#define AUTH_NX(r) ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NXRRSET)
|
|
#define NXDOMAIN_RESULT(r) \
|
|
((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN)
|
|
#define NXRRSET_RESULT(r) \
|
|
((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \
|
|
(r) == DNS_R_HINTNXRRSET)
|
|
|
|
/*
|
|
* Error state rankings.
|
|
*/
|
|
|
|
#define FIND_ERR_SUCCESS 0 /* highest rank */
|
|
#define FIND_ERR_CANCELED 1
|
|
#define FIND_ERR_FAILURE 2
|
|
#define FIND_ERR_NXDOMAIN 3
|
|
#define FIND_ERR_NXRRSET 4
|
|
#define FIND_ERR_UNEXPECTED 5
|
|
#define FIND_ERR_NOTFOUND 6
|
|
#define FIND_ERR_MAX 7
|
|
|
|
static const char *errnames[] = { "success", "canceled", "failure",
|
|
"nxdomain", "nxrrset", "unexpected",
|
|
"not_found" };
|
|
|
|
#define NEWERR(old, new) (ISC_MIN((old), (new)))
|
|
|
|
static isc_result_t find_err_map[FIND_ERR_MAX] = {
|
|
ISC_R_SUCCESS, ISC_R_CANCELED, ISC_R_FAILURE, DNS_R_NXDOMAIN,
|
|
DNS_R_NXRRSET, ISC_R_UNEXPECTED, ISC_R_NOTFOUND /* not YET found */
|
|
};
|
|
|
|
static void
|
|
DP(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
|
|
|
|
static void
|
|
DP(int level, const char *format, ...) {
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
|
|
level, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
/*%
|
|
* Increment resolver-related statistics counters.
|
|
*/
|
|
static inline void
|
|
inc_stats(dns_adb_t *adb, isc_statscounter_t counter) {
|
|
if (adb->view->resstats != NULL) {
|
|
isc_stats_increment(adb->view->resstats, counter);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Set adb-related statistics counters.
|
|
*/
|
|
static inline void
|
|
set_adbstat(dns_adb_t *adb, uint64_t val, isc_statscounter_t counter) {
|
|
if (adb->view->adbstats != NULL) {
|
|
isc_stats_set(adb->view->adbstats, val, counter);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
dec_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
|
|
if (adb->view->adbstats != NULL) {
|
|
isc_stats_decrement(adb->view->adbstats, counter);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) {
|
|
if (adb->view->adbstats != NULL) {
|
|
isc_stats_increment(adb->view->adbstats, counter);
|
|
}
|
|
}
|
|
|
|
static inline dns_ttl_t
|
|
ttlclamp(dns_ttl_t ttl) {
|
|
if (ttl < ADB_CACHE_MINIMUM) {
|
|
ttl = ADB_CACHE_MINIMUM;
|
|
}
|
|
if (ttl > ADB_CACHE_MAXIMUM) {
|
|
ttl = ADB_CACHE_MAXIMUM;
|
|
}
|
|
|
|
return (ttl);
|
|
}
|
|
|
|
/*
|
|
* Hashing is most efficient if the number of buckets is prime.
|
|
* The sequence below is the closest previous primes to 2^n and
|
|
* 1.5 * 2^n, for values of n from 10 to 28. (The tables will
|
|
* no longer grow beyond 2^28 entries.)
|
|
*/
|
|
static const unsigned nbuckets[] = {
|
|
1021, 1531, 2039, 3067, 4093, 6143,
|
|
8191, 12281, 16381, 24571, 32749, 49193,
|
|
65521, 98299, 131071, 199603, 262139, 393209,
|
|
524287, 768431, 1048573, 1572853, 2097143, 3145721,
|
|
4194301, 6291449, 8388593, 12582893, 16777213, 25165813,
|
|
33554393, 50331599, 67108859, 100663291, 134217689, 201326557,
|
|
268535431, 0
|
|
};
|
|
|
|
static void
|
|
grow_entries(isc_task_t *task, isc_event_t *ev) {
|
|
dns_adb_t *adb;
|
|
dns_adbentry_t *e;
|
|
dns_adbentrylist_t *newdeadentries = NULL;
|
|
dns_adbentrylist_t *newentries = NULL;
|
|
bool *newentry_sd = NULL;
|
|
isc_mutex_t *newentrylocks = NULL;
|
|
isc_result_t result;
|
|
unsigned int *newentry_refcnt = NULL;
|
|
unsigned int i, n, bucket;
|
|
|
|
adb = ev->ev_arg;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
isc_event_free(&ev);
|
|
|
|
result = isc_task_beginexclusive(task);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto check_exit;
|
|
}
|
|
|
|
i = 0;
|
|
while (nbuckets[i] != 0 && adb->nentries >= nbuckets[i]) {
|
|
i++;
|
|
}
|
|
if (nbuckets[i] != 0) {
|
|
n = nbuckets[i];
|
|
} else {
|
|
goto done;
|
|
}
|
|
|
|
DP(ISC_LOG_INFO, "adb: grow_entries to %u starting", n);
|
|
|
|
/*
|
|
* Are we shutting down?
|
|
*/
|
|
for (i = 0; i < adb->nentries; i++) {
|
|
if (adb->entry_sd[i]) {
|
|
goto cleanup;
|
|
|
|
/*
|
|
* Grab all the resources we need.
|
|
*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Grab all the resources we need.
|
|
*/
|
|
newentries = isc_mem_get(adb->mctx, sizeof(*newentries) * n);
|
|
newdeadentries = isc_mem_get(adb->mctx, sizeof(*newdeadentries) * n);
|
|
newentrylocks = isc_mem_get(adb->mctx, sizeof(*newentrylocks) * n);
|
|
newentry_sd = isc_mem_get(adb->mctx, sizeof(*newentry_sd) * n);
|
|
newentry_refcnt = isc_mem_get(adb->mctx, sizeof(*newentry_refcnt) * n);
|
|
if (newentries == NULL || newdeadentries == NULL ||
|
|
newentrylocks == NULL || newentry_sd == NULL ||
|
|
newentry_refcnt == NULL)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Initialise the new resources.
|
|
*/
|
|
isc_mutexblock_init(newentrylocks, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
ISC_LIST_INIT(newentries[i]);
|
|
ISC_LIST_INIT(newdeadentries[i]);
|
|
newentry_sd[i] = false;
|
|
newentry_refcnt[i] = 0;
|
|
adb->irefcnt++;
|
|
}
|
|
|
|
/*
|
|
* Move entries to new arrays.
|
|
*/
|
|
for (i = 0; i < adb->nentries; i++) {
|
|
e = ISC_LIST_HEAD(adb->entries[i]);
|
|
while (e != NULL) {
|
|
ISC_LIST_UNLINK(adb->entries[i], e, plink);
|
|
bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
|
|
e->lock_bucket = bucket;
|
|
ISC_LIST_APPEND(newentries[bucket], e, plink);
|
|
INSIST(adb->entry_refcnt[i] > 0);
|
|
adb->entry_refcnt[i]--;
|
|
newentry_refcnt[bucket]++;
|
|
e = ISC_LIST_HEAD(adb->entries[i]);
|
|
}
|
|
e = ISC_LIST_HEAD(adb->deadentries[i]);
|
|
while (e != NULL) {
|
|
ISC_LIST_UNLINK(adb->deadentries[i], e, plink);
|
|
bucket = isc_sockaddr_hash(&e->sockaddr, true) % n;
|
|
e->lock_bucket = bucket;
|
|
ISC_LIST_APPEND(newdeadentries[bucket], e, plink);
|
|
INSIST(adb->entry_refcnt[i] > 0);
|
|
adb->entry_refcnt[i]--;
|
|
newentry_refcnt[bucket]++;
|
|
e = ISC_LIST_HEAD(adb->deadentries[i]);
|
|
}
|
|
INSIST(adb->entry_refcnt[i] == 0);
|
|
adb->irefcnt--;
|
|
}
|
|
|
|
/*
|
|
* Cleanup old resources.
|
|
*/
|
|
isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entries,
|
|
sizeof(*adb->entries) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->deadentries,
|
|
sizeof(*adb->deadentries) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entrylocks,
|
|
sizeof(*adb->entrylocks) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entry_sd,
|
|
sizeof(*adb->entry_sd) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entry_refcnt,
|
|
sizeof(*adb->entry_refcnt) * adb->nentries);
|
|
|
|
/*
|
|
* Install new resources.
|
|
*/
|
|
adb->entries = newentries;
|
|
adb->deadentries = newdeadentries;
|
|
adb->entrylocks = newentrylocks;
|
|
adb->entry_sd = newentry_sd;
|
|
adb->entry_refcnt = newentry_refcnt;
|
|
adb->nentries = n;
|
|
|
|
set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
|
|
|
|
/*
|
|
* Only on success do we set adb->growentries_sent to false.
|
|
* This will prevent us being continuously being called on error.
|
|
*/
|
|
adb->growentries_sent = false;
|
|
goto done;
|
|
|
|
cleanup:
|
|
if (newentries != NULL) {
|
|
isc_mem_put(adb->mctx, newentries, sizeof(*newentries) * n);
|
|
}
|
|
if (newdeadentries != NULL) {
|
|
isc_mem_put(adb->mctx, newdeadentries,
|
|
sizeof(*newdeadentries) * n);
|
|
}
|
|
if (newentrylocks != NULL) {
|
|
isc_mem_put(adb->mctx, newentrylocks,
|
|
sizeof(*newentrylocks) * n);
|
|
}
|
|
if (newentry_sd != NULL) {
|
|
isc_mem_put(adb->mctx, newentry_sd, sizeof(*newentry_sd) * n);
|
|
}
|
|
if (newentry_refcnt != NULL) {
|
|
isc_mem_put(adb->mctx, newentry_refcnt,
|
|
sizeof(*newentry_refcnt) * n);
|
|
}
|
|
done:
|
|
isc_task_endexclusive(task);
|
|
|
|
check_exit:
|
|
LOCK(&adb->lock);
|
|
if (dec_adb_irefcnt(adb)) {
|
|
check_exit(adb);
|
|
}
|
|
UNLOCK(&adb->lock);
|
|
DP(ISC_LOG_INFO, "adb: grow_entries finished");
|
|
}
|
|
|
|
static void
|
|
grow_names(isc_task_t *task, isc_event_t *ev) {
|
|
dns_adb_t *adb;
|
|
dns_adbname_t *name;
|
|
dns_adbnamelist_t *newdeadnames = NULL;
|
|
dns_adbnamelist_t *newnames = NULL;
|
|
bool *newname_sd = NULL;
|
|
isc_mutex_t *newnamelocks = NULL;
|
|
isc_result_t result;
|
|
unsigned int *newname_refcnt = NULL;
|
|
unsigned int i, n;
|
|
unsigned int bucket;
|
|
|
|
adb = ev->ev_arg;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
isc_event_free(&ev);
|
|
|
|
result = isc_task_beginexclusive(task);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto check_exit;
|
|
}
|
|
|
|
i = 0;
|
|
while (nbuckets[i] != 0 && adb->nnames >= nbuckets[i]) {
|
|
i++;
|
|
}
|
|
if (nbuckets[i] != 0) {
|
|
n = nbuckets[i];
|
|
} else {
|
|
goto done;
|
|
}
|
|
|
|
DP(ISC_LOG_INFO, "adb: grow_names to %u starting", n);
|
|
|
|
/*
|
|
* Are we shutting down?
|
|
*/
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
if (adb->name_sd[i]) {
|
|
goto cleanup;
|
|
|
|
/*
|
|
* Grab all the resources we need.
|
|
*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Grab all the resources we need.
|
|
*/
|
|
newnames = isc_mem_get(adb->mctx, sizeof(*newnames) * n);
|
|
newdeadnames = isc_mem_get(adb->mctx, sizeof(*newdeadnames) * n);
|
|
newnamelocks = isc_mem_get(adb->mctx, sizeof(*newnamelocks) * n);
|
|
newname_sd = isc_mem_get(adb->mctx, sizeof(*newname_sd) * n);
|
|
newname_refcnt = isc_mem_get(adb->mctx, sizeof(*newname_refcnt) * n);
|
|
if (newnames == NULL || newdeadnames == NULL || newnamelocks == NULL ||
|
|
newname_sd == NULL || newname_refcnt == NULL)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Initialise the new resources.
|
|
*/
|
|
isc_mutexblock_init(newnamelocks, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
ISC_LIST_INIT(newnames[i]);
|
|
ISC_LIST_INIT(newdeadnames[i]);
|
|
newname_sd[i] = false;
|
|
newname_refcnt[i] = 0;
|
|
adb->irefcnt++;
|
|
}
|
|
|
|
/*
|
|
* Move names to new arrays.
|
|
*/
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
name = ISC_LIST_HEAD(adb->names[i]);
|
|
while (name != NULL) {
|
|
ISC_LIST_UNLINK(adb->names[i], name, plink);
|
|
bucket = dns_name_fullhash(&name->name, true) % n;
|
|
name->lock_bucket = bucket;
|
|
ISC_LIST_APPEND(newnames[bucket], name, plink);
|
|
INSIST(adb->name_refcnt[i] > 0);
|
|
adb->name_refcnt[i]--;
|
|
newname_refcnt[bucket]++;
|
|
name = ISC_LIST_HEAD(adb->names[i]);
|
|
}
|
|
name = ISC_LIST_HEAD(adb->deadnames[i]);
|
|
while (name != NULL) {
|
|
ISC_LIST_UNLINK(adb->deadnames[i], name, plink);
|
|
bucket = dns_name_fullhash(&name->name, true) % n;
|
|
name->lock_bucket = bucket;
|
|
ISC_LIST_APPEND(newdeadnames[bucket], name, plink);
|
|
INSIST(adb->name_refcnt[i] > 0);
|
|
adb->name_refcnt[i]--;
|
|
newname_refcnt[bucket]++;
|
|
name = ISC_LIST_HEAD(adb->deadnames[i]);
|
|
}
|
|
INSIST(adb->name_refcnt[i] == 0);
|
|
adb->irefcnt--;
|
|
}
|
|
|
|
/*
|
|
* Cleanup old resources.
|
|
*/
|
|
isc_mutexblock_destroy(adb->namelocks, adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->names, sizeof(*adb->names) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->deadnames,
|
|
sizeof(*adb->deadnames) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->namelocks,
|
|
sizeof(*adb->namelocks) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->name_sd,
|
|
sizeof(*adb->name_sd) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->name_refcnt,
|
|
sizeof(*adb->name_refcnt) * adb->nnames);
|
|
|
|
/*
|
|
* Install new resources.
|
|
*/
|
|
adb->names = newnames;
|
|
adb->deadnames = newdeadnames;
|
|
adb->namelocks = newnamelocks;
|
|
adb->name_sd = newname_sd;
|
|
adb->name_refcnt = newname_refcnt;
|
|
adb->nnames = n;
|
|
|
|
set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
|
|
|
|
/*
|
|
* Only on success do we set adb->grownames_sent to false.
|
|
* This will prevent us being continuously being called on error.
|
|
*/
|
|
adb->grownames_sent = false;
|
|
goto done;
|
|
|
|
cleanup:
|
|
if (newnames != NULL) {
|
|
isc_mem_put(adb->mctx, newnames, sizeof(*newnames) * n);
|
|
}
|
|
if (newdeadnames != NULL) {
|
|
isc_mem_put(adb->mctx, newdeadnames, sizeof(*newdeadnames) * n);
|
|
}
|
|
if (newnamelocks != NULL) {
|
|
isc_mem_put(adb->mctx, newnamelocks, sizeof(*newnamelocks) * n);
|
|
}
|
|
if (newname_sd != NULL) {
|
|
isc_mem_put(adb->mctx, newname_sd, sizeof(*newname_sd) * n);
|
|
}
|
|
if (newname_refcnt != NULL) {
|
|
isc_mem_put(adb->mctx, newname_refcnt,
|
|
sizeof(*newname_refcnt) * n);
|
|
}
|
|
done:
|
|
isc_task_endexclusive(task);
|
|
|
|
check_exit:
|
|
LOCK(&adb->lock);
|
|
if (dec_adb_irefcnt(adb)) {
|
|
check_exit(adb);
|
|
}
|
|
UNLOCK(&adb->lock);
|
|
DP(ISC_LOG_INFO, "adb: grow_names finished");
|
|
}
|
|
|
|
/*
|
|
* Requires the adbname bucket be locked and that no entry buckets be locked.
|
|
*
|
|
* This code handles A and AAAA rdatasets only.
|
|
*/
|
|
static isc_result_t
|
|
import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
|
|
isc_stdtime_t now) {
|
|
isc_result_t result;
|
|
dns_adb_t *adb;
|
|
dns_adbnamehook_t *nh;
|
|
dns_adbnamehook_t *anh;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
struct in_addr ina;
|
|
struct in6_addr in6a;
|
|
isc_sockaddr_t sockaddr;
|
|
dns_adbentry_t *foundentry; /* NO CLEAN UP! */
|
|
int addr_bucket;
|
|
bool new_addresses_added;
|
|
dns_rdatatype_t rdtype;
|
|
unsigned int findoptions;
|
|
dns_adbnamehooklist_t *hookhead;
|
|
|
|
INSIST(DNS_ADBNAME_VALID(adbname));
|
|
adb = adbname->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
rdtype = rdataset->type;
|
|
INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa));
|
|
if (rdtype == dns_rdatatype_a) {
|
|
findoptions = DNS_ADBFIND_INET;
|
|
} else {
|
|
findoptions = DNS_ADBFIND_INET6;
|
|
}
|
|
|
|
addr_bucket = DNS_ADB_INVALIDBUCKET;
|
|
new_addresses_added = false;
|
|
|
|
nh = NULL;
|
|
result = dns_rdataset_first(rdataset);
|
|
while (result == ISC_R_SUCCESS) {
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
if (rdtype == dns_rdatatype_a) {
|
|
INSIST(rdata.length == 4);
|
|
memmove(&ina.s_addr, rdata.data, 4);
|
|
isc_sockaddr_fromin(&sockaddr, &ina, 0);
|
|
hookhead = &adbname->v4;
|
|
} else {
|
|
INSIST(rdata.length == 16);
|
|
memmove(in6a.s6_addr, rdata.data, 16);
|
|
isc_sockaddr_fromin6(&sockaddr, &in6a, 0);
|
|
hookhead = &adbname->v6;
|
|
}
|
|
|
|
INSIST(nh == NULL);
|
|
nh = new_adbnamehook(adb, NULL);
|
|
if (nh == NULL) {
|
|
adbname->partial_result |= findoptions;
|
|
result = ISC_R_NOMEMORY;
|
|
goto fail;
|
|
}
|
|
|
|
foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket,
|
|
now);
|
|
if (foundentry == NULL) {
|
|
dns_adbentry_t *entry;
|
|
|
|
entry = new_adbentry(adb);
|
|
if (entry == NULL) {
|
|
adbname->partial_result |= findoptions;
|
|
result = ISC_R_NOMEMORY;
|
|
goto fail;
|
|
}
|
|
|
|
entry->sockaddr = sockaddr;
|
|
entry->refcnt = 1;
|
|
entry->nh = 1;
|
|
|
|
nh->entry = entry;
|
|
|
|
link_entry(adb, addr_bucket, entry);
|
|
} else {
|
|
for (anh = ISC_LIST_HEAD(*hookhead); anh != NULL;
|
|
anh = ISC_LIST_NEXT(anh, plink))
|
|
{
|
|
if (anh->entry == foundentry) {
|
|
break;
|
|
}
|
|
}
|
|
if (anh == NULL) {
|
|
foundentry->refcnt++;
|
|
foundentry->nh++;
|
|
nh->entry = foundentry;
|
|
} else {
|
|
free_adbnamehook(adb, &nh);
|
|
}
|
|
}
|
|
|
|
new_addresses_added = true;
|
|
if (nh != NULL) {
|
|
ISC_LIST_APPEND(*hookhead, nh, plink);
|
|
}
|
|
nh = NULL;
|
|
result = dns_rdataset_next(rdataset);
|
|
}
|
|
|
|
fail:
|
|
if (nh != NULL) {
|
|
free_adbnamehook(adb, &nh);
|
|
}
|
|
|
|
if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
|
|
UNLOCK(&adb->entrylocks[addr_bucket]);
|
|
}
|
|
|
|
if (rdataset->trust == dns_trust_glue ||
|
|
rdataset->trust == dns_trust_additional)
|
|
{
|
|
rdataset->ttl = ADB_CACHE_MINIMUM;
|
|
} else if (rdataset->trust == dns_trust_ultimate) {
|
|
rdataset->ttl = 0;
|
|
} else {
|
|
rdataset->ttl = ttlclamp(rdataset->ttl);
|
|
}
|
|
|
|
if (rdtype == dns_rdatatype_a) {
|
|
DP(NCACHE_LEVEL, "expire_v4 set to MIN(%u,%u) import_rdataset",
|
|
adbname->expire_v4, now + rdataset->ttl);
|
|
adbname->expire_v4 = ISC_MIN(
|
|
adbname->expire_v4,
|
|
ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
|
|
} else {
|
|
DP(NCACHE_LEVEL, "expire_v6 set to MIN(%u,%u) import_rdataset",
|
|
adbname->expire_v6, now + rdataset->ttl);
|
|
adbname->expire_v6 = ISC_MIN(
|
|
adbname->expire_v6,
|
|
ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl));
|
|
}
|
|
|
|
if (new_addresses_added) {
|
|
/*
|
|
* Lie a little here. This is more or less so code that cares
|
|
* can find out if any new information was added or not.
|
|
*/
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Requires the name's bucket be locked.
|
|
*/
|
|
static bool
|
|
kill_name(dns_adbname_t **n, isc_eventtype_t ev) {
|
|
dns_adbname_t *name;
|
|
bool result = false;
|
|
bool result4, result6;
|
|
int bucket;
|
|
dns_adb_t *adb;
|
|
|
|
INSIST(n != NULL);
|
|
name = *n;
|
|
*n = NULL;
|
|
INSIST(DNS_ADBNAME_VALID(name));
|
|
adb = name->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
DP(DEF_LEVEL, "killing name %p", name);
|
|
|
|
/*
|
|
* If we're dead already, just check to see if we should go
|
|
* away now or not.
|
|
*/
|
|
if (NAME_DEAD(name) && !NAME_FETCH(name)) {
|
|
result = unlink_name(adb, name);
|
|
free_adbname(adb, &name);
|
|
if (result) {
|
|
result = dec_adb_irefcnt(adb);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Clean up the name's various lists. These two are destructive
|
|
* in that they will always empty the list.
|
|
*/
|
|
clean_finds_at_name(name, ev, DNS_ADBFIND_ADDRESSMASK);
|
|
result4 = clean_namehooks(adb, &name->v4);
|
|
result6 = clean_namehooks(adb, &name->v6);
|
|
clean_target(adb, &name->target);
|
|
result = (result4 || result6);
|
|
|
|
/*
|
|
* If fetches are running, cancel them. If none are running, we can
|
|
* just kill the name here.
|
|
*/
|
|
if (!NAME_FETCH(name)) {
|
|
INSIST(!result);
|
|
result = unlink_name(adb, name);
|
|
free_adbname(adb, &name);
|
|
if (result) {
|
|
result = dec_adb_irefcnt(adb);
|
|
}
|
|
} else {
|
|
cancel_fetches_at_name(name);
|
|
if (!NAME_DEAD(name)) {
|
|
bucket = name->lock_bucket;
|
|
ISC_LIST_UNLINK(adb->names[bucket], name, plink);
|
|
ISC_LIST_APPEND(adb->deadnames[bucket], name, plink);
|
|
name->flags |= NAME_IS_DEAD;
|
|
}
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Requires the name's bucket be locked and no entry buckets be locked.
|
|
*/
|
|
static bool
|
|
check_expire_namehooks(dns_adbname_t *name, isc_stdtime_t now) {
|
|
dns_adb_t *adb;
|
|
bool result4 = false;
|
|
bool result6 = false;
|
|
|
|
INSIST(DNS_ADBNAME_VALID(name));
|
|
adb = name->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
/*
|
|
* Check to see if we need to remove the v4 addresses
|
|
*/
|
|
if (!NAME_FETCH_V4(name) && EXPIRE_OK(name->expire_v4, now)) {
|
|
if (NAME_HAS_V4(name)) {
|
|
DP(DEF_LEVEL, "expiring v4 for name %p", name);
|
|
result4 = clean_namehooks(adb, &name->v4);
|
|
name->partial_result &= ~DNS_ADBFIND_INET;
|
|
}
|
|
name->expire_v4 = INT_MAX;
|
|
name->fetch_err = FIND_ERR_UNEXPECTED;
|
|
}
|
|
|
|
/*
|
|
* Check to see if we need to remove the v6 addresses
|
|
*/
|
|
if (!NAME_FETCH_V6(name) && EXPIRE_OK(name->expire_v6, now)) {
|
|
if (NAME_HAS_V6(name)) {
|
|
DP(DEF_LEVEL, "expiring v6 for name %p", name);
|
|
result6 = clean_namehooks(adb, &name->v6);
|
|
name->partial_result &= ~DNS_ADBFIND_INET6;
|
|
}
|
|
name->expire_v6 = INT_MAX;
|
|
name->fetch6_err = FIND_ERR_UNEXPECTED;
|
|
}
|
|
|
|
/*
|
|
* Check to see if we need to remove the alias target.
|
|
*/
|
|
if (EXPIRE_OK(name->expire_target, now)) {
|
|
clean_target(adb, &name->target);
|
|
name->expire_target = INT_MAX;
|
|
}
|
|
return (result4 || result6);
|
|
}
|
|
|
|
/*
|
|
* Requires the name's bucket be locked.
|
|
*/
|
|
static inline void
|
|
link_name(dns_adb_t *adb, int bucket, dns_adbname_t *name) {
|
|
INSIST(name->lock_bucket == DNS_ADB_INVALIDBUCKET);
|
|
|
|
ISC_LIST_PREPEND(adb->names[bucket], name, plink);
|
|
name->lock_bucket = bucket;
|
|
adb->name_refcnt[bucket]++;
|
|
}
|
|
|
|
/*
|
|
* Requires the name's bucket be locked.
|
|
*/
|
|
static inline bool
|
|
unlink_name(dns_adb_t *adb, dns_adbname_t *name) {
|
|
int bucket;
|
|
bool result = false;
|
|
|
|
bucket = name->lock_bucket;
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
|
|
if (NAME_DEAD(name)) {
|
|
ISC_LIST_UNLINK(adb->deadnames[bucket], name, plink);
|
|
} else {
|
|
ISC_LIST_UNLINK(adb->names[bucket], name, plink);
|
|
}
|
|
name->lock_bucket = DNS_ADB_INVALIDBUCKET;
|
|
INSIST(adb->name_refcnt[bucket] > 0);
|
|
adb->name_refcnt[bucket]--;
|
|
if (adb->name_sd[bucket] && adb->name_refcnt[bucket] == 0) {
|
|
result = true;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Requires the entry's bucket be locked.
|
|
*/
|
|
static inline void
|
|
link_entry(dns_adb_t *adb, int bucket, dns_adbentry_t *entry) {
|
|
int i;
|
|
dns_adbentry_t *e;
|
|
|
|
if (isc_mem_isovermem(adb->mctx)) {
|
|
for (i = 0; i < 2; i++) {
|
|
e = ISC_LIST_TAIL(adb->entries[bucket]);
|
|
if (e == NULL) {
|
|
break;
|
|
}
|
|
if (e->refcnt == 0) {
|
|
unlink_entry(adb, e);
|
|
free_adbentry(adb, &e);
|
|
continue;
|
|
}
|
|
INSIST((e->flags & ENTRY_IS_DEAD) == 0);
|
|
e->flags |= ENTRY_IS_DEAD;
|
|
ISC_LIST_UNLINK(adb->entries[bucket], e, plink);
|
|
ISC_LIST_PREPEND(adb->deadentries[bucket], e, plink);
|
|
}
|
|
}
|
|
|
|
ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
|
|
entry->lock_bucket = bucket;
|
|
adb->entry_refcnt[bucket]++;
|
|
}
|
|
|
|
/*
|
|
* Requires the entry's bucket be locked.
|
|
*/
|
|
static inline bool
|
|
unlink_entry(dns_adb_t *adb, dns_adbentry_t *entry) {
|
|
int bucket;
|
|
bool result = false;
|
|
|
|
bucket = entry->lock_bucket;
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
|
|
if ((entry->flags & ENTRY_IS_DEAD) != 0) {
|
|
ISC_LIST_UNLINK(adb->deadentries[bucket], entry, plink);
|
|
} else {
|
|
ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
|
|
}
|
|
entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
|
|
INSIST(adb->entry_refcnt[bucket] > 0);
|
|
adb->entry_refcnt[bucket]--;
|
|
if (adb->entry_sd[bucket] && adb->entry_refcnt[bucket] == 0) {
|
|
result = true;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static inline void
|
|
violate_locking_hierarchy(isc_mutex_t *have, isc_mutex_t *want) {
|
|
if (isc_mutex_trylock(want) != ISC_R_SUCCESS) {
|
|
UNLOCK(have);
|
|
LOCK(want);
|
|
LOCK(have);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The ADB _MUST_ be locked before calling. Also, exit conditions must be
|
|
* checked after calling this function.
|
|
*/
|
|
static bool
|
|
shutdown_names(dns_adb_t *adb) {
|
|
unsigned int bucket;
|
|
bool result = false;
|
|
dns_adbname_t *name;
|
|
dns_adbname_t *next_name;
|
|
|
|
for (bucket = 0; bucket < adb->nnames; bucket++) {
|
|
LOCK(&adb->namelocks[bucket]);
|
|
adb->name_sd[bucket] = true;
|
|
|
|
name = ISC_LIST_HEAD(adb->names[bucket]);
|
|
if (name == NULL) {
|
|
/*
|
|
* This bucket has no names. We must decrement the
|
|
* irefcnt ourselves, since it will not be
|
|
* automatically triggered by a name being unlinked.
|
|
*/
|
|
INSIST(!result);
|
|
result = dec_adb_irefcnt(adb);
|
|
} else {
|
|
/*
|
|
* Run through the list. For each name, clean up finds
|
|
* found there, and cancel any fetches running. When
|
|
* all the fetches are canceled, the name will destroy
|
|
* itself.
|
|
*/
|
|
while (name != NULL) {
|
|
next_name = ISC_LIST_NEXT(name, plink);
|
|
INSIST(!result);
|
|
result = kill_name(&name,
|
|
DNS_EVENT_ADBSHUTDOWN);
|
|
name = next_name;
|
|
}
|
|
}
|
|
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* The ADB _MUST_ be locked before calling. Also, exit conditions must be
|
|
* checked after calling this function.
|
|
*/
|
|
static bool
|
|
shutdown_entries(dns_adb_t *adb) {
|
|
unsigned int bucket;
|
|
bool result = false;
|
|
dns_adbentry_t *entry;
|
|
dns_adbentry_t *next_entry;
|
|
|
|
for (bucket = 0; bucket < adb->nentries; bucket++) {
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
adb->entry_sd[bucket] = true;
|
|
|
|
entry = ISC_LIST_HEAD(adb->entries[bucket]);
|
|
if (adb->entry_refcnt[bucket] == 0) {
|
|
/*
|
|
* This bucket has no entries. We must decrement the
|
|
* irefcnt ourselves, since it will not be
|
|
* automatically triggered by an entry being unlinked.
|
|
*/
|
|
result = dec_adb_irefcnt(adb);
|
|
} else {
|
|
/*
|
|
* Run through the list. Cleanup any entries not
|
|
* associated with names, and which are not in use.
|
|
*/
|
|
while (entry != NULL) {
|
|
next_entry = ISC_LIST_NEXT(entry, plink);
|
|
if (entry->refcnt == 0 && entry->expires != 0) {
|
|
result = unlink_entry(adb, entry);
|
|
free_adbentry(adb, &entry);
|
|
if (result) {
|
|
result = dec_adb_irefcnt(adb);
|
|
}
|
|
}
|
|
entry = next_entry;
|
|
}
|
|
}
|
|
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Name bucket must be locked
|
|
*/
|
|
static void
|
|
cancel_fetches_at_name(dns_adbname_t *name) {
|
|
if (NAME_FETCH_A(name)) {
|
|
dns_resolver_cancelfetch(name->fetch_a->fetch);
|
|
}
|
|
|
|
if (NAME_FETCH_AAAA(name)) {
|
|
dns_resolver_cancelfetch(name->fetch_aaaa->fetch);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Assumes the name bucket is locked.
|
|
*/
|
|
static bool
|
|
clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks) {
|
|
dns_adbentry_t *entry;
|
|
dns_adbnamehook_t *namehook;
|
|
int addr_bucket;
|
|
bool result = false;
|
|
bool overmem = isc_mem_isovermem(adb->mctx);
|
|
|
|
addr_bucket = DNS_ADB_INVALIDBUCKET;
|
|
namehook = ISC_LIST_HEAD(*namehooks);
|
|
while (namehook != NULL) {
|
|
INSIST(DNS_ADBNAMEHOOK_VALID(namehook));
|
|
|
|
/*
|
|
* Clean up the entry if needed.
|
|
*/
|
|
entry = namehook->entry;
|
|
if (entry != NULL) {
|
|
INSIST(DNS_ADBENTRY_VALID(entry));
|
|
|
|
if (addr_bucket != entry->lock_bucket) {
|
|
if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
|
|
UNLOCK(&adb->entrylocks[addr_bucket]);
|
|
}
|
|
addr_bucket = entry->lock_bucket;
|
|
INSIST(addr_bucket != DNS_ADB_INVALIDBUCKET);
|
|
LOCK(&adb->entrylocks[addr_bucket]);
|
|
}
|
|
|
|
entry->nh--;
|
|
result = dec_entry_refcnt(adb, overmem, entry, false);
|
|
}
|
|
|
|
/*
|
|
* Free the namehook
|
|
*/
|
|
namehook->entry = NULL;
|
|
ISC_LIST_UNLINK(*namehooks, namehook, plink);
|
|
free_adbnamehook(adb, &namehook);
|
|
|
|
namehook = ISC_LIST_HEAD(*namehooks);
|
|
}
|
|
|
|
if (addr_bucket != DNS_ADB_INVALIDBUCKET) {
|
|
UNLOCK(&adb->entrylocks[addr_bucket]);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
clean_target(dns_adb_t *adb, dns_name_t *target) {
|
|
if (dns_name_countlabels(target) > 0) {
|
|
dns_name_free(target, adb->mctx);
|
|
dns_name_init(target, NULL);
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
set_target(dns_adb_t *adb, const dns_name_t *name, const dns_name_t *fname,
|
|
dns_rdataset_t *rdataset, dns_name_t *target) {
|
|
isc_result_t result;
|
|
dns_namereln_t namereln;
|
|
unsigned int nlabels;
|
|
int order;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_fixedname_t fixed1, fixed2;
|
|
dns_name_t *prefix, *new_target;
|
|
|
|
REQUIRE(dns_name_countlabels(target) == 0);
|
|
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
dns_rdata_cname_t cname;
|
|
|
|
/*
|
|
* Copy the CNAME's target into the target name.
|
|
*/
|
|
result = dns_rdataset_first(rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &cname, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_name_dup(&cname.cname, adb->mctx, target);
|
|
dns_rdata_freestruct(&cname);
|
|
} else {
|
|
dns_rdata_dname_t dname;
|
|
|
|
INSIST(rdataset->type == dns_rdatatype_dname);
|
|
namereln = dns_name_fullcompare(name, fname, &order, &nlabels);
|
|
INSIST(namereln == dns_namereln_subdomain);
|
|
/*
|
|
* Get the target name of the DNAME.
|
|
*/
|
|
result = dns_rdataset_first(rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &dname, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
/*
|
|
* Construct the new target name.
|
|
*/
|
|
prefix = dns_fixedname_initname(&fixed1);
|
|
new_target = dns_fixedname_initname(&fixed2);
|
|
dns_name_split(name, nlabels, prefix, NULL);
|
|
result = dns_name_concatenate(prefix, &dname.dname, new_target,
|
|
NULL);
|
|
dns_rdata_freestruct(&dname);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_name_dup(new_target, adb->mctx, target);
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Assumes nothing is locked, since this is called by the client.
|
|
*/
|
|
static void
|
|
event_free(isc_event_t *event) {
|
|
dns_adbfind_t *find;
|
|
|
|
INSIST(event != NULL);
|
|
find = event->ev_destroy_arg;
|
|
INSIST(DNS_ADBFIND_VALID(find));
|
|
|
|
LOCK(&find->lock);
|
|
find->flags |= FIND_EVENT_FREED;
|
|
event->ev_destroy_arg = NULL;
|
|
UNLOCK(&find->lock);
|
|
}
|
|
|
|
/*
|
|
* Assumes the name bucket is locked.
|
|
*/
|
|
static void
|
|
clean_finds_at_name(dns_adbname_t *name, isc_eventtype_t evtype,
|
|
unsigned int addrs) {
|
|
isc_event_t *ev;
|
|
isc_task_t *task;
|
|
dns_adbfind_t *find;
|
|
dns_adbfind_t *next_find;
|
|
bool process;
|
|
unsigned int wanted, notify;
|
|
|
|
DP(ENTER_LEVEL,
|
|
"ENTER clean_finds_at_name, name %p, evtype %08x, addrs %08x", name,
|
|
evtype, addrs);
|
|
|
|
find = ISC_LIST_HEAD(name->finds);
|
|
while (find != NULL) {
|
|
LOCK(&find->lock);
|
|
next_find = ISC_LIST_NEXT(find, plink);
|
|
|
|
process = false;
|
|
wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
|
|
notify = wanted & addrs;
|
|
|
|
switch (evtype) {
|
|
case DNS_EVENT_ADBMOREADDRESSES:
|
|
DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBMOREADDRESSES");
|
|
if ((notify) != 0) {
|
|
find->flags &= ~addrs;
|
|
process = true;
|
|
}
|
|
break;
|
|
case DNS_EVENT_ADBNOMOREADDRESSES:
|
|
DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBNOMOREADDRESSES");
|
|
find->flags &= ~addrs;
|
|
wanted = find->flags & DNS_ADBFIND_ADDRESSMASK;
|
|
if (wanted == 0) {
|
|
process = true;
|
|
}
|
|
break;
|
|
default:
|
|
find->flags &= ~addrs;
|
|
process = true;
|
|
}
|
|
|
|
if (process) {
|
|
DP(DEF_LEVEL, "cfan: processing find %p", find);
|
|
/*
|
|
* Unlink the find from the name, letting the caller
|
|
* call dns_adb_destroyfind() on it to clean it up
|
|
* later.
|
|
*/
|
|
ISC_LIST_UNLINK(name->finds, find, plink);
|
|
find->adbname = NULL;
|
|
find->name_bucket = DNS_ADB_INVALIDBUCKET;
|
|
|
|
INSIST(!FIND_EVENTSENT(find));
|
|
|
|
ev = &find->event;
|
|
task = ev->ev_sender;
|
|
ev->ev_sender = find;
|
|
find->result_v4 = find_err_map[name->fetch_err];
|
|
find->result_v6 = find_err_map[name->fetch6_err];
|
|
ev->ev_type = evtype;
|
|
ev->ev_destroy = event_free;
|
|
ev->ev_destroy_arg = find;
|
|
|
|
DP(DEF_LEVEL, "sending event %p to task %p for find %p",
|
|
ev, task, find);
|
|
|
|
isc_task_sendanddetach(&task, (isc_event_t **)&ev);
|
|
find->flags |= FIND_EVENT_SENT;
|
|
} else {
|
|
DP(DEF_LEVEL, "cfan: skipping find %p", find);
|
|
}
|
|
|
|
UNLOCK(&find->lock);
|
|
find = next_find;
|
|
}
|
|
DP(ENTER_LEVEL, "EXIT clean_finds_at_name, name %p", name);
|
|
}
|
|
|
|
static inline void
|
|
check_exit(dns_adb_t *adb) {
|
|
isc_event_t *event;
|
|
/*
|
|
* The caller must be holding the adb lock.
|
|
*/
|
|
if (adb->shutting_down) {
|
|
/*
|
|
* If there aren't any external references either, we're
|
|
* done. Send the control event to initiate shutdown.
|
|
*/
|
|
INSIST(!adb->cevent_out); /* Sanity check. */
|
|
ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
|
|
DNS_EVENT_ADBCONTROL, shutdown_task, adb, adb,
|
|
NULL, NULL);
|
|
event = &adb->cevent;
|
|
isc_task_send(adb->task, &event);
|
|
adb->cevent_out = true;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
dec_adb_irefcnt(dns_adb_t *adb) {
|
|
isc_event_t *event;
|
|
isc_task_t *etask;
|
|
bool result = false;
|
|
|
|
LOCK(&adb->reflock);
|
|
|
|
INSIST(adb->irefcnt > 0);
|
|
adb->irefcnt--;
|
|
|
|
if (adb->irefcnt == 0) {
|
|
event = ISC_LIST_HEAD(adb->whenshutdown);
|
|
while (event != NULL) {
|
|
ISC_LIST_UNLINK(adb->whenshutdown, event, ev_link);
|
|
etask = event->ev_sender;
|
|
event->ev_sender = adb;
|
|
isc_task_sendanddetach(&etask, &event);
|
|
event = ISC_LIST_HEAD(adb->whenshutdown);
|
|
}
|
|
}
|
|
|
|
if (adb->irefcnt == 0 && adb->erefcnt == 0) {
|
|
result = true;
|
|
}
|
|
UNLOCK(&adb->reflock);
|
|
return (result);
|
|
}
|
|
|
|
static inline void
|
|
inc_adb_irefcnt(dns_adb_t *adb) {
|
|
LOCK(&adb->reflock);
|
|
adb->irefcnt++;
|
|
UNLOCK(&adb->reflock);
|
|
}
|
|
|
|
static inline void
|
|
inc_adb_erefcnt(dns_adb_t *adb) {
|
|
LOCK(&adb->reflock);
|
|
adb->erefcnt++;
|
|
UNLOCK(&adb->reflock);
|
|
}
|
|
|
|
static inline void
|
|
inc_entry_refcnt(dns_adb_t *adb, dns_adbentry_t *entry, bool lock) {
|
|
int bucket;
|
|
|
|
bucket = entry->lock_bucket;
|
|
|
|
if (lock) {
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
entry->refcnt++;
|
|
|
|
if (lock) {
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
dec_entry_refcnt(dns_adb_t *adb, bool overmem, dns_adbentry_t *entry,
|
|
bool lock) {
|
|
int bucket;
|
|
bool destroy_entry;
|
|
bool result = false;
|
|
|
|
bucket = entry->lock_bucket;
|
|
|
|
if (lock) {
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
INSIST(entry->refcnt > 0);
|
|
entry->refcnt--;
|
|
|
|
destroy_entry = false;
|
|
if (entry->refcnt == 0 &&
|
|
(adb->entry_sd[bucket] || entry->expires == 0 || overmem ||
|
|
(entry->flags & ENTRY_IS_DEAD) != 0))
|
|
{
|
|
destroy_entry = true;
|
|
result = unlink_entry(adb, entry);
|
|
}
|
|
|
|
if (lock) {
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
if (!destroy_entry) {
|
|
return (result);
|
|
}
|
|
|
|
entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
|
|
|
|
free_adbentry(adb, &entry);
|
|
if (result) {
|
|
result = dec_adb_irefcnt(adb);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
static inline dns_adbname_t *
|
|
new_adbname(dns_adb_t *adb, const dns_name_t *dnsname) {
|
|
dns_adbname_t *name;
|
|
|
|
name = isc_mempool_get(adb->nmp);
|
|
if (name == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
dns_name_init(&name->name, NULL);
|
|
dns_name_dup(dnsname, adb->mctx, &name->name);
|
|
dns_name_init(&name->target, NULL);
|
|
name->magic = DNS_ADBNAME_MAGIC;
|
|
name->adb = adb;
|
|
name->partial_result = 0;
|
|
name->flags = 0;
|
|
name->expire_v4 = INT_MAX;
|
|
name->expire_v6 = INT_MAX;
|
|
name->expire_target = INT_MAX;
|
|
name->chains = 0;
|
|
name->lock_bucket = DNS_ADB_INVALIDBUCKET;
|
|
ISC_LIST_INIT(name->v4);
|
|
ISC_LIST_INIT(name->v6);
|
|
name->fetch_a = NULL;
|
|
name->fetch_aaaa = NULL;
|
|
name->fetch_err = FIND_ERR_UNEXPECTED;
|
|
name->fetch6_err = FIND_ERR_UNEXPECTED;
|
|
ISC_LIST_INIT(name->finds);
|
|
ISC_LINK_INIT(name, plink);
|
|
|
|
LOCK(&adb->namescntlock);
|
|
adb->namescnt++;
|
|
inc_adbstats(adb, dns_adbstats_namescnt);
|
|
if (!adb->grownames_sent && adb->excl != NULL &&
|
|
adb->namescnt > (adb->nnames * 8))
|
|
{
|
|
isc_event_t *event = &adb->grownames;
|
|
inc_adb_irefcnt(adb);
|
|
isc_task_send(adb->excl, &event);
|
|
adb->grownames_sent = true;
|
|
}
|
|
UNLOCK(&adb->namescntlock);
|
|
|
|
return (name);
|
|
}
|
|
|
|
static inline void
|
|
free_adbname(dns_adb_t *adb, dns_adbname_t **name) {
|
|
dns_adbname_t *n;
|
|
|
|
INSIST(name != NULL && DNS_ADBNAME_VALID(*name));
|
|
n = *name;
|
|
*name = NULL;
|
|
|
|
INSIST(!NAME_HAS_V4(n));
|
|
INSIST(!NAME_HAS_V6(n));
|
|
INSIST(!NAME_FETCH(n));
|
|
INSIST(ISC_LIST_EMPTY(n->finds));
|
|
INSIST(!ISC_LINK_LINKED(n, plink));
|
|
INSIST(n->lock_bucket == DNS_ADB_INVALIDBUCKET);
|
|
INSIST(n->adb == adb);
|
|
|
|
n->magic = 0;
|
|
dns_name_free(&n->name, adb->mctx);
|
|
|
|
isc_mempool_put(adb->nmp, n);
|
|
LOCK(&adb->namescntlock);
|
|
adb->namescnt--;
|
|
dec_adbstats(adb, dns_adbstats_namescnt);
|
|
UNLOCK(&adb->namescntlock);
|
|
}
|
|
|
|
static inline dns_adbnamehook_t *
|
|
new_adbnamehook(dns_adb_t *adb, dns_adbentry_t *entry) {
|
|
dns_adbnamehook_t *nh;
|
|
|
|
nh = isc_mempool_get(adb->nhmp);
|
|
if (nh == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
nh->magic = DNS_ADBNAMEHOOK_MAGIC;
|
|
nh->entry = entry;
|
|
ISC_LINK_INIT(nh, plink);
|
|
|
|
return (nh);
|
|
}
|
|
|
|
static inline void
|
|
free_adbnamehook(dns_adb_t *adb, dns_adbnamehook_t **namehook) {
|
|
dns_adbnamehook_t *nh;
|
|
|
|
INSIST(namehook != NULL && DNS_ADBNAMEHOOK_VALID(*namehook));
|
|
nh = *namehook;
|
|
*namehook = NULL;
|
|
|
|
INSIST(nh->entry == NULL);
|
|
INSIST(!ISC_LINK_LINKED(nh, plink));
|
|
|
|
nh->magic = 0;
|
|
isc_mempool_put(adb->nhmp, nh);
|
|
}
|
|
|
|
static inline dns_adblameinfo_t *
|
|
new_adblameinfo(dns_adb_t *adb, const dns_name_t *qname,
|
|
dns_rdatatype_t qtype) {
|
|
dns_adblameinfo_t *li;
|
|
|
|
li = isc_mempool_get(adb->limp);
|
|
if (li == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
dns_name_init(&li->qname, NULL);
|
|
dns_name_dup(qname, adb->mctx, &li->qname);
|
|
li->magic = DNS_ADBLAMEINFO_MAGIC;
|
|
li->lame_timer = 0;
|
|
li->qtype = qtype;
|
|
ISC_LINK_INIT(li, plink);
|
|
|
|
return (li);
|
|
}
|
|
|
|
static inline void
|
|
free_adblameinfo(dns_adb_t *adb, dns_adblameinfo_t **lameinfo) {
|
|
dns_adblameinfo_t *li;
|
|
|
|
INSIST(lameinfo != NULL && DNS_ADBLAMEINFO_VALID(*lameinfo));
|
|
li = *lameinfo;
|
|
*lameinfo = NULL;
|
|
|
|
INSIST(!ISC_LINK_LINKED(li, plink));
|
|
|
|
dns_name_free(&li->qname, adb->mctx);
|
|
|
|
li->magic = 0;
|
|
|
|
isc_mempool_put(adb->limp, li);
|
|
}
|
|
|
|
static inline dns_adbentry_t *
|
|
new_adbentry(dns_adb_t *adb) {
|
|
dns_adbentry_t *e;
|
|
|
|
e = isc_mempool_get(adb->emp);
|
|
if (e == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
e->magic = DNS_ADBENTRY_MAGIC;
|
|
e->lock_bucket = DNS_ADB_INVALIDBUCKET;
|
|
e->refcnt = 0;
|
|
e->nh = 0;
|
|
e->flags = 0;
|
|
e->udpsize = 0;
|
|
e->edns = 0;
|
|
e->ednsto = 0;
|
|
e->completed = 0;
|
|
e->timeouts = 0;
|
|
e->plain = 0;
|
|
e->plainto = 0;
|
|
e->cookie = NULL;
|
|
e->cookielen = 0;
|
|
e->srtt = (isc_random_uniform(0x1f)) + 1;
|
|
e->lastage = 0;
|
|
e->expires = 0;
|
|
atomic_init(&e->active, 0);
|
|
e->mode = 0;
|
|
atomic_init(&e->quota, adb->quota);
|
|
e->atr = 0.0;
|
|
ISC_LIST_INIT(e->lameinfo);
|
|
ISC_LINK_INIT(e, plink);
|
|
LOCK(&adb->entriescntlock);
|
|
adb->entriescnt++;
|
|
inc_adbstats(adb, dns_adbstats_entriescnt);
|
|
if (!adb->growentries_sent && adb->excl != NULL &&
|
|
adb->entriescnt > (adb->nentries * 8))
|
|
{
|
|
isc_event_t *event = &adb->growentries;
|
|
inc_adb_irefcnt(adb);
|
|
isc_task_send(adb->excl, &event);
|
|
adb->growentries_sent = true;
|
|
}
|
|
UNLOCK(&adb->entriescntlock);
|
|
|
|
return (e);
|
|
}
|
|
|
|
static inline void
|
|
free_adbentry(dns_adb_t *adb, dns_adbentry_t **entry) {
|
|
dns_adbentry_t *e;
|
|
dns_adblameinfo_t *li;
|
|
|
|
INSIST(entry != NULL && DNS_ADBENTRY_VALID(*entry));
|
|
e = *entry;
|
|
*entry = NULL;
|
|
|
|
INSIST(e->lock_bucket == DNS_ADB_INVALIDBUCKET);
|
|
INSIST(e->refcnt == 0);
|
|
INSIST(!ISC_LINK_LINKED(e, plink));
|
|
|
|
e->magic = 0;
|
|
|
|
if (e->cookie != NULL) {
|
|
isc_mem_put(adb->mctx, e->cookie, e->cookielen);
|
|
}
|
|
|
|
li = ISC_LIST_HEAD(e->lameinfo);
|
|
while (li != NULL) {
|
|
ISC_LIST_UNLINK(e->lameinfo, li, plink);
|
|
free_adblameinfo(adb, &li);
|
|
li = ISC_LIST_HEAD(e->lameinfo);
|
|
}
|
|
|
|
isc_mempool_put(adb->emp, e);
|
|
LOCK(&adb->entriescntlock);
|
|
adb->entriescnt--;
|
|
dec_adbstats(adb, dns_adbstats_entriescnt);
|
|
UNLOCK(&adb->entriescntlock);
|
|
}
|
|
|
|
static inline dns_adbfind_t *
|
|
new_adbfind(dns_adb_t *adb) {
|
|
dns_adbfind_t *h;
|
|
|
|
h = isc_mempool_get(adb->ahmp);
|
|
if (h == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Public members.
|
|
*/
|
|
h->magic = 0;
|
|
h->adb = adb;
|
|
h->partial_result = 0;
|
|
h->options = 0;
|
|
h->flags = 0;
|
|
h->result_v4 = ISC_R_UNEXPECTED;
|
|
h->result_v6 = ISC_R_UNEXPECTED;
|
|
ISC_LINK_INIT(h, publink);
|
|
ISC_LINK_INIT(h, plink);
|
|
ISC_LIST_INIT(h->list);
|
|
h->adbname = NULL;
|
|
h->name_bucket = DNS_ADB_INVALIDBUCKET;
|
|
|
|
/*
|
|
* private members
|
|
*/
|
|
isc_mutex_init(&h->lock);
|
|
|
|
ISC_EVENT_INIT(&h->event, sizeof(isc_event_t), 0, 0, 0, NULL, NULL,
|
|
NULL, NULL, h);
|
|
|
|
inc_adb_irefcnt(adb);
|
|
h->magic = DNS_ADBFIND_MAGIC;
|
|
return (h);
|
|
}
|
|
|
|
static inline dns_adbfetch_t *
|
|
new_adbfetch(dns_adb_t *adb) {
|
|
dns_adbfetch_t *f;
|
|
|
|
f = isc_mempool_get(adb->afmp);
|
|
if (f == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
f->magic = 0;
|
|
f->fetch = NULL;
|
|
|
|
dns_rdataset_init(&f->rdataset);
|
|
|
|
f->magic = DNS_ADBFETCH_MAGIC;
|
|
|
|
return (f);
|
|
}
|
|
|
|
static inline void
|
|
free_adbfetch(dns_adb_t *adb, dns_adbfetch_t **fetch) {
|
|
dns_adbfetch_t *f;
|
|
|
|
INSIST(fetch != NULL && DNS_ADBFETCH_VALID(*fetch));
|
|
f = *fetch;
|
|
*fetch = NULL;
|
|
|
|
f->magic = 0;
|
|
|
|
if (dns_rdataset_isassociated(&f->rdataset)) {
|
|
dns_rdataset_disassociate(&f->rdataset);
|
|
}
|
|
|
|
isc_mempool_put(adb->afmp, f);
|
|
}
|
|
|
|
static inline bool
|
|
free_adbfind(dns_adb_t *adb, dns_adbfind_t **findp) {
|
|
dns_adbfind_t *find;
|
|
|
|
INSIST(findp != NULL && DNS_ADBFIND_VALID(*findp));
|
|
find = *findp;
|
|
*findp = NULL;
|
|
|
|
INSIST(!FIND_HAS_ADDRS(find));
|
|
INSIST(!ISC_LINK_LINKED(find, publink));
|
|
INSIST(!ISC_LINK_LINKED(find, plink));
|
|
INSIST(find->name_bucket == DNS_ADB_INVALIDBUCKET);
|
|
INSIST(find->adbname == NULL);
|
|
|
|
find->magic = 0;
|
|
|
|
isc_mutex_destroy(&find->lock);
|
|
isc_mempool_put(adb->ahmp, find);
|
|
return (dec_adb_irefcnt(adb));
|
|
}
|
|
|
|
/*
|
|
* Copy bits from the entry into the newly allocated addrinfo. The entry
|
|
* must be locked, and the reference count must be bumped up by one
|
|
* if this function returns a valid pointer.
|
|
*/
|
|
static inline dns_adbaddrinfo_t *
|
|
new_adbaddrinfo(dns_adb_t *adb, dns_adbentry_t *entry, in_port_t port) {
|
|
dns_adbaddrinfo_t *ai;
|
|
|
|
ai = isc_mempool_get(adb->aimp);
|
|
if (ai == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
ai->magic = DNS_ADBADDRINFO_MAGIC;
|
|
ai->sockaddr = entry->sockaddr;
|
|
isc_sockaddr_setport(&ai->sockaddr, port);
|
|
ai->srtt = entry->srtt;
|
|
ai->flags = entry->flags;
|
|
ai->entry = entry;
|
|
ai->dscp = -1;
|
|
ISC_LINK_INIT(ai, publink);
|
|
|
|
return (ai);
|
|
}
|
|
|
|
static inline void
|
|
free_adbaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **ainfo) {
|
|
dns_adbaddrinfo_t *ai;
|
|
|
|
INSIST(ainfo != NULL && DNS_ADBADDRINFO_VALID(*ainfo));
|
|
ai = *ainfo;
|
|
*ainfo = NULL;
|
|
|
|
INSIST(ai->entry == NULL);
|
|
INSIST(!ISC_LINK_LINKED(ai, publink));
|
|
|
|
ai->magic = 0;
|
|
|
|
isc_mempool_put(adb->aimp, ai);
|
|
}
|
|
|
|
/*
|
|
* Search for the name. NOTE: The bucket is kept locked on both
|
|
* success and failure, so it must always be unlocked by the caller!
|
|
*
|
|
* On the first call to this function, *bucketp must be set to
|
|
* DNS_ADB_INVALIDBUCKET.
|
|
*/
|
|
static inline dns_adbname_t *
|
|
find_name_and_lock(dns_adb_t *adb, const dns_name_t *name, unsigned int options,
|
|
int *bucketp) {
|
|
dns_adbname_t *adbname;
|
|
int bucket;
|
|
|
|
bucket = dns_name_fullhash(name, false) % adb->nnames;
|
|
|
|
if (*bucketp == DNS_ADB_INVALIDBUCKET) {
|
|
LOCK(&adb->namelocks[bucket]);
|
|
*bucketp = bucket;
|
|
} else if (*bucketp != bucket) {
|
|
UNLOCK(&adb->namelocks[*bucketp]);
|
|
LOCK(&adb->namelocks[bucket]);
|
|
*bucketp = bucket;
|
|
}
|
|
|
|
adbname = ISC_LIST_HEAD(adb->names[bucket]);
|
|
while (adbname != NULL) {
|
|
if (!NAME_DEAD(adbname)) {
|
|
if (dns_name_equal(name, &adbname->name) &&
|
|
GLUEHINT_OK(adbname, options) &&
|
|
STARTATZONE_MATCHES(adbname, options))
|
|
{
|
|
return (adbname);
|
|
}
|
|
}
|
|
adbname = ISC_LIST_NEXT(adbname, plink);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Search for the address. NOTE: The bucket is kept locked on both
|
|
* success and failure, so it must always be unlocked by the caller.
|
|
*
|
|
* On the first call to this function, *bucketp must be set to
|
|
* DNS_ADB_INVALIDBUCKET. This will cause a lock to occur. On
|
|
* later calls (within the same "lock path") it can be left alone, so
|
|
* if this function is called multiple times locking is only done if
|
|
* the bucket changes.
|
|
*/
|
|
static inline dns_adbentry_t *
|
|
find_entry_and_lock(dns_adb_t *adb, const isc_sockaddr_t *addr, int *bucketp,
|
|
isc_stdtime_t now) {
|
|
dns_adbentry_t *entry, *entry_next;
|
|
int bucket;
|
|
|
|
bucket = isc_sockaddr_hash(addr, true) % adb->nentries;
|
|
|
|
if (*bucketp == DNS_ADB_INVALIDBUCKET) {
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
*bucketp = bucket;
|
|
} else if (*bucketp != bucket) {
|
|
UNLOCK(&adb->entrylocks[*bucketp]);
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
*bucketp = bucket;
|
|
}
|
|
|
|
/* Search the list, while cleaning up expired entries. */
|
|
for (entry = ISC_LIST_HEAD(adb->entries[bucket]); entry != NULL;
|
|
entry = entry_next)
|
|
{
|
|
entry_next = ISC_LIST_NEXT(entry, plink);
|
|
(void)check_expire_entry(adb, &entry, now);
|
|
if (entry != NULL &&
|
|
(entry->expires == 0 || entry->expires > now) &&
|
|
isc_sockaddr_equal(addr, &entry->sockaddr))
|
|
{
|
|
ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
|
|
ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
|
|
return (entry);
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Entry bucket MUST be locked!
|
|
*/
|
|
static bool
|
|
entry_is_lame(dns_adb_t *adb, dns_adbentry_t *entry, const dns_name_t *qname,
|
|
dns_rdatatype_t qtype, isc_stdtime_t now) {
|
|
dns_adblameinfo_t *li, *next_li;
|
|
bool is_bad;
|
|
|
|
is_bad = false;
|
|
|
|
li = ISC_LIST_HEAD(entry->lameinfo);
|
|
if (li == NULL) {
|
|
return (false);
|
|
}
|
|
while (li != NULL) {
|
|
next_li = ISC_LIST_NEXT(li, plink);
|
|
|
|
/*
|
|
* Has the entry expired?
|
|
*/
|
|
if (li->lame_timer < now) {
|
|
ISC_LIST_UNLINK(entry->lameinfo, li, plink);
|
|
free_adblameinfo(adb, &li);
|
|
}
|
|
|
|
/*
|
|
* Order tests from least to most expensive.
|
|
*
|
|
* We do not break out of the main loop here as
|
|
* we use the loop for house keeping.
|
|
*/
|
|
if (li != NULL && !is_bad && li->qtype == qtype &&
|
|
dns_name_equal(qname, &li->qname))
|
|
{
|
|
is_bad = true;
|
|
}
|
|
|
|
li = next_li;
|
|
}
|
|
|
|
return (is_bad);
|
|
}
|
|
|
|
static void
|
|
log_quota(dns_adbentry_t *entry, const char *fmt, ...) {
|
|
va_list ap;
|
|
char msgbuf[2048];
|
|
char addrbuf[ISC_NETADDR_FORMATSIZE];
|
|
isc_netaddr_t netaddr;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
|
|
isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
|
|
ISC_LOG_INFO,
|
|
"adb: quota %s (%" PRIuFAST32 "/%" PRIuFAST32 "): %s",
|
|
addrbuf, atomic_load_relaxed(&entry->active),
|
|
atomic_load_relaxed(&entry->quota), msgbuf);
|
|
}
|
|
|
|
static void
|
|
copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
|
|
const dns_name_t *qname, dns_rdatatype_t qtype,
|
|
dns_adbname_t *name, isc_stdtime_t now) {
|
|
dns_adbnamehook_t *namehook;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
dns_adbentry_t *entry;
|
|
int bucket;
|
|
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
|
|
if ((find->options & DNS_ADBFIND_INET) != 0) {
|
|
namehook = ISC_LIST_HEAD(name->v4);
|
|
while (namehook != NULL) {
|
|
entry = namehook->entry;
|
|
bucket = entry->lock_bucket;
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
if (dns_adbentry_overquota(entry)) {
|
|
find->options |= (DNS_ADBFIND_LAMEPRUNED |
|
|
DNS_ADBFIND_OVERQUOTA);
|
|
goto nextv4;
|
|
}
|
|
|
|
if (!FIND_RETURNLAME(find) &&
|
|
entry_is_lame(adb, entry, qname, qtype, now)) {
|
|
find->options |= DNS_ADBFIND_LAMEPRUNED;
|
|
goto nextv4;
|
|
}
|
|
addrinfo = new_adbaddrinfo(adb, entry, find->port);
|
|
if (addrinfo == NULL) {
|
|
find->partial_result |= DNS_ADBFIND_INET;
|
|
goto out;
|
|
}
|
|
/*
|
|
* Found a valid entry. Add it to the find's list.
|
|
*/
|
|
inc_entry_refcnt(adb, entry, false);
|
|
ISC_LIST_APPEND(find->list, addrinfo, publink);
|
|
addrinfo = NULL;
|
|
nextv4:
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
namehook = ISC_LIST_NEXT(namehook, plink);
|
|
}
|
|
}
|
|
|
|
if ((find->options & DNS_ADBFIND_INET6) != 0) {
|
|
namehook = ISC_LIST_HEAD(name->v6);
|
|
while (namehook != NULL) {
|
|
entry = namehook->entry;
|
|
bucket = entry->lock_bucket;
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
if (dns_adbentry_overquota(entry)) {
|
|
find->options |= (DNS_ADBFIND_LAMEPRUNED |
|
|
DNS_ADBFIND_OVERQUOTA);
|
|
goto nextv6;
|
|
}
|
|
|
|
if (!FIND_RETURNLAME(find) &&
|
|
entry_is_lame(adb, entry, qname, qtype, now)) {
|
|
find->options |= DNS_ADBFIND_LAMEPRUNED;
|
|
goto nextv6;
|
|
}
|
|
addrinfo = new_adbaddrinfo(adb, entry, find->port);
|
|
if (addrinfo == NULL) {
|
|
find->partial_result |= DNS_ADBFIND_INET6;
|
|
goto out;
|
|
}
|
|
/*
|
|
* Found a valid entry. Add it to the find's list.
|
|
*/
|
|
inc_entry_refcnt(adb, entry, false);
|
|
ISC_LIST_APPEND(find->list, addrinfo, publink);
|
|
addrinfo = NULL;
|
|
nextv6:
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
namehook = ISC_LIST_NEXT(namehook, plink);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (bucket != DNS_ADB_INVALIDBUCKET) {
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
shutdown_task(isc_task_t *task, isc_event_t *ev) {
|
|
dns_adb_t *adb;
|
|
|
|
UNUSED(task);
|
|
|
|
adb = ev->ev_arg;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
isc_event_free(&ev);
|
|
/*
|
|
* Wait for lock around check_exit() call to be released.
|
|
*/
|
|
LOCK(&adb->lock);
|
|
UNLOCK(&adb->lock);
|
|
destroy(adb);
|
|
}
|
|
|
|
/*
|
|
* Name bucket must be locked; adb may be locked; no other locks held.
|
|
*/
|
|
static bool
|
|
check_expire_name(dns_adbname_t **namep, isc_stdtime_t now) {
|
|
dns_adbname_t *name;
|
|
bool result = false;
|
|
|
|
INSIST(namep != NULL && DNS_ADBNAME_VALID(*namep));
|
|
name = *namep;
|
|
|
|
if (NAME_HAS_V4(name) || NAME_HAS_V6(name)) {
|
|
return (result);
|
|
}
|
|
if (NAME_FETCH(name)) {
|
|
return (result);
|
|
}
|
|
if (!EXPIRE_OK(name->expire_v4, now)) {
|
|
return (result);
|
|
}
|
|
if (!EXPIRE_OK(name->expire_v6, now)) {
|
|
return (result);
|
|
}
|
|
if (!EXPIRE_OK(name->expire_target, now)) {
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* The name is empty. Delete it.
|
|
*/
|
|
*namep = NULL;
|
|
result = kill_name(&name, DNS_EVENT_ADBEXPIRED);
|
|
|
|
/*
|
|
* Our caller, or one of its callers, will be calling check_exit() at
|
|
* some point, so we don't need to do it here.
|
|
*/
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Examine the tail entry of the LRU list to see if it expires or is stale
|
|
* (unused for some period); if so, the name entry will be freed. If the ADB
|
|
* is in the overmem condition, the tail and the next to tail entries
|
|
* will be unconditionally removed (unless they have an outstanding fetch).
|
|
* We don't care about a race on 'overmem' at the risk of causing some
|
|
* collateral damage or a small delay in starting cleanup, so we don't bother
|
|
* to lock ADB (if it's not locked).
|
|
*
|
|
* Name bucket must be locked; adb may be locked; no other locks held.
|
|
*/
|
|
static void
|
|
check_stale_name(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
|
|
int victims, max_victims;
|
|
dns_adbname_t *victim, *next_victim;
|
|
bool overmem = isc_mem_isovermem(adb->mctx);
|
|
int scans = 0;
|
|
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
|
|
max_victims = overmem ? 2 : 1;
|
|
|
|
/*
|
|
* We limit the number of scanned entries to 10 (arbitrary choice)
|
|
* in order to avoid examining too many entries when there are many
|
|
* tail entries that have fetches (this should be rare, but could
|
|
* happen).
|
|
*/
|
|
victim = ISC_LIST_TAIL(adb->names[bucket]);
|
|
for (victims = 0; victim != NULL && victims < max_victims && scans < 10;
|
|
victim = next_victim)
|
|
{
|
|
INSIST(!NAME_DEAD(victim));
|
|
scans++;
|
|
next_victim = ISC_LIST_PREV(victim, plink);
|
|
(void)check_expire_name(&victim, now);
|
|
if (victim == NULL) {
|
|
victims++;
|
|
goto next;
|
|
}
|
|
|
|
if (!NAME_FETCH(victim) &&
|
|
(overmem || victim->last_used + ADB_STALE_MARGIN <= now))
|
|
{
|
|
RUNTIME_CHECK(
|
|
!kill_name(&victim, DNS_EVENT_ADBCANCELED));
|
|
victims++;
|
|
}
|
|
|
|
next:
|
|
if (!overmem) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Entry bucket must be locked; adb may be locked; no other locks held.
|
|
*/
|
|
static bool
|
|
check_expire_entry(dns_adb_t *adb, dns_adbentry_t **entryp, isc_stdtime_t now) {
|
|
dns_adbentry_t *entry;
|
|
bool result = false;
|
|
|
|
INSIST(entryp != NULL && DNS_ADBENTRY_VALID(*entryp));
|
|
entry = *entryp;
|
|
|
|
if (entry->refcnt != 0) {
|
|
return (result);
|
|
}
|
|
|
|
if (entry->expires == 0 || entry->expires > now) {
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* The entry is not in use. Delete it.
|
|
*/
|
|
*entryp = NULL;
|
|
DP(DEF_LEVEL, "killing entry %p", entry);
|
|
INSIST(ISC_LINK_LINKED(entry, plink));
|
|
result = unlink_entry(adb, entry);
|
|
free_adbentry(adb, &entry);
|
|
if (result) {
|
|
dec_adb_irefcnt(adb);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* ADB must be locked, and no other locks held.
|
|
*/
|
|
static bool
|
|
cleanup_names(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
|
|
dns_adbname_t *name;
|
|
dns_adbname_t *next_name;
|
|
bool result = false;
|
|
|
|
DP(CLEAN_LEVEL, "cleaning name bucket %d", bucket);
|
|
|
|
LOCK(&adb->namelocks[bucket]);
|
|
if (adb->name_sd[bucket]) {
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
return (result);
|
|
}
|
|
|
|
name = ISC_LIST_HEAD(adb->names[bucket]);
|
|
while (name != NULL) {
|
|
next_name = ISC_LIST_NEXT(name, plink);
|
|
INSIST(!result);
|
|
result = check_expire_namehooks(name, now);
|
|
if (!result) {
|
|
result = check_expire_name(&name, now);
|
|
}
|
|
name = next_name;
|
|
}
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* ADB must be locked, and no other locks held.
|
|
*/
|
|
static bool
|
|
cleanup_entries(dns_adb_t *adb, int bucket, isc_stdtime_t now) {
|
|
dns_adbentry_t *entry, *next_entry;
|
|
bool result = false;
|
|
|
|
DP(CLEAN_LEVEL, "cleaning entry bucket %d", bucket);
|
|
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
entry = ISC_LIST_HEAD(adb->entries[bucket]);
|
|
while (entry != NULL) {
|
|
next_entry = ISC_LIST_NEXT(entry, plink);
|
|
INSIST(!result);
|
|
result = check_expire_entry(adb, &entry, now);
|
|
entry = next_entry;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
destroy(dns_adb_t *adb) {
|
|
adb->magic = 0;
|
|
|
|
isc_task_detach(&adb->task);
|
|
if (adb->excl != NULL) {
|
|
isc_task_detach(&adb->excl);
|
|
}
|
|
|
|
isc_mempool_destroy(&adb->nmp);
|
|
isc_mempool_destroy(&adb->nhmp);
|
|
isc_mempool_destroy(&adb->limp);
|
|
isc_mempool_destroy(&adb->emp);
|
|
isc_mempool_destroy(&adb->ahmp);
|
|
isc_mempool_destroy(&adb->aimp);
|
|
isc_mempool_destroy(&adb->afmp);
|
|
|
|
isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entries,
|
|
sizeof(*adb->entries) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->deadentries,
|
|
sizeof(*adb->deadentries) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entrylocks,
|
|
sizeof(*adb->entrylocks) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entry_sd,
|
|
sizeof(*adb->entry_sd) * adb->nentries);
|
|
isc_mem_put(adb->mctx, adb->entry_refcnt,
|
|
sizeof(*adb->entry_refcnt) * adb->nentries);
|
|
|
|
isc_mutexblock_destroy(adb->namelocks, adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->names, sizeof(*adb->names) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->deadnames,
|
|
sizeof(*adb->deadnames) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->namelocks,
|
|
sizeof(*adb->namelocks) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->name_sd,
|
|
sizeof(*adb->name_sd) * adb->nnames);
|
|
isc_mem_put(adb->mctx, adb->name_refcnt,
|
|
sizeof(*adb->name_refcnt) * adb->nnames);
|
|
|
|
isc_mutex_destroy(&adb->reflock);
|
|
isc_mutex_destroy(&adb->lock);
|
|
isc_mutex_destroy(&adb->mplock);
|
|
isc_mutex_destroy(&adb->overmemlock);
|
|
isc_mutex_destroy(&adb->entriescntlock);
|
|
isc_mutex_destroy(&adb->namescntlock);
|
|
|
|
isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
|
|
}
|
|
|
|
/*
|
|
* Public functions.
|
|
*/
|
|
|
|
isc_result_t
|
|
dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr,
|
|
isc_taskmgr_t *taskmgr, dns_adb_t **newadb) {
|
|
dns_adb_t *adb;
|
|
isc_result_t result;
|
|
unsigned int i;
|
|
|
|
REQUIRE(mem != NULL);
|
|
REQUIRE(view != NULL);
|
|
REQUIRE(timermgr != NULL); /* this is actually unused */
|
|
REQUIRE(taskmgr != NULL);
|
|
REQUIRE(newadb != NULL && *newadb == NULL);
|
|
|
|
UNUSED(timermgr);
|
|
|
|
adb = isc_mem_get(mem, sizeof(dns_adb_t));
|
|
|
|
/*
|
|
* Initialize things here that cannot fail, and especially things
|
|
* that must be NULL for the error return to work properly.
|
|
*/
|
|
adb->magic = 0;
|
|
adb->erefcnt = 1;
|
|
adb->irefcnt = 0;
|
|
adb->nmp = NULL;
|
|
adb->nhmp = NULL;
|
|
adb->limp = NULL;
|
|
adb->emp = NULL;
|
|
adb->ahmp = NULL;
|
|
adb->aimp = NULL;
|
|
adb->afmp = NULL;
|
|
adb->task = NULL;
|
|
adb->excl = NULL;
|
|
adb->mctx = NULL;
|
|
adb->view = view;
|
|
adb->taskmgr = taskmgr;
|
|
adb->next_cleanbucket = 0;
|
|
ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, 0, NULL,
|
|
NULL, NULL, NULL, NULL);
|
|
adb->cevent_out = false;
|
|
adb->shutting_down = false;
|
|
ISC_LIST_INIT(adb->whenshutdown);
|
|
|
|
adb->nentries = nbuckets[0];
|
|
adb->entriescnt = 0;
|
|
adb->entries = NULL;
|
|
adb->deadentries = NULL;
|
|
adb->entry_sd = NULL;
|
|
adb->entry_refcnt = NULL;
|
|
adb->entrylocks = NULL;
|
|
ISC_EVENT_INIT(&adb->growentries, sizeof(adb->growentries), 0, NULL,
|
|
DNS_EVENT_ADBGROWENTRIES, grow_entries, adb, adb, NULL,
|
|
NULL);
|
|
adb->growentries_sent = false;
|
|
|
|
adb->quota = 0;
|
|
adb->atr_freq = 0;
|
|
adb->atr_low = 0.0;
|
|
adb->atr_high = 0.0;
|
|
adb->atr_discount = 0.0;
|
|
|
|
adb->nnames = nbuckets[0];
|
|
adb->namescnt = 0;
|
|
adb->names = NULL;
|
|
adb->deadnames = NULL;
|
|
adb->name_sd = NULL;
|
|
adb->name_refcnt = NULL;
|
|
adb->namelocks = NULL;
|
|
ISC_EVENT_INIT(&adb->grownames, sizeof(adb->grownames), 0, NULL,
|
|
DNS_EVENT_ADBGROWNAMES, grow_names, adb, adb, NULL,
|
|
NULL);
|
|
adb->grownames_sent = false;
|
|
|
|
result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl);
|
|
if (result != ISC_R_SUCCESS) {
|
|
DP(DEF_LEVEL,
|
|
"adb: task-exclusive mode unavailable, "
|
|
"initializing table sizes to %u\n",
|
|
nbuckets[11]);
|
|
adb->nentries = nbuckets[11];
|
|
adb->nnames = nbuckets[11];
|
|
}
|
|
|
|
isc_mem_attach(mem, &adb->mctx);
|
|
|
|
isc_mutex_init(&adb->lock);
|
|
isc_mutex_init(&adb->mplock);
|
|
isc_mutex_init(&adb->reflock);
|
|
isc_mutex_init(&adb->overmemlock);
|
|
isc_mutex_init(&adb->entriescntlock);
|
|
isc_mutex_init(&adb->namescntlock);
|
|
|
|
#define ALLOCENTRY(adb, el) \
|
|
do { \
|
|
(adb)->el = isc_mem_get((adb)->mctx, \
|
|
sizeof(*(adb)->el) * (adb)->nentries); \
|
|
if ((adb)->el == NULL) { \
|
|
result = ISC_R_NOMEMORY; \
|
|
goto fail1; \
|
|
} \
|
|
} while (0)
|
|
ALLOCENTRY(adb, entries);
|
|
ALLOCENTRY(adb, deadentries);
|
|
ALLOCENTRY(adb, entrylocks);
|
|
ALLOCENTRY(adb, entry_sd);
|
|
ALLOCENTRY(adb, entry_refcnt);
|
|
#undef ALLOCENTRY
|
|
|
|
#define ALLOCNAME(adb, el) \
|
|
do { \
|
|
(adb)->el = isc_mem_get((adb)->mctx, \
|
|
sizeof(*(adb)->el) * (adb)->nnames); \
|
|
if ((adb)->el == NULL) { \
|
|
result = ISC_R_NOMEMORY; \
|
|
goto fail1; \
|
|
} \
|
|
} while (0)
|
|
ALLOCNAME(adb, names);
|
|
ALLOCNAME(adb, deadnames);
|
|
ALLOCNAME(adb, namelocks);
|
|
ALLOCNAME(adb, name_sd);
|
|
ALLOCNAME(adb, name_refcnt);
|
|
#undef ALLOCNAME
|
|
|
|
/*
|
|
* Initialize the bucket locks for names and elements.
|
|
* May as well initialize the list heads, too.
|
|
*/
|
|
isc_mutexblock_init(adb->namelocks, adb->nnames);
|
|
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
ISC_LIST_INIT(adb->names[i]);
|
|
ISC_LIST_INIT(adb->deadnames[i]);
|
|
adb->name_sd[i] = false;
|
|
adb->name_refcnt[i] = 0;
|
|
adb->irefcnt++;
|
|
}
|
|
for (i = 0; i < adb->nentries; i++) {
|
|
ISC_LIST_INIT(adb->entries[i]);
|
|
ISC_LIST_INIT(adb->deadentries[i]);
|
|
adb->entry_sd[i] = false;
|
|
adb->entry_refcnt[i] = 0;
|
|
adb->irefcnt++;
|
|
}
|
|
isc_mutexblock_init(adb->entrylocks, adb->nentries);
|
|
|
|
/*
|
|
* Memory pools
|
|
*/
|
|
#define MPINIT(t, p, n) \
|
|
do { \
|
|
isc_mempool_create(mem, sizeof(t), &(p)); \
|
|
isc_mempool_setfreemax((p), FREE_ITEMS); \
|
|
isc_mempool_setfillcount((p), FILL_COUNT); \
|
|
isc_mempool_setname((p), n); \
|
|
isc_mempool_associatelock((p), &adb->mplock); \
|
|
} while (0)
|
|
|
|
MPINIT(dns_adbname_t, adb->nmp, "adbname");
|
|
MPINIT(dns_adbnamehook_t, adb->nhmp, "adbnamehook");
|
|
MPINIT(dns_adblameinfo_t, adb->limp, "adblameinfo");
|
|
MPINIT(dns_adbentry_t, adb->emp, "adbentry");
|
|
MPINIT(dns_adbfind_t, adb->ahmp, "adbfind");
|
|
MPINIT(dns_adbaddrinfo_t, adb->aimp, "adbaddrinfo");
|
|
MPINIT(dns_adbfetch_t, adb->afmp, "adbfetch");
|
|
|
|
#undef MPINIT
|
|
|
|
/*
|
|
* Allocate an internal task.
|
|
*/
|
|
result = isc_task_create(adb->taskmgr, 0, &adb->task);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail2;
|
|
}
|
|
|
|
isc_task_setname(adb->task, "ADB", adb);
|
|
|
|
result = isc_stats_create(adb->mctx, &view->adbstats, dns_adbstats_max);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail2;
|
|
}
|
|
|
|
set_adbstat(adb, adb->nentries, dns_adbstats_nentries);
|
|
set_adbstat(adb, adb->nnames, dns_adbstats_nnames);
|
|
|
|
/*
|
|
* Normal return.
|
|
*/
|
|
adb->magic = DNS_ADB_MAGIC;
|
|
*newadb = adb;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
fail2:
|
|
if (adb->task != NULL) {
|
|
isc_task_detach(&adb->task);
|
|
}
|
|
|
|
/* clean up entrylocks */
|
|
isc_mutexblock_destroy(adb->entrylocks, adb->nentries);
|
|
isc_mutexblock_destroy(adb->namelocks, adb->nnames);
|
|
|
|
fail1: /* clean up only allocated memory */
|
|
if (adb->entries != NULL) {
|
|
isc_mem_put(adb->mctx, adb->entries,
|
|
sizeof(*adb->entries) * adb->nentries);
|
|
}
|
|
if (adb->deadentries != NULL) {
|
|
isc_mem_put(adb->mctx, adb->deadentries,
|
|
sizeof(*adb->deadentries) * adb->nentries);
|
|
}
|
|
if (adb->entrylocks != NULL) {
|
|
isc_mem_put(adb->mctx, adb->entrylocks,
|
|
sizeof(*adb->entrylocks) * adb->nentries);
|
|
}
|
|
if (adb->entry_sd != NULL) {
|
|
isc_mem_put(adb->mctx, adb->entry_sd,
|
|
sizeof(*adb->entry_sd) * adb->nentries);
|
|
}
|
|
if (adb->entry_refcnt != NULL) {
|
|
isc_mem_put(adb->mctx, adb->entry_refcnt,
|
|
sizeof(*adb->entry_refcnt) * adb->nentries);
|
|
}
|
|
if (adb->names != NULL) {
|
|
isc_mem_put(adb->mctx, adb->names,
|
|
sizeof(*adb->names) * adb->nnames);
|
|
}
|
|
if (adb->deadnames != NULL) {
|
|
isc_mem_put(adb->mctx, adb->deadnames,
|
|
sizeof(*adb->deadnames) * adb->nnames);
|
|
}
|
|
if (adb->namelocks != NULL) {
|
|
isc_mem_put(adb->mctx, adb->namelocks,
|
|
sizeof(*adb->namelocks) * adb->nnames);
|
|
}
|
|
if (adb->name_sd != NULL) {
|
|
isc_mem_put(adb->mctx, adb->name_sd,
|
|
sizeof(*adb->name_sd) * adb->nnames);
|
|
}
|
|
if (adb->name_refcnt != NULL) {
|
|
isc_mem_put(adb->mctx, adb->name_refcnt,
|
|
sizeof(*adb->name_refcnt) * adb->nnames);
|
|
}
|
|
if (adb->nmp != NULL) {
|
|
isc_mempool_destroy(&adb->nmp);
|
|
}
|
|
if (adb->nhmp != NULL) {
|
|
isc_mempool_destroy(&adb->nhmp);
|
|
}
|
|
if (adb->limp != NULL) {
|
|
isc_mempool_destroy(&adb->limp);
|
|
}
|
|
if (adb->emp != NULL) {
|
|
isc_mempool_destroy(&adb->emp);
|
|
}
|
|
if (adb->ahmp != NULL) {
|
|
isc_mempool_destroy(&adb->ahmp);
|
|
}
|
|
if (adb->aimp != NULL) {
|
|
isc_mempool_destroy(&adb->aimp);
|
|
}
|
|
if (adb->afmp != NULL) {
|
|
isc_mempool_destroy(&adb->afmp);
|
|
}
|
|
|
|
isc_mutex_destroy(&adb->namescntlock);
|
|
isc_mutex_destroy(&adb->entriescntlock);
|
|
isc_mutex_destroy(&adb->overmemlock);
|
|
isc_mutex_destroy(&adb->reflock);
|
|
isc_mutex_destroy(&adb->mplock);
|
|
isc_mutex_destroy(&adb->lock);
|
|
if (adb->excl != NULL) {
|
|
isc_task_detach(&adb->excl);
|
|
}
|
|
isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t));
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbx) {
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(adbx != NULL && *adbx == NULL);
|
|
|
|
inc_adb_erefcnt(adb);
|
|
*adbx = adb;
|
|
}
|
|
|
|
void
|
|
dns_adb_detach(dns_adb_t **adbx) {
|
|
dns_adb_t *adb;
|
|
bool need_exit_check;
|
|
|
|
REQUIRE(adbx != NULL && DNS_ADB_VALID(*adbx));
|
|
|
|
adb = *adbx;
|
|
*adbx = NULL;
|
|
|
|
LOCK(&adb->reflock);
|
|
INSIST(adb->erefcnt > 0);
|
|
adb->erefcnt--;
|
|
need_exit_check = (adb->erefcnt == 0 && adb->irefcnt == 0);
|
|
UNLOCK(&adb->reflock);
|
|
|
|
if (need_exit_check) {
|
|
LOCK(&adb->lock);
|
|
INSIST(adb->shutting_down);
|
|
check_exit(adb);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp) {
|
|
isc_task_t *tclone;
|
|
isc_event_t *event;
|
|
bool zeroirefcnt;
|
|
|
|
/*
|
|
* Send '*eventp' to 'task' when 'adb' has shutdown.
|
|
*/
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(eventp != NULL);
|
|
|
|
event = *eventp;
|
|
*eventp = NULL;
|
|
|
|
LOCK(&adb->lock);
|
|
LOCK(&adb->reflock);
|
|
|
|
zeroirefcnt = (adb->irefcnt == 0);
|
|
|
|
if (adb->shutting_down && zeroirefcnt &&
|
|
isc_mempool_getallocated(adb->ahmp) == 0)
|
|
{
|
|
/*
|
|
* We're already shutdown. Send the event.
|
|
*/
|
|
event->ev_sender = adb;
|
|
isc_task_send(task, &event);
|
|
} else {
|
|
tclone = NULL;
|
|
isc_task_attach(task, &tclone);
|
|
event->ev_sender = tclone;
|
|
ISC_LIST_APPEND(adb->whenshutdown, event, ev_link);
|
|
}
|
|
|
|
UNLOCK(&adb->reflock);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
static void
|
|
shutdown_stage2(isc_task_t *task, isc_event_t *event) {
|
|
dns_adb_t *adb;
|
|
|
|
UNUSED(task);
|
|
|
|
adb = event->ev_arg;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
LOCK(&adb->lock);
|
|
INSIST(adb->shutting_down);
|
|
adb->cevent_out = false;
|
|
(void)shutdown_names(adb);
|
|
(void)shutdown_entries(adb);
|
|
if (dec_adb_irefcnt(adb)) {
|
|
check_exit(adb);
|
|
}
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
void
|
|
dns_adb_shutdown(dns_adb_t *adb) {
|
|
isc_event_t *event;
|
|
|
|
/*
|
|
* Shutdown 'adb'.
|
|
*/
|
|
|
|
LOCK(&adb->lock);
|
|
|
|
if (!adb->shutting_down) {
|
|
adb->shutting_down = true;
|
|
isc_mem_setwater(adb->mctx, water, adb, 0, 0);
|
|
/*
|
|
* Isolate shutdown_names and shutdown_entries calls.
|
|
*/
|
|
inc_adb_irefcnt(adb);
|
|
ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL,
|
|
DNS_EVENT_ADBCONTROL, shutdown_stage2, adb, adb,
|
|
NULL, NULL);
|
|
adb->cevent_out = true;
|
|
event = &adb->cevent;
|
|
isc_task_send(adb->task, &event);
|
|
}
|
|
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
|
|
void *arg, const dns_name_t *name, const dns_name_t *qname,
|
|
dns_rdatatype_t qtype, unsigned int options,
|
|
isc_stdtime_t now, dns_name_t *target, in_port_t port,
|
|
unsigned int depth, isc_counter_t *qc,
|
|
dns_adbfind_t **findp) {
|
|
dns_adbfind_t *find;
|
|
dns_adbname_t *adbname;
|
|
int bucket;
|
|
bool want_event, start_at_zone, alias, have_address;
|
|
isc_result_t result;
|
|
unsigned int wanted_addresses;
|
|
unsigned int wanted_fetches;
|
|
unsigned int query_pending;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
if (task != NULL) {
|
|
REQUIRE(action != NULL);
|
|
}
|
|
REQUIRE(name != NULL);
|
|
REQUIRE(qname != NULL);
|
|
REQUIRE(findp != NULL && *findp == NULL);
|
|
REQUIRE(target == NULL || dns_name_hasbuffer(target));
|
|
|
|
REQUIRE((options & DNS_ADBFIND_ADDRESSMASK) != 0);
|
|
|
|
result = ISC_R_UNEXPECTED;
|
|
POST(result);
|
|
wanted_addresses = (options & DNS_ADBFIND_ADDRESSMASK);
|
|
wanted_fetches = 0;
|
|
query_pending = 0;
|
|
want_event = false;
|
|
start_at_zone = false;
|
|
alias = false;
|
|
|
|
if (now == 0) {
|
|
isc_stdtime_get(&now);
|
|
}
|
|
|
|
/*
|
|
* XXXMLG Move this comment somewhere else!
|
|
*
|
|
* Look up the name in our internal database.
|
|
*
|
|
* Possibilities: Note that these are not always exclusive.
|
|
*
|
|
* No name found. In this case, allocate a new name header and
|
|
* an initial namehook or two. If any of these allocations
|
|
* fail, clean up and return ISC_R_NOMEMORY.
|
|
*
|
|
* Name found, valid addresses present. Allocate one addrinfo
|
|
* structure for each found and append it to the linked list
|
|
* of addresses for this header.
|
|
*
|
|
* Name found, queries pending. In this case, if a task was
|
|
* passed in, allocate a job id, attach it to the name's job
|
|
* list and remember to tell the caller that there will be
|
|
* more info coming later.
|
|
*/
|
|
|
|
find = new_adbfind(adb);
|
|
if (find == NULL) {
|
|
return (ISC_R_NOMEMORY);
|
|
}
|
|
|
|
find->port = port;
|
|
|
|
/*
|
|
* Remember what types of addresses we are interested in.
|
|
*/
|
|
find->options = options;
|
|
find->flags |= wanted_addresses;
|
|
if (FIND_WANTEVENT(find)) {
|
|
REQUIRE(task != NULL);
|
|
}
|
|
|
|
if (isc_log_wouldlog(dns_lctx, DEF_LEVEL)) {
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
} else {
|
|
namebuf[0] = 0;
|
|
}
|
|
|
|
/*
|
|
* Try to see if we know anything about this name at all.
|
|
*/
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
adbname = find_name_and_lock(adb, name, find->options, &bucket);
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
if (adb->name_sd[bucket]) {
|
|
DP(DEF_LEVEL, "dns_adb_createfind: returning "
|
|
"ISC_R_SHUTTINGDOWN");
|
|
RUNTIME_CHECK(!free_adbfind(adb, &find));
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Nothing found. Allocate a new adbname structure for this name.
|
|
*/
|
|
if (adbname == NULL) {
|
|
/*
|
|
* See if there is any stale name at the end of list, and purge
|
|
* it if so.
|
|
*/
|
|
check_stale_name(adb, bucket, now);
|
|
|
|
adbname = new_adbname(adb, name);
|
|
if (adbname == NULL) {
|
|
RUNTIME_CHECK(!free_adbfind(adb, &find));
|
|
result = ISC_R_NOMEMORY;
|
|
goto out;
|
|
}
|
|
link_name(adb, bucket, adbname);
|
|
if (FIND_HINTOK(find)) {
|
|
adbname->flags |= NAME_HINT_OK;
|
|
}
|
|
if (FIND_GLUEOK(find)) {
|
|
adbname->flags |= NAME_GLUE_OK;
|
|
}
|
|
if (FIND_STARTATZONE(find)) {
|
|
adbname->flags |= NAME_STARTATZONE;
|
|
}
|
|
} else {
|
|
/* Move this name forward in the LRU list */
|
|
ISC_LIST_UNLINK(adb->names[bucket], adbname, plink);
|
|
ISC_LIST_PREPEND(adb->names[bucket], adbname, plink);
|
|
}
|
|
adbname->last_used = now;
|
|
|
|
/*
|
|
* Expire old entries, etc.
|
|
*/
|
|
RUNTIME_CHECK(!check_expire_namehooks(adbname, now));
|
|
|
|
/*
|
|
* Do we know that the name is an alias?
|
|
*/
|
|
if (!EXPIRE_OK(adbname->expire_target, now)) {
|
|
/*
|
|
* Yes, it is.
|
|
*/
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: name %s (%p) is an alias (cached)",
|
|
namebuf, adbname);
|
|
alias = true;
|
|
goto post_copy;
|
|
}
|
|
|
|
/*
|
|
* Try to populate the name from the database and/or
|
|
* start fetches. First try looking for an A record
|
|
* in the database.
|
|
*/
|
|
if (!NAME_HAS_V4(adbname) && EXPIRE_OK(adbname->expire_v4, now) &&
|
|
WANT_INET(wanted_addresses))
|
|
{
|
|
result = dbfind_name(adbname, now, dns_rdatatype_a);
|
|
if (result == ISC_R_SUCCESS) {
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: found A for name %s (%p) in db",
|
|
namebuf, adbname);
|
|
goto v6;
|
|
}
|
|
|
|
/*
|
|
* Did we get a CNAME or DNAME?
|
|
*/
|
|
if (result == DNS_R_ALIAS) {
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: name %s (%p) is an alias",
|
|
namebuf, adbname);
|
|
alias = true;
|
|
goto post_copy;
|
|
}
|
|
|
|
/*
|
|
* If the name doesn't exist at all, don't bother with
|
|
* v6 queries; they won't work.
|
|
*
|
|
* If the name does exist but we didn't get our data, go
|
|
* ahead and try AAAA.
|
|
*
|
|
* If the result is neither of these, try a fetch for A.
|
|
*/
|
|
if (NXDOMAIN_RESULT(result)) {
|
|
goto fetch;
|
|
} else if (NXRRSET_RESULT(result)) {
|
|
goto v6;
|
|
}
|
|
|
|
if (!NAME_FETCH_V4(adbname)) {
|
|
wanted_fetches |= DNS_ADBFIND_INET;
|
|
}
|
|
}
|
|
|
|
v6:
|
|
if (!NAME_HAS_V6(adbname) && EXPIRE_OK(adbname->expire_v6, now) &&
|
|
WANT_INET6(wanted_addresses))
|
|
{
|
|
result = dbfind_name(adbname, now, dns_rdatatype_aaaa);
|
|
if (result == ISC_R_SUCCESS) {
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: found AAAA for name %s (%p)",
|
|
namebuf, adbname);
|
|
goto fetch;
|
|
}
|
|
|
|
/*
|
|
* Did we get a CNAME or DNAME?
|
|
*/
|
|
if (result == DNS_R_ALIAS) {
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: name %s (%p) is an alias",
|
|
namebuf, adbname);
|
|
alias = true;
|
|
goto post_copy;
|
|
}
|
|
|
|
/*
|
|
* Listen to negative cache hints, and don't start
|
|
* another query.
|
|
*/
|
|
if (NCACHE_RESULT(result) || AUTH_NX(result)) {
|
|
goto fetch;
|
|
}
|
|
|
|
if (!NAME_FETCH_V6(adbname)) {
|
|
wanted_fetches |= DNS_ADBFIND_INET6;
|
|
}
|
|
}
|
|
|
|
fetch:
|
|
if ((WANT_INET(wanted_addresses) && NAME_HAS_V4(adbname)) ||
|
|
(WANT_INET6(wanted_addresses) && NAME_HAS_V6(adbname)))
|
|
{
|
|
have_address = true;
|
|
} else {
|
|
have_address = false;
|
|
}
|
|
if (wanted_fetches != 0 && !(FIND_AVOIDFETCHES(find) && have_address) &&
|
|
!FIND_NOFETCH(find))
|
|
{
|
|
/*
|
|
* We're missing at least one address family. Either the
|
|
* caller hasn't instructed us to avoid fetches, or we don't
|
|
* know anything about any of the address families that would
|
|
* be acceptable so we have to launch fetches.
|
|
*/
|
|
|
|
if (FIND_STARTATZONE(find)) {
|
|
start_at_zone = true;
|
|
}
|
|
|
|
/*
|
|
* Start V4.
|
|
*/
|
|
if (WANT_INET(wanted_fetches) &&
|
|
fetch_name(adbname, start_at_zone, depth, qc,
|
|
dns_rdatatype_a) == ISC_R_SUCCESS)
|
|
{
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: "
|
|
"started A fetch for name %s (%p)",
|
|
namebuf, adbname);
|
|
}
|
|
|
|
/*
|
|
* Start V6.
|
|
*/
|
|
if (WANT_INET6(wanted_fetches) &&
|
|
fetch_name(adbname, start_at_zone, depth, qc,
|
|
dns_rdatatype_aaaa) == ISC_R_SUCCESS)
|
|
{
|
|
DP(DEF_LEVEL,
|
|
"dns_adb_createfind: "
|
|
"started AAAA fetch for name %s (%p)",
|
|
namebuf, adbname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Run through the name and copy out the bits we are
|
|
* interested in.
|
|
*/
|
|
copy_namehook_lists(adb, find, qname, qtype, adbname, now);
|
|
|
|
post_copy:
|
|
if (NAME_FETCH_V4(adbname)) {
|
|
query_pending |= DNS_ADBFIND_INET;
|
|
}
|
|
if (NAME_FETCH_V6(adbname)) {
|
|
query_pending |= DNS_ADBFIND_INET6;
|
|
}
|
|
|
|
/*
|
|
* Attach to the name's query list if there are queries
|
|
* already running, and we have been asked to.
|
|
*/
|
|
want_event = true;
|
|
if (!FIND_WANTEVENT(find)) {
|
|
want_event = false;
|
|
}
|
|
if (FIND_WANTEMPTYEVENT(find) && FIND_HAS_ADDRS(find)) {
|
|
want_event = false;
|
|
}
|
|
if ((wanted_addresses & query_pending) == 0) {
|
|
want_event = false;
|
|
}
|
|
if (alias) {
|
|
want_event = false;
|
|
}
|
|
if (want_event) {
|
|
find->adbname = adbname;
|
|
find->name_bucket = bucket;
|
|
bool empty = ISC_LIST_EMPTY(adbname->finds);
|
|
ISC_LIST_APPEND(adbname->finds, find, plink);
|
|
find->query_pending = (query_pending & wanted_addresses);
|
|
find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
|
|
find->flags |= (find->query_pending & DNS_ADBFIND_ADDRESSMASK);
|
|
DP(DEF_LEVEL,
|
|
"createfind: attaching find %p to adbname "
|
|
"%p %d",
|
|
find, adbname, empty);
|
|
} else {
|
|
/*
|
|
* Remove the flag so the caller knows there will never
|
|
* be an event, and set internal flags to fake that
|
|
* the event was sent and freed, so dns_adb_destroyfind() will
|
|
* do the right thing.
|
|
*/
|
|
find->query_pending = (query_pending & wanted_addresses);
|
|
find->options &= ~DNS_ADBFIND_WANTEVENT;
|
|
find->flags |= (FIND_EVENT_SENT | FIND_EVENT_FREED);
|
|
find->flags &= ~DNS_ADBFIND_ADDRESSMASK;
|
|
}
|
|
|
|
find->partial_result |= (adbname->partial_result & wanted_addresses);
|
|
if (alias) {
|
|
if (target != NULL) {
|
|
dns_name_copynf(&adbname->target, target);
|
|
}
|
|
result = DNS_R_ALIAS;
|
|
} else {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Copy out error flags from the name structure into the find.
|
|
*/
|
|
find->result_v4 = find_err_map[adbname->fetch_err];
|
|
find->result_v6 = find_err_map[adbname->fetch6_err];
|
|
|
|
out:
|
|
if (find != NULL) {
|
|
*findp = find;
|
|
|
|
if (want_event) {
|
|
isc_task_t *taskp;
|
|
|
|
INSIST((find->flags & DNS_ADBFIND_ADDRESSMASK) != 0);
|
|
taskp = NULL;
|
|
isc_task_attach(task, &taskp);
|
|
find->event.ev_sender = taskp;
|
|
find->event.ev_action = action;
|
|
find->event.ev_arg = arg;
|
|
}
|
|
}
|
|
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_adb_destroyfind(dns_adbfind_t **findp) {
|
|
dns_adbfind_t *find;
|
|
dns_adbentry_t *entry;
|
|
dns_adbaddrinfo_t *ai;
|
|
int bucket;
|
|
dns_adb_t *adb;
|
|
bool overmem;
|
|
|
|
REQUIRE(findp != NULL && DNS_ADBFIND_VALID(*findp));
|
|
find = *findp;
|
|
*findp = NULL;
|
|
|
|
LOCK(&find->lock);
|
|
|
|
DP(DEF_LEVEL, "dns_adb_destroyfind on find %p", find);
|
|
|
|
adb = find->adb;
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
|
|
REQUIRE(FIND_EVENTFREED(find));
|
|
|
|
bucket = find->name_bucket;
|
|
INSIST(bucket == DNS_ADB_INVALIDBUCKET);
|
|
|
|
UNLOCK(&find->lock);
|
|
|
|
/*
|
|
* The find doesn't exist on any list, and nothing is locked.
|
|
* Return the find to the memory pool, and decrement the adb's
|
|
* reference count.
|
|
*/
|
|
overmem = isc_mem_isovermem(adb->mctx);
|
|
ai = ISC_LIST_HEAD(find->list);
|
|
while (ai != NULL) {
|
|
ISC_LIST_UNLINK(find->list, ai, publink);
|
|
entry = ai->entry;
|
|
ai->entry = NULL;
|
|
INSIST(DNS_ADBENTRY_VALID(entry));
|
|
RUNTIME_CHECK(!dec_entry_refcnt(adb, overmem, entry, true));
|
|
free_adbaddrinfo(adb, &ai);
|
|
ai = ISC_LIST_HEAD(find->list);
|
|
}
|
|
|
|
/*
|
|
* WARNING: The find is freed with the adb locked. This is done
|
|
* to avoid a race condition where we free the find, some other
|
|
* thread tests to see if it should be destroyed, detects it should
|
|
* be, destroys it, and then we try to lock it for our check, but the
|
|
* lock is destroyed.
|
|
*/
|
|
LOCK(&adb->lock);
|
|
if (free_adbfind(adb, &find)) {
|
|
check_exit(adb);
|
|
}
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
void
|
|
dns_adb_cancelfind(dns_adbfind_t *find) {
|
|
isc_event_t *ev;
|
|
isc_task_t *task;
|
|
dns_adb_t *adb;
|
|
int bucket;
|
|
int unlock_bucket;
|
|
|
|
LOCK(&find->lock);
|
|
|
|
DP(DEF_LEVEL, "dns_adb_cancelfind on find %p", find);
|
|
|
|
adb = find->adb;
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
|
|
REQUIRE(!FIND_EVENTFREED(find));
|
|
REQUIRE(FIND_WANTEVENT(find));
|
|
|
|
bucket = find->name_bucket;
|
|
if (bucket == DNS_ADB_INVALIDBUCKET) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* We need to get the adbname's lock to unlink the find.
|
|
*/
|
|
unlock_bucket = bucket;
|
|
violate_locking_hierarchy(&find->lock, &adb->namelocks[unlock_bucket]);
|
|
bucket = find->name_bucket;
|
|
if (bucket != DNS_ADB_INVALIDBUCKET) {
|
|
ISC_LIST_UNLINK(find->adbname->finds, find, plink);
|
|
find->adbname = NULL;
|
|
find->name_bucket = DNS_ADB_INVALIDBUCKET;
|
|
}
|
|
UNLOCK(&adb->namelocks[unlock_bucket]);
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
POST(bucket);
|
|
|
|
cleanup:
|
|
|
|
if (!FIND_EVENTSENT(find)) {
|
|
ev = &find->event;
|
|
task = ev->ev_sender;
|
|
ev->ev_sender = find;
|
|
ev->ev_type = DNS_EVENT_ADBCANCELED;
|
|
ev->ev_destroy = event_free;
|
|
ev->ev_destroy_arg = find;
|
|
find->result_v4 = ISC_R_CANCELED;
|
|
find->result_v6 = ISC_R_CANCELED;
|
|
|
|
DP(DEF_LEVEL, "sending event %p to task %p for find %p", ev,
|
|
task, find);
|
|
|
|
isc_task_sendanddetach(&task, (isc_event_t **)&ev);
|
|
}
|
|
|
|
UNLOCK(&find->lock);
|
|
}
|
|
|
|
void
|
|
dns_adb_dump(dns_adb_t *adb, FILE *f) {
|
|
unsigned int i;
|
|
isc_stdtime_t now;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(f != NULL);
|
|
|
|
/*
|
|
* Lock the adb itself, lock all the name buckets, then lock all
|
|
* the entry buckets. This should put the adb into a state where
|
|
* nothing can change, so we can iterate through everything and
|
|
* print at our leisure.
|
|
*/
|
|
|
|
LOCK(&adb->lock);
|
|
isc_stdtime_get(&now);
|
|
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
RUNTIME_CHECK(!cleanup_names(adb, i, now));
|
|
}
|
|
for (i = 0; i < adb->nentries; i++) {
|
|
RUNTIME_CHECK(!cleanup_entries(adb, i, now));
|
|
}
|
|
|
|
dump_adb(adb, f, false, now);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
static void
|
|
dump_ttl(FILE *f, const char *legend, isc_stdtime_t value, isc_stdtime_t now) {
|
|
if (value == INT_MAX) {
|
|
return;
|
|
}
|
|
fprintf(f, " [%s TTL %d]", legend, (int)(value - now));
|
|
}
|
|
|
|
static void
|
|
dump_adb(dns_adb_t *adb, FILE *f, bool debug, isc_stdtime_t now) {
|
|
dns_adbname_t *name;
|
|
dns_adbentry_t *entry;
|
|
|
|
fprintf(f, ";\n; Address database dump\n;\n");
|
|
fprintf(f, "; [edns success/timeout]\n");
|
|
fprintf(f, "; [plain success/timeout]\n;\n");
|
|
if (debug) {
|
|
LOCK(&adb->reflock);
|
|
fprintf(f, "; addr %p, erefcnt %u, irefcnt %u, finds out %u\n",
|
|
adb, adb->erefcnt, adb->irefcnt,
|
|
isc_mempool_getallocated(adb->nhmp));
|
|
UNLOCK(&adb->reflock);
|
|
}
|
|
|
|
/*
|
|
* In TSAN mode we need to lock the locks individually, as TSAN
|
|
* can't handle more than 64 locks locked by one thread.
|
|
* In regular mode we want a consistent dump so we need to
|
|
* lock everything.
|
|
*/
|
|
#ifndef __SANITIZE_THREAD__
|
|
for (size_t i = 0; i < adb->nnames; i++) {
|
|
LOCK(&adb->namelocks[i]);
|
|
}
|
|
for (size_t i = 0; i < adb->nentries; i++) {
|
|
LOCK(&adb->entrylocks[i]);
|
|
}
|
|
#endif /* ifndef __SANITIZE_THREAD__ */
|
|
|
|
/*
|
|
* Dump the names
|
|
*/
|
|
for (size_t i = 0; i < adb->nnames; i++) {
|
|
#ifdef __SANITIZE_THREAD__
|
|
LOCK(&adb->namelocks[i]);
|
|
#endif /* ifdef __SANITIZE_THREAD__ */
|
|
name = ISC_LIST_HEAD(adb->names[i]);
|
|
if (name == NULL) {
|
|
#ifdef __SANITIZE_THREAD__
|
|
UNLOCK(&adb->namelocks[i]);
|
|
#endif /* ifdef __SANITIZE_THREAD__ */
|
|
continue;
|
|
}
|
|
if (debug) {
|
|
fprintf(f, "; bucket %zu\n", i);
|
|
}
|
|
for (; name != NULL; name = ISC_LIST_NEXT(name, plink)) {
|
|
if (debug) {
|
|
fprintf(f, "; name %p (flags %08x)\n", name,
|
|
name->flags);
|
|
}
|
|
fprintf(f, "; ");
|
|
print_dns_name(f, &name->name);
|
|
if (dns_name_countlabels(&name->target) > 0) {
|
|
fprintf(f, " alias ");
|
|
print_dns_name(f, &name->target);
|
|
}
|
|
|
|
dump_ttl(f, "v4", name->expire_v4, now);
|
|
dump_ttl(f, "v6", name->expire_v6, now);
|
|
dump_ttl(f, "target", name->expire_target, now);
|
|
|
|
fprintf(f, " [v4 %s] [v6 %s]",
|
|
errnames[name->fetch_err],
|
|
errnames[name->fetch6_err]);
|
|
|
|
fprintf(f, "\n");
|
|
|
|
print_namehook_list(f, "v4", adb, &name->v4, debug,
|
|
now);
|
|
print_namehook_list(f, "v6", adb, &name->v6, debug,
|
|
now);
|
|
|
|
if (debug) {
|
|
print_fetch_list(f, name);
|
|
print_find_list(f, name);
|
|
}
|
|
}
|
|
#ifdef __SANITIZE_THREAD__
|
|
UNLOCK(&adb->namelocks[i]);
|
|
#endif /* ifdef __SANITIZE_THREAD__ */
|
|
}
|
|
|
|
fprintf(f, ";\n; Unassociated entries\n;\n");
|
|
|
|
for (size_t i = 0; i < adb->nentries; i++) {
|
|
#ifdef __SANITIZE_THREAD__
|
|
LOCK(&adb->entrylocks[i]);
|
|
#endif /* ifdef __SANITIZE_THREAD__ */
|
|
entry = ISC_LIST_HEAD(adb->entries[i]);
|
|
while (entry != NULL) {
|
|
if (entry->nh == 0) {
|
|
dump_entry(f, adb, entry, debug, now);
|
|
}
|
|
entry = ISC_LIST_NEXT(entry, plink);
|
|
}
|
|
#ifdef __SANITIZE_THREAD__
|
|
UNLOCK(&adb->entrylocks[i]);
|
|
#endif /* ifdef __SANITIZE_THREAD__ */
|
|
}
|
|
|
|
#ifndef __SANITIZE_THREAD__
|
|
/*
|
|
* Unlock everything
|
|
*/
|
|
for (ssize_t i = adb->nentries - 1; i >= 0; i--) {
|
|
UNLOCK(&adb->entrylocks[i]);
|
|
}
|
|
for (ssize_t i = adb->nnames - 1; i >= 0; i--) {
|
|
UNLOCK(&adb->namelocks[i]);
|
|
}
|
|
#endif /* ifndef __SANITIZE_THREAD__ */
|
|
}
|
|
|
|
static void
|
|
dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry, bool debug,
|
|
isc_stdtime_t now) {
|
|
char addrbuf[ISC_NETADDR_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
isc_netaddr_t netaddr;
|
|
dns_adblameinfo_t *li;
|
|
|
|
isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
|
|
isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
|
|
|
|
if (debug) {
|
|
fprintf(f, ";\t%p: refcnt %u\n", entry, entry->refcnt);
|
|
}
|
|
|
|
fprintf(f,
|
|
";\t%s [srtt %u] [flags %08x] [edns %u/%u] "
|
|
"[plain %u/%u]",
|
|
addrbuf, entry->srtt, entry->flags, entry->edns, entry->ednsto,
|
|
entry->plain, entry->plainto);
|
|
if (entry->udpsize != 0U) {
|
|
fprintf(f, " [udpsize %u]", entry->udpsize);
|
|
}
|
|
if (entry->cookie != NULL) {
|
|
unsigned int i;
|
|
fprintf(f, " [cookie=");
|
|
for (i = 0; i < entry->cookielen; i++) {
|
|
fprintf(f, "%02x", entry->cookie[i]);
|
|
}
|
|
fprintf(f, "]");
|
|
}
|
|
if (entry->expires != 0) {
|
|
fprintf(f, " [ttl %d]", (int)(entry->expires - now));
|
|
}
|
|
|
|
if (adb != NULL && adb->quota != 0 && adb->atr_freq != 0) {
|
|
uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
|
|
fprintf(f, " [atr %0.2f] [quota %" PRIuFAST32 "]", entry->atr,
|
|
quota);
|
|
}
|
|
|
|
fprintf(f, "\n");
|
|
for (li = ISC_LIST_HEAD(entry->lameinfo); li != NULL;
|
|
li = ISC_LIST_NEXT(li, plink))
|
|
{
|
|
fprintf(f, ";\t\t");
|
|
print_dns_name(f, &li->qname);
|
|
dns_rdatatype_format(li->qtype, typebuf, sizeof(typebuf));
|
|
fprintf(f, " %s [lame TTL %d]\n", typebuf,
|
|
(int)(li->lame_timer - now));
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_adb_dumpfind(dns_adbfind_t *find, FILE *f) {
|
|
char tmp[512];
|
|
const char *tmpp;
|
|
dns_adbaddrinfo_t *ai;
|
|
isc_sockaddr_t *sa;
|
|
|
|
/*
|
|
* Not used currently, in the API Just In Case we
|
|
* want to dump out the name and/or entries too.
|
|
*/
|
|
|
|
LOCK(&find->lock);
|
|
|
|
fprintf(f, ";Find %p\n", find);
|
|
fprintf(f, ";\tqpending %08x partial %08x options %08x flags %08x\n",
|
|
find->query_pending, find->partial_result, find->options,
|
|
find->flags);
|
|
fprintf(f, ";\tname_bucket %d, name %p, event sender %p\n",
|
|
find->name_bucket, find->adbname, find->event.ev_sender);
|
|
|
|
ai = ISC_LIST_HEAD(find->list);
|
|
if (ai != NULL) {
|
|
fprintf(f, "\tAddresses:\n");
|
|
}
|
|
while (ai != NULL) {
|
|
sa = &ai->sockaddr;
|
|
switch (sa->type.sa.sa_family) {
|
|
case AF_INET:
|
|
tmpp = inet_ntop(AF_INET, &sa->type.sin.sin_addr, tmp,
|
|
sizeof(tmp));
|
|
break;
|
|
case AF_INET6:
|
|
tmpp = inet_ntop(AF_INET6, &sa->type.sin6.sin6_addr,
|
|
tmp, sizeof(tmp));
|
|
break;
|
|
default:
|
|
tmpp = "UnkFamily";
|
|
}
|
|
|
|
if (tmpp == NULL) {
|
|
tmpp = "BadAddress";
|
|
}
|
|
|
|
fprintf(f,
|
|
"\t\tentry %p, flags %08x"
|
|
" srtt %u addr %s\n",
|
|
ai->entry, ai->flags, ai->srtt, tmpp);
|
|
|
|
ai = ISC_LIST_NEXT(ai, publink);
|
|
}
|
|
|
|
UNLOCK(&find->lock);
|
|
}
|
|
|
|
static void
|
|
print_dns_name(FILE *f, const dns_name_t *name) {
|
|
char buf[DNS_NAME_FORMATSIZE];
|
|
|
|
INSIST(f != NULL);
|
|
|
|
dns_name_format(name, buf, sizeof(buf));
|
|
fprintf(f, "%s", buf);
|
|
}
|
|
|
|
static void
|
|
print_namehook_list(FILE *f, const char *legend, dns_adb_t *adb,
|
|
dns_adbnamehooklist_t *list, bool debug,
|
|
isc_stdtime_t now) {
|
|
dns_adbnamehook_t *nh;
|
|
|
|
for (nh = ISC_LIST_HEAD(*list); nh != NULL;
|
|
nh = ISC_LIST_NEXT(nh, plink)) {
|
|
if (debug) {
|
|
fprintf(f, ";\tHook(%s) %p\n", legend, nh);
|
|
}
|
|
dump_entry(f, adb, nh->entry, debug, now);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
print_fetch(FILE *f, dns_adbfetch_t *ft, const char *type) {
|
|
fprintf(f, "\t\tFetch(%s): %p -> { fetch %p }\n", type, ft, ft->fetch);
|
|
}
|
|
|
|
static void
|
|
print_fetch_list(FILE *f, dns_adbname_t *n) {
|
|
if (NAME_FETCH_A(n)) {
|
|
print_fetch(f, n->fetch_a, "A");
|
|
}
|
|
if (NAME_FETCH_AAAA(n)) {
|
|
print_fetch(f, n->fetch_aaaa, "AAAA");
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_find_list(FILE *f, dns_adbname_t *name) {
|
|
dns_adbfind_t *find;
|
|
|
|
find = ISC_LIST_HEAD(name->finds);
|
|
while (find != NULL) {
|
|
dns_adb_dumpfind(find, f);
|
|
find = ISC_LIST_NEXT(find, plink);
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) {
|
|
isc_result_t result;
|
|
dns_rdataset_t rdataset;
|
|
dns_adb_t *adb;
|
|
dns_fixedname_t foundname;
|
|
dns_name_t *fname;
|
|
|
|
INSIST(DNS_ADBNAME_VALID(adbname));
|
|
adb = adbname->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
INSIST(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
|
|
|
|
fname = dns_fixedname_initname(&foundname);
|
|
dns_rdataset_init(&rdataset);
|
|
|
|
if (rdtype == dns_rdatatype_a) {
|
|
adbname->fetch_err = FIND_ERR_UNEXPECTED;
|
|
} else {
|
|
adbname->fetch6_err = FIND_ERR_UNEXPECTED;
|
|
}
|
|
|
|
/*
|
|
* We need to specify whether to search static-stub zones (if
|
|
* configured) depending on whether this is a "start at zone" lookup,
|
|
* i.e., whether it's a "bailiwick" glue. If it's bailiwick (in which
|
|
* case NAME_STARTATZONE is set) we need to stop the search at any
|
|
* matching static-stub zone without looking into the cache to honor
|
|
* the configuration on which server we should send queries to.
|
|
*/
|
|
result = dns_view_find(adb->view, &adbname->name, rdtype, now,
|
|
NAME_GLUEOK(adbname) ? DNS_DBFIND_GLUEOK : 0,
|
|
NAME_HINTOK(adbname),
|
|
((adbname->flags & NAME_STARTATZONE) != 0), NULL,
|
|
NULL, fname, &rdataset, NULL);
|
|
|
|
/* XXXVIX this switch statement is too sparse to gen a jump table. */
|
|
switch (result) {
|
|
case DNS_R_GLUE:
|
|
case DNS_R_HINT:
|
|
case ISC_R_SUCCESS:
|
|
/*
|
|
* Found in the database. Even if we can't copy out
|
|
* any information, return success, or else a fetch
|
|
* will be made, which will only make things worse.
|
|
*/
|
|
if (rdtype == dns_rdatatype_a) {
|
|
adbname->fetch_err = FIND_ERR_SUCCESS;
|
|
} else {
|
|
adbname->fetch6_err = FIND_ERR_SUCCESS;
|
|
}
|
|
result = import_rdataset(adbname, &rdataset, now);
|
|
break;
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NXRRSET:
|
|
/*
|
|
* We're authoritative and the data doesn't exist.
|
|
* Make up a negative cache entry so we don't ask again
|
|
* for a while.
|
|
*
|
|
* XXXRTH What time should we use? I'm putting in 30 seconds
|
|
* for now.
|
|
*/
|
|
if (rdtype == dns_rdatatype_a) {
|
|
adbname->expire_v4 = now + 30;
|
|
DP(NCACHE_LEVEL,
|
|
"adb name %p: Caching auth negative entry for A",
|
|
adbname);
|
|
if (result == DNS_R_NXDOMAIN) {
|
|
adbname->fetch_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
adbname->fetch_err = FIND_ERR_NXRRSET;
|
|
}
|
|
} else {
|
|
DP(NCACHE_LEVEL,
|
|
"adb name %p: Caching auth negative entry for AAAA",
|
|
adbname);
|
|
adbname->expire_v6 = now + 30;
|
|
if (result == DNS_R_NXDOMAIN) {
|
|
adbname->fetch6_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
adbname->fetch6_err = FIND_ERR_NXRRSET;
|
|
}
|
|
}
|
|
break;
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_NCACHENXRRSET:
|
|
/*
|
|
* We found a negative cache entry. Pull the TTL from it
|
|
* so we won't ask again for a while.
|
|
*/
|
|
rdataset.ttl = ttlclamp(rdataset.ttl);
|
|
if (rdtype == dns_rdatatype_a) {
|
|
adbname->expire_v4 = rdataset.ttl + now;
|
|
if (result == DNS_R_NCACHENXDOMAIN) {
|
|
adbname->fetch_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
adbname->fetch_err = FIND_ERR_NXRRSET;
|
|
}
|
|
DP(NCACHE_LEVEL,
|
|
"adb name %p: Caching negative entry for A (ttl %u)",
|
|
adbname, rdataset.ttl);
|
|
} else {
|
|
DP(NCACHE_LEVEL,
|
|
"adb name %p: Caching negative entry for AAAA (ttl "
|
|
"%u)",
|
|
adbname, rdataset.ttl);
|
|
adbname->expire_v6 = rdataset.ttl + now;
|
|
if (result == DNS_R_NCACHENXDOMAIN) {
|
|
adbname->fetch6_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
adbname->fetch6_err = FIND_ERR_NXRRSET;
|
|
}
|
|
}
|
|
break;
|
|
case DNS_R_CNAME:
|
|
case DNS_R_DNAME:
|
|
/*
|
|
* Clear the hint and glue flags, so this will match
|
|
* more often.
|
|
*/
|
|
adbname->flags &= ~(DNS_ADBFIND_GLUEOK | DNS_ADBFIND_HINTOK);
|
|
|
|
rdataset.ttl = ttlclamp(rdataset.ttl);
|
|
clean_target(adb, &adbname->target);
|
|
adbname->expire_target = INT_MAX;
|
|
result = set_target(adb, &adbname->name, fname, &rdataset,
|
|
&adbname->target);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = DNS_R_ALIAS;
|
|
DP(NCACHE_LEVEL, "adb name %p: caching alias target",
|
|
adbname);
|
|
adbname->expire_target = rdataset.ttl + now;
|
|
}
|
|
if (rdtype == dns_rdatatype_a) {
|
|
adbname->fetch_err = FIND_ERR_SUCCESS;
|
|
} else {
|
|
adbname->fetch6_err = FIND_ERR_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
fetch_callback(isc_task_t *task, isc_event_t *ev) {
|
|
dns_fetchevent_t *dev;
|
|
dns_adbname_t *name;
|
|
dns_adb_t *adb;
|
|
dns_adbfetch_t *fetch;
|
|
int bucket;
|
|
isc_eventtype_t ev_status;
|
|
isc_stdtime_t now;
|
|
isc_result_t result;
|
|
unsigned int address_type;
|
|
bool want_check_exit = false;
|
|
|
|
UNUSED(task);
|
|
|
|
INSIST(ev->ev_type == DNS_EVENT_FETCHDONE);
|
|
dev = (dns_fetchevent_t *)ev;
|
|
name = ev->ev_arg;
|
|
INSIST(DNS_ADBNAME_VALID(name));
|
|
adb = name->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
bucket = name->lock_bucket;
|
|
LOCK(&adb->namelocks[bucket]);
|
|
|
|
INSIST(NAME_FETCH_A(name) || NAME_FETCH_AAAA(name));
|
|
address_type = 0;
|
|
if (NAME_FETCH_A(name) && (name->fetch_a->fetch == dev->fetch)) {
|
|
address_type = DNS_ADBFIND_INET;
|
|
fetch = name->fetch_a;
|
|
name->fetch_a = NULL;
|
|
} else if (NAME_FETCH_AAAA(name) &&
|
|
(name->fetch_aaaa->fetch == dev->fetch)) {
|
|
address_type = DNS_ADBFIND_INET6;
|
|
fetch = name->fetch_aaaa;
|
|
name->fetch_aaaa = NULL;
|
|
} else {
|
|
fetch = NULL;
|
|
}
|
|
|
|
INSIST(address_type != 0 && fetch != NULL);
|
|
|
|
dns_resolver_destroyfetch(&fetch->fetch);
|
|
dev->fetch = NULL;
|
|
|
|
ev_status = DNS_EVENT_ADBNOMOREADDRESSES;
|
|
|
|
/*
|
|
* Cleanup things we don't care about.
|
|
*/
|
|
if (dev->node != NULL) {
|
|
dns_db_detachnode(dev->db, &dev->node);
|
|
}
|
|
if (dev->db != NULL) {
|
|
dns_db_detach(&dev->db);
|
|
}
|
|
|
|
/*
|
|
* If this name is marked as dead, clean up, throwing away
|
|
* potentially good data.
|
|
*/
|
|
if (NAME_DEAD(name)) {
|
|
free_adbfetch(adb, &fetch);
|
|
isc_event_free(&ev);
|
|
|
|
want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED);
|
|
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
|
|
if (want_check_exit) {
|
|
LOCK(&adb->lock);
|
|
check_exit(adb);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
isc_stdtime_get(&now);
|
|
|
|
/*
|
|
* If we got a negative cache response, remember it.
|
|
*/
|
|
if (NCACHE_RESULT(dev->result)) {
|
|
dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
|
|
if (address_type == DNS_ADBFIND_INET) {
|
|
DP(NCACHE_LEVEL,
|
|
"adb fetch name %p: "
|
|
"caching negative entry for A (ttl %u)",
|
|
name, dev->rdataset->ttl);
|
|
name->expire_v4 = ISC_MIN(name->expire_v4,
|
|
dev->rdataset->ttl + now);
|
|
if (dev->result == DNS_R_NCACHENXDOMAIN) {
|
|
name->fetch_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
name->fetch_err = FIND_ERR_NXRRSET;
|
|
}
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
|
|
} else {
|
|
DP(NCACHE_LEVEL,
|
|
"adb fetch name %p: "
|
|
"caching negative entry for AAAA (ttl %u)",
|
|
name, dev->rdataset->ttl);
|
|
name->expire_v6 = ISC_MIN(name->expire_v6,
|
|
dev->rdataset->ttl + now);
|
|
if (dev->result == DNS_R_NCACHENXDOMAIN) {
|
|
name->fetch6_err = FIND_ERR_NXDOMAIN;
|
|
} else {
|
|
name->fetch6_err = FIND_ERR_NXRRSET;
|
|
}
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Handle CNAME/DNAME.
|
|
*/
|
|
if (dev->result == DNS_R_CNAME || dev->result == DNS_R_DNAME) {
|
|
dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl);
|
|
clean_target(adb, &name->target);
|
|
name->expire_target = INT_MAX;
|
|
result = set_target(adb, &name->name,
|
|
dns_fixedname_name(&dev->foundname),
|
|
dev->rdataset, &name->target);
|
|
if (result == ISC_R_SUCCESS) {
|
|
DP(NCACHE_LEVEL,
|
|
"adb fetch name %p: caching alias target", name);
|
|
name->expire_target = dev->rdataset->ttl + now;
|
|
}
|
|
goto check_result;
|
|
}
|
|
|
|
/*
|
|
* Did we get back junk? If so, and there are no more fetches
|
|
* sitting out there, tell all the finds about it.
|
|
*/
|
|
if (dev->result != ISC_R_SUCCESS) {
|
|
char buf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_name_format(&name->name, buf, sizeof(buf));
|
|
DP(DEF_LEVEL, "adb: fetch of '%s' %s failed: %s", buf,
|
|
address_type == DNS_ADBFIND_INET ? "A" : "AAAA",
|
|
dns_result_totext(dev->result));
|
|
/*
|
|
* Don't record a failure unless this is the initial
|
|
* fetch of a chain.
|
|
*/
|
|
if (fetch->depth > 1) {
|
|
goto out;
|
|
}
|
|
/* XXXMLG Don't pound on bad servers. */
|
|
if (address_type == DNS_ADBFIND_INET) {
|
|
name->expire_v4 = ISC_MIN(name->expire_v4, now + 10);
|
|
name->fetch_err = FIND_ERR_FAILURE;
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv4fail);
|
|
} else {
|
|
name->expire_v6 = ISC_MIN(name->expire_v6, now + 10);
|
|
name->fetch6_err = FIND_ERR_FAILURE;
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv6fail);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* We got something potentially useful.
|
|
*/
|
|
result = import_rdataset(name, &fetch->rdataset, now);
|
|
|
|
check_result:
|
|
if (result == ISC_R_SUCCESS) {
|
|
ev_status = DNS_EVENT_ADBMOREADDRESSES;
|
|
if (address_type == DNS_ADBFIND_INET) {
|
|
name->fetch_err = FIND_ERR_SUCCESS;
|
|
} else {
|
|
name->fetch6_err = FIND_ERR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
out:
|
|
free_adbfetch(adb, &fetch);
|
|
isc_event_free(&ev);
|
|
|
|
clean_finds_at_name(name, ev_status, address_type);
|
|
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
}
|
|
|
|
static isc_result_t
|
|
fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth,
|
|
isc_counter_t *qc, dns_rdatatype_t type) {
|
|
isc_result_t result;
|
|
dns_adbfetch_t *fetch = NULL;
|
|
dns_adb_t *adb;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *name;
|
|
dns_rdataset_t rdataset;
|
|
dns_rdataset_t *nameservers;
|
|
unsigned int options;
|
|
|
|
INSIST(DNS_ADBNAME_VALID(adbname));
|
|
adb = adbname->adb;
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
INSIST((type == dns_rdatatype_a && !NAME_FETCH_V4(adbname)) ||
|
|
(type == dns_rdatatype_aaaa && !NAME_FETCH_V6(adbname)));
|
|
|
|
adbname->fetch_err = FIND_ERR_NOTFOUND;
|
|
|
|
name = NULL;
|
|
nameservers = NULL;
|
|
dns_rdataset_init(&rdataset);
|
|
|
|
options = DNS_FETCHOPT_NOVALIDATE;
|
|
if (start_at_zone) {
|
|
DP(ENTER_LEVEL, "fetch_name: starting at zone for name %p",
|
|
adbname);
|
|
name = dns_fixedname_initname(&fixed);
|
|
result = dns_view_findzonecut(adb->view, &adbname->name, name,
|
|
NULL, 0, 0, true, false,
|
|
&rdataset, NULL);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_HINT) {
|
|
goto cleanup;
|
|
}
|
|
nameservers = &rdataset;
|
|
options |= DNS_FETCHOPT_UNSHARED;
|
|
}
|
|
|
|
fetch = new_adbfetch(adb);
|
|
if (fetch == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
fetch->depth = depth;
|
|
|
|
/*
|
|
* We're not minimizing this query, as nothing user-related should
|
|
* be leaked here.
|
|
* However, if we'd ever want to change it we'd have to modify
|
|
* createfetch to find deepest cached name when we're providing
|
|
* domain and nameservers.
|
|
*/
|
|
result = dns_resolver_createfetch(
|
|
adb->view->resolver, &adbname->name, type, name, nameservers,
|
|
NULL, NULL, 0, options, depth, qc, adb->task, fetch_callback,
|
|
adbname, &fetch->rdataset, NULL, &fetch->fetch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s",
|
|
isc_result_totext(result));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (type == dns_rdatatype_a) {
|
|
adbname->fetch_a = fetch;
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv4);
|
|
} else {
|
|
adbname->fetch_aaaa = fetch;
|
|
inc_stats(adb, dns_resstatscounter_gluefetchv6);
|
|
}
|
|
fetch = NULL; /* Keep us from cleaning this up below. */
|
|
|
|
cleanup:
|
|
if (fetch != NULL) {
|
|
free_adbfetch(adb, &fetch);
|
|
}
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* XXXMLG Needs to take a find argument and an address info, no zone or adb,
|
|
* since these can be extracted from the find itself.
|
|
*/
|
|
isc_result_t
|
|
dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
|
|
const dns_name_t *qname, dns_rdatatype_t qtype,
|
|
isc_stdtime_t expire_time) {
|
|
dns_adblameinfo_t *li;
|
|
int bucket;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
REQUIRE(qname != NULL);
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
li = ISC_LIST_HEAD(addr->entry->lameinfo);
|
|
while (li != NULL &&
|
|
(li->qtype != qtype || !dns_name_equal(qname, &li->qname))) {
|
|
li = ISC_LIST_NEXT(li, plink);
|
|
}
|
|
if (li != NULL) {
|
|
if (expire_time > li->lame_timer) {
|
|
li->lame_timer = expire_time;
|
|
}
|
|
goto unlock;
|
|
}
|
|
li = new_adblameinfo(adb, qname, qtype);
|
|
if (li == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto unlock;
|
|
}
|
|
|
|
li->lame_timer = expire_time;
|
|
|
|
ISC_LIST_PREPEND(addr->entry->lameinfo, li, plink);
|
|
unlock:
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt,
|
|
unsigned int factor) {
|
|
int bucket;
|
|
isc_stdtime_t now = 0;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
REQUIRE(factor <= 10);
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
if (addr->entry->expires == 0 || factor == DNS_ADB_RTTADJAGE) {
|
|
isc_stdtime_get(&now);
|
|
}
|
|
adjustsrtt(addr, rtt, factor, now);
|
|
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
void
|
|
dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
adjustsrtt(addr, 0, DNS_ADB_RTTADJAGE, now);
|
|
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
static void
|
|
adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor,
|
|
isc_stdtime_t now) {
|
|
uint64_t new_srtt;
|
|
|
|
if (factor == DNS_ADB_RTTADJAGE) {
|
|
if (addr->entry->lastage != now) {
|
|
new_srtt = addr->entry->srtt;
|
|
new_srtt <<= 9;
|
|
new_srtt -= addr->entry->srtt;
|
|
new_srtt >>= 9;
|
|
addr->entry->lastage = now;
|
|
} else {
|
|
new_srtt = addr->entry->srtt;
|
|
}
|
|
} else {
|
|
new_srtt = ((uint64_t)addr->entry->srtt / 10 * factor) +
|
|
((uint64_t)rtt / 10 * (10 - factor));
|
|
}
|
|
|
|
addr->entry->srtt = (unsigned int)new_srtt;
|
|
addr->srtt = (unsigned int)new_srtt;
|
|
|
|
if (addr->entry->expires == 0) {
|
|
addr->entry->expires = now + ADB_ENTRY_WINDOW;
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits,
|
|
unsigned int mask) {
|
|
int bucket;
|
|
isc_stdtime_t now;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
REQUIRE((bits & ENTRY_IS_DEAD) == 0);
|
|
REQUIRE((mask & ENTRY_IS_DEAD) == 0);
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
addr->entry->flags = (addr->entry->flags & ~mask) | (bits & mask);
|
|
if (addr->entry->expires == 0) {
|
|
isc_stdtime_get(&now);
|
|
addr->entry->expires = now + ADB_ENTRY_WINDOW;
|
|
}
|
|
|
|
/*
|
|
* Note that we do not update the other bits in addr->flags with
|
|
* the most recent values from addr->entry->flags.
|
|
*/
|
|
addr->flags = (addr->flags & ~mask) | (bits & mask);
|
|
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
/*
|
|
* The polynomial backoff curve (10000 / ((10 + n) / 10)^(3/2)) <0..99> drops
|
|
* fairly aggressively at first, then slows down and tails off at around 2-3%.
|
|
*
|
|
* These will be used to make quota adjustments.
|
|
*/
|
|
static int quota_adj[] = {
|
|
10000, 8668, 7607, 6747, 6037, 5443, 4941, 4512, 4141, 3818, 3536,
|
|
3286, 3065, 2867, 2690, 2530, 2385, 2254, 2134, 2025, 1925, 1832,
|
|
1747, 1668, 1595, 1527, 1464, 1405, 1350, 1298, 1250, 1205, 1162,
|
|
1121, 1083, 1048, 1014, 981, 922, 894, 868, 843, 820, 797,
|
|
775, 755, 735, 716, 698, 680, 664, 648, 632, 618, 603,
|
|
590, 577, 564, 552, 540, 529, 518, 507, 497, 487, 477,
|
|
468, 459, 450, 442, 434, 426, 418, 411, 404, 397, 390,
|
|
383, 377, 370, 364, 358, 353, 347, 342, 336, 331, 326,
|
|
321, 316, 312, 307, 303, 298, 294, 290, 286, 282, 278
|
|
};
|
|
|
|
#define QUOTA_ADJ_SIZE (sizeof(quota_adj) / sizeof(quota_adj[0]))
|
|
|
|
/*
|
|
* Caller must hold adbentry lock
|
|
*/
|
|
static void
|
|
maybe_adjust_quota(dns_adb_t *adb, dns_adbaddrinfo_t *addr, bool timeout) {
|
|
double tr;
|
|
|
|
UNUSED(adb);
|
|
|
|
if (adb->quota == 0 || adb->atr_freq == 0) {
|
|
return;
|
|
}
|
|
|
|
if (timeout) {
|
|
addr->entry->timeouts++;
|
|
}
|
|
|
|
if (addr->entry->completed++ <= adb->atr_freq) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Calculate an exponential rolling average of the timeout ratio
|
|
*
|
|
* XXX: Integer arithmetic might be better than floating point
|
|
*/
|
|
tr = (double)addr->entry->timeouts / addr->entry->completed;
|
|
addr->entry->timeouts = addr->entry->completed = 0;
|
|
INSIST(addr->entry->atr >= 0.0);
|
|
INSIST(addr->entry->atr <= 1.0);
|
|
INSIST(adb->atr_discount >= 0.0);
|
|
INSIST(adb->atr_discount <= 1.0);
|
|
addr->entry->atr *= 1.0 - adb->atr_discount;
|
|
addr->entry->atr += tr * adb->atr_discount;
|
|
addr->entry->atr = ISC_CLAMP(addr->entry->atr, 0.0, 1.0);
|
|
|
|
if (addr->entry->atr < adb->atr_low && addr->entry->mode > 0) {
|
|
uint_fast32_t new_quota =
|
|
adb->quota * quota_adj[--addr->entry->mode] / 10000;
|
|
atomic_store_release(&addr->entry->quota,
|
|
ISC_MIN(1, new_quota));
|
|
log_quota(addr->entry,
|
|
"atr %0.2f, quota increased to %" PRIuFAST32,
|
|
addr->entry->atr, new_quota);
|
|
} else if (addr->entry->atr > adb->atr_high &&
|
|
addr->entry->mode < (QUOTA_ADJ_SIZE - 1))
|
|
{
|
|
uint_fast32_t new_quota =
|
|
adb->quota * quota_adj[++addr->entry->mode] / 10000;
|
|
atomic_store_release(&addr->entry->quota,
|
|
ISC_MIN(1, new_quota));
|
|
log_quota(addr->entry,
|
|
"atr %0.2f, quota decreased to %" PRIuFAST32,
|
|
addr->entry->atr, new_quota);
|
|
}
|
|
}
|
|
|
|
#define EDNSTOS 3U
|
|
|
|
void
|
|
dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
maybe_adjust_quota(adb, addr, false);
|
|
|
|
addr->entry->plain++;
|
|
if (addr->entry->plain == 0xff) {
|
|
addr->entry->edns >>= 1;
|
|
addr->entry->ednsto >>= 1;
|
|
addr->entry->plain >>= 1;
|
|
addr->entry->plainto >>= 1;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
void
|
|
dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
maybe_adjust_quota(adb, addr, true);
|
|
|
|
addr->entry->plainto++;
|
|
if (addr->entry->plainto == 0xff) {
|
|
addr->entry->edns >>= 1;
|
|
addr->entry->ednsto >>= 1;
|
|
addr->entry->plain >>= 1;
|
|
addr->entry->plainto >>= 1;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
void
|
|
dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
maybe_adjust_quota(adb, addr, true);
|
|
|
|
addr->entry->ednsto++;
|
|
if (addr->entry->ednsto == 0xff) {
|
|
addr->entry->edns >>= 1;
|
|
addr->entry->ednsto >>= 1;
|
|
addr->entry->plain >>= 1;
|
|
addr->entry->plainto >>= 1;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
void
|
|
dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
if (size < 512U) {
|
|
size = 512U;
|
|
}
|
|
if (size > addr->entry->udpsize) {
|
|
addr->entry->udpsize = size;
|
|
}
|
|
|
|
maybe_adjust_quota(adb, addr, false);
|
|
|
|
addr->entry->edns++;
|
|
if (addr->entry->edns == 0xff) {
|
|
addr->entry->edns >>= 1;
|
|
addr->entry->ednsto >>= 1;
|
|
addr->entry->plain >>= 1;
|
|
addr->entry->plainto >>= 1;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
unsigned int
|
|
dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
int bucket;
|
|
unsigned int size;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
size = addr->entry->udpsize;
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
|
|
return (size);
|
|
}
|
|
|
|
void
|
|
dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
|
|
const unsigned char *cookie, size_t len) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
if (addr->entry->cookie != NULL &&
|
|
(cookie == NULL || len != addr->entry->cookielen))
|
|
{
|
|
isc_mem_put(adb->mctx, addr->entry->cookie,
|
|
addr->entry->cookielen);
|
|
addr->entry->cookie = NULL;
|
|
addr->entry->cookielen = 0;
|
|
}
|
|
|
|
if (addr->entry->cookie == NULL && cookie != NULL && len != 0U) {
|
|
addr->entry->cookie = isc_mem_get(adb->mctx, len);
|
|
addr->entry->cookielen = (uint16_t)len;
|
|
}
|
|
|
|
if (addr->entry->cookie != NULL) {
|
|
memmove(addr->entry->cookie, cookie, len);
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
}
|
|
|
|
size_t
|
|
dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
|
|
unsigned char *cookie, size_t len) {
|
|
int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
if (cookie != NULL && addr->entry->cookie != NULL &&
|
|
len >= addr->entry->cookielen)
|
|
{
|
|
memmove(cookie, addr->entry->cookie, addr->entry->cookielen);
|
|
len = addr->entry->cookielen;
|
|
} else {
|
|
len = 0;
|
|
}
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
|
|
return (len);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa,
|
|
dns_adbaddrinfo_t **addrp, isc_stdtime_t now) {
|
|
int bucket;
|
|
dns_adbentry_t *entry;
|
|
dns_adbaddrinfo_t *addr;
|
|
isc_result_t result;
|
|
in_port_t port;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(addrp != NULL && *addrp == NULL);
|
|
|
|
UNUSED(now);
|
|
|
|
result = ISC_R_SUCCESS;
|
|
bucket = DNS_ADB_INVALIDBUCKET;
|
|
entry = find_entry_and_lock(adb, sa, &bucket, now);
|
|
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
|
|
if (adb->entry_sd[bucket]) {
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
goto unlock;
|
|
}
|
|
if (entry == NULL) {
|
|
/*
|
|
* We don't know anything about this address.
|
|
*/
|
|
entry = new_adbentry(adb);
|
|
if (entry == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto unlock;
|
|
}
|
|
entry->sockaddr = *sa;
|
|
link_entry(adb, bucket, entry);
|
|
DP(ENTER_LEVEL, "findaddrinfo: new entry %p", entry);
|
|
} else {
|
|
DP(ENTER_LEVEL, "findaddrinfo: found entry %p", entry);
|
|
}
|
|
|
|
port = isc_sockaddr_getport(sa);
|
|
addr = new_adbaddrinfo(adb, entry, port);
|
|
if (addr == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
} else {
|
|
inc_entry_refcnt(adb, entry, false);
|
|
*addrp = addr;
|
|
}
|
|
|
|
unlock:
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp) {
|
|
dns_adbaddrinfo_t *addr;
|
|
dns_adbentry_t *entry;
|
|
int bucket;
|
|
isc_stdtime_t now;
|
|
bool want_check_exit = false;
|
|
bool overmem;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(addrp != NULL);
|
|
addr = *addrp;
|
|
*addrp = NULL;
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
entry = addr->entry;
|
|
REQUIRE(DNS_ADBENTRY_VALID(entry));
|
|
|
|
overmem = isc_mem_isovermem(adb->mctx);
|
|
|
|
bucket = addr->entry->lock_bucket;
|
|
LOCK(&adb->entrylocks[bucket]);
|
|
|
|
if (entry->expires == 0) {
|
|
isc_stdtime_get(&now);
|
|
entry->expires = now + ADB_ENTRY_WINDOW;
|
|
}
|
|
|
|
want_check_exit = dec_entry_refcnt(adb, overmem, entry, false);
|
|
|
|
UNLOCK(&adb->entrylocks[bucket]);
|
|
|
|
addr->entry = NULL;
|
|
free_adbaddrinfo(adb, &addr);
|
|
|
|
if (want_check_exit) {
|
|
LOCK(&adb->lock);
|
|
check_exit(adb);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_adb_flush(dns_adb_t *adb) {
|
|
unsigned int i;
|
|
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
LOCK(&adb->lock);
|
|
|
|
/*
|
|
* Call our cleanup routines.
|
|
*/
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
RUNTIME_CHECK(!cleanup_names(adb, i, INT_MAX));
|
|
}
|
|
for (i = 0; i < adb->nentries; i++) {
|
|
RUNTIME_CHECK(!cleanup_entries(adb, i, INT_MAX));
|
|
}
|
|
|
|
#ifdef DUMP_ADB_AFTER_CLEANING
|
|
dump_adb(adb, stdout, true, INT_MAX);
|
|
#endif /* ifdef DUMP_ADB_AFTER_CLEANING */
|
|
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
void
|
|
dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name) {
|
|
dns_adbname_t *adbname;
|
|
dns_adbname_t *nextname;
|
|
unsigned int bucket;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(name != NULL);
|
|
|
|
LOCK(&adb->lock);
|
|
bucket = dns_name_hash(name, false) % adb->nnames;
|
|
LOCK(&adb->namelocks[bucket]);
|
|
adbname = ISC_LIST_HEAD(adb->names[bucket]);
|
|
while (adbname != NULL) {
|
|
nextname = ISC_LIST_NEXT(adbname, plink);
|
|
if (!NAME_DEAD(adbname) && dns_name_equal(name, &adbname->name))
|
|
{
|
|
RUNTIME_CHECK(
|
|
!kill_name(&adbname, DNS_EVENT_ADBCANCELED));
|
|
}
|
|
adbname = nextname;
|
|
}
|
|
UNLOCK(&adb->namelocks[bucket]);
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
void
|
|
dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name) {
|
|
dns_adbname_t *adbname, *nextname;
|
|
unsigned int i;
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(name != NULL);
|
|
|
|
LOCK(&adb->lock);
|
|
for (i = 0; i < adb->nnames; i++) {
|
|
LOCK(&adb->namelocks[i]);
|
|
adbname = ISC_LIST_HEAD(adb->names[i]);
|
|
while (adbname != NULL) {
|
|
bool ret;
|
|
nextname = ISC_LIST_NEXT(adbname, plink);
|
|
if (!NAME_DEAD(adbname) &&
|
|
dns_name_issubdomain(&adbname->name, name)) {
|
|
ret = kill_name(&adbname,
|
|
DNS_EVENT_ADBCANCELED);
|
|
RUNTIME_CHECK(!ret);
|
|
}
|
|
adbname = nextname;
|
|
}
|
|
UNLOCK(&adb->namelocks[i]);
|
|
}
|
|
UNLOCK(&adb->lock);
|
|
}
|
|
|
|
static void
|
|
water(void *arg, int mark) {
|
|
/*
|
|
* We're going to change the way to handle overmem condition: use
|
|
* isc_mem_isovermem() instead of storing the state via this callback,
|
|
* since the latter way tends to cause race conditions.
|
|
* To minimize the change, and in case we re-enable the callback
|
|
* approach, however, keep this function at the moment.
|
|
*/
|
|
|
|
dns_adb_t *adb = arg;
|
|
bool overmem = (mark == ISC_MEM_HIWATER);
|
|
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
|
|
DP(ISC_LOG_DEBUG(1), "adb reached %s water mark",
|
|
overmem ? "high" : "low");
|
|
}
|
|
|
|
void
|
|
dns_adb_setadbsize(dns_adb_t *adb, size_t size) {
|
|
size_t hiwater, lowater;
|
|
|
|
INSIST(DNS_ADB_VALID(adb));
|
|
|
|
if (size != 0U && size < DNS_ADB_MINADBSIZE) {
|
|
size = DNS_ADB_MINADBSIZE;
|
|
}
|
|
|
|
hiwater = size - (size >> 3); /* Approximately 7/8ths. */
|
|
lowater = size - (size >> 2); /* Approximately 3/4ths. */
|
|
|
|
if (size == 0U || hiwater == 0U || lowater == 0U) {
|
|
isc_mem_setwater(adb->mctx, water, adb, 0, 0);
|
|
} else {
|
|
isc_mem_setwater(adb->mctx, water, adb, hiwater, lowater);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low,
|
|
double high, double discount) {
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
|
|
adb->quota = quota;
|
|
adb->atr_freq = freq;
|
|
adb->atr_low = low;
|
|
adb->atr_high = high;
|
|
adb->atr_discount = discount;
|
|
}
|
|
|
|
bool
|
|
dns_adbentry_overquota(dns_adbentry_t *entry) {
|
|
REQUIRE(DNS_ADBENTRY_VALID(entry));
|
|
|
|
uint_fast32_t quota = atomic_load_relaxed(&entry->quota);
|
|
uint_fast32_t active = atomic_load_acquire(&entry->active);
|
|
|
|
return (quota != 0 && active >= quota);
|
|
}
|
|
|
|
void
|
|
dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
INSIST(atomic_fetch_add_relaxed(&addr->entry->active, 1) != UINT32_MAX);
|
|
}
|
|
|
|
void
|
|
dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
|
|
REQUIRE(DNS_ADB_VALID(adb));
|
|
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
|
|
|
|
INSIST(atomic_fetch_sub_release(&addr->entry->active, 1) != 0);
|
|
}
|