mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 10:10:06 +00:00
All databases in the codebase follow the same structure: a database is an associative container from DNS names to nodes, and each node is an associative container from RR types to RR data. Each database implementation (qpzone, qpcache, sdlz, builtin, dyndb) has its own corresponding node type (qpznode, qpcnode, etc). However, some code needs to work with nodes generically regardless of their specific type - for example, to acquire locks, manage references, or register/unregister slabs from the heap. Currently, these generic node operations are implemented as methods in the database vtable, which creates problematic coupling between database and node lifetimes. If a node outlives its parent database, the node destructor will destroy all RR data, and each RR data destructor will try to unregister from heaps by calling a virtual function from the database vtable. Since the database was already freed, this causes a crash. This commit breaks the coupling by standardizing the layout of all database nodes, adding a dedicated vtable for node operations, and moving node-specific methods from the database vtable to the node vtable.
10695 lines
271 KiB
C
10695 lines
271 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include <isc/ascii.h>
|
|
#include <isc/async.h>
|
|
#include <isc/atomic.h>
|
|
#include <isc/counter.h>
|
|
#include <isc/hash.h>
|
|
#include <isc/hashmap.h>
|
|
#include <isc/hex.h>
|
|
#include <isc/log.h>
|
|
#include <isc/loop.h>
|
|
#include <isc/mutex.h>
|
|
#include <isc/random.h>
|
|
#include <isc/refcount.h>
|
|
#include <isc/result.h>
|
|
#include <isc/rwlock.h>
|
|
#include <isc/siphash.h>
|
|
#include <isc/stats.h>
|
|
#include <isc/string.h>
|
|
#include <isc/tid.h>
|
|
#include <isc/time.h>
|
|
#include <isc/timer.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/acl.h>
|
|
#include <dns/adb.h>
|
|
#include <dns/cache.h>
|
|
#include <dns/db.h>
|
|
#include <dns/dispatch.h>
|
|
#include <dns/dns64.h>
|
|
#include <dns/dnstap.h>
|
|
#include <dns/ds.h>
|
|
#include <dns/ede.h>
|
|
#include <dns/edns.h>
|
|
#include <dns/forward.h>
|
|
#include <dns/keytable.h>
|
|
#include <dns/message.h>
|
|
#include <dns/name.h>
|
|
#include <dns/nametree.h>
|
|
#include <dns/ncache.h>
|
|
#include <dns/nsec.h>
|
|
#include <dns/nsec3.h>
|
|
#include <dns/opcode.h>
|
|
#include <dns/peer.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/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_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, \
|
|
ISC_LOG_DEBUG(3), "res %p: %s", res, (m))
|
|
#define RRTRACE(r, m) \
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, \
|
|
ISC_LOG_DEBUG(3), "res %p: %s", (r), (m))
|
|
#define FCTXTRACE(m) \
|
|
isc_log_write(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_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_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_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_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, \
|
|
ISC_LOG_DEBUG(3), "fctx %p(%s): %s %s%u", fctx, \
|
|
fctx->info, (m1), (m2), (v))
|
|
#define FCTXTRACEN(m1, name, res) \
|
|
do { \
|
|
if (isc_log_wouldlog(ISC_LOG_DEBUG(3))) { \
|
|
char dbuf[DNS_NAME_FORMATSIZE]; \
|
|
dns_name_format((name), dbuf, sizeof(dbuf)); \
|
|
FCTXTRACE4((m1), dbuf, (res)); \
|
|
} \
|
|
} while (0)
|
|
#define FTRACE(m) \
|
|
isc_log_write(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_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(fctx); \
|
|
UNUSED(m); \
|
|
} while (0)
|
|
#define FCTXTRACE2(m1, m2) \
|
|
do { \
|
|
UNUSED(fctx); \
|
|
UNUSED(m1); \
|
|
UNUSED(m2); \
|
|
} while (0)
|
|
#define FCTXTRACE3(m1, res) \
|
|
do { \
|
|
UNUSED(fctx); \
|
|
UNUSED(m1); \
|
|
UNUSED(res); \
|
|
} while (0)
|
|
#define FCTXTRACE4(m1, m2, res) \
|
|
do { \
|
|
UNUSED(fctx); \
|
|
UNUSED(m1); \
|
|
UNUSED(m2); \
|
|
UNUSED(res); \
|
|
} while (0)
|
|
#define FCTXTRACE5(m1, m2, v) \
|
|
do { \
|
|
UNUSED(fctx); \
|
|
UNUSED(m1); \
|
|
UNUSED(m2); \
|
|
UNUSED(v); \
|
|
} while (0)
|
|
#define FCTXTRACEN(m1, name, res) FCTXTRACE4(m1, name, res)
|
|
#define FTRACE(m) \
|
|
do { \
|
|
UNUSED(m); \
|
|
} while (0)
|
|
#define QTRACE(m) \
|
|
do { \
|
|
UNUSED(m); \
|
|
} while (0)
|
|
#endif /* WANT_QUERYTRACE */
|
|
|
|
/*
|
|
* 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_MS)
|
|
|
|
/*
|
|
* The default maximum number of validations and validation failures per-fetch
|
|
*/
|
|
#ifndef DEFAULT_MAX_VALIDATIONS
|
|
#define DEFAULT_MAX_VALIDATIONS 16
|
|
#endif
|
|
#ifndef DEFAULT_MAX_VALIDATION_FAILURES
|
|
#define DEFAULT_MAX_VALIDATION_FAILURES 1
|
|
#endif
|
|
|
|
/*
|
|
* A minumum sane timeout value for the whole query to live when e.g. talking to
|
|
* a backend server and a quick timeout is preferred by the user.
|
|
*
|
|
* IMPORTANT: if changing this value, note there is a documented behavior when
|
|
* values of 'resolver-query-timeout' less than or equal to 300 are treated as
|
|
* seconds and converted to milliseconds before applying the limits, that's
|
|
* why the value of 301 was chosen as the absolute minimum in order to not break
|
|
* backward compatibility.
|
|
*/
|
|
#define MINIMUM_QUERY_TIMEOUT 301U
|
|
|
|
/*
|
|
* The default time in seconds for the whole query to live.
|
|
* We want to allow an individual query time to complete / timeout.
|
|
*/
|
|
#ifndef DEFAULT_QUERY_TIMEOUT
|
|
#define DEFAULT_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
|
|
#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 50
|
|
#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
|
|
|
|
STATIC_ASSERT(NS_PROCESSING_LIMIT > NS_RR_LIMIT,
|
|
"The maximum number of NS RRs processed for each "
|
|
"delegation "
|
|
"(NS_PROCESSING_LIMIT) must be larger than the large "
|
|
"delegation "
|
|
"threshold (NS_RR_LIMIT).");
|
|
|
|
/* Hash table for zone counters */
|
|
#ifndef RES_DOMAIN_HASH_BITS
|
|
#define RES_DOMAIN_HASH_BITS 12
|
|
#endif /* ifndef RES_DOMAIN_HASH_BITS */
|
|
|
|
/*%
|
|
* 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
|
|
|
|
typedef struct fetchctx fetchctx_t;
|
|
|
|
typedef struct query {
|
|
/* Locked by loop event serialization. */
|
|
unsigned int magic;
|
|
isc_refcount_t references;
|
|
fetchctx_t *fctx;
|
|
dns_message_t *rmessage;
|
|
dns_dispatchmgr_t *dispatchmgr;
|
|
dns_dispatch_t *dispatch;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
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;
|
|
int ednsversion;
|
|
unsigned int options;
|
|
unsigned int attributes;
|
|
unsigned int udpsize;
|
|
unsigned char data[512];
|
|
} resquery_t;
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
#define resquery_ref(ptr) resquery__ref(ptr, __func__, __FILE__, __LINE__)
|
|
#define resquery_unref(ptr) resquery__unref(ptr, __func__, __FILE__, __LINE__)
|
|
#define resquery_attach(ptr, ptrp) \
|
|
resquery__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
|
|
#define resquery_detach(ptrp) \
|
|
resquery__detach(ptrp, __func__, __FILE__, __LINE__)
|
|
ISC_REFCOUNT_TRACE_DECL(resquery);
|
|
#else
|
|
ISC_REFCOUNT_DECL(resquery);
|
|
#endif
|
|
|
|
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_active,
|
|
fetchstate_done /*%< Fetch completion events posted. */
|
|
} fetchstate_t;
|
|
|
|
typedef enum {
|
|
badns_unreachable = 0,
|
|
badns_response,
|
|
badns_validation,
|
|
badns_forwarder,
|
|
} badnstype_t;
|
|
|
|
#define FCTXCOUNT_MAGIC ISC_MAGIC('F', 'C', 'n', 't')
|
|
#define VALID_FCTXCOUNT(counter) ISC_MAGIC_VALID(counter, FCTXCOUNT_MAGIC)
|
|
|
|
typedef struct fctxcount fctxcount_t;
|
|
struct fctxcount {
|
|
unsigned int magic;
|
|
isc_mem_t *mctx;
|
|
isc_mutex_t lock;
|
|
dns_fixedname_t dfname;
|
|
dns_name_t *domain;
|
|
uint_fast32_t count;
|
|
uint_fast32_t allowed;
|
|
uint_fast32_t dropped;
|
|
isc_stdtime_t logged;
|
|
};
|
|
|
|
struct fetchctx {
|
|
/*% Not locked. */
|
|
unsigned int magic;
|
|
dns_resolver_t *res;
|
|
dns_fixedname_t fname;
|
|
dns_name_t *name;
|
|
dns_rdatatype_t type;
|
|
unsigned int options;
|
|
fctxcount_t *counter;
|
|
char *info;
|
|
isc_mem_t *mctx;
|
|
isc_stdtime_t now;
|
|
|
|
isc_loop_t *loop;
|
|
isc_tid_t tid;
|
|
|
|
dns_edectx_t edectx;
|
|
|
|
/* Atomic */
|
|
isc_refcount_t references;
|
|
|
|
/*% Locked by lock. */
|
|
isc_mutex_t lock;
|
|
fetchstate_t state;
|
|
bool cloned;
|
|
bool spilled;
|
|
uint_fast32_t allowed;
|
|
uint_fast32_t dropped;
|
|
ISC_LIST(dns_fetchresponse_t) resps;
|
|
|
|
/*% Locked by loop event serialization. */
|
|
dns_fixedname_t dfname;
|
|
dns_name_t *domain;
|
|
dns_rdataset_t nameservers;
|
|
atomic_uint_fast32_t attributes;
|
|
isc_timer_t *timer;
|
|
isc_time_t expires;
|
|
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(dns_validator_t) validators;
|
|
dns_db_t *cache;
|
|
dns_adb_t *adb;
|
|
bool ns_ttl_ok;
|
|
uint32_t ns_ttl;
|
|
isc_counter_t *qc;
|
|
isc_counter_t *gqc;
|
|
bool minimized;
|
|
unsigned int qmin_labels;
|
|
isc_result_t qmin_warning;
|
|
bool force_qmin_warning;
|
|
bool ip6arpaskip;
|
|
bool forwarding;
|
|
dns_fixedname_t qminfname;
|
|
dns_name_t *qminname;
|
|
dns_rdatatype_t qmintype;
|
|
dns_fetch_t *qminfetch;
|
|
dns_rdataset_t qminrrset;
|
|
dns_rdataset_t qminsigrrset;
|
|
dns_fixedname_t qmindcfname;
|
|
dns_name_t *qmindcname;
|
|
dns_fixedname_t fwdfname;
|
|
dns_name_t *fwdname;
|
|
|
|
/*%
|
|
* The number of events we're waiting for.
|
|
*/
|
|
atomic_uint_fast32_t pending;
|
|
|
|
/*%
|
|
* 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_fixedname_t nsfname;
|
|
dns_name_t *nsname;
|
|
|
|
dns_fetch_t *nsfetch;
|
|
dns_rdataset_t nsrrset;
|
|
|
|
/*%
|
|
* Number of queries that reference this context.
|
|
*/
|
|
atomic_uint_fast32_t nqueries; /* Bucket lock. */
|
|
|
|
/*%
|
|
* 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 */
|
|
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;
|
|
unsigned int depth;
|
|
char clientstr[ISC_SOCKADDR_FORMATSIZE];
|
|
|
|
isc_counter_t *nvalidations;
|
|
isc_counter_t *nfails;
|
|
};
|
|
|
|
#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_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) ((f)->state == fetchstate_done)
|
|
#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))
|
|
|
|
#define CHECKNTA(f) (((f)->options & DNS_FETCHOPT_NONTA) == 0)
|
|
|
|
typedef struct {
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
fetchctx_t *fctx;
|
|
} dns_valarg_t;
|
|
|
|
struct dns_fetch {
|
|
unsigned int magic;
|
|
isc_mem_t *mctx;
|
|
dns_resolver_t *res;
|
|
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 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;
|
|
dns_view_t *view;
|
|
bool frozen;
|
|
unsigned int options;
|
|
isc_tlsctx_cache_t *tlsctx_cache;
|
|
dns_dispatchset_t *dispatches4;
|
|
dns_dispatchset_t *dispatches6;
|
|
|
|
isc_hashmap_t *fctxs;
|
|
isc_rwlock_t fctxs_lock;
|
|
|
|
isc_hashmap_t *counters;
|
|
isc_rwlock_t counters_lock;
|
|
|
|
uint32_t lame_ttl;
|
|
ISC_LIST(alternate_t) alternates;
|
|
dns_nametree_t *algorithms;
|
|
dns_nametree_t *digests;
|
|
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];
|
|
isc_stats_t *stats;
|
|
dns_stats_t *querystats;
|
|
|
|
/* 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;
|
|
|
|
atomic_uint_fast32_t maxvalidations;
|
|
atomic_uint_fast32_t maxvalidationfails;
|
|
|
|
/* Locked by lock. */
|
|
unsigned int spillat; /* clients-per-query */
|
|
|
|
/* Locked by primelock. */
|
|
dns_fetch_t *primefetch;
|
|
|
|
uint32_t nloops;
|
|
|
|
isc_mempool_t **namepools;
|
|
isc_mempool_t **rdspools;
|
|
};
|
|
|
|
#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!')
|
|
#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC)
|
|
|
|
/*%
|
|
* Private addrinfo flags.
|
|
*/
|
|
enum {
|
|
FCTX_ADDRINFO_MARK = 1 << 0,
|
|
FCTX_ADDRINFO_FORWARDER = 1 << 1,
|
|
FCTX_ADDRINFO_EDNSOK = 1 << 2,
|
|
FCTX_ADDRINFO_NOCOOKIE = 1 << 3,
|
|
FCTX_ADDRINFO_BADCOOKIE = 1 << 4,
|
|
FCTX_ADDRINFO_DUALSTACK = 1 << 5,
|
|
FCTX_ADDRINFO_NOEDNS0 = 1 << 6,
|
|
};
|
|
|
|
#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.nxdomain))
|
|
#define NEGATIVE(r) (((r)->attributes.negative))
|
|
#define STATICSTUB(r) (((r)->attributes.staticstub))
|
|
|
|
#ifdef ENABLE_AFL
|
|
bool dns_fuzzing_resolver = false;
|
|
void
|
|
dns_resolver_setfuzzing(void) {
|
|
dns_fuzzing_resolver = true;
|
|
}
|
|
#endif /* ifdef ENABLE_AFL */
|
|
|
|
static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA";
|
|
static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data);
|
|
|
|
static void
|
|
dns_resolver__destroy(dns_resolver_t *res);
|
|
static isc_result_t
|
|
resquery_send(resquery_t *query);
|
|
static void
|
|
resquery_response(isc_result_t eresult, isc_region_t *region, void *arg);
|
|
static void
|
|
resquery_response_continue(void *arg, isc_result_t result);
|
|
static void
|
|
resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg);
|
|
static void
|
|
fctx_try(fetchctx_t *fctx, bool retrying);
|
|
static void
|
|
fctx_shutdown(void *arg);
|
|
static void
|
|
fctx_minimize_qname(fetchctx_t *fctx);
|
|
static void
|
|
fctx_destroy(fetchctx_t *fctx);
|
|
static isc_result_t
|
|
negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name,
|
|
isc_stdtime_t now, bool optout, bool secure, dns_rdataset_t *added,
|
|
dns_dbnode_t **nodep);
|
|
static void
|
|
validated(void *arg);
|
|
static void
|
|
maybe_cancel_validators(fetchctx_t *fctx);
|
|
static void
|
|
add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
|
|
isc_result_t reason, badnstype_t badtype);
|
|
static void
|
|
findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
|
|
|
|
#define fctx_done_detach(fctxp, result) \
|
|
if (fctx__done(*fctxp, result, __func__, __FILE__, __LINE__)) { \
|
|
fetchctx_detach(fctxp); \
|
|
}
|
|
|
|
#define fctx_done_unref(fctx, result) \
|
|
if (fctx__done(fctx, result, __func__, __FILE__, __LINE__)) { \
|
|
fetchctx_unref(fctx); \
|
|
}
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
#define fetchctx_ref(ptr) fetchctx__ref(ptr, __func__, __FILE__, __LINE__)
|
|
#define fetchctx_unref(ptr) fetchctx__unref(ptr, __func__, __FILE__, __LINE__)
|
|
#define fetchctx_attach(ptr, ptrp) \
|
|
fetchctx__attach(ptr, ptrp, __func__, __FILE__, __LINE__)
|
|
#define fetchctx_detach(ptrp) \
|
|
fetchctx__detach(ptrp, __func__, __FILE__, __LINE__)
|
|
ISC_REFCOUNT_TRACE_DECL(fetchctx);
|
|
#else
|
|
ISC_REFCOUNT_DECL(fetchctx);
|
|
#endif
|
|
|
|
static bool
|
|
fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func,
|
|
const char *file, unsigned int line);
|
|
|
|
static void
|
|
resume_qmin(void *arg);
|
|
|
|
static void
|
|
clone_results(fetchctx_t *fctx);
|
|
|
|
static isc_result_t
|
|
get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
|
|
dns_rdatatype_t type, const dns_name_t *domain,
|
|
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
|
|
unsigned int options, unsigned int depth, isc_counter_t *qc,
|
|
isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx);
|
|
|
|
/*%
|
|
* 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()).
|
|
* - 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 {
|
|
resquery_t *query;
|
|
fetchctx_t *fctx;
|
|
isc_mem_t *mctx;
|
|
isc_result_t result;
|
|
isc_buffer_t buffer;
|
|
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 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 */
|
|
|
|
dns_rdataset_t *vrdataset;
|
|
dns_rdataset_t *vsigrdataset;
|
|
} respctx_t;
|
|
|
|
static void
|
|
rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result,
|
|
isc_region_t *region, 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 isc_result_t
|
|
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 isc_result_t
|
|
rctx_timedout(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->stats != NULL) {
|
|
isc_stats_increment(res->stats, counter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dec_stats(dns_resolver_t *res, isc_statscounter_t counter) {
|
|
if (res->stats != NULL) {
|
|
isc_stats_decrement(res->stats, counter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_stats(dns_resolver_t *res, isc_statscounter_t counter, uint64_t val) {
|
|
if (res->stats != NULL) {
|
|
isc_stats_set(res->stats, val, counter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
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) {
|
|
dns_validator_t *validator = NULL;
|
|
dns_valarg_t *valarg = NULL;
|
|
unsigned int valoptions = 0;
|
|
isc_result_t result;
|
|
|
|
valarg = isc_mem_get(fctx->mctx, sizeof(*valarg));
|
|
*valarg = (dns_valarg_t){
|
|
.addrinfo = addrinfo,
|
|
};
|
|
|
|
fetchctx_attach(fctx, &valarg->fctx);
|
|
|
|
/* Set up validator options */
|
|
if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NOCDFLAG;
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NONTA;
|
|
}
|
|
|
|
if (!ISC_LIST_EMPTY(fctx->validators)) {
|
|
valoptions |= DNS_VALIDATOR_DEFER;
|
|
}
|
|
|
|
result = dns_validator_create(
|
|
fctx->res->view, name, type, rdataset, sigrdataset, message,
|
|
valoptions, fctx->loop, validated, valarg, fctx->nvalidations,
|
|
fctx->nfails, fctx->qc, fctx->gqc, &fctx->edectx, &validator);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
inc_stats(fctx->res, dns_resstatscounter_val);
|
|
ISC_LIST_APPEND(fctx->validators, validator, link);
|
|
}
|
|
|
|
static void
|
|
resquery_destroy(resquery_t *query) {
|
|
fetchctx_t *fctx = query->fctx;
|
|
|
|
query->magic = 0;
|
|
|
|
if (ISC_LINK_LINKED(query, link)) {
|
|
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->dispentry != NULL) {
|
|
dns_dispatch_done(&query->dispentry);
|
|
}
|
|
|
|
if (query->dispatch != NULL) {
|
|
dns_dispatch_detach(&query->dispatch);
|
|
}
|
|
|
|
LOCK(&fctx->lock);
|
|
atomic_fetch_sub_release(&fctx->nqueries, 1);
|
|
UNLOCK(&fctx->lock);
|
|
|
|
if (query->rmessage != NULL) {
|
|
dns_message_detach(&query->rmessage);
|
|
}
|
|
|
|
isc_mem_put(fctx->mctx, query, sizeof(*query));
|
|
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
ISC_REFCOUNT_TRACE_IMPL(resquery, resquery_destroy);
|
|
#else
|
|
ISC_REFCOUNT_IMPL(resquery, resquery_destroy);
|
|
#endif
|
|
|
|
/*%
|
|
* 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);
|
|
} else {
|
|
dns_adb_timeout(fctx->adb, query->addrinfo);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_expired(void *arg);
|
|
|
|
/*
|
|
* Start the maximum lifetime timer for the fetch. This will
|
|
* trigger if, for example, some ADB or validator dependency
|
|
* loop occurs and causes a fetch to hang.
|
|
*/
|
|
static void
|
|
fctx_starttimer(fetchctx_t *fctx) {
|
|
isc_interval_t interval;
|
|
isc_time_t now;
|
|
isc_time_t expires;
|
|
|
|
isc_interval_set(&interval, 2, 0);
|
|
isc_time_add(&fctx->expires, &interval, &expires);
|
|
|
|
now = isc_time_now();
|
|
if (isc_time_compare(&expires, &now) <= 0) {
|
|
isc_interval_set(&interval, 0, 1);
|
|
} else {
|
|
isc_time_subtract(&expires, &now, &interval);
|
|
}
|
|
|
|
isc_timer_start(fctx->timer, isc_timertype_once, &interval);
|
|
}
|
|
|
|
static void
|
|
fctx_stoptimer(fetchctx_t *fctx) {
|
|
isc_timer_stop(fctx->timer);
|
|
}
|
|
|
|
static void
|
|
fctx_cancelquery(resquery_t **queryp, isc_time_t *finish, bool no_response,
|
|
bool age_untried) {
|
|
resquery_t *query = NULL;
|
|
fetchctx_t *fctx = NULL;
|
|
isc_stdtime_t now = isc_stdtime_now();
|
|
|
|
REQUIRE(queryp != NULL);
|
|
|
|
query = *queryp;
|
|
fctx = query->fctx;
|
|
|
|
if (RESQUERY_CANCELED(query)) {
|
|
return;
|
|
}
|
|
|
|
FCTXTRACE("cancelquery");
|
|
|
|
query->attributes |= RESQUERY_ATTR_CANCELED;
|
|
|
|
/*
|
|
* Should we update the RTT?
|
|
*/
|
|
if (finish != NULL || no_response) {
|
|
unsigned int rtt, factor;
|
|
if (finish != NULL) {
|
|
/*
|
|
* We have both the start and finish times for this
|
|
* packet, so we can compute a real RTT.
|
|
*/
|
|
unsigned int rttms;
|
|
|
|
rtt = (unsigned int)isc_time_microdiff(finish,
|
|
&query->start);
|
|
rttms = rtt / US_PER_MS;
|
|
factor = DNS_ADB_RTTADJDEFAULT;
|
|
|
|
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;
|
|
}
|
|
if (rtt > fctx->res->query_timeout * US_PER_MS) {
|
|
rtt = fctx->res->query_timeout * US_PER_MS;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (finish != NULL || age_untried) {
|
|
ISC_LIST_FOREACH (fctx->forwaddrs, addrinfo, publink) {
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) {
|
|
ISC_LIST_FOREACH (fctx->finds, find, publink) {
|
|
ISC_LIST_FOREACH (find->list, addrinfo, publink) {
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDALT(fctx)) {
|
|
ISC_LIST_FOREACH (fctx->altaddrs, addrinfo, publink) {
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
}
|
|
}
|
|
ISC_LIST_FOREACH (fctx->altfinds, find, publink) {
|
|
ISC_LIST_FOREACH (find->list, addrinfo, publink) {
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for any outstanding dispatch responses and if they
|
|
* exist, cancel them.
|
|
*/
|
|
if (query->dispentry != NULL) {
|
|
dns_dispatch_done(&query->dispentry);
|
|
}
|
|
|
|
LOCK(&fctx->lock);
|
|
if (ISC_LINK_LINKED(query, link)) {
|
|
ISC_LIST_UNLINK(fctx->queries, query, link);
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
resquery_detach(queryp);
|
|
}
|
|
|
|
static void
|
|
fctx_cleanup(fetchctx_t *fctx) {
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
ISC_LIST_FOREACH (fctx->finds, find, publink) {
|
|
ISC_LIST_UNLINK(fctx->finds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
fetchctx_unref(fctx);
|
|
}
|
|
fctx->find = NULL;
|
|
|
|
ISC_LIST_FOREACH (fctx->altfinds, find, publink) {
|
|
ISC_LIST_UNLINK(fctx->altfinds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
fetchctx_unref(fctx);
|
|
}
|
|
fctx->altfind = NULL;
|
|
|
|
ISC_LIST_FOREACH (fctx->forwaddrs, addr, publink) {
|
|
ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
|
|
ISC_LIST_FOREACH (fctx->altaddrs, addr, publink) {
|
|
ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
|
|
ISC_LIST(resquery_t) queries;
|
|
|
|
FCTXTRACE("cancelqueries");
|
|
|
|
ISC_LIST_INIT(queries);
|
|
|
|
/*
|
|
* Move the queries to a local list so we can cancel
|
|
* them without holding the lock.
|
|
*/
|
|
LOCK(&fctx->lock);
|
|
ISC_LIST_MOVE(queries, fctx->queries);
|
|
UNLOCK(&fctx->lock);
|
|
|
|
ISC_LIST_FOREACH (queries, query, link) {
|
|
/*
|
|
* Note that we have to unlink the query here,
|
|
* because if it's still linked in fctx_cancelquery(),
|
|
* then it will try to unlink it from fctx->queries.
|
|
*/
|
|
ISC_LIST_UNLINK(queries, query, link);
|
|
fctx_cancelquery(&query, NULL, no_response, age_untried);
|
|
}
|
|
}
|
|
|
|
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(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. */
|
|
now = isc_stdtime_now();
|
|
if (!final && counter->logged > now - 60) {
|
|
return;
|
|
}
|
|
|
|
dns_name_format(fctx->domain, dbuf, sizeof(dbuf));
|
|
|
|
if (!final) {
|
|
isc_log_write(DNS_LOGCATEGORY_SPILL, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO,
|
|
"too many simultaneous fetches for %s "
|
|
"(allowed %" PRIuFAST32 " spilled %" PRIuFAST32
|
|
"; %s)",
|
|
dbuf, counter->allowed, counter->dropped,
|
|
counter->dropped == 1 ? "initial trigger event"
|
|
: "cumulative since "
|
|
"initial trigger event");
|
|
} else {
|
|
isc_log_write(DNS_LOGCATEGORY_SPILL, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO,
|
|
"fetch counters for %s now being discarded "
|
|
"(allowed %" PRIuFAST32 " spilled %" PRIuFAST32
|
|
"; cumulative since initial trigger event)",
|
|
dbuf, counter->allowed, counter->dropped);
|
|
}
|
|
|
|
counter->logged = now;
|
|
}
|
|
|
|
static bool
|
|
fcount_match(void *node, const void *key) {
|
|
const fctxcount_t *counter = node;
|
|
const dns_name_t *domain = key;
|
|
|
|
return dns_name_equal(counter->domain, domain);
|
|
}
|
|
|
|
static isc_result_t
|
|
fcount_incr(fetchctx_t *fctx, bool force) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
dns_resolver_t *res = NULL;
|
|
fctxcount_t *counter = NULL;
|
|
uint32_t hashval;
|
|
uint_fast32_t spill;
|
|
isc_rwlocktype_t locktype = isc_rwlocktype_read;
|
|
|
|
REQUIRE(fctx != NULL);
|
|
res = fctx->res;
|
|
REQUIRE(res != NULL);
|
|
INSIST(fctx->counter == NULL);
|
|
|
|
/* Skip any counting if fetches-per-zone is disabled */
|
|
spill = atomic_load_acquire(&res->zspill);
|
|
if (spill == 0) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
hashval = dns_name_hash(fctx->domain);
|
|
|
|
RWLOCK(&res->counters_lock, locktype);
|
|
result = isc_hashmap_find(res->counters, hashval, fcount_match,
|
|
fctx->domain, (void **)&counter);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case ISC_R_NOTFOUND:
|
|
counter = isc_mem_get(fctx->mctx, sizeof(*counter));
|
|
*counter = (fctxcount_t){
|
|
.magic = FCTXCOUNT_MAGIC,
|
|
};
|
|
isc_mem_attach(fctx->mctx, &counter->mctx);
|
|
isc_mutex_init(&counter->lock);
|
|
counter->domain = dns_fixedname_initname(&counter->dfname);
|
|
dns_name_copy(fctx->domain, counter->domain);
|
|
|
|
UPGRADELOCK(&res->counters_lock, locktype);
|
|
|
|
void *found = NULL;
|
|
result = isc_hashmap_add(res->counters, hashval, fcount_match,
|
|
counter->domain, counter, &found);
|
|
if (result == ISC_R_EXISTS) {
|
|
isc_mutex_destroy(&counter->lock);
|
|
isc_mem_putanddetach(&counter->mctx, counter,
|
|
sizeof(*counter));
|
|
counter = found;
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
INSIST(VALID_FCTXCOUNT(counter));
|
|
|
|
INSIST(spill > 0);
|
|
LOCK(&counter->lock);
|
|
if (++counter->count > spill && !force) {
|
|
counter->count--;
|
|
INSIST(counter->count > 0);
|
|
counter->dropped++;
|
|
fcount_logspill(fctx, counter, false);
|
|
result = ISC_R_QUOTA;
|
|
} else {
|
|
counter->allowed++;
|
|
fctx->counter = counter;
|
|
}
|
|
UNLOCK(&counter->lock);
|
|
RWUNLOCK(&res->counters_lock, locktype);
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
match_ptr(void *node, const void *key) {
|
|
return node == key;
|
|
}
|
|
|
|
static void
|
|
fcount_decr(fetchctx_t *fctx) {
|
|
REQUIRE(fctx != NULL);
|
|
|
|
fctxcount_t *counter = fctx->counter;
|
|
if (counter == NULL) {
|
|
return;
|
|
}
|
|
fctx->counter = NULL;
|
|
|
|
/*
|
|
* FIXME: This should not require a write lock, but should be
|
|
* implemented using reference counting later, otherwise we would could
|
|
* encounter ABA problem here - the count could go up and down when we
|
|
* switch from read to write lock.
|
|
*/
|
|
RWLOCK(&fctx->res->counters_lock, isc_rwlocktype_write);
|
|
|
|
LOCK(&counter->lock);
|
|
INSIST(VALID_FCTXCOUNT(counter));
|
|
INSIST(counter->count > 0);
|
|
if (--counter->count > 0) {
|
|
UNLOCK(&counter->lock);
|
|
RWUNLOCK(&fctx->res->counters_lock, isc_rwlocktype_write);
|
|
return;
|
|
}
|
|
|
|
isc_result_t result = isc_hashmap_delete(fctx->res->counters,
|
|
dns_name_hash(counter->domain),
|
|
match_ptr, counter);
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
|
|
fcount_logspill(fctx, counter, true);
|
|
UNLOCK(&counter->lock);
|
|
|
|
isc_mutex_destroy(&counter->lock);
|
|
isc_mem_putanddetach(&counter->mctx, counter, sizeof(*counter));
|
|
|
|
RWUNLOCK(&fctx->res->counters_lock, isc_rwlocktype_write);
|
|
}
|
|
|
|
static void
|
|
spillattimer_countdown(void *arg);
|
|
|
|
static void
|
|
fctx_sendevents(fetchctx_t *fctx, isc_result_t result) {
|
|
unsigned int count = 0;
|
|
bool logit = false;
|
|
isc_time_t now;
|
|
unsigned int old_spillat;
|
|
unsigned int new_spillat = 0; /* initialized to silence
|
|
* compiler warnings */
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
REQUIRE(fctx->state == fetchstate_done);
|
|
|
|
FCTXTRACE("sendevents");
|
|
|
|
/*
|
|
* Keep some record of fetch result for logging later (if required).
|
|
*/
|
|
fctx->result = result;
|
|
now = isc_time_now();
|
|
fctx->duration = isc_time_microdiff(&now, &fctx->start);
|
|
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
ISC_LIST_UNLINK(fctx->resps, resp, link);
|
|
|
|
count++;
|
|
|
|
resp->vresult = fctx->vresult;
|
|
if (!HAVE_ANSWER(fctx)) {
|
|
resp->result = result;
|
|
}
|
|
|
|
INSIST(resp->result != ISC_R_SUCCESS ||
|
|
dns_rdataset_isassociated(resp->rdataset) ||
|
|
dns_rdatatype_ismulti(fctx->type));
|
|
|
|
/*
|
|
* Negative results must be indicated in resp->result.
|
|
*/
|
|
if (dns_rdataset_isassociated(resp->rdataset) &&
|
|
NEGATIVE(resp->rdataset))
|
|
{
|
|
INSIST(resp->result == DNS_R_NCACHENXDOMAIN ||
|
|
resp->result == DNS_R_NCACHENXRRSET);
|
|
}
|
|
|
|
/*
|
|
* Finalize the EDE context so it becomes "constant", and
|
|
* assign it to all clients.
|
|
*/
|
|
if (resp->edectx != NULL) {
|
|
dns_ede_copy(resp->edectx, &fctx->edectx);
|
|
}
|
|
|
|
FCTXTRACE("post response event");
|
|
isc_async_run(resp->loop, resp->cb, resp);
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
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;
|
|
}
|
|
|
|
/* Timer not running */
|
|
if (fctx->res->spillattimer == NULL) {
|
|
isc_interval_t i;
|
|
|
|
isc_timer_create(
|
|
isc_loop(), spillattimer_countdown,
|
|
fctx->res, &fctx->res->spillattimer);
|
|
|
|
isc_interval_set(&i, 20 * 60, 0);
|
|
isc_timer_start(fctx->res->spillattimer,
|
|
isc_timertype_ticker, &i);
|
|
}
|
|
}
|
|
UNLOCK(&fctx->res->lock);
|
|
if (logit) {
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"clients-per-query increased to %u",
|
|
new_spillat);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
fctx_hash(fetchctx_t *fctx) {
|
|
isc_hash32_t hash32;
|
|
isc_hash32_init(&hash32);
|
|
isc_hash32_hash(&hash32, fctx->name->ndata, fctx->name->length, false);
|
|
isc_hash32_hash(&hash32, &fctx->options, sizeof(fctx->options), true);
|
|
isc_hash32_hash(&hash32, &fctx->type, sizeof(fctx->type), true);
|
|
return isc_hash32_finalize(&hash32);
|
|
}
|
|
|
|
static bool
|
|
fctx_match(void *node, const void *key) {
|
|
const fetchctx_t *fctx0 = node;
|
|
const fetchctx_t *fctx1 = key;
|
|
|
|
return fctx0->options == fctx1->options && fctx0->type == fctx1->type &&
|
|
dns_name_equal(fctx0->name, fctx1->name);
|
|
}
|
|
|
|
static bool
|
|
fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func,
|
|
const char *file, unsigned int line) {
|
|
bool no_response = false;
|
|
bool age_untried = false;
|
|
|
|
REQUIRE(fctx != NULL);
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
FCTXTRACE("done");
|
|
|
|
#ifdef DNS_RESOLVER_TRACE
|
|
fprintf(stderr, "%s:%s:%s:%u:(%p): %s\n", __func__, func, file, line,
|
|
fctx, isc_result_totext(result));
|
|
#else
|
|
UNUSED(file);
|
|
UNUSED(line);
|
|
UNUSED(func);
|
|
#endif
|
|
|
|
LOCK(&fctx->lock);
|
|
/* We need to do this under the lock for intra-thread synchronization */
|
|
if (fctx->state == fetchstate_done) {
|
|
UNLOCK(&fctx->lock);
|
|
return false;
|
|
}
|
|
fctx->state = fetchstate_done;
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
UNLOCK(&fctx->lock);
|
|
|
|
/* The fctx will get deleted either here or in get_attached_fctx() */
|
|
RWLOCK(&fctx->res->fctxs_lock, isc_rwlocktype_write);
|
|
(void)isc_hashmap_delete(fctx->res->fctxs, fctx_hash(fctx), match_ptr,
|
|
fctx);
|
|
RWUNLOCK(&fctx->res->fctxs_lock, isc_rwlocktype_write);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
if (fctx->qmin_warning != ISC_R_SUCCESS) {
|
|
isc_log_write(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));
|
|
}
|
|
|
|
/*
|
|
* A success result indicates we got a response to a
|
|
* query. That query should be canceled already. If
|
|
* there still are any outstanding queries attached to the
|
|
* same fctx, then those have *not* gotten a response,
|
|
* so we set 'no_response' to true here: that way, when
|
|
* we run fctx_cancelqueries() below, the SRTTs will
|
|
* be adjusted.
|
|
*/
|
|
no_response = true;
|
|
} else if (result == ISC_R_TIMEDOUT) {
|
|
age_untried = true;
|
|
}
|
|
|
|
fctx->qmin_warning = ISC_R_SUCCESS;
|
|
|
|
fctx_cancelqueries(fctx, no_response, age_untried);
|
|
fctx_stoptimer(fctx);
|
|
|
|
/*
|
|
* Cancel all pending validators. Note that this must be done
|
|
* without the fctx lock held, since that could cause
|
|
* deadlock.
|
|
*/
|
|
maybe_cancel_validators(fctx);
|
|
|
|
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.
|
|
*/
|
|
fctx_sendevents(fctx, result);
|
|
fctx_cleanup(fctx);
|
|
|
|
isc_timer_destroy(&fctx->timer);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|
resquery_t *query = (resquery_t *)arg;
|
|
resquery_t *copy = query;
|
|
fetchctx_t *fctx = NULL;
|
|
|
|
QTRACE("senddone");
|
|
|
|
UNUSED(region);
|
|
|
|
REQUIRE(VALID_QUERY(query));
|
|
fctx = query->fctx;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
if (RESQUERY_CANCELED(query)) {
|
|
goto detach;
|
|
}
|
|
|
|
/*
|
|
* See the note in resquery_connected() about reference
|
|
* counting on error conditions.
|
|
*/
|
|
switch (eresult) {
|
|
case ISC_R_SUCCESS:
|
|
case ISC_R_CANCELED:
|
|
case ISC_R_SHUTTINGDOWN:
|
|
break;
|
|
|
|
case ISC_R_HOSTDOWN:
|
|
case ISC_R_HOSTUNREACH:
|
|
case ISC_R_NETDOWN:
|
|
case ISC_R_NETUNREACH:
|
|
case ISC_R_NOPERM:
|
|
case ISC_R_ADDRNOTAVAIL:
|
|
case ISC_R_CONNREFUSED:
|
|
case ISC_R_CONNECTIONRESET:
|
|
case ISC_R_TIMEDOUT:
|
|
/* No route to remote. */
|
|
FCTXTRACE3("query canceled in resquery_senddone(): "
|
|
"no route to host; no response",
|
|
eresult);
|
|
add_bad(fctx, query->rmessage, query->addrinfo, eresult,
|
|
badns_unreachable);
|
|
fctx_cancelquery(©, NULL, true, false);
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
fctx_try(fctx, true);
|
|
break;
|
|
|
|
default:
|
|
FCTXTRACE3("query canceled in resquery_senddone() "
|
|
"due to unexpected result; responding",
|
|
eresult);
|
|
fctx_cancelquery(©, NULL, false, false);
|
|
fctx_done_detach(&fctx, eresult);
|
|
break;
|
|
}
|
|
|
|
detach:
|
|
resquery_detach(&query);
|
|
}
|
|
|
|
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, us;
|
|
uint64_t limit;
|
|
isc_time_t now;
|
|
|
|
/*
|
|
* Has this fetch already expired?
|
|
*/
|
|
now = isc_time_now();
|
|
limit = isc_time_microdiff(&fctx->expires, &now);
|
|
if (limit < US_PER_MS) {
|
|
FCTXTRACE("fetch already expired");
|
|
isc_interval_set(&fctx->interval, 0, 0);
|
|
return;
|
|
}
|
|
|
|
us = fctx->res->retryinterval * US_PER_MS;
|
|
|
|
/*
|
|
* 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 wait past the the final expiration of the fetch,
|
|
* or for more than 10 seconds total.
|
|
*/
|
|
if (us > limit) {
|
|
us = limit;
|
|
}
|
|
if (us > MAX_SINGLE_QUERY_TIMEOUT_US) {
|
|
us = MAX_SINGLE_QUERY_TIMEOUT_US;
|
|
}
|
|
if (us > fctx->res->query_timeout * US_PER_MS) {
|
|
us = fctx->res->query_timeout * US_PER_MS;
|
|
}
|
|
|
|
seconds = us / US_PER_SEC;
|
|
us -= seconds * US_PER_SEC;
|
|
isc_interval_set(&fctx->interval, seconds, us * NS_PER_US);
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
|
|
unsigned int options) {
|
|
isc_result_t result;
|
|
dns_resolver_t *res = NULL;
|
|
dns_dns64_t *dns64 = NULL;
|
|
resquery_t *query = NULL;
|
|
isc_sockaddr_t addr, sockaddr;
|
|
bool have_addr = false;
|
|
unsigned int srtt;
|
|
isc_tlsctx_cache_t *tlsctx_cache = NULL;
|
|
|
|
FCTXTRACE("query");
|
|
|
|
res = fctx->res;
|
|
|
|
srtt = addrinfo->srtt;
|
|
|
|
if (addrinfo->transport != NULL) {
|
|
switch (dns_transport_get_type(addrinfo->transport)) {
|
|
case DNS_TRANSPORT_TLS:
|
|
options |= DNS_FETCHOPT_TCP;
|
|
tlsctx_cache = res->tlsctx_cache;
|
|
break;
|
|
case DNS_TRANSPORT_TCP:
|
|
case DNS_TRANSPORT_HTTP:
|
|
options |= DNS_FETCHOPT_TCP;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Maybe apply DNS64 mappings to IPv4 addresses.
|
|
*/
|
|
sockaddr = addrinfo->sockaddr;
|
|
dns64 = ISC_LIST_HEAD(fctx->res->view->dns64);
|
|
if (isc_sockaddr_pf(&sockaddr) == AF_INET &&
|
|
fctx->res->view->usedns64 && dns64 != NULL)
|
|
{
|
|
struct in6_addr aaaa;
|
|
|
|
result = dns_dns64_aaaafroma(
|
|
dns64, NULL, NULL, fctx->res->view->aclenv, 0,
|
|
(unsigned char *)&sockaddr.type.sin.sin_addr.s_addr,
|
|
aaaa.s6_addr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
char sockaddrbuf1[ISC_SOCKADDR_FORMATSIZE];
|
|
char sockaddrbuf2[ISC_SOCKADDR_FORMATSIZE];
|
|
|
|
/* format old address */
|
|
isc_sockaddr_format(&sockaddr, sockaddrbuf1,
|
|
sizeof(sockaddrbuf1));
|
|
|
|
/* replace address */
|
|
isc_sockaddr_fromin6(&sockaddr, &aaaa,
|
|
ntohs(sockaddr.type.sin.sin_port));
|
|
addrinfo->sockaddr = sockaddr;
|
|
|
|
/* format new address */
|
|
isc_sockaddr_format(&sockaddr, sockaddrbuf2,
|
|
sizeof(sockaddrbuf2));
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"Using DNS64 address %s to talk to %s\n",
|
|
sockaddrbuf2, sockaddrbuf1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if the address is in the peers list and has a special
|
|
* confguration.
|
|
*/
|
|
if (res->view->peers != NULL) {
|
|
dns_peer_t *peer = NULL;
|
|
isc_netaddr_t dstip;
|
|
bool usetcp = false;
|
|
isc_netaddr_fromsockaddr(&dstip, &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_getforcetcp(peer, &usetcp);
|
|
if (result == ISC_R_SUCCESS && usetcp) {
|
|
options |= DNS_FETCHOPT_TCP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 += US_PER_SEC;
|
|
}
|
|
|
|
/*
|
|
* A forwarder needs to make multiple queries. Give it at least
|
|
* a second to do these in.
|
|
*/
|
|
if (ISFORWARDER(addrinfo) && srtt < US_PER_SEC) {
|
|
srtt = US_PER_SEC;
|
|
}
|
|
|
|
fctx_setretryinterval(fctx, srtt);
|
|
if (isc_interval_iszero(&fctx->interval)) {
|
|
FCTXTRACE("fetch expired");
|
|
dns_ede_add(&fctx->edectx, DNS_EDE_NOREACHABLEAUTH, NULL);
|
|
return ISC_R_TIMEDOUT;
|
|
}
|
|
|
|
INSIST(ISC_LIST_EMPTY(fctx->validators));
|
|
|
|
query = isc_mem_get(fctx->mctx, sizeof(*query));
|
|
*query = (resquery_t){
|
|
.options = options,
|
|
.addrinfo = addrinfo,
|
|
.dispatchmgr = res->view->dispatchmgr,
|
|
.link = ISC_LINK_INITIALIZER,
|
|
};
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
fprintf(stderr, "rctx_init:%s:%s:%d:%p->references = 1\n", __func__,
|
|
__FILE__, __LINE__, query);
|
|
#endif
|
|
isc_refcount_init(&query->references, 1);
|
|
|
|
/*
|
|
* Note that the caller MUST guarantee that 'addrinfo' will
|
|
* remain valid until this query is canceled.
|
|
*/
|
|
|
|
dns_message_create(fctx->mctx, fctx->res->namepools[fctx->tid],
|
|
fctx->res->rdspools[fctx->tid],
|
|
DNS_MESSAGE_INTENTPARSE, &query->rmessage);
|
|
query->start = isc_time_now();
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
|
|
int pf;
|
|
|
|
pf = isc_sockaddr_pf(&sockaddr);
|
|
if (!have_addr) {
|
|
switch (pf) {
|
|
case PF_INET:
|
|
result = dns_dispatch_getlocaladdress(
|
|
res->dispatches4->dispatches[0], &addr);
|
|
break;
|
|
case PF_INET6:
|
|
result = dns_dispatch_getlocaladdress(
|
|
res->dispatches6->dispatches[0], &addr);
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
break;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_query;
|
|
}
|
|
}
|
|
isc_sockaddr_setport(&addr, 0);
|
|
|
|
result = dns_dispatch_createtcp(res->view->dispatchmgr, &addr,
|
|
&sockaddr, addrinfo->transport,
|
|
DNS_DISPATCHOPT_UNSHARED,
|
|
&query->dispatch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_query;
|
|
}
|
|
|
|
FCTXTRACE("connecting via TCP");
|
|
} else {
|
|
if (have_addr) {
|
|
result = dns_dispatch_createudp(res->view->dispatchmgr,
|
|
&addr,
|
|
&query->dispatch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_query;
|
|
}
|
|
} else {
|
|
switch (isc_sockaddr_pf(&sockaddr)) {
|
|
case PF_INET:
|
|
dns_dispatch_attach(
|
|
dns_resolver_dispatchv4(res),
|
|
&query->dispatch);
|
|
break;
|
|
case PF_INET6:
|
|
dns_dispatch_attach(
|
|
dns_resolver_dispatchv6(res),
|
|
&query->dispatch);
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
goto cleanup_query;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
LOCK(&fctx->lock);
|
|
INSIST(!SHUTTINGDOWN(fctx));
|
|
fetchctx_attach(fctx, &query->fctx);
|
|
query->magic = QUERY_MAGIC;
|
|
|
|
if ((query->options & DNS_FETCHOPT_TCP) == 0) {
|
|
if (dns_adb_overquota(fctx->adb, addrinfo)) {
|
|
UNLOCK(&fctx->lock);
|
|
result = ISC_R_QUOTA;
|
|
goto cleanup_dispatch;
|
|
}
|
|
|
|
/* Inform the ADB that we're starting a UDP fetch */
|
|
dns_adb_beginudpfetch(fctx->adb, addrinfo);
|
|
}
|
|
|
|
ISC_LIST_APPEND(fctx->queries, query, link);
|
|
atomic_fetch_add_relaxed(&fctx->nqueries, 1);
|
|
UNLOCK(&fctx->lock);
|
|
|
|
/* Set up the dispatch and set the query ID */
|
|
const unsigned int timeout_ms = isc_interval_ms(&fctx->interval);
|
|
result = dns_dispatch_add(query->dispatch, fctx->loop, 0, timeout_ms,
|
|
timeout_ms, &sockaddr, addrinfo->transport,
|
|
tlsctx_cache, resquery_connected,
|
|
resquery_senddone, resquery_response, query,
|
|
&query->id, &query->dispentry);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_udpfetch;
|
|
}
|
|
|
|
/* Connect the socket */
|
|
resquery_ref(query);
|
|
result = dns_dispatch_connect(query->dispentry);
|
|
|
|
if (result != ISC_R_SUCCESS && (query->options & DNS_FETCHOPT_TCP) != 0)
|
|
{
|
|
int log_level = ISC_LOG_NOTICE;
|
|
if (isc_log_wouldlog(log_level)) {
|
|
char peerbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
|
|
isc_sockaddr_format(&sockaddr, peerbuf,
|
|
ISC_SOCKADDR_FORMATSIZE);
|
|
|
|
isc_log_write(
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, log_level,
|
|
"Unable to establish a connection to %s: %s\n",
|
|
peerbuf, isc_result_totext(result));
|
|
}
|
|
dns_dispatch_done(&query->dispentry);
|
|
goto cleanup_fetch;
|
|
} else {
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
|
|
return result;
|
|
|
|
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_fetch:
|
|
LOCK(&fctx->lock);
|
|
if (ISC_LINK_LINKED(query, link)) {
|
|
atomic_fetch_sub_release(&fctx->nqueries, 1);
|
|
ISC_LIST_UNLINK(fctx->queries, query, link);
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
cleanup_dispatch:
|
|
fetchctx_detach(&query->fctx);
|
|
|
|
if (query->dispatch != NULL) {
|
|
dns_dispatch_detach(&query->dispatch);
|
|
}
|
|
|
|
cleanup_query:
|
|
query->magic = 0;
|
|
dns_message_detach(&query->rmessage);
|
|
isc_mem_put(fctx->mctx, query, sizeof(*query));
|
|
|
|
return result;
|
|
}
|
|
|
|
static struct tried *
|
|
triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
ISC_LIST_FOREACH (fctx->edns, 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 = 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 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 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, true,
|
|
digest);
|
|
memmove(cookie, digest, CLIENT_COOKIE_SIZE);
|
|
}
|
|
|
|
static bool
|
|
issecuredomain(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
|
|
isc_stdtime_t now, bool *ntap) {
|
|
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);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
name = &suffix;
|
|
}
|
|
|
|
return dns_view_issecuredomain(fctx->res->view, name, now,
|
|
CHECKNTA(fctx), ntap);
|
|
}
|
|
|
|
static isc_result_t
|
|
resquery_send(resquery_t *query) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = query->fctx;
|
|
dns_resolver_t *res = fctx->res;
|
|
isc_buffer_t buffer;
|
|
dns_name_t *qname = NULL;
|
|
dns_rdataset_t *qrdataset = NULL;
|
|
isc_region_t r;
|
|
isc_netaddr_t ipaddr;
|
|
dns_tsigkey_t *tsigkey = NULL;
|
|
dns_peer_t *peer = NULL;
|
|
dns_compress_t cctx;
|
|
bool useedns;
|
|
bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0);
|
|
dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
|
|
unsigned int ednsopt = 0;
|
|
uint16_t hint = 0, udpsize = 0; /* No EDNS */
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
#ifdef HAVE_DNSTAP
|
|
unsigned char zone[DNS_NAME_MAXWIRE];
|
|
dns_transport_type_t transport_type;
|
|
dns_dtmsgtype_t dtmsgtype;
|
|
isc_region_t zr;
|
|
isc_buffer_t zb;
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
QTRACE("send");
|
|
|
|
if (atomic_load_acquire(&res->exiting)) {
|
|
FCTXTRACE("resquery_send: resolver shutting down");
|
|
return ISC_R_SHUTTINGDOWN;
|
|
}
|
|
|
|
dns_message_gettempname(fctx->qmessage, &qname);
|
|
dns_message_gettemprdataset(fctx->qmessage, &qrdataset);
|
|
|
|
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);
|
|
|
|
/*
|
|
* 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 ||
|
|
(query->options & DNS_FETCHOPT_TRYCD) != 0)
|
|
{
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
|
|
} else if (res->view->enablevalidation &&
|
|
((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
|
|
{
|
|
query->options |= DNS_FETCHOPT_TRYCD;
|
|
}
|
|
|
|
/*
|
|
* We don't have to set opcode because it defaults to query.
|
|
*/
|
|
fctx->qmessage->id = query->id;
|
|
|
|
/*
|
|
* Convert the question to wire format.
|
|
*/
|
|
dns_compress_init(&cctx, fctx->mctx, 0);
|
|
|
|
isc_buffer_init(&buffer, query->data, sizeof(query->data));
|
|
result = dns_message_renderbegin(fctx->qmessage, &cctx, &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;
|
|
}
|
|
|
|
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 & FCTX_ADDRINFO_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,
|
|
FCTX_ADDRINFO_NOEDNS0,
|
|
FCTX_ADDRINFO_NOEDNS0);
|
|
}
|
|
|
|
/* Sync NOEDNS0 flag in addrinfo->flags and options now. */
|
|
if ((query->addrinfo->flags & FCTX_ADDRINFO_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 this is the first timeout for this server in this
|
|
* fetch context, try setting EDNS UDP buffer size to
|
|
* the largest UDP response size we have seen from this
|
|
* server so far.
|
|
*
|
|
* If this server has already timed out twice or more in
|
|
* this fetch context, force TCP.
|
|
*/
|
|
if ((tried = triededns(fctx, sockaddr)) != NULL) {
|
|
if (tried->count == 1U) {
|
|
hint = dns_adb_getudpsize(fctx->adb,
|
|
query->addrinfo);
|
|
} else if (tried->count >= 2U) {
|
|
if ((query->options & DNS_FETCHOPT_TCP) == 0) {
|
|
/*
|
|
* Inform the ADB that we're ending a
|
|
* UDP fetch, and turn the query into
|
|
* a TCP query.
|
|
*/
|
|
dns_adb_endudpfetch(fctx->adb,
|
|
query->addrinfo);
|
|
query->options |= DNS_FETCHOPT_TCP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 & FCTX_ADDRINFO_NOEDNS0) == 0) {
|
|
uint16_t peerudpsize = 0;
|
|
unsigned int version = DNS_EDNS_VERSION;
|
|
unsigned int flags = query->addrinfo->flags;
|
|
bool reqnsid = res->view->requestnsid;
|
|
bool sendcookie = res->view->sendcookie;
|
|
bool reqzoneversion = res->view->requestzoneversion;
|
|
bool tcpkeepalive = false;
|
|
unsigned char cookie[COOKIE_BUFFER_SIZE];
|
|
uint16_t padding = 0;
|
|
|
|
/*
|
|
* Set the default UDP size to what was
|
|
* configured as 'edns-buffer-size'
|
|
*/
|
|
udpsize = res->view->udpsize;
|
|
|
|
/*
|
|
* This server timed out for the first time in
|
|
* this fetch context and we received a response
|
|
* from it before (either in this fetch context
|
|
* or in a different one). Set 'udpsize' to the
|
|
* size of the largest UDP response we have
|
|
* received from this server so far.
|
|
*/
|
|
if (hint != 0U) {
|
|
udpsize = hint;
|
|
}
|
|
|
|
/*
|
|
* If a fixed EDNS UDP buffer size is configured
|
|
* for this server, make sure we obey that.
|
|
*/
|
|
if (peer != NULL) {
|
|
(void)dns_peer_getudpsize(peer, &peerudpsize);
|
|
if (peerudpsize != 0) {
|
|
udpsize = peerudpsize;
|
|
}
|
|
}
|
|
|
|
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;
|
|
result = dns_peer_getednsversion(peer,
|
|
&ednsversion);
|
|
if (result == ISC_R_SUCCESS &&
|
|
ednsversion < version)
|
|
{
|
|
version = ednsversion;
|
|
}
|
|
(void)dns_peer_getrequestnsid(peer, &reqnsid);
|
|
(void)dns_peer_getrequestzoneversion(
|
|
peer, &reqzoneversion);
|
|
(void)dns_peer_getsendcookie(peer, &sendcookie);
|
|
}
|
|
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 (reqzoneversion) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_ZONEVERSION;
|
|
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(
|
|
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 (result == ISC_R_SUCCESS) {
|
|
if (reqnsid) {
|
|
query->options |= DNS_FETCHOPT_WANTNSID;
|
|
}
|
|
if (reqzoneversion) {
|
|
query->options |=
|
|
DNS_FETCHOPT_WANTZONEVERSION;
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
add_triededns(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_setpermitted(&cctx, false);
|
|
result = dns_name_towire(fctx->domain, &cctx, &zb);
|
|
if (result == ISC_R_SUCCESS) {
|
|
isc_buffer_usedregion(&zb, &zr);
|
|
}
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
if (dns_message_gettsigkey(fctx->qmessage) != NULL) {
|
|
dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage),
|
|
&query->tsigkey);
|
|
result = dns_message_getquerytsig(fctx->qmessage, fctx->mctx,
|
|
&query->tsig);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_message;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Log the outgoing packet.
|
|
*/
|
|
result = dns_dispentry_getlocaladdress(query->dispentry, &localaddr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
la = &localaddr;
|
|
}
|
|
|
|
dns_message_logpacketfromto(
|
|
fctx->qmessage, "sending packet", la,
|
|
&query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_PACKETS, ISC_LOG_DEBUG(11), fctx->mctx);
|
|
|
|
/*
|
|
* We're now done with the query message.
|
|
*/
|
|
dns_compress_invalidate(&cctx);
|
|
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
|
|
|
|
isc_buffer_usedregion(&buffer, &r);
|
|
|
|
resquery_ref(query);
|
|
dns_dispatch_send(query->dispentry, &r);
|
|
|
|
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;
|
|
}
|
|
|
|
if (query->addrinfo->transport != NULL) {
|
|
transport_type =
|
|
dns_transport_get_type(query->addrinfo->transport);
|
|
} else if ((query->options & DNS_FETCHOPT_TCP) != 0) {
|
|
transport_type = DNS_TRANSPORT_TCP;
|
|
} else {
|
|
transport_type = DNS_TRANSPORT_UDP;
|
|
}
|
|
|
|
dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr,
|
|
transport_type, &zr, &query->start, NULL, &buffer);
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
cleanup_message:
|
|
dns_compress_invalidate(&cctx);
|
|
|
|
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
|
|
|
|
/*
|
|
* Stop the dispatcher from listening.
|
|
*/
|
|
dns_dispatch_done(&query->dispentry);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|
resquery_t *query = (resquery_t *)arg;
|
|
resquery_t *copy = query;
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = NULL;
|
|
dns_resolver_t *res = NULL;
|
|
int pf;
|
|
|
|
REQUIRE(VALID_QUERY(query));
|
|
|
|
QTRACE("connected");
|
|
|
|
UNUSED(region);
|
|
|
|
fctx = query->fctx;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
res = fctx->res;
|
|
|
|
if (RESQUERY_CANCELED(query)) {
|
|
goto detach;
|
|
}
|
|
|
|
if (atomic_load_acquire(&fctx->res->exiting)) {
|
|
eresult = ISC_R_SHUTTINGDOWN;
|
|
}
|
|
|
|
/*
|
|
* The reference counting of resquery objects is complex:
|
|
*
|
|
* 1. attached in fctx_query()
|
|
* 2. attached prior to dns_dispatch_connect(), detached in
|
|
* resquery_connected()
|
|
* 3. attached prior to dns_dispatch_send(), detached in
|
|
* resquery_senddone()
|
|
* 4. finally detached in fctx_cancelquery()
|
|
*
|
|
* On error conditions, it's necessary to call fctx_cancelquery()
|
|
* from resquery_connected() or _senddone(), detaching twice
|
|
* within the same function. To make it clear that's what's
|
|
* happening, we cancel-and-detach 'copy' and detach 'query',
|
|
* which are both pointing to the same object.
|
|
*/
|
|
switch (eresult) {
|
|
case ISC_R_SUCCESS:
|
|
/*
|
|
* We are connected. Send the query.
|
|
*/
|
|
|
|
result = resquery_send(query);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("query canceled: resquery_send() failed; "
|
|
"responding");
|
|
|
|
fctx_cancelquery(©, NULL, false, false);
|
|
fctx_done_detach(&fctx, result);
|
|
break;
|
|
}
|
|
|
|
fctx->querysent++;
|
|
|
|
pf = isc_sockaddr_pf(&query->addrinfo->sockaddr);
|
|
if (pf == PF_INET) {
|
|
inc_stats(res, dns_resstatscounter_queryv4);
|
|
} else {
|
|
inc_stats(res, dns_resstatscounter_queryv6);
|
|
}
|
|
if (res->querystats != NULL) {
|
|
dns_rdatatypestats_increment(res->querystats,
|
|
fctx->type);
|
|
}
|
|
break;
|
|
|
|
case ISC_R_CANCELED:
|
|
case ISC_R_SHUTTINGDOWN:
|
|
FCTXTRACE3("shutdown in resquery_connected()", eresult);
|
|
fctx_cancelquery(©, NULL, true, false);
|
|
fctx_done_detach(&fctx, eresult);
|
|
break;
|
|
|
|
case ISC_R_HOSTDOWN:
|
|
case ISC_R_HOSTUNREACH:
|
|
case ISC_R_NETDOWN:
|
|
case ISC_R_NETUNREACH:
|
|
case ISC_R_CONNREFUSED:
|
|
case ISC_R_NOPERM:
|
|
case ISC_R_ADDRNOTAVAIL:
|
|
case ISC_R_CONNECTIONRESET:
|
|
case ISC_R_TIMEDOUT:
|
|
/*
|
|
* Do not query this server again in this fetch context.
|
|
*/
|
|
FCTXTRACE3("query failed in resquery_connected(): "
|
|
"no response",
|
|
eresult);
|
|
add_bad(fctx, query->rmessage, query->addrinfo, eresult,
|
|
badns_unreachable);
|
|
fctx_cancelquery(©, NULL, true, false);
|
|
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
fctx_try(fctx, true);
|
|
break;
|
|
|
|
default:
|
|
FCTXTRACE3("query canceled in resquery_connected() "
|
|
"due to unexpected result; responding",
|
|
eresult);
|
|
|
|
fctx_cancelquery(©, NULL, false, false);
|
|
fctx_done_detach(&fctx, eresult);
|
|
break;
|
|
}
|
|
|
|
detach:
|
|
resquery_detach(&query);
|
|
}
|
|
|
|
static void
|
|
fctx_finddone(void *arg) {
|
|
dns_adbfind_t *find = (dns_adbfind_t *)arg;
|
|
fetchctx_t *fctx = (fetchctx_t *)find->cbarg;
|
|
bool want_try = false;
|
|
bool want_done = false;
|
|
uint_fast32_t pending;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
FCTXTRACE("finddone");
|
|
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
LOCK(&fctx->lock);
|
|
pending = atomic_fetch_sub_release(&fctx->pending, 1);
|
|
INSIST(pending > 0);
|
|
|
|
if (ADDRWAIT(fctx)) {
|
|
/*
|
|
* The fetch is waiting for a name to be found.
|
|
*/
|
|
INSIST(!SHUTTINGDOWN(fctx));
|
|
if (dns_adb_findstatus(find) == DNS_ADB_MOREADDRESSES) {
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
want_try = true;
|
|
} else {
|
|
fctx->findfail++;
|
|
if (atomic_load_acquire(&fctx->pending) == 0) {
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
if (!ISC_LIST_EMPTY(fctx->res->alternates)) {
|
|
want_try = true;
|
|
} else {
|
|
/*
|
|
* We've got nothing else to wait for
|
|
* and don't know the answer. There's
|
|
* nothing to do but fail the fctx.
|
|
*/
|
|
want_done = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UNLOCK(&fctx->lock);
|
|
|
|
dns_adb_destroyfind(&find);
|
|
|
|
if (want_done) {
|
|
FCTXTRACE("fetch failed in finddone(); return "
|
|
"ISC_R_FAILURE");
|
|
|
|
fctx_done_unref(fctx, ISC_R_FAILURE);
|
|
} else if (want_try) {
|
|
fctx_try(fctx, true);
|
|
}
|
|
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
static bool
|
|
bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
ISC_LIST_FOREACH (fctx->bad, sa, link) {
|
|
if (isc_sockaddr_equal(sa, address)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
mark_bad(fetchctx_t *fctx) {
|
|
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.
|
|
*/
|
|
ISC_LIST_FOREACH (fctx->finds, curr, publink) {
|
|
ISC_LIST_FOREACH (curr->list, addrinfo, publink) {
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark any bad forwarders.
|
|
*/
|
|
ISC_LIST_FOREACH (fctx->forwaddrs, addrinfo, publink) {
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark any bad alternates.
|
|
*/
|
|
ISC_LIST_FOREACH (fctx->altfinds, curr, publink) {
|
|
ISC_LIST_FOREACH (curr->list, addrinfo, publink) {
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
ISC_LIST_FOREACH (fctx->altaddrs, 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_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code,
|
|
spc, isc_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;
|
|
|
|
/* Lame N^2 bubble sort. */
|
|
ISC_LIST_INIT(sorted);
|
|
while (!ISC_LIST_EMPTY(find->list)) {
|
|
unsigned int best_srtt;
|
|
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) {
|
|
unsigned int 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 = NULL;
|
|
dns_adbfindlist_t sorted;
|
|
dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
|
|
|
|
/* Sort each find's addrinfo list by SRTT. */
|
|
ISC_LIST_FOREACH (*findlist, curr, publink) {
|
|
sort_adbfind(curr, bias);
|
|
}
|
|
|
|
/* Lame N^2 bubble sort. */
|
|
ISC_LIST_INIT(sorted);
|
|
while (!ISC_LIST_EMPTY(*findlist)) {
|
|
dns_adbfind_t *curr = NULL;
|
|
unsigned int best_srtt;
|
|
|
|
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) {
|
|
unsigned int curr_srtt;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Return true iff the ADB find has a pending fetch for 'type'. This is
|
|
* used to find out whether we're in a loop, where a fetch is waiting for a
|
|
* find which is waiting for that same fetch.
|
|
*
|
|
* Note: This could be done with either an equivalence check (e.g.,
|
|
* query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If
|
|
* we checked for equivalence, that would mean we could only detect a loop
|
|
* when there is exactly one pending fetch, and we're it. If there were
|
|
* pending fetches for *both* address families, then a loop would be
|
|
* undetected.
|
|
*
|
|
* However, using a bit check means that in theory, an ADB find might be
|
|
* aborted that could have succeeded, if the other fetch had returned an
|
|
* answer.
|
|
*
|
|
* Since there's a good chance the server is broken and won't answer either
|
|
* query, and since an ADB find with two pending fetches is a very rare
|
|
* occurrance anyway, we regard this theoretical SERVFAIL as the lesser
|
|
* evil.
|
|
*/
|
|
static bool
|
|
waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) {
|
|
switch (type) {
|
|
case dns_rdatatype_a:
|
|
return (find->query_pending & DNS_ADBFIND_INET) != 0;
|
|
case dns_rdatatype_aaaa:
|
|
return (find->query_pending & DNS_ADBFIND_INET6) != 0;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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_adbfind_t *find = NULL;
|
|
dns_resolver_t *res = fctx->res;
|
|
bool unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0);
|
|
isc_result_t result;
|
|
|
|
FCTXTRACE("FINDNAME");
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Exempt prefetches from ADB quota.
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
|
|
options |= DNS_ADBFIND_QUOTAEXEMPT;
|
|
}
|
|
|
|
/*
|
|
* Pass through NOVALIDATE to any lookups ADB makes.
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
|
|
options |= DNS_ADBFIND_NOVALIDATE;
|
|
}
|
|
|
|
/*
|
|
* See what we know about this address.
|
|
*/
|
|
INSIST(!SHUTTINGDOWN(fctx));
|
|
fetchctx_ref(fctx);
|
|
result = dns_adb_createfind(fctx->adb, fctx->loop, fctx_finddone, fctx,
|
|
name, fctx->name, fctx->type, options, now,
|
|
res->view->dstport, fctx->depth + 1,
|
|
fctx->qc, fctx->gqc, &find);
|
|
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3), "fctx %p(%s): createfind for %s - %s",
|
|
fctx, fctx->info, fctx->clientstr,
|
|
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_LOGCATEGORY_CNAME,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"skipping nameserver '%s' because it "
|
|
"is a CNAME, while resolving '%s'",
|
|
namebuf, fctx->info);
|
|
}
|
|
fetchctx_detach(&fctx);
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
ISC_LIST_FOREACH (find->list, 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);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We don't know any of the addresses for this name.
|
|
*
|
|
* The find may be waiting on a resolver fetch for a server
|
|
* address. We need to make sure it isn't waiting on *this*
|
|
* fetch, because if it is, we won't be answering it and it
|
|
* won't be answering us.
|
|
*/
|
|
if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) {
|
|
fctx->adberr++;
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO, "loop detected resolving '%s'",
|
|
fctx->info);
|
|
|
|
if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
|
|
atomic_fetch_add_relaxed(&fctx->pending, 1);
|
|
dns_adb_cancelfind(find);
|
|
} else {
|
|
dns_adb_destroyfind(&find);
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We may be waiting for another fetch to complete, and
|
|
* we'll get an event later when the find has what it needs.
|
|
*/
|
|
if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
|
|
atomic_fetch_add_relaxed(&fctx->pending, 1);
|
|
|
|
/*
|
|
* 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)++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* No addresses and no pending events: the find failed.
|
|
*/
|
|
if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
|
|
if (overquota != NULL) {
|
|
*overquota = true;
|
|
}
|
|
fctx->quotacount++; /* quota exceeded */
|
|
} 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);
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
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) {
|
|
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 = false;
|
|
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_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;
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
unsigned int labels;
|
|
dns_name_init(&suffix);
|
|
labels = dns_name_countlabels(name);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
name = &suffix;
|
|
}
|
|
|
|
result = dns_fwdtable_find(res->view->fwdtable, name,
|
|
&forwarders);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
fwd = ISC_LIST_HEAD(forwarders->fwdrs);
|
|
fctx->fwdpolicy = forwarders->fwdpolicy;
|
|
dns_name_copy(&forwarders->name, fctx->fwdname);
|
|
if (fctx->fwdpolicy == dns_fwdpolicy_only &&
|
|
isstrictsubdomain(&forwarders->name, fctx->domain))
|
|
{
|
|
fcount_decr(fctx);
|
|
dns_name_copy(&forwarders->name, fctx->domain);
|
|
result = fcount_incr(fctx, true);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_forwarders_detach(&forwarders);
|
|
return result;
|
|
}
|
|
}
|
|
dns_forwarders_detach(&forwarders);
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (fwd->tlsname != NULL) {
|
|
result = dns_view_gettransport(
|
|
res->view, DNS_TRANSPORT_TLS,
|
|
fwd->tlsname, &ai->transport);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_adb_freeaddrinfo(fctx->adb, &ai);
|
|
goto next;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
next:
|
|
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;
|
|
}
|
|
|
|
now = isc_stdtime_now();
|
|
all_spilled = true; /* resets to false below after the first success */
|
|
|
|
INSIST(ISC_LIST_EMPTY(fctx->finds));
|
|
INSIST(ISC_LIST_EMPTY(fctx->altfinds));
|
|
|
|
DNS_RDATASET_FOREACH (&fctx->nameservers) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
bool overquota = false;
|
|
unsigned int static_stub = 0;
|
|
|
|
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 (STATICSTUB(&fctx->nameservers) &&
|
|
dns_name_equal(&ns.name, fctx->domain))
|
|
{
|
|
static_stub = DNS_ADBFIND_STATICSTUB;
|
|
}
|
|
|
|
if (no_addresses > NS_FAIL_LIMIT &&
|
|
dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT)
|
|
{
|
|
stdoptions |= DNS_ADBFIND_NOFETCH;
|
|
}
|
|
findname(fctx, &ns.name, 0, stdoptions | static_stub, 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) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do we need to use 6 to 4?
|
|
*/
|
|
if (need_alternate) {
|
|
int family;
|
|
family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET;
|
|
ISC_LIST_FOREACH (res->alternates, 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 (atomic_load_acquire(&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 {
|
|
/*
|
|
* We've lost completely. We don't know any
|
|
* addresses, and the ADB has told us it can't
|
|
* get them.
|
|
*/
|
|
FCTXTRACE("no addresses");
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
/*
|
|
* If all of the addresses found were over the
|
|
* fetches-per-server quota, increase the ServerQuota
|
|
* counter and return the configured response.
|
|
*/
|
|
if (all_spilled) {
|
|
result = res->quotaresp[dns_quotatype_server];
|
|
inc_stats(res, dns_resstatscounter_serverquota);
|
|
}
|
|
|
|
/*
|
|
* If we are using a 'forward only' policy, and all
|
|
* the forwarders are bad, increase the ForwardOnlyFail
|
|
* counter.
|
|
*/
|
|
if (fctx->fwdpolicy == dns_fwdpolicy_only) {
|
|
inc_stats(res,
|
|
dns_resstatscounter_forwardonlyfail);
|
|
}
|
|
}
|
|
} 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;
|
|
isc_sockaddr_t *sa = &addr->sockaddr;
|
|
bool aborted = false;
|
|
bool bogus;
|
|
dns_acl_t *blackhole;
|
|
isc_netaddr_t ipaddr;
|
|
dns_peer_t *peer = NULL;
|
|
dns_resolver_t *res = fctx->res;
|
|
const char *msg = NULL;
|
|
|
|
isc_netaddr_fromsockaddr(&ipaddr, sa);
|
|
blackhole = dns_dispatchmgr_getblackhole(res->view->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(ISC_LOG_DEBUG(3))) {
|
|
char buf[ISC_NETADDR_FORMATSIZE];
|
|
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 = NULL, *start = NULL;
|
|
dns_adbaddrinfo_t *addrinfo = NULL, *faddrinfo = NULL;
|
|
|
|
/*
|
|
* Return the next untried address, if any.
|
|
*/
|
|
|
|
/*
|
|
* Find the first unmarked forwarder (if any).
|
|
*/
|
|
ISC_LIST_FOREACH (fctx->forwaddrs, ai, publink) {
|
|
if (!UNMARKED(ai)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, ai);
|
|
if (UNMARKED(ai)) {
|
|
ai->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 ai;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (find != NULL) {
|
|
start = find;
|
|
do {
|
|
ISC_LIST_FOREACH (find->list, ai, publink) {
|
|
if (!UNMARKED(ai)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, ai);
|
|
if (UNMARKED(ai)) {
|
|
ai->flags |= FCTX_ADDRINFO_MARK;
|
|
faddrinfo = ai;
|
|
break;
|
|
}
|
|
}
|
|
if (faddrinfo != NULL) {
|
|
break;
|
|
}
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->finds);
|
|
}
|
|
} while (find != start);
|
|
}
|
|
|
|
fctx->find = find;
|
|
if (faddrinfo != NULL) {
|
|
return faddrinfo;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (find != NULL) {
|
|
start = find;
|
|
do {
|
|
ISC_LIST_FOREACH (find->list, ai, publink) {
|
|
if (!UNMARKED(ai)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, ai);
|
|
if (UNMARKED(ai)) {
|
|
ai->flags |= FCTX_ADDRINFO_MARK;
|
|
faddrinfo = ai;
|
|
break;
|
|
}
|
|
}
|
|
if (faddrinfo != NULL) {
|
|
break;
|
|
}
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->altfinds);
|
|
}
|
|
} while (find != start);
|
|
}
|
|
|
|
/*
|
|
* See if we have a better alternate server by address.
|
|
*/
|
|
ISC_LIST_FOREACH (fctx->altaddrs, ai, publink) {
|
|
if (!UNMARKED(ai)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, ai);
|
|
if (UNMARKED(ai) &&
|
|
(faddrinfo == NULL || ai->srtt < faddrinfo->srtt))
|
|
{
|
|
if (faddrinfo != NULL) {
|
|
faddrinfo->flags &= ~FCTX_ADDRINFO_MARK;
|
|
}
|
|
ai->flags |= FCTX_ADDRINFO_MARK;
|
|
addrinfo = ai;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (addrinfo == NULL) {
|
|
addrinfo = faddrinfo;
|
|
fctx->altfind = find;
|
|
}
|
|
|
|
return addrinfo;
|
|
}
|
|
|
|
static void
|
|
fctx_try(fetchctx_t *fctx, bool retrying) {
|
|
isc_result_t result;
|
|
dns_adbaddrinfo_t *addrinfo = NULL;
|
|
dns_resolver_t *res = NULL;
|
|
|
|
FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc));
|
|
if (fctx->gqc != NULL) {
|
|
FCTXTRACE5("try", "fctx->gqc=", isc_counter_used(fctx->gqc));
|
|
}
|
|
|
|
REQUIRE(!ADDRWAIT(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
res = fctx->res;
|
|
|
|
/* We've already exceeded maximum query count */
|
|
if (isc_counter_used(fctx->qc) > isc_counter_getlimit(fctx->qc)) {
|
|
isc_log_write(
|
|
DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3),
|
|
"exceeded max queries resolving '%s' "
|
|
"(max-recursion-queries, querycount=%u, maxqueries=%u)",
|
|
fctx->info, isc_counter_used(fctx->qc),
|
|
isc_counter_getlimit(fctx->qc));
|
|
result = DNS_R_SERVFAIL;
|
|
goto done;
|
|
}
|
|
|
|
if (fctx->gqc != NULL &&
|
|
isc_counter_used(fctx->gqc) > isc_counter_getlimit(fctx->gqc))
|
|
{
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3),
|
|
"exceeded global max queries resolving '%s' "
|
|
"(max-query-count, querycount=%u, maxqueries=%u)",
|
|
fctx->info, isc_counter_used(fctx->gqc),
|
|
isc_counter_getlimit(fctx->gqc));
|
|
result = DNS_R_SERVFAIL;
|
|
goto done;
|
|
}
|
|
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
/* Try to find an address that isn't over quota */
|
|
while (addrinfo != NULL && dns_adb_overquota(fctx->adb, addrinfo)) {
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
}
|
|
|
|
if (addrinfo == NULL) {
|
|
/* We have no more addresses. Start over. */
|
|
fctx_cancelqueries(fctx, true, false);
|
|
fctx_cleanup(fctx);
|
|
result = fctx_getaddresses(fctx);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case DNS_R_WAIT:
|
|
/* Sleep waiting for addresses. */
|
|
FCTXTRACE("addrwait");
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_ADDRWAIT);
|
|
return;
|
|
default:
|
|
goto done;
|
|
}
|
|
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
while (addrinfo != NULL &&
|
|
dns_adb_overquota(fctx->adb, addrinfo))
|
|
{
|
|
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) {
|
|
result = DNS_R_SERVFAIL;
|
|
goto done;
|
|
}
|
|
}
|
|
/*
|
|
* 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;
|
|
|
|
options &= ~DNS_FETCHOPT_QMINIMIZE;
|
|
|
|
/*
|
|
* 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_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>");
|
|
result = DNS_R_SERVFAIL;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Turn on NOFOLLOW in relaxed mode so that QNAME minimization
|
|
* doesn't cause additional queries to resolve the target of the
|
|
* QNAME minimization request when a referral is returned. This
|
|
* will also reduce the impact of mis-matched NS RRsets where
|
|
* the child's NS RRset is garbage. If a delegation is
|
|
* discovered DNS_R_DELEGATION will be returned to resume_qmin.
|
|
*/
|
|
if ((options & DNS_FETCHOPT_QMIN_STRICT) == 0) {
|
|
options |= DNS_FETCHOPT_NOFOLLOW;
|
|
}
|
|
|
|
fetchctx_ref(fctx);
|
|
result = dns_resolver_createfetch(
|
|
fctx->res, fctx->qminname, fctx->qmintype, fctx->domain,
|
|
&fctx->nameservers, NULL, NULL, 0,
|
|
options | DNS_FETCHOPT_QMINFETCH, 0, fctx->qc,
|
|
fctx->gqc, fctx->loop, resume_qmin, fctx, &fctx->edectx,
|
|
&fctx->qminrrset, &fctx->qminsigrrset,
|
|
&fctx->qminfetch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fetchctx_unref(fctx);
|
|
goto done;
|
|
}
|
|
return;
|
|
}
|
|
|
|
result = isc_counter_increment(fctx->qc);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3),
|
|
"exceeded max queries resolving '%s' "
|
|
"(max-recursion-queries, querycount=%u)",
|
|
fctx->info, isc_counter_used(fctx->qc));
|
|
goto done;
|
|
}
|
|
|
|
if (fctx->gqc != NULL) {
|
|
result = isc_counter_increment(fctx->gqc);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"exceeded global max queries resolving "
|
|
"'%s' (max-query-count, querycount=%u)",
|
|
fctx->info, isc_counter_used(fctx->gqc));
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
result = fctx_query(fctx, addrinfo, fctx->options);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto done;
|
|
}
|
|
if (retrying) {
|
|
inc_stats(res, dns_resstatscounter_retry);
|
|
}
|
|
|
|
done:
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done_detach(&fctx, result);
|
|
}
|
|
}
|
|
|
|
static void
|
|
resume_qmin(void *arg) {
|
|
dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
|
|
fetchctx_t *fctx = resp->arg;
|
|
dns_resolver_t *res = NULL;
|
|
isc_result_t result;
|
|
unsigned int findoptions = 0;
|
|
dns_name_t *fname = NULL, *dcname = NULL;
|
|
dns_fixedname_t ffixed, dcfixed;
|
|
dns_rdataset_t rdataset;
|
|
dns_rdataset_t sigrdataset;
|
|
dns_db_t *db = NULL;
|
|
dns_dbnode_t *node = NULL;
|
|
bool fixup_result = false;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
res = fctx->res;
|
|
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
FCTXTRACE("resume_qmin");
|
|
|
|
fname = dns_fixedname_initname(&ffixed);
|
|
dcname = dns_fixedname_initname(&dcfixed);
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
dns_rdataset_init(&sigrdataset);
|
|
|
|
if (resp->node != NULL) {
|
|
dns_db_attachnode(resp->node, &node);
|
|
dns_db_detachnode(&resp->node);
|
|
}
|
|
if (resp->db != NULL) {
|
|
dns_db_attach(resp->db, &db);
|
|
dns_db_detach(&resp->db);
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(resp->rdataset)) {
|
|
dns_rdataset_clone(resp->rdataset, &rdataset);
|
|
dns_rdataset_disassociate(resp->rdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(resp->sigrdataset)) {
|
|
dns_rdataset_clone(resp->sigrdataset, &sigrdataset);
|
|
dns_rdataset_disassociate(resp->sigrdataset);
|
|
}
|
|
dns_name_copy(resp->foundname, fname);
|
|
|
|
result = resp->result;
|
|
|
|
dns_resolver_freefresp(&resp);
|
|
|
|
LOCK(&fctx->lock);
|
|
if (SHUTTINGDOWN(fctx)) {
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
dns_resolver_destroyfetch(&fctx->qminfetch);
|
|
|
|
/*
|
|
* Beware, the switch() below is little bit tricky - the order of the
|
|
* branches is important.
|
|
*/
|
|
switch (result) {
|
|
case ISC_R_SHUTTINGDOWN:
|
|
case ISC_R_CANCELED:
|
|
goto cleanup;
|
|
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_FORMERR:
|
|
case DNS_R_REMOTEFORMERR:
|
|
case ISC_R_FAILURE:
|
|
case ISC_R_TIMEDOUT:
|
|
if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) != 0) {
|
|
/* These results cause a hard fail in strict mode */
|
|
goto cleanup;
|
|
}
|
|
|
|
if (result == DNS_R_NXDOMAIN &&
|
|
fctx->qmin_labels == dns_name_countlabels(fctx->name))
|
|
{
|
|
LOCK(&fctx->lock);
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (resp != NULL) {
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_clone(&rdataset,
|
|
resp->rdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&sigrdataset) &&
|
|
resp->sigrdataset != NULL)
|
|
{
|
|
dns_rdataset_clone(&sigrdataset,
|
|
resp->sigrdataset);
|
|
}
|
|
if (db != NULL) {
|
|
dns_db_attach(db, &resp->db);
|
|
}
|
|
if (node != NULL) {
|
|
dns_db_attachnode(node, &resp->node);
|
|
}
|
|
dns_name_copy(fname, resp->foundname);
|
|
clone_results(fctx);
|
|
UNLOCK(&fctx->lock);
|
|
goto cleanup;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
}
|
|
|
|
/* ...or disable minimization in relaxed mode */
|
|
fctx->qmin_labels = DNS_NAME_MAXLABELS;
|
|
|
|
/*
|
|
* We store the result. If we succeed in the end
|
|
* we'll issue a warning that the server is
|
|
* broken.
|
|
*/
|
|
fctx->qmin_warning = result;
|
|
break;
|
|
|
|
case ISC_R_SUCCESS:
|
|
case DNS_R_DELEGATION:
|
|
case DNS_R_NXRRSET:
|
|
case DNS_R_NCACHENXRRSET:
|
|
case DNS_R_CNAME:
|
|
case DNS_R_DNAME:
|
|
/*
|
|
* We have previously detected a possible error of an
|
|
* incorrect NXDOMAIN and now have a response that
|
|
* indicates that it was an actual error.
|
|
*/
|
|
if (fctx->qmin_warning == DNS_R_NCACHENXDOMAIN ||
|
|
fctx->qmin_warning == DNS_R_NXDOMAIN)
|
|
{
|
|
fctx->force_qmin_warning = true;
|
|
}
|
|
|
|
/*
|
|
* We have got a CNAME or DNAME respone to the NS query
|
|
* so we are done in almost all cases.
|
|
*/
|
|
if ((result == DNS_R_CNAME || result == DNS_R_DNAME) &&
|
|
fctx->qmin_labels == dns_name_countlabels(fctx->name) &&
|
|
fctx->type != dns_rdatatype_key &&
|
|
fctx->type != dns_rdatatype_nsec &&
|
|
fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_sig &&
|
|
fctx->type != dns_rdatatype_rrsig)
|
|
{
|
|
LOCK(&fctx->lock);
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (resp != NULL) {
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_clone(&rdataset,
|
|
resp->rdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&sigrdataset) &&
|
|
resp->sigrdataset != NULL)
|
|
{
|
|
dns_rdataset_clone(&sigrdataset,
|
|
resp->sigrdataset);
|
|
}
|
|
if (db != NULL) {
|
|
dns_db_attach(db, &resp->db);
|
|
}
|
|
if (node != NULL) {
|
|
dns_db_attachnode(node, &resp->node);
|
|
}
|
|
dns_name_copy(fname, resp->foundname);
|
|
if (result == DNS_R_CNAME &&
|
|
dns_rdataset_isassociated(&rdataset) &&
|
|
fctx->type == dns_rdatatype_cname)
|
|
{
|
|
fixup_result = true;
|
|
}
|
|
clone_results(fctx);
|
|
UNLOCK(&fctx->lock);
|
|
goto cleanup;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
}
|
|
|
|
/*
|
|
* Any other result will *not* cause a failure in strict
|
|
* mode, or cause minimization to be disabled in relaxed
|
|
* mode.
|
|
*
|
|
* If DNS_R_DELEGATION is set here, it implies that
|
|
* DNS_FETCHOPT_NOFOLLOW was set, and a delegation was
|
|
* discovered but not followed; we will do so now.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(5),
|
|
"QNAME minimization: unexpected result %s",
|
|
isc_result_totext(result));
|
|
break;
|
|
}
|
|
|
|
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);
|
|
FCTXTRACEN("resume_qmin findzonecut", fname, result);
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* CNAME or DNAME means zone were added with that record
|
|
* after the start of a recursion. It means we do not have
|
|
* initialized correct hevent->foundname and have to fail.
|
|
*/
|
|
if (result == DNS_R_NXDOMAIN || result == DNS_R_CNAME ||
|
|
result == DNS_R_DNAME)
|
|
{
|
|
result = DNS_R_SERVFAIL;
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
fcount_decr(fctx);
|
|
|
|
dns_name_copy(fname, fctx->domain);
|
|
|
|
result = fcount_incr(fctx, false);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
dns_name_copy(dcname, fctx->qmindcname);
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = true;
|
|
|
|
fctx_minimize_qname(fctx);
|
|
|
|
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_cleanup(fctx);
|
|
}
|
|
|
|
fctx_try(fctx, true);
|
|
|
|
cleanup:
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
if (db != NULL) {
|
|
dns_db_detach(&db);
|
|
}
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&sigrdataset)) {
|
|
dns_rdataset_disassociate(&sigrdataset);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* An error occurred, tear down whole fctx */
|
|
fctx_done_unref(fctx, fixup_result ? ISC_R_SUCCESS : result);
|
|
}
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
static void
|
|
fctx_destroy(fetchctx_t *fctx) {
|
|
dns_resolver_t *res = NULL;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->resps));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->finds));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
|
|
REQUIRE(atomic_load_acquire(&fctx->pending) == 0);
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->validators));
|
|
REQUIRE(fctx->state != fetchstate_active);
|
|
|
|
FCTXTRACE("destroy");
|
|
|
|
fctx->magic = 0;
|
|
|
|
res = fctx->res;
|
|
|
|
dec_stats(res, dns_resstatscounter_nfetch);
|
|
|
|
/* Free bad */
|
|
ISC_LIST_FOREACH (fctx->bad, sa, link) {
|
|
ISC_LIST_UNLINK(fctx->bad, sa, link);
|
|
isc_mem_put(fctx->mctx, sa, sizeof(*sa));
|
|
}
|
|
|
|
ISC_LIST_FOREACH (fctx->edns, tried, link) {
|
|
ISC_LIST_UNLINK(fctx->edns, tried, link);
|
|
isc_mem_put(fctx->mctx, tried, sizeof(*tried));
|
|
}
|
|
|
|
if (fctx->nfails != NULL) {
|
|
isc_counter_detach(&fctx->nfails);
|
|
}
|
|
if (fctx->nvalidations != NULL) {
|
|
isc_counter_detach(&fctx->nvalidations);
|
|
}
|
|
isc_counter_detach(&fctx->qc);
|
|
if (fctx->gqc != NULL) {
|
|
isc_counter_detach(&fctx->gqc);
|
|
}
|
|
fcount_decr(fctx);
|
|
dns_message_detach(&fctx->qmessage);
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
dns_db_detach(&fctx->cache);
|
|
dns_adb_detach(&fctx->adb);
|
|
|
|
dns_resolver_detach(&fctx->res);
|
|
|
|
dns_ede_invalidate(&fctx->edectx);
|
|
|
|
isc_mutex_destroy(&fctx->lock);
|
|
|
|
isc_mem_free(fctx->mctx, fctx->info);
|
|
isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx));
|
|
}
|
|
|
|
static void
|
|
fctx_expired(void *arg) {
|
|
fetchctx_t *fctx = (fetchctx_t *)arg;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
FCTXTRACE(isc_result_totext(ISC_R_TIMEDOUT));
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO, "gave up on resolving '%s'", fctx->info);
|
|
|
|
dns_ede_add(&fctx->edectx, DNS_EDE_NOREACHABLEAUTH, NULL);
|
|
|
|
fctx_done_detach(&fctx, DNS_R_SERVFAIL);
|
|
}
|
|
|
|
static void
|
|
fctx_shutdown(void *arg) {
|
|
fetchctx_t *fctx = arg;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
fctx_done_unref(fctx, ISC_R_SHUTTINGDOWN);
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
static void
|
|
fctx_start(void *arg) {
|
|
fetchctx_t *fctx = (fetchctx_t *)arg;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
FCTXTRACE("start");
|
|
|
|
LOCK(&fctx->lock);
|
|
if (SHUTTINGDOWN(fctx)) {
|
|
UNLOCK(&fctx->lock);
|
|
goto detach;
|
|
}
|
|
|
|
/*
|
|
* Normal fctx startup.
|
|
*/
|
|
fctx->state = fetchstate_active;
|
|
UNLOCK(&fctx->lock);
|
|
|
|
/*
|
|
* As a backstop, we also set a timer to stop the fetch
|
|
* if in-band netmgr timeouts don't work. It will fire two
|
|
* seconds after the fetch should have finished. (This
|
|
* should be enough of a gap to avoid the timer firing
|
|
* while a response is being processed normally.)
|
|
*/
|
|
fctx_starttimer(fctx);
|
|
fctx_try(fctx, false);
|
|
|
|
detach:
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
/*
|
|
* Fetch Creation, Joining, and Cancellation.
|
|
*/
|
|
|
|
static void
|
|
fctx_add_event(fetchctx_t *fctx, isc_loop_t *loop, const isc_sockaddr_t *client,
|
|
dns_messageid_t id, isc_job_cb cb, void *arg,
|
|
dns_edectx_t *edectx, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset, dns_fetch_t *fetch) {
|
|
dns_fetchresponse_t *resp = NULL;
|
|
|
|
FCTXTRACE("addevent");
|
|
|
|
resp = isc_mem_get(fctx->mctx, sizeof(*resp));
|
|
*resp = (dns_fetchresponse_t){
|
|
.result = DNS_R_SERVFAIL,
|
|
.qtype = fctx->type,
|
|
.rdataset = rdataset,
|
|
.sigrdataset = sigrdataset,
|
|
.fetch = fetch,
|
|
.client = client,
|
|
.id = id,
|
|
.loop = loop,
|
|
.cb = cb,
|
|
.arg = arg,
|
|
.link = ISC_LINK_INITIALIZER,
|
|
.edectx = edectx,
|
|
};
|
|
isc_mem_attach(fctx->mctx, &resp->mctx);
|
|
|
|
resp->foundname = dns_fixedname_initname(&resp->fname);
|
|
|
|
/*
|
|
* Store the sigrdataset in the first resp in case it is needed
|
|
* by any of the events.
|
|
*/
|
|
if (resp->sigrdataset != NULL) {
|
|
ISC_LIST_PREPEND(fctx->resps, resp, link);
|
|
} else {
|
|
ISC_LIST_APPEND(fctx->resps, resp, link);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_join(fetchctx_t *fctx, isc_loop_t *loop, const isc_sockaddr_t *client,
|
|
dns_messageid_t id, isc_job_cb cb, void *arg, dns_edectx_t *edectx,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
|
|
dns_fetch_t *fetch) {
|
|
FCTXTRACE("join");
|
|
|
|
REQUIRE(!SHUTTINGDOWN(fctx));
|
|
|
|
fctx_add_event(fctx, loop, client, id, cb, arg, edectx, rdataset,
|
|
sigrdataset, fetch);
|
|
|
|
fetch->magic = DNS_FETCH_MAGIC;
|
|
fetchctx_attach(fctx, &fetch->private);
|
|
}
|
|
|
|
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_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, isc_loop_t *loop, const dns_name_t *name,
|
|
dns_rdatatype_t type, const dns_name_t *domain,
|
|
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
|
|
unsigned int options, unsigned int depth, isc_counter_t *qc,
|
|
isc_counter_t *gqc, fetchctx_t **fctxp) {
|
|
fetchctx_t *fctx = NULL;
|
|
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 = isc_loop_getmctx(loop);
|
|
size_t p;
|
|
uint32_t nvalidations = atomic_load_relaxed(&res->maxvalidations);
|
|
uint32_t nfails = atomic_load_relaxed(&res->maxvalidationfails);
|
|
|
|
/*
|
|
* Caller must be holding the lock for 'bucket'
|
|
*/
|
|
REQUIRE(fctxp != NULL && *fctxp == NULL);
|
|
|
|
fctx = isc_mem_get(mctx, sizeof(*fctx));
|
|
*fctx = (fetchctx_t){
|
|
.type = type,
|
|
.qmintype = type,
|
|
.options = options,
|
|
.tid = isc_tid(),
|
|
.state = fetchstate_active,
|
|
.depth = depth,
|
|
.qmin_labels = 1,
|
|
.fwdpolicy = dns_fwdpolicy_none,
|
|
.result = ISC_R_FAILURE,
|
|
.loop = loop,
|
|
};
|
|
|
|
isc_mem_attach(mctx, &fctx->mctx);
|
|
dns_resolver_attach(res, &fctx->res);
|
|
|
|
isc_mutex_init(&fctx->lock);
|
|
|
|
dns_ede_init(fctx->mctx, &fctx->edectx);
|
|
|
|
/*
|
|
* Make fctx->info point to a copy of a formatted string
|
|
* "name/type". FCTXTRACE won't work until this is done.
|
|
*/
|
|
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(fctx->mctx, buf);
|
|
|
|
FCTXTRACE("create");
|
|
|
|
if (nfails > 0) {
|
|
isc_counter_create(mctx, nfails, &fctx->nfails);
|
|
}
|
|
|
|
if (nvalidations > 0) {
|
|
isc_counter_create(mctx, nvalidations, &fctx->nvalidations);
|
|
}
|
|
|
|
if (qc != NULL) {
|
|
isc_counter_attach(qc, &fctx->qc);
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(9),
|
|
"fctx %p(%s): attached to counter %p (%d)", fctx,
|
|
fctx->info, fctx->qc, isc_counter_used(fctx->qc));
|
|
} else {
|
|
isc_counter_create(fctx->mctx, res->maxqueries, &fctx->qc);
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(9),
|
|
"fctx %p(%s): created counter %p", fctx,
|
|
fctx->info, fctx->qc);
|
|
}
|
|
|
|
if (gqc != NULL) {
|
|
isc_counter_attach(gqc, &fctx->gqc);
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(9),
|
|
"fctx %p(%s): attached to counter %p (%d)", fctx,
|
|
fctx->info, fctx->gqc,
|
|
isc_counter_used(fctx->gqc));
|
|
}
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
fprintf(stderr, "fetchctx__init:%s:%s:%d:%p:%p->references = 1\n",
|
|
__func__, __FILE__, __LINE__, fctx, fctx);
|
|
#endif
|
|
isc_refcount_init(&fctx->references, 1);
|
|
|
|
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);
|
|
ISC_LIST_INIT(fctx->bad);
|
|
ISC_LIST_INIT(fctx->edns);
|
|
ISC_LIST_INIT(fctx->validators);
|
|
|
|
atomic_init(&fctx->attributes, 0);
|
|
|
|
fctx->name = dns_fixedname_initname(&fctx->fname);
|
|
fctx->nsname = dns_fixedname_initname(&fctx->nsfname);
|
|
fctx->domain = dns_fixedname_initname(&fctx->dfname);
|
|
fctx->qminname = dns_fixedname_initname(&fctx->qminfname);
|
|
fctx->qmindcname = dns_fixedname_initname(&fctx->qmindcfname);
|
|
fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname);
|
|
|
|
dns_name_copy(name, fctx->name);
|
|
dns_name_copy(name, fctx->qminname);
|
|
|
|
dns_rdataset_init(&fctx->nameservers);
|
|
dns_rdataset_init(&fctx->qminrrset);
|
|
dns_rdataset_init(&fctx->qminsigrrset);
|
|
dns_rdataset_init(&fctx->nsrrset);
|
|
|
|
fctx->start = isc_time_now();
|
|
fctx->now = (isc_stdtime_t)fctx->start.seconds;
|
|
|
|
if (client != NULL) {
|
|
isc_sockaddr_format(client, fctx->clientstr,
|
|
sizeof(fctx->clientstr));
|
|
} else {
|
|
strlcpy(fctx->clientstr, "<unknown>", sizeof(fctx->clientstr));
|
|
}
|
|
|
|
if (domain == NULL) {
|
|
dns_forwarders_t *forwarders = NULL;
|
|
unsigned int labels;
|
|
const dns_name_t *fwdname = name;
|
|
dns_name_t suffix;
|
|
|
|
/*
|
|
* 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);
|
|
labels = dns_name_countlabels(name);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
fwdname = &suffix;
|
|
}
|
|
|
|
/* Find the forwarder for this name. */
|
|
result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname,
|
|
&forwarders);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
fctx->fwdpolicy = forwarders->fwdpolicy;
|
|
dns_name_copy(&forwarders->name, fctx->fwdname);
|
|
dns_forwarders_detach(&forwarders);
|
|
}
|
|
|
|
if (fctx->fwdpolicy == dns_fwdpolicy_only) {
|
|
/*
|
|
* We're in forward-only mode. Set the query
|
|
* domain.
|
|
*/
|
|
dns_name_copy(fctx->fwdname, fctx->domain);
|
|
dns_name_copy(fctx->fwdname, fctx->qmindcname);
|
|
/*
|
|
* Disable query minimization
|
|
*/
|
|
options &= ~DNS_FETCHOPT_QMINIMIZE;
|
|
} else {
|
|
dns_fixedname_t dcfixed;
|
|
dns_name_t *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, fctx->fwdname, dcname,
|
|
fctx->now, findoptions, true, true,
|
|
&fctx->nameservers, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_nameservers;
|
|
}
|
|
|
|
dns_name_copy(fctx->fwdname, fctx->domain);
|
|
dns_name_copy(dcname, fctx->qmindcname);
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = true;
|
|
}
|
|
} else {
|
|
dns_name_copy(domain, fctx->domain);
|
|
dns_name_copy(domain, fctx->qmindcname);
|
|
dns_rdataset_clone(nameservers, &fctx->nameservers);
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = true;
|
|
}
|
|
|
|
/*
|
|
* Exempt prefetch queries from the fetches-per-zone quota check
|
|
* also exempt QMIN fetches as the calling fetch has already
|
|
* successfully called fcount_incr for this zone.
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) == 0 &&
|
|
(fctx->options & DNS_FETCHOPT_QMINFETCH) == 0)
|
|
{
|
|
/*
|
|
* 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_nameservers;
|
|
}
|
|
}
|
|
|
|
log_ns_ttl(fctx, "fctx_create");
|
|
|
|
if (!dns_name_issubdomain(fctx->name, fctx->domain)) {
|
|
dns_name_format(fctx->domain, buf, sizeof(buf));
|
|
UNEXPECTED_ERROR("'%s' is not subdomain of '%s'", fctx->info,
|
|
buf);
|
|
result = ISC_R_UNEXPECTED;
|
|
goto cleanup_fcount;
|
|
}
|
|
|
|
dns_message_create(fctx->mctx, fctx->res->namepools[fctx->tid],
|
|
fctx->res->rdspools[fctx->tid],
|
|
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("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);
|
|
|
|
/*
|
|
* Attach to the view's cache and adb.
|
|
*/
|
|
dns_db_attach(res->view->cachedb, &fctx->cache);
|
|
dns_view_getadb(res->view, &fctx->adb);
|
|
|
|
ISC_LIST_INIT(fctx->resps);
|
|
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);
|
|
fctx_minimize_qname(fctx);
|
|
}
|
|
|
|
inc_stats(res, dns_resstatscounter_nfetch);
|
|
|
|
isc_timer_create(fctx->loop, fctx_expired, fctx, &fctx->timer);
|
|
|
|
*fctxp = fctx;
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
cleanup_qmessage:
|
|
dns_message_detach(&fctx->qmessage);
|
|
|
|
cleanup_fcount:
|
|
fcount_decr(fctx);
|
|
|
|
cleanup_nameservers:
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
isc_mem_free(fctx->mctx, fctx->info);
|
|
if (fctx->nfails != NULL) {
|
|
isc_counter_detach(&fctx->nfails);
|
|
}
|
|
if (fctx->nvalidations != NULL) {
|
|
isc_counter_detach(&fctx->nvalidations);
|
|
}
|
|
isc_counter_detach(&fctx->qc);
|
|
if (fctx->gqc != NULL) {
|
|
isc_counter_detach(&fctx->gqc);
|
|
}
|
|
|
|
dns_resolver_detach(&fctx->res);
|
|
isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx));
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Handle Responses
|
|
*/
|
|
static bool
|
|
is_lame(fetchctx_t *fctx, dns_message_t *message) {
|
|
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;
|
|
}
|
|
|
|
MSG_SECTION_FOREACH (message, DNS_SECTION_AUTHORITY, name) {
|
|
ISC_LIST_FOREACH (name->list, 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;
|
|
}
|
|
}
|
|
|
|
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_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_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) {
|
|
dns_name_t *name = NULL;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
|
|
/*
|
|
* Caller must be holding the fctx lock.
|
|
*/
|
|
|
|
/*
|
|
* XXXRTH Currently we support only one question.
|
|
*/
|
|
if (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 (message->counts[DNS_SECTION_QUESTION] > 1) {
|
|
log_formerr(fctx, "too many questions");
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
if (ISC_LIST_EMPTY(message->sections[DNS_SECTION_QUESTION])) {
|
|
return ISC_R_NOMORE;
|
|
}
|
|
name = ISC_LIST_HEAD(message->sections[DNS_SECTION_QUESTION]);
|
|
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_fetchresponse_t *hresp = NULL;
|
|
|
|
REQUIRE(!ISC_LIST_EMPTY(fctx->resps));
|
|
|
|
/*
|
|
* Set up any other resps to have the same data as the first.
|
|
* Caller must be holding the appropriate lock.
|
|
*/
|
|
|
|
FCTXTRACE("clone_results");
|
|
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
/* This is the head resp; keep a pointer and move on */
|
|
if (hresp == NULL) {
|
|
hresp = ISC_LIST_HEAD(fctx->resps);
|
|
FCTXTRACEN("clone_results", hresp->foundname,
|
|
hresp->result);
|
|
continue;
|
|
}
|
|
|
|
resp->result = hresp->result;
|
|
dns_name_copy(hresp->foundname, resp->foundname);
|
|
dns_db_attach(hresp->db, &resp->db);
|
|
dns_db_attachnode(hresp->node, &resp->node);
|
|
|
|
INSIST(hresp->rdataset != NULL && resp->rdataset != NULL);
|
|
if (dns_rdataset_isassociated(hresp->rdataset)) {
|
|
dns_rdataset_clone(hresp->rdataset, resp->rdataset);
|
|
}
|
|
|
|
INSIST(hresp->sigrdataset != NULL || resp->sigrdataset == NULL);
|
|
if (resp->sigrdataset != NULL && hresp->sigrdataset != NULL &&
|
|
dns_rdataset_isassociated(hresp->sigrdataset))
|
|
{
|
|
dns_rdataset_clone(hresp->sigrdataset,
|
|
resp->sigrdataset);
|
|
}
|
|
}
|
|
|
|
fctx->cloned = true;
|
|
}
|
|
|
|
#define CACHE(r) (((r)->attributes.cache))
|
|
#define ANSWER(r) (((r)->attributes.answer))
|
|
#define ANSWERSIG(r) (((r)->attributes.answersig))
|
|
#define EXTERNAL(r) (((r)->attributes.external))
|
|
#define CHAINING(r) (((r)->attributes.chaining))
|
|
#define CHASE(r) (((r)->attributes.chase))
|
|
#define CHECKNAMES(r) (((r)->attributes.checknames))
|
|
|
|
/*
|
|
* Cancel validators associated with '*fctx' if it is ready to be
|
|
* destroyed (i.e., no queries waiting for it and no pending ADB finds).
|
|
* Caller must hold fctx bucket lock.
|
|
*
|
|
* Requires:
|
|
* '*fctx' is shutting down.
|
|
*/
|
|
static void
|
|
maybe_cancel_validators(fetchctx_t *fctx) {
|
|
if (atomic_load_acquire(&fctx->pending) != 0 ||
|
|
atomic_load_acquire(&fctx->nqueries) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
REQUIRE(SHUTTINGDOWN(fctx));
|
|
ISC_LIST_FOREACH (fctx->validators, validator, link) {
|
|
dns_validator_cancel(validator);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* typemap with just RRSIG(46) and NSEC(47) bits set.
|
|
*
|
|
* Bitmap calculation from dns_nsec_setbit:
|
|
*
|
|
* 46 47
|
|
* shift = 7 - (type % 8); 0 1
|
|
* mask = 1 << shift; 0x02 0x01
|
|
* array[type / 8] |= mask;
|
|
*
|
|
* Window (0), bitmap length (6), and bitmap.
|
|
*/
|
|
static const unsigned char minimal_typemap[] = { 0, 6, 0, 0, 0, 0, 0, 0x03 };
|
|
|
|
static bool
|
|
is_minimal_nsec(dns_rdataset_t *nsecset) {
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
DNS_RDATASET_FOREACH (&rdataset) {
|
|
isc_result_t result;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_nsec_t nsec;
|
|
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &nsec, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (nsec.len == sizeof(minimal_typemap) &&
|
|
memcmp(nsec.typebits, minimal_typemap, nsec.len) == 0)
|
|
{
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return true;
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If there is a SOA record in the type map then there must be a DNSKEY.
|
|
*/
|
|
static bool
|
|
check_soa_and_dnskey(dns_rdataset_t *nsecset) {
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
DNS_RDATASET_FOREACH (&rdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
if (dns_nsec_typepresent(&rdata, dns_rdatatype_soa) &&
|
|
(!dns_nsec_typepresent(&rdata, dns_rdatatype_dnskey) ||
|
|
!dns_nsec_typepresent(&rdata, dns_rdatatype_ns)))
|
|
{
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return false;
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Look for NSEC next name that starts with the label '\000'.
|
|
*/
|
|
static bool
|
|
has_000_label(dns_rdataset_t *nsecset) {
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
DNS_RDATASET_FOREACH (&rdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
if (rdata.length > 1 && rdata.data[0] == 1 &&
|
|
rdata.data[1] == 0)
|
|
{
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return true;
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&rdataset);
|
|
return false;
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
|
|
isc_result_t eresult = ISC_R_SUCCESS;
|
|
|
|
if (NEGATIVE(rdataset)) {
|
|
eresult = NXDOMAIN(rdataset) ? DNS_R_NCACHENXDOMAIN
|
|
: DNS_R_NCACHENXRRSET;
|
|
} else if (eresult == ISC_R_SUCCESS && rdataset->type != fctx->type) {
|
|
switch (rdataset->type) {
|
|
case dns_rdatatype_cname:
|
|
eresult = DNS_R_CNAME;
|
|
break;
|
|
case dns_rdatatype_dname:
|
|
eresult = DNS_R_DNAME;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return eresult;
|
|
}
|
|
|
|
static inline dns_trust_t
|
|
gettrust(dns_rdataset_t *rdataset) {
|
|
if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) {
|
|
return dns_trust_none;
|
|
}
|
|
|
|
return rdataset->trust;
|
|
}
|
|
|
|
static inline dns_rdataset_t *
|
|
getrrsig(dns_name_t *name, dns_rdatatype_t type) {
|
|
for (dns_rdataset_t *sig = ISC_LIST_HEAD(name->list); sig != NULL;
|
|
sig = ISC_LIST_NEXT(sig, link))
|
|
{
|
|
if (dns_rdataset_issigtype(sig, type)) {
|
|
return sig;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
|
|
dns_dbnode_t **nodep, dns_rdataset_t *added,
|
|
dns_rdataset_t *addedsig) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
unsigned int options = 0;
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
if (rdataset == NULL) {
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
/*
|
|
* If the trust level is glue, we must be caching a referral.
|
|
* New referral data always takes precedence over the existing
|
|
* cache contents. We also force a cache update if the fctx
|
|
* has the _NOCACHED option.
|
|
*/
|
|
if ((gettrust(rdataset) == dns_trust_glue &&
|
|
dns_rdataset_matchestype(rdataset, dns_rdatatype_ns)) ||
|
|
(fctx->options & DNS_FETCHOPT_NOCACHED) != 0)
|
|
{
|
|
options = DNS_DBADD_FORCE;
|
|
} else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
|
|
options = DNS_DBADD_PREFETCH;
|
|
}
|
|
|
|
/*
|
|
* If the node pointer points to a node, attach to it.
|
|
*
|
|
* If it points to NULL, find or create the node and pass
|
|
* it back to the caller.
|
|
*
|
|
* If there's no node pointer at all, find the node, but
|
|
* detach it before returning.
|
|
*/
|
|
if (nodep != NULL && *nodep != NULL) {
|
|
dns_db_attachnode(*nodep, &node);
|
|
} else {
|
|
result = dns_db_findnode(fctx->cache, name, true, &node);
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
|
|
rdataset, options, added);
|
|
}
|
|
if ((result == ISC_R_SUCCESS || result == DNS_R_UNCHANGED) &&
|
|
sigrdataset != NULL)
|
|
{
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
|
|
sigrdataset, options, addedsig);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we're passing a node that we looked up back to the
|
|
* caller, then we don't detach it.
|
|
*/
|
|
if (nodep != NULL && *nodep == NULL) {
|
|
*nodep = node;
|
|
} else if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
delete_rrset(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type,
|
|
bool delrrsig) {
|
|
isc_result_t result;
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
result = dns_db_findnode(fctx->cache, name, false, &node);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_db_deleterdataset(fctx->cache, node, NULL, type, 0);
|
|
if (delrrsig) {
|
|
dns_db_deleterdataset(fctx->cache, node, NULL,
|
|
dns_rdatatype_rrsig, type);
|
|
}
|
|
}
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_cacheauthority(fetchctx_t *fctx, dns_message_t *message,
|
|
isc_stdtime_t now) {
|
|
isc_result_t result;
|
|
|
|
/*
|
|
* Cache any SOA/NS/NSEC records that happened to be validated.
|
|
*/
|
|
MSG_SECTION_FOREACH (message, DNS_SECTION_AUTHORITY, name) {
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
|
|
if ((rdataset->type != dns_rdatatype_ns &&
|
|
rdataset->type != dns_rdatatype_soa &&
|
|
rdataset->type != dns_rdatatype_nsec) ||
|
|
gettrust(rdataset) != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset = getrrsig(name, rdataset->type);
|
|
if (gettrust(sigrdataset) != dns_trust_secure) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Don't cache NSEC if missing NSEC or RRSIG types.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
!dns_nsec_requiredtypespresent(rdataset))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Don't cache "white lies" but do cache
|
|
* "black lies".
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
!dns_name_equal(fctx->name, name) &&
|
|
is_minimal_nsec(rdataset))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Check SOA and DNSKEY consistency.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
!check_soa_and_dnskey(rdataset))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Look for \000 label in next name.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
has_000_label(rdataset))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = cache_rrset(fctx, now, name, rdataset,
|
|
sigrdataset, NULL, NULL, NULL);
|
|
if (result != ISC_R_SUCCESS &&
|
|
result != DNS_R_UNCHANGED)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The validator has finished.
|
|
*/
|
|
static void
|
|
validated(void *arg) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
dns_validator_t *val = (dns_validator_t *)arg;
|
|
dns_valarg_t *valarg = val->arg;
|
|
dns_validator_t *nextval = NULL;
|
|
dns_adbaddrinfo_t *addrinfo = NULL;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_fetchresponse_t *resp = NULL;
|
|
dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL;
|
|
dns_message_t *message = NULL;
|
|
fetchctx_t *fctx = NULL;
|
|
dns_resolver_t *res = NULL;
|
|
isc_stdtime_t now;
|
|
bool done = false;
|
|
bool negative = (val->rdataset == NULL);
|
|
bool chaining = (val->result == ISC_R_SUCCESS) && !negative &&
|
|
CHAINING(val->rdataset);
|
|
|
|
fctx = valarg->fctx;
|
|
valarg->fctx = NULL;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
res = fctx->res;
|
|
addrinfo = valarg->addrinfo;
|
|
|
|
message = val->message;
|
|
fctx->vresult = val->result;
|
|
|
|
FCTXTRACE("received validation completion event");
|
|
|
|
isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
ISC_LIST_UNLINK(fctx->validators, val, link);
|
|
|
|
if (SHUTTINGDOWN(fctx)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
now = isc_stdtime_now();
|
|
|
|
/* Validation failed. */
|
|
if (val->result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("validation failed");
|
|
inc_stats(res, dns_resstatscounter_valfail);
|
|
fctx->valfail++;
|
|
fctx->vresult = val->result;
|
|
if (fctx->vresult != DNS_R_BROKENCHAIN) {
|
|
if (val->rdataset != NULL) {
|
|
delete_rrset(fctx, val->name, val->type,
|
|
val->sigrdataset != NULL);
|
|
}
|
|
} else if (!negative) {
|
|
/*
|
|
* Cache the data as pending for later validation.
|
|
*/
|
|
cache_rrset(fctx, now, val->name, val->rdataset,
|
|
val->sigrdataset, NULL, NULL, NULL);
|
|
}
|
|
|
|
add_bad(fctx, message, addrinfo, result, badns_validation);
|
|
|
|
/* Start the next validator if there is one. */
|
|
nextval = ISC_LIST_HEAD(fctx->validators);
|
|
if (nextval != NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* A broken trust chain isn't recoverable. */
|
|
if (result == DNS_R_BROKENCHAIN) {
|
|
done = true;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Some other error, we can try again. We have to
|
|
* unlock the fctx before calling fctx_try().
|
|
*/
|
|
UNLOCK(&fctx->lock);
|
|
fctx_try(fctx, true);
|
|
goto cleanup_unlocked;
|
|
}
|
|
|
|
/*
|
|
* For non-ANY responses, and all negative and chaining responses,
|
|
* we pass an rdataset back to the caller. Otherwise the caller
|
|
* iterates the node.
|
|
*/
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (resp != NULL &&
|
|
(negative || chaining || !dns_rdatatype_ismulti(fctx->type)))
|
|
{
|
|
ardataset = resp->rdataset;
|
|
asigrdataset = resp->sigrdataset;
|
|
}
|
|
|
|
/*
|
|
* Validator proved nonexistence.
|
|
*/
|
|
if (negative) {
|
|
FCTXTRACE("nonexistence validation OK");
|
|
inc_stats(res, dns_resstatscounter_valnegsuccess);
|
|
|
|
result = negcache(message, fctx, val->name, now, val->optout,
|
|
val->secure, ardataset, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
done = true;
|
|
goto cleanup;
|
|
}
|
|
goto answer_response;
|
|
}
|
|
|
|
/*
|
|
* Validator proved a positive answer.
|
|
*/
|
|
FCTXTRACE("validation OK");
|
|
inc_stats(res, dns_resstatscounter_valsuccess);
|
|
|
|
if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
|
|
result = dns_rdataset_addnoqname(
|
|
val->rdataset, val->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
INSIST(val->sigrdataset != NULL);
|
|
val->sigrdataset->ttl = val->rdataset->ttl;
|
|
if (val->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
|
|
result = dns_rdataset_addclosest(
|
|
val->rdataset,
|
|
val->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
} else if (gettrust(val->rdataset) == dns_trust_answer) {
|
|
findnoqname(fctx, message, val->name, val->rdataset,
|
|
val->sigrdataset);
|
|
}
|
|
|
|
/*
|
|
* The data was already cached as pending. Re-cache it as secure.
|
|
*/
|
|
result = cache_rrset(fctx, now, val->name, val->rdataset,
|
|
val->sigrdataset, &node, ardataset, asigrdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
done = true;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* If this was an ANY query, we might have more rdatasets
|
|
* needing to be validated before we can respond.
|
|
*/
|
|
if (!ISC_LIST_EMPTY(fctx->validators)) {
|
|
INSIST(!negative);
|
|
INSIST(dns_rdatatype_ismulti(fctx->type));
|
|
|
|
nextval = ISC_LIST_HEAD(fctx->validators);
|
|
goto cleanup;
|
|
}
|
|
|
|
answer_response:
|
|
fctx_cacheauthority(fctx, message, now);
|
|
|
|
/*
|
|
* Cache the wild card entry.
|
|
*/
|
|
if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL &&
|
|
gettrust(val->rdataset) == dns_trust_secure &&
|
|
gettrust(val->sigrdataset) == dns_trust_secure)
|
|
{
|
|
cache_rrset(fctx, now, dns_fixedname_name(&val->wild),
|
|
val->rdataset, val->sigrdataset, NULL, NULL, NULL);
|
|
}
|
|
|
|
/*
|
|
* We're responding with an answer, positive or negative,
|
|
* not an error.
|
|
*/
|
|
if (resp != NULL) {
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
|
|
resp->result = fctx_setresult(fctx, resp->rdataset);
|
|
dns_name_copy(val->name, resp->foundname);
|
|
dns_db_attach(fctx->cache, &resp->db);
|
|
dns_db_transfernode(fctx->cache, &node, &resp->node);
|
|
clone_results(fctx);
|
|
}
|
|
|
|
done = true;
|
|
|
|
cleanup:
|
|
UNLOCK(&fctx->lock);
|
|
cleanup_unlocked:
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
if (nextval != NULL) {
|
|
dns_validator_send(nextval);
|
|
}
|
|
|
|
if (done) {
|
|
fctx_done_unref(fctx, result);
|
|
}
|
|
|
|
/*
|
|
* val->name points to name on a message on one of the
|
|
* queries on the fetch context so the name has to be
|
|
* released first with a dns_validator_shutdown() call.
|
|
*/
|
|
dns_validator_shutdown(val);
|
|
dns_validator_detach(&val);
|
|
fetchctx_detach(&fctx);
|
|
INSIST(node == NULL);
|
|
}
|
|
|
|
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_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level,
|
|
"fctx %p(%s): %s", fctx, fctx->info, msgbuf);
|
|
}
|
|
|
|
static void
|
|
findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
|
|
isc_result_t result;
|
|
dns_rdata_rrsig_t rrsig;
|
|
unsigned int labels;
|
|
dns_name_t *zonename = NULL;
|
|
dns_fixedname_t fzonename;
|
|
dns_name_t *closest = NULL;
|
|
dns_fixedname_t fclosest;
|
|
dns_name_t *nearest = NULL;
|
|
dns_fixedname_t fnearest;
|
|
dns_rdatatype_t found = dns_rdatatype_none;
|
|
dns_name_t *noqname = NULL;
|
|
dns_rdatatype_t type = rdataset->type;
|
|
|
|
FCTXTRACE("findnoqname");
|
|
|
|
if (dns_rdatatype_issig(rdataset->type) || sigrdataset == NULL) {
|
|
return;
|
|
}
|
|
|
|
labels = dns_name_countlabels(name);
|
|
|
|
result = ISC_R_NOTFOUND;
|
|
DNS_RDATASET_FOREACH (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;
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
zonename = dns_fixedname_initname(&fzonename);
|
|
closest = dns_fixedname_initname(&fclosest);
|
|
nearest = dns_fixedname_initname(&fnearest);
|
|
|
|
#define NXND(x) ((x) == ISC_R_SUCCESS)
|
|
|
|
MSG_SECTION_FOREACH (message, DNS_SECTION_AUTHORITY, nsec) {
|
|
ISC_LIST_FOREACH (nsec->list, nrdataset, link) {
|
|
bool data = false, exists = false;
|
|
bool optout = false, unknown = false;
|
|
bool setclosest = false;
|
|
bool setnearest = false;
|
|
|
|
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 (noqname != NULL) {
|
|
sigrdataset = getrrsig(noqname, found);
|
|
if (sigrdataset == NULL) {
|
|
noqname = NULL;
|
|
}
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS && noqname != NULL) {
|
|
(void)dns_rdataset_addnoqname(rdataset, noqname);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_cacheable(dns_name_t *name, dns_rdataset_t *rdataset, bool fail) {
|
|
/* This rdataset isn't marked for caching */
|
|
if (!CACHE(rdataset)) {
|
|
return DNS_R_CONTINUE;
|
|
}
|
|
|
|
/* See if there are any name errors */
|
|
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_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)) {
|
|
return DNS_R_BADNAME;
|
|
}
|
|
|
|
return DNS_R_CONTINUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We do not attempt to cache or validate glue or out-of-bailiwick
|
|
* data - even if there might be some performance benefit to doing
|
|
* so - because it makes it simpler and safer.
|
|
*/
|
|
if (EXTERNAL(rdataset)) {
|
|
return DNS_R_CONTINUE;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
fixttls(dns_view_t *view, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset) {
|
|
/*
|
|
* Enforce the configured maximum and minimum cache TTL.
|
|
*/
|
|
if (rdataset->ttl > view->maxcachettl) {
|
|
rdataset->ttl = view->maxcachettl;
|
|
}
|
|
|
|
if (rdataset->ttl < view->mincachettl) {
|
|
rdataset->ttl = view->mincachettl;
|
|
}
|
|
|
|
/*
|
|
* Mark the rdataset as being prefetch eligible.
|
|
*/
|
|
if (rdataset->ttl >= view->prefetch_eligible) {
|
|
rdataset->attributes.prefetch = true;
|
|
}
|
|
|
|
/* Normalize the rdataset and sigrdataset TTLs */
|
|
if (sigrdataset != NULL) {
|
|
rdataset->ttl = ISC_MIN(rdataset->ttl, sigrdataset->ttl);
|
|
sigrdataset->ttl = rdataset->ttl;
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
rctx_cache_secure(respctx_t *rctx, dns_message_t *message, dns_name_t *name,
|
|
dns_dbnode_t *node, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset, bool need_validation) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
dns_rdataset_t *ardataset = NULL, *asigset = NULL;
|
|
dns_fetchresponse_t *resp = ISC_LIST_HEAD(fctx->resps);
|
|
|
|
/*
|
|
* RRSIGs are validated as part of validating the type they cover.
|
|
*/
|
|
if (dns_rdatatype_issig(rdataset->type)) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Ignore unrelated non-answer rdatasets that are missing
|
|
* signatures.
|
|
*/
|
|
if (sigrdataset == NULL && need_validation && !ANSWER(rdataset)) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Mark this rdataset/sigrdataset pair as "pending".
|
|
*/
|
|
if (rdataset->trust == dns_trust_additional) {
|
|
rdataset->trust = dns_trust_pending_additional;
|
|
} else {
|
|
rdataset->trust = dns_trust_pending_answer;
|
|
}
|
|
|
|
if (sigrdataset != NULL) {
|
|
sigrdataset->trust = rdataset->trust;
|
|
}
|
|
|
|
if (ANSWER(rdataset) && need_validation) {
|
|
if (!dns_rdatatype_ismulti(fctx->type)) {
|
|
/*
|
|
* This is The Answer. We will validate it,
|
|
* but first we finish caching the rest of the
|
|
* response; it may contain useful keys.
|
|
*/
|
|
INSIST(rctx->vrdataset == NULL &&
|
|
rctx->vsigrdataset == NULL);
|
|
rctx->vrdataset = rdataset;
|
|
rctx->vsigrdataset = sigrdataset;
|
|
} else {
|
|
/*
|
|
* This is one of (potentially) multiple answers to
|
|
* an ANY query. To keep things simple, we just
|
|
* start the validator right away rather than
|
|
* caching first and having to remember which
|
|
* rdatasets needed validation.
|
|
*/
|
|
valcreate(fctx, message, query->addrinfo, name,
|
|
rdataset->type, rdataset, sigrdataset);
|
|
}
|
|
} else {
|
|
if (ANSWER(rdataset)) {
|
|
/*
|
|
* We're not validating, but the client might
|
|
* be, so look for the NOQNAME proof.
|
|
*/
|
|
findnoqname(fctx, message, name, rdataset, sigrdataset);
|
|
|
|
/*
|
|
* If this was not an ANY query - or if it was,
|
|
* but we got a CNAME/DNAME - then we need to
|
|
* set up rdatasets to send back to the caller.
|
|
*/
|
|
if (resp != NULL &&
|
|
(!dns_rdatatype_ismulti(fctx->type) ||
|
|
CHAINING(rdataset)))
|
|
{
|
|
ardataset = resp->rdataset;
|
|
asigset = resp->sigrdataset;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In this case we cache the rdataset and sigrdataset
|
|
* (if any) in two steps, so we can do an extra check
|
|
* in-between.
|
|
*/
|
|
result = cache_rrset(fctx, rctx->now, name, rdataset, NULL,
|
|
&node, ardataset, NULL);
|
|
if (result != DNS_R_UNCHANGED && result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
if (sigrdataset == NULL) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (result == DNS_R_UNCHANGED && !need_validation &&
|
|
ardataset != NULL &&
|
|
!dns_rdataset_equals(rdataset, ardataset))
|
|
{
|
|
/*
|
|
* The cache wasn't updated because something was
|
|
* already there. If the data was the same as what
|
|
* we were trying to add, then sigrdataset might
|
|
* still be useful, and we should carry on caching
|
|
* it. Otherwise, move on.
|
|
*/
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
result = cache_rrset(fctx, rctx->now, name, sigrdataset, NULL,
|
|
&node, asigset, NULL);
|
|
if (result != DNS_R_UNCHANGED && result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
rctx_cache_insecure(respctx_t *rctx, dns_message_t *message, dns_name_t *name,
|
|
dns_dbnode_t *node, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_fetchresponse_t *resp = ISC_LIST_HEAD(fctx->resps);
|
|
dns_rdataset_t *added = NULL;
|
|
|
|
/*
|
|
* If this was not an ANY query - or if it was, but we got a
|
|
* CNAME/DNAME - then we need to set up an rdataset to send
|
|
* back to the caller.
|
|
*/
|
|
if (resp != NULL &&
|
|
(!dns_rdatatype_ismulti(fctx->type) || CHAINING(rdataset)))
|
|
{
|
|
if (ANSWER(rdataset)) {
|
|
added = resp->rdataset;
|
|
} else if (ANSWERSIG(rdataset)) {
|
|
added = resp->sigrdataset;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for the NOQNAME proof.
|
|
*/
|
|
if (ANSWER(rdataset)) {
|
|
findnoqname(fctx, message, name, rdataset, sigrdataset);
|
|
}
|
|
|
|
/*
|
|
* Cache the rdataset.
|
|
*/
|
|
result = cache_rrset(fctx, rctx->now, name, rdataset, NULL, &node,
|
|
added, NULL);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
rctx_cachename(respctx_t *rctx, dns_message_t *message, dns_name_t *name) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
dns_resolver_t *res = fctx->res;
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
dns_dbnode_t *node = NULL;
|
|
|
|
FCTXTRACE("rctx_cachename");
|
|
|
|
/*
|
|
* The appropriate bucket lock must be held.
|
|
*/
|
|
|
|
/*
|
|
* Is DNSSEC validation required for this name?
|
|
*/
|
|
bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now,
|
|
NULL);
|
|
bool need_validation = secure_domain &&
|
|
((fctx->options & DNS_FETCHOPT_NOVALIDATE) == 0);
|
|
|
|
/*
|
|
* Find or create the cache node.
|
|
*/
|
|
result = dns_db_findnode(fctx->cache, name, true, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Cache or validate each cacheable rdataset.
|
|
*/
|
|
bool fail = ((res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0);
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
result = check_cacheable(name, rdataset, fail);
|
|
if (result == DNS_R_CONTINUE) {
|
|
result = ISC_R_SUCCESS;
|
|
continue;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Find the signature for this rdataset */
|
|
sigrdataset = getrrsig(name, rdataset->type);
|
|
|
|
/*
|
|
* Make the TTL consistent with the configured
|
|
* maximum and minimum
|
|
*/
|
|
fixttls(res->view, rdataset, sigrdataset);
|
|
|
|
if (secure_domain && gettrust(rdataset) != dns_trust_glue) {
|
|
/*
|
|
* If this is a secure domain and the rdataset
|
|
* isn't glue, start a validator. The data will
|
|
* be cached when the validator finishes.
|
|
*/
|
|
result = rctx_cache_secure(rctx, message, name, node,
|
|
rdataset, sigrdataset,
|
|
need_validation);
|
|
} else {
|
|
/* Insecure domain or glue: cache the data now. */
|
|
result = rctx_cache_insecure(rctx, message, name, node,
|
|
rdataset, sigrdataset);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there was a delayed validation set up in
|
|
* rctx_cache_secure(), run it now.
|
|
*/
|
|
if (rctx->vrdataset != NULL) {
|
|
dns_rdatatype_t vtype = fctx->type;
|
|
if (CHAINING(rctx->vrdataset)) {
|
|
vtype = rctx->vrdataset->type;
|
|
INSIST(dns_rdatatype_isalias(vtype));
|
|
}
|
|
|
|
valcreate(fctx, message, query->addrinfo, name, vtype,
|
|
rctx->vrdataset, rctx->vsigrdataset);
|
|
rctx->vrdataset = NULL;
|
|
rctx->vsigrdataset = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* We're not validating and have an answer ready; pass
|
|
* it back to the caller.
|
|
*/
|
|
if (!need_validation && name->attributes.answer && !HAVE_ANSWER(fctx)) {
|
|
dns_fetchresponse_t *resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (resp != NULL) {
|
|
isc_result_t eresult = ISC_R_SUCCESS;
|
|
|
|
if (dns_rdataset_isassociated(resp->rdataset)) {
|
|
eresult = fctx_setresult(fctx, resp->rdataset);
|
|
}
|
|
|
|
resp->result = eresult;
|
|
dns_name_copy(name, resp->foundname);
|
|
dns_db_attach(fctx->cache, &resp->db);
|
|
dns_db_transfernode(fctx->cache, &node, &resp->node);
|
|
clone_results(fctx);
|
|
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
rctx_cachemessage(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
dns_message_t *message = query->rmessage;
|
|
|
|
FCTXTRACE("rctx_cachemessage");
|
|
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
for (dns_section_t section = DNS_SECTION_ANSWER;
|
|
section <= DNS_SECTION_ADDITIONAL; section++)
|
|
{
|
|
MSG_SECTION_FOREACH (message, section, name) {
|
|
if (name->attributes.cache) {
|
|
result = rctx_cachename(rctx, message, name);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
UNLOCK(&fctx->lock);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Call dns_ncache_add() and then compute an appropriate eresult.
|
|
*/
|
|
static isc_result_t
|
|
negcache(dns_message_t *message, fetchctx_t *fctx, const dns_name_t *name,
|
|
isc_stdtime_t now, bool optout, bool secure, dns_rdataset_t *added,
|
|
dns_dbnode_t **nodep) {
|
|
isc_result_t result;
|
|
dns_ttl_t minttl = fctx->res->view->minncachettl;
|
|
dns_ttl_t maxttl = fctx->res->view->maxncachettl;
|
|
dns_rdatatype_t covers = fctx->type;
|
|
dns_db_t *cache = fctx->cache;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
|
|
/* Set up a placeholder in case added was NULL */
|
|
if (added == NULL) {
|
|
added = &rdataset;
|
|
}
|
|
|
|
/*
|
|
* Cache DS NXDOMAIN separately to other types.
|
|
*/
|
|
if (message->rcode == dns_rcode_nxdomain &&
|
|
fctx->type != dns_rdatatype_ds)
|
|
{
|
|
covers = dns_rdatatype_any;
|
|
}
|
|
|
|
/*
|
|
* If the request was for an SOA record, set the cache time
|
|
* to zero to facilitate locating the containing zone of
|
|
* an arbitrary zone.
|
|
*/
|
|
if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any &&
|
|
fctx->res->zero_no_soa_ttl)
|
|
{
|
|
maxttl = 0;
|
|
}
|
|
|
|
/*
|
|
* Don't warn about QNAME minimization NXDOMAIN errors
|
|
* if the final result is NXDOMAIN anyway.
|
|
*/
|
|
if (!fctx->force_qmin_warning && message->rcode == dns_rcode_nxdomain &&
|
|
(fctx->qmin_warning == DNS_R_NXDOMAIN ||
|
|
fctx->qmin_warning == DNS_R_NCACHENXDOMAIN))
|
|
{
|
|
fctx->qmin_warning = ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Cache the negative entry.
|
|
*/
|
|
result = dns_db_findnode(fctx->cache, name, true, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
result = dns_ncache_add(message, cache, node, covers, now, minttl,
|
|
maxttl, optout, secure, added);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (added == &rdataset && dns_rdataset_isassociated(added)) {
|
|
dns_rdataset_disassociate(added);
|
|
}
|
|
|
|
*nodep = node;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* 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 = ISC_R_SUCCESS;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_name_t *name = fctx->name;
|
|
dns_message_t *message = rctx->query->rmessage;
|
|
dns_adbaddrinfo_t *addrinfo = rctx->query->addrinfo;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_fetchresponse_t *resp = NULL;
|
|
dns_rdataset_t *added = NULL;
|
|
|
|
FCTXTRACE("rctx_ncache");
|
|
|
|
if (!WANTNCACHE(fctx)) {
|
|
goto done;
|
|
}
|
|
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE);
|
|
|
|
/*
|
|
* 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?
|
|
*/
|
|
bool secure_domain = issecuredomain(fctx, name, fctx->type, rctx->now,
|
|
NULL);
|
|
bool need_validation = secure_domain &&
|
|
((fctx->options & DNS_FETCHOPT_NOVALIDATE) == 0);
|
|
|
|
if (secure_domain) {
|
|
/*
|
|
* Mark all rdatasets as pending. (We do this for
|
|
* any domain under a trust anchor, regardless
|
|
* of whether we're actually validating.)
|
|
*/
|
|
MSG_SECTION_FOREACH (message, DNS_SECTION_AUTHORITY, tname) {
|
|
ISC_LIST_FOREACH (tname->list, trdataset, link) {
|
|
trdataset->trust = dns_trust_pending_answer;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (need_validation) {
|
|
/*
|
|
* Start the validator for the negative response. It
|
|
* will call validated() on completion; the caching of
|
|
* negative answers will be done then.
|
|
*/
|
|
valcreate(fctx, message, addrinfo, name, fctx->type, NULL,
|
|
NULL);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Cache the negative answer, and copy it into the fetch response.
|
|
*/
|
|
LOCK(&fctx->lock);
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (!HAVE_ANSWER(fctx) && resp != NULL) {
|
|
added = resp->rdataset;
|
|
}
|
|
|
|
result = negcache(message, fctx, name, rctx->now, false, false, added,
|
|
&node);
|
|
if (result != ISC_R_SUCCESS || HAVE_ANSWER(fctx)) {
|
|
goto unlock;
|
|
}
|
|
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
if (resp != NULL) {
|
|
resp->result = fctx_setresult(fctx, resp->rdataset);
|
|
dns_name_copy(name, resp->foundname);
|
|
dns_db_attach(fctx->cache, &resp->db);
|
|
dns_db_transfernode(fctx->cache, &node, &resp->node);
|
|
clone_results(fctx);
|
|
}
|
|
|
|
unlock:
|
|
UNLOCK(&fctx->lock);
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
|
|
done:
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_ncache complete", result);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
|
|
bool gluing) {
|
|
name->attributes.cache = true;
|
|
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.chase = true;
|
|
rdataset->attributes.chase = true;
|
|
}
|
|
rdataset->attributes.cache = true;
|
|
if (external) {
|
|
rdataset->attributes.external = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 inline 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_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);
|
|
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.
|
|
*/
|
|
dns_ztfind_t options = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_MIRROR;
|
|
result = dns_view_findzone(fctx->res->view, name, options, &zone);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
dns_name_t *zname = dns_zone_getorigin(zone);
|
|
dns_namereln_t reln = dns_name_fullcompare(
|
|
zname, apex, &(int){ 0 }, &(unsigned int){ 0U });
|
|
dns_zone_detach(&zone);
|
|
if (reln == dns_namereln_subdomain) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for a forward declaration below 'name'.
|
|
*/
|
|
result = dns_fwdtable_find(fctx->res->view->fwdtable, name,
|
|
&forwarders);
|
|
|
|
if (ISFORWARDER(fctx->addrinfo)) {
|
|
/*
|
|
* See if the forwarder declaration is better.
|
|
*/
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
bool better = !dns_name_equal(&forwarders->name,
|
|
fctx->fwdname);
|
|
dns_forwarders_detach(&forwarders);
|
|
return better;
|
|
}
|
|
|
|
/*
|
|
* If the lookup failed, the configuration must have
|
|
* changed: play it safe and don't cache.
|
|
*/
|
|
return true;
|
|
} else if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
/*
|
|
* If 'name' is covered by a 'forward only' clause then we
|
|
* can't cache this response.
|
|
*/
|
|
bool nocache = (forwarders->fwdpolicy == dns_fwdpolicy_only &&
|
|
!ISC_LIST_EMPTY(forwarders->fwdrs));
|
|
dns_forwarders_detach(&forwarders);
|
|
return nocache;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
|
|
dns_rdataset_t *found, dns_section_t section) {
|
|
respctx_t *rctx = arg;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_result_t result;
|
|
dns_name_t *name = 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) {
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
if (dns_rdatatype_issig(rdataset->type)) {
|
|
rtype = rdataset->covers;
|
|
} else {
|
|
rtype = rdataset->type;
|
|
}
|
|
if (dns_rdatatype_isaddr(rtype)) {
|
|
mark_related(name, rdataset, external,
|
|
gluing);
|
|
}
|
|
}
|
|
} else {
|
|
dns_rdataset_t *rdataset = NULL;
|
|
result = dns_message_findtype(name, type, 0, &rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
mark_related(name, rdataset, external, gluing);
|
|
if (found != NULL) {
|
|
dns_rdataset_clone(rdataset, found);
|
|
}
|
|
/*
|
|
* 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,
|
|
dns_rdataset_t *found DNS__DB_FLARG) {
|
|
return check_section(arg, addname, type, found, 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,
|
|
dns_rdataset_t *found) {
|
|
return check_section(arg, addname, type, found, 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 (dns_nametree_covered(view->answeracl_exclude, name, NULL, 0)) {
|
|
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.
|
|
*/
|
|
DNS_RDATASET_FOREACH (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_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_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(dns_rdatatype_isalias(rdataset->type));
|
|
|
|
/*
|
|
* 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);
|
|
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);
|
|
if (result == DNS_R_NAMETOOLONG) {
|
|
SET_IF_NOT_NULL(chainingp, true);
|
|
return true;
|
|
}
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
SET_IF_NOT_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 (dns_nametree_covered(view->answernames_exclude, qname, NULL, 0)) {
|
|
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.
|
|
*/
|
|
if (dns_nametree_covered(view->denyanswernames, tname, NULL, 0)) {
|
|
char qnamebuf[DNS_NAME_FORMATSIZE];
|
|
char tnamebuf[DNS_NAME_FORMATSIZE];
|
|
char classbuf[64];
|
|
char typebuf[64];
|
|
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_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) {
|
|
if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) {
|
|
char ns_namebuf[DNS_NAME_FORMATSIZE];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
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_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;
|
|
}
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
ISC_REFCOUNT_TRACE_IMPL(fetchctx, fctx_destroy);
|
|
#else
|
|
ISC_REFCOUNT_IMPL(fetchctx, fctx_destroy);
|
|
#endif
|
|
|
|
static void
|
|
resume_dslookup(void *arg) {
|
|
dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
|
|
fetchctx_t *fctx = resp->arg;
|
|
isc_loop_t *loop = resp->loop;
|
|
isc_result_t result;
|
|
dns_resolver_t *res = NULL;
|
|
dns_rdataset_t *frdataset = NULL, *nsrdataset = NULL;
|
|
dns_rdataset_t nameservers;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *domain = NULL;
|
|
unsigned int n;
|
|
dns_fetch_t *fetch = NULL;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
res = fctx->res;
|
|
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
FCTXTRACE("resume_dslookup");
|
|
|
|
if (resp->node != NULL) {
|
|
dns_db_detachnode(&resp->node);
|
|
}
|
|
if (resp->db != NULL) {
|
|
dns_db_detach(&resp->db);
|
|
}
|
|
|
|
/* Preserve data from resp before freeing it. */
|
|
frdataset = resp->rdataset; /* a.k.a. fctx->nsrrset */
|
|
result = resp->result;
|
|
|
|
dns_resolver_freefresp(&resp);
|
|
|
|
LOCK(&fctx->lock);
|
|
if (SHUTTINGDOWN(fctx)) {
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
fetch = fctx->nsfetch;
|
|
fctx->nsfetch = NULL;
|
|
|
|
FTRACE("resume_dslookup");
|
|
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
FCTXTRACE("resuming DS lookup");
|
|
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
dns_rdataset_clone(frdataset, &fctx->nameservers);
|
|
|
|
/*
|
|
* Disassociate now the NS's are saved.
|
|
*/
|
|
if (dns_rdataset_isassociated(frdataset)) {
|
|
dns_rdataset_disassociate(frdataset);
|
|
}
|
|
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = true;
|
|
log_ns_ttl(fctx, "resume_dslookup");
|
|
|
|
fcount_decr(fctx);
|
|
dns_name_copy(fctx->nsname, fctx->domain);
|
|
result = fcount_incr(fctx, false);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Try again. */
|
|
fctx_try(fctx, true);
|
|
break;
|
|
|
|
case ISC_R_SHUTTINGDOWN:
|
|
case ISC_R_CANCELED:
|
|
/* Don't try anymore. */
|
|
/* Can't be done in cleanup. */
|
|
if (dns_rdataset_isassociated(frdataset)) {
|
|
dns_rdataset_disassociate(frdataset);
|
|
}
|
|
goto cleanup;
|
|
|
|
default:
|
|
/*
|
|
* Disassociate for the next dns_resolver_createfetch call.
|
|
*/
|
|
if (dns_rdataset_isassociated(frdataset)) {
|
|
dns_rdataset_disassociate(frdataset);
|
|
}
|
|
|
|
/*
|
|
* If the chain of resume_dslookup() invocations managed to
|
|
* chop off enough labels from the original DS owner name to
|
|
* reach the top of the namespace, no further progress can be
|
|
* made. Interrupt the DS chasing process, returning SERVFAIL.
|
|
*/
|
|
if (dns_name_equal(fctx->nsname, fetch->private->domain)) {
|
|
result = DNS_R_SERVFAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Get nameservers from fetch before we destroy it. */
|
|
dns_rdataset_init(&nameservers);
|
|
if (dns_rdataset_isassociated(&fetch->private->nameservers)) {
|
|
dns_rdataset_clone(&fetch->private->nameservers,
|
|
&nameservers);
|
|
nsrdataset = &nameservers;
|
|
|
|
/* Get domain from fetch before we destroy it. */
|
|
domain = dns_fixedname_initname(&fixed);
|
|
dns_name_copy(fetch->private->domain, domain);
|
|
}
|
|
|
|
n = dns_name_countlabels(fctx->nsname);
|
|
dns_name_getlabelsequence(fctx->nsname, 1, n - 1, fctx->nsname);
|
|
|
|
FCTXTRACE("continuing to look for parent's NS records");
|
|
|
|
fetchctx_ref(fctx);
|
|
result = dns_resolver_createfetch(
|
|
res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset,
|
|
NULL, NULL, 0, fctx->options, 0, fctx->qc, fctx->gqc,
|
|
loop, resume_dslookup, fctx, &fctx->edectx,
|
|
&fctx->nsrrset, NULL, &fctx->nsfetch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fetchctx_unref(fctx);
|
|
if (result == DNS_R_DUPLICATE) {
|
|
result = DNS_R_SERVFAIL;
|
|
}
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&nameservers)) {
|
|
dns_rdataset_disassociate(&nameservers);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
dns_resolver_destroyfetch(&fetch);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* An error occurred, tear down whole fctx */
|
|
fctx_done_unref(fctx, result);
|
|
}
|
|
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
static void
|
|
checknamessection(dns_message_t *message, dns_section_t section) {
|
|
MSG_SECTION_FOREACH (message, section, name) {
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
DNS_RDATASET_FOREACH (rdataset) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
if (!dns_rdata_checkowner(name, rdata.rdclass,
|
|
rdata.type, false) ||
|
|
!dns_rdata_checknames(&rdata, name, NULL))
|
|
{
|
|
rdataset->attributes.checknames = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
checknames(dns_message_t *message) {
|
|
checknamessection(message, DNS_SECTION_ANSWER);
|
|
checknamessection(message, DNS_SECTION_AUTHORITY);
|
|
checknamessection(message, DNS_SECTION_ADDITIONAL);
|
|
}
|
|
|
|
static void
|
|
make_hex(unsigned char *src, size_t srclen, char *buf, size_t buflen) {
|
|
isc_buffer_t b;
|
|
isc_region_t r;
|
|
isc_result_t result;
|
|
|
|
r.base = src;
|
|
r.length = srclen;
|
|
isc_buffer_init(&b, buf, buflen);
|
|
result = isc_hex_totext(&r, 0, "", &b);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
isc_buffer_putuint8(&b, '\0');
|
|
}
|
|
|
|
static void
|
|
make_printable(unsigned char *src, size_t srclen, char *buf, size_t buflen) {
|
|
INSIST(buflen > srclen);
|
|
while (srclen-- > 0) {
|
|
unsigned char c = *src++;
|
|
*buf++ = isprint(c) ? c : '.';
|
|
}
|
|
*buf = '\0';
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE], *buf = NULL, *pbuf = NULL;
|
|
size_t buflen;
|
|
|
|
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 */
|
|
make_hex(isc_buffer_current(opt), nsid_len, buf, buflen);
|
|
|
|
/* Make printable version */
|
|
make_printable(isc_buffer_current(opt), nsid_len, pbuf, nsid_len + 1);
|
|
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(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 void
|
|
log_zoneversion(unsigned char *version, size_t version_len, unsigned char *nsid,
|
|
size_t nsid_len, resquery_t *query, int level,
|
|
isc_mem_t *mctx) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
size_t nsid_buflen = 0;
|
|
char *nsid_buf = NULL;
|
|
char *nsid_pbuf = NULL;
|
|
const char *nsid_hex = "";
|
|
const char *nsid_print = "";
|
|
const char *sep_1 = "";
|
|
const char *sep_2 = "";
|
|
const char *sep_3 = "";
|
|
dns_name_t suffix = DNS_NAME_INITEMPTY;
|
|
unsigned int labels;
|
|
|
|
REQUIRE(version_len <= UINT16_MAX);
|
|
|
|
/*
|
|
* Don't log reflected ZONEVERSION option.
|
|
*/
|
|
if (version_len == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Enforced by dns_rdata_fromwire. */
|
|
INSIST(version_len >= 2);
|
|
|
|
/*
|
|
* Sanity check on label count.
|
|
*/
|
|
labels = version[0] + 1;
|
|
if (dns_name_countlabels(query->fctx->name) < labels) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get zone name.
|
|
*/
|
|
dns_name_split(query->fctx->name, labels, NULL, &suffix);
|
|
dns_name_format(&suffix, namebuf, sizeof(namebuf));
|
|
|
|
if (nsid != NULL) {
|
|
nsid_buflen = nsid_len * 2 + 1;
|
|
nsid_hex = nsid_buf = isc_mem_get(mctx, nsid_buflen);
|
|
nsid_print = nsid_pbuf = isc_mem_get(mctx, nsid_len + 1);
|
|
|
|
/* Convert to hex */
|
|
make_hex(nsid, nsid_len, nsid_buf, nsid_buflen);
|
|
|
|
/* Convert to printable */
|
|
make_printable(nsid, nsid_len, nsid_pbuf, nsid_len + 1);
|
|
|
|
sep_1 = " (NSID ";
|
|
sep_2 = " (";
|
|
sep_3 = "))";
|
|
}
|
|
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
if (version[1] == 0 && version_len == 6) {
|
|
uint32_t serial = version[2] << 24 | version[3] << 2 |
|
|
version[4] << 8 | version[5];
|
|
isc_log_write(DNS_LOGCATEGORY_ZONEVERSION,
|
|
DNS_LOGMODULE_RESOLVER, level,
|
|
"received ZONEVERSION serial %u from %s for %s "
|
|
"zone %s%s%s%s%s%s",
|
|
serial, addrbuf, query->fctx->info, namebuf,
|
|
sep_1, nsid_hex, sep_2, nsid_print, sep_3);
|
|
} else {
|
|
size_t version_buflen = version_len * 2 + 1;
|
|
char *version_hex = isc_mem_get(mctx, version_buflen);
|
|
char *version_pbuf = isc_mem_get(mctx, version_len - 1);
|
|
|
|
/* Convert to hex */
|
|
make_hex(version + 2, version_len - 2, version_hex,
|
|
version_buflen);
|
|
|
|
/* Convert to printable */
|
|
make_printable(version + 2, version_len - 2, version_pbuf,
|
|
version_len - 1);
|
|
|
|
isc_log_write(DNS_LOGCATEGORY_ZONEVERSION,
|
|
DNS_LOGMODULE_RESOLVER, level,
|
|
"received ZONEVERSION type %u value %s (%s) from "
|
|
"%s for %s zone %s%s%s%s%s%s",
|
|
version[1], version_hex, version_pbuf, addrbuf,
|
|
query->fctx->info, namebuf, sep_1, nsid_hex,
|
|
sep_2, nsid_print, sep_3);
|
|
isc_mem_put(mctx, version_hex, version_buflen);
|
|
isc_mem_put(mctx, version_pbuf, version_len - 1);
|
|
}
|
|
|
|
if (nsid_pbuf != NULL) {
|
|
isc_mem_put(mctx, nsid_pbuf, nsid_len + 1);
|
|
}
|
|
if (nsid_buf != NULL) {
|
|
isc_mem_put(mctx, nsid_buf, nsid_buflen);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
betterreferral(respctx_t *rctx) {
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
|
|
MSG_SECTION_FOREACH (msg, DNS_SECTION_AUTHORITY, name) {
|
|
if (!isstrictsubdomain(name, rctx->fctx->domain)) {
|
|
continue;
|
|
}
|
|
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
if (rdataset->type == dns_rdatatype_ns) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Handles responses received in response to iterative queries sent by
|
|
* resquery_send(). Sets up a response context (respctx_t).
|
|
*/
|
|
static void
|
|
resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|
isc_result_t result;
|
|
resquery_t *query = (resquery_t *)arg;
|
|
fetchctx_t *fctx = NULL;
|
|
respctx_t *rctx = NULL;
|
|
|
|
if (eresult == ISC_R_CANCELED) {
|
|
return;
|
|
}
|
|
|
|
REQUIRE(VALID_QUERY(query));
|
|
fctx = query->fctx;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
QTRACE("response");
|
|
|
|
if (eresult == ISC_R_SUCCESS) {
|
|
if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) {
|
|
inc_stats(fctx->res, dns_resstatscounter_responsev4);
|
|
} else {
|
|
inc_stats(fctx->res, dns_resstatscounter_responsev6);
|
|
}
|
|
}
|
|
|
|
rctx = isc_mem_get(fctx->mctx, sizeof(*rctx));
|
|
rctx_respinit(query, fctx, eresult, region, rctx);
|
|
|
|
if (eresult == ISC_R_SHUTTINGDOWN ||
|
|
atomic_load_acquire(&fctx->res->exiting))
|
|
{
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
FCTXTRACE("resolver shutting down");
|
|
rctx->finish = NULL;
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
|
|
result = rctx_timedout(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
goto cleanup;
|
|
}
|
|
|
|
fctx->addrinfo = query->addrinfo;
|
|
fctx->timeout = false;
|
|
fctx->timeouts = 0;
|
|
|
|
/*
|
|
* Check whether the dispatcher has failed; if so we're done
|
|
*/
|
|
result = rctx_dispfail(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (query->tsig != NULL) {
|
|
dns_message_setquerytsig(query->rmessage, query->tsig);
|
|
}
|
|
|
|
if (query->tsigkey != NULL) {
|
|
result = dns_message_settsigkey(query->rmessage,
|
|
query->tsigkey);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("unable to set tsig key", result);
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
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(&rctx->buffer));
|
|
} else {
|
|
dns_adb_plainresponse(fctx->adb, query->addrinfo);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse response message.
|
|
*/
|
|
result = rctx_parse(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Log the incoming packet.
|
|
*/
|
|
rctx_logpacket(rctx);
|
|
|
|
if (query->rmessage->rdclass != fctx->res->rdclass) {
|
|
rctx->resend = true;
|
|
FCTXTRACE("bad class");
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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(ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"bad cookie from %s", addrbuf);
|
|
}
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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("question section invalid", result);
|
|
rctx->nextitem = true;
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (query->rmessage->tsigkey == NULL && query->rmessage->tsig == NULL &&
|
|
query->rmessage->sig0 != NULL)
|
|
{
|
|
/*
|
|
* If the message is not TSIG-signed (which has priorty) and is
|
|
* SIG(0)-signed (which consumes more resources), then run an
|
|
* asynchronous check.
|
|
*/
|
|
result = dns_message_checksig_async(
|
|
query->rmessage, fctx->res->view, fctx->loop,
|
|
resquery_response_continue, rctx);
|
|
INSIST(result == DNS_R_WAIT);
|
|
} else {
|
|
/*
|
|
* If the message is signed, check the signature. If not, this
|
|
* returns success anyway.
|
|
*/
|
|
result = dns_message_checksig(query->rmessage, fctx->res->view);
|
|
resquery_response_continue(rctx, result);
|
|
}
|
|
|
|
return;
|
|
|
|
cleanup:
|
|
isc_mem_putanddetach(&rctx->mctx, rctx, sizeof(*rctx));
|
|
}
|
|
|
|
static isc_result_t
|
|
rctx_cookiecheck(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
/*
|
|
* If TSIG signed, sent via TCP, or cookie present,
|
|
* no need to continue.
|
|
*/
|
|
if (dns_message_gettsig(query->rmessage, NULL) != NULL ||
|
|
query->rmessage->cc_ok || query->rmessage->cc_bad ||
|
|
(rctx->retryopts & DNS_FETCHOPT_TCP) != 0)
|
|
{
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* If we've had a cookie from the same server previously,
|
|
* retry with TCP. This may be a misconfigured anycast server
|
|
* or an attempt to send a spoofed response.
|
|
*/
|
|
if (dns_adb_getcookie(query->addrinfo, NULL, 0) > CLIENT_COOKIE_SIZE) {
|
|
if (isc_log_wouldlog(ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(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, ISC_R_SUCCESS);
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/*
|
|
* Retry over TCP if require-cookie is true.
|
|
*/
|
|
if (fctx->res->view->peers != NULL) {
|
|
isc_result_t result;
|
|
dns_peer_t *peer = NULL;
|
|
bool required = false;
|
|
isc_netaddr_t netaddr;
|
|
|
|
isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr);
|
|
result = dns_peerlist_peerbyaddr(fctx->res->view->peers,
|
|
&netaddr, &peer);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_peer_getrequirecookie(peer, &required);
|
|
}
|
|
if (!required) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
if (isc_log_wouldlog(ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"missing required "
|
|
"cookie from %s",
|
|
addrbuf);
|
|
}
|
|
|
|
rctx->retryopts |= DNS_FETCHOPT_TCP;
|
|
rctx->resend = true;
|
|
rctx_done(rctx, ISC_R_SUCCESS);
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
resquery_response_continue(void *arg, isc_result_t result) {
|
|
respctx_t *rctx = arg;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
QTRACE("response_continue");
|
|
|
|
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);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* The dispatcher should ensure we only get responses with QR
|
|
* set.
|
|
*/
|
|
INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0);
|
|
|
|
/*
|
|
* Check for cookie issues.
|
|
*/
|
|
result = rctx_cookiecheck(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Check for EDNS issues.
|
|
*/
|
|
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);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Lame server?
|
|
*/
|
|
result = rctx_lameserver(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
goto cleanup;
|
|
}
|
|
} 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 return
|
|
* DNS_R_DELEGATION to resume_qmin.
|
|
*/
|
|
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);
|
|
goto cleanup;
|
|
}
|
|
} 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);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* 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 = rctx_cachemessage(rctx);
|
|
if (tresult != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_cachemessage complete", tresult);
|
|
rctx_done(rctx, tresult);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Negative caching
|
|
*/
|
|
rctx_ncache(rctx);
|
|
|
|
FCTXTRACE("resquery_response done");
|
|
rctx_done(rctx, result);
|
|
|
|
cleanup:
|
|
isc_mem_putanddetach(&rctx->mctx, rctx, sizeof(*rctx));
|
|
}
|
|
|
|
/*
|
|
* rctx_respinit():
|
|
* Initialize the response context structure 'rctx' to all zeroes, then
|
|
* set the loop, event, query and fctx information from
|
|
* resquery_response().
|
|
*/
|
|
static void
|
|
rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result,
|
|
isc_region_t *region, respctx_t *rctx) {
|
|
*rctx = (respctx_t){ .result = result,
|
|
.query = query,
|
|
.fctx = fctx,
|
|
.broken_type = badns_response,
|
|
.retryopts = query->options };
|
|
if (result == ISC_R_SUCCESS) {
|
|
REQUIRE(region != NULL);
|
|
isc_buffer_init(&rctx->buffer, region->base, region->length);
|
|
isc_buffer_add(&rctx->buffer, region->length);
|
|
} else {
|
|
isc_buffer_initnull(&rctx->buffer);
|
|
}
|
|
rctx->tnow = isc_time_now();
|
|
rctx->finish = &rctx->tnow;
|
|
rctx->now = (isc_stdtime_t)isc_time_seconds(&rctx->tnow);
|
|
isc_mem_attach(fctx->mctx, &rctx->mctx);
|
|
}
|
|
|
|
/*
|
|
* 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 (dns_rdatatype_issig(fctx->type)) {
|
|
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->vrdataset = NULL;
|
|
rctx->vsigrdataset = NULL;
|
|
}
|
|
|
|
/*
|
|
* rctx_dispfail():
|
|
* Handle the case where the dispatcher failed
|
|
*/
|
|
static isc_result_t
|
|
rctx_dispfail(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (rctx->result == ISC_R_SUCCESS) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* There's no hope for this response.
|
|
*/
|
|
rctx->next_server = true;
|
|
|
|
/*
|
|
* If this is a network failure, the operation is cancelled,
|
|
* or the network manager is being shut down, we 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 adjustments later.
|
|
*/
|
|
switch (rctx->result) {
|
|
case ISC_R_EOF:
|
|
case ISC_R_HOSTDOWN:
|
|
case ISC_R_HOSTUNREACH:
|
|
case ISC_R_NETDOWN:
|
|
case ISC_R_NETUNREACH:
|
|
case ISC_R_CONNREFUSED:
|
|
case ISC_R_CONNECTIONRESET:
|
|
case ISC_R_INVALIDPROTO:
|
|
case ISC_R_CANCELED:
|
|
case ISC_R_SHUTTINGDOWN:
|
|
rctx->broken_server = rctx->result;
|
|
rctx->broken_type = badns_unreachable;
|
|
rctx->finish = NULL;
|
|
rctx->no_response = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
FCTXTRACE3("dispatcher failure", rctx->result);
|
|
rctx_done(rctx, ISC_R_SUCCESS);
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/*
|
|
* rctx_timedout():
|
|
* Handle the case where a dispatch read timed out.
|
|
*/
|
|
static isc_result_t
|
|
rctx_timedout(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (rctx->result == ISC_R_TIMEDOUT) {
|
|
isc_time_t now;
|
|
|
|
inc_stats(fctx->res, dns_resstatscounter_querytimeout);
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
|
|
fctx->timeout = true;
|
|
fctx->timeouts++;
|
|
|
|
rctx->no_response = true;
|
|
rctx->finish = NULL;
|
|
|
|
now = isc_time_now();
|
|
/* netmgr timeouts are accurate to the millisecond */
|
|
if (isc_time_microdiff(&fctx->expires, &now) < US_PER_MS) {
|
|
FCTXTRACE("query timed out; stopped trying to make "
|
|
"fetch happen");
|
|
dns_ede_add(&fctx->edectx, DNS_EDE_NOREACHABLEAUTH,
|
|
NULL);
|
|
} else {
|
|
FCTXTRACE("query timed out; trying next server");
|
|
/* try next server */
|
|
rctx->next_server = true;
|
|
}
|
|
|
|
rctx_done(rctx, rctx->result);
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* 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->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;
|
|
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;
|
|
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;
|
|
bool seen_cookie = false;
|
|
bool seen_nsid = false;
|
|
bool seen_zoneversion = false;
|
|
unsigned char *nsid = NULL;
|
|
uint16_t nsidlen = 0;
|
|
unsigned char *zoneversion = NULL;
|
|
uint16_t zoneversionlen = 0;
|
|
|
|
result = dns_rdataset_first(rctx->opt);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
uint16_t optcode;
|
|
uint16_t optlen;
|
|
unsigned char *optvalue;
|
|
unsigned char cookie[CLIENT_COOKIE_SIZE];
|
|
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) {
|
|
break;
|
|
}
|
|
seen_nsid = true;
|
|
nsid = isc_buffer_current(&optbuf);
|
|
nsidlen = optlen;
|
|
if ((query->options & DNS_FETCHOPT_WANTNSID) != 0) {
|
|
log_nsid(&optbuf, optlen, query, ISC_LOG_INFO,
|
|
fctx->mctx);
|
|
}
|
|
break;
|
|
case DNS_OPT_COOKIE:
|
|
/* Only process the first cookie option. */
|
|
if (seen_cookie) {
|
|
break;
|
|
}
|
|
seen_cookie = true;
|
|
|
|
optvalue = isc_buffer_current(&optbuf);
|
|
compute_cc(query, cookie, sizeof(cookie));
|
|
INSIST(query->rmessage->cc_bad == 0 &&
|
|
query->rmessage->cc_ok == 0);
|
|
|
|
inc_stats(fctx->res, dns_resstatscounter_cookiein);
|
|
|
|
if (optlen < CLIENT_COOKIE_SIZE ||
|
|
memcmp(cookie, optvalue, CLIENT_COOKIE_SIZE) != 0)
|
|
{
|
|
query->rmessage->cc_bad = 1;
|
|
break;
|
|
}
|
|
|
|
/* Cookie OK */
|
|
if (optlen == CLIENT_COOKIE_SIZE) {
|
|
query->rmessage->cc_echoed = 1;
|
|
} else {
|
|
query->rmessage->cc_ok = 1;
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_cookieok);
|
|
dns_adb_setcookie(fctx->adb, query->addrinfo,
|
|
optvalue, optlen);
|
|
}
|
|
break;
|
|
case DNS_OPT_ZONEVERSION:
|
|
if (seen_zoneversion) {
|
|
break;
|
|
}
|
|
seen_zoneversion = true;
|
|
zoneversion = isc_buffer_current(&optbuf);
|
|
zoneversionlen = optlen;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
}
|
|
INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
|
|
|
|
if ((query->options & DNS_FETCHOPT_WANTZONEVERSION) != 0 &&
|
|
zoneversion != NULL)
|
|
{
|
|
log_zoneversion(zoneversion, zoneversionlen, nsid, nsidlen,
|
|
query, ISC_LOG_INFO, fctx->mctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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 (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.
|
|
*/
|
|
|
|
result = rctx_answer_none(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_none", result);
|
|
}
|
|
|
|
if (result == DNS_R_DELEGATION) {
|
|
/*
|
|
* With NOFOLLOW we want to return DNS_R_DELEGATION to
|
|
* resume_qmin.
|
|
*/
|
|
if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) != 0)
|
|
{
|
|
return result;
|
|
}
|
|
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;
|
|
FCTXTRACE3("rctx_answer lame", result);
|
|
rctx_done(rctx, result);
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (result == DNS_R_FORMERR) {
|
|
rctx->next_server = true;
|
|
}
|
|
FCTXTRACE3("rctx_answer failed", result);
|
|
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_positive");
|
|
|
|
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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
|
|
MSG_SECTION_FOREACH (msg, DNS_SECTION_ANSWER, name) {
|
|
int order;
|
|
unsigned int nlabels;
|
|
dns_namereln_t namereln;
|
|
|
|
namereln = dns_name_fullcompare(fctx->name, name, &order,
|
|
&nlabels);
|
|
switch (namereln) {
|
|
case dns_namereln_equal:
|
|
ISC_LIST_FOREACH (name->list, 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).
|
|
*/
|
|
ISC_LIST_FOREACH (name->list, 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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
ISC_LIST_FOREACH (rctx->aname->list, rdataset, link) {
|
|
if (!validinanswer(rdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (dns_rdatatype_issig(fctx->type) &&
|
|
rdataset->type != fctx->type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (dns_rdatatype_isaddr(rdataset->type) &&
|
|
!is_answeraddress_allowed(fctx->res->view, rctx->aname,
|
|
rdataset))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (dns_rdatatype_isalias(rdataset->type) &&
|
|
!is_answertarget_allowed(fctx, fctx->name, rctx->aname,
|
|
rdataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
rctx->aname->attributes.cache = true;
|
|
rctx->aname->attributes.answer = true;
|
|
if (dns_rdatatype_issig(rdataset->type)) {
|
|
rdataset->attributes.answersig = true;
|
|
} else {
|
|
rdataset->attributes.answer = true;
|
|
}
|
|
rdataset->attributes.cache = true;
|
|
rdataset->trust = rctx->trust;
|
|
}
|
|
|
|
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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->ardataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (dns_rdatatype_isaddr(rctx->ardataset->type) &&
|
|
!is_answeraddress_allowed(fctx->res->view, rctx->aname,
|
|
rctx->ardataset))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
if (dns_rdatatype_isalias(rctx->ardataset->type) &&
|
|
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.cache = true;
|
|
rctx->aname->attributes.answer = true;
|
|
rctx->ardataset->attributes.answer = true;
|
|
rctx->ardataset->attributes.cache = true;
|
|
rctx->ardataset->trust = rctx->trust;
|
|
(void)dns_rdataset_additionaldata(rctx->ardataset, rctx->aname,
|
|
check_related, rctx,
|
|
DNS_RDATASET_MAXADDITIONAL);
|
|
|
|
ISC_LIST_FOREACH (rctx->aname->list, sigrdataset, link) {
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (!dns_rdataset_issigtype(sigrdataset, rctx->type)) {
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes.answersig = true;
|
|
sigrdataset->attributes.cache = true;
|
|
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) {
|
|
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.cache = true;
|
|
rctx->cname->attributes.answer = true;
|
|
rctx->cname->attributes.chaining = true;
|
|
rctx->crdataset->attributes.answer = true;
|
|
rctx->crdataset->attributes.cache = true;
|
|
rctx->crdataset->attributes.chaining = true;
|
|
rctx->crdataset->trust = rctx->trust;
|
|
|
|
ISC_LIST_FOREACH (rctx->cname->list, 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.answersig = true;
|
|
sigrdataset->attributes.cache = true;
|
|
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) {
|
|
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.cache = true;
|
|
rctx->dname->attributes.answer = true;
|
|
rctx->dname->attributes.chaining = true;
|
|
rctx->drdataset->attributes.answer = true;
|
|
rctx->drdataset->attributes.cache = true;
|
|
rctx->drdataset->attributes.chaining = true;
|
|
rctx->drdataset->trust = rctx->trust;
|
|
|
|
ISC_LIST_FOREACH (rctx->dname->list, 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.answersig = true;
|
|
sigrdataset->attributes.cache = true;
|
|
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;
|
|
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
MSG_SECTION_FOREACH (msg, DNS_SECTION_AUTHORITY, name) {
|
|
if (!name_external(name, dns_rdatatype_ns, fctx)) {
|
|
/*
|
|
* We expect to find NS or SIG NS rdatasets, and
|
|
* nothing else.
|
|
*/
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
if (dns_rdataset_matchestype(rdataset,
|
|
dns_rdatatype_ns))
|
|
{
|
|
name->attributes.cache = true;
|
|
rdataset->attributes.cache = true;
|
|
|
|
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, name, check_related,
|
|
rctx,
|
|
DNS_RDATASET_MAXADDITIONAL);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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.cache = false;
|
|
}
|
|
|
|
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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_section_t section;
|
|
|
|
section = DNS_SECTION_AUTHORITY;
|
|
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
MSG_SECTION_FOREACH (msg, section, name) {
|
|
if (!dns_name_issubdomain(name, fctx->domain)) {
|
|
continue;
|
|
}
|
|
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
dns_rdatatype_t type = rdataset->type;
|
|
if (dns_rdatatype_issig(rdataset->type)) {
|
|
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.cache = true;
|
|
rdataset->attributes.cache = true;
|
|
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.ncache = true;
|
|
rdataset->attributes.ncache = true;
|
|
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_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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
MSG_SECTION_FOREACH (msg, DNS_SECTION_AUTHORITY, name) {
|
|
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;
|
|
}
|
|
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
bool secure_domain = false;
|
|
dns_rdatatype_t type = rdataset->type;
|
|
|
|
if (dns_rdatatype_issig(type)) {
|
|
type = rdataset->covers;
|
|
}
|
|
|
|
switch (type) {
|
|
case dns_rdatatype_nsec:
|
|
case dns_rdatatype_nsec3:
|
|
if (rctx->negative) {
|
|
name->attributes.ncache = true;
|
|
rdataset->attributes.ncache = true;
|
|
} else if (type == dns_rdatatype_nsec) {
|
|
name->attributes.cache = true;
|
|
rdataset->attributes.cache = true;
|
|
}
|
|
|
|
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.cache = true;
|
|
rdataset->attributes.cache = true;
|
|
|
|
secure_domain = issecuredomain(fctx, name,
|
|
dns_rdatatype_ds,
|
|
fctx->now, NULL);
|
|
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);
|
|
/*
|
|
* We want to append **all** the GLUE records here.
|
|
*/
|
|
(void)dns_rdataset_additionaldata(rctx->ns_rdataset, rctx->ns_name,
|
|
check_related, rctx, 0);
|
|
#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 && dns_rdatatype_isaddr(fctx->type)) {
|
|
(void)dns_rdataset_additionaldata(
|
|
rctx->ns_rdataset, rctx->ns_name, 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);
|
|
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
|
|
dns_name_copy(rctx->ns_name, fctx->domain);
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) {
|
|
dns_name_copy(rctx->ns_name, fctx->qmindcname);
|
|
|
|
fctx_minimize_qname(fctx);
|
|
}
|
|
|
|
result = fcount_incr(fctx, false);
|
|
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;
|
|
|
|
again:
|
|
rescan = false;
|
|
|
|
dns_message_t *msg = rctx->query->rmessage;
|
|
MSG_SECTION_FOREACH (msg, section, name) {
|
|
if (!name->attributes.chase) {
|
|
continue;
|
|
}
|
|
name->attributes.chase = false;
|
|
ISC_LIST_FOREACH (name->list, rdataset, link) {
|
|
if (CHASE(rdataset)) {
|
|
rdataset->attributes.chase = false;
|
|
(void)dns_rdataset_additionaldata(
|
|
rdataset, name, check_related, rctx,
|
|
DNS_RDATASET_MAXADDITIONAL);
|
|
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;
|
|
bool retrying = true;
|
|
|
|
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_detach(&rctx->fctx, DNS_R_SERVFAIL);
|
|
return;
|
|
}
|
|
if (dns_rdatatype_atparent(fctx->type)) {
|
|
findoptions |= DNS_DBFIND_NOEXACT;
|
|
}
|
|
/* FIXME: Why??? */
|
|
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_detach(&rctx->fctx, DNS_R_SERVFAIL);
|
|
return;
|
|
}
|
|
if (!dns_name_issubdomain(fname, fctx->domain)) {
|
|
/*
|
|
* The best nameservers are now above our
|
|
* QDOMAIN.
|
|
*/
|
|
FCTXTRACE("nameservers now above QDOMAIN");
|
|
fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
|
|
return;
|
|
}
|
|
|
|
fcount_decr(fctx);
|
|
|
|
dns_name_copy(fname, fctx->domain);
|
|
dns_name_copy(dcname, fctx->qmindcname);
|
|
|
|
result = fcount_incr(fctx, true);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
|
|
return;
|
|
}
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = true;
|
|
fctx_cancelqueries(fctx, true, false);
|
|
fctx_cleanup(fctx);
|
|
retrying = false;
|
|
}
|
|
|
|
/*
|
|
* Try again.
|
|
*/
|
|
fctx_try(fctx, retrying);
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_result_t result;
|
|
|
|
FCTXTRACE("resend");
|
|
inc_stats(fctx->res, dns_resstatscounter_retry);
|
|
result = fctx_query(fctx, addrinfo, rctx->retryopts);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done_detach(&rctx->fctx, result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 isc_result_t
|
|
rctx_next(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
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);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* 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_cleanup(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");
|
|
|
|
fetchctx_ref(fctx);
|
|
result = dns_resolver_createfetch(
|
|
fctx->res, fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL,
|
|
NULL, 0, fctx->options, 0, fctx->qc, fctx->gqc, fctx->loop,
|
|
resume_dslookup, fctx, &fctx->edectx, &fctx->nsrrset, NULL,
|
|
&fctx->nsfetch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (result == DNS_R_DUPLICATE) {
|
|
result = DNS_R_SERVFAIL;
|
|
}
|
|
fctx_done_detach(&rctx->fctx, result);
|
|
fetchctx_detach(&fctx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
dns_message_t *message = NULL;
|
|
|
|
/*
|
|
* Need to attach to the message until the scope
|
|
* of this function ends, since there are many places
|
|
* where the message is used and/or may be destroyed
|
|
* before this function ends.
|
|
*/
|
|
dns_message_attach(query->rmessage, &message);
|
|
|
|
FCTXTRACE4("query canceled in rctx_done();",
|
|
rctx->no_response ? "no response" : "responding", result);
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (dns_fuzzing_resolver &&
|
|
(rctx->next_server || rctx->resend || rctx->nextitem))
|
|
{
|
|
fctx_cancelquery(&query, rctx->finish, rctx->no_response,
|
|
false);
|
|
fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL);
|
|
goto detach;
|
|
}
|
|
#endif /* ifdef ENABLE_AFL */
|
|
|
|
if (rctx->nextitem) {
|
|
REQUIRE(!rctx->next_server);
|
|
REQUIRE(!rctx->resend);
|
|
|
|
result = rctx_next(rctx);
|
|
if (result == ISC_R_SUCCESS) {
|
|
goto detach;
|
|
}
|
|
}
|
|
|
|
/* Cancel the query */
|
|
fctx_cancelquery(&query, rctx->finish, rctx->no_response, false);
|
|
|
|
/*
|
|
* If nobody's waiting for results, don't resend or try next server.
|
|
*/
|
|
LOCK(&fctx->lock);
|
|
if (ISC_LIST_EMPTY(fctx->resps)) {
|
|
rctx->next_server = false;
|
|
rctx->resend = false;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
if (rctx->next_server) {
|
|
rctx_nextserver(rctx, message, addrinfo, result);
|
|
} else if (rctx->resend) {
|
|
rctx_resend(rctx, addrinfo);
|
|
} 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);
|
|
} else {
|
|
/*
|
|
* We're done.
|
|
*/
|
|
fctx_done_detach(&rctx->fctx, result);
|
|
}
|
|
|
|
detach:
|
|
dns_message_detach(&message);
|
|
}
|
|
|
|
/*
|
|
* rctx_logpacket():
|
|
* Log the incoming packet; also log to DNSTAP if configured.
|
|
*/
|
|
static void
|
|
rctx_logpacket(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_result_t result;
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
#ifdef HAVE_DNSTAP
|
|
unsigned char zone[DNS_NAME_MAXWIRE];
|
|
dns_transport_type_t transport_type;
|
|
dns_dtmsgtype_t dtmsgtype;
|
|
dns_compress_t cctx;
|
|
isc_region_t zr;
|
|
isc_buffer_t zb;
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
result = dns_dispentry_getlocaladdress(rctx->query->dispentry,
|
|
&localaddr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
la = &localaddr;
|
|
}
|
|
|
|
dns_message_logpacketfromto(
|
|
rctx->query->rmessage, "received packet",
|
|
&rctx->query->addrinfo->sockaddr, la, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_PACKETS, ISC_LOG_DEBUG(10), fctx->mctx);
|
|
|
|
#ifdef HAVE_DNSTAP
|
|
/*
|
|
* Log the response via dnstap.
|
|
*/
|
|
memset(&zr, 0, sizeof(zr));
|
|
dns_compress_init(&cctx, fctx->mctx, 0);
|
|
dns_compress_setpermitted(&cctx, false);
|
|
isc_buffer_init(&zb, zone, sizeof(zone));
|
|
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;
|
|
}
|
|
|
|
if (rctx->query->addrinfo->transport != NULL) {
|
|
transport_type = dns_transport_get_type(
|
|
rctx->query->addrinfo->transport);
|
|
} else if ((rctx->query->options & DNS_FETCHOPT_TCP) != 0) {
|
|
transport_type = DNS_TRANSPORT_TCP;
|
|
} else {
|
|
transport_type = DNS_TRANSPORT_UDP;
|
|
}
|
|
|
|
dns_dt_send(fctx->res->view, dtmsgtype, la,
|
|
&rctx->query->addrinfo->sockaddr, transport_type, &zr,
|
|
&rctx->query->start, NULL, &rctx->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;
|
|
|
|
QTRACE("rctx_badserver");
|
|
|
|
if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain ||
|
|
rcode == dns_rcode_nxdomain)
|
|
{
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
if ((rcode == dns_rcode_formerr) && rctx->opt == NULL &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
|
|
{
|
|
/*
|
|
* It's very likely they don't like EDNS0.
|
|
*/
|
|
rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
|
|
rctx->resend = true;
|
|
/*
|
|
* Remember that they may not like EDNS0.
|
|
*/
|
|
inc_stats(fctx->res, dns_resstatscounter_edns0fail);
|
|
} else if (rcode == dns_rcode_formerr) {
|
|
if (query->rmessage->cc_echoed) {
|
|
/*
|
|
* Retry without DNS COOKIE.
|
|
*/
|
|
query->addrinfo->flags |= FCTX_ADDRINFO_NOCOOKIE;
|
|
rctx->resend = true;
|
|
log_formerr(fctx, "server sent FORMERR with echoed DNS "
|
|
"COOKIE");
|
|
} else {
|
|
/*
|
|
* The server (or forwarder) doesn't understand us,
|
|
* but others might.
|
|
*/
|
|
rctx->next_server = true;
|
|
rctx->broken_server = DNS_R_REMOTEFORMERR;
|
|
log_formerr(fctx, "server sent 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 if (ISFORWARDER(query->addrinfo) &&
|
|
query->rmessage->rcode == dns_rcode_servfail &&
|
|
(query->options & DNS_FETCHOPT_TRYCD) != 0)
|
|
{
|
|
/*
|
|
* We got a SERVFAIL from a forwarder with
|
|
* CD=0; try again with CD=1.
|
|
*/
|
|
rctx->retryopts |= DNS_FETCHOPT_TRYCD;
|
|
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);
|
|
rctx->broken_server = DNS_R_LAME;
|
|
rctx->next_server = true;
|
|
FCTXTRACE("lame server");
|
|
rctx_done(rctx, result);
|
|
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/***
|
|
*** Resolver Methods
|
|
***/
|
|
static void
|
|
dns_resolver__destroy(dns_resolver_t *res) {
|
|
alternate_t *a = NULL;
|
|
|
|
REQUIRE(!atomic_load_acquire(&res->priming));
|
|
REQUIRE(res->primefetch == NULL);
|
|
|
|
RTRACE("destroy");
|
|
|
|
res->magic = 0;
|
|
|
|
dns_nametree_detach(&res->algorithms);
|
|
dns_nametree_detach(&res->digests);
|
|
|
|
if (res->querystats != NULL) {
|
|
dns_stats_detach(&res->querystats);
|
|
}
|
|
if (res->stats != NULL) {
|
|
isc_stats_detach(&res->stats);
|
|
}
|
|
|
|
isc_mutex_destroy(&res->primelock);
|
|
isc_mutex_destroy(&res->lock);
|
|
|
|
INSIST(isc_hashmap_count(res->fctxs) == 0);
|
|
isc_hashmap_destroy(&res->fctxs);
|
|
isc_rwlock_destroy(&res->fctxs_lock);
|
|
|
|
INSIST(isc_hashmap_count(res->counters) == 0);
|
|
isc_hashmap_destroy(&res->counters);
|
|
isc_rwlock_destroy(&res->counters_lock);
|
|
|
|
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_view_weakdetach(&res->view);
|
|
|
|
for (size_t i = 0; i < res->nloops; i++) {
|
|
dns_message_destroypools(&res->namepools[i], &res->rdspools[i]);
|
|
}
|
|
isc_mem_cput(res->mctx, res->rdspools, res->nloops,
|
|
sizeof(res->rdspools[0]));
|
|
isc_mem_cput(res->mctx, res->namepools, res->nloops,
|
|
sizeof(res->namepools[0]));
|
|
|
|
isc_mem_putanddetach(&res->mctx, res, sizeof(*res));
|
|
}
|
|
|
|
static void
|
|
spillattimer_countdown(void *arg) {
|
|
dns_resolver_t *res = (dns_resolver_t *)arg;
|
|
unsigned int spillat = 0;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
if (atomic_load(&res->exiting)) {
|
|
isc_timer_destroy(&res->spillattimer);
|
|
return;
|
|
}
|
|
|
|
LOCK(&res->lock);
|
|
INSIST(!atomic_load_acquire(&res->exiting));
|
|
if (res->spillat > res->spillatmin) {
|
|
spillat = --res->spillat;
|
|
}
|
|
if (res->spillat <= res->spillatmin) {
|
|
isc_timer_destroy(&res->spillattimer);
|
|
}
|
|
UNLOCK(&res->lock);
|
|
if (spillat > 0) {
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_NOTICE,
|
|
"clients-per-query decreased to %u", spillat);
|
|
}
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_create(dns_view_t *view, unsigned int options,
|
|
isc_tlsctx_cache_t *tlsctx_cache,
|
|
dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
|
|
dns_resolver_t **resp) {
|
|
dns_resolver_t *res = NULL;
|
|
|
|
/*
|
|
* Create a resolver.
|
|
*/
|
|
|
|
REQUIRE(DNS_VIEW_VALID(view));
|
|
REQUIRE(resp != NULL && *resp == NULL);
|
|
REQUIRE(tlsctx_cache != NULL);
|
|
REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL);
|
|
|
|
res = isc_mem_get(view->mctx, sizeof(*res));
|
|
*res = (dns_resolver_t){
|
|
.rdclass = view->rdclass,
|
|
.options = options,
|
|
.tlsctx_cache = tlsctx_cache,
|
|
.spillatmin = 10,
|
|
.spillat = 10,
|
|
.spillatmax = 100,
|
|
.retryinterval = 800,
|
|
.nonbackofftries = 3,
|
|
.query_timeout = DEFAULT_QUERY_TIMEOUT,
|
|
.maxdepth = DEFAULT_RECURSION_DEPTH,
|
|
.maxqueries = DEFAULT_MAX_QUERIES,
|
|
.alternates = ISC_LIST_INITIALIZER,
|
|
.nloops = isc_loopmgr_nloops(),
|
|
.maxvalidations = DEFAULT_MAX_VALIDATIONS,
|
|
.maxvalidationfails = DEFAULT_MAX_VALIDATION_FAILURES,
|
|
};
|
|
|
|
RTRACE("create");
|
|
|
|
dns_view_weakattach(view, &res->view);
|
|
isc_mem_attach(view->mctx, &res->mctx);
|
|
|
|
res->quotaresp[dns_quotatype_zone] = DNS_R_DROP;
|
|
res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL;
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
fprintf(stderr, "dns_resolver__init:%s:%s:%d:%p->references = 1\n",
|
|
__func__, __FILE__, __LINE__, res);
|
|
#endif
|
|
isc_refcount_init(&res->references, 1);
|
|
|
|
isc_hashmap_create(view->mctx, RES_DOMAIN_HASH_BITS, &res->fctxs);
|
|
isc_rwlock_init(&res->fctxs_lock);
|
|
|
|
isc_hashmap_create(view->mctx, RES_DOMAIN_HASH_BITS, &res->counters);
|
|
isc_rwlock_init(&res->counters_lock);
|
|
|
|
if (dispatchv4 != NULL) {
|
|
dns_dispatchset_create(res->mctx, dispatchv4, &res->dispatches4,
|
|
res->nloops);
|
|
}
|
|
|
|
if (dispatchv6 != NULL) {
|
|
dns_dispatchset_create(res->mctx, dispatchv6, &res->dispatches6,
|
|
res->nloops);
|
|
}
|
|
|
|
isc_mutex_init(&res->lock);
|
|
isc_mutex_init(&res->primelock);
|
|
|
|
dns_nametree_create(res->mctx, DNS_NAMETREE_BITS, "algorithms",
|
|
&res->algorithms);
|
|
dns_nametree_create(res->mctx, DNS_NAMETREE_BITS, "ds-digests",
|
|
&res->digests);
|
|
|
|
res->namepools = isc_mem_cget(res->mctx, res->nloops,
|
|
sizeof(res->namepools[0]));
|
|
res->rdspools = isc_mem_cget(res->mctx, res->nloops,
|
|
sizeof(res->rdspools[0]));
|
|
for (size_t i = 0; i < res->nloops; i++) {
|
|
isc_loop_t *loop = isc_loop_get(i);
|
|
isc_mem_t *pool_mctx = isc_loop_getmctx(loop);
|
|
|
|
dns_message_createpools(pool_mctx, &res->namepools[i],
|
|
&res->rdspools[i]);
|
|
}
|
|
|
|
res->magic = RES_MAGIC;
|
|
|
|
*resp = res;
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
prime_done(void *arg) {
|
|
dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
|
|
dns_resolver_t *res = resp->arg;
|
|
dns_fetch_t *fetch = NULL;
|
|
dns_db_t *db = NULL;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
int level = (resp->result == ISC_R_SUCCESS) ? ISC_LOG_DEBUG(1)
|
|
: ISC_LOG_NOTICE;
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level,
|
|
"resolver priming query complete: %s",
|
|
isc_result_totext(resp->result));
|
|
|
|
LOCK(&res->primelock);
|
|
fetch = res->primefetch;
|
|
res->primefetch = NULL;
|
|
UNLOCK(&res->primelock);
|
|
|
|
atomic_compare_exchange_enforced(&res->priming, &(bool){ true }, false);
|
|
|
|
if (resp->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 (resp->node != NULL) {
|
|
dns_db_detachnode(&resp->node);
|
|
}
|
|
if (resp->db != NULL) {
|
|
dns_db_detach(&resp->db);
|
|
}
|
|
if (dns_rdataset_isassociated(resp->rdataset)) {
|
|
dns_rdataset_disassociate(resp->rdataset);
|
|
}
|
|
INSIST(resp->sigrdataset == NULL);
|
|
|
|
isc_mem_put(res->mctx, resp->rdataset, sizeof(*resp->rdataset));
|
|
dns_resolver_freefresp(&resp);
|
|
dns_resolver_destroyfetch(&fetch);
|
|
}
|
|
|
|
void
|
|
dns_resolver_prime(dns_resolver_t *res) {
|
|
bool want_priming = false;
|
|
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");
|
|
|
|
dns_rdataset_t *rdataset = isc_mem_get(res->mctx,
|
|
sizeof(*rdataset));
|
|
dns_rdataset_init(rdataset);
|
|
|
|
LOCK(&res->primelock);
|
|
result = dns_resolver_createfetch(
|
|
res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL,
|
|
NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, NULL,
|
|
isc_loop(), prime_done, res, NULL, rdataset, NULL,
|
|
&res->primefetch);
|
|
UNLOCK(&res->primelock);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_put(res->mctx, rdataset, sizeof(*rdataset));
|
|
atomic_compare_exchange_enforced(
|
|
&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_shutdown(dns_resolver_t *res) {
|
|
isc_result_t result;
|
|
bool is_false = false;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
RTRACE("shutdown");
|
|
|
|
if (atomic_compare_exchange_strong(&res->exiting, &is_false, true)) {
|
|
isc_hashmap_iter_t *it = NULL;
|
|
|
|
RTRACE("exiting");
|
|
|
|
RWLOCK(&res->fctxs_lock, isc_rwlocktype_write);
|
|
isc_hashmap_iter_create(res->fctxs, &it);
|
|
for (result = isc_hashmap_iter_first(it);
|
|
result == ISC_R_SUCCESS;
|
|
result = isc_hashmap_iter_next(it))
|
|
{
|
|
fetchctx_t *fctx = NULL;
|
|
|
|
isc_hashmap_iter_current(it, (void **)&fctx);
|
|
INSIST(fctx != NULL);
|
|
|
|
fetchctx_ref(fctx);
|
|
isc_async_run(fctx->loop, fctx_shutdown, fctx);
|
|
}
|
|
isc_hashmap_iter_destroy(&it);
|
|
RWUNLOCK(&res->fctxs_lock, isc_rwlocktype_write);
|
|
|
|
LOCK(&res->lock);
|
|
if (res->spillattimer != NULL) {
|
|
isc_timer_async_destroy(&res->spillattimer);
|
|
}
|
|
UNLOCK(&res->lock);
|
|
}
|
|
}
|
|
|
|
#if DNS_RESOLVER_TRACE
|
|
ISC_REFCOUNT_TRACE_IMPL(dns_resolver, dns_resolver__destroy);
|
|
#else
|
|
ISC_REFCOUNT_IMPL(dns_resolver, dns_resolver__destroy);
|
|
#endif
|
|
|
|
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(level)) {
|
|
return;
|
|
}
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
|
|
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level,
|
|
"fetch: %s/%s", namebuf, typebuf);
|
|
}
|
|
|
|
static void
|
|
fctx_minimize_qname(fetchctx_t *fctx) {
|
|
isc_result_t result;
|
|
unsigned int dlabels, nlabels;
|
|
dns_name_t name;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
dns_name_init(&name);
|
|
|
|
dlabels = dns_name_countlabels(fctx->qmindcname);
|
|
nlabels = dns_name_countlabels(fctx->name);
|
|
|
|
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 + 1;
|
|
}
|
|
} else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) {
|
|
fctx->qmin_labels = DNS_NAME_MAXLABELS;
|
|
}
|
|
|
|
if (fctx->qmin_labels <= nlabels) {
|
|
dns_rdataset_t rdataset;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *fname = dns_fixedname_initname(&fixed);
|
|
dns_rdataset_init(&rdataset);
|
|
do {
|
|
/*
|
|
* We want to query for qmin_labels from fctx->name.
|
|
*/
|
|
dns_name_split(fctx->name, fctx->qmin_labels, NULL,
|
|
&name);
|
|
/*
|
|
* Look to see if we have anything cached about NS
|
|
* RRsets at this name and if so skip this name and
|
|
* try with an additional label prepended.
|
|
*/
|
|
result = dns_db_find(fctx->cache, &name, NULL,
|
|
dns_rdatatype_ns, 0, 0, NULL,
|
|
fname, &rdataset, NULL);
|
|
if (dns_rdataset_isassociated(&rdataset)) {
|
|
dns_rdataset_disassociate(&rdataset);
|
|
}
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
case DNS_R_CNAME:
|
|
case DNS_R_DNAME:
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_NCACHENXRRSET:
|
|
fctx->qmin_labels++;
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
} while (fctx->qmin_labels <= nlabels);
|
|
}
|
|
|
|
/*
|
|
* DS lookups come from the parent zone so we don't need to do a
|
|
* NS lookup at the QNAME. If the QTYPE is NS we are not leaking
|
|
* the type if we just do the final NS lookup.
|
|
*/
|
|
if (fctx->qmin_labels < nlabels ||
|
|
(fctx->type != dns_rdatatype_ns && fctx->type != dns_rdatatype_ds &&
|
|
fctx->qmin_labels == nlabels))
|
|
{
|
|
dns_name_copy(&name, fctx->qminname);
|
|
fctx->qmintype = dns_rdatatype_ns;
|
|
fctx->minimized = true;
|
|
} else {
|
|
/* Minimization is done, we'll ask for whole qname */
|
|
dns_name_copy(fctx->name, fctx->qminname);
|
|
fctx->qmintype = fctx->type;
|
|
fctx->minimized = false;
|
|
}
|
|
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
dns_name_format(fctx->qminname, domainbuf, sizeof(domainbuf));
|
|
isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(5),
|
|
"QNAME minimization - %s minimized, qmintype %d "
|
|
"qminname %s",
|
|
fctx->minimized ? "" : "not", fctx->qmintype, domainbuf);
|
|
}
|
|
|
|
static isc_result_t
|
|
get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
|
|
dns_rdatatype_t type, const dns_name_t *domain,
|
|
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
|
|
unsigned int options, unsigned int depth, isc_counter_t *qc,
|
|
isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx) {
|
|
isc_result_t result;
|
|
fetchctx_t key = {
|
|
.name = UNCONST(name),
|
|
.options = options,
|
|
.type = type,
|
|
};
|
|
fetchctx_t *fctx = NULL;
|
|
isc_rwlocktype_t locktype = isc_rwlocktype_read;
|
|
uint32_t hashval = fctx_hash(&key);
|
|
|
|
again:
|
|
RWLOCK(&res->fctxs_lock, locktype);
|
|
result = isc_hashmap_find(res->fctxs, hashval, fctx_match, &key,
|
|
(void **)&fctx);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case ISC_R_NOTFOUND:
|
|
result = fctx_create(res, loop, name, type, domain, nameservers,
|
|
client, options, depth, qc, gqc, &fctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
RWUNLOCK(&res->fctxs_lock, locktype);
|
|
return result;
|
|
}
|
|
|
|
UPGRADELOCK(&res->fctxs_lock, locktype);
|
|
|
|
void *found = NULL;
|
|
result = isc_hashmap_add(res->fctxs, hashval, fctx_match, fctx,
|
|
fctx, &found);
|
|
if (result == ISC_R_SUCCESS) {
|
|
*new_fctx = true;
|
|
} else {
|
|
/*
|
|
* The fctx_done() tries to acquire the fctxs_lock.
|
|
* Destroy the newly created fetchctx directly.
|
|
*/
|
|
fctx->state = fetchstate_done;
|
|
isc_timer_destroy(&fctx->timer);
|
|
|
|
fetchctx_detach(&fctx);
|
|
fctx = found;
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
fetchctx_ref(fctx);
|
|
|
|
/*
|
|
* We need to lock the fetch context before unlocking the hash table to
|
|
* prevent other threads from looking up this thread before it has been
|
|
* properly initialized and started.
|
|
*/
|
|
LOCK(&fctx->lock);
|
|
RWUNLOCK(&res->fctxs_lock, locktype);
|
|
|
|
if (SHUTTINGDOWN(fctx) || fctx->cloned) {
|
|
/*
|
|
* This is the single place where fctx might get
|
|
* accesses from a different thread, so we need to
|
|
* double check whether fctxs is done (or cloned) and
|
|
* help with the release if the fctx has been cloned.
|
|
*/
|
|
UNLOCK(&fctx->lock);
|
|
|
|
/* The fctx will get deleted either here or in fctx__done() */
|
|
RWLOCK(&res->fctxs_lock, isc_rwlocktype_write);
|
|
(void)isc_hashmap_delete(res->fctxs, fctx_hash(fctx), match_ptr,
|
|
fctx);
|
|
RWUNLOCK(&res->fctxs_lock, isc_rwlocktype_write);
|
|
|
|
fetchctx_detach(&fctx);
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* The function returns a locked fetch context,
|
|
*/
|
|
*fctxp = fctx;
|
|
|
|
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_counter_t *gqc,
|
|
isc_loop_t *loop, isc_job_cb cb, void *arg,
|
|
dns_edectx_t *edectx, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp) {
|
|
dns_fetch_t *fetch = NULL;
|
|
fetchctx_t *fctx = NULL;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
bool new_fctx = false;
|
|
unsigned int count = 0;
|
|
unsigned int spillat;
|
|
unsigned int spillatmin;
|
|
isc_mem_t *mctx = isc_loop_getmctx(loop);
|
|
|
|
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);
|
|
|
|
if (atomic_load_acquire(&res->exiting)) {
|
|
return ISC_R_SHUTTINGDOWN;
|
|
}
|
|
|
|
log_fetch(name, type);
|
|
|
|
fetch = isc_mem_get(mctx, sizeof(*fetch));
|
|
*fetch = (dns_fetch_t){ 0 };
|
|
|
|
dns_resolver_attach(res, &fetch->res);
|
|
isc_mem_attach(mctx, &fetch->mctx);
|
|
|
|
if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
|
|
/*
|
|
* We don't save the unshared fetch context to a bucket because
|
|
* we also would never match it again.
|
|
*/
|
|
|
|
LOCK(&res->lock);
|
|
spillat = res->spillat;
|
|
spillatmin = res->spillatmin;
|
|
UNLOCK(&res->lock);
|
|
|
|
result = get_attached_fctx(res, loop, name, type, domain,
|
|
nameservers, client, options, depth,
|
|
qc, gqc, &fctx, &new_fctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
/* On success, the fctx is locked in get_attached_fctx() */
|
|
INSIST(!SHUTTINGDOWN(fctx));
|
|
|
|
/* Is this a duplicate? */
|
|
if (client != NULL) {
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
if (resp->client != NULL && resp->id == id &&
|
|
isc_sockaddr_equal(resp->client, client))
|
|
{
|
|
result = DNS_R_DUPLICATE;
|
|
goto unlock;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
if (count >= spillatmin && spillatmin != 0) {
|
|
if (count >= spillat) {
|
|
fctx->spilled = true;
|
|
}
|
|
if (fctx->spilled) {
|
|
inc_stats(res, dns_resstatscounter_clientquota);
|
|
fctx->dropped++;
|
|
result = DNS_R_DROP;
|
|
goto unlock;
|
|
}
|
|
}
|
|
} else {
|
|
result = fctx_create(res, loop, name, type, domain, nameservers,
|
|
client, options, depth, qc, gqc, &fctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
new_fctx = true;
|
|
}
|
|
|
|
RUNTIME_CHECK(fctx != NULL);
|
|
|
|
if (fctx->depth > depth) {
|
|
fctx->depth = depth;
|
|
}
|
|
|
|
fctx->allowed++;
|
|
|
|
fctx_join(fctx, loop, client, id, cb, arg, edectx, rdataset,
|
|
sigrdataset, fetch);
|
|
|
|
if (new_fctx) {
|
|
fetchctx_ref(fctx);
|
|
isc_async_run(fctx->loop, fctx_start, fctx);
|
|
}
|
|
|
|
unlock:
|
|
if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
|
|
UNLOCK(&fctx->lock);
|
|
fetchctx_unref(fctx);
|
|
}
|
|
|
|
fail:
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_resolver_detach(&fetch->res);
|
|
isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
|
|
return result;
|
|
}
|
|
|
|
FTRACE("created");
|
|
*fetchp = fetch;
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
void
|
|
dns_resolver_cancelfetch(dns_fetch_t *fetch) {
|
|
fetchctx_t *fctx = NULL;
|
|
bool last_fetch = false;
|
|
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
FTRACE("cancelfetch");
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
/*
|
|
* Find the completion event associated with this fetch (as opposed
|
|
* to those for other fetches that have joined the same fctx) and run
|
|
* the callback asynchronously with a ISC_R_CANCELED result.
|
|
*/
|
|
if (fctx->state != fetchstate_done) {
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
if (resp->fetch == fetch) {
|
|
resp->result = ISC_R_CANCELED;
|
|
ISC_LIST_UNLINK(fctx->resps, resp, link);
|
|
isc_async_run(resp->loop, resp->cb, resp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ISC_LIST_EMPTY(fctx->resps)) {
|
|
last_fetch = true;
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
if (last_fetch) {
|
|
fetchctx_ref(fctx);
|
|
isc_async_run(fctx->loop, fctx_shutdown, fctx);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
|
|
dns_fetch_t *fetch = NULL;
|
|
dns_resolver_t *res = NULL;
|
|
fetchctx_t *fctx = NULL;
|
|
|
|
REQUIRE(fetchp != NULL);
|
|
fetch = *fetchp;
|
|
*fetchp = NULL;
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fetch->res;
|
|
|
|
FTRACE("destroyfetch");
|
|
|
|
fetch->magic = 0;
|
|
|
|
LOCK(&fctx->lock);
|
|
/*
|
|
* Sanity check: the caller should have gotten its event before
|
|
* trying to destroy the fetch.
|
|
*/
|
|
if (fctx->state != fetchstate_done) {
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
RUNTIME_CHECK(resp->fetch != fetch);
|
|
}
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
|
|
isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
|
|
|
|
fetchctx_detach(&fctx);
|
|
dns_resolver_detach(&res);
|
|
}
|
|
|
|
void
|
|
dns_resolver_logfetch(dns_fetch_t *fetch, isc_logcategory_t category,
|
|
isc_logmodule_t module, int level, bool duplicateok) {
|
|
fetchctx_t *fctx = NULL;
|
|
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
if (!fctx->logged || duplicateok) {
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
|
|
isc_log_write(category, module, level,
|
|
"fetch completed 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]",
|
|
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(&fctx->lock);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void
|
|
dns_resolver_addalternate(dns_resolver_t *res, const isc_sockaddr_t *alt,
|
|
const dns_name_t *name, in_port_t port) {
|
|
alternate_t *a;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(!res->frozen);
|
|
REQUIRE((alt == NULL) ^ (name == NULL));
|
|
|
|
a = isc_mem_get(res->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);
|
|
dns_name_dup(name, res->mctx, &a->_u._n.name);
|
|
}
|
|
ISC_LINK_INIT(a, link);
|
|
ISC_LIST_APPEND(res->alternates, a, link);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
|
|
unsigned int alg) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if (alg >= DST_MAX_ALGS) {
|
|
return ISC_R_RANGE;
|
|
}
|
|
|
|
return dns_nametree_add(resolver->algorithms, name, alg);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name,
|
|
unsigned int digest_type) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if (digest_type > 255) {
|
|
return ISC_R_RANGE;
|
|
}
|
|
|
|
return dns_nametree_add(resolver->digests, name, digest_type);
|
|
}
|
|
|
|
bool
|
|
dns_resolver_algorithm_supported(dns_resolver_t *resolver,
|
|
const dns_name_t *name, unsigned int alg,
|
|
unsigned char *private, size_t len) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Look up the DST algorithm identifier for private-OID
|
|
* and private-DNS keys.
|
|
*/
|
|
if (alg == DST_ALG_PRIVATEDNS && private != NULL) {
|
|
isc_buffer_t b;
|
|
isc_buffer_init(&b, private, len);
|
|
isc_buffer_add(&b, len);
|
|
alg = dst_algorithm_fromprivatedns(&b);
|
|
if (alg == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (alg == DST_ALG_PRIVATEOID && private != NULL) {
|
|
isc_buffer_t b;
|
|
isc_buffer_init(&b, private, len);
|
|
isc_buffer_add(&b, len);
|
|
alg = dst_algorithm_fromprivateoid(&b);
|
|
if (alg == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (dns_nametree_covered(resolver->algorithms, name, NULL, alg)) {
|
|
return false;
|
|
}
|
|
|
|
return dst_algorithm_supported(alg);
|
|
}
|
|
|
|
bool
|
|
dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
|
|
const dns_name_t *name,
|
|
unsigned int digest_type) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if (dns_nametree_covered(resolver->digests, name, NULL, digest_type)) {
|
|
return false;
|
|
}
|
|
|
|
return dst_ds_digest_supported(digest_type);
|
|
}
|
|
|
|
void
|
|
dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur,
|
|
uint32_t *min, uint32_t *max) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
LOCK(&resolver->lock);
|
|
SET_IF_NOT_NULL(cur, resolver->spillat);
|
|
SET_IF_NOT_NULL(min, resolver->spillatmin);
|
|
SET_IF_NOT_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);
|
|
}
|
|
|
|
uint32_t
|
|
dns_resolver_getfetchesperzone(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return atomic_load_relaxed(&resolver->zspill);
|
|
}
|
|
|
|
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 < MINIMUM_QUERY_TIMEOUT) {
|
|
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_setmaxvalidations(dns_resolver_t *resolver, uint32_t max) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
atomic_store(&resolver->maxvalidations, max);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
atomic_store(&resolver->maxvalidationfails, max);
|
|
}
|
|
|
|
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 *res, isc_statsformat_t format,
|
|
FILE *fp) {
|
|
isc_result_t result;
|
|
isc_hashmap_iter_t *it = NULL;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(fp != NULL);
|
|
REQUIRE(format == isc_statsformat_file);
|
|
|
|
LOCK(&res->lock);
|
|
fprintf(fp, "clients-per-query: %u/%u/%u\n", res->spillatmin,
|
|
res->spillat, res->spillatmax);
|
|
UNLOCK(&res->lock);
|
|
|
|
RWLOCK(&res->fctxs_lock, isc_rwlocktype_read);
|
|
isc_hashmap_iter_create(res->fctxs, &it);
|
|
for (result = isc_hashmap_iter_first(it); result == ISC_R_SUCCESS;
|
|
result = isc_hashmap_iter_next(it))
|
|
{
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
char timebuf[1024];
|
|
fetchctx_t *fctx = NULL;
|
|
unsigned int resp_count = 0, query_count = 0;
|
|
|
|
isc_hashmap_iter_current(it, (void **)&fctx);
|
|
|
|
LOCK(&fctx->lock);
|
|
dns_name_print(fctx->name, fp);
|
|
|
|
isc_time_formatISO8601ms(&fctx->start, timebuf,
|
|
sizeof(timebuf));
|
|
|
|
dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
|
|
|
|
fprintf(fp, "/%s (%s): started %s, ", typebuf,
|
|
fctx->state == fetchstate_active ? "active" : "done",
|
|
timebuf);
|
|
|
|
ISC_LIST_FOREACH (fctx->resps, resp, link) {
|
|
resp_count++;
|
|
}
|
|
|
|
ISC_LIST_FOREACH (fctx->queries, query, link) {
|
|
query_count++;
|
|
}
|
|
|
|
if (isc_timer_running(fctx->timer)) {
|
|
strlcpy(timebuf, "expires ", sizeof(timebuf));
|
|
isc_time_formatISO8601ms(&fctx->expires, timebuf + 8,
|
|
sizeof(timebuf) - 8);
|
|
} else {
|
|
strlcpy(timebuf, "not running", sizeof(timebuf));
|
|
}
|
|
|
|
fprintf(fp,
|
|
"fetches: %u active (%" PRIuFAST32
|
|
" allowed, %" PRIuFAST32
|
|
" dropped%s), queries: %u, timer %s\n",
|
|
resp_count, fctx->allowed, fctx->dropped,
|
|
fctx->spilled ? ", spilled" : "", query_count, timebuf);
|
|
|
|
UNLOCK(&fctx->lock);
|
|
}
|
|
isc_hashmap_iter_destroy(&it);
|
|
RWUNLOCK(&res->fctxs_lock, isc_rwlocktype_read);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_dumpquota(dns_resolver_t *res, isc_buffer_t **buf) {
|
|
isc_result_t result;
|
|
isc_hashmap_iter_t *it = NULL;
|
|
uint_fast32_t spill;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
spill = atomic_load_acquire(&res->zspill);
|
|
if (spill == 0) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
RWLOCK(&res->counters_lock, isc_rwlocktype_read);
|
|
isc_hashmap_iter_create(res->counters, &it);
|
|
for (result = isc_hashmap_iter_first(it); result == ISC_R_SUCCESS;
|
|
result = isc_hashmap_iter_next(it))
|
|
{
|
|
fctxcount_t *counter = NULL;
|
|
uint_fast32_t count, dropped, allowed;
|
|
char nb[DNS_NAME_FORMATSIZE];
|
|
char text[DNS_NAME_FORMATSIZE + BUFSIZ];
|
|
|
|
isc_hashmap_iter_current(it, (void **)&counter);
|
|
|
|
LOCK(&counter->lock);
|
|
count = counter->count;
|
|
dropped = counter->dropped;
|
|
allowed = counter->allowed;
|
|
UNLOCK(&counter->lock);
|
|
|
|
if (count < spill) {
|
|
continue;
|
|
}
|
|
|
|
dns_name_format(counter->domain, nb, sizeof(nb));
|
|
snprintf(text, sizeof(text),
|
|
"\n- %s: %" PRIuFAST32 " active (allowed %" PRIuFAST32
|
|
" spilled %" PRIuFAST32 ")",
|
|
nb, count, allowed, dropped);
|
|
|
|
result = isc_buffer_reserve(*buf, strlen(text));
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
isc_buffer_putstr(*buf, text);
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
cleanup:
|
|
isc_hashmap_iter_destroy(&it);
|
|
RWUNLOCK(&res->counters_lock, isc_rwlocktype_read);
|
|
return result;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
void
|
|
dns_resolver_setstats(dns_resolver_t *res, isc_stats_t *stats) {
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(res->stats == NULL);
|
|
|
|
isc_stats_attach(stats, &res->stats);
|
|
|
|
/* initialize the bucket "counter"; it's a static value */
|
|
set_stats(res, dns_resstatscounter_buckets, isc_loopmgr_nloops());
|
|
}
|
|
|
|
void
|
|
dns_resolver_getstats(dns_resolver_t *res, isc_stats_t **statsp) {
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(statsp != NULL && *statsp == NULL);
|
|
|
|
if (res->stats != NULL) {
|
|
isc_stats_attach(res->stats, statsp);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_resolver_incstats(dns_resolver_t *res, isc_statscounter_t counter) {
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
isc_stats_increment(res->stats, counter);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setquerystats(dns_resolver_t *res, dns_stats_t *stats) {
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(res->querystats == NULL);
|
|
|
|
dns_stats_attach(stats, &res->querystats);
|
|
}
|
|
|
|
void
|
|
dns_resolver_getquerystats(dns_resolver_t *res, dns_stats_t **statsp) {
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(statsp != NULL && *statsp == NULL);
|
|
|
|
if (res->querystats != NULL) {
|
|
dns_stats_attach(res->querystats, statsp);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_resolver_freefresp(dns_fetchresponse_t **frespp) {
|
|
REQUIRE(frespp != NULL);
|
|
|
|
if (*frespp == NULL) {
|
|
return;
|
|
}
|
|
|
|
dns_fetchresponse_t *fresp = *frespp;
|
|
|
|
*frespp = NULL;
|
|
isc_mem_putanddetach(&fresp->mctx, fresp, sizeof(*fresp));
|
|
}
|