diff options
Diffstat (limited to '')
-rw-r--r-- | lib/dns/resolver.c | 12095 |
1 files changed, 12095 insertions, 0 deletions
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c new file mode 100644 index 0000000..a97aaa8 --- /dev/null +++ b/lib/dns/resolver.c @@ -0,0 +1,12095 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <ctype.h> +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/atomic.h> +#include <isc/counter.h> +#include <isc/log.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/siphash.h> +#include <isc/socket.h> +#include <isc/stats.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/acl.h> +#include <dns/adb.h> +#include <dns/badcache.h> +#include <dns/cache.h> +#include <dns/db.h> +#include <dns/dispatch.h> +#include <dns/dnstap.h> +#include <dns/ds.h> +#include <dns/edns.h> +#include <dns/events.h> +#include <dns/forward.h> +#include <dns/keytable.h> +#include <dns/log.h> +#include <dns/message.h> +#include <dns/ncache.h> +#include <dns/nsec.h> +#include <dns/nsec3.h> +#include <dns/opcode.h> +#include <dns/peer.h> +#include <dns/rbt.h> +#include <dns/rcode.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> +#include <dns/resolver.h> +#include <dns/result.h> +#include <dns/rootns.h> +#include <dns/stats.h> +#include <dns/tsig.h> +#include <dns/validator.h> +#include <dns/zone.h> + +#ifdef WANT_QUERYTRACE +#define RTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \ + res, (m)) +#define RRTRACE(r, m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \ + (r), (m)) +#define FCTXTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s", fctx, fctx->info, (m)) +#define FCTXTRACE2(m1, m2) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s %s", fctx, fctx->info, (m1), (m2)) +#define FCTXTRACE3(m, res) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): [result: %s] %s", fctx, fctx->info, \ + isc_result_totext(res), (m)) +#define FCTXTRACE4(m1, m2, res) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): [result: %s] %s %s", fctx, fctx->info, \ + isc_result_totext(res), (m1), (m2)) +#define FCTXTRACE5(m1, m2, v) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s %s%u", fctx, fctx->info, (m1), (m2), \ + (v)) +#define FTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fetch %p (fctx %p(%s)): %s", fetch, fetch->private, \ + fetch->private->info, (m)) +#define QTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "resquery %p (fctx %p(%s)): %s", query, query->fctx, \ + query->fctx->info, (m)) +#else /* ifdef WANT_QUERYTRACE */ +#define RTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#define RRTRACE(r, m) \ + do { \ + UNUSED(r); \ + UNUSED(m); \ + } while (0) +#define FCTXTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#define FCTXTRACE2(m1, m2) \ + do { \ + UNUSED(m1); \ + UNUSED(m2); \ + } while (0) +#define FCTXTRACE3(m1, res) \ + do { \ + UNUSED(m1); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE4(m1, m2, res) \ + do { \ + UNUSED(m1); \ + UNUSED(m2); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE5(m1, m2, v) \ + do { \ + UNUSED(m1); \ + UNUSED(m2); \ + UNUSED(v); \ + } while (0) +#define FTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#define QTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#endif /* WANT_QUERYTRACE */ + +#define US_PER_SEC 1000000U +#define US_PER_MSEC 1000U +/* + * The maximum time we will wait for a single query. + */ +#define MAX_SINGLE_QUERY_TIMEOUT 9000U +#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT * US_PER_MSEC) + +/* + * We need to allow a individual query time to complete / timeout. + */ +#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U) + +/* The default time in seconds for the whole query to live. */ +#ifndef DEFAULT_QUERY_TIMEOUT +#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT +#endif /* ifndef DEFAULT_QUERY_TIMEOUT */ + +/* The maximum time in seconds for the whole query to live. */ +#ifndef MAXIMUM_QUERY_TIMEOUT +#define MAXIMUM_QUERY_TIMEOUT 30000 +#endif /* ifndef MAXIMUM_QUERY_TIMEOUT */ + +/* The default maximum number of recursions to follow before giving up. */ +#ifndef DEFAULT_RECURSION_DEPTH +#define DEFAULT_RECURSION_DEPTH 7 +#endif /* ifndef DEFAULT_RECURSION_DEPTH */ + +/* The default maximum number of iterative queries to allow before giving up. */ +#ifndef DEFAULT_MAX_QUERIES +#define DEFAULT_MAX_QUERIES 100 +#endif /* ifndef DEFAULT_MAX_QUERIES */ + +/* + * After NS_FAIL_LIMIT attempts to fetch a name server address, + * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT, + * stop trying to fetch, in order to avoid wasting resources. + */ +#define NS_FAIL_LIMIT 4 +#define NS_RR_LIMIT 5 +/* + * IP address lookups are performed for at most NS_PROCESSING_LIMIT NS RRs in + * any NS RRset encountered, to avoid excessive resource use while processing + * large delegations. + */ +#define NS_PROCESSING_LIMIT 20 + +/* Number of hash buckets for zone counters */ +#ifndef RES_DOMAIN_BUCKETS +#define RES_DOMAIN_BUCKETS 523 +#endif /* ifndef RES_DOMAIN_BUCKETS */ +#define RES_NOBUCKET 0xffffffff + +/*% + * Maximum EDNS0 input packet size. + */ +#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */ + +/*% + * This defines the maximum number of timeouts we will permit before we + * disable EDNS0 on the query. + */ +#define MAX_EDNS0_TIMEOUTS 3 + +#define DNS_RESOLVER_BADCACHESIZE 1021 +#define DNS_RESOLVER_BADCACHETTL(fctx) \ + (((fctx)->res->lame_ttl > 30) ? (fctx)->res->lame_ttl : 30) + +typedef struct fetchctx fetchctx_t; + +typedef struct query { + /* Locked by task event serialization. */ + unsigned int magic; + fetchctx_t *fctx; + dns_message_t *rmessage; + isc_mem_t *mctx; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatch; + bool exclusivesocket; + dns_adbaddrinfo_t *addrinfo; + isc_socket_t *tcpsocket; + isc_time_t start; + dns_messageid_t id; + dns_dispentry_t *dispentry; + ISC_LINK(struct query) link; + isc_buffer_t buffer; + isc_buffer_t *tsig; + dns_tsigkey_t *tsigkey; + isc_socketevent_t sendevent; + isc_dscp_t dscp; + int ednsversion; + unsigned int options; + isc_sockeventattr_t attributes; + unsigned int sends; + unsigned int connects; + unsigned int udpsize; + unsigned char data[512]; +} resquery_t; + +struct tried { + isc_sockaddr_t addr; + unsigned int count; + ISC_LINK(struct tried) link; +}; + +#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!') +#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC) + +#define RESQUERY_ATTR_CANCELED 0x02 + +#define RESQUERY_CONNECTING(q) ((q)->connects > 0) +#define RESQUERY_CANCELED(q) (((q)->attributes & RESQUERY_ATTR_CANCELED) != 0) +#define RESQUERY_SENDING(q) ((q)->sends > 0) + +typedef enum { + fetchstate_init = 0, /*%< Start event has not run yet. */ + fetchstate_active, + fetchstate_done /*%< FETCHDONE events posted. */ +} fetchstate; + +typedef enum { + badns_unreachable = 0, + badns_response, + badns_validation, + badns_forwarder, +} badnstype_t; + +struct fetchctx { + /*% Not locked. */ + unsigned int magic; + dns_resolver_t *res; + dns_name_t name; + dns_rdatatype_t type; + unsigned int options; + unsigned int bucketnum; + unsigned int dbucketnum; + char *info; + isc_mem_t *mctx; + isc_stdtime_t now; + + /* Atomic */ + isc_refcount_t references; + + /*% Locked by appropriate bucket lock. */ + fetchstate state; + bool want_shutdown; + bool cloned; + bool spilled; + isc_event_t control_event; + ISC_LINK(struct fetchctx) link; + ISC_LIST(dns_fetchevent_t) events; + + /*% Locked by task event serialization. */ + dns_name_t domain; + dns_rdataset_t nameservers; + atomic_uint_fast32_t attributes; + isc_timer_t *timer; + isc_timer_t *timer_try_stale; + isc_time_t expires; + isc_time_t expires_try_stale; + isc_interval_t interval; + dns_message_t *qmessage; + ISC_LIST(resquery_t) queries; + dns_adbfindlist_t finds; + dns_adbfind_t *find; + /* + * altfinds are names and/or addresses of dual stack servers that + * should be used when iterative resolution to a server is not + * possible because the address family of that server is not usable. + */ + dns_adbfindlist_t altfinds; + dns_adbfind_t *altfind; + dns_adbaddrinfolist_t forwaddrs; + dns_adbaddrinfolist_t altaddrs; + dns_forwarderlist_t forwarders; + dns_fwdpolicy_t fwdpolicy; + isc_sockaddrlist_t bad; + ISC_LIST(struct tried) edns; + ISC_LIST(struct tried) edns512; + isc_sockaddrlist_t bad_edns; + dns_validator_t *validator; + ISC_LIST(dns_validator_t) validators; + dns_db_t *cache; + dns_adb_t *adb; + bool ns_ttl_ok; + uint32_t ns_ttl; + isc_counter_t *qc; + bool minimized; + unsigned int qmin_labels; + isc_result_t qmin_warning; + bool ip6arpaskip; + bool forwarding; + dns_name_t qminname; + dns_rdatatype_t qmintype; + dns_fetch_t *qminfetch; + dns_rdataset_t qminrrset; + dns_name_t qmindcname; + dns_fixedname_t fwdfname; + dns_name_t *fwdname; + + /*% + * The number of events we're waiting for. + */ + unsigned int pending; /* Bucket lock. */ + + /*% + * The number of times we've "restarted" the current + * nameserver set. This acts as a failsafe to prevent + * us from pounding constantly on a particular set of + * servers that, for whatever reason, are not giving + * us useful responses, but are responding in such a + * way that they are not marked "bad". + */ + unsigned int restarts; + + /*% + * The number of timeouts that have occurred since we + * last successfully received a response packet. This + * is used for EDNS0 black hole detection. + */ + unsigned int timeouts; + + /*% + * Look aside state for DS lookups. + */ + dns_name_t nsname; + dns_fetch_t *nsfetch; + dns_rdataset_t nsrrset; + + /*% + * Number of queries that reference this context. + */ + unsigned int nqueries; /* Bucket lock. */ + + /*% + * The reason to print when logging a successful + * response to a query. + */ + const char *reason; + + /*% + * Random numbers to use for mixing up server addresses. + */ + uint32_t rand_buf; + uint32_t rand_bits; + + /*% + * Fetch-local statistics for detailed logging. + */ + isc_result_t result; /*%< fetch result */ + isc_result_t vresult; /*%< validation result */ + int exitline; + isc_time_t start; + uint64_t duration; + bool logged; + unsigned int querysent; + unsigned int referrals; + unsigned int lamecount; + unsigned int quotacount; + unsigned int neterr; + unsigned int badresp; + unsigned int adberr; + unsigned int findfail; + unsigned int valfail; + bool timeout; + dns_adbaddrinfo_t *addrinfo; + dns_messageid_t id; + unsigned int depth; + char clientstr[ISC_SOCKADDR_FORMATSIZE]; +}; + +#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!') +#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC) + +#define FCTX_ATTR_HAVEANSWER 0x0001 +#define FCTX_ATTR_GLUING 0x0002 +#define FCTX_ATTR_ADDRWAIT 0x0004 +#define FCTX_ATTR_SHUTTINGDOWN 0x0008 /* Bucket lock */ +#define FCTX_ATTR_WANTCACHE 0x0010 +#define FCTX_ATTR_WANTNCACHE 0x0020 +#define FCTX_ATTR_NEEDEDNS0 0x0040 +#define FCTX_ATTR_TRIEDFIND 0x0080 +#define FCTX_ATTR_TRIEDALT 0x0100 + +#define HAVE_ANSWER(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_HAVEANSWER) != 0) +#define GLUING(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_GLUING) != 0) +#define ADDRWAIT(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_ADDRWAIT) != 0) +#define SHUTTINGDOWN(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_SHUTTINGDOWN) != 0) +#define WANTCACHE(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTCACHE) != 0) +#define WANTNCACHE(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTNCACHE) != 0) +#define NEEDEDNS0(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_NEEDEDNS0) != 0) +#define TRIEDFIND(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDFIND) != 0) +#define TRIEDALT(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDALT) != 0) + +#define FCTX_ATTR_SET(f, a) atomic_fetch_or_release(&(f)->attributes, (a)) +#define FCTX_ATTR_CLR(f, a) atomic_fetch_and_release(&(f)->attributes, ~(a)) + +typedef struct { + dns_adbaddrinfo_t *addrinfo; + fetchctx_t *fctx; + dns_message_t *message; +} dns_valarg_t; + +struct dns_fetch { + unsigned int magic; + isc_mem_t *mctx; + fetchctx_t *private; +}; + +#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h') +#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC) + +typedef struct fctxbucket { + isc_task_t *task; + isc_mutex_t lock; + ISC_LIST(fetchctx_t) fctxs; + atomic_bool exiting; + isc_mem_t *mctx; +} fctxbucket_t; + +typedef struct fctxcount fctxcount_t; +struct fctxcount { + dns_fixedname_t fdname; + dns_name_t *domain; + uint32_t count; + uint32_t allowed; + uint32_t dropped; + isc_stdtime_t logged; + ISC_LINK(fctxcount_t) link; +}; + +typedef struct zonebucket { + isc_mutex_t lock; + isc_mem_t *mctx; + ISC_LIST(fctxcount_t) list; +} zonebucket_t; + +typedef struct alternate { + bool isaddress; + union { + isc_sockaddr_t addr; + struct { + dns_name_t name; + in_port_t port; + } _n; + } _u; + ISC_LINK(struct alternate) link; +} alternate_t; + +struct dns_resolver { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_mutex_t primelock; + dns_rdataclass_t rdclass; + isc_socketmgr_t *socketmgr; + isc_timermgr_t *timermgr; + isc_taskmgr_t *taskmgr; + dns_view_t *view; + bool frozen; + unsigned int options; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatchset_t *dispatches4; + bool exclusivev4; + dns_dispatchset_t *dispatches6; + isc_dscp_t querydscp4; + isc_dscp_t querydscp6; + bool exclusivev6; + unsigned int nbuckets; + fctxbucket_t *buckets; + zonebucket_t *dbuckets; + uint32_t lame_ttl; + ISC_LIST(alternate_t) alternates; + uint16_t udpsize; +#if USE_ALGLOCK + isc_rwlock_t alglock; +#endif /* if USE_ALGLOCK */ + dns_rbt_t *algorithms; + dns_rbt_t *digests; +#if USE_MBSLOCK + isc_rwlock_t mbslock; +#endif /* if USE_MBSLOCK */ + dns_rbt_t *mustbesecure; + unsigned int spillatmax; + unsigned int spillatmin; + isc_timer_t *spillattimer; + bool zero_no_soa_ttl; + unsigned int query_timeout; + unsigned int maxdepth; + unsigned int maxqueries; + isc_result_t quotaresp[2]; + + /* Additions for serve-stale feature. */ + unsigned int retryinterval; /* in milliseconds */ + unsigned int nonbackofftries; + + /* Atomic */ + isc_refcount_t references; + atomic_uint_fast32_t zspill; /* fetches-per-zone */ + atomic_bool exiting; + atomic_bool priming; + + /* Locked by lock. */ + isc_eventlist_t whenshutdown; + unsigned int activebuckets; + unsigned int spillat; /* clients-per-query */ + + dns_badcache_t *badcache; /* Bad cache. */ + + /* Locked by primelock. */ + dns_fetch_t *primefetch; + + /* Atomic. */ + atomic_uint_fast32_t nfctx; +}; + +#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!') +#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC) + +/*% + * Private addrinfo flags. These must not conflict with DNS_FETCHOPT_NOEDNS0 + * (0x008) which we also use as an addrinfo flag. + */ +#define FCTX_ADDRINFO_MARK 0x00001 +#define FCTX_ADDRINFO_FORWARDER 0x01000 +#define FCTX_ADDRINFO_EDNSOK 0x04000 +#define FCTX_ADDRINFO_NOCOOKIE 0x08000 +#define FCTX_ADDRINFO_BADCOOKIE 0x10000 +#define FCTX_ADDRINFO_DUALSTACK 0x20000 + +#define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) == 0) +#define ISFORWARDER(a) (((a)->flags & FCTX_ADDRINFO_FORWARDER) != 0) +#define NOCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_NOCOOKIE) != 0) +#define EDNSOK(a) (((a)->flags & FCTX_ADDRINFO_EDNSOK) != 0) +#define BADCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_BADCOOKIE) != 0) +#define ISDUALSTACK(a) (((a)->flags & FCTX_ADDRINFO_DUALSTACK) != 0) + +#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) +#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) + +#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) + +#ifdef ENABLE_AFL +bool dns_fuzzing_resolver = false; +void +dns_resolver_setfuzzing() { + dns_fuzzing_resolver = true; +} +#endif /* ifdef ENABLE_AFL */ + +static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA"; +static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 }; +static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data, + ip6_arpa_offsets); + +static unsigned char underscore_data[] = "\001_"; +static unsigned char underscore_offsets[] = { 0 }; +static const dns_name_t underscore_name = + DNS_NAME_INITNONABSOLUTE(underscore_data, underscore_offsets); + +static void +destroy(dns_resolver_t *res); +static void +empty_bucket(dns_resolver_t *res); +static isc_result_t +resquery_send(resquery_t *query); +static void +resquery_response(isc_task_t *task, isc_event_t *event); +static void +resquery_connected(isc_task_t *task, isc_event_t *event); +static void +fctx_try(fetchctx_t *fctx, bool retrying, bool badcache); +static isc_result_t +fctx_minimize_qname(fetchctx_t *fctx); +static void +fctx_destroy(fetchctx_t *fctx); +static bool +fctx_unlink(fetchctx_t *fctx); +static isc_result_t +ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *ardataset, isc_result_t *eresultp); +static void +validated(isc_task_t *task, isc_event_t *event); +static void +add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo, + isc_result_t reason, badnstype_t badtype); +static isc_result_t +findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name, + dns_rdatatype_t type, dns_name_t **noqname); +static void +fctx_increference(fetchctx_t *fctx); +static bool +fctx_decreference(fetchctx_t *fctx); +static void +resume_qmin(isc_task_t *task, isc_event_t *event); + +/*% + * The structure and functions defined below implement the resolver + * query (resquery) response handling logic. + * + * When a resolver query is sent and a response is received, the + * resquery_response() event handler is run, which calls the rctx_*() + * functions. The respctx_t structure maintains state from function + * to function. + * + * The call flow is described below: + * + * 1. resquery_response(): + * - Initialize a respctx_t structure (rctx_respinit()). + * - Check for dispatcher failure (rctx_dispfail()). + * - Parse the response (rctx_parse()). + * - Log the response (rctx_logpacket()). + * - Check the parsed response for an OPT record and handle + * EDNS (rctx_opt(), rctx_edns()). + * - Check for a bad or lame server (rctx_badserver(), rctx_lameserver()). + * - Handle delegation-only zones (rctx_delonly_zone()). + * - If RCODE and ANCOUNT suggest this is a positive answer, and + * if so, call rctx_answer(): go to step 2. + * - If RCODE and NSCOUNT suggest this is a negative answer or a + * referral, call rctx_answer_none(): go to step 4. + * - Check the additional section for data that should be cached + * (rctx_additional()). + * - Clean up and finish by calling rctx_done(): go to step 5. + * + * 2. rctx_answer(): + * - If the answer appears to be positive, call rctx_answer_positive(): + * go to step 3. + * - If the response is a malformed delegation (with glue or NS records + * in the answer section), call rctx_answer_none(): go to step 4. + * + * 3. rctx_answer_positive(): + * - Initialize the portions of respctx_t needed for processing an answer + * (rctx_answer_init()). + * - Scan the answer section to find records that are responsive to the + * query (rctx_answer_scan()). + * - For whichever type of response was found, call a separate routine + * to handle it: matching QNAME/QTYPE (rctx_answer_match()), + * CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()), + * or any records returned in response to a query of type ANY + * (rctx_answer_any()). + * - Scan the authority section for NS or other records that may be + * included with a positive answer (rctx_authority_scan()). + * + * 4. rctx_answer_none(): + * - Determine whether this is an NXDOMAIN, NXRRSET, or referral. + * - If referral, set up the resolver to follow the delegation + * (rctx_referral()). + * - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA + * records included with a negative response (rctx_authority_negative()), + * then for DNSSEC proof of nonexistence (rctx_authority_dnssec()). + * + * 5. rctx_done(): + * - Set up chasing of DS records if needed (rctx_chaseds()). + * - If the response wasn't intended for us, wait for another response + * from the dispatcher (rctx_next()). + * - If there is a problem with the responding server, set up another + * query to a different server (rctx_nextserver()). + * - If there is a problem that might be temporary or dependent on + * EDNS options, set up another query to the same server with changed + * options (rctx_resend()). + * - Shut down the fetch context. + */ + +typedef struct respctx { + isc_task_t *task; + dns_dispatchevent_t *devent; + resquery_t *query; + fetchctx_t *fctx; + isc_result_t result; + unsigned int retryopts; /* updated options to pass to + * fctx_query() when resending */ + + dns_rdatatype_t type; /* type being sought (set to + * ANY if qtype was SIG or RRSIG) */ + bool aa; /* authoritative answer? */ + dns_trust_t trust; /* answer trust level */ + bool chaining; /* CNAME/DNAME processing? */ + bool next_server; /* give up, try the next server + * */ + + badnstype_t broken_type; /* type of name server problem + * */ + isc_result_t broken_server; + + bool get_nameservers; /* get a new NS rrset at + * zone cut? */ + bool resend; /* resend this query? */ + bool nextitem; /* invalid response; keep + * listening for the correct one */ + bool truncated; /* response was truncated */ + bool no_response; /* no response was received */ + bool glue_in_answer; /* glue may be in the answer + * section */ + bool ns_in_answer; /* NS may be in the answer + * section */ + bool negative; /* is this a negative response? */ + + isc_stdtime_t now; /* time info */ + isc_time_t tnow; + isc_time_t *finish; + + unsigned int dname_labels; + unsigned int domain_labels; /* range of permissible number + * of + * labels in a DNAME */ + + dns_name_t *aname; /* answer name */ + dns_rdataset_t *ardataset; /* answer rdataset */ + + dns_name_t *cname; /* CNAME name */ + dns_rdataset_t *crdataset; /* CNAME rdataset */ + + dns_name_t *dname; /* DNAME name */ + dns_rdataset_t *drdataset; /* DNAME rdataset */ + + dns_name_t *ns_name; /* NS name */ + dns_rdataset_t *ns_rdataset; /* NS rdataset */ + + dns_name_t *soa_name; /* SOA name in a negative answer */ + dns_name_t *ds_name; /* DS name in a negative answer */ + + dns_name_t *found_name; /* invalid name in negative + * response */ + dns_rdatatype_t found_type; /* invalid type in negative + * response */ + + dns_rdataset_t *opt; /* OPT rdataset */ +} respctx_t; + +static void +rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query, + fetchctx_t *fctx, respctx_t *rctx); + +static void +rctx_answer_init(respctx_t *rctx); + +static void +rctx_answer_scan(respctx_t *rctx); + +static void +rctx_authority_positive(respctx_t *rctx); + +static isc_result_t +rctx_answer_any(respctx_t *rctx); + +static isc_result_t +rctx_answer_match(respctx_t *rctx); + +static isc_result_t +rctx_answer_cname(respctx_t *rctx); + +static isc_result_t +rctx_answer_dname(respctx_t *rctx); + +static isc_result_t +rctx_answer_positive(respctx_t *rctx); + +static isc_result_t +rctx_authority_negative(respctx_t *rctx); + +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx); + +static void +rctx_additional(respctx_t *rctx); + +static isc_result_t +rctx_referral(respctx_t *rctx); + +static isc_result_t +rctx_answer_none(respctx_t *rctx); + +static void +rctx_nextserver(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result); + +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo); + +static void +rctx_next(respctx_t *rctx); + +static void +rctx_chaseds(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result); + +static void +rctx_done(respctx_t *rctx, isc_result_t result); + +static void +rctx_logpacket(respctx_t *rctx); + +static void +rctx_opt(respctx_t *rctx); + +static void +rctx_edns(respctx_t *rctx); + +static isc_result_t +rctx_parse(respctx_t *rctx); + +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result); + +static isc_result_t +rctx_answer(respctx_t *rctx); + +static isc_result_t +rctx_lameserver(respctx_t *rctx); + +static isc_result_t +rctx_dispfail(respctx_t *rctx); + +static void +rctx_delonly_zone(respctx_t *rctx); + +static void +rctx_ncache(respctx_t *rctx); + +/*% + * Increment resolver-related statistics counters. + */ +static void +inc_stats(dns_resolver_t *res, isc_statscounter_t counter) { + if (res->view->resstats != NULL) { + isc_stats_increment(res->view->resstats, counter); + } +} + +static void +dec_stats(dns_resolver_t *res, isc_statscounter_t counter) { + if (res->view->resstats != NULL) { + isc_stats_decrement(res->view->resstats, counter); + } +} + +static isc_result_t +valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, + dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, unsigned int valoptions, + isc_task_t *task) { + dns_validator_t *validator = NULL; + dns_valarg_t *valarg = NULL; + isc_result_t result; + + if (SHUTTINGDOWN(fctx)) { + return (ISC_R_SHUTTINGDOWN); + } + + valarg = isc_mem_get(fctx->mctx, sizeof(*valarg)); + *valarg = (dns_valarg_t){ .fctx = fctx, .addrinfo = addrinfo }; + + dns_message_attach(message, &valarg->message); + + if (!ISC_LIST_EMPTY(fctx->validators)) { + valoptions |= DNS_VALIDATOR_DEFER; + } else { + valoptions &= ~DNS_VALIDATOR_DEFER; + } + + result = dns_validator_create(fctx->res->view, name, type, rdataset, + sigrdataset, message, valoptions, task, + validated, valarg, &validator); + if (result == ISC_R_SUCCESS) { + inc_stats(fctx->res, dns_resstatscounter_val); + if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { + INSIST(fctx->validator == NULL); + fctx->validator = validator; + } + ISC_LIST_APPEND(fctx->validators, validator, link); + } else { + dns_message_detach(&valarg->message); + isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); + } + return (result); +} + +static bool +rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) { + dns_namereln_t namereln; + dns_rdata_rrsig_t rrsig; + dns_rdata_t rdata = DNS_RDATA_INIT; + int order; + isc_result_t result; + unsigned int labels; + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + namereln = dns_name_fullcompare(&rrsig.signer, &fctx->domain, + &order, &labels); + if (namereln == dns_namereln_subdomain) { + return (true); + } + dns_rdata_reset(&rdata); + } + return (false); +} + +static bool +fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) { + dns_name_t *name; + dns_name_t *domain = &fctx->domain; + dns_rdataset_t *rdataset; + dns_rdatatype_t type; + isc_result_t result; + bool keep_auth = false; + + if (message->rcode == dns_rcode_nxdomain) { + return (false); + } + + /* + * A DS RRset can appear anywhere in a zone, even for a delegation-only + * zone. So a response to an explicit query for this type should be + * excluded from delegation-only fixup. + * + * SOA, NS, and DNSKEY can only exist at a zone apex, so a positive + * response to a query for these types can never violate the + * delegation-only assumption: if the query name is below a + * zone cut, the response should normally be a referral, which should + * be accepted; if the query name is below a zone cut but the server + * happens to have authority for the zone of the query name, the + * response is a (non-referral) answer. But this does not violate + * delegation-only because the query name must be in a different zone + * due to the "apex-only" nature of these types. Note that if the + * remote server happens to have authority for a child zone of a + * delegation-only zone, we may still incorrectly "fix" the response + * with NXDOMAIN for queries for other types. Unfortunately it's + * generally impossible to differentiate this case from violation of + * the delegation-only assumption. Once the resolver learns the + * correct zone cut, possibly via a separate query for an "apex-only" + * type, queries for other types will be resolved correctly. + * + * A query for type ANY will be accepted if it hits an exceptional + * type above in the answer section as it should be from a child + * zone. + * + * Also accept answers with RRSIG records from the child zone. + * Direct queries for RRSIG records should not be answered from + * the parent zone. + */ + + if (message->counts[DNS_SECTION_ANSWER] != 0 && + (fctx->type == dns_rdatatype_ns || fctx->type == dns_rdatatype_ds || + fctx->type == dns_rdatatype_soa || + fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_dnskey)) + { + result = dns_message_firstname(message, DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_ANSWER, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!dns_name_equal(name, &fctx->name)) { + continue; + } + type = rdataset->type; + /* + * RRsig from child? + */ + if (type == dns_rdatatype_rrsig && + rrsig_fromchildzone(fctx, rdataset)) + { + return (false); + } + /* + * Direct query for apex records or DS. + */ + if (fctx->type == type && + (type == dns_rdatatype_ds || + type == dns_rdatatype_ns || + type == dns_rdatatype_soa || + type == dns_rdatatype_dnskey)) + { + return (false); + } + /* + * Indirect query for apex records or DS. + */ + if (fctx->type == dns_rdatatype_any && + (type == dns_rdatatype_ns || + type == dns_rdatatype_ds || + type == dns_rdatatype_soa || + type == dns_rdatatype_dnskey)) + { + return (false); + } + } + result = dns_message_nextname(message, + DNS_SECTION_ANSWER); + } + } + + /* + * A NODATA response to a DS query? + */ + if (fctx->type == dns_rdatatype_ds && + message->counts[DNS_SECTION_ANSWER] == 0) + { + return (false); + } + + /* Look for referral or indication of answer from child zone? */ + if (message->counts[DNS_SECTION_AUTHORITY] == 0) { + goto munge; + } + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + type = rdataset->type; + if (type == dns_rdatatype_soa && + dns_name_equal(name, domain)) + { + keep_auth = true; + } + + if (type != dns_rdatatype_ns && + type != dns_rdatatype_soa && + type != dns_rdatatype_rrsig) + { + continue; + } + + if (type == dns_rdatatype_rrsig) { + if (rrsig_fromchildzone(fctx, rdataset)) { + return (false); + } else { + continue; + } + } + + /* NS or SOA records. */ + if (dns_name_equal(name, domain)) { + /* + * If a query for ANY causes a negative + * response, we can be sure that this is + * an empty node. For other type of queries + * we cannot differentiate an empty node + * from a node that just doesn't have that + * type of record. We only accept the former + * case. + */ + if (message->counts[DNS_SECTION_ANSWER] == 0 && + fctx->type == dns_rdatatype_any) + { + return (false); + } + } else if (dns_name_issubdomain(name, domain)) { + /* Referral or answer from child zone. */ + return (false); + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + +munge: + message->rcode = dns_rcode_nxdomain; + message->counts[DNS_SECTION_ANSWER] = 0; + if (!keep_auth) { + message->counts[DNS_SECTION_AUTHORITY] = 0; + } + message->counts[DNS_SECTION_ADDITIONAL] = 0; + return (true); +} + +static isc_result_t +fctx_starttimer(fetchctx_t *fctx) { + /* + * Start the lifetime timer for fctx. + * + * This is also used for stopping the idle timer; in that + * case we must purge events already posted to ensure that + * no further idle events are delivered. + */ + return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, + NULL, true)); +} + +static isc_result_t +fctx_starttimer_trystale(fetchctx_t *fctx) { + /* + * Start the stale-answer-client-timeout timer for fctx. + */ + + return (isc_timer_reset(fctx->timer_try_stale, isc_timertype_once, + &fctx->expires_try_stale, NULL, true)); +} + +static void +fctx_stoptimer(fetchctx_t *fctx) { + isc_result_t result; + + /* + * We don't return a result if resetting the timer to inactive fails + * since there's nothing to be done about it. Resetting to inactive + * should never fail anyway, since the code as currently written + * cannot fail in that case. + */ + result = isc_timer_reset(fctx->timer, isc_timertype_inactive, NULL, + NULL, true); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_reset(): %s", + isc_result_totext(result)); + } +} + +static void +fctx_stoptimer_trystale(fetchctx_t *fctx) { + isc_result_t result; + + if (fctx->timer_try_stale != NULL) { + result = isc_timer_reset(fctx->timer_try_stale, + isc_timertype_inactive, NULL, NULL, + true); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_reset(): %s", + isc_result_totext(result)); + } + } +} + +static isc_result_t +fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) { + /* + * Start the idle timer for fctx. The lifetime timer continues + * to be in effect. + */ + return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, + interval, false)); +} + +/* + * Stopping the idle timer is equivalent to calling fctx_starttimer(), but + * we use fctx_stopidletimer for readability in the code below. + */ +#define fctx_stopidletimer fctx_starttimer + +static void +resquery_destroy(resquery_t **queryp) { + dns_resolver_t *res; + bool empty; + resquery_t *query; + fetchctx_t *fctx; + unsigned int bucket; + + REQUIRE(queryp != NULL); + query = *queryp; + *queryp = NULL; + REQUIRE(!ISC_LINK_LINKED(query, link)); + + INSIST(query->tcpsocket == NULL); + + fctx = query->fctx; + res = fctx->res; + bucket = fctx->bucketnum; + + LOCK(&res->buckets[bucket].lock); + fctx->nqueries--; + empty = fctx_decreference(query->fctx); + UNLOCK(&res->buckets[bucket].lock); + + if (query->rmessage != NULL) { + dns_message_detach(&query->rmessage); + } + + query->magic = 0; + isc_mem_put(query->mctx, query, sizeof(*query)); + + if (empty) { + empty_bucket(res); + } +} + +/*% + * Update EDNS statistics for a server after not getting a response to a UDP + * query sent to it. + */ +static void +update_edns_stats(resquery_t *query) { + fetchctx_t *fctx = query->fctx; + + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + return; + } + + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + dns_adb_ednsto(fctx->adb, query->addrinfo, query->udpsize); + } else { + dns_adb_timeout(fctx->adb, query->addrinfo); + } +} + +static void +fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp, + isc_time_t *finish, bool no_response, bool age_untried) { + fetchctx_t *fctx; + resquery_t *query; + unsigned int rtt, rttms; + unsigned int factor; + dns_adbfind_t *find; + dns_adbaddrinfo_t *addrinfo; + isc_socket_t *sock; + isc_stdtime_t now; + + query = *queryp; + fctx = query->fctx; + + FCTXTRACE("cancelquery"); + + REQUIRE(!RESQUERY_CANCELED(query)); + + query->attributes |= RESQUERY_ATTR_CANCELED; + + /* + * Should we update the RTT? + */ + if (finish != NULL || no_response) { + if (finish != NULL) { + /* + * We have both the start and finish times for this + * packet, so we can compute a real RTT. + */ + rtt = (unsigned int)isc_time_microdiff(finish, + &query->start); + factor = DNS_ADB_RTTADJDEFAULT; + + rttms = rtt / 1000; + if (rttms < DNS_RESOLVER_QRYRTTCLASS0) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt0); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt1); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt2); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt3); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt4); + } else { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt5); + } + } else { + uint32_t value; + uint32_t mask; + + update_edns_stats(query); + + /* + * If "forward first;" is used and a forwarder timed + * out, do not attempt to query it again in this fetch + * context. + */ + if (fctx->fwdpolicy == dns_fwdpolicy_first && + ISFORWARDER(query->addrinfo)) + { + add_bad(fctx, query->rmessage, query->addrinfo, + ISC_R_TIMEDOUT, badns_forwarder); + } + + /* + * We don't have an RTT for this query. Maybe the + * packet was lost, or maybe this server is very + * slow. We don't know. Increase the RTT. + */ + INSIST(no_response); + value = isc_random32(); + if (query->addrinfo->srtt > 800000) { + mask = 0x3fff; + } else if (query->addrinfo->srtt > 400000) { + mask = 0x7fff; + } else if (query->addrinfo->srtt > 200000) { + mask = 0xffff; + } else if (query->addrinfo->srtt > 100000) { + mask = 0x1ffff; + } else if (query->addrinfo->srtt > 50000) { + mask = 0x3ffff; + } else if (query->addrinfo->srtt > 25000) { + mask = 0x7ffff; + } else { + mask = 0xfffff; + } + + /* + * Don't adjust timeout on EDNS queries unless we have + * seen a EDNS response. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 && + !EDNSOK(query->addrinfo)) + { + mask >>= 2; + } + + rtt = query->addrinfo->srtt + (value & mask); + if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US) { + rtt = MAX_SINGLE_QUERY_TIMEOUT_US; + } + + /* + * Replace the current RTT with our value. + */ + factor = DNS_ADB_RTTADJREPLACE; + } + + dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor); + } + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + /* Inform the ADB that we're ending a UDP fetch */ + dns_adb_endudpfetch(fctx->adb, query->addrinfo); + } + + /* + * Age RTTs of servers not tried. + */ + isc_stdtime_get(&now); + if (finish != NULL || age_untried) { + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, now); + } + } + } + + if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) { + for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; + find = ISC_LIST_NEXT(find, publink)) + { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, + now); + } + } + } + } + + if ((finish != NULL || age_untried) && TRIEDALT(fctx)) { + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, now); + } + } + for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; + find = ISC_LIST_NEXT(find, publink)) + { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, + now); + } + } + } + } + + /* + * Check for any outstanding socket events. If they exist, cancel + * them and let the event handlers finish the cleanup. The resolver + * only needs to worry about managing the connect and send events; + * the dispatcher manages the recv events. + */ + if (RESQUERY_CONNECTING(query)) { + /* + * Cancel the connect. + */ + if (query->tcpsocket != NULL) { + isc_socket_cancel(query->tcpsocket, NULL, + ISC_SOCKCANCEL_CONNECT); + } else if (query->dispentry != NULL) { + INSIST(query->exclusivesocket); + sock = dns_dispatch_getentrysocket(query->dispentry); + if (sock != NULL) { + isc_socket_cancel(sock, NULL, + ISC_SOCKCANCEL_CONNECT); + } + } + } + if (RESQUERY_SENDING(query)) { + /* + * Cancel the pending send. + */ + if (query->exclusivesocket && query->dispentry != NULL) { + sock = dns_dispatch_getentrysocket(query->dispentry); + } else { + sock = dns_dispatch_getsocket(query->dispatch); + } + if (sock != NULL) { + isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND); + } + } + + if (query->dispentry != NULL) { + dns_dispatch_removeresponse(&query->dispentry, deventp); + } + + ISC_LIST_UNLINK(fctx->queries, query, link); + + if (query->tsig != NULL) { + isc_buffer_free(&query->tsig); + } + + if (query->tsigkey != NULL) { + dns_tsigkey_detach(&query->tsigkey); + } + + if (query->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + + if (!(RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query))) { + /* + * It's safe to destroy the query now. + */ + resquery_destroy(&query); + } +} + +static void +fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) { + resquery_t *query, *next_query; + + FCTXTRACE("cancelqueries"); + + for (query = ISC_LIST_HEAD(fctx->queries); query != NULL; + query = next_query) + { + next_query = ISC_LIST_NEXT(query, link); + fctx_cancelquery(&query, NULL, NULL, no_response, age_untried); + } +} + +static void +fctx_cleanupfinds(fetchctx_t *fctx) { + dns_adbfind_t *find, *next_find; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find) + { + next_find = ISC_LIST_NEXT(find, publink); + ISC_LIST_UNLINK(fctx->finds, find, publink); + dns_adb_destroyfind(&find); + } + fctx->find = NULL; +} + +static void +fctx_cleanupaltfinds(fetchctx_t *fctx) { + dns_adbfind_t *find, *next_find; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; + find = next_find) + { + next_find = ISC_LIST_NEXT(find, publink); + ISC_LIST_UNLINK(fctx->altfinds, find, publink); + dns_adb_destroyfind(&find); + } + fctx->altfind = NULL; +} + +static void +fctx_cleanupforwaddrs(fetchctx_t *fctx) { + dns_adbaddrinfo_t *addr, *next_addr; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL; + addr = next_addr) + { + next_addr = ISC_LIST_NEXT(addr, publink); + ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink); + dns_adb_freeaddrinfo(fctx->adb, &addr); + } +} + +static void +fctx_cleanupaltaddrs(fetchctx_t *fctx) { + dns_adbaddrinfo_t *addr, *next_addr; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL; + addr = next_addr) + { + next_addr = ISC_LIST_NEXT(addr, publink); + ISC_LIST_UNLINK(fctx->altaddrs, addr, publink); + dns_adb_freeaddrinfo(fctx->adb, &addr); + } +} + +static void +fctx_stopqueries(fetchctx_t *fctx, bool no_response, bool age_untried) { + FCTXTRACE("stopqueries"); + fctx_cancelqueries(fctx, no_response, age_untried); + fctx_stoptimer(fctx); + fctx_stoptimer_trystale(fctx); +} + +static void +fctx_cleanupall(fetchctx_t *fctx) { + fctx_cleanupfinds(fctx); + fctx_cleanupaltfinds(fctx); + fctx_cleanupforwaddrs(fctx); + fctx_cleanupaltaddrs(fctx); +} + +static void +fcount_logspill(fetchctx_t *fctx, fctxcount_t *counter, bool final) { + char dbuf[DNS_NAME_FORMATSIZE]; + isc_stdtime_t now; + + if (!isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + return; + } + + /* Do not log a message if there were no dropped fetches. */ + if (counter->dropped == 0) { + return; + } + + /* Do not log the cumulative message if the previous log is recent. */ + isc_stdtime_get(&now); + if (!final && counter->logged > now - 60) { + return; + } + + dns_name_format(&fctx->domain, dbuf, sizeof(dbuf)); + + if (!final) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "too many simultaneous fetches for %s " + "(allowed %d spilled %d)", + dbuf, counter->allowed, counter->dropped); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "fetch counters for %s now being discarded " + "(allowed %d spilled %d; cumulative since " + "initial trigger event)", + dbuf, counter->allowed, counter->dropped); + } + + counter->logged = now; +} + +static isc_result_t +fcount_incr(fetchctx_t *fctx, bool force) { + isc_result_t result = ISC_R_SUCCESS; + zonebucket_t *dbucket; + fctxcount_t *counter; + unsigned int bucketnum; + + REQUIRE(fctx != NULL); + REQUIRE(fctx->res != NULL); + + INSIST(fctx->dbucketnum == RES_NOBUCKET); + bucketnum = dns_name_fullhash(&fctx->domain, false) % + RES_DOMAIN_BUCKETS; + + dbucket = &fctx->res->dbuckets[bucketnum]; + + LOCK(&dbucket->lock); + for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL; + counter = ISC_LIST_NEXT(counter, link)) + { + if (dns_name_equal(counter->domain, &fctx->domain)) { + break; + } + } + + if (counter == NULL) { + counter = isc_mem_get(dbucket->mctx, sizeof(fctxcount_t)); + { + ISC_LINK_INIT(counter, link); + counter->count = 1; + counter->logged = 0; + counter->allowed = 1; + counter->dropped = 0; + counter->domain = + dns_fixedname_initname(&counter->fdname); + dns_name_copynf(&fctx->domain, counter->domain); + ISC_LIST_APPEND(dbucket->list, counter, link); + } + } else { + uint_fast32_t spill = atomic_load_acquire(&fctx->res->zspill); + if (!force && spill != 0 && counter->count >= spill) { + counter->dropped++; + fcount_logspill(fctx, counter, false); + result = ISC_R_QUOTA; + } else { + counter->count++; + counter->allowed++; + } + } + UNLOCK(&dbucket->lock); + + if (result == ISC_R_SUCCESS) { + fctx->dbucketnum = bucketnum; + } + + return (result); +} + +static void +fcount_decr(fetchctx_t *fctx) { + zonebucket_t *dbucket; + fctxcount_t *counter; + + REQUIRE(fctx != NULL); + + if (fctx->dbucketnum == RES_NOBUCKET) { + return; + } + + dbucket = &fctx->res->dbuckets[fctx->dbucketnum]; + + LOCK(&dbucket->lock); + for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL; + counter = ISC_LIST_NEXT(counter, link)) + { + if (dns_name_equal(counter->domain, &fctx->domain)) { + break; + } + } + + if (counter != NULL) { + INSIST(counter->count != 0); + counter->count--; + fctx->dbucketnum = RES_NOBUCKET; + + if (counter->count == 0) { + fcount_logspill(fctx, counter, true); + ISC_LIST_UNLINK(dbucket->list, counter, link); + isc_mem_put(dbucket->mctx, counter, sizeof(*counter)); + } + } + + UNLOCK(&dbucket->lock); +} + +static void +fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) { + dns_fetchevent_t *event, *next_event; + isc_task_t *task; + unsigned int count = 0; + isc_interval_t i; + bool logit = false; + isc_time_t now; + unsigned int old_spillat; + unsigned int new_spillat = 0; /* initialized to silence + * compiler warnings */ + + /* + * Caller must be holding the appropriate bucket lock. + */ + REQUIRE(fctx->state == fetchstate_done); + + FCTXTRACE("sendevents"); + + /* + * Keep some record of fetch result for logging later (if required). + */ + fctx->result = result; + fctx->exitline = line; + TIME_NOW(&now); + fctx->duration = isc_time_microdiff(&now, &fctx->start); + + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(fctx->events, event, ev_link); + if (event->ev_type == DNS_EVENT_TRYSTALE) { + /* + * Not applicable to TRY STALE events, this function is + * called when the fetch has either completed or timed + * out due to resolver-query-timeout being reached. + */ + isc_task_detach((isc_task_t **)&event->ev_sender); + isc_event_free((isc_event_t **)&event); + continue; + } + task = event->ev_sender; + event->ev_sender = fctx; + event->vresult = fctx->vresult; + if (!HAVE_ANSWER(fctx)) { + event->result = result; + } + + INSIST(event->result != ISC_R_SUCCESS || + dns_rdataset_isassociated(event->rdataset) || + fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig); + + /* + * Negative results must be indicated in event->result. + */ + if (dns_rdataset_isassociated(event->rdataset) && + NEGATIVE(event->rdataset)) + { + INSIST(event->result == DNS_R_NCACHENXDOMAIN || + event->result == DNS_R_NCACHENXRRSET); + } + + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event)); + count++; + } + + if (HAVE_ANSWER(fctx) && fctx->spilled && + (count < fctx->res->spillatmax || fctx->res->spillatmax == 0)) + { + LOCK(&fctx->res->lock); + if (count == fctx->res->spillat && + !atomic_load_acquire(&fctx->res->exiting)) + { + old_spillat = fctx->res->spillat; + fctx->res->spillat += 5; + if (fctx->res->spillat > fctx->res->spillatmax && + fctx->res->spillatmax != 0) + { + fctx->res->spillat = fctx->res->spillatmax; + } + new_spillat = fctx->res->spillat; + if (new_spillat != old_spillat) { + logit = true; + } + isc_interval_set(&i, 20 * 60, 0); + result = isc_timer_reset(fctx->res->spillattimer, + isc_timertype_ticker, NULL, &i, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + UNLOCK(&fctx->res->lock); + if (logit) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "clients-per-query increased to %u", + new_spillat); + } + } +} + +static void +log_edns(fetchctx_t *fctx) { + char domainbuf[DNS_NAME_FORMATSIZE]; + + if (fctx->reason == NULL) { + return; + } + + /* + * We do not know if fctx->domain is the actual domain the record + * lives in or a parent domain so we have a '?' after it. + */ + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_EDNS_DISABLED, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "success resolving '%s' (in '%s'?) after %s", fctx->info, + domainbuf, fctx->reason); +} + +static void +fctx_done(fetchctx_t *fctx, isc_result_t result, int line) { + dns_resolver_t *res; + bool no_response = false; + bool age_untried = false; + + REQUIRE(line >= 0); + + FCTXTRACE("done"); + + res = fctx->res; + + if (result == ISC_R_SUCCESS) { + /*% + * Log any deferred EDNS timeout messages. + */ + log_edns(fctx); + no_response = true; + if (fctx->qmin_warning != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "success resolving '%s' " + "after disabling qname minimization due " + "to '%s'", + fctx->info, + isc_result_totext(fctx->qmin_warning)); + } + } else if (result == ISC_R_TIMEDOUT) { + age_untried = true; + } + + fctx->qmin_warning = ISC_R_SUCCESS; + fctx->reason = NULL; + + fctx_stopqueries(fctx, no_response, age_untried); + + LOCK(&res->buckets[fctx->bucketnum].lock); + + fctx->state = fetchstate_done; + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx_sendevents(fctx, result, line); + + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +static void +process_sendevent(resquery_t *query, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + bool destroy_query = false; + bool retry = false; + isc_result_t result; + fetchctx_t *fctx; + + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + if (query->sends == 0 && query->connects == 0) { + /* + * This query was canceled while the + * isc_socket_sendto/connect() was in progress. + */ + if (query->tcpsocket != NULL) { + isc_socket_detach(&query->tcpsocket); + } + destroy_query = true; + } + } else { + switch (sevent->result) { + case ISC_R_SUCCESS: + break; + + case ISC_R_HOSTUNREACH: + case ISC_R_NETUNREACH: + case ISC_R_NOPERM: + case ISC_R_ADDRNOTAVAIL: + case ISC_R_CONNREFUSED: + FCTXTRACE3("query canceled in sendevent(): " + "no route to host; no response", + sevent->result); + + /* + * No route to remote. + */ + add_bad(fctx, query->rmessage, query->addrinfo, + sevent->result, badns_unreachable); + fctx_cancelquery(&query, NULL, NULL, true, false); + retry = true; + break; + + default: + FCTXTRACE3("query canceled in sendevent() due to " + "unexpected event result; responding", + sevent->result); + + fctx_cancelquery(&query, NULL, NULL, false, false); + break; + } + } + + if (event->ev_type == ISC_SOCKEVENT_CONNECT) { + isc_event_free(&event); + } + + if (retry) { + /* + * Behave as if the idle timer has expired. For TCP + * this may not actually reflect the latest timer. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, true, false); + } + } + + if (destroy_query) { + resquery_destroy(&query); + } +} + +static void +resquery_udpconnected(isc_task_t *task, isc_event_t *event) { + resquery_t *query = event->ev_arg; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); + + QTRACE("udpconnected"); + + UNUSED(task); + + INSIST(RESQUERY_CONNECTING(query)); + + query->connects--; + + process_sendevent(query, event); +} + +static void +resquery_senddone(isc_task_t *task, isc_event_t *event) { + resquery_t *query = event->ev_arg; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE); + + QTRACE("senddone"); + + /* + * XXXRTH + * + * Currently we don't wait for the senddone event before retrying + * a query. This means that if we get really behind, we may end + * up doing extra work! + */ + + UNUSED(task); + + INSIST(RESQUERY_SENDING(query)); + + query->sends--; + + process_sendevent(query, event); +} + +static isc_result_t +fctx_addopt(dns_message_t *message, unsigned int version, uint16_t udpsize, + dns_ednsopt_t *ednsopts, size_t count) { + dns_rdataset_t *rdataset = NULL; + isc_result_t result; + + result = dns_message_buildopt(message, &rdataset, version, udpsize, + DNS_MESSAGEEXTFLAG_DO, ednsopts, count); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (dns_message_setopt(message, rdataset)); +} + +static void +fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) { + unsigned int seconds; + unsigned int us; + + us = fctx->res->retryinterval * 1000; + /* + * Exponential backoff after the first few tries. + */ + if (fctx->restarts > fctx->res->nonbackofftries) { + int shift = fctx->restarts - fctx->res->nonbackofftries; + if (shift > 6) { + shift = 6; + } + us <<= shift; + } + + /* + * Add a fudge factor to the expected rtt based on the current + * estimate. + */ + if (rtt < 50000) { + rtt += 50000; + } else if (rtt < 100000) { + rtt += 100000; + } else { + rtt += 200000; + } + + /* + * Always wait for at least the expected rtt. + */ + if (us < rtt) { + us = rtt; + } + + /* + * But don't ever wait for more than 10 seconds. + */ + if (us > MAX_SINGLE_QUERY_TIMEOUT_US) { + us = MAX_SINGLE_QUERY_TIMEOUT_US; + } + + seconds = us / US_PER_SEC; + us -= seconds * US_PER_SEC; + isc_interval_set(&fctx->interval, seconds, us * 1000); +} + +static isc_result_t +fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + unsigned int options) { + dns_resolver_t *res; + isc_task_t *task; + isc_result_t result; + resquery_t *query; + isc_sockaddr_t addr; + bool have_addr = false; + unsigned int srtt; + isc_dscp_t dscp = -1; + unsigned int bucketnum; + + FCTXTRACE("query"); + + res = fctx->res; + task = res->buckets[fctx->bucketnum].task; + + srtt = addrinfo->srtt; + + /* + * Allow an additional second for the kernel to resend the SYN (or + * SYN without ECN in the case of stupid firewalls blocking ECN + * negotiation) over the current RTT estimate. + */ + if ((options & DNS_FETCHOPT_TCP) != 0) { + srtt += 1000000; + } + + /* + * A forwarder needs to make multiple queries. Give it at least + * a second to do these in. + */ + if (ISFORWARDER(addrinfo) && srtt < 1000000) { + srtt = 1000000; + } + + fctx_setretryinterval(fctx, srtt); + result = fctx_startidletimer(fctx, &fctx->interval); + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(ISC_LIST_EMPTY(fctx->validators)); + + query = isc_mem_get(fctx->mctx, sizeof(*query)); + query->rmessage = NULL; + dns_message_create(fctx->mctx, DNS_MESSAGE_INTENTPARSE, + &query->rmessage); + query->mctx = fctx->mctx; + query->options = options; + query->attributes = 0; + query->sends = 0; + query->connects = 0; + query->dscp = addrinfo->dscp; + query->udpsize = 0; + /* + * Note that the caller MUST guarantee that 'addrinfo' will remain + * valid until this query is canceled. + */ + query->addrinfo = addrinfo; + TIME_NOW(&query->start); + + /* + * If this is a TCP query, then we need to make a socket and + * a dispatch for it here. Otherwise we use the resolver's + * shared dispatch. + */ + query->dispatchmgr = res->dispatchmgr; + query->dispatch = NULL; + query->exclusivesocket = false; + query->tcpsocket = NULL; + if (res->view->peers != NULL) { + dns_peer_t *peer = NULL; + isc_netaddr_t dstip; + bool usetcp = false; + isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr); + result = dns_peerlist_peerbyaddr(res->view->peers, &dstip, + &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getquerysource(peer, &addr); + if (result == ISC_R_SUCCESS) { + have_addr = true; + } + result = dns_peer_getquerydscp(peer, &dscp); + if (result == ISC_R_SUCCESS) { + query->dscp = dscp; + } + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + query->options |= DNS_FETCHOPT_TCP; + } + } + } + + dscp = -1; + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + int pf; + + pf = isc_sockaddr_pf(&addrinfo->sockaddr); + if (!have_addr) { + switch (pf) { + case PF_INET: + result = dns_dispatch_getlocaladdress( + res->dispatches4->dispatches[0], &addr); + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case PF_INET6: + result = dns_dispatch_getlocaladdress( + res->dispatches6->dispatches[0], &addr); + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + break; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + } + isc_sockaddr_setport(&addr, 0); + if (query->dscp == -1) { + query->dscp = dscp; + } + + result = isc_socket_create(res->socketmgr, pf, + isc_sockettype_tcp, + &query->tcpsocket); + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + +#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT + result = isc_socket_bind(query->tcpsocket, &addr, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_socket; + } +#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */ + + /* + * A dispatch will be created once the connect succeeds. + */ + } else { + if (have_addr) { + unsigned int attrs, attrmask; + attrs = DNS_DISPATCHATTR_UDP; + switch (isc_sockaddr_pf(&addr)) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_query; + } + attrmask = DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + result = dns_dispatch_getudp( + res->dispatchmgr, res->socketmgr, res->taskmgr, + &addr, 4096, 20000, 32768, 16411, 16433, attrs, + attrmask, &query->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + } else { + switch (isc_sockaddr_pf(&addrinfo->sockaddr)) { + case PF_INET: + dns_dispatch_attach( + dns_resolver_dispatchv4(res), + &query->dispatch); + query->exclusivesocket = res->exclusivev4; + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case PF_INET6: + dns_dispatch_attach( + dns_resolver_dispatchv6(res), + &query->dispatch); + query->exclusivesocket = res->exclusivev6; + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_query; + } + } + + if (query->dscp == -1) { + query->dscp = dscp; + } + /* + * We should always have a valid dispatcher here. If we + * don't support a protocol family, then its dispatcher + * will be NULL, but we shouldn't be finding addresses for + * protocol types we don't support, so the dispatcher + * we found should never be NULL. + */ + INSIST(query->dispatch != NULL); + } + + query->dispentry = NULL; + query->fctx = fctx; /* reference added by caller */ + query->tsig = NULL; + query->tsigkey = NULL; + ISC_LINK_INIT(query, link); + query->magic = QUERY_MAGIC; + + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + /* + * Connect to the remote server. + * + * XXXRTH Should we attach to the socket? + */ + if (query->dscp != -1) { + isc_socket_dscp(query->tcpsocket, query->dscp); + } + result = isc_socket_connect(query->tcpsocket, + &addrinfo->sockaddr, task, + resquery_connected, query); + if (result != ISC_R_SUCCESS) { + goto cleanup_socket; + } + query->connects++; + QTRACE("connecting via TCP"); + } else { + if (dns_adbentry_overquota(addrinfo->entry)) { + goto cleanup_dispatch; + } + + /* Inform the ADB that we're starting a UDP fetch */ + dns_adb_beginudpfetch(fctx->adb, addrinfo); + + result = resquery_send(query); + if (result != ISC_R_SUCCESS) { + goto cleanup_udpfetch; + } + } + + fctx->querysent++; + + ISC_LIST_APPEND(fctx->queries, query, link); + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + fctx->nqueries++; + UNLOCK(&res->buckets[bucketnum].lock); + if (isc_sockaddr_pf(&addrinfo->sockaddr) == PF_INET) { + inc_stats(res, dns_resstatscounter_queryv4); + } else { + inc_stats(res, dns_resstatscounter_queryv6); + } + if (res->view->resquerystats != NULL) { + dns_rdatatypestats_increment(res->view->resquerystats, + fctx->type); + } + + return (ISC_R_SUCCESS); + +cleanup_socket: + isc_socket_detach(&query->tcpsocket); + +cleanup_udpfetch: + if (!RESQUERY_CANCELED(query)) { + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + /* Inform the ADB that we're ending a UDP fetch */ + dns_adb_endudpfetch(fctx->adb, addrinfo); + } + } + +cleanup_dispatch: + if (query->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + +cleanup_query: + if (query->connects == 0) { + query->magic = 0; + dns_message_detach(&query->rmessage); + isc_mem_put(fctx->mctx, query, sizeof(*query)); + } + + RUNTIME_CHECK(fctx_stopidletimer(fctx) == ISC_R_SUCCESS); + + return (result); +} + +static bool +bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + + for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + if (isc_sockaddr_equal(sa, address)) { + return (true); + } + } + + return (false); +} + +static void +add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return; + } +#endif /* ifdef ENABLE_AFL */ + if (bad_edns(fctx, address)) { + return; + } + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + + *sa = *address; + ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link); +} + +static struct tried * +triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL; + tried = ISC_LIST_NEXT(tried, link)) + { + if (isc_sockaddr_equal(&tried->addr, address)) { + return (tried); + } + } + + return (NULL); +} + +static void +add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + tried = triededns(fctx, address); + if (tried != NULL) { + tried->count++; + return; + } + + tried = isc_mem_get(fctx->mctx, sizeof(*tried)); + + tried->addr = *address; + tried->count = 1; + ISC_LIST_INITANDAPPEND(fctx->edns, tried, link); +} + +static struct tried * +triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + for (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL; + tried = ISC_LIST_NEXT(tried, link)) + { + if (isc_sockaddr_equal(&tried->addr, address)) { + return (tried); + } + } + + return (NULL); +} + +static void +add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + tried = triededns512(fctx, address); + if (tried != NULL) { + tried->count++; + return; + } + + tried = isc_mem_get(fctx->mctx, sizeof(*tried)); + + tried->addr = *address; + tried->count = 1; + ISC_LIST_INITANDAPPEND(fctx->edns512, tried, link); +} + +static size_t +addr2buf(void *buf, const size_t bufsize, const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + switch (netaddr.family) { + case AF_INET: + INSIST(bufsize >= 4); + memmove(buf, &netaddr.type.in, 4); + return (4); + case AF_INET6: + INSIST(bufsize >= 16); + memmove(buf, &netaddr.type.in6, 16); + return (16); + default: + UNREACHABLE(); + } + return (0); +} + +static isc_socket_t * +query2sock(const resquery_t *query) { + if (query->exclusivesocket) { + return (dns_dispatch_getentrysocket(query->dispentry)); + } else { + return (dns_dispatch_getsocket(query->dispatch)); + } +} + +static size_t +add_serveraddr(uint8_t *buf, const size_t bufsize, const resquery_t *query) { + return (addr2buf(buf, bufsize, &query->addrinfo->sockaddr)); +} + +/* + * Client cookie is 8 octets. + * Server cookie is [8..32] octets. + */ +#define CLIENT_COOKIE_SIZE 8U +#define COOKIE_BUFFER_SIZE (8U + 32U) + +static void +compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) { + INSIST(len >= CLIENT_COOKIE_SIZE); + STATIC_ASSERT(sizeof(query->fctx->res->view->secret) >= + ISC_SIPHASH24_KEY_LENGTH, + "The view->secret size can't fit SipHash 2-4 key length"); + + uint8_t buf[16] ISC_NONSTRING = { 0 }; + size_t buflen = add_serveraddr(buf, sizeof(buf), query); + + uint8_t digest[ISC_SIPHASH24_TAG_LENGTH] ISC_NONSTRING = { 0 }; + isc_siphash24(query->fctx->res->view->secret, buf, buflen, digest); + memmove(cookie, digest, CLIENT_COOKIE_SIZE); +} + +static isc_result_t +issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, bool checknta, bool *ntap, bool *issecure) { + dns_name_t suffix; + unsigned int labels; + + /* + * For DS variants we need to check fom the parent domain, + * since there may be a negative trust anchor for the name, + * while the enclosing domain where the DS record lives is + * under a secure entry point. + */ + labels = dns_name_countlabels(name); + if (dns_rdatatype_atparent(type) && labels > 1) { + dns_name_init(&suffix, NULL); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } + + return (dns_view_issecuredomain(view, name, now, checknta, ntap, + issecure)); +} + +static isc_result_t +resquery_send(resquery_t *query) { + fetchctx_t *fctx; + isc_result_t result; + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_region_t r; + dns_resolver_t *res; + isc_task_t *task; + isc_socket_t *sock; + isc_buffer_t tcpbuffer; + isc_sockaddr_t *address; + isc_buffer_t *buffer; + isc_netaddr_t ipaddr; + dns_tsigkey_t *tsigkey = NULL; + dns_peer_t *peer = NULL; + bool useedns; + dns_compress_t cctx; + bool cleanup_cctx = false; + bool secure_domain; + bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0); + dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; + unsigned ednsopt = 0; + uint16_t hint = 0, udpsize = 0; /* No EDNS */ +#ifdef HAVE_DNSTAP + isc_sockaddr_t localaddr, *la = NULL; + unsigned char zone[DNS_NAME_MAXWIRE]; + dns_dtmsgtype_t dtmsgtype; + isc_region_t zr; + isc_buffer_t zb; +#endif /* HAVE_DNSTAP */ + + fctx = query->fctx; + QTRACE("send"); + + res = fctx->res; + task = res->buckets[fctx->bucketnum].task; + address = NULL; + + if (tcp) { + /* + * Reserve space for the TCP message length. + */ + isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data)); + isc_buffer_init(&query->buffer, query->data + 2, + sizeof(query->data) - 2); + buffer = &tcpbuffer; + } else { + isc_buffer_init(&query->buffer, query->data, + sizeof(query->data)); + buffer = &query->buffer; + } + + result = dns_message_gettempname(fctx->qmessage, &qname); + if (result != ISC_R_SUCCESS) { + goto cleanup_temps; + } + result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup_temps; + } + + /* + * Get a query id from the dispatch. + */ + result = dns_dispatch_addresponse(query->dispatch, 0, + &query->addrinfo->sockaddr, task, + resquery_response, query, &query->id, + &query->dispentry, res->socketmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup_temps; + } + + fctx->qmessage->opcode = dns_opcode_query; + + /* + * Set up question. + */ + dns_name_clone(&fctx->name, qname); + dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION); + qname = NULL; + qrdataset = NULL; + + /* + * Set RD if the client has requested that we do a recursive query, + * or if we're sending to a forwarder. + */ + if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 || + ISFORWARDER(query->addrinfo)) + { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD; + } + + /* + * Set CD if the client says not to validate, or if the + * question is under a secure entry point and this is a + * recursive/forward query -- unless the client said not to. + */ + if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + /* Do nothing */ + } else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; + } else if (res->view->enablevalidation && + ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) + { + bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0); + bool ntacovered = false; + result = issecuredomain(res->view, &fctx->name, fctx->type, + isc_time_seconds(&query->start), + checknta, &ntacovered, &secure_domain); + if (result != ISC_R_SUCCESS) { + secure_domain = false; + } + if (secure_domain || + (ISFORWARDER(query->addrinfo) && ntacovered)) + { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; + } + } + + /* + * We don't have to set opcode because it defaults to query. + */ + fctx->qmessage->id = query->id; + + /* + * Convert the question to wire format. + */ + result = dns_compress_init(&cctx, -1, fctx->res->mctx); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + cleanup_cctx = true; + + result = dns_message_renderbegin(fctx->qmessage, &cctx, &query->buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_QUESTION, + 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + peer = NULL; + isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr); + (void)dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer); + + /* + * The ADB does not know about servers with "edns no". Check this, + * and then inform the ADB for future use. + */ + if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0 && + peer != NULL && + dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS && + !useedns) + { + query->options |= DNS_FETCHOPT_NOEDNS0; + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); + } + + /* Sync NOEDNS0 flag in addrinfo->flags and options now. */ + if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) != 0) { + query->options |= DNS_FETCHOPT_NOEDNS0; + } + + if (fctx->timeout && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + isc_sockaddr_t *sockaddr = &query->addrinfo->sockaddr; + struct tried *tried; + + if ((tried = triededns(fctx, sockaddr)) != NULL) { + if (tried->count == 1U) { + hint = dns_adb_getudpsize(fctx->adb, + query->addrinfo); + } else if (tried->count >= 2U) { + query->options |= DNS_FETCHOPT_EDNS512; + fctx->reason = "reducing the advertised EDNS " + "UDP packet size to 512 octets"; + } + } + } + fctx->timeout = false; + + /* + * Use EDNS0, unless the caller doesn't want it, or we know that the + * remote server doesn't like it. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) { + unsigned int version = DNS_EDNS_VERSION; + unsigned int flags = query->addrinfo->flags; + bool reqnsid = res->view->requestnsid; + bool sendcookie = res->view->sendcookie; + bool tcpkeepalive = false; + unsigned char cookie[COOKIE_BUFFER_SIZE]; + uint16_t padding = 0; + + if ((flags & FCTX_ADDRINFO_EDNSOK) != 0 && + (query->options & DNS_FETCHOPT_EDNS512) == 0) + { + udpsize = dns_adb_probesize(fctx->adb, + query->addrinfo, + fctx->timeouts); + if (udpsize > res->udpsize) { + udpsize = res->udpsize; + } + } + + if (peer != NULL) { + (void)dns_peer_getudpsize(peer, &udpsize); + } + + if (udpsize == 0U && res->udpsize == 512U) { + udpsize = 512; + } + + /* + * Was the size forced to 512 in the configuration? + */ + if (udpsize == 512U) { + query->options |= DNS_FETCHOPT_EDNS512; + } + + /* + * We have talked to this server before. + */ + if (hint != 0U) { + udpsize = hint; + } + + /* + * We know nothing about the peer's capabilities + * so start with minimal EDNS UDP size. + */ + if (udpsize == 0U) { + udpsize = 512; + } + + if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) { + version = flags & DNS_FETCHOPT_EDNSVERSIONMASK; + version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT; + } + + /* Request NSID/COOKIE/VERSION for current peer? */ + if (peer != NULL) { + uint8_t ednsversion; + (void)dns_peer_getrequestnsid(peer, &reqnsid); + (void)dns_peer_getsendcookie(peer, &sendcookie); + result = dns_peer_getednsversion(peer, + &ednsversion); + if (result == ISC_R_SUCCESS && + ednsversion < version) + { + version = ednsversion; + } + } + if (NOCOOKIE(query->addrinfo)) { + sendcookie = false; + } + if (reqnsid) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_NSID; + ednsopts[ednsopt].length = 0; + ednsopts[ednsopt].value = NULL; + ednsopt++; + } + if (sendcookie) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_COOKIE; + ednsopts[ednsopt].length = + (uint16_t)dns_adb_getcookie( + fctx->adb, query->addrinfo, + cookie, sizeof(cookie)); + if (ednsopts[ednsopt].length != 0) { + ednsopts[ednsopt].value = cookie; + inc_stats( + fctx->res, + dns_resstatscounter_cookieout); + } else { + compute_cc(query, cookie, + CLIENT_COOKIE_SIZE); + ednsopts[ednsopt].value = cookie; + ednsopts[ednsopt].length = + CLIENT_COOKIE_SIZE; + inc_stats( + fctx->res, + dns_resstatscounter_cookienew); + } + ednsopt++; + } + + /* Add TCP keepalive option if appropriate */ + if ((peer != NULL) && tcp) { + (void)dns_peer_gettcpkeepalive(peer, + &tcpkeepalive); + } + if (tcpkeepalive) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_TCP_KEEPALIVE; + ednsopts[ednsopt].length = 0; + ednsopts[ednsopt].value = NULL; + ednsopt++; + } + + /* Add PAD for current peer? Require TCP for now */ + if ((peer != NULL) && tcp) { + (void)dns_peer_getpadding(peer, &padding); + } + if (padding != 0) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_PAD; + ednsopts[ednsopt].length = 0; + ednsopt++; + dns_message_setpadding(fctx->qmessage, padding); + } + + query->ednsversion = version; + result = fctx_addopt(fctx->qmessage, version, udpsize, + ednsopts, ednsopt); + if (reqnsid && result == ISC_R_SUCCESS) { + query->options |= DNS_FETCHOPT_WANTNSID; + } else if (result != ISC_R_SUCCESS) { + /* + * We couldn't add the OPT, but we'll press on. + * We're not using EDNS0, so set the NOEDNS0 + * bit. + */ + query->options |= DNS_FETCHOPT_NOEDNS0; + query->ednsversion = -1; + udpsize = 0; + } + } else { + /* + * We know this server doesn't like EDNS0, so we + * won't use it. Set the NOEDNS0 bit since we're + * not using EDNS0. + */ + query->options |= DNS_FETCHOPT_NOEDNS0; + query->ednsversion = -1; + } + } else { + query->ednsversion = -1; + } + + /* + * Record the UDP EDNS size chosen. + */ + query->udpsize = udpsize; + + /* + * If we need EDNS0 to do this query and aren't using it, we lose. + */ + if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) { + result = DNS_R_SERVFAIL; + goto cleanup_message; + } + + if (udpsize > 512U) { + add_triededns(fctx, &query->addrinfo->sockaddr); + } + + if (udpsize == 512U) { + add_triededns512(fctx, &query->addrinfo->sockaddr); + } + + /* + * Clear CD if EDNS is not in use. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0) { + fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD; + } + + /* + * Add TSIG record tailored to the current recipient. + */ + result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto cleanup_message; + } + + if (tsigkey != NULL) { + result = dns_message_settsigkey(fctx->qmessage, tsigkey); + dns_tsigkey_detach(&tsigkey); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + } + + result = dns_message_rendersection(fctx->qmessage, + DNS_SECTION_ADDITIONAL, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + result = dns_message_renderend(fctx->qmessage); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + +#ifdef HAVE_DNSTAP + memset(&zr, 0, sizeof(zr)); + isc_buffer_init(&zb, zone, sizeof(zone)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(&fctx->domain, &cctx, &zb); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(&zb, &zr); + } +#endif /* HAVE_DNSTAP */ + + dns_compress_invalidate(&cctx); + cleanup_cctx = false; + + if (dns_message_gettsigkey(fctx->qmessage) != NULL) { + dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage), + &query->tsigkey); + result = dns_message_getquerytsig( + fctx->qmessage, fctx->res->mctx, &query->tsig); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + } + + /* + * If using TCP, write the length of the message at the beginning + * of the buffer. + */ + if (tcp) { + isc_buffer_usedregion(&query->buffer, &r); + isc_buffer_putuint16(&tcpbuffer, (uint16_t)r.length); + isc_buffer_add(&tcpbuffer, r.length); + } + + /* + * Log the outgoing packet. + */ + dns_message_logfmtpacket( + fctx->qmessage, "sending packet to", &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_PACKETS, + &dns_master_style_comment, ISC_LOG_DEBUG(11), fctx->res->mctx); + + /* + * We're now done with the query message. + */ + dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); + + sock = query2sock(query); + + /* + * Send the query! + */ + if (!tcp) { + address = &query->addrinfo->sockaddr; + if (query->exclusivesocket) { + result = isc_socket_connect(sock, address, task, + resquery_udpconnected, + query); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + query->connects++; + } + } + isc_buffer_usedregion(buffer, &r); + + /* + * XXXRTH Make sure we don't send to ourselves! We should probably + * prune out these addresses when we get them from the ADB. + */ + memset(&query->sendevent, 0, sizeof(query->sendevent)); + ISC_EVENT_INIT(&query->sendevent, sizeof(query->sendevent), 0, NULL, + ISC_SOCKEVENT_SENDDONE, resquery_senddone, query, NULL, + NULL, NULL); + + if (query->dscp == -1) { + query->sendevent.attributes &= ~ISC_SOCKEVENTATTR_DSCP; + query->sendevent.dscp = 0; + } else { + query->sendevent.attributes |= ISC_SOCKEVENTATTR_DSCP; + query->sendevent.dscp = query->dscp; + if (tcp) { + isc_socket_dscp(sock, query->dscp); + } + } + + result = isc_socket_sendto2(sock, &r, task, address, NULL, + &query->sendevent, 0); + INSIST(result == ISC_R_SUCCESS); + + query->sends++; + + QTRACE("sent"); + +#ifdef HAVE_DNSTAP + /* + * Log the outgoing query via dnstap. + */ + if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + dtmsgtype = DNS_DTTYPE_FQ; + } else { + dtmsgtype = DNS_DTTYPE_RQ; + } + + result = isc_socket_getsockname(sock, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr, + tcp, &zr, &query->start, NULL, &query->buffer); +#endif /* HAVE_DNSTAP */ + + return (ISC_R_SUCCESS); + +cleanup_message: + if (cleanup_cctx) { + dns_compress_invalidate(&cctx); + } + + dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); + + /* + * Stop the dispatcher from listening. + */ + dns_dispatch_removeresponse(&query->dispentry, NULL); + +cleanup_temps: + if (qname != NULL) { + dns_message_puttempname(fctx->qmessage, &qname); + } + if (qrdataset != NULL) { + dns_message_puttemprdataset(fctx->qmessage, &qrdataset); + } + + return (result); +} + +static void +resquery_connected(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + resquery_t *query = event->ev_arg; + bool retry = false; + isc_interval_t interval; + isc_result_t result; + unsigned int attrs; + fetchctx_t *fctx; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); + REQUIRE(VALID_QUERY(query)); + + QTRACE("connected"); + + UNUSED(task); + + /* + * XXXRTH + * + * Currently we don't wait for the connect event before retrying + * a query. This means that if we get really behind, we may end + * up doing extra work! + */ + + query->connects--; + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + /* + * This query was canceled while the connect() was in + * progress. + */ + isc_socket_detach(&query->tcpsocket); + resquery_destroy(&query); + } else { + switch (sevent->result) { + case ISC_R_SUCCESS: + + /* + * Extend the idle timer for TCP. Half of + * "resolver-query-timeout" will hopefully be long + * enough for a TCP connection to be established, a + * single DNS request to be sent, and the response + * received. + */ + isc_interval_set(&interval, + fctx->res->query_timeout / 1000 / 2, + 0); + result = fctx_startidletimer(query->fctx, &interval); + if (result != ISC_R_SUCCESS) { + FCTXTRACE("query canceled: idle timer failed; " + "responding"); + + fctx_cancelquery(&query, NULL, NULL, false, + false); + fctx_done(fctx, result, __LINE__); + break; + } + /* + * We are connected. Create a dispatcher and + * send the query. + */ + attrs = 0; + attrs |= DNS_DISPATCHATTR_TCP; + attrs |= DNS_DISPATCHATTR_PRIVATE; + attrs |= DNS_DISPATCHATTR_CONNECTED; + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == + AF_INET) + { + attrs |= DNS_DISPATCHATTR_IPV4; + } else { + attrs |= DNS_DISPATCHATTR_IPV6; + } + attrs |= DNS_DISPATCHATTR_MAKEQUERY; + + result = dns_dispatch_createtcp( + query->dispatchmgr, query->tcpsocket, + query->fctx->res->taskmgr, NULL, NULL, 4096, 2, + 1, 1, 3, attrs, &query->dispatch); + + /* + * Regardless of whether dns_dispatch_create() + * succeeded or not, we don't need our reference + * to the socket anymore. + */ + isc_socket_detach(&query->tcpsocket); + + if (result == ISC_R_SUCCESS) { + result = resquery_send(query); + } + + if (result != ISC_R_SUCCESS) { + FCTXTRACE("query canceled: " + "resquery_send() failed; responding"); + + fctx_cancelquery(&query, NULL, NULL, false, + false); + fctx_done(fctx, result, __LINE__); + } + break; + + case ISC_R_NETUNREACH: + case ISC_R_HOSTUNREACH: + case ISC_R_CONNREFUSED: + case ISC_R_NOPERM: + case ISC_R_ADDRNOTAVAIL: + case ISC_R_CONNECTIONRESET: + FCTXTRACE3("query canceled in connected(): " + "no route to host; no response", + sevent->result); + + /* + * No route to remote. + */ + isc_socket_detach(&query->tcpsocket); + /* + * Do not query this server again in this fetch context + * if we already tried reducing the advertised EDNS UDP + * payload size to 512 bytes and the server is + * unavailable over TCP. This prevents query loops + * lasting until the fetch context restart limit is + * reached when attempting to get answers whose size + * exceeds 512 bytes from broken servers. + */ + if ((query->options & DNS_FETCHOPT_EDNS512) != 0) { + add_bad(fctx, query->rmessage, query->addrinfo, + sevent->result, badns_unreachable); + } + fctx_cancelquery(&query, NULL, NULL, true, false); + retry = true; + break; + + default: + FCTXTRACE3("query canceled in connected() due to " + "unexpected event result; responding", + sevent->result); + + isc_socket_detach(&query->tcpsocket); + fctx_cancelquery(&query, NULL, NULL, false, false); + break; + } + } + + isc_event_free(&event); + + if (retry) { + /* + * Behave as if the idle timer has expired. For TCP + * connections this may not actually reflect the latest timer. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, true, false); + } + } +} + +static void +fctx_finddone(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx; + dns_adbfind_t *find; + dns_resolver_t *res; + bool want_try = false; + bool want_done = false; + bool bucket_empty = false; + unsigned int bucketnum; + bool dodestroy = false; + + find = event->ev_sender; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + + FCTXTRACE("finddone"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + INSIST(fctx->pending > 0); + fctx->pending--; + + if (ADDRWAIT(fctx)) { + /* + * The fetch is waiting for a name to be found. + */ + INSIST(!SHUTTINGDOWN(fctx)); + if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) { + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + want_try = true; + } else { + fctx->findfail++; + if (fctx->pending == 0) { + /* + * We've got nothing else to wait for and don't + * know the answer. There's nothing to do but + * fail the fctx. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + want_done = true; + } + } + } else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 && + fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) + { + if (isc_refcount_current(&fctx->references) == 0) { + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + } + UNLOCK(&res->buckets[bucketnum].lock); + + isc_event_free(&event); + dns_adb_destroyfind(&find); + + if (want_try) { + fctx_try(fctx, true, false); + } else if (want_done) { + FCTXTRACE("fetch failed in finddone(); return ISC_R_FAILURE"); + fctx_done(fctx, ISC_R_FAILURE, __LINE__); + } else if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +static bool +bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + + for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + if (isc_sockaddr_equal(sa, address)) { + return (true); + } + } + + return (false); +} + +static bool +mark_bad(fetchctx_t *fctx) { + dns_adbfind_t *curr; + dns_adbaddrinfo_t *addrinfo; + bool all_bad = true; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return (false); + } +#endif /* ifdef ENABLE_AFL */ + + /* + * Mark all known bad servers, so we don't try to talk to them + * again. + */ + + /* + * Mark any bad nameservers. + */ + for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + } + + /* + * Mark any bad forwarders. + */ + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + + /* + * Mark any bad alternates. + */ + for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + } + + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + + return (all_bad); +} + +static void +add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo, + isc_result_t reason, badnstype_t badtype) { + char namebuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + char code[64]; + isc_buffer_t b; + isc_sockaddr_t *sa; + const char *spc = ""; + isc_sockaddr_t *address = &addrinfo->sockaddr; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return; + } +#endif /* ifdef ENABLE_AFL */ + + if (reason == DNS_R_LAME) { + fctx->lamecount++; + } else { + switch (badtype) { + case badns_unreachable: + fctx->neterr++; + break; + case badns_response: + fctx->badresp++; + break; + case badns_validation: + break; /* counted as 'valfail' */ + case badns_forwarder: + /* + * We were called to prevent the given forwarder from + * being used again for this fetch context. + */ + break; + } + } + + if (bad_server(fctx, address)) { + /* + * We already know this server is bad. + */ + return; + } + + FCTXTRACE("add_bad"); + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + *sa = *address; + ISC_LIST_INITANDAPPEND(fctx->bad, sa, link); + + if (reason == DNS_R_LAME) { /* already logged */ + return; + } + + if (reason == DNS_R_UNEXPECTEDRCODE && + rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo)) + { + return; + } + + if (reason == DNS_R_UNEXPECTEDRCODE) { + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(rmessage->rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + spc = " "; + } else if (reason == DNS_R_UNEXPECTEDOPCODE) { + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_opcode_totext((dns_opcode_t)rmessage->opcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + spc = " "; + } else { + code[0] = '\0'; + } + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); + isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER, + ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code, spc, + dns_result_totext(reason), namebuf, typebuf, classbuf, addrbuf); +} + +/* + * Sort addrinfo list by RTT. + */ +static void +sort_adbfind(dns_adbfind_t *find, unsigned int bias) { + dns_adbaddrinfo_t *best, *curr; + dns_adbaddrinfolist_t sorted; + unsigned int best_srtt, curr_srtt; + + /* Lame N^2 bubble sort. */ + ISC_LIST_INIT(sorted); + while (!ISC_LIST_EMPTY(find->list)) { + best = ISC_LIST_HEAD(find->list); + best_srtt = best->srtt; + if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) { + best_srtt += bias; + } + curr = ISC_LIST_NEXT(best, publink); + while (curr != NULL) { + curr_srtt = curr->srtt; + if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) { + curr_srtt += bias; + } + if (curr_srtt < best_srtt) { + best = curr; + best_srtt = curr_srtt; + } + curr = ISC_LIST_NEXT(curr, publink); + } + ISC_LIST_UNLINK(find->list, best, publink); + ISC_LIST_APPEND(sorted, best, publink); + } + find->list = sorted; +} + +/* + * Sort a list of finds by server RTT. + */ +static void +sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { + dns_adbfind_t *best, *curr; + dns_adbfindlist_t sorted; + dns_adbaddrinfo_t *addrinfo, *bestaddrinfo; + unsigned int best_srtt, curr_srtt; + + /* Sort each find's addrinfo list by SRTT. */ + for (curr = ISC_LIST_HEAD(*findlist); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + sort_adbfind(curr, bias); + } + + /* Lame N^2 bubble sort. */ + ISC_LIST_INIT(sorted); + while (!ISC_LIST_EMPTY(*findlist)) { + best = ISC_LIST_HEAD(*findlist); + bestaddrinfo = ISC_LIST_HEAD(best->list); + INSIST(bestaddrinfo != NULL); + best_srtt = bestaddrinfo->srtt; + if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) { + best_srtt += bias; + } + curr = ISC_LIST_NEXT(best, publink); + while (curr != NULL) { + addrinfo = ISC_LIST_HEAD(curr->list); + INSIST(addrinfo != NULL); + curr_srtt = addrinfo->srtt; + if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) { + curr_srtt += bias; + } + if (curr_srtt < best_srtt) { + best = curr; + best_srtt = curr_srtt; + } + curr = ISC_LIST_NEXT(curr, publink); + } + ISC_LIST_UNLINK(*findlist, best, publink); + ISC_LIST_APPEND(sorted, best, publink); + } + *findlist = sorted; +} + +static void +findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, + unsigned int options, unsigned int flags, isc_stdtime_t now, + bool *overquota, bool *need_alternate, unsigned int *no_addresses) { + dns_adbaddrinfo_t *ai; + dns_adbfind_t *find; + dns_resolver_t *res; + bool unshared; + isc_result_t result; + + FCTXTRACE("FINDNAME"); + res = fctx->res; + unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0); + /* + * If this name is a subdomain of the query domain, tell + * the ADB to start looking using zone/hint data. This keeps us + * from getting stuck if the nameserver is beneath the zone cut + * and we don't know its address (e.g. because the A record has + * expired). + */ + if (dns_name_issubdomain(name, &fctx->domain)) { + options |= DNS_ADBFIND_STARTATZONE; + } + options |= DNS_ADBFIND_GLUEOK; + options |= DNS_ADBFIND_HINTOK; + + /* + * See what we know about this address. + */ + find = NULL; + result = dns_adb_createfind( + fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone, + fctx, name, &fctx->name, fctx->type, options, now, NULL, + res->view->dstport, fctx->depth + 1, fctx->qc, &find); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "fctx %p(%s): createfind for %s/%d - %s", fctx, + fctx->info, fctx->clientstr, fctx->id, + isc_result_totext(result)); + + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_ALIAS) { + char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * XXXRTH Follow the CNAME/DNAME chain? + */ + dns_adb_destroyfind(&find); + fctx->adberr++; + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "skipping nameserver '%s' because it " + "is a CNAME, while resolving '%s'", + namebuf, fctx->info); + } + } else if (!ISC_LIST_EMPTY(find->list)) { + /* + * We have at least some of the addresses for the + * name. + */ + INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0); + if (flags != 0 || port != 0) { + for (ai = ISC_LIST_HEAD(find->list); ai != NULL; + ai = ISC_LIST_NEXT(ai, publink)) + { + ai->flags |= flags; + if (port != 0) { + isc_sockaddr_setport(&ai->sockaddr, + port); + } + } + } + if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) { + ISC_LIST_APPEND(fctx->altfinds, find, publink); + } else { + ISC_LIST_APPEND(fctx->finds, find, publink); + } + } else { + /* + * We don't know any of the addresses for this + * name. + */ + if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { + /* + * We're looking for them and will get an + * event about it later. + */ + fctx->pending++; + /* + * Bootstrap. + */ + if (need_alternate != NULL && !*need_alternate && + unshared && + ((res->dispatches4 == NULL && + find->result_v6 != DNS_R_NXDOMAIN) || + (res->dispatches6 == NULL && + find->result_v4 != DNS_R_NXDOMAIN))) + { + *need_alternate = true; + } + if (no_addresses != NULL) { + (*no_addresses)++; + } + } else { + if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) { + if (overquota != NULL) { + *overquota = true; + } + fctx->quotacount++; /* quota exceeded */ + } else if ((find->options & DNS_ADBFIND_LAMEPRUNED) != + 0) + { + fctx->lamecount++; /* cached lame server */ + } else { + fctx->adberr++; /* unreachable server, etc. */ + } + + /* + * If we know there are no addresses for + * the family we are using then try to add + * an alternative server. + */ + if (need_alternate != NULL && !*need_alternate && + ((res->dispatches4 == NULL && + find->result_v6 == DNS_R_NXRRSET) || + (res->dispatches6 == NULL && + find->result_v4 == DNS_R_NXRRSET))) + { + *need_alternate = true; + } + dns_adb_destroyfind(&find); + } + } +} + +static bool +isstrictsubdomain(const dns_name_t *name1, const dns_name_t *name2) { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + + namereln = dns_name_fullcompare(name1, name2, &order, &nlabels); + return (namereln == dns_namereln_subdomain); +} + +static isc_result_t +fctx_getaddresses(fetchctx_t *fctx, bool badcache) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_resolver_t *res; + isc_stdtime_t now; + unsigned int stdoptions = 0; + dns_forwarder_t *fwd; + dns_adbaddrinfo_t *ai; + bool all_bad; + dns_rdata_ns_t ns; + bool need_alternate = false; + bool all_spilled = true; + unsigned int no_addresses = 0; + unsigned int ns_processed = 0; + + FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth); + + /* + * Don't pound on remote servers. (Failsafe!) + */ + fctx->restarts++; + if (fctx->restarts > 100) { + FCTXTRACE("too many restarts"); + return (DNS_R_SERVFAIL); + } + + res = fctx->res; + + if (fctx->depth > res->maxdepth) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "too much NS indirection resolving '%s' " + "(depth=%u, maxdepth=%u)", + fctx->info, fctx->depth, res->maxdepth); + return (DNS_R_SERVFAIL); + } + + /* + * Forwarders. + */ + + INSIST(ISC_LIST_EMPTY(fctx->forwaddrs)); + INSIST(ISC_LIST_EMPTY(fctx->altaddrs)); + + /* + * If we have DNS_FETCHOPT_NOFORWARD set and forwarding policy + * allows us to not forward - skip forwarders and go straight + * to NSes. This is currently used to make sure that priming query + * gets root servers' IP addresses in ADDITIONAL section. + */ + if ((fctx->options & DNS_FETCHOPT_NOFORWARD) != 0 && + (fctx->fwdpolicy != dns_fwdpolicy_only)) + { + goto normal_nses; + } + + /* + * If this fctx has forwarders, use them; otherwise use any + * selective forwarders specified in the view; otherwise use the + * resolver's forwarders (if any). + */ + fwd = ISC_LIST_HEAD(fctx->forwarders); + if (fwd == NULL) { + dns_forwarders_t *forwarders = NULL; + dns_name_t *name = &fctx->name; + dns_name_t suffix; + unsigned int labels; + dns_fixedname_t fixed; + dns_name_t *domain; + + /* + * DS records are found in the parent server. + * Strip label to get the correct forwarder (if any). + */ + if (dns_rdatatype_atparent(fctx->type) && + dns_name_countlabels(name) > 1) + { + dns_name_init(&suffix, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } + + domain = dns_fixedname_initname(&fixed); + result = dns_fwdtable_find(res->view->fwdtable, name, domain, + &forwarders); + if (result == ISC_R_SUCCESS) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copynf(domain, fctx->fwdname); + if (fctx->fwdpolicy == dns_fwdpolicy_only && + isstrictsubdomain(domain, &fctx->domain)) + { + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(domain, fctx->mctx, &fctx->domain); + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } + } + + while (fwd != NULL) { + if ((isc_sockaddr_pf(&fwd->addr) == AF_INET && + res->dispatches4 == NULL) || + (isc_sockaddr_pf(&fwd->addr) == AF_INET6 && + res->dispatches6 == NULL)) + { + fwd = ISC_LIST_NEXT(fwd, link); + continue; + } + ai = NULL; + result = dns_adb_findaddrinfo(fctx->adb, &fwd->addr, &ai, 0); + if (result == ISC_R_SUCCESS) { + dns_adbaddrinfo_t *cur; + ai->flags |= FCTX_ADDRINFO_FORWARDER; + ai->dscp = fwd->dscp; + cur = ISC_LIST_HEAD(fctx->forwaddrs); + while (cur != NULL && cur->srtt < ai->srtt) { + cur = ISC_LIST_NEXT(cur, publink); + } + if (cur != NULL) { + ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur, ai, + publink); + } else { + ISC_LIST_APPEND(fctx->forwaddrs, ai, publink); + } + } + fwd = ISC_LIST_NEXT(fwd, link); + } + + /* + * If the forwarding policy is "only", we don't need the addresses + * of the nameservers. + */ + if (fctx->fwdpolicy == dns_fwdpolicy_only) { + goto out; + } + + /* + * Normal nameservers. + */ +normal_nses: + stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT; + if (fctx->restarts == 1) { + /* + * To avoid sending out a flood of queries likely to + * result in NXRRSET, we suppress fetches for address + * families we don't have the first time through, + * provided that we have addresses in some family we + * can use. + * + * We don't want to set this option all the time, since + * if fctx->restarts > 1, we've clearly been having trouble + * with the addresses we had, so getting more could help. + */ + stdoptions |= DNS_ADBFIND_AVOIDFETCHES; + } + if (res->dispatches4 != NULL) { + stdoptions |= DNS_ADBFIND_INET; + } + if (res->dispatches6 != NULL) { + stdoptions |= DNS_ADBFIND_INET6; + } + + if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0) { + return (DNS_R_SERVFAIL); + } + + isc_stdtime_get(&now); + + INSIST(ISC_LIST_EMPTY(fctx->finds)); + INSIST(ISC_LIST_EMPTY(fctx->altfinds)); + + for (result = dns_rdataset_first(&fctx->nameservers); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&fctx->nameservers)) + { + bool overquota = false; + + dns_rdataset_current(&fctx->nameservers, &rdata); + /* + * Extract the name from the NS record. + */ + result = dns_rdata_tostruct(&rdata, &ns, NULL); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (no_addresses > NS_FAIL_LIMIT && + dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT) + { + stdoptions |= DNS_ADBFIND_NOFETCH; + } + findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota, + &need_alternate, &no_addresses); + + if (!overquota) { + all_spilled = false; + } + + dns_rdata_reset(&rdata); + dns_rdata_freestruct(&ns); + + if (++ns_processed >= NS_PROCESSING_LIMIT) { + result = ISC_R_NOMORE; + break; + } + } + if (result != ISC_R_NOMORE) { + return (result); + } + + /* + * Do we need to use 6 to 4? + */ + if (need_alternate) { + int family; + alternate_t *a; + family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET; + for (a = ISC_LIST_HEAD(res->alternates); a != NULL; + a = ISC_LIST_NEXT(a, link)) + { + if (!a->isaddress) { + findname(fctx, &a->_u._n.name, a->_u._n.port, + stdoptions, FCTX_ADDRINFO_DUALSTACK, + now, NULL, NULL, NULL); + continue; + } + if (isc_sockaddr_pf(&a->_u.addr) != family) { + continue; + } + ai = NULL; + result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr, + &ai, 0); + if (result == ISC_R_SUCCESS) { + dns_adbaddrinfo_t *cur; + ai->flags |= FCTX_ADDRINFO_FORWARDER; + ai->flags |= FCTX_ADDRINFO_DUALSTACK; + cur = ISC_LIST_HEAD(fctx->altaddrs); + while (cur != NULL && cur->srtt < ai->srtt) { + cur = ISC_LIST_NEXT(cur, publink); + } + if (cur != NULL) { + ISC_LIST_INSERTBEFORE(fctx->altaddrs, + cur, ai, publink); + } else { + ISC_LIST_APPEND(fctx->altaddrs, ai, + publink); + } + } + } + } + +out: + /* + * Mark all known bad servers. + */ + all_bad = mark_bad(fctx); + + /* + * How are we doing? + */ + if (all_bad) { + /* + * We've got no addresses. + */ + if (fctx->pending > 0) { + /* + * We're fetching the addresses, but don't have any + * yet. Tell the caller to wait for an answer. + */ + result = DNS_R_WAIT; + } else { + isc_time_t expire; + isc_interval_t i; + /* + * We've lost completely. We don't know any + * addresses, and the ADB has told us it can't get + * them. + */ + FCTXTRACE("no addresses"); + isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0); + result = isc_time_nowplusinterval(&expire, &i); + if (badcache && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_ds) && + result == ISC_R_SUCCESS) + { + dns_resolver_addbadcache(res, &fctx->name, + fctx->type, &expire); + } + + result = ISC_R_FAILURE; + + /* + * If all of the addresses found were over the + * fetches-per-server quota, return the configured + * response. + */ + if (all_spilled) { + result = res->quotaresp[dns_quotatype_server]; + inc_stats(res, dns_resstatscounter_serverquota); + } + } + } else { + /* + * We've found some addresses. We might still be looking + * for more addresses. + */ + sort_finds(&fctx->finds, res->view->v6bias); + sort_finds(&fctx->altfinds, 0); + result = ISC_R_SUCCESS; + } + + return (result); +} + +static void +possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) { + isc_netaddr_t na; + char buf[ISC_NETADDR_FORMATSIZE]; + isc_sockaddr_t *sa; + bool aborted = false; + bool bogus; + dns_acl_t *blackhole; + isc_netaddr_t ipaddr; + dns_peer_t *peer = NULL; + dns_resolver_t *res; + const char *msg = NULL; + + sa = &addr->sockaddr; + + res = fctx->res; + isc_netaddr_fromsockaddr(&ipaddr, sa); + blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr); + (void)dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer); + + if (blackhole != NULL) { + int match; + + if ((dns_acl_match(&ipaddr, NULL, blackhole, &res->view->aclenv, + &match, NULL) == ISC_R_SUCCESS) && + match > 0) + { + aborted = true; + } + } + + if (peer != NULL && dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS && + bogus) + { + aborted = true; + } + + if (aborted) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring blackholed / bogus server: "; + } else if (isc_sockaddr_isnetzero(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring net zero address: "; + } else if (isc_sockaddr_ismulticast(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring multicast address: "; + } else if (isc_sockaddr_isexperimental(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring experimental address: "; + } else if (sa->type.sa.sa_family != AF_INET6) { + return; + } else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring IPv6 mapped IPV4 address: "; + } else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring IPv6 compatibility IPV4 address: "; + } else { + return; + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + isc_netaddr_fromsockaddr(&na, sa); + isc_netaddr_format(&na, buf, sizeof(buf)); + FCTXTRACE2(msg, buf); + } +} + +static dns_adbaddrinfo_t * +fctx_nextaddress(fetchctx_t *fctx) { + dns_adbfind_t *find, *start; + dns_adbaddrinfo_t *addrinfo; + dns_adbaddrinfo_t *faddrinfo; + + /* + * Return the next untried address, if any. + */ + + /* + * Find the first unmarked forwarder (if any). + */ + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + fctx->find = NULL; + fctx->forwarding = true; + + /* + * QNAME minimization is disabled when + * forwarding, and has to remain disabled if + * we switch back to normal recursion; otherwise + * forwarding could leave us in an inconsistent + * state. + */ + fctx->minimized = false; + return (addrinfo); + } + } + + /* + * No forwarders. Move to the next find. + */ + fctx->forwarding = false; + FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND); + + find = fctx->find; + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } else { + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } + } + + /* + * Find the first unmarked addrinfo. + */ + addrinfo = NULL; + if (find != NULL) { + start = find; + do { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + if (addrinfo != NULL) { + break; + } + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } + } while (find != start); + } + + fctx->find = find; + if (addrinfo != NULL) { + return (addrinfo); + } + + /* + * No nameservers left. Try alternates. + */ + + FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDALT); + + find = fctx->altfind; + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } else { + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } + } + + /* + * Find the first unmarked addrinfo. + */ + addrinfo = NULL; + if (find != NULL) { + start = find; + do { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + if (addrinfo != NULL) { + break; + } + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } + } while (find != start); + } + + faddrinfo = addrinfo; + + /* + * See if we have a better alternate server by address. + */ + + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo) && + (faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt)) + { + if (faddrinfo != NULL) { + faddrinfo->flags &= ~FCTX_ADDRINFO_MARK; + } + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + + if (addrinfo == NULL) { + addrinfo = faddrinfo; + fctx->altfind = find; + } + + return (addrinfo); +} + +static void +fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) { + isc_result_t result; + dns_adbaddrinfo_t *addrinfo = NULL; + dns_resolver_t *res; + isc_task_t *task; + unsigned int bucketnum; + bool bucket_empty; + + FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc)); + + REQUIRE(!ADDRWAIT(fctx)); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + /* We've already exceeded maximum query count */ + if (isc_counter_used(fctx->qc) > res->maxqueries) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded max queries resolving '%s' " + "(querycount=%u, maxqueries=%u)", + fctx->info, isc_counter_used(fctx->qc), + res->maxqueries); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + addrinfo = fctx_nextaddress(fctx); + + /* Try to find an address that isn't over quota */ + while (addrinfo != NULL && dns_adbentry_overquota(addrinfo->entry)) { + addrinfo = fctx_nextaddress(fctx); + } + + if (addrinfo == NULL) { + /* We have no more addresses. Start over. */ + fctx_cancelqueries(fctx, true, false); + fctx_cleanupall(fctx); + result = fctx_getaddresses(fctx, badcache); + if (result == DNS_R_WAIT) { + /* + * Sleep waiting for addresses. + */ + FCTXTRACE("addrwait"); + FCTX_ATTR_SET(fctx, FCTX_ATTR_ADDRWAIT); + return; + } else if (result != ISC_R_SUCCESS) { + /* + * Something bad happened. + */ + fctx_done(fctx, result, __LINE__); + return; + } + + addrinfo = fctx_nextaddress(fctx); + + while (addrinfo != NULL && + dns_adbentry_overquota(addrinfo->entry)) + { + addrinfo = fctx_nextaddress(fctx); + } + + /* + * While we may have addresses from the ADB, they + * might be bad ones. In this case, return SERVFAIL. + */ + if (addrinfo == NULL) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + } + /* + * We're minimizing and we're not yet at the final NS - + * we need to launch a query for NS for 'upper' domain + */ + if (fctx->minimized && !fctx->forwarding) { + unsigned int options = fctx->options; + /* + * Also clear DNS_FETCHOPT_TRYSTALE_ONTIMEOUT here, otherwise + * every query minimization step will activate the try-stale + * timer again. + */ + options &= ~(DNS_FETCHOPT_QMINIMIZE | + DNS_FETCHOPT_TRYSTALE_ONTIMEOUT); + + /* + * Is another QNAME minimization fetch still running? + */ + if (fctx->qminfetch != NULL) { + bool validfctx = (DNS_FETCH_VALID(fctx->qminfetch) && + VALID_FCTX(fctx->qminfetch->private)); + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(&fctx->qminname, namebuf, + sizeof(namebuf)); + dns_rdatatype_format(fctx->qmintype, typebuf, + sizeof(typebuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, + "fctx %p(%s): attempting QNAME " + "minimization fetch for %s/%s but " + "fetch %p(%s) still running", + fctx, fctx->info, namebuf, typebuf, + fctx->qminfetch, + validfctx ? fctx->qminfetch->private->info + : "<invalid>"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + /* + * In "_ A" mode we're asking for _.domain - + * resolver by default will follow delegations + * then, we don't want that. + */ + if ((options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + options |= DNS_FETCHOPT_NOFOLLOW; + } + fctx_increference(fctx); + task = res->buckets[bucketnum].task; + fctx_stoptimer(fctx); + fctx_stoptimer_trystale(fctx); + result = dns_resolver_createfetch( + fctx->res, &fctx->qminname, fctx->qmintype, + &fctx->domain, &fctx->nameservers, NULL, NULL, 0, + options, 0, fctx->qc, task, resume_qmin, fctx, + &fctx->qminrrset, NULL, &fctx->qminfetch); + if (result != ISC_R_SUCCESS) { + LOCK(&res->buckets[bucketnum].lock); + RUNTIME_CHECK(!fctx_decreference(fctx)); + UNLOCK(&res->buckets[bucketnum].lock); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + } + return; + } + + result = isc_counter_increment(fctx->qc); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded max queries resolving '%s'", + fctx->info); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + fctx_increference(fctx); + + result = fctx_query(fctx, addrinfo, fctx->options); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } + } else if (retrying) { + inc_stats(res, dns_resstatscounter_retry); + } +} + +static void +resume_qmin(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *fevent; + dns_resolver_t *res; + fetchctx_t *fctx; + isc_result_t result; + bool bucket_empty; + unsigned int bucketnum; + unsigned int findoptions = 0; + dns_name_t *fname, *dcname; + dns_fixedname_t ffixed, dcfixed; + fname = dns_fixedname_initname(&ffixed); + dcname = dns_fixedname_initname(&dcfixed); + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + FCTXTRACE("resume_qmin"); + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + + bucketnum = fctx->bucketnum; + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + result = fevent->result; + fevent = NULL; + isc_event_free(&event); + + dns_resolver_destroyfetch(&fctx->qminfetch); + + LOCK(&res->buckets[bucketnum].lock); + if (SHUTTINGDOWN(fctx)) { + UNLOCK(&res->buckets[bucketnum].lock); + goto cleanup; + } + UNLOCK(&res->buckets[bucketnum].lock); + + /* + * Note: fevent->rdataset must be disassociated and + * isc_event_free(&event) be called before resuming + * processing of the 'fctx' to prevent use-after-free. + * 'fevent' is set to NULL so as to not have a dangling + * pointer. + */ + if (result == ISC_R_CANCELED) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + /* + * If we're doing "_ A"-style minimization we can get + * NX answer to minimized query - we need to continue then. + * + * Otherwise - either disable minimization if we're + * in relaxed mode or fail if we're in strict mode. + */ + + if ((NXDOMAIN_RESULT(result) && + (fctx->options & DNS_FETCHOPT_QMIN_USE_A) == 0) || + result == DNS_R_FORMERR || result == DNS_R_REMOTEFORMERR || + result == ISC_R_FAILURE) + { + if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) == 0) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; + /* + * We store the result. If we succeed in the end + * we'll issue a warning that the server is broken. + */ + fctx->qmin_warning = result; + } else { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + } + + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + result = dns_view_findzonecut(res->view, &fctx->name, fname, dcname, + fctx->now, findoptions, true, true, + &fctx->nameservers, NULL); + + /* + * DNS_R_NXDOMAIN here means we have not loaded the root zone mirror + * yet - but DNS_R_NXDOMAIN is not a valid return value when doing + * recursion, we need to patch it. + */ + if (result == DNS_R_NXDOMAIN) { + result = DNS_R_SERVFAIL; + } + + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(fname, fctx->mctx, &fctx->domain); + + result = fcount_incr(fctx, false); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + if (!fctx->minimized) { + /* + * We have finished minimizing, but fctx->finds was filled at + * the beginning of the run - now we need to clear it before + * sending the final query to use proper nameservers. + */ + fctx_cancelqueries(fctx, false, false); + fctx_cleanupall(fctx); + } + + fctx_try(fctx, true, false); + +cleanup: + INSIST(event == NULL); + INSIST(fevent == NULL); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +static bool +fctx_unlink(fetchctx_t *fctx) { + dns_resolver_t *res; + unsigned int bucketnum; + + /* + * Caller must be holding the bucket lock. + */ + + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(fctx->state == fetchstate_done || + fctx->state == fetchstate_init); + REQUIRE(ISC_LIST_EMPTY(fctx->events)); + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + REQUIRE(ISC_LIST_EMPTY(fctx->finds)); + REQUIRE(ISC_LIST_EMPTY(fctx->altfinds)); + REQUIRE(fctx->pending == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + + FCTXTRACE("unlink"); + + isc_refcount_destroy(&fctx->references); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link); + + INSIST(atomic_fetch_sub_release(&res->nfctx, 1) > 0); + + dec_stats(res, dns_resstatscounter_nfetch); + + if (atomic_load_acquire(&res->buckets[bucketnum].exiting) && + ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs)) + { + return (true); + } + + return (false); +} + +static void +fctx_destroy(fetchctx_t *fctx) { + isc_sockaddr_t *sa, *next_sa; + struct tried *tried; + + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(fctx->state == fetchstate_done || + fctx->state == fetchstate_init); + REQUIRE(ISC_LIST_EMPTY(fctx->events)); + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + REQUIRE(ISC_LIST_EMPTY(fctx->finds)); + REQUIRE(ISC_LIST_EMPTY(fctx->altfinds)); + REQUIRE(fctx->pending == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + REQUIRE(!ISC_LINK_LINKED(fctx, link)); + + FCTXTRACE("destroy"); + + isc_refcount_destroy(&fctx->references); + + /* + * Free bad. + */ + for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) { + next_sa = ISC_LIST_NEXT(sa, link); + ISC_LIST_UNLINK(fctx->bad, sa, link); + isc_mem_put(fctx->mctx, sa, sizeof(*sa)); + } + + for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL; + tried = ISC_LIST_HEAD(fctx->edns)) + { + ISC_LIST_UNLINK(fctx->edns, tried, link); + isc_mem_put(fctx->mctx, tried, sizeof(*tried)); + } + + for (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL; + tried = ISC_LIST_HEAD(fctx->edns512)) + { + ISC_LIST_UNLINK(fctx->edns512, tried, link); + isc_mem_put(fctx->mctx, tried, sizeof(*tried)); + } + + for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = next_sa) { + next_sa = ISC_LIST_NEXT(sa, link); + ISC_LIST_UNLINK(fctx->bad_edns, sa, link); + isc_mem_put(fctx->mctx, sa, sizeof(*sa)); + } + + isc_counter_detach(&fctx->qc); + fcount_decr(fctx); + isc_timer_destroy(&fctx->timer); + if (fctx->timer_try_stale != NULL) { + isc_timer_destroy(&fctx->timer_try_stale); + } + dns_message_detach(&fctx->qmessage); + if (dns_name_countlabels(&fctx->domain) > 0) { + dns_name_free(&fctx->domain, fctx->mctx); + } + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_name_free(&fctx->name, fctx->mctx); + dns_name_free(&fctx->qminname, fctx->mctx); + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_db_detach(&fctx->cache); + dns_adb_detach(&fctx->adb); + isc_mem_free(fctx->mctx, fctx->info); + isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx)); +} + +/* + * Fetch event handlers. + */ + +static void +fctx_timeout(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + isc_timerevent_t *tevent = (isc_timerevent_t *)event; + resquery_t *query; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + FCTXTRACE("timeout"); + + inc_stats(fctx->res, dns_resstatscounter_querytimeout); + + if (event->ev_type == ISC_TIMEREVENT_LIFE) { + fctx->reason = NULL; + fctx_done(fctx, ISC_R_TIMEDOUT, __LINE__); + } else { + isc_result_t result; + + fctx->timeouts++; + fctx->timeout = true; + + /* + * We could cancel the running queries here, or we could let + * them keep going. Since we normally use separate sockets for + * different queries, we adopt the former approach to reduce + * the number of open sockets: cancel the oldest query if it + * expired after the query had started (this is usually the + * case but is not always so, depending on the task schedule + * timing). + */ + query = ISC_LIST_HEAD(fctx->queries); + if (query != NULL && + isc_time_compare(&tevent->due, &query->start) >= 0) + { + FCTXTRACE("query timed out; no response"); + fctx_cancelquery(&query, NULL, NULL, true, false); + } + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + + /* + * Our timer has triggered. Reestablish the fctx lifetime + * timer. + */ + result = fctx_starttimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + /* Keep trying */ + fctx_try(fctx, true, false); + } + } + + isc_event_free(&event); +} + +/* + * Fetch event handlers called if stale answers are enabled + * (stale-answer-enabled) and the fetch took more than + * stale-answer-client-timeout to complete. + */ +static void +fctx_timeout_try_stale(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + dns_fetchevent_t *dns_event, *next_event; + isc_task_t *sender_task; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + FCTXTRACE("timeout_try_stale"); + + if (event->ev_type != ISC_TIMEREVENT_LIFE) { + return; + } + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + /* + * Trigger events of type DNS_EVENT_TRYSTALE. + */ + for (dns_event = ISC_LIST_HEAD(fctx->events); dns_event != NULL; + dns_event = next_event) + { + next_event = ISC_LIST_NEXT(dns_event, ev_link); + + if (dns_event->ev_type != DNS_EVENT_TRYSTALE) { + continue; + } + + ISC_LIST_UNLINK(fctx->events, dns_event, ev_link); + sender_task = dns_event->ev_sender; + dns_event->ev_sender = fctx; + dns_event->vresult = ISC_R_TIMEDOUT; + dns_event->result = ISC_R_TIMEDOUT; + + isc_task_sendanddetach(&sender_task, ISC_EVENT_PTR(&dns_event)); + } + + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + isc_event_free(&event); +} + +static void +fctx_shutdown(fetchctx_t *fctx) { + isc_event_t *cevent; + + /* + * Start the shutdown process for fctx, if it isn't already underway. + */ + + FCTXTRACE("shutdown"); + + /* + * The caller must be holding the appropriate bucket lock. + */ + + if (fctx->want_shutdown) { + return; + } + + fctx->want_shutdown = true; + + /* + * Unless we're still initializing (in which case the + * control event is still outstanding), we need to post + * the control event to tell the fetch we want it to + * exit. + */ + if (fctx->state != fetchstate_init) { + cevent = &fctx->control_event; + isc_task_sendto(fctx->res->buckets[fctx->bucketnum].task, + &cevent, fctx->bucketnum); + } +} + +static void +fctx_doshutdown(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + bool bucket_empty = false; + dns_resolver_t *res; + unsigned int bucketnum; + dns_validator_t *validator; + bool dodestroy = false; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + FCTXTRACE("doshutdown"); + + /* + * An fctx that is shutting down is no longer in ADDRWAIT mode. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + + /* + * Cancel all pending validators. Note that this must be done + * without the bucket lock held, since that could cause deadlock. + */ + validator = ISC_LIST_HEAD(fctx->validators); + while (validator != NULL) { + dns_validator_cancel(validator); + validator = ISC_LIST_NEXT(validator, link); + } + + if (fctx->nsfetch != NULL) { + dns_resolver_cancelfetch(fctx->nsfetch); + } + + if (fctx->qminfetch != NULL) { + dns_resolver_cancelfetch(fctx->qminfetch); + } + + /* + * Shut down anything still running on behalf of this + * fetch, and clean up finds and addresses. To avoid deadlock + * with the ADB, we must do this before we lock the bucket lock. + */ + fctx_stopqueries(fctx, false, false); + fctx_cleanupall(fctx); + + LOCK(&res->buckets[bucketnum].lock); + + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + + INSIST(fctx->state == fetchstate_active || + fctx->state == fetchstate_done); + INSIST(fctx->want_shutdown); + + if (fctx->state != fetchstate_done) { + fctx->state = fetchstate_done; + fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); + } + + if (isc_refcount_current(&fctx->references) == 0 && + fctx->pending == 0 && fctx->nqueries == 0 && + ISC_LIST_EMPTY(fctx->validators)) + { + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + + UNLOCK(&res->buckets[bucketnum].lock); + + if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +static void +fctx_start(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + bool done = false, bucket_empty = false; + dns_resolver_t *res; + unsigned int bucketnum; + bool dodestroy = false; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + FCTXTRACE("start"); + + LOCK(&res->buckets[bucketnum].lock); + + INSIST(fctx->state == fetchstate_init); + if (fctx->want_shutdown) { + /* + * We haven't started this fctx yet, and we've been requested + * to shut it down. + */ + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + fctx->state = fetchstate_done; + fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); + /* + * Since we haven't started, we INSIST that we have no + * pending ADB finds and no pending validations. + */ + INSIST(fctx->pending == 0); + INSIST(fctx->nqueries == 0); + INSIST(ISC_LIST_EMPTY(fctx->validators)); + if (isc_refcount_current(&fctx->references) == 0) { + /* + * It's now safe to destroy this fctx. + */ + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + done = true; + } else { + /* + * Normal fctx startup. + */ + fctx->state = fetchstate_active; + /* + * Reset the control event for later use in shutting down + * the fctx. + */ + ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, + DNS_EVENT_FETCHCONTROL, fctx_doshutdown, fctx, + NULL, NULL, NULL); + } + + UNLOCK(&res->buckets[bucketnum].lock); + + if (!done) { + isc_result_t result; + + INSIST(!dodestroy); + + /* + * All is well. Start working on the fetch. + */ + result = fctx_starttimer(fctx); + if (result == ISC_R_SUCCESS && fctx->timer_try_stale != NULL) { + result = fctx_starttimer_trystale(fctx); + } + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, false, false); + } + } else if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +/* + * Fetch Creation, Joining, and Cancellation. + */ + +static isc_result_t +fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client, + dns_messageid_t id, isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t *fetch) { + isc_task_t *tclone; + dns_fetchevent_t *event; + + FCTXTRACE("join"); + + /* + * We store the task we're going to send this event to in the + * sender field. We'll make the fetch the sender when we actually + * send the event. + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_fetchevent_t *)isc_event_allocate( + fctx->res->mctx, tclone, DNS_EVENT_FETCHDONE, action, arg, + sizeof(*event)); + event->result = DNS_R_SERVFAIL; + event->qtype = fctx->type; + event->db = NULL; + event->node = NULL; + event->rdataset = rdataset; + event->sigrdataset = sigrdataset; + event->fetch = fetch; + event->client = client; + event->id = id; + dns_fixedname_init(&event->foundname); + + /* + * Make sure that we can store the sigrdataset in the + * first event if it is needed by any of the events. + */ + if (event->sigrdataset != NULL) { + ISC_LIST_PREPEND(fctx->events, event, ev_link); + } else { + ISC_LIST_APPEND(fctx->events, event, ev_link); + } + + fctx_increference(fctx); + + fetch->magic = DNS_FETCH_MAGIC; + fetch->private = fctx; + + return (ISC_R_SUCCESS); +} + +static void +fctx_add_event(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client, + dns_messageid_t id, isc_taskaction_t action, void *arg, + dns_fetch_t *fetch, isc_eventtype_t event_type) { + isc_task_t *tclone; + dns_fetchevent_t *event; + /* + * We store the task we're going to send this event to in the + * sender field. We'll make the fetch the sender when we actually + * send the event. + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_fetchevent_t *)isc_event_allocate(fctx->res->mctx, tclone, + event_type, action, arg, + sizeof(*event)); + event->result = DNS_R_SERVFAIL; + event->qtype = fctx->type; + event->db = NULL; + event->node = NULL; + event->rdataset = NULL; + event->sigrdataset = NULL; + event->fetch = fetch; + event->client = client; + event->id = id; + ISC_LIST_APPEND(fctx->events, event, ev_link); +} + +static void +log_ns_ttl(fetchctx_t *fctx, const char *where) { + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), + "log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx, + where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl); +} + +static isc_result_t +fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type, + const dns_name_t *domain, dns_rdataset_t *nameservers, + const isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, unsigned int bucketnum, unsigned int depth, + isc_counter_t *qc, fetchctx_t **fctxp) { + fetchctx_t *fctx; + isc_result_t result; + isc_result_t iresult; + isc_interval_t interval; + unsigned int findoptions = 0; + char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1]; + isc_mem_t *mctx; + size_t p; + bool try_stale; + + /* + * Caller must be holding the lock for bucket number 'bucketnum'. + */ + REQUIRE(fctxp != NULL && *fctxp == NULL); + + mctx = res->buckets[bucketnum].mctx; + fctx = isc_mem_get(mctx, sizeof(*fctx)); + + fctx->qc = NULL; + if (qc != NULL) { + isc_counter_attach(qc, &fctx->qc); + } else { + result = isc_counter_create(res->mctx, res->maxqueries, + &fctx->qc); + if (result != ISC_R_SUCCESS) { + goto cleanup_fetch; + } + } + + /* + * Make fctx->info point to a copy of a formatted string + * "name/type". + */ + dns_name_format(name, buf, sizeof(buf)); + p = strlcat(buf, "/", sizeof(buf)); + INSIST(p + DNS_RDATATYPE_FORMATSIZE < sizeof(buf)); + dns_rdatatype_format(type, buf + p, sizeof(buf) - p); + fctx->info = isc_mem_strdup(mctx, buf); + + FCTXTRACE("create"); + dns_name_init(&fctx->name, NULL); + dns_name_dup(name, mctx, &fctx->name); + dns_name_init(&fctx->qminname, NULL); + dns_name_dup(name, mctx, &fctx->qminname); + dns_name_init(&fctx->domain, NULL); + dns_rdataset_init(&fctx->nameservers); + + fctx->type = type; + fctx->qmintype = type; + fctx->options = options; + /* + * Note! We do not attach to the task. We are relying on the + * resolver to ensure that this task doesn't go away while we are + * using it. + */ + fctx->res = res; + isc_refcount_init(&fctx->references, 0); + fctx->bucketnum = bucketnum; + fctx->dbucketnum = RES_NOBUCKET; + fctx->state = fetchstate_init; + fctx->want_shutdown = false; + fctx->cloned = false; + fctx->depth = depth; + fctx->minimized = false; + fctx->ip6arpaskip = false; + fctx->forwarding = false; + fctx->qmin_labels = 1; + fctx->qmin_warning = ISC_R_SUCCESS; + fctx->qminfetch = NULL; + dns_rdataset_init(&fctx->qminrrset); + dns_name_init(&fctx->qmindcname, NULL); + isc_stdtime_get(&fctx->now); + ISC_LIST_INIT(fctx->queries); + ISC_LIST_INIT(fctx->finds); + ISC_LIST_INIT(fctx->altfinds); + ISC_LIST_INIT(fctx->forwaddrs); + ISC_LIST_INIT(fctx->altaddrs); + ISC_LIST_INIT(fctx->forwarders); + fctx->fwdpolicy = dns_fwdpolicy_none; + ISC_LIST_INIT(fctx->bad); + ISC_LIST_INIT(fctx->edns); + ISC_LIST_INIT(fctx->edns512); + ISC_LIST_INIT(fctx->bad_edns); + ISC_LIST_INIT(fctx->validators); + fctx->validator = NULL; + fctx->find = NULL; + fctx->altfind = NULL; + fctx->pending = 0; + fctx->restarts = 0; + fctx->querysent = 0; + fctx->referrals = 0; + + fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname); + + TIME_NOW(&fctx->start); + fctx->timeouts = 0; + fctx->lamecount = 0; + fctx->quotacount = 0; + fctx->adberr = 0; + fctx->neterr = 0; + fctx->badresp = 0; + fctx->findfail = 0; + fctx->valfail = 0; + fctx->result = ISC_R_FAILURE; + fctx->vresult = ISC_R_SUCCESS; + fctx->exitline = -1; /* sentinel */ + fctx->logged = false; + atomic_init(&fctx->attributes, 0); + fctx->spilled = false; + fctx->nqueries = 0; + fctx->reason = NULL; + fctx->rand_buf = 0; + fctx->rand_bits = 0; + fctx->timeout = false; + fctx->addrinfo = NULL; + if (client != NULL) { + isc_sockaddr_format(client, fctx->clientstr, + sizeof(fctx->clientstr)); + } else { + strlcpy(fctx->clientstr, "<unknown>", sizeof(fctx->clientstr)); + } + fctx->id = id; + fctx->ns_ttl = 0; + fctx->ns_ttl_ok = false; + + dns_name_init(&fctx->nsname, NULL); + fctx->nsfetch = NULL; + dns_rdataset_init(&fctx->nsrrset); + + if (domain == NULL) { + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed; + unsigned int labels; + const dns_name_t *fwdname = name; + dns_name_t suffix; + dns_name_t *fname; + + /* + * DS records are found in the parent server. Strip one + * leading label from the name (to be used in finding + * the forwarder). + */ + if (dns_rdatatype_atparent(fctx->type) && + dns_name_countlabels(name) > 1) + { + dns_name_init(&suffix, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + fwdname = &suffix; + } + + /* Find the forwarder for this name. */ + fname = dns_fixedname_initname(&fixed); + result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname, + fname, &forwarders); + if (result == ISC_R_SUCCESS) { + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copynf(fname, fctx->fwdname); + } + + if (fctx->fwdpolicy != dns_fwdpolicy_only) { + dns_fixedname_t dcfixed; + dns_name_t *dcname; + dcname = dns_fixedname_initname(&dcfixed); + /* + * The caller didn't supply a query domain and + * nameservers, and we're not in forward-only mode, + * so find the best nameservers to use. + */ + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + result = dns_view_findzonecut(res->view, name, fname, + dcname, fctx->now, + findoptions, true, true, + &fctx->nameservers, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup_nameservers; + } + + dns_name_dup(fname, mctx, &fctx->domain); + dns_name_dup(dcname, mctx, &fctx->qmindcname); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + } else { + /* + * We're in forward-only mode. Set the query domain. + */ + dns_name_dup(fname, mctx, &fctx->domain); + dns_name_dup(fname, mctx, &fctx->qmindcname); + /* + * Disable query minimization + */ + options &= ~DNS_FETCHOPT_QMINIMIZE; + } + } else { + dns_name_dup(domain, mctx, &fctx->domain); + dns_name_dup(domain, mctx, &fctx->qmindcname); + dns_rdataset_clone(nameservers, &fctx->nameservers); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + } + + /* + * Are there too many simultaneous queries for this domain? + */ + result = fcount_incr(fctx, false); + if (result != ISC_R_SUCCESS) { + result = fctx->res->quotaresp[dns_quotatype_zone]; + inc_stats(res, dns_resstatscounter_zonequota); + goto cleanup_domain; + } + + log_ns_ttl(fctx, "fctx_create"); + + if (!dns_name_issubdomain(&fctx->name, &fctx->domain)) { + dns_name_format(&fctx->domain, buf, sizeof(buf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "'%s' is not subdomain of '%s'", fctx->info, + buf); + result = ISC_R_UNEXPECTED; + goto cleanup_fcount; + } + + fctx->qmessage = NULL; + dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &fctx->qmessage); + + /* + * Compute an expiration time for the entire fetch. + */ + isc_interval_set(&interval, res->query_timeout / 1000, + res->query_timeout % 1000 * 1000000); + iresult = isc_time_nowplusinterval(&fctx->expires, &interval); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_time_nowplusinterval: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + try_stale = ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0); + if (try_stale) { + INSIST(res->view->staleanswerclienttimeout <= + (res->query_timeout - 1000)); + /* + * Compute an expiration time after which stale data will + * attempted to be served, if stale answers are enabled and + * target RRset is available in cache. + */ + isc_interval_set( + &interval, res->view->staleanswerclienttimeout / 1000, + res->view->staleanswerclienttimeout % 1000 * 1000000); + iresult = isc_time_nowplusinterval(&fctx->expires_try_stale, + &interval); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_time_nowplusinterval: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + } + + /* + * Default retry interval initialization. We set the interval now + * mostly so it won't be uninitialized. It will be set to the + * correct value before a query is issued. + */ + isc_interval_set(&fctx->interval, 2, 0); + + /* + * Create an inactive timer for resolver-query-timeout. It + * will be made active when the fetch is actually started. + */ + fctx->timer = NULL; + + iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL, + NULL, res->buckets[bucketnum].task, + fctx_timeout, fctx, &fctx->timer); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_create: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + /* + * If stale answers are enabled, then create an inactive timer + * for stale-answer-client-timeout. It will be made active when + * the fetch is actually started. + */ + fctx->timer_try_stale = NULL; + if (try_stale) { + iresult = isc_timer_create( + res->timermgr, isc_timertype_inactive, NULL, NULL, + res->buckets[bucketnum].task, fctx_timeout_try_stale, + fctx, &fctx->timer_try_stale); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_create: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + } + + /* + * Attach to the view's cache and adb. + */ + fctx->cache = NULL; + dns_db_attach(res->view->cachedb, &fctx->cache); + fctx->adb = NULL; + dns_adb_attach(res->view->adb, &fctx->adb); + fctx->mctx = NULL; + isc_mem_attach(mctx, &fctx->mctx); + + ISC_LIST_INIT(fctx->events); + ISC_LINK_INIT(fctx, link); + fctx->magic = FCTX_MAGIC; + + /* + * If qname minimization is enabled we need to trim + * the name in fctx to proper length. + */ + if ((options & DNS_FETCHOPT_QMINIMIZE) != 0) { + fctx->ip6arpaskip = + (options & DNS_FETCHOPT_QMIN_SKIP_IP6A) != 0 && + dns_name_issubdomain(&fctx->name, &ip6_arpa); + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + goto cleanup_mctx; + } + } + + ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link); + + INSIST(atomic_fetch_add_relaxed(&res->nfctx, 1) < UINT32_MAX); + + inc_stats(res, dns_resstatscounter_nfetch); + + *fctxp = fctx; + + return (ISC_R_SUCCESS); + +cleanup_mctx: + fctx->magic = 0; + isc_mem_detach(&fctx->mctx); + dns_adb_detach(&fctx->adb); + dns_db_detach(&fctx->cache); + isc_timer_destroy(&fctx->timer); + isc_timer_destroy(&fctx->timer_try_stale); + +cleanup_qmessage: + dns_message_detach(&fctx->qmessage); + +cleanup_fcount: + fcount_decr(fctx); + +cleanup_domain: + if (dns_name_countlabels(&fctx->domain) > 0) { + dns_name_free(&fctx->domain, mctx); + } + if (dns_name_countlabels(&fctx->qmindcname) > 0) { + dns_name_free(&fctx->qmindcname, mctx); + } + +cleanup_nameservers: + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_name_free(&fctx->name, mctx); + dns_name_free(&fctx->qminname, mctx); + isc_mem_free(mctx, fctx->info); + isc_counter_detach(&fctx->qc); + +cleanup_fetch: + isc_mem_put(mctx, fctx, sizeof(*fctx)); + + return (result); +} + +/* + * Handle Responses + */ +static bool +is_lame(fetchctx_t *fctx, dns_message_t *message) { + dns_name_t *name; + dns_rdataset_t *rdataset; + isc_result_t result; + + if (message->rcode != dns_rcode_noerror && + message->rcode != dns_rcode_yxdomain && + message->rcode != dns_rcode_nxdomain) + { + return (false); + } + + if (message->counts[DNS_SECTION_ANSWER] != 0) { + return (false); + } + + if (message->counts[DNS_SECTION_AUTHORITY] == 0) { + return (false); + } + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_namereln_t namereln; + int order; + unsigned int labels; + if (rdataset->type != dns_rdatatype_ns) { + continue; + } + namereln = dns_name_fullcompare(name, &fctx->domain, + &order, &labels); + if (namereln == dns_namereln_equal && + (message->flags & DNS_MESSAGEFLAG_AA) != 0) + { + return (false); + } + if (namereln == dns_namereln_subdomain) { + return (false); + } + return (true); + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + + return (false); +} + +static void +log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) { + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "lame server resolving '%s' (in '%s'?): %s", namebuf, + domainbuf, addrbuf); +} + +static void +log_formerr(fetchctx_t *fctx, const char *format, ...) { + char nsbuf[ISC_SOCKADDR_FORMATSIZE]; + char msgbuf[2048]; + va_list args; + + va_start(args, format); + vsnprintf(msgbuf, sizeof(msgbuf), format, args); + va_end(args); + + isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "DNS format error from %s resolving %s for %s: %s", nsbuf, + fctx->info, fctx->clientstr, msgbuf); +} + +static isc_result_t +same_question(fetchctx_t *fctx, dns_message_t *message) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + + /* + * Caller must be holding the fctx lock. + */ + + /* + * XXXRTH Currently we support only one question. + */ + if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] == 0)) { + if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) { + /* + * If TC=1 and the question section is empty, we + * accept the reply message as a truncated + * answer, to be retried over TCP. + * + * It is really a FORMERR condition, but this is + * a workaround to accept replies from some + * implementations. + * + * Because the question section matching is not + * performed, the worst that could happen is + * that an attacker who gets past the ID and + * source port checks can force the use of + * TCP. This is considered an acceptable risk. + */ + log_formerr(fctx, "empty question section, " + "accepting it anyway as TC=1"); + return (ISC_R_SUCCESS); + } else { + log_formerr(fctx, "empty question section"); + return (DNS_R_FORMERR); + } + } else if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] > 1)) { + log_formerr(fctx, "too many questions"); + return (DNS_R_FORMERR); + } + + result = dns_message_firstname(message, DNS_SECTION_QUESTION); + if (result != ISC_R_SUCCESS) { + return (result); + } + name = NULL; + dns_message_currentname(message, DNS_SECTION_QUESTION, &name); + rdataset = ISC_LIST_HEAD(name->list); + INSIST(rdataset != NULL); + INSIST(ISC_LIST_NEXT(rdataset, link) == NULL); + + if (fctx->type != rdataset->type || + fctx->res->rdclass != rdataset->rdclass || + !dns_name_equal(&fctx->name, name)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + log_formerr(fctx, "question section mismatch: got %s/%s/%s", + namebuf, classbuf, typebuf); + return (DNS_R_FORMERR); + } + + return (ISC_R_SUCCESS); +} + +static void +clone_results(fetchctx_t *fctx) { + dns_fetchevent_t *event, *hevent; + dns_name_t *name, *hname; + + FCTXTRACE("clone_results"); + + /* + * Set up any other events to have the same data as the first + * event. + * + * Caller must be holding the appropriate lock. + */ + + fctx->cloned = true; + hevent = ISC_LIST_HEAD(fctx->events); + if (hevent == NULL) { + return; + } + hname = dns_fixedname_name(&hevent->foundname); + for (event = ISC_LIST_NEXT(hevent, ev_link); event != NULL; + event = ISC_LIST_NEXT(event, ev_link)) + { + if (event->ev_type == DNS_EVENT_TRYSTALE) { + /* + * We don't need to clone resulting data to this + * type of event, as its associated callback is only + * called when stale-answer-client-timeout triggers, + * and the logic in there doesn't expect any result + * as input, as it will itself lookup for stale data + * in cache to use as result, if any is available. + * + * Also, if we reached this point, then the whole fetch + * context is done, it will cancel timers, process + * associated callbacks of type DNS_EVENT_FETCHDONE, and + * silently remove/free events of type + * DNS_EVENT_TRYSTALE. + */ + continue; + } + name = dns_fixedname_name(&event->foundname); + dns_name_copynf(hname, name); + event->result = hevent->result; + dns_db_attach(hevent->db, &event->db); + dns_db_attachnode(hevent->db, hevent->node, &event->node); + INSIST(hevent->rdataset != NULL); + INSIST(event->rdataset != NULL); + if (dns_rdataset_isassociated(hevent->rdataset)) { + dns_rdataset_clone(hevent->rdataset, event->rdataset); + } + INSIST(!(hevent->sigrdataset == NULL && + event->sigrdataset != NULL)); + if (hevent->sigrdataset != NULL && + dns_rdataset_isassociated(hevent->sigrdataset) && + event->sigrdataset != NULL) + { + dns_rdataset_clone(hevent->sigrdataset, + event->sigrdataset); + } + } +} + +#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0) +#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0) +#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0) +#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0) +#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0) +#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0) +#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0) + +/* + * The validator has finished. + */ +static void +validated(isc_task_t *task, isc_event_t *event) { + dns_adbaddrinfo_t *addrinfo; + dns_dbnode_t *node = NULL; + dns_dbnode_t *nsnode = NULL; + dns_fetchevent_t *hevent; + dns_name_t *name; + dns_rdataset_t *ardataset = NULL; + dns_rdataset_t *asigrdataset = NULL; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_resolver_t *res; + dns_valarg_t *valarg = event->ev_arg; + dns_validatorevent_t *vevent; + fetchctx_t *fctx; + bool chaining; + bool negative; + bool sentresponse; + bool bucket_empty; + isc_result_t eresult = ISC_R_SUCCESS; + isc_result_t result = ISC_R_SUCCESS; + isc_stdtime_t now; + uint32_t ttl; + unsigned options; + uint32_t bucketnum; + dns_fixedname_t fwild; + dns_name_t *wild = NULL; + dns_message_t *message = NULL; + + UNUSED(task); /* for now */ + + REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE); + REQUIRE(VALID_FCTX(valarg->fctx)); + REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators)); + + fctx = valarg->fctx; + fctx_increference(fctx); + dns_message_attach(valarg->message, &message); + + res = fctx->res; + addrinfo = valarg->addrinfo; + + vevent = (dns_validatorevent_t *)event; + fctx->vresult = vevent->result; + + FCTXTRACE("received validation completion event"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + ISC_LIST_UNLINK(fctx->validators, vevent->validator, link); + fctx->validator = NULL; + UNLOCK(&res->buckets[bucketnum].lock); + + /* + * Destroy the validator early so that we can + * destroy the fctx if necessary. Save the wildcard name. + */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + wild = dns_fixedname_initname(&fwild); + dns_name_copynf(dns_fixedname_name(&vevent->validator->wild), + wild); + } + dns_validator_destroy(&vevent->validator); + dns_message_detach(&valarg->message); + isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); + + negative = (vevent->rdataset == NULL); + + LOCK(&res->buckets[bucketnum].lock); + sentresponse = ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0); + + /* + * If shutting down, ignore the results. + */ + if (SHUTTINGDOWN(fctx) && !sentresponse) { + UNLOCK(&res->buckets[bucketnum].lock); + goto cleanup_event; + } + + isc_stdtime_get(&now); + + /* + * If chaining, we need to make sure that the right result code is + * returned, and that the rdatasets are bound. + */ + if (vevent->result == ISC_R_SUCCESS && !negative && + vevent->rdataset != NULL && CHAINING(vevent->rdataset)) + { + if (vevent->rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(vevent->rdataset->type == dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + chaining = true; + } else { + chaining = false; + } + + /* + * Either we're not shutting down, or we are shutting down but want + * to cache the result anyway (if this was a validation started by + * a query with cd set) + */ + + hevent = ISC_LIST_HEAD(fctx->events); + if (hevent != NULL) { + if (!negative && !chaining && + (fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig)) + { + /* + * Don't bind rdatasets; the caller + * will iterate the node. + */ + } else { + ardataset = hevent->rdataset; + asigrdataset = hevent->sigrdataset; + } + } + + if (vevent->result != ISC_R_SUCCESS) { + FCTXTRACE("validation failed"); + inc_stats(res, dns_resstatscounter_valfail); + fctx->valfail++; + fctx->vresult = vevent->result; + if (fctx->vresult != DNS_R_BROKENCHAIN) { + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) { + result = dns_db_findnode( + fctx->cache, vevent->name, true, &node); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, vevent->type, + 0); + if (vevent->sigrdataset != NULL) { + (void)dns_db_deleterdataset( + fctx->cache, node, NULL, + dns_rdatatype_rrsig, + vevent->type); + } + dns_db_detachnode(fctx->cache, &node); + } + } else if (!negative) { + /* + * Cache the data as pending for later validation. + */ + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) { + result = dns_db_findnode( + fctx->cache, vevent->name, true, &node); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset( + fctx->cache, node, NULL, now, + vevent->rdataset, 0, NULL); + if (vevent->sigrdataset != NULL) { + (void)dns_db_addrdataset( + fctx->cache, node, NULL, now, + vevent->sigrdataset, 0, NULL); + } + dns_db_detachnode(fctx->cache, &node); + } + } + result = fctx->vresult; + add_bad(fctx, message, addrinfo, result, badns_validation); + UNLOCK(&res->buckets[bucketnum].lock); + INSIST(fctx->validator == NULL); + fctx->validator = ISC_LIST_HEAD(fctx->validators); + if (fctx->validator != NULL) { + dns_validator_send(fctx->validator); + } else if (sentresponse) { + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + } else if (result == DNS_R_BROKENCHAIN) { + isc_result_t tresult; + isc_time_t expire; + isc_interval_t i; + + isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0); + tresult = isc_time_nowplusinterval(&expire, &i); + if (negative && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_ds) && + tresult == ISC_R_SUCCESS) + { + dns_resolver_addbadcache(res, &fctx->name, + fctx->type, &expire); + } + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + } else { + fctx_try(fctx, true, true); /* Locks bucket. */ + } + + goto cleanup_event; + } + + if (negative) { + dns_rdatatype_t covers; + FCTXTRACE("nonexistence validation OK"); + + inc_stats(res, dns_resstatscounter_valnegsuccess); + + /* + * Cache DS NXDOMAIN separately to other types. + */ + if (message->rcode == dns_rcode_nxdomain && + fctx->type != dns_rdatatype_ds) + { + covers = dns_rdatatype_any; + } else { + covers = fctx->type; + } + + result = dns_db_findnode(fctx->cache, vevent->name, true, + &node); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + + /* + * If we are asking for a SOA record set the cache time + * to zero to facilitate locating the containing zone of + * a arbitrary zone. + */ + ttl = res->view->maxncachettl; + if (fctx->type == dns_rdatatype_soa && + covers == dns_rdatatype_any && res->zero_no_soa_ttl) + { + ttl = 0; + } + + result = ncache_adderesult(message, fctx->cache, node, covers, + now, fctx->res->view->minncachettl, + ttl, vevent->optout, vevent->secure, + ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + goto answer_response; + } else { + inc_stats(res, dns_resstatscounter_valsuccess); + } + + FCTXTRACE("validation OK"); + + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + result = dns_rdataset_addnoqname( + vevent->rdataset, + vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + INSIST(vevent->sigrdataset != NULL); + vevent->sigrdataset->ttl = vevent->rdataset->ttl; + if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) { + result = dns_rdataset_addclosest( + vevent->rdataset, + vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + } else if (vevent->rdataset->trust == dns_trust_answer && + vevent->rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname(fctx, message, vevent->name, + vevent->rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && noqname != NULL) { + tresult = dns_rdataset_addnoqname(vevent->rdataset, + noqname); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + } + } + + /* + * The data was already cached as pending data. + * Re-cache it as secure and bind the cached + * rdatasets to the first event on the fetch + * event list. + */ + result = dns_db_findnode(fctx->cache, vevent->name, true, &node); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + + options = 0; + if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) { + options = DNS_DBADD_PREFETCH; + } + result = dns_db_addrdataset(fctx->cache, node, NULL, now, + vevent->rdataset, options, ardataset); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + goto noanswer_response; + } + if (ardataset != NULL && NEGATIVE(ardataset)) { + if (NXDOMAIN(ardataset)) { + eresult = DNS_R_NCACHENXDOMAIN; + } else { + eresult = DNS_R_NCACHENXRRSET; + } + } else if (vevent->sigrdataset != NULL) { + result = dns_db_addrdataset(fctx->cache, node, NULL, now, + vevent->sigrdataset, options, + asigrdataset); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + goto noanswer_response; + } + } + + if (sentresponse) { + /* + * If we only deferred the destroy because we wanted to cache + * the data, destroy now. + */ + dns_db_detachnode(fctx->cache, &node); + UNLOCK(&res->buckets[bucketnum].lock); + goto cleanup_event; + } + + if (!ISC_LIST_EMPTY(fctx->validators)) { + INSIST(!negative); + INSIST(fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig); + /* + * Don't send a response yet - we have + * more rdatasets that still need to + * be validated. + */ + dns_db_detachnode(fctx->cache, &node); + UNLOCK(&res->buckets[bucketnum].lock); + dns_validator_send(ISC_LIST_HEAD(fctx->validators)); + goto cleanup_event; + } + +answer_response: + /* + * Cache any SOA/NS/NSEC records that happened to be validated. + */ + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if ((rdataset->type != dns_rdatatype_ns && + rdataset->type != dns_rdatatype_soa && + rdataset->type != dns_rdatatype_nsec) || + rdataset->trust != dns_trust_secure) + { + continue; + } + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != rdataset->type) + { + continue; + } + break; + } + if (sigrdataset == NULL || + sigrdataset->trust != dns_trust_secure) + { + continue; + } + result = dns_db_findnode(fctx->cache, name, true, + &nsnode); + if (result != ISC_R_SUCCESS) { + continue; + } + + result = dns_db_addrdataset(fctx->cache, nsnode, NULL, + now, rdataset, 0, NULL); + if (result == ISC_R_SUCCESS) { + result = dns_db_addrdataset( + fctx->cache, nsnode, NULL, now, + sigrdataset, 0, NULL); + } + dns_db_detachnode(fctx->cache, &nsnode); + if (result != ISC_R_SUCCESS) { + continue; + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + + /* + * Add the wild card entry. + */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL && + vevent->rdataset != NULL && + dns_rdataset_isassociated(vevent->rdataset) && + vevent->rdataset->trust == dns_trust_secure && + vevent->sigrdataset != NULL && + dns_rdataset_isassociated(vevent->sigrdataset) && + vevent->sigrdataset->trust == dns_trust_secure && wild != NULL) + { + dns_dbnode_t *wnode = NULL; + + result = dns_db_findnode(fctx->cache, wild, true, &wnode); + if (result == ISC_R_SUCCESS) { + result = dns_db_addrdataset(fctx->cache, wnode, NULL, + now, vevent->rdataset, 0, + NULL); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset(fctx->cache, wnode, NULL, now, + vevent->sigrdataset, 0, NULL); + } + if (wnode != NULL) { + dns_db_detachnode(fctx->cache, &wnode); + } + } + + result = ISC_R_SUCCESS; + + /* + * Respond with an answer, positive or negative, + * as opposed to an error. 'node' must be non-NULL. + */ + + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + + if (hevent != NULL) { + /* + * Negative results must be indicated in event->result. + */ + INSIST(hevent->rdataset != NULL); + if (dns_rdataset_isassociated(hevent->rdataset) && + NEGATIVE(hevent->rdataset)) + { + INSIST(eresult == DNS_R_NCACHENXDOMAIN || + eresult == DNS_R_NCACHENXRRSET); + } + hevent->result = eresult; + dns_name_copynf(vevent->name, + dns_fixedname_name(&hevent->foundname)); + dns_db_attach(fctx->cache, &hevent->db); + dns_db_transfernode(fctx->cache, &node, &hevent->node); + clone_results(fctx); + } + +noanswer_response: + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + UNLOCK(&res->buckets[bucketnum].lock); + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + +cleanup_event: + INSIST(node == NULL); + dns_message_detach(&message); + isc_event_free(&event); + + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +static void +fctx_log(void *arg, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list args; + fetchctx_t *fctx = arg; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); + va_end(args); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx, + fctx->info, msgbuf); +} + +static isc_result_t +findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name, + dns_rdatatype_t type, dns_name_t **noqnamep) { + dns_rdataset_t *nrdataset, *next, *sigrdataset; + dns_rdata_rrsig_t rrsig; + isc_result_t result; + unsigned int labels; + dns_section_t section; + dns_name_t *zonename; + dns_fixedname_t fzonename; + dns_name_t *closest; + dns_fixedname_t fclosest; + dns_name_t *nearest; + dns_fixedname_t fnearest; + dns_rdatatype_t found = dns_rdatatype_none; + dns_name_t *noqname = NULL; + + FCTXTRACE("findnoqname"); + + REQUIRE(noqnamep != NULL && *noqnamep == NULL); + + /* + * Find the SIG for this rdataset, if we have it. + */ + for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == type) + { + break; + } + } + + if (sigrdataset == NULL) { + return (ISC_R_NOTFOUND); + } + + labels = dns_name_countlabels(name); + + for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigrdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(sigrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* Wildcard has rrsig.labels < labels - 1. */ + if (rrsig.labels + 1U >= labels) { + continue; + } + break; + } + + if (result == ISC_R_NOMORE) { + return (ISC_R_NOTFOUND); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + zonename = dns_fixedname_initname(&fzonename); + closest = dns_fixedname_initname(&fclosest); + nearest = dns_fixedname_initname(&fnearest); + +#define NXND(x) ((x) == ISC_R_SUCCESS) + + section = DNS_SECTION_AUTHORITY; + for (result = dns_message_firstname(message, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, section)) + { + dns_name_t *nsec = NULL; + dns_message_currentname(message, section, &nsec); + for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL; + nrdataset = next) + { + bool data = false, exists = false; + bool optout = false, unknown = false; + bool setclosest = false; + bool setnearest = false; + + next = ISC_LIST_NEXT(nrdataset, link); + if (nrdataset->type != dns_rdatatype_nsec && + nrdataset->type != dns_rdatatype_nsec3) + { + continue; + } + + if (nrdataset->type == dns_rdatatype_nsec && + NXND(dns_nsec_noexistnodata( + type, name, nsec, nrdataset, &exists, &data, + NULL, fctx_log, fctx))) + { + if (!exists) { + noqname = nsec; + found = dns_rdatatype_nsec; + } + } + + if (nrdataset->type == dns_rdatatype_nsec3 && + NXND(dns_nsec3_noexistnodata( + type, name, nsec, nrdataset, zonename, + &exists, &data, &optout, &unknown, + &setclosest, &setnearest, closest, nearest, + fctx_log, fctx))) + { + if (!exists && setnearest) { + noqname = nsec; + found = dns_rdatatype_nsec3; + } + } + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + if (noqname != NULL) { + for (sigrdataset = ISC_LIST_HEAD(noqname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == found) + { + break; + } + } + if (sigrdataset != NULL) { + *noqnamep = noqname; + } + } + return (result); +} + +static isc_result_t +cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t *addedrdataset = NULL; + dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL; + dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL; + dns_dbnode_t *node = NULL, **anodep = NULL; + dns_db_t **adbp = NULL; + dns_name_t *aname = NULL; + dns_resolver_t *res = fctx->res; + bool need_validation = false; + bool secure_domain = false; + bool have_answer = false; + isc_result_t result, eresult = ISC_R_SUCCESS; + dns_fetchevent_t *event = NULL; + unsigned int options; + isc_task_t *task; + bool fail; + unsigned int valoptions = 0; + bool checknta = true; + + FCTXTRACE("cache_name"); + + /* + * The appropriate bucket lock must be held. + */ + task = res->buckets[fctx->bucketnum].task; + + /* + * Is DNSSEC validation required for this name? + */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = false; + } + + if (res->view->enablevalidation) { + result = issecuredomain(res->view, name, fctx->type, now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + valoptions |= DNS_VALIDATOR_NOCDFLAG; + } + + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + need_validation = false; + } else { + need_validation = secure_domain; + } + + if (((name->attributes & DNS_NAMEATTR_ANSWER) != 0) && + (!need_validation)) + { + have_answer = true; + event = ISC_LIST_HEAD(fctx->events); + + if (event != NULL) { + adbp = &event->db; + aname = dns_fixedname_name(&event->foundname); + dns_name_copynf(name, aname); + anodep = &event->node; + /* + * If this is an ANY, SIG or RRSIG query, we're not + * going to return any rdatasets, unless we encountered + * a CNAME or DNAME as "the answer". In this case, + * we're going to return DNS_R_CNAME or DNS_R_DNAME + * and we must set up the rdatasets. + */ + if ((fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_rrsig && + fctx->type != dns_rdatatype_sig) || + (name->attributes & DNS_NAMEATTR_CHAINING) != 0) + { + ardataset = event->rdataset; + asigrdataset = event->sigrdataset; + } + } + } + + /* + * Find or create the cache node. + */ + node = NULL; + result = dns_db_findnode(fctx->cache, name, true, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Cache or validate each cacheable rdataset. + */ + fail = ((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!CACHE(rdataset)) { + continue; + } + if (CHECKNAMES(rdataset)) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "check-names %s %s/%s/%s", + fail ? "failure" : "warning", namebuf, + typebuf, classbuf); + if (fail) { + if (ANSWER(rdataset)) { + dns_db_detachnode(fctx->cache, &node); + return (DNS_R_BADNAME); + } + continue; + } + } + + /* + * Enforce the configure maximum cache TTL. + */ + if (rdataset->ttl > res->view->maxcachettl) { + rdataset->ttl = res->view->maxcachettl; + } + + /* + * Enforce configured minimum cache TTL. + */ + if (rdataset->ttl < res->view->mincachettl) { + rdataset->ttl = res->view->mincachettl; + } + + /* + * Mark the rdataset as being prefetch eligible. + */ + if (rdataset->ttl >= fctx->res->view->prefetch_eligible) { + rdataset->attributes |= DNS_RDATASETATTR_PREFETCH; + } + + /* + * Find the SIG for this rdataset, if we have it. + */ + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == rdataset->type) + { + break; + } + } + + /* + * If this RRset is in a secure domain, is in bailiwick, + * and is not glue, attempt DNSSEC validation. (We do not + * attempt to validate glue or out-of-bailiwick data--even + * though there might be some performance benefit to doing + * so--because it makes it simpler and safer to ensure that + * records from a secure domain are only cached if validated + * within the context of a query to the domain that owns + * them.) + */ + if (secure_domain && rdataset->trust != dns_trust_glue && + !EXTERNAL(rdataset)) + { + dns_trust_t trust; + + /* + * RRSIGs are validated as part of validating the + * type they cover. + */ + if (rdataset->type == dns_rdatatype_rrsig) { + continue; + } + + if (sigrdataset == NULL && need_validation && + !ANSWER(rdataset)) + { + /* + * Ignore unrelated non-answer + * rdatasets that are missing signatures. + */ + continue; + } + + /* + * Normalize the rdataset and sigrdataset TTLs. + */ + if (sigrdataset != NULL) { + rdataset->ttl = ISC_MIN(rdataset->ttl, + sigrdataset->ttl); + sigrdataset->ttl = rdataset->ttl; + } + + /* + * Mark the rdataset as being prefetch eligible. + */ + if (rdataset->ttl >= fctx->res->view->prefetch_eligible) + { + rdataset->attributes |= + DNS_RDATASETATTR_PREFETCH; + } + + /* + * Cache this rdataset/sigrdataset pair as + * pending data. Track whether it was additional + * or not. If this was a priming query, additional + * should be cached as glue. + */ + if (rdataset->trust == dns_trust_additional) { + trust = dns_trust_pending_additional; + } else { + trust = dns_trust_pending_answer; + } + + rdataset->trust = trust; + if (sigrdataset != NULL) { + sigrdataset->trust = trust; + } + if (!need_validation || !ANSWER(rdataset)) { + options = 0; + if (ANSWER(rdataset) && + rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname( + fctx, message, name, + rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && + noqname != NULL) + { + (void)dns_rdataset_addnoqname( + rdataset, noqname); + } + } + if ((fctx->options & DNS_FETCHOPT_PREFETCH) != + 0) + { + options = DNS_DBADD_PREFETCH; + } + if ((fctx->options & DNS_FETCHOPT_NOCACHED) != + 0) + { + options |= DNS_DBADD_FORCE; + } + addedrdataset = ardataset; + result = dns_db_addrdataset( + fctx->cache, node, NULL, now, rdataset, + options, addedrdataset); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + if (!need_validation && + ardataset != NULL && + NEGATIVE(ardataset)) + { + /* + * The answer in the cache is + * better than the answer we + * found, and is a negative + * cache entry, so we must set + * eresult appropriately. + */ + if (NXDOMAIN(ardataset)) { + eresult = + DNS_R_NCACHENXDOMAIN; + } else { + eresult = + DNS_R_NCACHENXRRSET; + } + /* + * We have a negative response + * from the cache so don't + * attempt to add the RRSIG + * rrset. + */ + continue; + } + } + if (result != ISC_R_SUCCESS) { + break; + } + if (sigrdataset != NULL) { + addedrdataset = asigrdataset; + result = dns_db_addrdataset( + fctx->cache, node, NULL, now, + sigrdataset, options, + addedrdataset); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS) { + break; + } + } else if (!ANSWER(rdataset)) { + continue; + } + } + + if (ANSWER(rdataset) && need_validation) { + if (fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_rrsig && + fctx->type != dns_rdatatype_sig) + { + /* + * This is The Answer. We will + * validate it, but first we cache + * the rest of the response - it may + * contain useful keys. + */ + INSIST(valrdataset == NULL && + valsigrdataset == NULL); + valrdataset = rdataset; + valsigrdataset = sigrdataset; + } else { + /* + * This is one of (potentially) + * multiple answers to an ANY + * or SIG query. To keep things + * simple, we just start the + * validator right away rather + * than caching first and + * having to remember which + * rdatasets needed validation. + */ + result = valcreate( + fctx, message, addrinfo, name, + rdataset->type, rdataset, + sigrdataset, valoptions, task); + } + } else if (CHAINING(rdataset)) { + if (rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(rdataset->type == + dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + } + } else if (!EXTERNAL(rdataset)) { + /* + * It's OK to cache this rdataset now. + */ + if (ANSWER(rdataset)) { + addedrdataset = ardataset; + } else if (ANSWERSIG(rdataset)) { + addedrdataset = asigrdataset; + } else { + addedrdataset = NULL; + } + if (CHAINING(rdataset)) { + if (rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(rdataset->type == + dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + } + if (rdataset->trust == dns_trust_glue && + (rdataset->type == dns_rdatatype_ns || + (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == dns_rdatatype_ns))) + { + /* + * If the trust level is 'dns_trust_glue' + * then we are adding data from a referral + * we got while executing the search algorithm. + * New referral data always takes precedence + * over the existing cache contents. + */ + options = DNS_DBADD_FORCE; + } else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) + { + options = DNS_DBADD_PREFETCH; + } else { + options = 0; + } + + if (ANSWER(rdataset) && + rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname(fctx, message, name, + rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && noqname != NULL) + { + (void)dns_rdataset_addnoqname(rdataset, + noqname); + } + } + + /* + * Now we can add the rdataset. + */ + result = dns_db_addrdataset(fctx->cache, node, NULL, + now, rdataset, options, + addedrdataset); + + if (result == DNS_R_UNCHANGED) { + if (ANSWER(rdataset) && ardataset != NULL && + NEGATIVE(ardataset)) + { + /* + * The answer in the cache is better + * than the answer we found, and is + * a negative cache entry, so we + * must set eresult appropriately. + */ + if (NXDOMAIN(ardataset)) { + eresult = DNS_R_NCACHENXDOMAIN; + } else { + eresult = DNS_R_NCACHENXRRSET; + } + } + result = ISC_R_SUCCESS; + } else if (result != ISC_R_SUCCESS) { + break; + } + } + } + + if (valrdataset != NULL) { + dns_rdatatype_t vtype = fctx->type; + if (CHAINING(valrdataset)) { + if (valrdataset->type == dns_rdatatype_cname) { + vtype = dns_rdatatype_cname; + } else { + vtype = dns_rdatatype_dname; + } + } + + result = valcreate(fctx, message, addrinfo, name, vtype, + valrdataset, valsigrdataset, valoptions, + task); + } + + if (result == ISC_R_SUCCESS && have_answer) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + if (event != NULL) { + /* + * Negative results must be indicated in event->result. + */ + if (dns_rdataset_isassociated(event->rdataset) && + NEGATIVE(event->rdataset)) + { + INSIST(eresult == DNS_R_NCACHENXDOMAIN || + eresult == DNS_R_NCACHENXRRSET); + } + event->result = eresult; + if (adbp != NULL && *adbp != NULL) { + if (anodep != NULL && *anodep != NULL) { + dns_db_detachnode(*adbp, anodep); + } + dns_db_detach(adbp); + } + dns_db_attach(fctx->cache, adbp); + dns_db_transfernode(fctx->cache, &node, anodep); + clone_results(fctx); + } + } + + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + return (result); +} + +static isc_result_t +cache_message(fetchctx_t *fctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { + isc_result_t result; + dns_section_t section; + dns_name_t *name; + + FCTXTRACE("cache_message"); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE); + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL; + section++) + { + result = dns_message_firstname(message, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, section, &name); + if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) { + result = cache_name(fctx, name, message, + addrinfo, now); + if (result != ISC_R_SUCCESS) { + break; + } + } + result = dns_message_nextname(message, section); + } + if (result != ISC_R_NOMORE) { + break; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + return (result); +} + +/* + * Do what dns_ncache_addoptout() does, and then compute an appropriate eresult. + */ +static isc_result_t +ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *ardataset, isc_result_t *eresultp) { + isc_result_t result; + dns_rdataset_t rdataset; + + if (ardataset == NULL) { + dns_rdataset_init(&rdataset); + ardataset = &rdataset; + } + if (secure) { + result = dns_ncache_addoptout(message, cache, node, covers, now, + minttl, maxttl, optout, + ardataset); + } else { + result = dns_ncache_add(message, cache, node, covers, now, + minttl, maxttl, ardataset); + } + if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) { + /* + * If the cache now contains a negative entry and we + * care about whether it is DNS_R_NCACHENXDOMAIN or + * DNS_R_NCACHENXRRSET then extract it. + */ + if (NEGATIVE(ardataset)) { + /* + * The cache data is a negative cache entry. + */ + if (NXDOMAIN(ardataset)) { + *eresultp = DNS_R_NCACHENXDOMAIN; + } else { + *eresultp = DNS_R_NCACHENXRRSET; + } + } else { + /* + * Either we don't care about the nature of the + * cache rdataset (because no fetch is interested + * in the outcome), or the cache rdataset is not + * a negative cache entry. Whichever case it is, + * we can return success. + * + * XXXRTH There's a CNAME/DNAME problem here. + */ + *eresultp = ISC_R_SUCCESS; + } + result = ISC_R_SUCCESS; + } + if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) { + dns_rdataset_disassociate(ardataset); + } + + return (result); +} + +static isc_result_t +ncache_message(fetchctx_t *fctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers, + isc_stdtime_t now) { + isc_result_t result, eresult; + dns_name_t *name; + dns_resolver_t *res; + dns_db_t **adbp; + dns_dbnode_t *node, **anodep; + dns_rdataset_t *ardataset; + bool need_validation, secure_domain; + dns_name_t *aname; + dns_fetchevent_t *event; + uint32_t ttl; + unsigned int valoptions = 0; + bool checknta = true; + + FCTXTRACE("ncache_message"); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE); + + res = fctx->res; + need_validation = false; + POST(need_validation); + secure_domain = false; + eresult = ISC_R_SUCCESS; + name = &fctx->name; + node = NULL; + + /* + * XXXMPA remove when we follow cnames and adjust the setting + * of FCTX_ATTR_WANTNCACHE in rctx_answer_none(). + */ + INSIST(message->counts[DNS_SECTION_ANSWER] == 0); + + /* + * Is DNSSEC validation required for this name? + */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = false; + } + + if (fctx->res->view->enablevalidation) { + result = issecuredomain(res->view, name, fctx->type, now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + valoptions |= DNS_VALIDATOR_NOCDFLAG; + } + + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + need_validation = false; + } else { + need_validation = secure_domain; + } + + if (secure_domain) { + /* + * Mark all rdatasets as pending. + */ + dns_rdataset_t *trdataset; + dns_name_t *tname; + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + tname = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, + &tname); + for (trdataset = ISC_LIST_HEAD(tname->list); + trdataset != NULL; + trdataset = ISC_LIST_NEXT(trdataset, link)) + { + trdataset->trust = dns_trust_pending_answer; + } + result = dns_message_nextname(message, + DNS_SECTION_AUTHORITY); + } + if (result != ISC_R_NOMORE) { + return (result); + } + } + + if (need_validation) { + /* + * Do negative response validation. + */ + result = valcreate(fctx, message, addrinfo, name, fctx->type, + NULL, NULL, valoptions, + res->buckets[fctx->bucketnum].task); + /* + * If validation is necessary, return now. Otherwise continue + * to process the message, letting the validation complete + * in its own good time. + */ + return (result); + } + + LOCK(&res->buckets[fctx->bucketnum].lock); + + adbp = NULL; + aname = NULL; + anodep = NULL; + ardataset = NULL; + if (!HAVE_ANSWER(fctx)) { + event = ISC_LIST_HEAD(fctx->events); + if (event != NULL) { + adbp = &event->db; + aname = dns_fixedname_name(&event->foundname); + dns_name_copynf(name, aname); + anodep = &event->node; + ardataset = event->rdataset; + } + } else { + event = NULL; + } + + result = dns_db_findnode(fctx->cache, name, true, &node); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + /* + * If we are asking for a SOA record set the cache time + * to zero to facilitate locating the containing zone of + * a arbitrary zone. + */ + ttl = fctx->res->view->maxncachettl; + if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any && + fctx->res->zero_no_soa_ttl) + { + ttl = 0; + } + + result = ncache_adderesult(message, fctx->cache, node, covers, now, + fctx->res->view->minncachettl, ttl, false, + false, ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + if (!HAVE_ANSWER(fctx)) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + if (event != NULL) { + event->result = eresult; + if (adbp != NULL && *adbp != NULL) { + if (anodep != NULL && *anodep != NULL) { + dns_db_detachnode(*adbp, anodep); + } + dns_db_detach(adbp); + } + dns_db_attach(fctx->cache, adbp); + dns_db_transfernode(fctx->cache, &node, anodep); + clone_results(fctx); + } + } + +unlock: + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + return (result); +} + +static void +mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external, + bool gluing) { + name->attributes |= DNS_NAMEATTR_CACHE; + if (gluing) { + rdataset->trust = dns_trust_glue; + /* + * Glue with 0 TTL causes problems. We force the TTL to + * 1 second to prevent this. + */ + if (rdataset->ttl == 0) { + rdataset->ttl = 1; + } + } else { + rdataset->trust = dns_trust_additional; + } + /* + * Avoid infinite loops by only marking new rdatasets. + */ + if (!CACHE(rdataset)) { + name->attributes |= DNS_NAMEATTR_CHASE; + rdataset->attributes |= DNS_RDATASETATTR_CHASE; + } + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + if (external) { + rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL; + } +} + +/* + * Returns true if 'name' is external to the namespace for which + * the server being queried can answer, either because it's not a + * subdomain or because it's below a forward declaration or a + * locally served zone. + */ +static bool +name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { + isc_result_t result; + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed, zfixed; + dns_name_t *fname = dns_fixedname_initname(&fixed); + dns_name_t *zfname = dns_fixedname_initname(&zfixed); + dns_name_t *apex = NULL; + dns_name_t suffix; + dns_zone_t *zone = NULL; + unsigned int labels; + dns_namereln_t rel; + + apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo)) + ? &fctx->domain + : fctx->fwdname; + + /* + * The name is outside the queried namespace. + */ + rel = dns_name_fullcompare(name, apex, &(int){ 0 }, + &(unsigned int){ 0U }); + if (rel != dns_namereln_subdomain && rel != dns_namereln_equal) { + return (true); + } + + /* + * If the record lives in the parent zone, adjust the name so we + * look for the correct zone or forward clause. + */ + labels = dns_name_countlabels(name); + if (dns_rdatatype_atparent(type) && labels > 1U) { + dns_name_init(&suffix, NULL); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } else if (rel == dns_namereln_equal) { + /* If 'name' is 'apex', no further checking is needed. */ + return (false); + } + + /* + * If there is a locally served zone between 'apex' and 'name' + * then don't cache. + */ + LOCK(&fctx->res->view->lock); + if (fctx->res->view->zonetable != NULL) { + unsigned int options = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_MIRROR; + result = dns_zt_find(fctx->res->view->zonetable, name, options, + zfname, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + if (dns_name_fullcompare(zfname, apex, &(int){ 0 }, + &(unsigned int){ 0U }) == + dns_namereln_subdomain) + { + UNLOCK(&fctx->res->view->lock); + return (true); + } + } + } + UNLOCK(&fctx->res->view->lock); + + /* + * Look for a forward declaration below 'name'. + */ + result = dns_fwdtable_find(fctx->res->view->fwdtable, name, fname, + &forwarders); + + if (ISFORWARDER(fctx->addrinfo)) { + /* + * See if the forwarder declaration is better. + */ + if (result == ISC_R_SUCCESS) { + return (!dns_name_equal(fname, fctx->fwdname)); + } + + /* + * If the lookup failed, the configuration must have + * changed: play it safe and don't cache. + */ + return (true); + } else if (result == ISC_R_SUCCESS && + forwarders->fwdpolicy == dns_fwdpolicy_only && + !ISC_LIST_EMPTY(forwarders->fwdrs)) + { + /* + * If 'name' is covered by a 'forward only' clause then we + * can't cache this repsonse. + */ + return (true); + } + + return (false); +} + +static isc_result_t +check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type, + dns_section_t section) { + respctx_t *rctx = arg; + fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + bool external; + dns_rdatatype_t rtype; + bool gluing; + + REQUIRE(VALID_FCTX(fctx)); + +#if CHECK_FOR_GLUE_IN_ANSWER + if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) { + return (ISC_R_SUCCESS); + } +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + + gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns && + dns_name_equal(&fctx->name, dns_rootname))); + + result = dns_message_findname(rctx->query->rmessage, section, addname, + dns_rdatatype_any, 0, &name, NULL); + if (result == ISC_R_SUCCESS) { + external = name_external(name, type, fctx); + if (type == dns_rdatatype_a) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_rrsig) { + rtype = rdataset->covers; + } else { + rtype = rdataset->type; + } + if (rtype == dns_rdatatype_a || + rtype == dns_rdatatype_aaaa) + { + mark_related(name, rdataset, external, + gluing); + } + } + } else { + result = dns_message_findtype(name, type, 0, &rdataset); + if (result == ISC_R_SUCCESS) { + mark_related(name, rdataset, external, gluing); + /* + * Do we have its SIG too? + */ + rdataset = NULL; + result = dns_message_findtype( + name, dns_rdatatype_rrsig, type, + &rdataset); + if (result == ISC_R_SUCCESS) { + mark_related(name, rdataset, external, + gluing); + } + } + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +check_related(void *arg, const dns_name_t *addname, dns_rdatatype_t type) { + return (check_section(arg, addname, type, DNS_SECTION_ADDITIONAL)); +} + +#ifndef CHECK_FOR_GLUE_IN_ANSWER +#define CHECK_FOR_GLUE_IN_ANSWER 0 +#endif /* ifndef CHECK_FOR_GLUE_IN_ANSWER */ + +#if CHECK_FOR_GLUE_IN_ANSWER +static isc_result_t +check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type) { + return (check_section(arg, addname, type, DNS_SECTION_ANSWER)); +} +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + +static bool +is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, + dns_rdataset_t *rdataset) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct in_addr ina; + struct in6_addr in6a; + isc_netaddr_t netaddr; + char addrbuf[ISC_NETADDR_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + int match; + + /* By default, we allow any addresses. */ + if (view->denyansweracl == NULL) { + return (true); + } + + /* + * If the owner name matches one in the exclusion list, either exactly + * or partially, allow it. + */ + if (view->answeracl_exclude != NULL) { + dns_rbtnode_t *node = NULL; + + result = dns_rbt_findnode(view->answeracl_exclude, name, NULL, + &node, NULL, 0, NULL, NULL); + + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + return (true); + } + } + + /* + * Otherwise, search the filter list for a match for each address + * record. If a match is found, the address should be filtered, + * so should the entire answer. + */ + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + if (rdataset->type == dns_rdatatype_a) { + INSIST(rdata.length == sizeof(ina.s_addr)); + memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr)); + isc_netaddr_fromin(&netaddr, &ina); + } else { + INSIST(rdata.length == sizeof(in6a.s6_addr)); + memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr)); + isc_netaddr_fromin6(&netaddr, &in6a); + } + + result = dns_acl_match(&netaddr, NULL, view->denyansweracl, + &view->aclenv, &match, NULL); + if (result == ISC_R_SUCCESS && match > 0) { + isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "answer address %s denied for %s/%s/%s", + addrbuf, namebuf, typebuf, classbuf); + return (false); + } + } + + return (true); +} + +static bool +is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname, + dns_rdataset_t *rdataset, bool *chainingp) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + char qnamebuf[DNS_NAME_FORMATSIZE]; + char tnamebuf[DNS_NAME_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + dns_name_t *tname = NULL; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + dns_view_t *view = fctx->res->view; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + dns_fixedname_t fixed; + dns_name_t prefix; + int order; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname); + + /* + * By default, we allow any target name. + * If newqname != NULL we also need to extract the newqname. + */ + if (chainingp == NULL && view->denyanswernames == NULL) { + return (true); + } + + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + switch (rdataset->type) { + case dns_rdatatype_cname: + result = dns_rdata_tostruct(&rdata, &cname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tname = &cname.cname; + break; + case dns_rdatatype_dname: + if (dns_name_fullcompare(qname, rname, &order, &nlabels) != + dns_namereln_subdomain) + { + return (true); + } + result = dns_rdata_tostruct(&rdata, &dname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_init(&prefix, NULL); + tname = dns_fixedname_initname(&fixed); + nlabels = dns_name_countlabels(rname); + dns_name_split(qname, nlabels, &prefix, NULL); + result = dns_name_concatenate(&prefix, &dname.dname, tname, + NULL); + if (result == DNS_R_NAMETOOLONG) { + if (chainingp != NULL) { + *chainingp = true; + } + return (true); + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + break; + default: + UNREACHABLE(); + } + + if (chainingp != NULL) { + *chainingp = true; + } + + if (view->denyanswernames == NULL) { + return (true); + } + + /* + * If the owner name matches one in the exclusion list, either exactly + * or partially, allow it. + */ + if (view->answernames_exclude != NULL) { + result = dns_rbt_findnode(view->answernames_exclude, qname, + NULL, &node, NULL, 0, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + return (true); + } + } + + /* + * If the target name is a subdomain of the search domain, allow it. + * + * Note that if BIND is configured as a forwarding DNS server, the + * search domain will always match the root domain ("."), so we + * must also check whether forwarding is enabled so that filters + * can be applied; see GL #1574. + */ + if (!fctx->forwarding && dns_name_issubdomain(tname, &fctx->domain)) { + return (true); + } + + /* + * Otherwise, apply filters. + */ + result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node, + NULL, 0, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + dns_name_format(qname, qnamebuf, sizeof(qnamebuf)); + dns_name_format(tname, tnamebuf, sizeof(tnamebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(view->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "%s target %s denied for %s/%s", typebuf, + tnamebuf, qnamebuf, classbuf); + return (false); + } + + return (true); +} + +static void +trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) { + char ns_namebuf[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) { + dns_name_format(name, ns_namebuf, sizeof(ns_namebuf)); + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), + "fctx %p: trimming ttl of %s/NS for %s/%s: " + "%u -> %u", + fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl, + fctx->ns_ttl); + rdataset->ttl = fctx->ns_ttl; + } +} + +static bool +validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) { + if (rdataset->type == dns_rdatatype_nsec3) { + /* + * NSEC3 records are not allowed to + * appear in the answer section. + */ + log_formerr(fctx, "NSEC3 in answer"); + return (false); + } + if (rdataset->type == dns_rdatatype_tkey) { + /* + * TKEY is not a valid record in a + * response to any query we can make. + */ + log_formerr(fctx, "TKEY in answer"); + return (false); + } + if (rdataset->rdclass != fctx->res->rdclass) { + log_formerr(fctx, "Mismatched class in answer"); + return (false); + } + return (true); +} + +static void +fctx_increference(fetchctx_t *fctx) { + REQUIRE(VALID_FCTX(fctx)); + + isc_refcount_increment0(&fctx->references); +} + +/* + * Requires bucket lock to be held. + */ +static bool +fctx_decreference(fetchctx_t *fctx) { + bool bucket_empty = false; + + REQUIRE(VALID_FCTX(fctx)); + + if (isc_refcount_decrement(&fctx->references) == 1) { + /* + * No one cares about the result of this fetch anymore. + */ + if (fctx->pending == 0 && fctx->nqueries == 0 && + ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) + { + /* + * This fctx is already shutdown; we were just + * waiting for the last reference to go away. + */ + bucket_empty = fctx_unlink(fctx); + fctx_destroy(fctx); + } else { + /* + * Initiate shutdown. + */ + fctx_shutdown(fctx); + } + } + return (bucket_empty); +} + +static void +resume_dslookup(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *fevent; + dns_resolver_t *res; + fetchctx_t *fctx; + isc_result_t result; + uint32_t bucketnum; + bool bucket_empty; + dns_rdataset_t nameservers; + dns_fixedname_t fixed; + dns_name_t *domain; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + bucketnum = fctx->bucketnum; + + UNUSED(task); + FCTXTRACE("resume_dslookup"); + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + + dns_rdataset_init(&nameservers); + + /* + * Note: fevent->rdataset must be disassociated and + * isc_event_free(&event) be called before resuming + * processing of the 'fctx' to prevent use-after-free. + * 'fevent' is set to NULL so as to not have a dangling + * pointer. + */ + if (fevent->result == ISC_R_CANCELED) { + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + dns_resolver_destroyfetch(&fctx->nsfetch); + fctx_done(fctx, ISC_R_CANCELED, __LINE__); + } else if (fevent->result == ISC_R_SUCCESS) { + FCTXTRACE("resuming DS lookup"); + + dns_resolver_destroyfetch(&fctx->nsfetch); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_rdataset_clone(fevent->rdataset, &fctx->nameservers); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + log_ns_ttl(fctx, "resume_dslookup"); + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + goto cleanup; + } + /* + * Try again. + */ + fctx_try(fctx, true, false); + } else { + unsigned int n; + dns_rdataset_t *nsrdataset = NULL; + + /* + * Retrieve state from fctx->nsfetch before we destroy it. + */ + domain = dns_fixedname_initname(&fixed); + dns_name_copynf(&fctx->nsfetch->private->domain, domain); + if (dns_name_equal(&fctx->nsname, domain)) { + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + dns_resolver_destroyfetch(&fctx->nsfetch); + goto cleanup; + } + if (dns_rdataset_isassociated( + &fctx->nsfetch->private->nameservers)) + { + dns_rdataset_clone(&fctx->nsfetch->private->nameservers, + &nameservers); + nsrdataset = &nameservers; + } else { + domain = NULL; + } + dns_resolver_destroyfetch(&fctx->nsfetch); + n = dns_name_countlabels(&fctx->nsname); + dns_name_getlabelsequence(&fctx->nsname, 1, n - 1, + &fctx->nsname); + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + FCTXTRACE("continuing to look for parent's NS records"); + + result = dns_resolver_createfetch( + fctx->res, &fctx->nsname, dns_rdatatype_ns, domain, + nsrdataset, NULL, NULL, 0, fctx->options, 0, NULL, task, + resume_dslookup, fctx, &fctx->nsrrset, NULL, + &fctx->nsfetch); + /* + * fevent->rdataset (a.k.a. fctx->nsrrset) must not be + * accessed below this point to prevent races with + * another thread concurrently processing the fetch. + */ + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_done(fctx, result, __LINE__); + } else { + fctx_increference(fctx); + } + } + +cleanup: + INSIST(event == NULL); + INSIST(fevent == NULL); + if (dns_rdataset_isassociated(&nameservers)) { + dns_rdataset_disassociate(&nameservers); + } + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +static void +checknamessection(dns_message_t *message, dns_section_t section) { + isc_result_t result; + dns_name_t *name; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t *rdataset; + + for (result = dns_message_firstname(message, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, section)) + { + name = NULL; + dns_message_currentname(message, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + if (!dns_rdata_checkowner(name, rdata.rdclass, + rdata.type, false) || + !dns_rdata_checknames(&rdata, name, NULL)) + { + rdataset->attributes |= + DNS_RDATASETATTR_CHECKNAMES; + } + dns_rdata_reset(&rdata); + } + } + } +} + +static void +checknames(dns_message_t *message) { + checknamessection(message, DNS_SECTION_ANSWER); + checknamessection(message, DNS_SECTION_AUTHORITY); + checknamessection(message, DNS_SECTION_ADDITIONAL); +} + +/* + * Log server NSID at log level 'level' + */ +static void +log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level, + isc_mem_t *mctx) { + static const char hex[17] = "0123456789abcdef"; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + size_t buflen; + unsigned char *p, *nsid; + unsigned char *buf = NULL, *pbuf = NULL; + + REQUIRE(nsid_len <= UINT16_MAX); + + /* Allocate buffer for storing hex version of the NSID */ + buflen = nsid_len * 2 + 1; + buf = isc_mem_get(mctx, buflen); + pbuf = isc_mem_get(mctx, nsid_len + 1); + + /* Convert to hex */ + p = buf; + nsid = isc_buffer_current(opt); + for (size_t i = 0; i < nsid_len; i++) { + *p++ = hex[(nsid[i] >> 4) & 0xf]; + *p++ = hex[nsid[i] & 0xf]; + } + *p = '\0'; + + /* Make printable version */ + p = pbuf; + for (size_t i = 0; i < nsid_len; i++) { + *p++ = isprint(nsid[i]) ? nsid[i] : '.'; + } + *p = '\0'; + + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_NSID, DNS_LOGMODULE_RESOLVER, + level, "received NSID %s (\"%s\") from %s", buf, pbuf, + addrbuf); + + isc_mem_put(mctx, pbuf, nsid_len + 1); + isc_mem_put(mctx, buf, buflen); +} + +static bool +iscname(dns_message_t *message, dns_name_t *name) { + isc_result_t result; + + result = dns_message_findname(message, DNS_SECTION_ANSWER, name, + dns_rdatatype_cname, 0, NULL, NULL); + return (result == ISC_R_SUCCESS ? true : false); +} + +static bool +betterreferral(respctx_t *rctx) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + + for (result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY)) + { + name = NULL; + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + if (!isstrictsubdomain(name, &rctx->fctx->domain)) { + continue; + } + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_ns) { + return (true); + } + } + } + return (false); +} + +/* + * resquery_response(): + * Handles responses received in response to iterative queries sent by + * resquery_send(). Sets up a response context (respctx_t). + */ +static void +resquery_response(isc_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + resquery_t *query = event->ev_arg; + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + fetchctx_t *fctx; + respctx_t rctx; + + REQUIRE(VALID_QUERY(query)); + fctx = query->fctx; + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + + QTRACE("response"); + + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) { + inc_stats(fctx->res, dns_resstatscounter_responsev4); + } else { + inc_stats(fctx->res, dns_resstatscounter_responsev6); + } + + (void)isc_timer_touch(fctx->timer); + + rctx_respinit(task, devent, query, fctx, &rctx); + + if (atomic_load_acquire(&fctx->res->exiting)) { + result = ISC_R_SHUTTINGDOWN; + FCTXTRACE("resolver shutting down"); + rctx_done(&rctx, result); + return; + } + + fctx->timeouts = 0; + fctx->timeout = false; + fctx->addrinfo = query->addrinfo; + + /* + * Check whether the dispatcher has failed; if so we're done + */ + result = rctx_dispfail(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + if (query->tsig != NULL) { + result = dns_message_setquerytsig(query->rmessage, query->tsig); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set query tsig", result); + rctx_done(&rctx, result); + return; + } + } + + if (query->tsigkey) { + result = dns_message_settsigkey(query->rmessage, + query->tsigkey); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set tsig key", result); + rctx_done(&rctx, result); + return; + } + } + + dns_message_setclass(query->rmessage, fctx->res->rdclass); + + if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { + if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + dns_adb_setudpsize( + fctx->adb, query->addrinfo, + isc_buffer_usedlength(&devent->buffer)); + } else { + dns_adb_plainresponse(fctx->adb, query->addrinfo); + } + } + + /* + * Parse response message. + */ + result = rctx_parse(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Log the incoming packet. + */ + rctx_logpacket(&rctx); + + if (query->rmessage->rdclass != fctx->res->rdclass) { + rctx.resend = true; + FCTXTRACE("bad class"); + rctx_done(&rctx, result); + return; + } + + /* + * Process receive opt record. + */ + rctx.opt = dns_message_getopt(query->rmessage); + if (rctx.opt != NULL) { + rctx_opt(&rctx); + } + + if (query->rmessage->cc_bad && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * If the COOKIE is bad, assume it is an attack and + * keep listening for a good answer. + */ + rctx.nextitem = true; + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "bad cookie from %s", addrbuf); + } + rctx_done(&rctx, result); + return; + } + + /* + * Is the question the same as the one we asked? + * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must have + * the same question. + * FORMERR/NOTIMP if they have a question section then it must match. + */ + switch (query->rmessage->rcode) { + case dns_rcode_notimp: + case dns_rcode_formerr: + if (query->rmessage->counts[DNS_SECTION_QUESTION] == 0) { + break; + } + FALLTHROUGH; + case dns_rcode_nxrrset: /* Not expected. */ + case dns_rcode_badcookie: + case dns_rcode_noerror: + case dns_rcode_nxdomain: + case dns_rcode_yxdomain: + case dns_rcode_refused: + case dns_rcode_servfail: + default: + result = same_question(fctx, query->rmessage); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("response did not match question", result); + rctx.nextitem = true; + rctx_done(&rctx, result); + return; + } + break; + } + + /* + * If the message is signed, check the signature. If not, this + * returns success anyway. + */ + result = dns_message_checksig(query->rmessage, fctx->res->view); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("signature check failed", result); + if (result == DNS_R_UNEXPECTEDTSIG || + result == DNS_R_EXPECTEDTSIG) + { + rctx.nextitem = true; + } + rctx_done(&rctx, result); + return; + } + + /* + * The dispatcher should ensure we only get responses with QR set. + */ + INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); + /* + * INSIST() that the message comes from the place we sent it to, + * since the dispatch code should ensure this. + * + * INSIST() that the message id is correct (this should also be + * ensured by the dispatch code). + */ + + /* + * If we have had a server cookie and don't get one retry over TCP. + * This may be a misconfigured anycast server or an attempt to send + * a spoofed response. Skip if we have a valid tsig. + */ + if (dns_message_gettsig(query->rmessage, NULL) == NULL && + !query->rmessage->cc_ok && !query->rmessage->cc_bad && + (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) + { + unsigned char cookie[COOKIE_BUFFER_SIZE]; + if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie, + sizeof(cookie)) > CLIENT_COOKIE_SIZE) + { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, + addrbuf, sizeof(addrbuf)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "missing expected cookie from %s", + addrbuf); + } + rctx.retryopts |= DNS_FETCHOPT_TCP; + rctx.resend = true; + rctx_done(&rctx, result); + return; + } + /* + * XXXMPA When support for DNS COOKIE becomes ubiquitous, fall + * back to TCP for all non-COOKIE responses. + */ + } + + rctx_edns(&rctx); + + /* + * Deal with truncated responses by retrying using TCP. + */ + if ((query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) { + rctx.truncated = true; + } + + if (rctx.truncated) { + inc_stats(fctx->res, dns_resstatscounter_truncated); + if ((rctx.retryopts & DNS_FETCHOPT_TCP) != 0) { + rctx.broken_server = DNS_R_TRUNCATEDTCP; + rctx.next_server = true; + } else { + rctx.retryopts |= DNS_FETCHOPT_TCP; + rctx.resend = true; + } + FCTXTRACE3("message truncated", result); + rctx_done(&rctx, result); + return; + } + + /* + * Is it a query response? + */ + if (query->rmessage->opcode != dns_opcode_query) { + rctx.broken_server = DNS_R_UNEXPECTEDOPCODE; + rctx.next_server = true; + FCTXTRACE("invalid message opcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Update statistics about erroneous responses. + */ + switch (query->rmessage->rcode) { + case dns_rcode_noerror: + /* no error */ + break; + case dns_rcode_nxdomain: + inc_stats(fctx->res, dns_resstatscounter_nxdomain); + break; + case dns_rcode_servfail: + inc_stats(fctx->res, dns_resstatscounter_servfail); + break; + case dns_rcode_formerr: + inc_stats(fctx->res, dns_resstatscounter_formerr); + break; + case dns_rcode_refused: + inc_stats(fctx->res, dns_resstatscounter_refused); + break; + case dns_rcode_badvers: + inc_stats(fctx->res, dns_resstatscounter_badvers); + break; + case dns_rcode_badcookie: + inc_stats(fctx->res, dns_resstatscounter_badcookie); + break; + default: + inc_stats(fctx->res, dns_resstatscounter_othererror); + break; + } + + /* + * Bad server? + */ + result = rctx_badserver(&rctx, result); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Lame server? + */ + result = rctx_lameserver(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Handle delegation-only zones like NET or COM. + */ + rctx_delonly_zone(&rctx); + + /* + * Optionally call dns_rdata_checkowner() and dns_rdata_checknames() + * to validate the names in the response message. + */ + if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) { + checknames(query->rmessage); + } + + /* + * Clear cache bits. + */ + FCTX_ATTR_CLR(fctx, (FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE)); + + /* + * Did we get any answers? + */ + if (query->rmessage->counts[DNS_SECTION_ANSWER] > 0 && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_yxdomain || + query->rmessage->rcode == dns_rcode_nxdomain)) + { + result = rctx_answer(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + } else if (query->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 || + query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain) + { + /* + * This might be an NXDOMAIN, NXRRSET, or referral. + * Call rctx_answer_none() to determine which it is. + */ + result = rctx_answer_none(&rctx); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_CHASEDSSERVERS: + break; + case DNS_R_DELEGATION: + /* With NOFOLLOW we want to pass the result code */ + if ((fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + result = ISC_R_SUCCESS; + } + break; + default: + /* + * Something has gone wrong. + */ + if (result == DNS_R_FORMERR) { + rctx.next_server = true; + } + FCTXTRACE3("rctx_answer_none", result); + rctx_done(&rctx, result); + return; + } + } else { + /* + * The server is insane. + */ + /* XXXRTH Log */ + rctx.broken_server = DNS_R_UNEXPECTEDRCODE; + rctx.next_server = true; + FCTXTRACE("broken server: unexpected rcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Follow additional section data chains. + */ + rctx_additional(&rctx); + + /* + * Cache the cacheable parts of the message. This may also cause + * work to be queued to the DNSSEC validator. + */ + if (WANTCACHE(fctx)) { + isc_result_t tresult; + tresult = cache_message(fctx, query->rmessage, query->addrinfo, + rctx.now); + if (tresult != ISC_R_SUCCESS) { + FCTXTRACE3("cache_message complete", tresult); + rctx_done(&rctx, tresult); + return; + } + } + + /* + * Negative caching + */ + rctx_ncache(&rctx); + + rctx_done(&rctx, result); +} + +/* + * rctx_respinit(): + * Initialize the response context structure 'rctx' to all zeroes, then set + * the task, event, query and fctx information from resquery_response(). + */ +static void +rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query, + fetchctx_t *fctx, respctx_t *rctx) { + memset(rctx, 0, sizeof(*rctx)); + + rctx->task = task; + rctx->devent = devent; + rctx->query = query; + rctx->fctx = fctx; + rctx->broken_type = badns_response; + rctx->retryopts = query->options; + + /* + * XXXRTH We should really get the current time just once. We + * need a routine to convert from an isc_time_t to an + * isc_stdtime_t. + */ + TIME_NOW(&rctx->tnow); + rctx->finish = &rctx->tnow; + isc_stdtime_get(&rctx->now); +} + +/* + * rctx_answer_init(): + * Clear and reinitialize those portions of 'rctx' that will be needed + * when scanning the answer section of the response message. This can be + * called more than once if scanning needs to be restarted (though currently + * there are no cases in which this occurs). + */ +static void +rctx_answer_init(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + + rctx->aa = ((rctx->query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0); + if (rctx->aa) { + rctx->trust = dns_trust_authanswer; + } else { + rctx->trust = dns_trust_answer; + } + + /* + * There can be multiple RRSIG and SIG records at a name so + * we treat these types as a subset of ANY. + */ + rctx->type = fctx->type; + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_sig) + { + rctx->type = dns_rdatatype_any; + } + + /* + * Bigger than any valid DNAME label count. + */ + rctx->dname_labels = dns_name_countlabels(&fctx->name); + rctx->domain_labels = dns_name_countlabels(&fctx->domain); + + rctx->found_type = dns_rdatatype_none; + + rctx->aname = NULL; + rctx->ardataset = NULL; + + rctx->cname = NULL; + rctx->crdataset = NULL; + + rctx->dname = NULL; + rctx->drdataset = NULL; + + rctx->ns_name = NULL; + rctx->ns_rdataset = NULL; + + rctx->soa_name = NULL; + rctx->ds_name = NULL; + rctx->found_name = NULL; +} + +/* + * rctx_dispfail(): + * Handle the case where the dispatcher failed + */ +static isc_result_t +rctx_dispfail(respctx_t *rctx) { + dns_dispatchevent_t *devent = rctx->devent; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (devent->result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + if (devent->result == ISC_R_EOF && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * The problem might be that they don't understand EDNS0. + * Turn it off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + } else { + /* + * There's no hope for this response. + */ + rctx->next_server = true; + + /* + * If this is a network error on an exclusive query + * socket, mark the server as bad so that we won't try + * it for this fetch again. Also adjust finish and + * no_response so that we penalize this address in SRTT + * adjustment later. + */ + if (query->exclusivesocket && + (devent->result == ISC_R_HOSTUNREACH || + devent->result == ISC_R_NETUNREACH || + devent->result == ISC_R_CONNREFUSED || + devent->result == ISC_R_CANCELED)) + { + rctx->broken_server = devent->result; + rctx->broken_type = badns_unreachable; + rctx->finish = NULL; + rctx->no_response = true; + } + } + FCTXTRACE3("dispatcher failure", devent->result); + rctx_done(rctx, ISC_R_SUCCESS); + return (ISC_R_COMPLETE); +} + +/* + * rctx_parse(): + * Parse the response message. + */ +static isc_result_t +rctx_parse(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + result = dns_message_parse(query->rmessage, &rctx->devent->buffer, 0); + if (result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + FCTXTRACE3("message failed to parse", result); + + switch (result) { + case ISC_R_UNEXPECTEDEND: + if (query->rmessage->question_ok && + (query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0 && + (rctx->retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * We defer retrying via TCP for a bit so we can + * check out this message further. + */ + rctx->truncated = true; + return (ISC_R_SUCCESS); + } + + /* + * Either the message ended prematurely, + * and/or wasn't marked as being truncated, + * and/or this is a response to a query we + * sent over TCP. In all of these cases, + * something is wrong with the remote + * server and we don't want to retry using + * TCP. + */ + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = result; + rctx->next_server = true; + } + + rctx_done(rctx, result); + break; + case DNS_R_FORMERR: + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = true; + } + + rctx_done(rctx, result); + break; + default: + /* + * Something bad has happened. + */ + rctx_done(rctx, result); + break; + } + + return (ISC_R_COMPLETE); +} + +/* + * rctx_opt(): + * Process the OPT record in the response. + */ +static void +rctx_opt(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_rdata_t rdata; + isc_buffer_t optbuf; + isc_result_t result; + uint16_t optcode; + uint16_t optlen; + unsigned char *optvalue; + dns_adbaddrinfo_t *addrinfo; + unsigned char cookie[CLIENT_COOKIE_SIZE]; + bool seen_cookie = false; + bool seen_nsid = false; + + result = dns_rdataset_first(rctx->opt); + if (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(rctx->opt, &rdata); + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) >= 4) { + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(optlen <= isc_buffer_remaininglength(&optbuf)); + switch (optcode) { + case DNS_OPT_NSID: + if (!seen_nsid && (query->options & + DNS_FETCHOPT_WANTNSID) != 0) + { + log_nsid(&optbuf, optlen, query, + ISC_LOG_INFO, fctx->res->mctx); + } + isc_buffer_forward(&optbuf, optlen); + seen_nsid = true; + break; + case DNS_OPT_COOKIE: + /* + * Only process the first cookie option. + */ + if (seen_cookie) { + isc_buffer_forward(&optbuf, optlen); + break; + } + optvalue = isc_buffer_current(&optbuf); + compute_cc(query, cookie, sizeof(cookie)); + INSIST(query->rmessage->cc_bad == 0 && + query->rmessage->cc_ok == 0); + if (optlen >= CLIENT_COOKIE_SIZE && + memcmp(cookie, optvalue, + CLIENT_COOKIE_SIZE) == 0) + { + query->rmessage->cc_ok = 1; + inc_stats(fctx->res, + dns_resstatscounter_cookieok); + addrinfo = query->addrinfo; + dns_adb_setcookie(fctx->adb, addrinfo, + optvalue, optlen); + } else { + query->rmessage->cc_bad = 1; + } + isc_buffer_forward(&optbuf, optlen); + inc_stats(fctx->res, + dns_resstatscounter_cookiein); + seen_cookie = true; + break; + default: + isc_buffer_forward(&optbuf, optlen); + break; + } + } + INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + } +} + +/* + * rctx_edns(): + * Determine whether the remote server is using EDNS correctly or + * incorrectly and record that information if needed. + */ +static void +rctx_edns(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + + /* + * We have an affirmative response to the query and we have + * previously got a response from this server which indicated + * EDNS may not be supported so we can now cache the lack of + * EDNS support. + */ + if (rctx->opt == NULL && !EDNSOK(query->addrinfo) && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain || + query->rmessage->rcode == dns_rcode_refused || + query->rmessage->rcode == dns_rcode_yxdomain) && + bad_edns(fctx, &query->addrinfo->sockaddr)) + { + dns_message_logpacket( + query->rmessage, "received packet (bad edns) from", + &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); + } else if (rctx->opt == NULL && + (query->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 && + !EDNSOK(query->addrinfo) && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * We didn't get a OPT record in response to a EDNS query. + * + * Old versions of named incorrectly drop the OPT record + * when there is a signed, truncated response so we check + * that TC is not set. + * + * Record that the server is not talking EDNS. While this + * should be safe to do for any rcode we limit it to NOERROR + * and NXDOMAIN. + */ + dns_message_logpacket( + query->rmessage, "received packet (no opt) from", + &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); + } + + /* + * If we get a non error EDNS response record the fact so we + * won't fallback to plain DNS in the future for this server. + */ + if (rctx->opt != NULL && !EDNSOK(query->addrinfo) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain || + query->rmessage->rcode == dns_rcode_refused || + query->rmessage->rcode == dns_rcode_yxdomain)) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK); + } +} + +/* + * rctx_answer(): + * We might have answers, or we might have a malformed delegation with + * records in the answer section. Call rctx_answer_positive() or + * rctx_answer_none() as appropriate. + */ +static isc_result_t +rctx_answer(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if ((query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 || + ISFORWARDER(query->addrinfo)) + { + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (AA/fwd)", result); + } + } else if (iscname(query->rmessage, &fctx->name) && + fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_cname) + { + /* + * A BIND8 server could return a non-authoritative + * answer when a CNAME is followed. We should treat + * it as a valid answer. + */ + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)", + result); + } + } else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) { + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!NS)", result); + } + } else { + /* + * This may be a delegation. First let's check for + */ + + if (fctx->type == dns_rdatatype_ns) { + /* + * A BIND 8 server could incorrectly return a + * non-authoritative answer to an NS query + * instead of a referral. Since this answer + * lacks the SIGs necessary to do DNSSEC + * validation, we must invoke the following + * special kludge to treat it as a referral. + */ + rctx->ns_in_answer = true; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none (NS)", result); + } + } else { + /* + * Some other servers may still somehow include + * an answer when it should return a referral + * with an empty answer. Check to see if we can + * treat this as a referral by ignoring the + * answer. Further more, there may be an + * implementation that moves A/AAAA glue records + * to the answer section for that type of + * delegation when the query is for that glue + * record. glue_in_answer will handle + * such a corner case. + */ + rctx->glue_in_answer = true; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none", result); + } + } + + if (result == DNS_R_DELEGATION) { + result = ISC_R_SUCCESS; + } else { + /* + * At this point, AA is not set, the response + * is not a referral, and the server is not a + * forwarder. It is technically lame and it's + * easier to treat it as such than to figure out + * some more elaborate course of action. + */ + rctx->broken_server = DNS_R_LAME; + rctx->next_server = true; + rctx_done(rctx, result); + return (ISC_R_COMPLETE); + } + } + + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_FORMERR) { + rctx->next_server = true; + } + rctx_done(rctx, result); + return (ISC_R_COMPLETE); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_positive(): + * Handles positive responses. Depending which type of answer this is + * (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine + * to handle it (rctx_answer_match(), rctx_answer_cname(), + * rctx_answer_dname(), rctx_answer_any()). + */ +static isc_result_t +rctx_answer_positive(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + FCTXTRACE("rctx_answer"); + + rctx_answer_init(rctx); + rctx_answer_scan(rctx); + + /* + * Determine which type of positive answer this is: + * type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE. + * Call the appropriate routine to handle the answer type. + */ + if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) { + result = rctx_answer_any(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->aname != NULL) { + result = rctx_answer_match(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->cname != NULL) { + result = rctx_answer_cname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->dname != NULL) { + result = rctx_answer_dname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else { + log_formerr(fctx, "reply has no answer"); + return (DNS_R_FORMERR); + } + + /* + * This response is now potentially cacheable. + */ + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE); + + /* + * Did chaining end before we got the final answer? + */ + if (rctx->chaining) { + return (ISC_R_SUCCESS); + } + + /* + * We didn't end with an incomplete chain, so the rcode should be + * "no error". + */ + if (rctx->query->rmessage->rcode != dns_rcode_noerror) { + log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE " + "indicates error"); + return (DNS_R_FORMERR); + } + + /* + * Cache records in the authority section, if + * there are any suitable for caching. + */ + rctx_authority_positive(rctx); + + log_ns_ttl(fctx, "rctx_answer"); + + if (rctx->ns_rdataset != NULL && + dns_name_equal(&fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_scan(): + * Perform a single pass over the answer section of a response, looking + * for an answer that matches QNAME/QTYPE, or a CNAME matching QNAME, or a + * covering DNAME. If more than one rdataset is found matching these + * criteria, then only one is kept. Order of preference is 1) the + * shortest DNAME, 2) the first matching answer, or 3) the first CNAME. + */ +static void +rctx_answer_scan(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_rdataset_t *rdataset = NULL; + + for (result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_ANSWER)) + { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_ANSWER, &name); + namereln = dns_name_fullcompare(&fctx->name, name, &order, + &nlabels); + switch (namereln) { + case dns_namereln_equal: + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == rctx->type || + rctx->type == dns_rdatatype_any) + { + rctx->aname = name; + if (rctx->type != dns_rdatatype_any) { + rctx->ardataset = rdataset; + } + break; + } + if (rdataset->type == dns_rdatatype_cname) { + rctx->cname = name; + rctx->crdataset = rdataset; + break; + } + } + break; + + case dns_namereln_subdomain: + /* + * Don't accept DNAME from parent namespace. + */ + if (name_external(name, dns_rdatatype_dname, fctx)) { + continue; + } + + /* + * In-scope DNAME records must have at least + * as many labels as the domain being queried. + * They also must be less that qname's labels + * and any previously found dname. + */ + if (nlabels >= rctx->dname_labels || + nlabels < rctx->domain_labels) + { + continue; + } + + /* + * We are looking for the shortest DNAME if there + * are multiple ones (which there shouldn't be). + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_dname) { + continue; + } + rctx->dname = name; + rctx->drdataset = rdataset; + rctx->dname_labels = nlabels; + break; + } + break; + default: + break; + } + } + + /* + * If a DNAME was found, then any CNAME or other answer matching + * QNAME that may also have been found must be ignored. Similarly, + * if a matching answer was found along with a CNAME, the CNAME + * must be ignored. + */ + if (rctx->dname != NULL) { + rctx->aname = NULL; + rctx->ardataset = NULL; + rctx->cname = NULL; + rctx->crdataset = NULL; + } else if (rctx->aname != NULL) { + rctx->cname = NULL; + rctx->crdataset = NULL; + } +} + +/* + * rctx_answer_any(): + * Handle responses to queries of type ANY. Scan the answer section, + * and as long as each RRset is of a type that is valid in the answer + * section, and the rdata isn't filtered, cache it. + */ +static isc_result_t +rctx_answer_any(respctx_t *rctx) { + dns_rdataset_t *rdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + for (rdataset = ISC_LIST_HEAD(rctx->aname->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!validinanswer(rdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if ((fctx->type == dns_rdatatype_sig || + fctx->type == dns_rdatatype_rrsig) && + rdataset->type != fctx->type) + { + continue; + } + + if ((rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, rctx->aname, + rdataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + if ((rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, &fctx->name, rctx->aname, + rdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = rctx->trust; + + (void)dns_rdataset_additionaldata(rdataset, check_related, + rctx); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_match(): + * Handle responses that match the QNAME/QTYPE of the resolver query. + * If QTYPE is valid in the answer section and the rdata isn't filtered, + * the answer can be cached. If there is additional section data related + * to the answer, it can be cached as well. + */ +static isc_result_t +rctx_answer_match(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->ardataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if ((rctx->ardataset->type == dns_rdatatype_a || + rctx->ardataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, rctx->aname, + rctx->ardataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + if ((rctx->ardataset->type == dns_rdatatype_cname || + rctx->ardataset->type == dns_rdatatype_dname) && + rctx->type != rctx->ardataset->type && + rctx->type != dns_rdatatype_any && + !is_answertarget_allowed(fctx, &fctx->name, rctx->aname, + rctx->ardataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->ardataset->trust = rctx->trust; + (void)dns_rdataset_additionaldata(rctx->ardataset, check_related, rctx); + + for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != rctx->type) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_cname(): + * Handle answers containing a CNAME. Cache the CNAME, and flag that + * there may be additional chain answers to find. + */ +static isc_result_t +rctx_answer_cname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->crdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec) + { + char buf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(rctx->type, buf, sizeof(buf)); + log_formerr(fctx, "CNAME response for %s RR", buf); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (!is_answertarget_allowed(fctx, &fctx->name, rctx->cname, + rctx->crdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->cname->attributes |= DNS_NAMEATTR_CACHE; + rctx->cname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->cname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->crdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_cname) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + rctx->chaining = true; + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_dname(): + * Handle responses with covering DNAME records. + */ +static isc_result_t +rctx_answer_dname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->drdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (!is_answertarget_allowed(fctx, &fctx->name, rctx->dname, + rctx->drdataset, &rctx->chaining)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->dname->attributes |= DNS_NAMEATTR_CACHE; + rctx->dname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->dname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->drdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_dname) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_authority_positive(): + * Examine the records in the authority section (if there are any) for a + * positive answer. We expect the names for all rdatasets in this section + * to be subdomains of the domain being queried; any that are not are + * skipped. We expect to find only *one* owner name; any names + * after the first one processed are ignored. We expect to find only + * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever + * remains can be cached at trust level authauthority or additional + * (depending on whether the AA bit was set on the answer). + */ +static void +rctx_authority_positive(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + bool done = false; + isc_result_t result; + + result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + while (!done && result == ISC_R_SUCCESS) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + + if (!name_external(name, dns_rdatatype_ns, fctx)) { + dns_rdataset_t *rdataset = NULL; + + /* + * We expect to find NS or SIG NS rdatasets, and + * nothing else. + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_ns || + (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == dns_rdatatype_ns)) + { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else { + rdataset->trust = + dns_trust_additional; + } + + if (rdataset->type == dns_rdatatype_ns) + { + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; + } + /* + * Mark any additional data related + * to this rdataset. + */ + (void)dns_rdataset_additionaldata( + rdataset, check_related, rctx); + done = true; + } + } + } + + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + } +} + +/* + * rctx_answer_none(): + * Handles a response without an answer: this is either a negative + * response (NXDOMAIN or NXRRSET) or a referral. Determine which it is, + * then either scan the authority section for negative caching and + * DNSSEC proof of nonexistence, or else call rctx_referral(). + */ +static isc_result_t +rctx_answer_none(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + FCTXTRACE("rctx_answer_none"); + + rctx_answer_init(rctx); + + /* + * Sometimes we can tell if its a negative response by looking at + * the message header. + */ + if (rctx->query->rmessage->rcode == dns_rcode_nxdomain || + (rctx->query->rmessage->counts[DNS_SECTION_ANSWER] == 0 && + rctx->query->rmessage->counts[DNS_SECTION_AUTHORITY] == 0)) + { + rctx->negative = true; + } + + /* + * Process the authority section + */ + result = rctx_authority_negative(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + + log_ns_ttl(fctx, "rctx_answer_none"); + + if (rctx->ns_rdataset != NULL && + dns_name_equal(&fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); + } + + /* + * A negative response has a SOA record (Type 2) + * and a optional NS RRset (Type 1) or it has neither + * a SOA or a NS RRset (Type 3, handled above) or + * rcode is NXDOMAIN (handled above) in which case + * the NS RRset is allowed (Type 4). + */ + if (rctx->soa_name != NULL) { + rctx->negative = true; + } + + if (!rctx->ns_in_answer && !rctx->glue_in_answer) { + /* + * Process DNSSEC records in the authority section. + */ + result = rctx_authority_dnssec(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } + + /* + * Trigger lookups for DNS nameservers. + */ + if (rctx->negative && + rctx->query->rmessage->rcode == dns_rcode_noerror && + fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL && + dns_name_equal(rctx->soa_name, &fctx->name) && + !dns_name_equal(&fctx->name, dns_rootname)) + { + return (DNS_R_CHASEDSSERVERS); + } + + /* + * Did we find anything? + */ + if (!rctx->negative && rctx->ns_name == NULL) { + /* + * The responder is insane. + */ + if (rctx->found_name == NULL) { + log_formerr(fctx, "invalid response"); + return (DNS_R_FORMERR); + } + if (!dns_name_issubdomain(rctx->found_name, &fctx->domain)) { + char nbuf[DNS_NAME_FORMATSIZE]; + char dbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_rdatatype_format(rctx->found_type, tbuf, + sizeof(tbuf)); + dns_name_format(rctx->found_name, nbuf, sizeof(nbuf)); + dns_name_format(&fctx->domain, dbuf, sizeof(dbuf)); + + log_formerr(fctx, + "Name %s (%s) not subdomain" + " of zone %s -- invalid response", + nbuf, tbuf, dbuf); + } else { + log_formerr(fctx, "invalid response"); + } + return (DNS_R_FORMERR); + } + + /* + * If we found both NS and SOA, they should be the same name. + */ + if (rctx->ns_name != NULL && rctx->soa_name != NULL && + rctx->ns_name != rctx->soa_name) + { + log_formerr(fctx, "NS/SOA mismatch"); + return (DNS_R_FORMERR); + } + + /* + * Handle a referral. + */ + result = rctx_referral(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + + /* + * Since we're not doing a referral, we don't want to cache any + * NS RRs we may have found. + */ + if (rctx->ns_name != NULL) { + rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE; + } + + if (rctx->negative) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTNCACHE); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_authority_negative(): + * Scan the authority section of a negative answer, handling + * NS and SOA records. (Note that this function does *not* handle + * DNSSEC records; those are addressed separately in + * rctx_authority_dnssec() below.) + */ +static isc_result_t +rctx_authority_negative(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_section_t section; + dns_rdataset_t *rdataset = NULL; + bool finished = false; + + if (rctx->ns_in_answer) { + INSIST(fctx->type == dns_rdatatype_ns); + section = DNS_SECTION_ANSWER; + } else { + section = DNS_SECTION_AUTHORITY; + } + + result = dns_message_firstname(rctx->query->rmessage, section); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, section, &name); + result = dns_message_nextname(rctx->query->rmessage, section); + if (result != ISC_R_SUCCESS) { + finished = true; + } + + if (!dns_name_issubdomain(name, &fctx->domain)) { + continue; + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_rdatatype_t type = rdataset->type; + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + if (((type == dns_rdatatype_ns || + type == dns_rdatatype_soa) && + !dns_name_issubdomain(&fctx->name, name))) + { + char qbuf[DNS_NAME_FORMATSIZE]; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(type, tbuf, sizeof(tbuf)); + dns_name_format(name, nbuf, sizeof(nbuf)); + dns_name_format(&fctx->name, qbuf, + sizeof(qbuf)); + log_formerr(fctx, + "unrelated %s %s in " + "%s authority section", + tbuf, nbuf, qbuf); + break; + } + + switch (type) { + case dns_rdatatype_ns: + /* + * NS or RRSIG NS. + * + * Only one set of NS RRs is allowed. + */ + if (rdataset->type == dns_rdatatype_ns) { + if (rctx->ns_name != NULL && + name != rctx->ns_name) + { + log_formerr(fctx, "multiple NS " + "RRsets " + "in " + "authority " + "section"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; + } + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = dns_trust_glue; + break; + case dns_rdatatype_soa: + /* + * SOA, or RRSIG SOA. + * + * Only one SOA is allowed. + */ + if (rdataset->type == dns_rdatatype_soa) { + if (rctx->soa_name != NULL && + name != rctx->soa_name) + { + log_formerr(fctx, "multiple " + "SOA RRs " + "in " + "authority " + "section"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->soa_name = name; + } + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= DNS_RDATASETATTR_NCACHE; + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; + } + } + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_ncache(): + * Cache the negatively cacheable parts of the message. This may + * also cause work to be queued to the DNSSEC validator. + */ +static void +rctx_ncache(respctx_t *rctx) { + isc_result_t result; + dns_rdatatype_t covers; + fetchctx_t *fctx = rctx->fctx; + + if (!WANTNCACHE(fctx)) { + return; + } + + /* + * Cache DS NXDOMAIN separately to other types. + */ + if (rctx->query->rmessage->rcode == dns_rcode_nxdomain && + fctx->type != dns_rdatatype_ds) + { + covers = dns_rdatatype_any; + } else { + covers = fctx->type; + } + + /* + * Cache any negative cache entries in the message. + */ + result = ncache_message(fctx, rctx->query->rmessage, + rctx->query->addrinfo, covers, rctx->now); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("ncache_message complete", result); + } +} + +/* + * rctx_authority_dnssec(): + * + * Scan the authority section of a negative answer or referral, + * handling DNSSEC records (i.e. NSEC, NSEC3, DS). + */ +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_rdataset_t *rdataset = NULL; + bool finished = false; + + REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer); + + result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) { + finished = true; + } + + if (!dns_name_issubdomain(name, &fctx->domain)) { + /* + * Invalid name found; preserve it for logging + * later. + */ + rctx->found_name = name; + rctx->found_type = ISC_LIST_HEAD(name->list)->type; + continue; + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + bool checknta = true; + bool secure_domain = false; + dns_rdatatype_t type = rdataset->type; + + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + + switch (type) { + case dns_rdatatype_nsec: + case dns_rdatatype_nsec3: + if (rctx->negative) { + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= + DNS_RDATASETATTR_NCACHE; + } else if (type == dns_rdatatype_nsec) { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + } + + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + /* + * No additional data needs to be marked. + */ + break; + case dns_rdatatype_ds: + /* + * DS or SIG DS. + * + * These should only be here if this is a + * referral, and there should only be one + * DS RRset. + */ + if (rctx->ns_name == NULL) { + log_formerr(fctx, "DS with no " + "referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (rdataset->type == dns_rdatatype_ds) { + if (rctx->ds_name != NULL && + name != rctx->ds_name) + { + log_formerr(fctx, "DS doesn't " + "match " + "referral " + "(NS)"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ds_name = name; + } + + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + checknta = false; + } + if (fctx->res->view->enablevalidation) { + result = issecuredomain( + fctx->res->view, name, + dns_rdatatype_ds, fctx->now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (secure_domain) { + rdataset->trust = + dns_trust_pending_answer; + } else if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; + } + } + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_referral(): + * Handles referral responses. Check for sanity, find glue as needed, + * and update the fetch context to follow the delegation. + */ +static isc_result_t +rctx_referral(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + if (rctx->negative || rctx->ns_name == NULL) { + return (ISC_R_SUCCESS); + } + + /* + * We already know ns_name is a subdomain of fctx->domain. + * If ns_name is equal to fctx->domain, we're not making + * progress. We return DNS_R_FORMERR so that we'll keep + * trying other servers. + */ + if (dns_name_equal(rctx->ns_name, &fctx->domain)) { + log_formerr(fctx, "non-improving referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + /* + * If the referral name is not a parent of the query + * name, consider the responder insane. + */ + if (!dns_name_issubdomain(&fctx->name, rctx->ns_name)) { + /* Logged twice */ + log_formerr(fctx, "referral to non-parent"); + FCTXTRACE("referral to non-parent"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + /* + * Mark any additional data related to this rdataset. + * It's important that we do this before we change the + * query domain. + */ + INSIST(rctx->ns_rdataset != NULL); + FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING); + (void)dns_rdataset_additionaldata(rctx->ns_rdataset, check_related, + rctx); +#if CHECK_FOR_GLUE_IN_ANSWER + /* + * Look in the answer section for "glue" that is incorrectly + * returned as a answer. This is needed if the server also + * minimizes the response size by not adding records to the + * additional section that are in the answer section or if + * the record gets dropped due to message size constraints. + */ + if (rctx->glue_in_answer && + (fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a)) + { + (void)dns_rdataset_additionaldata(rctx->ns_rdataset, + check_answer, fctx); + } +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING); + + /* + * NS rdatasets with 0 TTL cause problems. + * dns_view_findzonecut() will not find them when we + * try to follow the referral, and we'll SERVFAIL + * because the best nameservers are now above QDOMAIN. + * We force the TTL to 1 second to prevent this. + */ + if (rctx->ns_rdataset->ttl == 0) { + rctx->ns_rdataset->ttl = 1; + } + + /* + * Set the current query domain to the referral name. + * + * XXXRTH We should check if we're in forward-only mode, and + * if so we should bail out. + */ + INSIST(dns_name_countlabels(&fctx->domain) > 0); + fcount_decr(fctx); + + dns_name_free(&fctx->domain, fctx->mctx); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + dns_name_init(&fctx->domain, NULL); + dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->domain); + + if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) { + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->qmindcname); + + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); + } + } + + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); + } + + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE); + fctx->ns_ttl_ok = false; + log_ns_ttl(fctx, "DELEGATION"); + rctx->result = DNS_R_DELEGATION; + + /* + * Reinitialize 'rctx' to prepare for following the delegation: + * set the get_nameservers and next_server flags appropriately and + * reset the fetch context counters. + * + */ + if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + rctx->get_nameservers = true; + rctx->next_server = true; + rctx->fctx->restarts = 0; + rctx->fctx->referrals++; + rctx->fctx->querysent = 0; + rctx->fctx->lamecount = 0; + rctx->fctx->quotacount = 0; + rctx->fctx->neterr = 0; + rctx->fctx->badresp = 0; + rctx->fctx->adberr = 0; + } + + return (ISC_R_COMPLETE); +} + +/* + * rctx_additional(): + * Scan the additional section of a response to find records related + * to answers we were interested in. + */ +static void +rctx_additional(respctx_t *rctx) { + bool rescan; + dns_section_t section = DNS_SECTION_ADDITIONAL; + isc_result_t result; + +again: + rescan = false; + + for (result = dns_message_firstname(rctx->query->rmessage, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, section)) + { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_ADDITIONAL, &name); + if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) { + continue; + } + name->attributes &= ~DNS_NAMEATTR_CHASE; + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (CHASE(rdataset)) { + rdataset->attributes &= ~DNS_RDATASETATTR_CHASE; + (void)dns_rdataset_additionaldata( + rdataset, check_related, rctx); + rescan = true; + } + } + } + if (rescan) { + goto again; + } +} + +/* + * rctx_nextserver(): + * We found something wrong with the remote server, but it may be + * useful to try another one. + */ +static void +rctx_nextserver(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + + if (result == DNS_R_FORMERR) { + rctx->broken_server = DNS_R_FORMERR; + } + if (rctx->broken_server != ISC_R_SUCCESS) { + /* + * Add this server to the list of bad servers for + * this fctx. + */ + add_bad(fctx, message, addrinfo, rctx->broken_server, + rctx->broken_type); + } + + if (rctx->get_nameservers) { + dns_fixedname_t foundname, founddc; + dns_name_t *name, *fname, *dcname; + unsigned int findoptions = 0; + + fname = dns_fixedname_initname(&foundname); + dcname = dns_fixedname_initname(&founddc); + + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) { + name = &fctx->name; + } else { + name = &fctx->domain; + } + result = dns_view_findzonecut( + fctx->res->view, name, fname, dcname, fctx->now, + findoptions, true, true, &fctx->nameservers, NULL); + if (result != ISC_R_SUCCESS) { + FCTXTRACE("couldn't find a zonecut"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + if (!dns_name_issubdomain(fname, &fctx->domain)) { + /* + * The best nameservers are now above our QDOMAIN. + */ + FCTXTRACE("nameservers now above QDOMAIN"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + fcount_decr(fctx); + + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(fname, fctx->mctx, &fctx->domain); + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname); + + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + fctx_cancelqueries(fctx, true, false); + fctx_cleanupall(fctx); + } + + /* + * Try again. + */ + fctx_try(fctx, !rctx->get_nameservers, false); +} + +/* + * rctx_resend(): + * + * Resend the query, probably with the options changed. Calls fctx_query(), + * passing rctx->retryopts (which is based on query->options, but may have been + * updated since the last time fctx_query() was called). + */ +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + bool bucket_empty; + dns_resolver_t *res = fctx->res; + unsigned int bucketnum; + + FCTXTRACE("resend"); + inc_stats(fctx->res, dns_resstatscounter_retry); + fctx_increference(fctx); + result = fctx_query(fctx, addrinfo, rctx->retryopts); + if (result == ISC_R_SUCCESS) { + return; + } + + bucketnum = fctx->bucketnum; + fctx_done(fctx, result, __LINE__); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +/* + * rctx_next(): + * We got what appeared to be a response but it didn't match the question + * or the cookie; it may have been meant for someone else, or it may be a + * spoofing attack. Drop it and continue listening for the response we + * wanted. + */ +static void +rctx_next(respctx_t *rctx) { +#ifdef WANT_QUERYTRACE + fetchctx_t *fctx = rctx->fctx; +#endif /* ifdef WANT_QUERYTRACE */ + isc_result_t result; + + FCTXTRACE("nextitem"); + inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem); + INSIST(rctx->query->dispentry != NULL); + dns_message_reset(rctx->query->rmessage, DNS_MESSAGE_INTENTPARSE); + result = dns_dispatch_getnext(rctx->query->dispentry, &rctx->devent); + if (result != ISC_R_SUCCESS) { + fctx_done(rctx->fctx, result, __LINE__); + } +} + +/* + * rctx_chaseds(): + * Look up the parent zone's NS records so that DS records can be fetched. + */ +static void +rctx_chaseds(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + unsigned int n; + + add_bad(fctx, message, addrinfo, result, rctx->broken_type); + fctx_cancelqueries(fctx, true, false); + fctx_cleanupfinds(fctx); + fctx_cleanupforwaddrs(fctx); + + n = dns_name_countlabels(&fctx->name); + dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname); + + FCTXTRACE("suspending DS lookup to find parent's NS records"); + + result = dns_resolver_createfetch( + fctx->res, &fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL, + NULL, 0, fctx->options, 0, NULL, rctx->task, resume_dslookup, + fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_done(fctx, result, __LINE__); + } else { + fctx_increference(fctx); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } + } +} + +/* + * rctx_done(): + * This resolver query response is finished, either because we encountered + * a problem or because we've gotten all the information from it that we + * can. We either wait for another response, resend the query to the + * same server, resend to a new server, or clean up and shut down the fetch. + */ +static void +rctx_done(respctx_t *rctx, isc_result_t result) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_adbaddrinfo_t *addrinfo = query->addrinfo; + /* + * Need to attach to the message until the scope + * of this function ends, since there are many places + * where te message is used and/or may be destroyed + * before this function ends. + */ + dns_message_t *message = NULL; + dns_message_attach(query->rmessage, &message); + + FCTXTRACE4("query canceled in response(); ", + rctx->no_response ? "no response" : "responding", result); + + /* + * Cancel the query. + * + * XXXRTH Don't cancel the query if waiting for validation? + */ + if (!rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, false); + } + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver && + (rctx->next_server || rctx->resend || rctx->nextitem)) + { + if (rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, false); + } + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } else +#endif /* ifdef ENABLE_AFL */ + if (rctx->next_server) { + rctx_nextserver(rctx, message, addrinfo, result); + } else if (rctx->resend) { + rctx_resend(rctx, addrinfo); + } else if (rctx->nextitem) { + rctx_next(rctx); + } else if (result == DNS_R_CHASEDSSERVERS) { + rctx_chaseds(rctx, message, addrinfo, result); + } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) { + /* + * All has gone well so far, but we are waiting for the + * DNSSEC validator to validate the answer. + */ + FCTXTRACE("wait for validator"); + fctx_cancelqueries(fctx, true, false); + /* + * We must not retransmit while the validator is + * working; it has references to the current rmessage. + */ + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } + } else { + /* + * We're done. + */ + fctx_done(fctx, result, __LINE__); + } + + dns_message_detach(&message); +} + +/* + * rctx_logpacket(): + * Log the incoming packet; also log to DNSTAP if configured. + */ +static void +rctx_logpacket(respctx_t *rctx) { +#ifdef HAVE_DNSTAP + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + isc_socket_t *sock = NULL; + isc_sockaddr_t localaddr, *la = NULL; + unsigned char zone[DNS_NAME_MAXWIRE]; + dns_dtmsgtype_t dtmsgtype; + dns_compress_t cctx; + isc_region_t zr; + isc_buffer_t zb; +#endif /* HAVE_DNSTAP */ + + dns_message_logfmtpacket( + rctx->query->rmessage, "received packet from", + &rctx->query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS, &dns_master_style_comment, + ISC_LOG_DEBUG(10), rctx->fctx->res->mctx); + +#ifdef HAVE_DNSTAP + /* + * Log the response via dnstap. + */ + memset(&zr, 0, sizeof(zr)); + result = dns_compress_init(&cctx, -1, fctx->res->mctx); + if (result == ISC_R_SUCCESS) { + isc_buffer_init(&zb, zone, sizeof(zone)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(&fctx->domain, &cctx, &zb); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(&zb, &zr); + } + dns_compress_invalidate(&cctx); + } + + if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + dtmsgtype = DNS_DTTYPE_FR; + } else { + dtmsgtype = DNS_DTTYPE_RR; + } + + sock = query2sock(rctx->query); + + if (sock != NULL) { + result = isc_socket_getsockname(sock, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, + &rctx->query->addrinfo->sockaddr, + ((rctx->query->options & DNS_FETCHOPT_TCP) != 0), &zr, + &rctx->query->start, NULL, &rctx->devent->buffer); +#endif /* HAVE_DNSTAP */ +} + +/* + * rctx_badserver(): + * Is the remote server broken, or does it dislike us? + */ +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + isc_buffer_t b; + char code[64]; + dns_rcode_t rcode = rctx->query->rmessage->rcode; + + if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain || + rcode == dns_rcode_nxdomain) + { + return (ISC_R_SUCCESS); + } + + if ((rcode == dns_rcode_formerr) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * It's very likely they don't like EDNS0. + * If the response code is SERVFAIL, also check if the + * response contains an OPT RR and don't cache the + * failure since it can be returned for various other + * reasons. + * + * XXXRTH We should check if the question + * we're asking requires EDNS0, and + * if so, we should bail out. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + /* + * Remember that they may not like EDNS0. + */ + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else if (rcode == dns_rcode_formerr) { + if (ISFORWARDER(query->addrinfo)) { + /* + * This forwarder doesn't understand us, + * but other forwarders might. Keep trying. + */ + rctx->broken_server = DNS_R_REMOTEFORMERR; + rctx->next_server = true; + } else { + /* + * The server doesn't understand us. Since + * all servers for a zone need similar + * capabilities, we assume that we will get + * FORMERR from all servers, and thus we + * cannot make any more progress with this + * fetch. + */ + log_formerr(fctx, "server sent FORMERR"); + result = DNS_R_FORMERR; + } + } else if (rcode == dns_rcode_badvers) { + unsigned int version; +#if DNS_EDNS_VERSION > 0 + unsigned int flags, mask; +#endif /* if DNS_EDNS_VERSION > 0 */ + + INSIST(rctx->opt != NULL); + version = (rctx->opt->ttl >> 16) & 0xff; +#if DNS_EDNS_VERSION > 0 + flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) | + DNS_FETCHOPT_EDNSVERSIONSET; + mask = DNS_FETCHOPT_EDNSVERSIONMASK | + DNS_FETCHOPT_EDNSVERSIONSET; +#endif /* if DNS_EDNS_VERSION > 0 */ + + /* + * Record that we got a good EDNS response. + */ + if (query->ednsversion > (int)version && + !EDNSOK(query->addrinfo)) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, + FCTX_ADDRINFO_EDNSOK); + } + + /* + * RFC 2671 was not clear that unknown options should + * be ignored. RFC 6891 is clear that that they + * should be ignored. If we are supporting the + * experimental EDNS > 0 then perform strict + * version checking of badvers responses. We won't + * be sending COOKIE etc. in that case. + */ +#if DNS_EDNS_VERSION > 0 + if ((int)version < query->ednsversion) { + dns_adb_changeflags(fctx->adb, query->addrinfo, flags, + mask); + rctx->resend = true; + } else { + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = true; + } +#else /* if DNS_EDNS_VERSION > 0 */ + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = true; +#endif /* if DNS_EDNS_VERSION > 0 */ + } else if (rcode == dns_rcode_badcookie && rctx->query->rmessage->cc_ok) + { + /* + * We have recorded the new cookie. + */ + if (BADCOOKIE(query->addrinfo)) { + rctx->retryopts |= DNS_FETCHOPT_TCP; + } + query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE; + rctx->resend = true; + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = true; + } + + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + FCTXTRACE2("remote server broken: returned ", code); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_lameserver(): + * Is the server lame? + */ +static isc_result_t +rctx_lameserver(respctx_t *rctx) { + isc_result_t result = ISC_R_SUCCESS; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (ISFORWARDER(query->addrinfo) || !is_lame(fctx, query->rmessage)) { + return (ISC_R_SUCCESS); + } + + inc_stats(fctx->res, dns_resstatscounter_lame); + log_lame(fctx, query->addrinfo); + if (fctx->res->lame_ttl != 0) { + result = dns_adb_marklame(fctx->adb, query->addrinfo, + &fctx->name, fctx->type, + rctx->now + fctx->res->lame_ttl); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, + "could not mark server as lame: %s", + isc_result_totext(result)); + } + } + rctx->broken_server = DNS_R_LAME; + rctx->next_server = true; + FCTXTRACE("lame server"); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_delonly_zone(): + * Handle delegation-only zones like NET and COM. + */ +static void +rctx_delonly_zone(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + + if (ISFORWARDER(rctx->query->addrinfo) || + !dns_view_isdelegationonly(fctx->res->view, &fctx->domain) || + dns_name_equal(&fctx->domain, &fctx->name) || + !fix_mustbedelegationornxdomain(rctx->query->rmessage, fctx)) + { + return; + } + + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); + isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "enforced delegation-only for '%s' (%s/%s/%s) from %s", + domainbuf, namebuf, typebuf, classbuf, addrbuf); +} + +/*** + *** Resolver Methods + ***/ +static void +destroy(dns_resolver_t *res) { + unsigned int i; + alternate_t *a; + + isc_refcount_destroy(&res->references); + REQUIRE(!atomic_load_acquire(&res->priming)); + REQUIRE(res->primefetch == NULL); + + RTRACE("destroy"); + + REQUIRE(atomic_load_acquire(&res->nfctx) == 0); + + isc_mutex_destroy(&res->primelock); + isc_mutex_destroy(&res->lock); + for (i = 0; i < res->nbuckets; i++) { + INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs)); + isc_task_shutdown(res->buckets[i].task); + isc_task_detach(&res->buckets[i].task); + isc_mutex_destroy(&res->buckets[i].lock); + isc_mem_detach(&res->buckets[i].mctx); + } + isc_mem_put(res->mctx, res->buckets, + res->nbuckets * sizeof(fctxbucket_t)); + for (i = 0; i < RES_DOMAIN_BUCKETS; i++) { + INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list)); + isc_mem_detach(&res->dbuckets[i].mctx); + isc_mutex_destroy(&res->dbuckets[i].lock); + } + isc_mem_put(res->mctx, res->dbuckets, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + if (res->dispatches4 != NULL) { + dns_dispatchset_destroy(&res->dispatches4); + } + if (res->dispatches6 != NULL) { + dns_dispatchset_destroy(&res->dispatches6); + } + while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) { + ISC_LIST_UNLINK(res->alternates, a, link); + if (!a->isaddress) { + dns_name_free(&a->_u._n.name, res->mctx); + } + isc_mem_put(res->mctx, a, sizeof(*a)); + } + dns_resolver_reset_algorithms(res); + dns_resolver_reset_ds_digests(res); + dns_badcache_destroy(&res->badcache); + dns_resolver_resetmustbesecure(res); +#if USE_ALGLOCK + isc_rwlock_destroy(&res->alglock); +#endif /* if USE_ALGLOCK */ +#if USE_MBSLOCK + isc_rwlock_destroy(&res->mbslock); +#endif /* if USE_MBSLOCK */ + isc_timer_destroy(&res->spillattimer); + res->magic = 0; + isc_mem_put(res->mctx, res, sizeof(*res)); +} + +static void +send_shutdown_events(dns_resolver_t *res) { + isc_event_t *event, *next_event; + isc_task_t *etask; + + /* + * Caller must be holding the resolver lock. + */ + + for (event = ISC_LIST_HEAD(res->whenshutdown); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(res->whenshutdown, event, ev_link); + etask = event->ev_sender; + event->ev_sender = res; + isc_task_sendanddetach(&etask, &event); + } +} + +static void +empty_bucket(dns_resolver_t *res) { + RTRACE("empty_bucket"); + + LOCK(&res->lock); + + INSIST(res->activebuckets > 0); + res->activebuckets--; + if (res->activebuckets == 0) { + send_shutdown_events(res); + } + + UNLOCK(&res->lock); +} + +static void +spillattimer_countdown(isc_task_t *task, isc_event_t *event) { + dns_resolver_t *res = event->ev_arg; + isc_result_t result; + unsigned int count; + bool logit = false; + + REQUIRE(VALID_RESOLVER(res)); + + UNUSED(task); + + LOCK(&res->lock); + INSIST(!atomic_load_acquire(&res->exiting)); + if (res->spillat > res->spillatmin) { + res->spillat--; + logit = true; + } + if (res->spillat <= res->spillatmin) { + result = isc_timer_reset(res->spillattimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + count = res->spillat; + UNLOCK(&res->lock); + if (logit) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "clients-per-query decreased to %u", count); + } + + isc_event_free(&event); +} + +isc_result_t +dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, + isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr, + unsigned int options, dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, + dns_resolver_t **resp) { + dns_resolver_t *res; + isc_result_t result = ISC_R_SUCCESS; + unsigned int i, buckets_created = 0, dbuckets_created = 0; + isc_task_t *task = NULL; + char name[16]; + unsigned dispattr; + + /* + * Create a resolver. + */ + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ntasks > 0); + REQUIRE(ndisp > 0); + REQUIRE(resp != NULL && *resp == NULL); + REQUIRE(dispatchmgr != NULL); + REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL); + + res = isc_mem_get(view->mctx, sizeof(*res)); + RTRACE("create"); + res->mctx = view->mctx; + res->rdclass = view->rdclass; + res->socketmgr = socketmgr; + res->timermgr = timermgr; + res->taskmgr = taskmgr; + res->dispatchmgr = dispatchmgr; + res->view = view; + res->options = options; + res->lame_ttl = 0; + ISC_LIST_INIT(res->alternates); + res->udpsize = RECV_BUFFER_SIZE; + res->algorithms = NULL; + res->digests = NULL; + res->badcache = NULL; + result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE, + &res->badcache); + if (result != ISC_R_SUCCESS) { + goto cleanup_res; + } + res->mustbesecure = NULL; + res->spillatmin = res->spillat = 10; + res->spillatmax = 100; + res->spillattimer = NULL; + atomic_init(&res->zspill, 0); + res->zero_no_soa_ttl = false; + res->retryinterval = 30000; + res->nonbackofftries = 3; + res->query_timeout = DEFAULT_QUERY_TIMEOUT; + res->maxdepth = DEFAULT_RECURSION_DEPTH; + res->maxqueries = DEFAULT_MAX_QUERIES; + res->quotaresp[dns_quotatype_zone] = DNS_R_DROP; + res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL; + res->nbuckets = ntasks; + if (view->resstats != NULL) { + isc_stats_set(view->resstats, ntasks, + dns_resstatscounter_buckets); + } + res->activebuckets = ntasks; + res->buckets = isc_mem_get(view->mctx, ntasks * sizeof(fctxbucket_t)); + for (i = 0; i < ntasks; i++) { + isc_mutex_init(&res->buckets[i].lock); + + res->buckets[i].task = NULL; + /* + * Since we have a pool of tasks we bind them to task queues + * to spread the load evenly + */ + result = isc_task_create_bound(taskmgr, 0, + &res->buckets[i].task, i); + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&res->buckets[i].lock); + goto cleanup_buckets; + } + res->buckets[i].mctx = NULL; + snprintf(name, sizeof(name), "res%u", i); + /* + * Use a separate memory context for each bucket to reduce + * contention among multiple threads. Do this only when + * enabling threads because it will be require more memory. + */ + isc_mem_create(&res->buckets[i].mctx); + isc_mem_setname(res->buckets[i].mctx, name, NULL); + isc_task_setname(res->buckets[i].task, name, res); + ISC_LIST_INIT(res->buckets[i].fctxs); + atomic_init(&res->buckets[i].exiting, false); + buckets_created++; + } + + res->dbuckets = isc_mem_get(view->mctx, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + for (i = 0; i < RES_DOMAIN_BUCKETS; i++) { + ISC_LIST_INIT(res->dbuckets[i].list); + res->dbuckets[i].mctx = NULL; + isc_mem_attach(view->mctx, &res->dbuckets[i].mctx); + isc_mutex_init(&res->dbuckets[i].lock); + dbuckets_created++; + } + + res->dispatches4 = NULL; + if (dispatchv4 != NULL) { + dns_dispatchset_create(view->mctx, socketmgr, taskmgr, + dispatchv4, &res->dispatches4, ndisp); + dispattr = dns_dispatch_getattributes(dispatchv4); + res->exclusivev4 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE); + } + + res->dispatches6 = NULL; + if (dispatchv6 != NULL) { + dns_dispatchset_create(view->mctx, socketmgr, taskmgr, + dispatchv6, &res->dispatches6, ndisp); + dispattr = dns_dispatch_getattributes(dispatchv6); + res->exclusivev6 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE); + } + + res->querydscp4 = -1; + res->querydscp6 = -1; + isc_refcount_init(&res->references, 1); + atomic_init(&res->exiting, false); + res->frozen = false; + ISC_LIST_INIT(res->whenshutdown); + atomic_init(&res->priming, false); + res->primefetch = NULL; + + atomic_init(&res->nfctx, 0); + + isc_mutex_init(&res->lock); + isc_mutex_init(&res->primelock); + + task = NULL; + result = isc_task_create(taskmgr, 0, &task); + if (result != ISC_R_SUCCESS) { + goto cleanup_primelock; + } + isc_task_setname(task, "resolver_task", NULL); + + result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, + task, spillattimer_countdown, res, + &res->spillattimer); + isc_task_detach(&task); + if (result != ISC_R_SUCCESS) { + goto cleanup_primelock; + } + +#if USE_ALGLOCK + isc_rwlock_init(&res->alglock, 0, 0); +#endif /* if USE_ALGLOCK */ +#if USE_MBSLOCK + isc_rwlock_init(&res->mbslock, 0, 0); +#endif /* if USE_MBSLOCK */ + + res->magic = RES_MAGIC; + + *resp = res; + + return (ISC_R_SUCCESS); + +cleanup_primelock: + isc_mutex_destroy(&res->primelock); + isc_mutex_destroy(&res->lock); + + if (res->dispatches6 != NULL) { + dns_dispatchset_destroy(&res->dispatches6); + } + if (res->dispatches4 != NULL) { + dns_dispatchset_destroy(&res->dispatches4); + } + + for (i = 0; i < dbuckets_created; i++) { + isc_mutex_destroy(&res->dbuckets[i].lock); + isc_mem_detach(&res->dbuckets[i].mctx); + } + isc_mem_put(view->mctx, res->dbuckets, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + +cleanup_buckets: + for (i = 0; i < buckets_created; i++) { + isc_mem_detach(&res->buckets[i].mctx); + isc_mutex_destroy(&res->buckets[i].lock); + isc_task_shutdown(res->buckets[i].task); + isc_task_detach(&res->buckets[i].task); + } + isc_mem_put(view->mctx, res->buckets, + res->nbuckets * sizeof(fctxbucket_t)); + + dns_badcache_destroy(&res->badcache); + +cleanup_res: + isc_mem_put(view->mctx, res, sizeof(*res)); + + return (result); +} + +static void +prime_done(isc_task_t *task, isc_event_t *event) { + dns_resolver_t *res; + dns_fetchevent_t *fevent; + dns_fetch_t *fetch; + dns_db_t *db = NULL; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + res = event->ev_arg; + REQUIRE(VALID_RESOLVER(res)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "resolver priming query complete"); + + UNUSED(task); + + LOCK(&res->primelock); + fetch = res->primefetch; + res->primefetch = NULL; + UNLOCK(&res->primelock); + + INSIST(atomic_compare_exchange_strong_acq_rel(&res->priming, + &(bool){ true }, false)); + + if (fevent->result == ISC_R_SUCCESS && res->view->cache != NULL && + res->view->hints != NULL) + { + dns_cache_attachdb(res->view->cache, &db); + dns_root_checkhints(res->view, res->view->hints, db); + dns_db_detach(&db); + } + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + INSIST(fevent->sigrdataset == NULL); + + isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset)); + + isc_event_free(&event); + dns_resolver_destroyfetch(&fetch); +} + +void +dns_resolver_prime(dns_resolver_t *res) { + bool want_priming = false; + dns_rdataset_t *rdataset; + isc_result_t result; + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(res->frozen); + + RTRACE("dns_resolver_prime"); + + if (!atomic_load_acquire(&res->exiting)) { + want_priming = atomic_compare_exchange_strong_acq_rel( + &res->priming, &(bool){ false }, true); + } + + if (want_priming) { + /* + * To avoid any possible recursive locking problems, we + * start the priming fetch like any other fetch, and holding + * no resolver locks. No one else will try to start it + * because we're the ones who set res->priming to true. + * Any other callers of dns_resolver_prime() while we're + * running will see that res->priming is already true and + * do nothing. + */ + RTRACE("priming"); + rdataset = isc_mem_get(res->mctx, sizeof(*rdataset)); + dns_rdataset_init(rdataset); + + LOCK(&res->primelock); + INSIST(res->primefetch == NULL); + result = dns_resolver_createfetch( + res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL, + NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, + res->buckets[0].task, prime_done, res, rdataset, NULL, + &res->primefetch); + UNLOCK(&res->primelock); + + if (result != ISC_R_SUCCESS) { + isc_mem_put(res->mctx, rdataset, sizeof(*rdataset)); + INSIST(atomic_compare_exchange_strong_acq_rel( + &res->priming, &(bool){ true }, false)); + } + inc_stats(res, dns_resstatscounter_priming); + } +} + +void +dns_resolver_freeze(dns_resolver_t *res) { + /* + * Freeze resolver. + */ + + REQUIRE(VALID_RESOLVER(res)); + + res->frozen = true; +} + +void +dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) { + REQUIRE(VALID_RESOLVER(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + RRTRACE(source, "attach"); + + LOCK(&source->lock); + REQUIRE(!atomic_load_acquire(&source->exiting)); + isc_refcount_increment(&source->references); + UNLOCK(&source->lock); + + *targetp = source; +} + +void +dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task, + isc_event_t **eventp) { + isc_task_t *tclone; + isc_event_t *event; + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(eventp != NULL); + + event = *eventp; + *eventp = NULL; + + LOCK(&res->lock); + + if (atomic_load_acquire(&res->exiting) && res->activebuckets == 0) { + /* + * We're already shutdown. Send the event. + */ + event->ev_sender = res; + isc_task_send(task, &event); + } else { + tclone = NULL; + isc_task_attach(task, &tclone); + event->ev_sender = tclone; + ISC_LIST_APPEND(res->whenshutdown, event, ev_link); + } + + UNLOCK(&res->lock); +} + +void +dns_resolver_shutdown(dns_resolver_t *res) { + unsigned int i; + fetchctx_t *fctx; + isc_result_t result; + bool is_false = false; + + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("shutdown"); + + LOCK(&res->lock); + if (atomic_compare_exchange_strong(&res->exiting, &is_false, true)) { + RTRACE("exiting"); + + for (i = 0; i < res->nbuckets; i++) { + LOCK(&res->buckets[i].lock); + for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs); + fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) + { + fctx_shutdown(fctx); + } + if (res->dispatches4 != NULL && !res->exclusivev4) { + dns_dispatchset_cancelall(res->dispatches4, + res->buckets[i].task); + } + if (res->dispatches6 != NULL && !res->exclusivev6) { + dns_dispatchset_cancelall(res->dispatches6, + res->buckets[i].task); + } + atomic_store(&res->buckets[i].exiting, true); + if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) { + INSIST(res->activebuckets > 0); + res->activebuckets--; + } + UNLOCK(&res->buckets[i].lock); + } + if (res->activebuckets == 0) { + send_shutdown_events(res); + } + result = isc_timer_reset(res->spillattimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + UNLOCK(&res->lock); +} + +void +dns_resolver_detach(dns_resolver_t **resp) { + dns_resolver_t *res; + + REQUIRE(resp != NULL); + res = *resp; + *resp = NULL; + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("detach"); + + if (isc_refcount_decrement(&res->references) == 1) { + LOCK(&res->lock); + INSIST(atomic_load_acquire(&res->exiting)); + INSIST(res->activebuckets == 0); + UNLOCK(&res->lock); + destroy(res); + } +} + +static bool +fctx_match(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type, + unsigned int options) { + /* + * Don't match fetch contexts that are shutting down. + */ + if (fctx->cloned || fctx->state == fetchstate_done || + ISC_LIST_EMPTY(fctx->events)) + { + return (false); + } + + if (fctx->type != type || fctx->options != options) { + return (false); + } + return (dns_name_equal(&fctx->name, name)); +} + +static void +log_fetch(const dns_name_t *name, dns_rdatatype_t type) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + int level = ISC_LOG_DEBUG(1); + + /* + * If there's no chance of logging it, don't render (format) the + * name and RDATA type (further below), and return early. + */ + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, "fetch: %s/%s", namebuf, + typebuf); +} + +static isc_result_t +fctx_minimize_qname(fetchctx_t *fctx) { + isc_result_t result = ISC_R_SUCCESS; + unsigned int dlabels, nlabels; + + REQUIRE(VALID_FCTX(fctx)); + + dlabels = dns_name_countlabels(&fctx->qmindcname); + nlabels = dns_name_countlabels(&fctx->name); + dns_name_free(&fctx->qminname, fctx->mctx); + dns_name_init(&fctx->qminname, NULL); + + if (dlabels > fctx->qmin_labels) { + fctx->qmin_labels = dlabels + 1; + } else { + fctx->qmin_labels++; + } + + if (fctx->ip6arpaskip) { + /* + * For ip6.arpa we want to skip some of the labels, with + * boundaries at /16, /32, /48, /56, /64 and /128 + * In 'label count' terms that's equal to + * 7 11 15 17 19 35 + * We fix fctx->qmin_labels to point to the nearest boundary + */ + if (fctx->qmin_labels < 7) { + fctx->qmin_labels = 7; + } else if (fctx->qmin_labels < 11) { + fctx->qmin_labels = 11; + } else if (fctx->qmin_labels < 15) { + fctx->qmin_labels = 15; + } else if (fctx->qmin_labels < 17) { + fctx->qmin_labels = 17; + } else if (fctx->qmin_labels < 19) { + fctx->qmin_labels = 19; + } else if (fctx->qmin_labels < 35) { + fctx->qmin_labels = 35; + } else { + fctx->qmin_labels = nlabels; + } + } else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; + } + + if (fctx->qmin_labels < nlabels) { + /* + * We want to query for qmin_labels from fctx->name + */ + dns_fixedname_t fname; + dns_name_t *name = dns_fixedname_initname(&fname); + dns_name_split(&fctx->name, fctx->qmin_labels, NULL, + dns_fixedname_name(&fname)); + if ((fctx->options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + isc_buffer_t dbuf; + dns_fixedname_t tmpname; + dns_name_t *tname = dns_fixedname_initname(&tmpname); + char ndata[DNS_NAME_MAXWIRE]; + + isc_buffer_init(&dbuf, ndata, DNS_NAME_MAXWIRE); + dns_fixedname_init(&tmpname); + result = dns_name_concatenate(&underscore_name, name, + tname, &dbuf); + if (result == ISC_R_SUCCESS) { + dns_name_dup(tname, fctx->mctx, + &fctx->qminname); + } + fctx->qmintype = dns_rdatatype_a; + } else { + dns_name_dup(dns_fixedname_name(&fname), fctx->mctx, + &fctx->qminname); + fctx->qmintype = dns_rdatatype_ns; + } + fctx->minimized = true; + } else { + /* Minimization is done, we'll ask for whole qname */ + fctx->qmintype = fctx->type; + dns_name_dup(&fctx->name, fctx->mctx, &fctx->qminname); + fctx->minimized = false; + } + + char domainbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&fctx->qminname, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5), + "QNAME minimization - %s minimized, qmintype %d " + "qminname %s", + fctx->minimized ? "" : "not", fctx->qmintype, domainbuf); + + return (result); +} + +isc_result_t +dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain, + dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + const isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, unsigned int depth, + isc_counter_t *qc, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp) { + dns_fetch_t *fetch; + fetchctx_t *fctx = NULL; + isc_result_t result = ISC_R_SUCCESS; + unsigned int bucketnum; + bool new_fctx = false; + isc_event_t *event; + unsigned int count = 0; + unsigned int spillat; + unsigned int spillatmin; + bool dodestroy = false; + + UNUSED(forwarders); + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(res->frozen); + /* XXXRTH Check for meta type */ + if (domain != NULL) { + REQUIRE(DNS_RDATASET_VALID(nameservers)); + REQUIRE(nameservers->type == dns_rdatatype_ns); + } else { + REQUIRE(nameservers == NULL); + } + REQUIRE(forwarders == NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + REQUIRE(sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)); + REQUIRE(fetchp != NULL && *fetchp == NULL); + + log_fetch(name, type); + + /* + * XXXRTH use a mempool? + */ + fetch = isc_mem_get(res->mctx, sizeof(*fetch)); + fetch->mctx = NULL; + isc_mem_attach(res->mctx, &fetch->mctx); + + bucketnum = dns_name_fullhash(name, false) % res->nbuckets; + + LOCK(&res->lock); + spillat = res->spillat; + spillatmin = res->spillatmin; + UNLOCK(&res->lock); + LOCK(&res->buckets[bucketnum].lock); + + if (atomic_load(&res->buckets[bucketnum].exiting)) { + result = ISC_R_SHUTTINGDOWN; + goto unlock; + } + + if ((options & DNS_FETCHOPT_UNSHARED) == 0) { + for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs); + fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) + { + if (fctx_match(fctx, name, type, options)) { + break; + } + } + } + + /* + * Is this a duplicate? + */ + if (fctx != NULL && client != NULL) { + dns_fetchevent_t *fevent; + for (fevent = ISC_LIST_HEAD(fctx->events); fevent != NULL; + fevent = ISC_LIST_NEXT(fevent, ev_link)) + { + if (fevent->client != NULL && fevent->id == id && + isc_sockaddr_equal(fevent->client, client)) + { + result = DNS_R_DUPLICATE; + goto unlock; + } + count++; + } + } + if (count >= spillatmin && spillatmin != 0) { + INSIST(fctx != NULL); + if (count >= spillat) { + fctx->spilled = true; + } + if (fctx->spilled) { + result = DNS_R_DROP; + goto unlock; + } + } + + if (fctx == NULL) { + result = fctx_create(res, name, type, domain, nameservers, + client, id, options, bucketnum, depth, qc, + &fctx); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + new_fctx = true; + } else if (fctx->depth > depth) { + fctx->depth = depth; + } + + result = fctx_join(fctx, task, client, id, action, arg, rdataset, + sigrdataset, fetch); + + if (result == ISC_R_SUCCESS && + ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0)) + { + fctx_add_event(fctx, task, client, id, action, arg, fetch, + DNS_EVENT_TRYSTALE); + } + + if (new_fctx) { + if (result == ISC_R_SUCCESS) { + /* + * Launch this fctx. + */ + event = &fctx->control_event; + ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, + DNS_EVENT_FETCHCONTROL, fctx_start, fctx, + NULL, NULL, NULL); + isc_task_send(res->buckets[bucketnum].task, &event); + } else { + /* + * We don't care about the result of fctx_unlink() + * since we know we're not exiting. + */ + (void)fctx_unlink(fctx); + dodestroy = true; + } + } + +unlock: + UNLOCK(&res->buckets[bucketnum].lock); + + if (dodestroy) { + fctx_destroy(fctx); + } + + if (result == ISC_R_SUCCESS) { + FTRACE("created"); + *fetchp = fetch; + } else { + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + } + + return (result); +} + +void +dns_resolver_cancelfetch(dns_fetch_t *fetch) { + fetchctx_t *fctx; + dns_resolver_t *res; + dns_fetchevent_t *event = NULL; + dns_fetchevent_t *event_trystale = NULL; + dns_fetchevent_t *event_fetchdone = NULL; + + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + FTRACE("cancelfetch"); + + LOCK(&res->buckets[fctx->bucketnum].lock); + + /* + * Find the events for this fetch (as opposed + * to those for other fetches that have joined the same + * fctx) and send them with result = ISC_R_CANCELED. + */ + if (fctx->state != fetchstate_done) { + dns_fetchevent_t *next_event = NULL; + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + if (event->fetch == fetch) { + ISC_LIST_UNLINK(fctx->events, event, ev_link); + switch (event->ev_type) { + case DNS_EVENT_TRYSTALE: + INSIST(event_trystale == NULL); + event_trystale = event; + break; + case DNS_EVENT_FETCHDONE: + INSIST(event_fetchdone == NULL); + event_fetchdone = event; + break; + default: + UNREACHABLE(); + } + if (event_trystale != NULL && + event_fetchdone != NULL) + { + break; + } + } + } + } + /* + * The "trystale" event must be sent before the "fetchdone" event, + * because the latter clears the "recursing" query attribute, which is + * required by both events (handled by the same callback function). + */ + if (event_trystale != NULL) { + isc_task_t *etask = event_trystale->ev_sender; + event_trystale->ev_sender = fctx; + event_trystale->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale)); + } + if (event_fetchdone != NULL) { + isc_task_t *etask = event_fetchdone->ev_sender; + event_fetchdone->ev_sender = fctx; + event_fetchdone->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone)); + } + + /* + * The fctx continues running even if no fetches remain; + * the answer is still cached. + */ + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +void +dns_resolver_destroyfetch(dns_fetch_t **fetchp) { + dns_fetch_t *fetch; + dns_resolver_t *res; + dns_fetchevent_t *event, *next_event; + fetchctx_t *fctx; + unsigned int bucketnum; + bool bucket_empty; + + REQUIRE(fetchp != NULL); + fetch = *fetchp; + *fetchp = NULL; + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + FTRACE("destroyfetch"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + /* + * Sanity check: the caller should have gotten its event before + * trying to destroy the fetch. + */ + event = NULL; + if (fctx->state != fetchstate_done) { + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + RUNTIME_CHECK(event->fetch != fetch); + } + } + + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + + if (bucket_empty) { + empty_bucket(res); + } +} + +void +dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, bool duplicateok) { + fetchctx_t *fctx; + dns_resolver_t *res; + char domainbuf[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + LOCK(&res->buckets[fctx->bucketnum].lock); + + INSIST(fctx->exitline >= 0); + if (!fctx->logged || duplicateok) { + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(lctx, category, module, level, + "fetch completed at %s:%d for %s in " + "%" PRIu64 "." + "%06" PRIu64 ": %s/%s " + "[domain:%s,referral:%u,restart:%u,qrysent:%u," + "timeout:%u,lame:%u,quota:%u,neterr:%u," + "badresp:%u,adberr:%u,findfail:%u,valfail:%u]", + __FILE__, fctx->exitline, fctx->info, + fctx->duration / US_PER_SEC, + fctx->duration % US_PER_SEC, + isc_result_totext(fctx->result), + isc_result_totext(fctx->vresult), domainbuf, + fctx->referrals, fctx->restarts, fctx->querysent, + fctx->timeouts, fctx->lamecount, fctx->quotacount, + fctx->neterr, fctx->badresp, fctx->adberr, + fctx->findfail, fctx->valfail); + fctx->logged = true; + } + + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +dns_dispatchmgr_t * +dns_resolver_dispatchmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->dispatchmgr); +} + +dns_dispatch_t * +dns_resolver_dispatchv4(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (dns_dispatchset_get(resolver->dispatches4)); +} + +dns_dispatch_t * +dns_resolver_dispatchv6(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (dns_dispatchset_get(resolver->dispatches6)); +} + +isc_socketmgr_t * +dns_resolver_socketmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->socketmgr); +} + +isc_taskmgr_t * +dns_resolver_taskmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->taskmgr); +} + +uint32_t +dns_resolver_getlamettl(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->lame_ttl); +} + +void +dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->lame_ttl = lame_ttl; +} + +isc_result_t +dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt, + const dns_name_t *name, in_port_t port) { + alternate_t *a; + + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(!resolver->frozen); + REQUIRE((alt == NULL) ^ (name == NULL)); + + a = isc_mem_get(resolver->mctx, sizeof(*a)); + if (alt != NULL) { + a->isaddress = true; + a->_u.addr = *alt; + } else { + a->isaddress = false; + a->_u._n.port = port; + dns_name_init(&a->_u._n.name, NULL); + dns_name_dup(name, resolver->mctx, &a->_u._n.name); + } + ISC_LINK_INIT(a, link); + ISC_LIST_APPEND(resolver->alternates, a, link); + + return (ISC_R_SUCCESS); +} + +void +dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->udpsize = udpsize; +} + +uint16_t +dns_resolver_getudpsize(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->udpsize); +} + +void +dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name) { + if (name != NULL) { + dns_badcache_flushname(resolver->badcache, name); + } else { + dns_badcache_flush(resolver->badcache); + } +} + +void +dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name) { + dns_badcache_flushtree(resolver->badcache, name); +} + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *expire) { +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) +#endif /* ifdef ENABLE_AFL */ + { + dns_badcache_add(resolver->badcache, name, type, false, 0, + expire); + } +} + +bool +dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *now) { + return (dns_badcache_find(resolver->badcache, name, type, NULL, now)); +} + +void +dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) { + (void)dns_badcache_print(resolver->badcache, "Bad cache", fp); +} + +static void +free_algorithm(void *node, void *arg) { + unsigned char *algorithms = node; + isc_mem_t *mctx = arg; + + isc_mem_put(mctx, algorithms, *algorithms); +} + +void +dns_resolver_reset_algorithms(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->algorithms != NULL) { + dns_rbt_destroy(&resolver->algorithms); + } +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ +} + +isc_result_t +dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int alg) { + unsigned int len, mask; + unsigned char *tmp; + unsigned char *algorithms; + isc_result_t result; + dns_rbtnode_t *node = NULL; + + /* + * Whether an algorithm is disabled (or not) is stored in a + * per-name bitfield that is stored as the node data of an + * RBT. + */ + + REQUIRE(VALID_RESOLVER(resolver)); + if (alg > 255) { + return (ISC_R_RANGE); + } + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->algorithms == NULL) { + result = dns_rbt_create(resolver->mctx, free_algorithm, + resolver->mctx, &resolver->algorithms); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + len = alg / 8 + 2; + mask = 1 << (alg % 8); + + result = dns_rbt_addnode(resolver->algorithms, name, &node); + + if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) { + algorithms = node->data; + /* + * If algorithms is set, algorithms[0] contains its + * length. + */ + if (algorithms == NULL || len > *algorithms) { + /* + * If no bitfield exists in the node data, or if + * it is not long enough, allocate a new + * bitfield and copy the old (smaller) bitfield + * into it if one exists. + */ + tmp = isc_mem_get(resolver->mctx, len); + memset(tmp, 0, len); + if (algorithms != NULL) { + memmove(tmp, algorithms, *algorithms); + } + tmp[len - 1] |= mask; + /* 'tmp[0]' should contain the length of 'tmp'. */ + *tmp = len; + node->data = tmp; + /* Free the older bitfield. */ + if (algorithms != NULL) { + isc_mem_put(resolver->mctx, algorithms, + *algorithms); + } + } else { + algorithms[len - 1] |= mask; + } + } + result = ISC_R_SUCCESS; +cleanup: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + return (result); +} + +bool +dns_resolver_algorithm_supported(dns_resolver_t *resolver, + const dns_name_t *name, unsigned int alg) { + unsigned int len, mask; + unsigned char *algorithms; + void *data = NULL; + isc_result_t result; + bool found = false; + + REQUIRE(VALID_RESOLVER(resolver)); + + /* + * DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1. + */ + if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) { + return (false); + } + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + if (resolver->algorithms == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + len = alg / 8 + 2; + mask = 1 << (alg % 8); + algorithms = data; + if (len <= *algorithms && (algorithms[len - 1] & mask) != 0) { + found = true; + } + } +unlock: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + if (found) { + return (false); + } + + return (dst_algorithm_supported(alg)); +} + +static void +free_digest(void *node, void *arg) { + unsigned char *digests = node; + isc_mem_t *mctx = arg; + + isc_mem_put(mctx, digests, *digests); +} + +void +dns_resolver_reset_ds_digests(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->digests != NULL) { + dns_rbt_destroy(&resolver->digests); + } +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ +} + +isc_result_t +dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int digest_type) { + unsigned int len, mask; + unsigned char *tmp; + unsigned char *digests; + isc_result_t result; + dns_rbtnode_t *node = NULL; + + /* + * Whether a digest is disabled (or not) is stored in a per-name + * bitfield that is stored as the node data of an RBT. + */ + + REQUIRE(VALID_RESOLVER(resolver)); + if (digest_type > 255) { + return (ISC_R_RANGE); + } + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->digests == NULL) { + result = dns_rbt_create(resolver->mctx, free_digest, + resolver->mctx, &resolver->digests); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + len = digest_type / 8 + 2; + mask = 1 << (digest_type % 8); + + result = dns_rbt_addnode(resolver->digests, name, &node); + + if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) { + digests = node->data; + /* If digests is set, digests[0] contains its length. */ + if (digests == NULL || len > *digests) { + /* + * If no bitfield exists in the node data, or if + * it is not long enough, allocate a new + * bitfield and copy the old (smaller) bitfield + * into it if one exists. + */ + tmp = isc_mem_get(resolver->mctx, len); + memset(tmp, 0, len); + if (digests != NULL) { + memmove(tmp, digests, *digests); + } + tmp[len - 1] |= mask; + /* tmp[0] should contain the length of 'tmp'. */ + *tmp = len; + node->data = tmp; + /* Free the older bitfield. */ + if (digests != NULL) { + isc_mem_put(resolver->mctx, digests, *digests); + } + } else { + digests[len - 1] |= mask; + } + } + result = ISC_R_SUCCESS; +cleanup: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + return (result); +} + +bool +dns_resolver_ds_digest_supported(dns_resolver_t *resolver, + const dns_name_t *name, + unsigned int digest_type) { + unsigned int len, mask; + unsigned char *digests; + void *data = NULL; + isc_result_t result; + bool found = false; + + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + if (resolver->digests == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->digests, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + len = digest_type / 8 + 2; + mask = 1 << (digest_type % 8); + digests = data; + if (len <= *digests && (digests[len - 1] & mask) != 0) { + found = true; + } + } +unlock: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + if (found) { + return (false); + } + return (dst_ds_digest_supported(digest_type)); +} + +void +dns_resolver_resetmustbesecure(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + if (resolver->mustbesecure != NULL) { + dns_rbt_destroy(&resolver->mustbesecure); + } +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ +} + +static bool yes = true, no = false; + +isc_result_t +dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name, + bool value) { + isc_result_t result; + + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + if (resolver->mustbesecure == NULL) { + result = dns_rbt_create(resolver->mctx, NULL, NULL, + &resolver->mustbesecure); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = dns_rbt_addname(resolver->mustbesecure, name, + value ? &yes : &no); +cleanup: +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + return (result); +} + +bool +dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) { + void *data = NULL; + bool value = false; + isc_result_t result; + + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_read); +#endif /* if USE_MBSLOCK */ + if (resolver->mustbesecure == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + value = *(bool *)data; + } +unlock: +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_read); +#endif /* if USE_MBSLOCK */ + return (value); +} + +void +dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur, + uint32_t *min, uint32_t *max) { + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + if (cur != NULL) { + *cur = resolver->spillat; + } + if (min != NULL) { + *min = resolver->spillatmin; + } + if (max != NULL) { + *max = resolver->spillatmax; + } + UNLOCK(&resolver->lock); +} + +void +dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min, + uint32_t max) { + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + resolver->spillatmin = resolver->spillat = min; + resolver->spillatmax = max; + UNLOCK(&resolver->lock); +} + +void +dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients) { + REQUIRE(VALID_RESOLVER(resolver)); + + atomic_store_release(&resolver->zspill, clients); +} + +bool +dns_resolver_getzeronosoattl(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->zero_no_soa_ttl); +} + +void +dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->zero_no_soa_ttl = state; +} + +unsigned int +dns_resolver_getoptions(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->options); +} + +unsigned int +dns_resolver_gettimeout(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->query_timeout); +} + +void +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (timeout <= 300) { + timeout *= 1000; + } + + if (timeout == 0) { + timeout = DEFAULT_QUERY_TIMEOUT; + } + if (timeout > MAXIMUM_QUERY_TIMEOUT) { + timeout = MAXIMUM_QUERY_TIMEOUT; + } + if (timeout < MINIMUM_QUERY_TIMEOUT) { + timeout = MINIMUM_QUERY_TIMEOUT; + } + + resolver->query_timeout = timeout; +} + +void +dns_resolver_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->querydscp4 = dscp; +} + +isc_dscp_t +dns_resolver_getquerydscp4(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->querydscp4); +} + +void +dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->querydscp6 = dscp; +} + +isc_dscp_t +dns_resolver_getquerydscp6(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->querydscp6); +} + +void +dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->maxdepth = maxdepth; +} + +unsigned int +dns_resolver_getmaxdepth(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->maxdepth); +} + +void +dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->maxqueries = queries; +} + +unsigned int +dns_resolver_getmaxqueries(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->maxqueries); +} + +void +dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format, + FILE *fp) { + int i; + + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(fp != NULL); + REQUIRE(format == isc_statsformat_file); + + for (i = 0; i < RES_DOMAIN_BUCKETS; i++) { + fctxcount_t *fc; + LOCK(&resolver->dbuckets[i].lock); + for (fc = ISC_LIST_HEAD(resolver->dbuckets[i].list); fc != NULL; + fc = ISC_LIST_NEXT(fc, link)) + { + dns_name_print(fc->domain, fp); + fprintf(fp, ": %u active (%u spilled, %u allowed)\n", + fc->count, fc->dropped, fc->allowed); + } + UNLOCK(&resolver->dbuckets[i].lock); + } +} + +void +dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which, + isc_result_t resp) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server); + REQUIRE(resp == DNS_R_DROP || resp == DNS_R_SERVFAIL); + + resolver->quotaresp[which] = resp; +} + +isc_result_t +dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server); + + return (resolver->quotaresp[which]); +} + +unsigned int +dns_resolver_getretryinterval(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->retryinterval); +} + +void +dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(interval > 0); + + resolver->retryinterval = ISC_MIN(interval, 2000); +} + +unsigned int +dns_resolver_getnonbackofftries(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->nonbackofftries); +} + +void +dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(tries > 0); + + resolver->nonbackofftries = tries; +} |