11139 lines
284 KiB
C
11139 lines
284 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/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/log.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/rbt.h>
|
|
#include <dns/rcode.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataclass.h>
|
|
#include <dns/rdatalist.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/resolver.h>
|
|
#include <dns/rootns.h>
|
|
#include <dns/stats.h>
|
|
#include <dns/tsig.h>
|
|
#include <dns/validator.h>
|
|
#include <dns/zone.h>
|
|
|
|
#ifdef WANT_QUERYTRACE
|
|
#define RTRACE(m) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
|
|
res, (m))
|
|
#define RRTRACE(r, m) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \
|
|
(r), (m))
|
|
#define FCTXTRACE(m) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fctx %p(%s): %s", fctx, fctx->info, (m))
|
|
#define FCTXTRACE2(m1, m2) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fctx %p(%s): %s %s", fctx, fctx->info, (m1), (m2))
|
|
#define FCTXTRACE3(m, res) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fctx %p(%s): [result: %s] %s", fctx, fctx->info, \
|
|
isc_result_totext(res), (m))
|
|
#define FCTXTRACE4(m1, m2, res) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fctx %p(%s): [result: %s] %s %s", fctx, fctx->info, \
|
|
isc_result_totext(res), (m1), (m2))
|
|
#define FCTXTRACE5(m1, m2, v) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fctx %p(%s): %s %s%u", fctx, fctx->info, (m1), (m2), \
|
|
(v))
|
|
#define FTRACE(m) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"fetch %p (fctx %p(%s)): %s", fetch, fetch->private, \
|
|
fetch->private->info, (m))
|
|
#define QTRACE(m) \
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \
|
|
"resquery %p (fctx %p(%s)): %s", query, query->fctx, \
|
|
query->fctx->info, (m))
|
|
#else /* ifdef WANT_QUERYTRACE */
|
|
#define RTRACE(m) \
|
|
do { \
|
|
UNUSED(m); \
|
|
} while (0)
|
|
#define RRTRACE(r, m) \
|
|
do { \
|
|
UNUSED(r); \
|
|
UNUSED(m); \
|
|
} while (0)
|
|
#define FCTXTRACE(m) \
|
|
do { \
|
|
UNUSED(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 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;
|
|
unsigned int 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_LINK(struct fetchctx) link;
|
|
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_time_t next_timeout;
|
|
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;
|
|
dns_validator_t *validator;
|
|
ISC_LIST(dns_validator_t) validators;
|
|
dns_db_t *cache;
|
|
dns_adb_t *adb;
|
|
bool ns_ttl_ok;
|
|
uint32_t ns_ttl;
|
|
isc_counter_t *qc;
|
|
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_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))
|
|
|
|
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;
|
|
isc_loopmgr_t *loopmgr;
|
|
isc_nm_t *nm;
|
|
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;
|
|
dns_nametree_t *mustbesecure;
|
|
unsigned int spillatmax;
|
|
unsigned int spillatmin;
|
|
isc_timer_t *spillattimer;
|
|
bool zero_no_soa_ttl;
|
|
unsigned int query_timeout;
|
|
unsigned int maxdepth;
|
|
unsigned int maxqueries;
|
|
isc_result_t quotaresp[2];
|
|
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 & DNS_RDATASETATTR_NXDOMAIN) != 0)
|
|
#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
|
|
#define STATICSTUB(r) (((r)->attributes & DNS_RDATASETATTR_STATICSTUB) != 0)
|
|
|
|
#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 unsigned char ip6_arpa_offsets[] = { 0, 4, 9 };
|
|
static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data,
|
|
ip6_arpa_offsets);
|
|
|
|
static 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
|
|
ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
|
|
dns_ttl_t maxttl, bool optout, bool secure,
|
|
dns_rdataset_t *ardataset, isc_result_t *eresultp);
|
|
static void
|
|
validated(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 isc_result_t
|
|
findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
|
|
dns_rdatatype_t type, dns_name_t **noqname);
|
|
|
|
#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 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 glue_in_answer; /* glue may be in the answer
|
|
* section */
|
|
bool ns_in_answer; /* NS may be in the answer
|
|
* section */
|
|
bool negative; /* is this a negative response? */
|
|
|
|
isc_stdtime_t now; /* time info */
|
|
isc_time_t tnow;
|
|
isc_time_t *finish;
|
|
|
|
unsigned int dname_labels;
|
|
unsigned int domain_labels; /* range of permissible number
|
|
* of
|
|
* labels in a DNAME */
|
|
|
|
dns_name_t *aname; /* answer name */
|
|
dns_rdataset_t *ardataset; /* answer rdataset */
|
|
|
|
dns_name_t *cname; /* CNAME name */
|
|
dns_rdataset_t *crdataset; /* CNAME rdataset */
|
|
|
|
dns_name_t *dname; /* DNAME name */
|
|
dns_rdataset_t *drdataset; /* DNAME rdataset */
|
|
|
|
dns_name_t *ns_name; /* NS name */
|
|
dns_rdataset_t *ns_rdataset; /* NS rdataset */
|
|
|
|
dns_name_t *soa_name; /* SOA name in a negative answer */
|
|
dns_name_t *ds_name; /* DS name in a negative answer */
|
|
|
|
dns_name_t *found_name; /* invalid name in negative
|
|
* response */
|
|
dns_rdatatype_t found_type; /* invalid type in negative
|
|
* response */
|
|
|
|
dns_rdataset_t *opt; /* OPT rdataset */
|
|
} respctx_t;
|
|
|
|
static void
|
|
rctx_respinit(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 isc_result_t
|
|
valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
|
|
dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset, unsigned int valoptions) {
|
|
dns_validator_t *validator = NULL;
|
|
dns_valarg_t *valarg = NULL;
|
|
isc_result_t result;
|
|
|
|
valarg = isc_mem_get(fctx->mctx, sizeof(*valarg));
|
|
*valarg = (dns_valarg_t){
|
|
.addrinfo = addrinfo,
|
|
};
|
|
|
|
fetchctx_attach(fctx, &valarg->fctx);
|
|
|
|
if (!ISC_LIST_EMPTY(fctx->validators)) {
|
|
valoptions |= DNS_VALIDATOR_DEFER;
|
|
} else {
|
|
valoptions &= ~DNS_VALIDATOR_DEFER;
|
|
}
|
|
|
|
result = dns_validator_create(
|
|
fctx->res->view, name, type, rdataset, sigrdataset, message,
|
|
valoptions, 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);
|
|
if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
|
|
INSIST(fctx->validator == NULL);
|
|
fctx->validator = validator;
|
|
}
|
|
ISC_LIST_APPEND(fctx->validators, validator, link);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
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;
|
|
dns_adbfind_t *find = NULL;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
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) {
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) {
|
|
for (find = ISC_LIST_HEAD(fctx->finds); find != NULL;
|
|
find = ISC_LIST_NEXT(find, publink))
|
|
{
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDALT(fctx)) {
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
}
|
|
}
|
|
for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
|
|
find = ISC_LIST_NEXT(find, publink))
|
|
{
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (UNMARKED(addrinfo)) {
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for any outstanding 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) {
|
|
dns_adbfind_t *find = NULL, *next_find = NULL;
|
|
dns_adbaddrinfo_t *addr = NULL, *next_addr = NULL;
|
|
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find)
|
|
{
|
|
next_find = ISC_LIST_NEXT(find, publink);
|
|
ISC_LIST_UNLINK(fctx->finds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
fetchctx_unref(fctx);
|
|
}
|
|
fctx->find = NULL;
|
|
|
|
for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
|
|
find = next_find)
|
|
{
|
|
next_find = ISC_LIST_NEXT(find, publink);
|
|
ISC_LIST_UNLINK(fctx->altfinds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
fetchctx_unref(fctx);
|
|
}
|
|
fctx->altfind = NULL;
|
|
|
|
for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL;
|
|
addr = next_addr)
|
|
{
|
|
next_addr = ISC_LIST_NEXT(addr, publink);
|
|
ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
|
|
for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL;
|
|
addr = next_addr)
|
|
{
|
|
next_addr = ISC_LIST_NEXT(addr, publink);
|
|
ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
|
|
resquery_t *query = NULL, *next_query = NULL;
|
|
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);
|
|
|
|
for (query = ISC_LIST_HEAD(queries); query != NULL; query = next_query)
|
|
{
|
|
next_query = ISC_LIST_NEXT(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(dns_lctx, ISC_LOG_INFO)) {
|
|
return;
|
|
}
|
|
|
|
/* Do not log a message if there were no dropped fetches. */
|
|
if (counter->dropped == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Do not log the cumulative message if the previous log is recent. */
|
|
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_lctx, 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_lctx, 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,
|
|
.count = 0,
|
|
.allowed = 0,
|
|
};
|
|
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) {
|
|
dns_fetchresponse_t *resp = NULL, *next = NULL;
|
|
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);
|
|
|
|
for (resp = ISC_LIST_HEAD(fctx->resps); resp != NULL; resp = next) {
|
|
next = ISC_LIST_NEXT(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) ||
|
|
fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig);
|
|
|
|
/*
|
|
* 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_lctx, 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_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"success resolving '%s' after disabling "
|
|
"qname minimization due to '%s'",
|
|
fctx->info,
|
|
isc_result_totext(fctx->qmin_warning));
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
isc_time_nowplusinterval(&fctx->next_timeout, &fctx->interval);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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();
|
|
|
|
/*
|
|
* 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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"Using DNS64 address %s to talk to %s\n",
|
|
sockaddrbuf2, sockaddrbuf1);
|
|
}
|
|
}
|
|
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) {
|
|
query->options |= DNS_FETCHOPT_TCP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 */
|
|
result = dns_dispatch_add(query->dispatch, fctx->loop, 0,
|
|
isc_interval_ms(&fctx->interval), &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(dns_lctx, log_level)) {
|
|
char peerbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
|
|
isc_sockaddr_format(&sockaddr, peerbuf,
|
|
ISC_SOCKADDR_FORMATSIZE);
|
|
|
|
isc_log_write(
|
|
dns_lctx, 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) {
|
|
struct tried *tried;
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
|
|
tried = ISC_LIST_NEXT(tried, link))
|
|
{
|
|
if (isc_sockaddr_equal(&tried->addr, address)) {
|
|
return tried;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
struct tried *tried;
|
|
|
|
tried = triededns(fctx, address);
|
|
if (tried != NULL) {
|
|
tried->count++;
|
|
return;
|
|
}
|
|
|
|
tried = isc_mem_get(fctx->mctx, sizeof(*tried));
|
|
|
|
tried->addr = *address;
|
|
tried->count = 1;
|
|
ISC_LIST_INITANDAPPEND(fctx->edns, tried, link);
|
|
}
|
|
|
|
static 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 isc_result_t
|
|
issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
|
|
isc_stdtime_t now, bool checknta, bool *ntap, bool *issecure) {
|
|
dns_name_t suffix;
|
|
unsigned int labels;
|
|
|
|
/*
|
|
* For DS variants we need to check fom the parent domain,
|
|
* since there may be a negative trust anchor for the name,
|
|
* while the enclosing domain where the DS record lives is
|
|
* under a secure entry point.
|
|
*/
|
|
labels = dns_name_countlabels(name);
|
|
if (dns_rdatatype_atparent(type) && labels > 1) {
|
|
dns_name_init(&suffix, NULL);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
name = &suffix;
|
|
}
|
|
|
|
return dns_view_issecuredomain(view, name, now, checknta, ntap,
|
|
issecure);
|
|
}
|
|
|
|
static isc_result_t
|
|
resquery_send(resquery_t *query) {
|
|
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 secure_domain;
|
|
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 */
|
|
#ifdef HAVE_DNSTAP
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
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) {
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
|
|
} else if (res->view->enablevalidation &&
|
|
((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
|
|
{
|
|
bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0);
|
|
bool ntacovered = false;
|
|
result = issecuredomain(res->view, fctx->name, fctx->type,
|
|
isc_time_seconds(&query->start),
|
|
checknta, &ntacovered, &secure_domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
secure_domain = false;
|
|
}
|
|
if (secure_domain ||
|
|
(ISFORWARDER(query->addrinfo) && ntacovered))
|
|
{
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We don't have to set opcode because it defaults to query.
|
|
*/
|
|
fctx->qmessage->id = query->id;
|
|
|
|
/*
|
|
* Convert the question to wire format.
|
|
*/
|
|
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 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;
|
|
(void)dns_peer_getrequestnsid(peer, &reqnsid);
|
|
(void)dns_peer_getsendcookie(peer, &sendcookie);
|
|
result = dns_peer_getednsversion(peer,
|
|
&ednsversion);
|
|
if (result == ISC_R_SUCCESS &&
|
|
ednsversion < version)
|
|
{
|
|
version = ednsversion;
|
|
}
|
|
}
|
|
if (NOCOOKIE(query->addrinfo)) {
|
|
sendcookie = false;
|
|
}
|
|
if (reqnsid) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_NSID;
|
|
ednsopts[ednsopt].length = 0;
|
|
ednsopts[ednsopt].value = NULL;
|
|
ednsopt++;
|
|
}
|
|
if (sendcookie) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_COOKIE;
|
|
ednsopts[ednsopt].length =
|
|
(uint16_t)dns_adb_getcookie(
|
|
query->addrinfo, cookie,
|
|
sizeof(cookie));
|
|
if (ednsopts[ednsopt].length != 0) {
|
|
ednsopts[ednsopt].value = cookie;
|
|
inc_stats(
|
|
fctx->res,
|
|
dns_resstatscounter_cookieout);
|
|
} else {
|
|
compute_cc(query, cookie,
|
|
CLIENT_COOKIE_SIZE);
|
|
ednsopts[ednsopt].value = cookie;
|
|
ednsopts[ednsopt].length =
|
|
CLIENT_COOKIE_SIZE;
|
|
inc_stats(
|
|
fctx->res,
|
|
dns_resstatscounter_cookienew);
|
|
}
|
|
ednsopt++;
|
|
}
|
|
|
|
/* Add TCP keepalive option if appropriate */
|
|
if ((peer != NULL) && tcp) {
|
|
(void)dns_peer_gettcpkeepalive(peer,
|
|
&tcpkeepalive);
|
|
}
|
|
if (tcpkeepalive) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_TCP_KEEPALIVE;
|
|
ednsopts[ednsopt].length = 0;
|
|
ednsopts[ednsopt].value = NULL;
|
|
ednsopt++;
|
|
}
|
|
|
|
/* Add PAD for current peer? Require TCP for now
|
|
*/
|
|
if ((peer != NULL) && tcp) {
|
|
(void)dns_peer_getpadding(peer, &padding);
|
|
}
|
|
if (padding != 0) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_PAD;
|
|
ednsopts[ednsopt].length = 0;
|
|
ednsopt++;
|
|
dns_message_setpadding(fctx->qmessage, padding);
|
|
}
|
|
|
|
query->ednsversion = version;
|
|
result = fctx_addopt(fctx->qmessage, version, udpsize,
|
|
ednsopts, ednsopt);
|
|
if (reqnsid && result == ISC_R_SUCCESS) {
|
|
query->options |= DNS_FETCHOPT_WANTNSID;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
/*
|
|
* We couldn't add the OPT, but we'll
|
|
* press on. We're not using EDNS0, so
|
|
* set the NOEDNS0 bit.
|
|
*/
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
query->ednsversion = -1;
|
|
udpsize = 0;
|
|
}
|
|
} else {
|
|
/*
|
|
* We know this server doesn't like EDNS0, so we
|
|
* won't use it. Set the NOEDNS0 bit since
|
|
* we're not using EDNS0.
|
|
*/
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
query->ednsversion = -1;
|
|
}
|
|
} else {
|
|
query->ednsversion = -1;
|
|
}
|
|
|
|
/*
|
|
* Record the UDP EDNS size chosen.
|
|
*/
|
|
query->udpsize = udpsize;
|
|
|
|
/*
|
|
* If we need EDNS0 to do this query and aren't using it, we
|
|
* lose.
|
|
*/
|
|
if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
|
|
result = DNS_R_SERVFAIL;
|
|
goto cleanup_message;
|
|
}
|
|
|
|
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, NULL);
|
|
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.
|
|
*/
|
|
dns_message_logfmtpacket(
|
|
fctx->qmessage, "sending packet to", &query->addrinfo->sockaddr,
|
|
DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_PACKETS,
|
|
&dns_master_style_comment, ISC_LOG_DEBUG(11), fctx->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;
|
|
}
|
|
|
|
result = dns_dispentry_getlocaladdress(query->dispentry, &localaddr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
la = &localaddr;
|
|
}
|
|
|
|
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_sockaddr_t *sa;
|
|
|
|
for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL;
|
|
sa = ISC_LIST_NEXT(sa, link))
|
|
{
|
|
if (isc_sockaddr_equal(sa, address)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
mark_bad(fetchctx_t *fctx) {
|
|
dns_adbfind_t *curr;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
bool all_bad = true;
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (dns_fuzzing_resolver) {
|
|
return false;
|
|
}
|
|
#endif /* ifdef ENABLE_AFL */
|
|
|
|
/*
|
|
* Mark all known bad servers, so we don't try to talk to them
|
|
* again.
|
|
*/
|
|
|
|
/*
|
|
* Mark any bad nameservers.
|
|
*/
|
|
for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL;
|
|
curr = ISC_LIST_NEXT(curr, publink))
|
|
{
|
|
for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark any bad forwarders.
|
|
*/
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark any bad alternates.
|
|
*/
|
|
for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL;
|
|
curr = ISC_LIST_NEXT(curr, publink))
|
|
{
|
|
for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (bad_server(fctx, &addrinfo->sockaddr)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
} else {
|
|
all_bad = false;
|
|
}
|
|
}
|
|
|
|
return all_bad;
|
|
}
|
|
|
|
static void
|
|
add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
|
|
isc_result_t reason, badnstype_t badtype) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char classbuf[64];
|
|
char typebuf[64];
|
|
char code[64];
|
|
isc_buffer_t b;
|
|
isc_sockaddr_t *sa;
|
|
const char *spc = "";
|
|
isc_sockaddr_t *address = &addrinfo->sockaddr;
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (dns_fuzzing_resolver) {
|
|
return;
|
|
}
|
|
#endif /* ifdef ENABLE_AFL */
|
|
|
|
if (reason == DNS_R_LAME) {
|
|
fctx->lamecount++;
|
|
} else {
|
|
switch (badtype) {
|
|
case badns_unreachable:
|
|
fctx->neterr++;
|
|
break;
|
|
case badns_response:
|
|
fctx->badresp++;
|
|
break;
|
|
case badns_validation:
|
|
break; /* counted as 'valfail' */
|
|
case badns_forwarder:
|
|
/*
|
|
* We were called to prevent the given forwarder
|
|
* from being used again for this fetch context.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bad_server(fctx, address)) {
|
|
/*
|
|
* We already know this server is bad.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
FCTXTRACE("add_bad");
|
|
|
|
sa = isc_mem_get(fctx->mctx, sizeof(*sa));
|
|
*sa = *address;
|
|
ISC_LIST_INITANDAPPEND(fctx->bad, sa, link);
|
|
|
|
if (reason == DNS_R_LAME) { /* already logged */
|
|
return;
|
|
}
|
|
|
|
if (reason == DNS_R_UNEXPECTEDRCODE &&
|
|
rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (reason == DNS_R_UNEXPECTEDRCODE) {
|
|
isc_buffer_init(&b, code, sizeof(code) - 1);
|
|
dns_rcode_totext(rmessage->rcode, &b);
|
|
code[isc_buffer_usedlength(&b)] = '\0';
|
|
spc = " ";
|
|
} else if (reason == DNS_R_UNEXPECTEDOPCODE) {
|
|
isc_buffer_init(&b, code, sizeof(code) - 1);
|
|
dns_opcode_totext((dns_opcode_t)rmessage->opcode, &b);
|
|
code[isc_buffer_usedlength(&b)] = '\0';
|
|
spc = " ";
|
|
} else {
|
|
code[0] = '\0';
|
|
}
|
|
dns_name_format(fctx->name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
|
|
dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
|
|
isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
|
|
isc_log_write(
|
|
dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code, spc,
|
|
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, *curr;
|
|
dns_adbfindlist_t sorted;
|
|
dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
|
|
|
|
/* Sort each find's addrinfo list by SRTT. */
|
|
for (curr = ISC_LIST_HEAD(*findlist); curr != NULL;
|
|
curr = ISC_LIST_NEXT(curr, publink))
|
|
{
|
|
sort_adbfind(curr, bias);
|
|
}
|
|
|
|
/* Lame N^2 bubble sort. */
|
|
ISC_LIST_INIT(sorted);
|
|
while (!ISC_LIST_EMPTY(*findlist)) {
|
|
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_adbaddrinfo_t *ai = NULL;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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,
|
|
NULL, res->view->dstport, fctx->depth + 1,
|
|
fctx->qc, fctx->gqc, &find);
|
|
|
|
isc_log_write(dns_lctx, 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_lctx, 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) {
|
|
for (ai = ISC_LIST_HEAD(find->list); ai != NULL;
|
|
ai = ISC_LIST_NEXT(ai, publink))
|
|
{
|
|
ai->flags |= flags;
|
|
if (port != 0) {
|
|
isc_sockaddr_setport(&ai->sockaddr,
|
|
port);
|
|
}
|
|
}
|
|
}
|
|
if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) {
|
|
ISC_LIST_APPEND(fctx->altfinds, find, publink);
|
|
} else {
|
|
ISC_LIST_APPEND(fctx->finds, find, publink);
|
|
}
|
|
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_lctx, 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) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
isc_result_t result;
|
|
dns_resolver_t *res;
|
|
isc_stdtime_t now;
|
|
unsigned int stdoptions = 0;
|
|
dns_forwarder_t *fwd;
|
|
dns_adbaddrinfo_t *ai;
|
|
bool all_bad;
|
|
dns_rdata_ns_t ns;
|
|
bool need_alternate = false;
|
|
bool all_spilled = 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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"too much NS indirection resolving '%s' "
|
|
"(depth=%u, maxdepth=%u)",
|
|
fctx->info, fctx->depth, res->maxdepth);
|
|
return DNS_R_SERVFAIL;
|
|
}
|
|
|
|
/*
|
|
* Forwarders.
|
|
*/
|
|
|
|
INSIST(ISC_LIST_EMPTY(fctx->forwaddrs));
|
|
INSIST(ISC_LIST_EMPTY(fctx->altaddrs));
|
|
|
|
/*
|
|
* If we have DNS_FETCHOPT_NOFORWARD set and forwarding policy
|
|
* allows us to not forward - skip forwarders and go straight
|
|
* to NSes. This is currently used to make sure that priming
|
|
* query gets root servers' IP addresses in ADDITIONAL section.
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NOFORWARD) != 0 &&
|
|
(fctx->fwdpolicy != dns_fwdpolicy_only))
|
|
{
|
|
goto normal_nses;
|
|
}
|
|
|
|
/*
|
|
* If this fctx has forwarders, use them; otherwise use any
|
|
* selective forwarders specified in the view; otherwise use the
|
|
* resolver's forwarders (if any).
|
|
*/
|
|
fwd = ISC_LIST_HEAD(fctx->forwarders);
|
|
if (fwd == NULL) {
|
|
dns_forwarders_t *forwarders = NULL;
|
|
dns_name_t *name = fctx->name;
|
|
dns_name_t suffix;
|
|
|
|
/*
|
|
* 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, NULL);
|
|
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));
|
|
|
|
for (result = dns_rdataset_first(&fctx->nameservers);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&fctx->nameservers))
|
|
{
|
|
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) {
|
|
result = ISC_R_NOMORE;
|
|
break;
|
|
}
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Do we need to use 6 to 4?
|
|
*/
|
|
if (need_alternate) {
|
|
int family;
|
|
alternate_t *a;
|
|
family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET;
|
|
for (a = ISC_LIST_HEAD(res->alternates); a != NULL;
|
|
a = ISC_LIST_NEXT(a, link))
|
|
{
|
|
if (!a->isaddress) {
|
|
findname(fctx, &a->_u._n.name, a->_u._n.port,
|
|
stdoptions, FCTX_ADDRINFO_DUALSTACK,
|
|
now, NULL, NULL, NULL);
|
|
continue;
|
|
}
|
|
if (isc_sockaddr_pf(&a->_u.addr) != family) {
|
|
continue;
|
|
}
|
|
ai = NULL;
|
|
result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr,
|
|
&ai, 0);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_adbaddrinfo_t *cur;
|
|
ai->flags |= FCTX_ADDRINFO_FORWARDER;
|
|
ai->flags |= FCTX_ADDRINFO_DUALSTACK;
|
|
cur = ISC_LIST_HEAD(fctx->altaddrs);
|
|
while (cur != NULL && cur->srtt < ai->srtt) {
|
|
cur = ISC_LIST_NEXT(cur, publink);
|
|
}
|
|
if (cur != NULL) {
|
|
ISC_LIST_INSERTBEFORE(fctx->altaddrs,
|
|
cur, ai, publink);
|
|
} else {
|
|
ISC_LIST_APPEND(fctx->altaddrs, ai,
|
|
publink);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* Mark all known bad servers.
|
|
*/
|
|
all_bad = mark_bad(fctx);
|
|
|
|
/*
|
|
* How are we doing?
|
|
*/
|
|
if (all_bad) {
|
|
/*
|
|
* We've got no addresses.
|
|
*/
|
|
if (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, return the
|
|
* configured response.
|
|
*/
|
|
if (all_spilled) {
|
|
result = res->quotaresp[dns_quotatype_server];
|
|
inc_stats(res, dns_resstatscounter_serverquota);
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* We've found some addresses. We might still be
|
|
* looking for more addresses.
|
|
*/
|
|
sort_finds(&fctx->finds, res->view->v6bias);
|
|
sort_finds(&fctx->altfinds, 0);
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
|
|
isc_netaddr_t na;
|
|
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(dns_lctx, 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, *start;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
dns_adbaddrinfo_t *faddrinfo;
|
|
|
|
/*
|
|
* Return the next untried address, if any.
|
|
*/
|
|
|
|
/*
|
|
* Find the first unmarked forwarder (if any).
|
|
*/
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (!UNMARKED(addrinfo)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, addrinfo);
|
|
if (UNMARKED(addrinfo)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
fctx->find = NULL;
|
|
fctx->forwarding = true;
|
|
|
|
/*
|
|
* QNAME minimization is disabled when
|
|
* forwarding, and has to remain disabled if
|
|
* we switch back to normal recursion; otherwise
|
|
* forwarding could leave us in an inconsistent
|
|
* state.
|
|
*/
|
|
fctx->minimized = false;
|
|
return addrinfo;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No forwarders. Move to the next find.
|
|
*/
|
|
fctx->forwarding = false;
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND);
|
|
|
|
find = fctx->find;
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->finds);
|
|
} else {
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->finds);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find the first unmarked addrinfo.
|
|
*/
|
|
addrinfo = NULL;
|
|
if (find != NULL) {
|
|
start = find;
|
|
do {
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (!UNMARKED(addrinfo)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, addrinfo);
|
|
if (UNMARKED(addrinfo)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
break;
|
|
}
|
|
}
|
|
if (addrinfo != NULL) {
|
|
break;
|
|
}
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->finds);
|
|
}
|
|
} while (find != start);
|
|
}
|
|
|
|
fctx->find = find;
|
|
if (addrinfo != NULL) {
|
|
return addrinfo;
|
|
}
|
|
|
|
/*
|
|
* No nameservers left. Try alternates.
|
|
*/
|
|
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDALT);
|
|
|
|
find = fctx->altfind;
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->altfinds);
|
|
} else {
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->altfinds);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find the first unmarked addrinfo.
|
|
*/
|
|
addrinfo = NULL;
|
|
if (find != NULL) {
|
|
start = find;
|
|
do {
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (!UNMARKED(addrinfo)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, addrinfo);
|
|
if (UNMARKED(addrinfo)) {
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
break;
|
|
}
|
|
}
|
|
if (addrinfo != NULL) {
|
|
break;
|
|
}
|
|
find = ISC_LIST_NEXT(find, publink);
|
|
if (find == NULL) {
|
|
find = ISC_LIST_HEAD(fctx->altfinds);
|
|
}
|
|
} while (find != start);
|
|
}
|
|
|
|
faddrinfo = addrinfo;
|
|
|
|
/*
|
|
* See if we have a better alternate server by address.
|
|
*/
|
|
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
{
|
|
if (!UNMARKED(addrinfo)) {
|
|
continue;
|
|
}
|
|
possibly_mark(fctx, addrinfo);
|
|
if (UNMARKED(addrinfo) &&
|
|
(faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt))
|
|
{
|
|
if (faddrinfo != NULL) {
|
|
faddrinfo->flags &= ~FCTX_ADDRINFO_MARK;
|
|
}
|
|
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (addrinfo == NULL) {
|
|
addrinfo = faddrinfo;
|
|
fctx->altfind = find;
|
|
}
|
|
|
|
return addrinfo;
|
|
}
|
|
|
|
static void
|
|
fctx_try(fetchctx_t *fctx, bool retrying) {
|
|
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_lctx, 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_lctx, 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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
|
|
"fctx %p(%s): attempting QNAME "
|
|
"minimization fetch for %s/%s but "
|
|
"fetch %p(%s) still running",
|
|
fctx, fctx->info, namebuf, typebuf,
|
|
fctx->qminfetch,
|
|
validfctx ? fctx->qminfetch->private->info
|
|
: "<invalid>");
|
|
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, 0, fctx->qc,
|
|
fctx->gqc, fctx->loop, resume_qmin, fctx, &fctx->edectx,
|
|
&fctx->qminrrset, NULL, &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_lctx, 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_lctx, 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;
|
|
|
|
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);
|
|
|
|
if (resp->node != NULL) {
|
|
dns_db_detachnode(resp->db, &resp->node);
|
|
}
|
|
if (resp->db != NULL) {
|
|
dns_db_detach(&resp->db);
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(resp->rdataset)) {
|
|
dns_rdataset_disassociate(resp->rdataset);
|
|
}
|
|
|
|
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:
|
|
if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) != 0) {
|
|
/* These results cause a hard fail in strict mode */
|
|
goto cleanup;
|
|
}
|
|
|
|
/* ...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;
|
|
}
|
|
/*
|
|
* 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_lctx, 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);
|
|
|
|
/*
|
|
* DNS_R_NXDOMAIN here means we have not loaded the root zone
|
|
* mirror yet - but DNS_R_NXDOMAIN is not a valid return value
|
|
* when doing recursion, we need to patch it.
|
|
*/
|
|
if (result == DNS_R_NXDOMAIN) {
|
|
result = DNS_R_SERVFAIL;
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
fcount_decr(fctx);
|
|
|
|
dns_name_copy(fname, fctx->domain);
|
|
|
|
result = fcount_incr(fctx, true);
|
|
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 (result != ISC_R_SUCCESS) {
|
|
/* An error occurred, tear down whole fctx */
|
|
fctx_done_unref(fctx, result);
|
|
}
|
|
fetchctx_detach(&fctx);
|
|
}
|
|
|
|
static void
|
|
fctx_destroy(fetchctx_t *fctx) {
|
|
dns_resolver_t *res = NULL;
|
|
isc_sockaddr_t *sa = NULL, *next_sa = NULL;
|
|
struct tried *tried = 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 */
|
|
for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) {
|
|
next_sa = ISC_LIST_NEXT(sa, link);
|
|
ISC_LIST_UNLINK(fctx->bad, sa, link);
|
|
isc_mem_put(fctx->mctx, sa, sizeof(*sa));
|
|
}
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL;
|
|
tried = ISC_LIST_HEAD(fctx->edns))
|
|
{
|
|
ISC_LIST_UNLINK(fctx->edns, tried, link);
|
|
isc_mem_put(fctx->mctx, tried, sizeof(*tried));
|
|
}
|
|
|
|
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());
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"shut down hung fetch while resolving %p(%s)", fctx,
|
|
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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
|
|
"log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx,
|
|
where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl);
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_create(dns_resolver_t *res, 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_lctx, 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 {
|
|
result = isc_counter_create(fctx->mctx, res->maxqueries,
|
|
&fctx->qc);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_fetch;
|
|
}
|
|
isc_log_write(dns_lctx, 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_lctx, 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->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, NULL);
|
|
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
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) == 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);
|
|
ISC_LINK_INIT(fctx, link);
|
|
fctx->magic = FCTX_MAGIC;
|
|
|
|
/*
|
|
* If qname minimization is enabled we need to trim
|
|
* the name in fctx to proper length.
|
|
*/
|
|
if ((options & DNS_FETCHOPT_QMINIMIZE) != 0) {
|
|
fctx->ip6arpaskip = (options & DNS_FETCHOPT_QMIN_SKIP_IP6A) !=
|
|
0 &&
|
|
dns_name_issubdomain(fctx->name, &ip6_arpa);
|
|
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);
|
|
}
|
|
|
|
cleanup_fetch:
|
|
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) {
|
|
dns_name_t *name;
|
|
dns_rdataset_t *rdataset;
|
|
isc_result_t result;
|
|
|
|
if (message->rcode != dns_rcode_noerror &&
|
|
message->rcode != dns_rcode_yxdomain &&
|
|
message->rcode != dns_rcode_nxdomain)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (message->counts[DNS_SECTION_ANSWER] != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (message->counts[DNS_SECTION_AUTHORITY] == 0) {
|
|
return false;
|
|
}
|
|
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
dns_namereln_t namereln;
|
|
int order;
|
|
unsigned int labels;
|
|
if (rdataset->type != dns_rdatatype_ns) {
|
|
continue;
|
|
}
|
|
namereln = dns_name_fullcompare(name, fctx->domain,
|
|
&order, &labels);
|
|
if (namereln == dns_namereln_equal &&
|
|
(message->flags & DNS_MESSAGEFLAG_AA) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
if (namereln == dns_namereln_subdomain) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
|
|
dns_name_format(fctx->name, namebuf, sizeof(namebuf));
|
|
dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf));
|
|
isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"lame server resolving '%s' (in '%s'?): %s", namebuf,
|
|
domainbuf, addrbuf);
|
|
}
|
|
|
|
static void
|
|
log_formerr(fetchctx_t *fctx, const char *format, ...) {
|
|
char nsbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char msgbuf[2048];
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
vsnprintf(msgbuf, sizeof(msgbuf), format, args);
|
|
va_end(args);
|
|
|
|
isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"DNS format error from %s resolving %s for %s: %s", nsbuf,
|
|
fctx->info, fctx->clientstr, msgbuf);
|
|
}
|
|
|
|
static isc_result_t
|
|
same_question(fetchctx_t *fctx, dns_message_t *message) {
|
|
isc_result_t result;
|
|
dns_name_t *name = 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;
|
|
}
|
|
|
|
result = dns_message_firstname(message, DNS_SECTION_QUESTION);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
dns_message_currentname(message, DNS_SECTION_QUESTION, &name);
|
|
rdataset = ISC_LIST_HEAD(name->list);
|
|
INSIST(rdataset != NULL);
|
|
INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
|
|
|
|
if (fctx->type != rdataset->type ||
|
|
fctx->res->rdclass != rdataset->rdclass ||
|
|
!dns_name_equal(fctx->name, name))
|
|
{
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char classbuf[DNS_RDATACLASS_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdataclass_format(rdataset->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
|
log_formerr(fctx, "question section mismatch: got %s/%s/%s",
|
|
namebuf, classbuf, typebuf);
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
clone_results(fetchctx_t *fctx) {
|
|
dns_fetchresponse_t *resp = NULL, *hresp = NULL;
|
|
|
|
FCTXTRACE("clone_results");
|
|
|
|
/*
|
|
* Set up any other resps to have the same data as the first.
|
|
*
|
|
* Caller must be holding the appropriate lock.
|
|
*/
|
|
|
|
fctx->cloned = true;
|
|
|
|
for (resp = ISC_LIST_HEAD(fctx->resps); resp != NULL;
|
|
resp = ISC_LIST_NEXT(resp, link))
|
|
{
|
|
/* This is the head resp; keep a pointer and move on */
|
|
if (hresp == NULL) {
|
|
hresp = ISC_LIST_HEAD(fctx->resps);
|
|
continue;
|
|
}
|
|
|
|
resp->result = hresp->result;
|
|
dns_name_copy(hresp->foundname, resp->foundname);
|
|
dns_db_attach(hresp->db, &resp->db);
|
|
dns_db_attachnode(hresp->db, hresp->node, &resp->node);
|
|
|
|
INSIST(hresp->rdataset != NULL);
|
|
INSIST(resp->rdataset != NULL);
|
|
if (dns_rdataset_isassociated(hresp->rdataset)) {
|
|
dns_rdataset_clone(hresp->rdataset, resp->rdataset);
|
|
}
|
|
|
|
INSIST(!(hresp->sigrdataset == NULL &&
|
|
resp->sigrdataset != NULL));
|
|
if (hresp->sigrdataset != NULL &&
|
|
dns_rdataset_isassociated(hresp->sigrdataset) &&
|
|
resp->sigrdataset != NULL)
|
|
{
|
|
dns_rdataset_clone(hresp->sigrdataset,
|
|
resp->sigrdataset);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0)
|
|
#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0)
|
|
#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0)
|
|
#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0)
|
|
#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0)
|
|
#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0)
|
|
#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0)
|
|
|
|
/*
|
|
* 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));
|
|
for (dns_validator_t *validator = ISC_LIST_HEAD(fctx->validators);
|
|
validator != NULL; validator = ISC_LIST_NEXT(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;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&rdataset))
|
|
{
|
|
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;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&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;
|
|
isc_result_t result;
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
dns_rdataset_clone(nsecset, &rdataset);
|
|
|
|
for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&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;
|
|
}
|
|
|
|
/*
|
|
* The validator has finished.
|
|
*/
|
|
static void
|
|
validated(void *arg) {
|
|
dns_validator_t *val = (dns_validator_t *)arg;
|
|
dns_adbaddrinfo_t *addrinfo = NULL;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_dbnode_t *nsnode = NULL;
|
|
dns_fetchresponse_t *hresp = NULL;
|
|
dns_name_t *name = NULL;
|
|
dns_rdataset_t *ardataset = NULL;
|
|
dns_rdataset_t *asigrdataset = NULL;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
dns_resolver_t *res = NULL;
|
|
dns_valarg_t *valarg = NULL;
|
|
fetchctx_t *fctx = NULL;
|
|
bool chaining;
|
|
bool negative;
|
|
bool sentresponse;
|
|
isc_result_t eresult = ISC_R_SUCCESS;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
isc_stdtime_t now;
|
|
uint32_t ttl;
|
|
unsigned int options;
|
|
dns_fixedname_t fwild;
|
|
dns_name_t *wild = NULL;
|
|
dns_message_t *message = NULL;
|
|
bool done = false;
|
|
|
|
valarg = val->arg;
|
|
|
|
REQUIRE(VALID_FCTX(valarg->fctx));
|
|
REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators));
|
|
|
|
fctx = valarg->fctx;
|
|
valarg->fctx = NULL;
|
|
|
|
REQUIRE(fctx->tid == isc_tid());
|
|
|
|
FCTXTRACE("received validation completion event");
|
|
|
|
res = fctx->res;
|
|
addrinfo = valarg->addrinfo;
|
|
|
|
message = val->message;
|
|
fctx->vresult = val->result;
|
|
|
|
LOCK(&fctx->lock);
|
|
ISC_LIST_UNLINK(fctx->validators, val, link);
|
|
fctx->validator = NULL;
|
|
UNLOCK(&fctx->lock);
|
|
|
|
/*
|
|
* Destroy the validator early so that we can
|
|
* destroy the fctx if necessary. Save the wildcard name.
|
|
*/
|
|
if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
|
|
wild = dns_fixedname_initname(&fwild);
|
|
dns_name_copy(dns_fixedname_name(&val->wild), wild);
|
|
}
|
|
|
|
isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
|
|
|
|
negative = (val->rdataset == NULL);
|
|
|
|
LOCK(&fctx->lock);
|
|
sentresponse = ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0);
|
|
|
|
/*
|
|
* If shutting down, ignore the results. Check to see if we're
|
|
* done waiting for validator completions and ADB pending
|
|
* events; if so, destroy the fctx.
|
|
*/
|
|
if (SHUTTINGDOWN(fctx) && !sentresponse) {
|
|
UNLOCK(&fctx->lock);
|
|
goto cleanup_fetchctx;
|
|
}
|
|
|
|
now = isc_stdtime_now();
|
|
|
|
/*
|
|
* If chaining, we need to make sure that the right result code
|
|
* is returned, and that the rdatasets are bound.
|
|
*/
|
|
if (val->result == ISC_R_SUCCESS && !negative &&
|
|
val->rdataset != NULL && CHAINING(val->rdataset))
|
|
{
|
|
if (val->rdataset->type == dns_rdatatype_cname) {
|
|
eresult = DNS_R_CNAME;
|
|
} else {
|
|
INSIST(val->rdataset->type == dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
chaining = true;
|
|
} else {
|
|
chaining = false;
|
|
}
|
|
|
|
/*
|
|
* Either we're not shutting down, or we are shutting down but
|
|
* want to cache the result anyway (if this was a validation
|
|
* started by a query with cd set)
|
|
*/
|
|
|
|
hresp = ISC_LIST_HEAD(fctx->resps);
|
|
if (hresp != NULL) {
|
|
if (!negative && !chaining &&
|
|
(fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig))
|
|
{
|
|
/*
|
|
* Don't bind rdatasets; the caller
|
|
* will iterate the node.
|
|
*/
|
|
} else {
|
|
ardataset = hresp->rdataset;
|
|
asigrdataset = hresp->sigrdataset;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
result = ISC_R_NOTFOUND;
|
|
if (val->rdataset != NULL) {
|
|
result = dns_db_findnode(fctx->cache, val->name,
|
|
false, &node);
|
|
}
|
|
if (result == ISC_R_SUCCESS) {
|
|
(void)dns_db_deleterdataset(fctx->cache, node,
|
|
NULL, val->type, 0);
|
|
}
|
|
if (result == ISC_R_SUCCESS && val->sigrdataset != NULL)
|
|
{
|
|
(void)dns_db_deleterdataset(
|
|
fctx->cache, node, NULL,
|
|
dns_rdatatype_rrsig, val->type);
|
|
}
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
}
|
|
if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) {
|
|
/*
|
|
* Cache the data as pending for later
|
|
* validation.
|
|
*/
|
|
result = ISC_R_NOTFOUND;
|
|
if (val->rdataset != NULL) {
|
|
result = dns_db_findnode(fctx->cache, val->name,
|
|
true, &node);
|
|
}
|
|
if (result == ISC_R_SUCCESS) {
|
|
(void)dns_db_addrdataset(
|
|
fctx->cache, node, NULL, now,
|
|
val->rdataset, 0, NULL);
|
|
}
|
|
if (result == ISC_R_SUCCESS && val->sigrdataset != NULL)
|
|
{
|
|
(void)dns_db_addrdataset(
|
|
fctx->cache, node, NULL, now,
|
|
val->sigrdataset, 0, NULL);
|
|
}
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
}
|
|
result = fctx->vresult;
|
|
add_bad(fctx, message, addrinfo, result, badns_validation);
|
|
|
|
UNLOCK(&fctx->lock);
|
|
|
|
INSIST(fctx->validator == NULL);
|
|
|
|
fctx->validator = ISC_LIST_HEAD(fctx->validators);
|
|
if (fctx->validator != NULL) {
|
|
dns_validator_send(fctx->validator);
|
|
goto cleanup_fetchctx;
|
|
} else if (sentresponse) {
|
|
done = true;
|
|
goto cleanup_fetchctx;
|
|
} else if (result == DNS_R_BROKENCHAIN) {
|
|
done = true;
|
|
goto cleanup_fetchctx;
|
|
} else {
|
|
fctx_try(fctx, true);
|
|
goto cleanup_fetchctx;
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
if (negative) {
|
|
dns_rdatatype_t covers;
|
|
FCTXTRACE("nonexistence validation OK");
|
|
|
|
inc_stats(res, dns_resstatscounter_valnegsuccess);
|
|
|
|
/*
|
|
* Cache DS NXDOMAIN separately to other types.
|
|
*/
|
|
if (message->rcode == dns_rcode_nxdomain &&
|
|
fctx->type != dns_rdatatype_ds)
|
|
{
|
|
covers = dns_rdatatype_any;
|
|
} else {
|
|
covers = fctx->type;
|
|
}
|
|
|
|
/*
|
|
* Don't report qname minimisation NXDOMAIN errors
|
|
* when the result is NXDOMAIN except we have already
|
|
* confirmed a higher error.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
result = dns_db_findnode(fctx->cache, val->name, true, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* fctx->lock unlocked in noanswer_response */
|
|
goto noanswer_response;
|
|
}
|
|
|
|
/*
|
|
* If we are asking for a SOA record set the cache time
|
|
* to zero to facilitate locating the containing zone of
|
|
* a arbitrary zone.
|
|
*/
|
|
ttl = res->view->maxncachettl;
|
|
if (fctx->type == dns_rdatatype_soa &&
|
|
covers == dns_rdatatype_any && res->zero_no_soa_ttl)
|
|
{
|
|
ttl = 0;
|
|
}
|
|
|
|
result = ncache_adderesult(message, fctx->cache, node, covers,
|
|
now, fctx->res->view->minncachettl,
|
|
ttl, val->optout, val->secure,
|
|
ardataset, &eresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto noanswer_response;
|
|
}
|
|
goto answer_response;
|
|
} else {
|
|
inc_stats(res, dns_resstatscounter_valsuccess);
|
|
}
|
|
|
|
FCTXTRACE("validation OK");
|
|
|
|
if (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 (val->rdataset->trust == dns_trust_answer &&
|
|
val->rdataset->type != dns_rdatatype_rrsig)
|
|
{
|
|
isc_result_t tresult;
|
|
dns_name_t *noqname = NULL;
|
|
tresult = findnoqname(fctx, message, val->name,
|
|
val->rdataset->type, &noqname);
|
|
if (tresult == ISC_R_SUCCESS && noqname != NULL) {
|
|
tresult = dns_rdataset_addnoqname(val->rdataset,
|
|
noqname);
|
|
RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The data was already cached as pending data.
|
|
* Re-cache it as secure and bind the cached
|
|
* rdatasets to the first event on the fetch
|
|
* event list.
|
|
*/
|
|
result = dns_db_findnode(fctx->cache, val->name, true, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto noanswer_response;
|
|
}
|
|
|
|
options = 0;
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
|
|
options = DNS_DBADD_PREFETCH;
|
|
}
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now, val->rdataset,
|
|
options, ardataset);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
|
|
goto noanswer_response;
|
|
}
|
|
if (ardataset != NULL && NEGATIVE(ardataset)) {
|
|
if (NXDOMAIN(ardataset)) {
|
|
eresult = DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
eresult = DNS_R_NCACHENXRRSET;
|
|
}
|
|
} else if (val->sigrdataset != NULL) {
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
|
|
val->sigrdataset, options,
|
|
asigrdataset);
|
|
if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
|
|
goto noanswer_response;
|
|
}
|
|
}
|
|
|
|
if (sentresponse) {
|
|
/*
|
|
* If we only deferred the destroy because we wanted to
|
|
* cache the data, destroy now.
|
|
*/
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
if (SHUTTINGDOWN(fctx)) {
|
|
maybe_cancel_validators(fctx);
|
|
}
|
|
UNLOCK(&fctx->lock);
|
|
goto cleanup_fetchctx;
|
|
}
|
|
|
|
if (!ISC_LIST_EMPTY(fctx->validators)) {
|
|
INSIST(!negative);
|
|
INSIST(fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig);
|
|
/*
|
|
* Don't send a response yet - we have
|
|
* more rdatasets that still need to
|
|
* be validated.
|
|
*/
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
UNLOCK(&fctx->lock);
|
|
dns_validator_send(ISC_LIST_HEAD(fctx->validators));
|
|
goto cleanup_fetchctx;
|
|
}
|
|
|
|
answer_response:
|
|
|
|
/*
|
|
* Cache any SOA/NS/NSEC records that happened to be validated.
|
|
*/
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if ((rdataset->type != dns_rdatatype_ns &&
|
|
rdataset->type != dns_rdatatype_soa &&
|
|
rdataset->type != dns_rdatatype_nsec) ||
|
|
rdataset->trust != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
for (sigrdataset = ISC_LIST_HEAD(name->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != rdataset->type)
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (sigrdataset == NULL ||
|
|
sigrdataset->trust != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* 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 = dns_db_findnode(fctx->cache, name, true,
|
|
&nsnode);
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
result = dns_db_addrdataset(fctx->cache, nsnode, NULL,
|
|
now, rdataset, 0, NULL);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_db_addrdataset(
|
|
fctx->cache, nsnode, NULL, now,
|
|
sigrdataset, 0, NULL);
|
|
}
|
|
dns_db_detachnode(fctx->cache, &nsnode);
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
}
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
|
|
}
|
|
|
|
/*
|
|
* Add the wild card entry.
|
|
*/
|
|
if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL &&
|
|
val->rdataset != NULL && dns_rdataset_isassociated(val->rdataset) &&
|
|
val->rdataset->trust == dns_trust_secure &&
|
|
val->sigrdataset != NULL &&
|
|
dns_rdataset_isassociated(val->sigrdataset) &&
|
|
val->sigrdataset->trust == dns_trust_secure && wild != NULL)
|
|
{
|
|
dns_dbnode_t *wnode = NULL;
|
|
|
|
result = dns_db_findnode(fctx->cache, wild, true, &wnode);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_db_addrdataset(fctx->cache, wnode, NULL,
|
|
now, val->rdataset, 0,
|
|
NULL);
|
|
}
|
|
if (result == ISC_R_SUCCESS) {
|
|
(void)dns_db_addrdataset(fctx->cache, wnode, NULL, now,
|
|
val->sigrdataset, 0, NULL);
|
|
}
|
|
if (wnode != NULL) {
|
|
dns_db_detachnode(fctx->cache, &wnode);
|
|
}
|
|
}
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
/*
|
|
* Respond with an answer, positive or negative,
|
|
* as opposed to an error. 'node' must be non-NULL.
|
|
*/
|
|
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
|
|
if (hresp != NULL) {
|
|
/*
|
|
* Negative results must be indicated in val->result.
|
|
*/
|
|
INSIST(hresp->rdataset != NULL);
|
|
if (dns_rdataset_isassociated(hresp->rdataset)) {
|
|
if (NEGATIVE(hresp->rdataset)) {
|
|
INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
|
|
eresult == DNS_R_NCACHENXRRSET);
|
|
} else if (eresult == ISC_R_SUCCESS &&
|
|
hresp->rdataset->type != fctx->type)
|
|
{
|
|
switch (hresp->rdataset->type) {
|
|
case dns_rdatatype_cname:
|
|
eresult = DNS_R_CNAME;
|
|
break;
|
|
case dns_rdatatype_dname:
|
|
eresult = DNS_R_DNAME;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
hresp->result = eresult;
|
|
dns_name_copy(val->name, hresp->foundname);
|
|
dns_db_attach(fctx->cache, &hresp->db);
|
|
dns_db_transfernode(fctx->cache, &node, &hresp->node);
|
|
clone_results(fctx);
|
|
}
|
|
|
|
noanswer_response:
|
|
if (node != NULL) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
|
|
UNLOCK(&fctx->lock);
|
|
done = true;
|
|
|
|
cleanup_fetchctx:
|
|
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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx,
|
|
fctx->info, msgbuf);
|
|
}
|
|
|
|
static isc_result_t
|
|
findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
|
|
dns_rdatatype_t type, dns_name_t **noqnamep) {
|
|
dns_rdataset_t *nrdataset, *next, *sigrdataset;
|
|
dns_rdata_rrsig_t rrsig;
|
|
isc_result_t result;
|
|
unsigned int labels;
|
|
dns_section_t section;
|
|
dns_name_t *zonename;
|
|
dns_fixedname_t fzonename;
|
|
dns_name_t *closest;
|
|
dns_fixedname_t fclosest;
|
|
dns_name_t *nearest;
|
|
dns_fixedname_t fnearest;
|
|
dns_rdatatype_t found = dns_rdatatype_none;
|
|
dns_name_t *noqname = NULL;
|
|
|
|
FCTXTRACE("findnoqname");
|
|
|
|
REQUIRE(noqnamep != NULL && *noqnamep == NULL);
|
|
|
|
/*
|
|
* Find the SIG for this rdataset, if we have it.
|
|
*/
|
|
for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (sigrdataset->type == dns_rdatatype_rrsig &&
|
|
sigrdataset->covers == type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sigrdataset == NULL) {
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
labels = dns_name_countlabels(name);
|
|
|
|
for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(sigrdataset))
|
|
{
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_current(sigrdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
/* Wildcard has rrsig.labels < labels - 1. */
|
|
if (rrsig.labels + 1U >= labels) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (result == ISC_R_NOMORE) {
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
zonename = dns_fixedname_initname(&fzonename);
|
|
closest = dns_fixedname_initname(&fclosest);
|
|
nearest = dns_fixedname_initname(&fnearest);
|
|
|
|
#define NXND(x) ((x) == ISC_R_SUCCESS)
|
|
|
|
section = DNS_SECTION_AUTHORITY;
|
|
for (result = dns_message_firstname(message, section);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(message, section))
|
|
{
|
|
dns_name_t *nsec = NULL;
|
|
dns_message_currentname(message, section, &nsec);
|
|
for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL;
|
|
nrdataset = next)
|
|
{
|
|
bool data = false, exists = false;
|
|
bool optout = false, unknown = false;
|
|
bool setclosest = false;
|
|
bool setnearest = false;
|
|
|
|
next = ISC_LIST_NEXT(nrdataset, link);
|
|
if (nrdataset->type != dns_rdatatype_nsec &&
|
|
nrdataset->type != dns_rdatatype_nsec3)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (nrdataset->type == dns_rdatatype_nsec &&
|
|
NXND(dns_nsec_noexistnodata(
|
|
type, name, nsec, nrdataset, &exists, &data,
|
|
NULL, fctx_log, fctx)))
|
|
{
|
|
if (!exists) {
|
|
noqname = nsec;
|
|
found = dns_rdatatype_nsec;
|
|
}
|
|
}
|
|
|
|
if (nrdataset->type == dns_rdatatype_nsec3 &&
|
|
NXND(dns_nsec3_noexistnodata(
|
|
type, name, nsec, nrdataset, zonename,
|
|
&exists, &data, &optout, &unknown,
|
|
&setclosest, &setnearest, closest, nearest,
|
|
fctx_log, fctx)))
|
|
{
|
|
if (!exists && setnearest) {
|
|
noqname = nsec;
|
|
found = dns_rdatatype_nsec3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (noqname != NULL) {
|
|
for (sigrdataset = ISC_LIST_HEAD(noqname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (sigrdataset->type == dns_rdatatype_rrsig &&
|
|
sigrdataset->covers == found)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (sigrdataset != NULL) {
|
|
*noqnamep = noqname;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
|
|
dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
|
|
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
|
|
dns_rdataset_t *addedrdataset = NULL;
|
|
dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL;
|
|
dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL;
|
|
dns_dbnode_t *node = NULL, **anodep = NULL;
|
|
dns_db_t **adbp = NULL;
|
|
dns_resolver_t *res = fctx->res;
|
|
bool need_validation = false;
|
|
bool secure_domain = false;
|
|
bool have_answer = false;
|
|
isc_result_t result, eresult = ISC_R_SUCCESS;
|
|
dns_fetchresponse_t *resp = NULL;
|
|
unsigned int options;
|
|
bool fail;
|
|
unsigned int valoptions = 0;
|
|
bool checknta = true;
|
|
|
|
FCTXTRACE("cache_name");
|
|
|
|
/*
|
|
* The appropriate bucket lock must be held.
|
|
*/
|
|
|
|
/*
|
|
* Is DNSSEC validation required for this name?
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NONTA;
|
|
checknta = false;
|
|
}
|
|
|
|
if (res->view->enablevalidation) {
|
|
result = issecuredomain(res->view, name, fctx->type, now,
|
|
checknta, NULL, &secure_domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NOCDFLAG;
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
|
|
need_validation = false;
|
|
} else {
|
|
need_validation = secure_domain;
|
|
}
|
|
|
|
if (name->attributes.answer && !need_validation) {
|
|
have_answer = true;
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
|
|
if (resp != NULL) {
|
|
adbp = &resp->db;
|
|
dns_name_copy(name, resp->foundname);
|
|
anodep = &resp->node;
|
|
|
|
/*
|
|
* If this is an ANY, SIG or RRSIG query, we're
|
|
* not going to return any rdatasets, unless we
|
|
* encountered a CNAME or DNAME as "the answer".
|
|
* In this case, we're going to return
|
|
* DNS_R_CNAME or DNS_R_DNAME and we must set up
|
|
* the rdatasets.
|
|
*/
|
|
if ((fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_rrsig &&
|
|
fctx->type != dns_rdatatype_sig) ||
|
|
name->attributes.chaining)
|
|
{
|
|
ardataset = resp->rdataset;
|
|
asigrdataset = resp->sigrdataset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
fail = ((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0);
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (!CACHE(rdataset)) {
|
|
continue;
|
|
}
|
|
if (CHECKNAMES(rdataset)) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
char classbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
sizeof(typebuf));
|
|
dns_rdataclass_format(rdataset->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"check-names %s %s/%s/%s",
|
|
fail ? "failure" : "warning", namebuf,
|
|
typebuf, classbuf);
|
|
if (fail) {
|
|
if (ANSWER(rdataset)) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
return DNS_R_BADNAME;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enforce the configure maximum cache TTL.
|
|
*/
|
|
if (rdataset->ttl > res->view->maxcachettl) {
|
|
rdataset->ttl = res->view->maxcachettl;
|
|
}
|
|
|
|
/*
|
|
* Enforce configured minimum cache TTL.
|
|
*/
|
|
if (rdataset->ttl < res->view->mincachettl) {
|
|
rdataset->ttl = res->view->mincachettl;
|
|
}
|
|
|
|
/*
|
|
* Mark the rdataset as being prefetch eligible.
|
|
*/
|
|
if (rdataset->ttl >= fctx->res->view->prefetch_eligible) {
|
|
rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
|
|
}
|
|
|
|
/*
|
|
* Find the SIG for this rdataset, if we have it.
|
|
*/
|
|
for (sigrdataset = ISC_LIST_HEAD(name->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (sigrdataset->type == dns_rdatatype_rrsig &&
|
|
sigrdataset->covers == rdataset->type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this RRset is in a secure domain, is in bailiwick,
|
|
* and is not glue, attempt DNSSEC validation. (We do
|
|
* not attempt to validate glue or out-of-bailiwick
|
|
* data--even though there might be some performance
|
|
* benefit to doing so--because it makes it simpler and
|
|
* safer to ensure that records from a secure domain are
|
|
* only cached if validated within the context of a
|
|
* query to the domain that owns them.)
|
|
*/
|
|
if (secure_domain && rdataset->trust != dns_trust_glue &&
|
|
!EXTERNAL(rdataset))
|
|
{
|
|
dns_trust_t trust;
|
|
|
|
/*
|
|
* RRSIGs are validated as part of validating
|
|
* the type they cover.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_rrsig) {
|
|
continue;
|
|
}
|
|
|
|
if (sigrdataset == NULL && need_validation &&
|
|
!ANSWER(rdataset))
|
|
{
|
|
/*
|
|
* Ignore unrelated non-answer
|
|
* rdatasets that are missing
|
|
* signatures.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Normalize the rdataset and sigrdataset TTLs.
|
|
*/
|
|
if (sigrdataset != NULL) {
|
|
rdataset->ttl = ISC_MIN(rdataset->ttl,
|
|
sigrdataset->ttl);
|
|
sigrdataset->ttl = rdataset->ttl;
|
|
}
|
|
|
|
/*
|
|
* Mark the rdataset as being prefetch eligible.
|
|
*/
|
|
if (rdataset->ttl >= fctx->res->view->prefetch_eligible)
|
|
{
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_PREFETCH;
|
|
}
|
|
|
|
/*
|
|
* Cache this rdataset/sigrdataset pair as
|
|
* pending data. Track whether it was
|
|
* additional or not. If this was a priming
|
|
* query, additional should be cached as glue.
|
|
*/
|
|
if (rdataset->trust == dns_trust_additional) {
|
|
trust = dns_trust_pending_additional;
|
|
} else {
|
|
trust = dns_trust_pending_answer;
|
|
}
|
|
|
|
rdataset->trust = trust;
|
|
if (sigrdataset != NULL) {
|
|
sigrdataset->trust = trust;
|
|
}
|
|
if (!need_validation || !ANSWER(rdataset)) {
|
|
options = 0;
|
|
if (ANSWER(rdataset) &&
|
|
rdataset->type != dns_rdatatype_rrsig)
|
|
{
|
|
isc_result_t tresult;
|
|
dns_name_t *noqname = NULL;
|
|
tresult = findnoqname(
|
|
fctx, message, name,
|
|
rdataset->type, &noqname);
|
|
if (tresult == ISC_R_SUCCESS &&
|
|
noqname != NULL)
|
|
{
|
|
(void)dns_rdataset_addnoqname(
|
|
rdataset, noqname);
|
|
}
|
|
}
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) !=
|
|
0)
|
|
{
|
|
options = DNS_DBADD_PREFETCH;
|
|
}
|
|
if ((fctx->options & DNS_FETCHOPT_NOCACHED) !=
|
|
0)
|
|
{
|
|
options |= DNS_DBADD_FORCE;
|
|
}
|
|
addedrdataset = ardataset;
|
|
result = dns_db_addrdataset(
|
|
fctx->cache, node, NULL, now, rdataset,
|
|
options, addedrdataset);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
if (!need_validation &&
|
|
ardataset != NULL &&
|
|
NEGATIVE(ardataset))
|
|
{
|
|
/*
|
|
* The answer in the
|
|
* cache is better than
|
|
* the answer we found.
|
|
* If it's a negative
|
|
* cache entry, we
|
|
* must set eresult
|
|
* appropriately.
|
|
*/
|
|
if (NXDOMAIN(ardataset)) {
|
|
eresult =
|
|
DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
eresult =
|
|
DNS_R_NCACHENXRRSET;
|
|
}
|
|
continue;
|
|
} else if (!need_validation &&
|
|
ardataset != NULL &&
|
|
sigrdataset != 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.
|
|
*/
|
|
continue;
|
|
}
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
if (sigrdataset != NULL) {
|
|
addedrdataset = asigrdataset;
|
|
result = dns_db_addrdataset(
|
|
fctx->cache, node, NULL, now,
|
|
sigrdataset, options,
|
|
addedrdataset);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
} else if (!ANSWER(rdataset)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ANSWER(rdataset) && need_validation) {
|
|
if (fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_rrsig &&
|
|
fctx->type != dns_rdatatype_sig)
|
|
{
|
|
/*
|
|
* This is The Answer. We will
|
|
* validate it, but first we
|
|
* cache the rest of the
|
|
* response - it may contain
|
|
* useful keys.
|
|
*/
|
|
INSIST(valrdataset == NULL &&
|
|
valsigrdataset == NULL);
|
|
valrdataset = rdataset;
|
|
valsigrdataset = sigrdataset;
|
|
} else {
|
|
/*
|
|
* This is one of (potentially)
|
|
* multiple answers to an ANY
|
|
* or SIG query. To keep things
|
|
* simple, we just start the
|
|
* validator right away rather
|
|
* than caching first and
|
|
* having to remember which
|
|
* rdatasets needed validation.
|
|
*/
|
|
result = valcreate(
|
|
fctx, message, addrinfo, name,
|
|
rdataset->type, rdataset,
|
|
sigrdataset, valoptions);
|
|
}
|
|
} else if (CHAINING(rdataset)) {
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
eresult = DNS_R_CNAME;
|
|
} else {
|
|
INSIST(rdataset->type ==
|
|
dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
}
|
|
} else if (!EXTERNAL(rdataset)) {
|
|
/*
|
|
* It's OK to cache this rdataset now.
|
|
*/
|
|
if (ANSWER(rdataset)) {
|
|
addedrdataset = ardataset;
|
|
} else if (ANSWERSIG(rdataset)) {
|
|
addedrdataset = asigrdataset;
|
|
} else {
|
|
addedrdataset = NULL;
|
|
}
|
|
if (CHAINING(rdataset)) {
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
eresult = DNS_R_CNAME;
|
|
} else {
|
|
INSIST(rdataset->type ==
|
|
dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
}
|
|
if (rdataset->trust == dns_trust_glue &&
|
|
(rdataset->type == dns_rdatatype_ns ||
|
|
(rdataset->type == dns_rdatatype_rrsig &&
|
|
rdataset->covers == dns_rdatatype_ns)))
|
|
{
|
|
/*
|
|
* If the trust level is
|
|
* 'dns_trust_glue' then we are adding
|
|
* data from a referral we got while
|
|
* executing the search algorithm. New
|
|
* referral data always takes precedence
|
|
* over the existing cache contents.
|
|
*/
|
|
options = DNS_DBADD_FORCE;
|
|
} else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0)
|
|
{
|
|
options = DNS_DBADD_PREFETCH;
|
|
} else {
|
|
options = 0;
|
|
}
|
|
|
|
if (ANSWER(rdataset) &&
|
|
rdataset->type != dns_rdatatype_rrsig)
|
|
{
|
|
isc_result_t tresult;
|
|
dns_name_t *noqname = NULL;
|
|
tresult = findnoqname(fctx, message, name,
|
|
rdataset->type, &noqname);
|
|
if (tresult == ISC_R_SUCCESS && noqname != NULL)
|
|
{
|
|
(void)dns_rdataset_addnoqname(rdataset,
|
|
noqname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now we can add the rdataset.
|
|
*/
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL,
|
|
now, rdataset, options,
|
|
addedrdataset);
|
|
|
|
if (result == DNS_R_UNCHANGED) {
|
|
if (ANSWER(rdataset) && ardataset != NULL &&
|
|
NEGATIVE(ardataset))
|
|
{
|
|
/*
|
|
* The answer in the cache is
|
|
* better than the answer we
|
|
* found, and is a negative
|
|
* cache entry, so we must set
|
|
* eresult appropriately.
|
|
*/
|
|
if (NXDOMAIN(ardataset)) {
|
|
eresult = DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
eresult = DNS_R_NCACHENXRRSET;
|
|
}
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valrdataset != NULL) {
|
|
dns_rdatatype_t vtype = fctx->type;
|
|
if (CHAINING(valrdataset)) {
|
|
if (valrdataset->type == dns_rdatatype_cname) {
|
|
vtype = dns_rdatatype_cname;
|
|
} else {
|
|
vtype = dns_rdatatype_dname;
|
|
}
|
|
}
|
|
|
|
result = valcreate(fctx, message, addrinfo, name, vtype,
|
|
valrdataset, valsigrdataset, valoptions);
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS && have_answer) {
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
if (resp != NULL) {
|
|
/*
|
|
* Negative results must be indicated in
|
|
* resp->result.
|
|
*/
|
|
if (dns_rdataset_isassociated(resp->rdataset)) {
|
|
if (NEGATIVE(resp->rdataset)) {
|
|
INSIST(eresult ==
|
|
DNS_R_NCACHENXDOMAIN ||
|
|
eresult == DNS_R_NCACHENXRRSET);
|
|
} else if (eresult == ISC_R_SUCCESS &&
|
|
resp->rdataset->type != fctx->type)
|
|
{
|
|
switch (resp->rdataset->type) {
|
|
case dns_rdatatype_cname:
|
|
eresult = DNS_R_CNAME;
|
|
break;
|
|
case dns_rdatatype_dname:
|
|
eresult = DNS_R_DNAME;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
resp->result = eresult;
|
|
if (adbp != NULL && *adbp != NULL) {
|
|
if (anodep != NULL && *anodep != NULL) {
|
|
dns_db_detachnode(*adbp, anodep);
|
|
}
|
|
dns_db_detach(adbp);
|
|
}
|
|
dns_db_attach(fctx->cache, adbp);
|
|
dns_db_transfernode(fctx->cache, &node, anodep);
|
|
clone_results(fctx);
|
|
}
|
|
}
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
cache_message(fetchctx_t *fctx, dns_message_t *message,
|
|
dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) {
|
|
isc_result_t result;
|
|
dns_section_t section;
|
|
dns_name_t *name;
|
|
|
|
FCTXTRACE("cache_message");
|
|
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL;
|
|
section++)
|
|
{
|
|
result = dns_message_firstname(message, section);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(message, section, &name);
|
|
if (name->attributes.cache) {
|
|
result = cache_name(fctx, name, message,
|
|
addrinfo, now);
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
result = dns_message_nextname(message, section);
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
break;
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
UNLOCK(&fctx->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Do what dns_ncache_addoptout() does, and then compute an appropriate
|
|
* eresult.
|
|
*/
|
|
static isc_result_t
|
|
ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
|
|
dns_ttl_t maxttl, bool optout, bool secure,
|
|
dns_rdataset_t *ardataset, isc_result_t *eresultp) {
|
|
isc_result_t result;
|
|
dns_rdataset_t rdataset;
|
|
|
|
if (ardataset == NULL) {
|
|
dns_rdataset_init(&rdataset);
|
|
ardataset = &rdataset;
|
|
}
|
|
if (secure) {
|
|
result = dns_ncache_addoptout(message, cache, node, covers, now,
|
|
minttl, maxttl, optout,
|
|
ardataset);
|
|
} else {
|
|
result = dns_ncache_add(message, cache, node, covers, now,
|
|
minttl, maxttl, ardataset);
|
|
}
|
|
if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
|
|
/*
|
|
* If the cache now contains a negative entry and we
|
|
* care about whether it is DNS_R_NCACHENXDOMAIN or
|
|
* DNS_R_NCACHENXRRSET then extract it.
|
|
*/
|
|
if (NEGATIVE(ardataset)) {
|
|
/*
|
|
* The cache data is a negative cache entry.
|
|
*/
|
|
if (NXDOMAIN(ardataset)) {
|
|
*eresultp = DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
*eresultp = DNS_R_NCACHENXRRSET;
|
|
}
|
|
} else {
|
|
/*
|
|
* The attempt to add a negative cache entry
|
|
* was rejected. Set *eresultp to reflect
|
|
* the type of the dataset being returned.
|
|
*/
|
|
switch (ardataset->type) {
|
|
case dns_rdatatype_cname:
|
|
*eresultp = DNS_R_CNAME;
|
|
break;
|
|
case dns_rdatatype_dname:
|
|
*eresultp = DNS_R_DNAME;
|
|
break;
|
|
default:
|
|
*eresultp = ISC_R_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) {
|
|
dns_rdataset_disassociate(ardataset);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
ncache_message(fetchctx_t *fctx, dns_message_t *message,
|
|
dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers,
|
|
isc_stdtime_t now) {
|
|
isc_result_t result, eresult = ISC_R_SUCCESS;
|
|
dns_name_t *name = fctx->name;
|
|
dns_resolver_t *res = fctx->res;
|
|
dns_db_t **adbp = NULL;
|
|
dns_dbnode_t *node = NULL, **anodep = NULL;
|
|
dns_rdataset_t *ardataset = NULL;
|
|
bool need_validation = false, secure_domain = false;
|
|
dns_fetchresponse_t *resp = NULL;
|
|
uint32_t ttl;
|
|
unsigned int valoptions = 0;
|
|
bool checknta = true;
|
|
|
|
FCTXTRACE("ncache_message");
|
|
|
|
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE);
|
|
|
|
POST(need_validation);
|
|
|
|
/*
|
|
* XXXMPA remove when we follow cnames and adjust the setting
|
|
* of FCTX_ATTR_WANTNCACHE in rctx_answer_none().
|
|
*/
|
|
INSIST(message->counts[DNS_SECTION_ANSWER] == 0);
|
|
|
|
/*
|
|
* Is DNSSEC validation required for this name?
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NONTA;
|
|
checknta = false;
|
|
}
|
|
|
|
if (fctx->res->view->enablevalidation) {
|
|
result = issecuredomain(res->view, name, fctx->type, now,
|
|
checknta, NULL, &secure_domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NOCDFLAG;
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
|
|
need_validation = false;
|
|
} else {
|
|
need_validation = secure_domain;
|
|
}
|
|
|
|
if (secure_domain) {
|
|
/*
|
|
* Mark all rdatasets as pending.
|
|
*/
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
dns_rdataset_t *trdataset = NULL;
|
|
dns_name_t *tname = NULL;
|
|
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY,
|
|
&tname);
|
|
for (trdataset = ISC_LIST_HEAD(tname->list);
|
|
trdataset != NULL;
|
|
trdataset = ISC_LIST_NEXT(trdataset, link))
|
|
{
|
|
trdataset->trust = dns_trust_pending_answer;
|
|
}
|
|
result = dns_message_nextname(message,
|
|
DNS_SECTION_AUTHORITY);
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (need_validation) {
|
|
/*
|
|
* Do negative response validation.
|
|
*/
|
|
result = valcreate(fctx, message, addrinfo, name, fctx->type,
|
|
NULL, NULL, valoptions);
|
|
/*
|
|
* If validation is necessary, return now. Otherwise
|
|
* continue to process the message, letting the
|
|
* validation complete in its own good time.
|
|
*/
|
|
return result;
|
|
}
|
|
|
|
LOCK(&fctx->lock);
|
|
|
|
if (!HAVE_ANSWER(fctx)) {
|
|
resp = ISC_LIST_HEAD(fctx->resps);
|
|
if (resp != NULL) {
|
|
adbp = &resp->db;
|
|
dns_name_copy(name, resp->foundname);
|
|
anodep = &resp->node;
|
|
ardataset = resp->rdataset;
|
|
}
|
|
}
|
|
|
|
result = dns_db_findnode(fctx->cache, name, true, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
/*
|
|
* Don't report qname minimisation NXDOMAIN errors
|
|
* when the result is NXDOMAIN except we have already
|
|
* confirmed a higher error.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If we are asking for a SOA record set the cache time
|
|
* to zero to facilitate locating the containing zone of
|
|
* a arbitrary zone.
|
|
*/
|
|
ttl = fctx->res->view->maxncachettl;
|
|
if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any &&
|
|
fctx->res->zero_no_soa_ttl)
|
|
{
|
|
ttl = 0;
|
|
}
|
|
|
|
result = ncache_adderesult(message, fctx->cache, node, covers, now,
|
|
fctx->res->view->minncachettl, ttl, false,
|
|
false, ardataset, &eresult);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
if (!HAVE_ANSWER(fctx)) {
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER);
|
|
if (resp != NULL) {
|
|
resp->result = eresult;
|
|
if (adbp != NULL && *adbp != NULL) {
|
|
if (anodep != NULL && *anodep != NULL) {
|
|
dns_db_detachnode(*adbp, anodep);
|
|
}
|
|
dns_db_detach(adbp);
|
|
}
|
|
dns_db_attach(fctx->cache, adbp);
|
|
dns_db_transfernode(fctx->cache, &node, anodep);
|
|
clone_results(fctx);
|
|
}
|
|
}
|
|
|
|
unlock:
|
|
UNLOCK(&fctx->lock);
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
|
|
bool gluing) {
|
|
name->attributes.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 |= DNS_RDATASETATTR_CHASE;
|
|
}
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
if (external) {
|
|
rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns true if 'name' is external to the namespace for which
|
|
* the server being queried can answer, either because it's not a
|
|
* subdomain or because it's below a forward declaration or a
|
|
* locally served zone.
|
|
*/
|
|
static 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, NULL);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
name = &suffix;
|
|
} else if (rel == dns_namereln_equal) {
|
|
/* If 'name' is 'apex', no further checking is needed. */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If there is a locally served zone between 'apex' and 'name'
|
|
* then don't cache.
|
|
*/
|
|
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;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
bool external;
|
|
dns_rdatatype_t rtype;
|
|
bool gluing;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
#if CHECK_FOR_GLUE_IN_ANSWER
|
|
if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
#endif /* if CHECK_FOR_GLUE_IN_ANSWER */
|
|
|
|
gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns &&
|
|
dns_name_equal(fctx->name, dns_rootname)));
|
|
|
|
result = dns_message_findname(rctx->query->rmessage, section, addname,
|
|
dns_rdatatype_any, 0, &name, NULL);
|
|
if (result == ISC_R_SUCCESS) {
|
|
external = name_external(name, type, fctx);
|
|
if (type == dns_rdatatype_a) {
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == dns_rdatatype_rrsig) {
|
|
rtype = rdataset->covers;
|
|
} else {
|
|
rtype = rdataset->type;
|
|
}
|
|
if (rtype == dns_rdatatype_a ||
|
|
rtype == dns_rdatatype_aaaa)
|
|
{
|
|
mark_related(name, rdataset, external,
|
|
gluing);
|
|
}
|
|
}
|
|
} else {
|
|
result = dns_message_findtype(name, type, 0, &rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
mark_related(name, rdataset, external, gluing);
|
|
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.
|
|
*/
|
|
for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
if (rdataset->type == dns_rdatatype_a) {
|
|
INSIST(rdata.length == sizeof(ina.s_addr));
|
|
memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr));
|
|
isc_netaddr_fromin(&netaddr, &ina);
|
|
} else {
|
|
INSIST(rdata.length == sizeof(in6a.s6_addr));
|
|
memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr));
|
|
isc_netaddr_fromin6(&netaddr, &in6a);
|
|
}
|
|
|
|
result = dns_acl_match(&netaddr, NULL, view->denyansweracl,
|
|
view->aclenv, &match, NULL);
|
|
if (result == ISC_R_SUCCESS && match > 0) {
|
|
isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf,
|
|
sizeof(typebuf));
|
|
dns_rdataclass_format(rdataset->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"answer address %s denied for %s/%s/%s",
|
|
addrbuf, namebuf, typebuf, classbuf);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
|
|
dns_rdataset_t *rdataset, bool *chainingp) {
|
|
isc_result_t result;
|
|
dns_name_t *tname = NULL;
|
|
dns_rdata_cname_t cname;
|
|
dns_rdata_dname_t dname;
|
|
dns_view_t *view = fctx->res->view;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
unsigned int nlabels;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t prefix;
|
|
int order;
|
|
|
|
REQUIRE(rdataset != NULL);
|
|
REQUIRE(rdataset->type == dns_rdatatype_cname ||
|
|
rdataset->type == dns_rdatatype_dname);
|
|
|
|
/*
|
|
* By default, we allow any target name.
|
|
* If newqname != NULL we also need to extract the newqname.
|
|
*/
|
|
if (chainingp == NULL && view->denyanswernames == NULL) {
|
|
return true;
|
|
}
|
|
|
|
result = dns_rdataset_first(rdataset);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
switch (rdataset->type) {
|
|
case dns_rdatatype_cname:
|
|
result = dns_rdata_tostruct(&rdata, &cname, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
tname = &cname.cname;
|
|
break;
|
|
case dns_rdatatype_dname:
|
|
if (dns_name_fullcompare(qname, rname, &order, &nlabels) !=
|
|
dns_namereln_subdomain)
|
|
{
|
|
return true;
|
|
}
|
|
result = dns_rdata_tostruct(&rdata, &dname, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
dns_name_init(&prefix, NULL);
|
|
tname = dns_fixedname_initname(&fixed);
|
|
nlabels = dns_name_countlabels(rname);
|
|
dns_name_split(qname, nlabels, &prefix, NULL);
|
|
result = dns_name_concatenate(&prefix, &dname.dname, tname,
|
|
NULL);
|
|
if (result == DNS_R_NAMETOOLONG) {
|
|
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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"%s target %s denied for %s/%s", typebuf,
|
|
tnamebuf, qnamebuf, classbuf);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) {
|
|
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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
|
|
"fctx %p: trimming ttl of %s/NS for %s/%s: "
|
|
"%u -> %u",
|
|
fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl,
|
|
fctx->ns_ttl);
|
|
rdataset->ttl = fctx->ns_ttl;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
|
|
if (rdataset->type == dns_rdatatype_nsec3) {
|
|
/*
|
|
* NSEC3 records are not allowed to
|
|
* appear in the answer section.
|
|
*/
|
|
log_formerr(fctx, "NSEC3 in answer");
|
|
return false;
|
|
}
|
|
if (rdataset->type == dns_rdatatype_tkey) {
|
|
/*
|
|
* TKEY is not a valid record in a
|
|
* response to any query we can make.
|
|
*/
|
|
log_formerr(fctx, "TKEY in answer");
|
|
return false;
|
|
}
|
|
if (rdataset->rdclass != fctx->res->rdclass) {
|
|
log_formerr(fctx, "Mismatched class in answer");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#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->db, &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, true);
|
|
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) {
|
|
isc_result_t result;
|
|
dns_name_t *name;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_t *rdataset;
|
|
|
|
for (result = dns_message_firstname(message, section);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(message, section))
|
|
{
|
|
name = NULL;
|
|
dns_message_currentname(message, section, &name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
for (result = dns_rdataset_first(rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
if (!dns_rdata_checkowner(name, rdata.rdclass,
|
|
rdata.type, false) ||
|
|
!dns_rdata_checknames(&rdata, name, NULL))
|
|
{
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_CHECKNAMES;
|
|
}
|
|
dns_rdata_reset(&rdata);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
checknames(dns_message_t *message) {
|
|
checknamessection(message, DNS_SECTION_ANSWER);
|
|
checknamessection(message, DNS_SECTION_AUTHORITY);
|
|
checknamessection(message, DNS_SECTION_ADDITIONAL);
|
|
}
|
|
|
|
/*
|
|
* Log server NSID at log level 'level'
|
|
*/
|
|
static void
|
|
log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
|
|
isc_mem_t *mctx) {
|
|
static const char hex[17] = "0123456789abcdef";
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
size_t buflen;
|
|
unsigned char *p, *nsid;
|
|
unsigned char *buf = NULL, *pbuf = NULL;
|
|
|
|
REQUIRE(nsid_len <= UINT16_MAX);
|
|
|
|
/* Allocate buffer for storing hex version of the NSID */
|
|
buflen = nsid_len * 2 + 1;
|
|
buf = isc_mem_get(mctx, buflen);
|
|
pbuf = isc_mem_get(mctx, nsid_len + 1);
|
|
|
|
/* Convert to hex */
|
|
p = buf;
|
|
nsid = isc_buffer_current(opt);
|
|
for (size_t i = 0; i < nsid_len; i++) {
|
|
*p++ = hex[(nsid[i] >> 4) & 0xf];
|
|
*p++ = hex[nsid[i] & 0xf];
|
|
}
|
|
*p = '\0';
|
|
|
|
/* Make printable version */
|
|
p = pbuf;
|
|
for (size_t i = 0; i < nsid_len; i++) {
|
|
*p++ = isprint(nsid[i]) ? nsid[i] : '.';
|
|
}
|
|
*p = '\0';
|
|
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_NSID, DNS_LOGMODULE_RESOLVER,
|
|
level, "received NSID %s (\"%s\") from %s", buf, pbuf,
|
|
addrbuf);
|
|
|
|
isc_mem_put(mctx, pbuf, nsid_len + 1);
|
|
isc_mem_put(mctx, buf, buflen);
|
|
}
|
|
|
|
static bool
|
|
iscname(dns_message_t *message, dns_name_t *name) {
|
|
isc_result_t result;
|
|
|
|
result = dns_message_findname(message, DNS_SECTION_ANSWER, name,
|
|
dns_rdatatype_cname, 0, NULL, NULL);
|
|
return result == ISC_R_SUCCESS ? true : false;
|
|
}
|
|
|
|
static bool
|
|
betterreferral(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
dns_name_t *name;
|
|
dns_rdataset_t *rdataset;
|
|
|
|
for (result = dns_message_firstname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY))
|
|
{
|
|
name = NULL;
|
|
dns_message_currentname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY, &name);
|
|
if (!isstrictsubdomain(name, rctx->fctx->domain)) {
|
|
continue;
|
|
}
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == dns_rdatatype_ns) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 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(dns_lctx, ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"bad cookie from %s", addrbuf);
|
|
}
|
|
rctx_done(rctx, result);
|
|
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 void
|
|
resquery_response_continue(void *arg, isc_result_t result) {
|
|
respctx_t *rctx = arg;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
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);
|
|
|
|
/*
|
|
* If we have had a server cookie and don't get one retry over
|
|
* TCP. This may be a misconfigured anycast server or an attempt
|
|
* to send a spoofed response. Additionally retry over TCP if
|
|
* require-cookie is true and we don't have a got client cookie.
|
|
* Skip if we have a valid TSIG.
|
|
*/
|
|
if (dns_message_gettsig(query->rmessage, NULL) == NULL &&
|
|
!query->rmessage->cc_ok && !query->rmessage->cc_bad &&
|
|
(rctx->retryopts & DNS_FETCHOPT_TCP) == 0)
|
|
{
|
|
if (dns_adb_getcookie(query->addrinfo, NULL, 0) >
|
|
CLIENT_COOKIE_SIZE)
|
|
{
|
|
if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr,
|
|
addrbuf, sizeof(addrbuf));
|
|
isc_log_write(
|
|
dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"missing expected cookie "
|
|
"from %s",
|
|
addrbuf);
|
|
}
|
|
rctx->retryopts |= DNS_FETCHOPT_TCP;
|
|
rctx->resend = true;
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
} else if (fctx->res->view->peers != NULL) {
|
|
dns_peer_t *peer = NULL;
|
|
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) {
|
|
bool required = false;
|
|
result = dns_peer_getrequirecookie(peer,
|
|
&required);
|
|
if (result == ISC_R_SUCCESS && required) {
|
|
if (isc_log_wouldlog(dns_lctx,
|
|
ISC_LOG_INFO))
|
|
{
|
|
char addrbuf
|
|
[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(
|
|
&query->addrinfo
|
|
->sockaddr,
|
|
addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(
|
|
dns_lctx,
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_INFO,
|
|
"missing required "
|
|
"cookie "
|
|
"from %s",
|
|
addrbuf);
|
|
}
|
|
rctx->retryopts |= DNS_FETCHOPT_TCP;
|
|
rctx->resend = true;
|
|
rctx_done(rctx, result);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = cache_message(fctx, query->rmessage, query->addrinfo,
|
|
rctx->now);
|
|
if (tresult != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("cache_message 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 (rctx->type == dns_rdatatype_rrsig ||
|
|
rctx->type == dns_rdatatype_sig)
|
|
{
|
|
rctx->type = dns_rdatatype_any;
|
|
}
|
|
|
|
/*
|
|
* Bigger than any valid DNAME label count.
|
|
*/
|
|
rctx->dname_labels = dns_name_countlabels(fctx->name);
|
|
rctx->domain_labels = dns_name_countlabels(fctx->domain);
|
|
|
|
rctx->found_type = dns_rdatatype_none;
|
|
|
|
rctx->aname = NULL;
|
|
rctx->ardataset = NULL;
|
|
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
|
|
rctx->dname = NULL;
|
|
rctx->drdataset = NULL;
|
|
|
|
rctx->ns_name = NULL;
|
|
rctx->ns_rdataset = NULL;
|
|
|
|
rctx->soa_name = NULL;
|
|
rctx->ds_name = NULL;
|
|
rctx->found_name = NULL;
|
|
}
|
|
|
|
/*
|
|
* rctx_dispfail():
|
|
* Handle the case where the dispatcher failed
|
|
*/
|
|
static isc_result_t
|
|
rctx_dispfail(respctx_t *rctx) {
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
}
|
|
INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
|
|
}
|
|
|
|
/*
|
|
* rctx_edns():
|
|
* Determine whether the remote server is using EDNS correctly or
|
|
* incorrectly and record that information if needed.
|
|
*/
|
|
static void
|
|
rctx_edns(respctx_t *rctx) {
|
|
resquery_t *query = rctx->query;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
/*
|
|
* If we get a non error EDNS response record the fact so we
|
|
* won't fallback to plain DNS in the future for this server.
|
|
*/
|
|
if (rctx->opt != NULL && !EDNSOK(query->addrinfo) &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 &&
|
|
(query->rmessage->rcode == dns_rcode_noerror ||
|
|
query->rmessage->rcode == dns_rcode_nxdomain ||
|
|
query->rmessage->rcode == dns_rcode_refused ||
|
|
query->rmessage->rcode == dns_rcode_yxdomain))
|
|
{
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_answer():
|
|
* We might have answers, or we might have a malformed delegation with
|
|
* records in the answer section. Call rctx_answer_positive() or
|
|
* rctx_answer_none() as appropriate.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
if ((query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
|
|
ISFORWARDER(query->addrinfo))
|
|
{
|
|
result = rctx_answer_positive(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_positive (AA/fwd)", result);
|
|
}
|
|
} else if (iscname(query->rmessage, fctx->name) &&
|
|
fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_cname)
|
|
{
|
|
/*
|
|
* A BIND8 server could return a non-authoritative
|
|
* answer when a CNAME is followed. We should treat
|
|
* it as a valid answer.
|
|
*/
|
|
result = rctx_answer_positive(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)",
|
|
result);
|
|
}
|
|
} else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) {
|
|
result = rctx_answer_positive(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_positive (!NS)", result);
|
|
}
|
|
} else {
|
|
/*
|
|
* This may be a delegation. First let's check for
|
|
*/
|
|
|
|
if (fctx->type == dns_rdatatype_ns) {
|
|
/*
|
|
* A BIND 8 server could incorrectly return a
|
|
* non-authoritative answer to an NS query
|
|
* instead of a referral. Since this answer
|
|
* lacks the SIGs necessary to do DNSSEC
|
|
* validation, we must invoke the following
|
|
* special kludge to treat it as a referral.
|
|
*/
|
|
rctx->ns_in_answer = true;
|
|
result = rctx_answer_none(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_none (NS)", result);
|
|
}
|
|
} else {
|
|
/*
|
|
* Some other servers may still somehow include
|
|
* an answer when it should return a referral
|
|
* with an empty answer. Check to see if we can
|
|
* treat this as a referral by ignoring the
|
|
* answer. Further more, there may be an
|
|
* implementation that moves A/AAAA glue records
|
|
* to the answer section for that type of
|
|
* delegation when the query is for that glue
|
|
* record. glue_in_answer will handle
|
|
* such a corner case.
|
|
*/
|
|
rctx->glue_in_answer = true;
|
|
result = rctx_answer_none(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_none", result);
|
|
}
|
|
}
|
|
|
|
if (result == DNS_R_DELEGATION) {
|
|
/*
|
|
* 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) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
|
|
for (result = dns_message_firstname(rctx->query->rmessage,
|
|
DNS_SECTION_ANSWER);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(rctx->query->rmessage,
|
|
DNS_SECTION_ANSWER))
|
|
{
|
|
int order;
|
|
unsigned int nlabels;
|
|
dns_namereln_t namereln;
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(rctx->query->rmessage,
|
|
DNS_SECTION_ANSWER, &name);
|
|
namereln = dns_name_fullcompare(fctx->name, name, &order,
|
|
&nlabels);
|
|
switch (namereln) {
|
|
case dns_namereln_equal:
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == rctx->type ||
|
|
rctx->type == dns_rdatatype_any)
|
|
{
|
|
rctx->aname = name;
|
|
if (rctx->type != dns_rdatatype_any) {
|
|
rctx->ardataset = rdataset;
|
|
}
|
|
break;
|
|
}
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
rctx->cname = name;
|
|
rctx->crdataset = rdataset;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case dns_namereln_subdomain:
|
|
/*
|
|
* Don't accept DNAME from parent namespace.
|
|
*/
|
|
if (name_external(name, dns_rdatatype_dname, fctx)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* In-scope DNAME records must have at least
|
|
* as many labels as the domain being queried.
|
|
* They also must be less that qname's labels
|
|
* and any previously found dname.
|
|
*/
|
|
if (nlabels >= rctx->dname_labels ||
|
|
nlabels < rctx->domain_labels)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We are looking for the shortest DNAME if
|
|
* there are multiple ones (which there
|
|
* shouldn't be).
|
|
*/
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type != dns_rdatatype_dname) {
|
|
continue;
|
|
}
|
|
rctx->dname = name;
|
|
rctx->drdataset = rdataset;
|
|
rctx->dname_labels = nlabels;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If a DNAME was found, then any CNAME or other answer matching
|
|
* QNAME that may also have been found must be ignored.
|
|
* Similarly, if a matching answer was found along with a CNAME,
|
|
* the CNAME must be ignored.
|
|
*/
|
|
if (rctx->dname != NULL) {
|
|
rctx->aname = NULL;
|
|
rctx->ardataset = NULL;
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
} else if (rctx->aname != NULL) {
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_any():
|
|
* Handle responses to queries of type ANY. Scan the answer section,
|
|
* and as long as each RRset is of a type that is valid in the answer
|
|
* section, and the rdata isn't filtered, cache it.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_any(respctx_t *rctx) {
|
|
dns_rdataset_t *rdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
for (rdataset = ISC_LIST_HEAD(rctx->aname->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (!validinanswer(rdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if ((fctx->type == dns_rdatatype_sig ||
|
|
fctx->type == dns_rdatatype_rrsig) &&
|
|
rdataset->type != fctx->type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((rdataset->type == dns_rdatatype_a ||
|
|
rdataset->type == dns_rdatatype_aaaa) &&
|
|
!is_answeraddress_allowed(fctx->res->view, rctx->aname,
|
|
rdataset))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if ((rdataset->type == dns_rdatatype_cname ||
|
|
rdataset->type == dns_rdatatype_dname) &&
|
|
!is_answertarget_allowed(fctx, fctx->name, rctx->aname,
|
|
rdataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
rctx->aname->attributes.cache = true;
|
|
rctx->aname->attributes.answer = true;
|
|
rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rdataset->trust = rctx->trust;
|
|
|
|
(void)dns_rdataset_additionaldata(rdataset, rctx->aname,
|
|
check_related, rctx, 0);
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_match():
|
|
* Handle responses that match the QNAME/QTYPE of the resolver query.
|
|
* If QTYPE is valid in the answer section and the rdata isn't filtered,
|
|
* the answer can be cached. If there is additional section data related
|
|
* to the answer, it can be cached as well.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_match(respctx_t *rctx) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->ardataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if ((rctx->ardataset->type == dns_rdatatype_a ||
|
|
rctx->ardataset->type == dns_rdatatype_aaaa) &&
|
|
!is_answeraddress_allowed(fctx->res->view, rctx->aname,
|
|
rctx->ardataset))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
if ((rctx->ardataset->type == dns_rdatatype_cname ||
|
|
rctx->ardataset->type == dns_rdatatype_dname) &&
|
|
rctx->type != rctx->ardataset->type &&
|
|
rctx->type != dns_rdatatype_any &&
|
|
!is_answertarget_allowed(fctx, fctx->name, rctx->aname,
|
|
rctx->ardataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
rctx->aname->attributes.cache = true;
|
|
rctx->aname->attributes.answer = true;
|
|
rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->ardataset->trust = rctx->trust;
|
|
(void)dns_rdataset_additionaldata(rctx->ardataset, rctx->aname,
|
|
check_related, rctx, 0);
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != rctx->type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_cname():
|
|
* Handle answers containing a CNAME. Cache the CNAME, and flag that
|
|
* there may be additional chain answers to find.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_cname(respctx_t *rctx) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->crdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (rctx->type == dns_rdatatype_rrsig ||
|
|
rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec)
|
|
{
|
|
char buf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdatatype_format(rctx->type, buf, sizeof(buf));
|
|
log_formerr(fctx, "CNAME response for %s RR", buf);
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (!is_answertarget_allowed(fctx, fctx->name, rctx->cname,
|
|
rctx->crdataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
rctx->cname->attributes.cache = true;
|
|
rctx->cname->attributes.answer = true;
|
|
rctx->cname->attributes.chaining = true;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
|
|
rctx->crdataset->trust = rctx->trust;
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != dns_rdatatype_cname)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
rctx->chaining = true;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_dname():
|
|
* Handle responses with covering DNAME records.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_dname(respctx_t *rctx) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->drdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (!is_answertarget_allowed(fctx, fctx->name, rctx->dname,
|
|
rctx->drdataset, &rctx->chaining))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
rctx->dname->attributes.cache = true;
|
|
rctx->dname->attributes.answer = true;
|
|
rctx->dname->attributes.chaining = true;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
|
|
rctx->drdataset->trust = rctx->trust;
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != dns_rdatatype_dname)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_authority_positive():
|
|
* Examine the records in the authority section (if there are any) for a
|
|
* positive answer. We expect the names for all rdatasets in this
|
|
* section to be subdomains of the domain being queried; any that are
|
|
* not are skipped. We expect to find only *one* owner name; any names
|
|
* after the first one processed are ignored. We expect to find only
|
|
* rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
|
|
* remains can be cached at trust level authauthority or additional
|
|
* (depending on whether the AA bit was set on the answer).
|
|
*/
|
|
static void
|
|
rctx_authority_positive(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
bool done = false;
|
|
isc_result_t result;
|
|
|
|
result = dns_message_firstname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
while (!done && result == ISC_R_SUCCESS) {
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY, &name);
|
|
|
|
if (!name_external(name, dns_rdatatype_ns, fctx)) {
|
|
dns_rdataset_t *rdataset = NULL;
|
|
|
|
/*
|
|
* We expect to find NS or SIG NS rdatasets, and
|
|
* nothing else.
|
|
*/
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == dns_rdatatype_ns ||
|
|
(rdataset->type == dns_rdatatype_rrsig &&
|
|
rdataset->covers == dns_rdatatype_ns))
|
|
{
|
|
name->attributes.cache = true;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_CACHE;
|
|
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else {
|
|
rdataset->trust =
|
|
dns_trust_additional;
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_ns)
|
|
{
|
|
rctx->ns_name = name;
|
|
rctx->ns_rdataset = rdataset;
|
|
}
|
|
/*
|
|
* Mark any additional data
|
|
* related to this rdataset.
|
|
*/
|
|
(void)dns_rdataset_additionaldata(
|
|
rdataset, name, check_related,
|
|
rctx, 0);
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
result = dns_message_nextname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_none():
|
|
* Handles a response without an answer: this is either a negative
|
|
* response (NXDOMAIN or NXRRSET) or a referral. Determine which it is,
|
|
* then either scan the authority section for negative caching and
|
|
* DNSSEC proof of nonexistence, or else call rctx_referral().
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_none(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
FCTXTRACE("rctx_answer_none");
|
|
|
|
rctx_answer_init(rctx);
|
|
|
|
/*
|
|
* Sometimes we can tell if its a negative response by looking
|
|
* at the message header.
|
|
*/
|
|
if (rctx->query->rmessage->rcode == dns_rcode_nxdomain ||
|
|
(rctx->query->rmessage->counts[DNS_SECTION_ANSWER] == 0 &&
|
|
rctx->query->rmessage->counts[DNS_SECTION_AUTHORITY] == 0))
|
|
{
|
|
rctx->negative = true;
|
|
}
|
|
|
|
/*
|
|
* Process the authority section
|
|
*/
|
|
result = rctx_authority_negative(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return rctx->result;
|
|
}
|
|
|
|
log_ns_ttl(fctx, "rctx_answer_none");
|
|
|
|
if (rctx->ns_rdataset != NULL &&
|
|
dns_name_equal(fctx->domain, rctx->ns_name) &&
|
|
!dns_name_equal(rctx->ns_name, dns_rootname))
|
|
{
|
|
trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset);
|
|
}
|
|
|
|
/*
|
|
* A negative response has a SOA record (Type 2)
|
|
* and a optional NS RRset (Type 1) or it has neither
|
|
* a SOA or a NS RRset (Type 3, handled above) or
|
|
* rcode is NXDOMAIN (handled above) in which case
|
|
* the NS RRset is allowed (Type 4).
|
|
*/
|
|
if (rctx->soa_name != NULL) {
|
|
rctx->negative = true;
|
|
}
|
|
|
|
if (!rctx->ns_in_answer && !rctx->glue_in_answer) {
|
|
/*
|
|
* Process DNSSEC records in the authority section.
|
|
*/
|
|
result = rctx_authority_dnssec(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return rctx->result;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Trigger lookups for DNS nameservers.
|
|
*/
|
|
if (rctx->negative &&
|
|
rctx->query->rmessage->rcode == dns_rcode_noerror &&
|
|
fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL &&
|
|
dns_name_equal(rctx->soa_name, fctx->name) &&
|
|
!dns_name_equal(fctx->name, dns_rootname))
|
|
{
|
|
return DNS_R_CHASEDSSERVERS;
|
|
}
|
|
|
|
/*
|
|
* Did we find anything?
|
|
*/
|
|
if (!rctx->negative && rctx->ns_name == NULL) {
|
|
/*
|
|
* The responder is insane.
|
|
*/
|
|
if (rctx->found_name == NULL) {
|
|
log_formerr(fctx, "invalid response");
|
|
return DNS_R_FORMERR;
|
|
}
|
|
if (!dns_name_issubdomain(rctx->found_name, fctx->domain)) {
|
|
char nbuf[DNS_NAME_FORMATSIZE];
|
|
char dbuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_rdatatype_format(rctx->found_type, tbuf,
|
|
sizeof(tbuf));
|
|
dns_name_format(rctx->found_name, nbuf, sizeof(nbuf));
|
|
dns_name_format(fctx->domain, dbuf, sizeof(dbuf));
|
|
|
|
log_formerr(fctx,
|
|
"Name %s (%s) not subdomain"
|
|
" of zone %s -- invalid response",
|
|
nbuf, tbuf, dbuf);
|
|
} else {
|
|
log_formerr(fctx, "invalid response");
|
|
}
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
/*
|
|
* If we found both NS and SOA, they should be the same name.
|
|
*/
|
|
if (rctx->ns_name != NULL && rctx->soa_name != NULL &&
|
|
rctx->ns_name != rctx->soa_name)
|
|
{
|
|
log_formerr(fctx, "NS/SOA mismatch");
|
|
return DNS_R_FORMERR;
|
|
}
|
|
|
|
/*
|
|
* Handle a referral.
|
|
*/
|
|
result = rctx_referral(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return rctx->result;
|
|
}
|
|
|
|
/*
|
|
* Since we're not doing a referral, we don't want to cache any
|
|
* NS RRs we may have found.
|
|
*/
|
|
if (rctx->ns_name != NULL) {
|
|
rctx->ns_name->attributes.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) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_section_t section;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
bool finished = false;
|
|
|
|
if (rctx->ns_in_answer) {
|
|
INSIST(fctx->type == dns_rdatatype_ns);
|
|
section = DNS_SECTION_ANSWER;
|
|
} else {
|
|
section = DNS_SECTION_AUTHORITY;
|
|
}
|
|
|
|
result = dns_message_firstname(rctx->query->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
while (!finished) {
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(rctx->query->rmessage, section, &name);
|
|
result = dns_message_nextname(rctx->query->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
finished = true;
|
|
}
|
|
|
|
if (!dns_name_issubdomain(name, fctx->domain)) {
|
|
continue;
|
|
}
|
|
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
dns_rdatatype_t type = rdataset->type;
|
|
if (type == dns_rdatatype_rrsig) {
|
|
type = rdataset->covers;
|
|
}
|
|
if ((type == dns_rdatatype_ns ||
|
|
type == dns_rdatatype_soa) &&
|
|
!dns_name_issubdomain(fctx->name, name))
|
|
{
|
|
char qbuf[DNS_NAME_FORMATSIZE];
|
|
char nbuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdatatype_format(type, tbuf, sizeof(tbuf));
|
|
dns_name_format(name, nbuf, sizeof(nbuf));
|
|
dns_name_format(fctx->name, qbuf, sizeof(qbuf));
|
|
log_formerr(fctx,
|
|
"unrelated %s %s in "
|
|
"%s authority section",
|
|
tbuf, nbuf, qbuf);
|
|
break;
|
|
}
|
|
|
|
switch (type) {
|
|
case dns_rdatatype_ns:
|
|
/*
|
|
* NS or RRSIG NS.
|
|
*
|
|
* Only one set of NS RRs is allowed.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_ns) {
|
|
if (rctx->ns_name != NULL &&
|
|
name != rctx->ns_name)
|
|
{
|
|
log_formerr(fctx, "multiple NS "
|
|
"RRsets "
|
|
"in "
|
|
"authority "
|
|
"section");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
rctx->ns_name = name;
|
|
rctx->ns_rdataset = rdataset;
|
|
}
|
|
name->attributes.cache = true;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rdataset->trust = dns_trust_glue;
|
|
break;
|
|
case dns_rdatatype_soa:
|
|
/*
|
|
* SOA, or RRSIG SOA.
|
|
*
|
|
* Only one SOA is allowed.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_soa) {
|
|
if (rctx->soa_name != NULL &&
|
|
name != rctx->soa_name)
|
|
{
|
|
log_formerr(fctx, "multiple "
|
|
"SOA RRs "
|
|
"in "
|
|
"authority "
|
|
"section");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
rctx->soa_name = name;
|
|
}
|
|
name->attributes.ncache = true;
|
|
rdataset->attributes |= DNS_RDATASETATTR_NCACHE;
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else if (ISFORWARDER(fctx->addrinfo)) {
|
|
rdataset->trust = dns_trust_answer;
|
|
} else {
|
|
rdataset->trust = dns_trust_additional;
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_ncache():
|
|
* Cache the negatively cacheable parts of the message. This may
|
|
* also cause work to be queued to the DNSSEC validator.
|
|
*/
|
|
static void
|
|
rctx_ncache(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
dns_rdatatype_t covers;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!WANTNCACHE(fctx)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Cache DS NXDOMAIN separately to other types.
|
|
*/
|
|
if (rctx->query->rmessage->rcode == dns_rcode_nxdomain &&
|
|
fctx->type != dns_rdatatype_ds)
|
|
{
|
|
covers = dns_rdatatype_any;
|
|
} else {
|
|
covers = fctx->type;
|
|
}
|
|
|
|
/*
|
|
* Cache any negative cache entries in the message.
|
|
*/
|
|
result = ncache_message(fctx, rctx->query->rmessage,
|
|
rctx->query->addrinfo, covers, rctx->now);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("ncache_message complete", result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_authority_dnssec():
|
|
*
|
|
* Scan the authority section of a negative answer or referral,
|
|
* handling DNSSEC records (i.e. NSEC, NSEC3, DS).
|
|
*/
|
|
static isc_result_t
|
|
rctx_authority_dnssec(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
bool finished = false;
|
|
|
|
REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer);
|
|
|
|
result = dns_message_firstname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
while (!finished) {
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY, &name);
|
|
result = dns_message_nextname(rctx->query->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
if (result != ISC_R_SUCCESS) {
|
|
finished = true;
|
|
}
|
|
|
|
if (!dns_name_issubdomain(name, fctx->domain)) {
|
|
/*
|
|
* Invalid name found; preserve it for logging
|
|
* later.
|
|
*/
|
|
rctx->found_name = name;
|
|
rctx->found_type = ISC_LIST_HEAD(name->list)->type;
|
|
continue;
|
|
}
|
|
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
bool checknta = true;
|
|
bool secure_domain = false;
|
|
dns_rdatatype_t type = rdataset->type;
|
|
|
|
if (type == dns_rdatatype_rrsig) {
|
|
type = rdataset->covers;
|
|
}
|
|
|
|
switch (type) {
|
|
case dns_rdatatype_nsec:
|
|
case dns_rdatatype_nsec3:
|
|
if (rctx->negative) {
|
|
name->attributes.ncache = true;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_NCACHE;
|
|
} else if (type == dns_rdatatype_nsec) {
|
|
name->attributes.cache = true;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_CACHE;
|
|
}
|
|
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else if (ISFORWARDER(fctx->addrinfo)) {
|
|
rdataset->trust = dns_trust_answer;
|
|
} else {
|
|
rdataset->trust = dns_trust_additional;
|
|
}
|
|
/*
|
|
* No additional data needs to be
|
|
* marked.
|
|
*/
|
|
break;
|
|
case dns_rdatatype_ds:
|
|
/*
|
|
* DS or SIG DS.
|
|
*
|
|
* These should only be here if this is
|
|
* a referral, and there should only be
|
|
* one DS RRset.
|
|
*/
|
|
if (rctx->ns_name == NULL) {
|
|
log_formerr(fctx, "DS with no "
|
|
"referral");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_ds) {
|
|
if (rctx->ds_name != NULL &&
|
|
name != rctx->ds_name)
|
|
{
|
|
log_formerr(fctx, "DS doesn't "
|
|
"match "
|
|
"referral "
|
|
"(NS)");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
rctx->ds_name = name;
|
|
}
|
|
|
|
name->attributes.cache = true;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
checknta = false;
|
|
}
|
|
if (fctx->res->view->enablevalidation) {
|
|
result = issecuredomain(
|
|
fctx->res->view, name,
|
|
dns_rdatatype_ds, fctx->now,
|
|
checknta, NULL, &secure_domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
if (secure_domain) {
|
|
rdataset->trust =
|
|
dns_trust_pending_answer;
|
|
} else if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else if (ISFORWARDER(fctx->addrinfo)) {
|
|
rdataset->trust = dns_trust_answer;
|
|
} else {
|
|
rdataset->trust = dns_trust_additional;
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* rctx_referral():
|
|
* Handles referral responses. Check for sanity, find glue as needed,
|
|
* and update the fetch context to follow the delegation.
|
|
*/
|
|
static isc_result_t
|
|
rctx_referral(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (rctx->negative || rctx->ns_name == NULL) {
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* We already know ns_name is a subdomain of fctx->domain.
|
|
* If ns_name is equal to fctx->domain, we're not making
|
|
* progress. We return DNS_R_FORMERR so that we'll keep
|
|
* trying other servers.
|
|
*/
|
|
if (dns_name_equal(rctx->ns_name, fctx->domain)) {
|
|
log_formerr(fctx, "non-improving referral");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/*
|
|
* If the referral name is not a parent of the query
|
|
* name, consider the responder insane.
|
|
*/
|
|
if (!dns_name_issubdomain(fctx->name, rctx->ns_name)) {
|
|
/* Logged twice */
|
|
log_formerr(fctx, "referral to non-parent");
|
|
FCTXTRACE("referral to non-parent");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/*
|
|
* Mark any additional data related to this rdataset.
|
|
* It's important that we do this before we change the
|
|
* query domain.
|
|
*/
|
|
INSIST(rctx->ns_rdataset != NULL);
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING);
|
|
|
|
/*
|
|
* Mark the glue records in the additional section to be cached.
|
|
*/
|
|
(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 &&
|
|
(fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a))
|
|
{
|
|
(void)dns_rdataset_additionaldata(rctx->ns_rdataset,
|
|
rctx->ns_name, check_answer,
|
|
fctx, 0);
|
|
}
|
|
#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, true);
|
|
if (result != ISC_R_SUCCESS) {
|
|
rctx->result = result;
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
|
|
fctx->ns_ttl_ok = false;
|
|
log_ns_ttl(fctx, "DELEGATION");
|
|
rctx->result = DNS_R_DELEGATION;
|
|
|
|
/*
|
|
* Reinitialize 'rctx' to prepare for following the delegation:
|
|
* set the get_nameservers and next_server flags appropriately
|
|
* and reset the fetch context counters.
|
|
*
|
|
*/
|
|
if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) {
|
|
rctx->get_nameservers = true;
|
|
rctx->next_server = true;
|
|
rctx->fctx->restarts = 0;
|
|
rctx->fctx->referrals++;
|
|
rctx->fctx->querysent = 0;
|
|
rctx->fctx->lamecount = 0;
|
|
rctx->fctx->quotacount = 0;
|
|
rctx->fctx->neterr = 0;
|
|
rctx->fctx->badresp = 0;
|
|
rctx->fctx->adberr = 0;
|
|
}
|
|
|
|
return ISC_R_COMPLETE;
|
|
}
|
|
|
|
/*
|
|
* rctx_additional():
|
|
* Scan the additional section of a response to find records related
|
|
* to answers we were interested in.
|
|
*/
|
|
static void
|
|
rctx_additional(respctx_t *rctx) {
|
|
bool rescan;
|
|
dns_section_t section = DNS_SECTION_ADDITIONAL;
|
|
isc_result_t result;
|
|
|
|
again:
|
|
rescan = false;
|
|
|
|
for (result = dns_message_firstname(rctx->query->rmessage, section);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(rctx->query->rmessage, section))
|
|
{
|
|
dns_name_t *name = NULL;
|
|
dns_rdataset_t *rdataset;
|
|
dns_message_currentname(rctx->query->rmessage,
|
|
DNS_SECTION_ADDITIONAL, &name);
|
|
if (!name->attributes.chase) {
|
|
continue;
|
|
}
|
|
name->attributes.chase = false;
|
|
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (CHASE(rdataset)) {
|
|
rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
|
|
(void)dns_rdataset_additionaldata(
|
|
rdataset, name, check_related, rctx, 0);
|
|
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;
|
|
#ifdef HAVE_DNSTAP
|
|
isc_result_t result;
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
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 */
|
|
|
|
dns_message_logfmtpacket(
|
|
rctx->query->rmessage, "received packet from",
|
|
&rctx->query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_PACKETS, &dns_master_style_comment,
|
|
ISC_LOG_DEBUG(10), 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, NULL);
|
|
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;
|
|
}
|
|
|
|
result = dns_dispentry_getlocaladdress(rctx->query->dispentry,
|
|
&localaddr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
la = &localaddr;
|
|
}
|
|
|
|
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;
|
|
|
|
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 {
|
|
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);
|
|
dns_nametree_detach(&res->mustbesecure);
|
|
|
|
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_lctx, 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, isc_loopmgr_t *loopmgr, isc_nm_t *nm,
|
|
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){
|
|
.loopmgr = loopmgr,
|
|
.rdclass = view->rdclass,
|
|
.nm = nm,
|
|
.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(loopmgr),
|
|
.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);
|
|
dns_nametree_create(res->mctx, DNS_NAMETREE_BOOL,
|
|
"dnssec-must-be-secure", &res->mustbesecure);
|
|
|
|
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(res->loopmgr, 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_lctx, 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->db, &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(dns_lctx, level)) {
|
|
return;
|
|
}
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, level, "fetch: %s/%s", namebuf,
|
|
typebuf);
|
|
}
|
|
|
|
static 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, NULL);
|
|
|
|
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;
|
|
}
|
|
} 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);
|
|
}
|
|
|
|
if (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_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5),
|
|
"QNAME minimization - %s minimized, qmintype %d "
|
|
"qminname %s",
|
|
fctx->minimized ? "" : "not", fctx->qmintype, domainbuf);
|
|
}
|
|
|
|
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) {
|
|
dns_fetchresponse_t *resp = NULL;
|
|
for (resp = ISC_LIST_HEAD(fctx->resps); resp != NULL;
|
|
resp = ISC_LIST_NEXT(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) {
|
|
dns_fetchresponse_t *next = NULL;
|
|
for (dns_fetchresponse_t *resp = ISC_LIST_HEAD(fctx->resps);
|
|
resp != NULL; resp = next)
|
|
{
|
|
next = ISC_LIST_NEXT(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) {
|
|
dns_fetchresponse_t *resp = NULL, *next = NULL;
|
|
for (resp = ISC_LIST_HEAD(fctx->resps); resp != NULL;
|
|
resp = next)
|
|
{
|
|
next = ISC_LIST_NEXT(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_log_t *lctx,
|
|
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(lctx, 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, NULL);
|
|
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 > 255) {
|
|
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) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) {
|
|
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);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
|
|
bool value) {
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
result = dns_nametree_add(resolver->mustbesecure, name, value);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return dns_nametree_covered(resolver->mustbesecure, name, NULL, 0);
|
|
}
|
|
|
|
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;
|
|
dns_fetchresponse_t *resp = NULL;
|
|
resquery_t *query = 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);
|
|
|
|
for (resp = ISC_LIST_HEAD(fctx->resps); resp != NULL;
|
|
resp = ISC_LIST_NEXT(resp, link))
|
|
{
|
|
resp_count++;
|
|
}
|
|
|
|
for (query = ISC_LIST_HEAD(fctx->queries); query != NULL;
|
|
query = ISC_LIST_NEXT(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(res->loopmgr));
|
|
}
|
|
|
|
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));
|
|
}
|