mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 10:10:06 +00:00
Replace dns_fixedname_init() calls followed by dns_fixedname_name() calls with calls to dns_fixedname_initname() where it is possible without affecting current behavior and/or performance. This patch was mostly prepared using Coccinelle and the following semantic patch: @@ expression fixedname, name; @@ - dns_fixedname_init(&fixedname); ... - name = dns_fixedname_name(&fixedname); + name = dns_fixedname_initname(&fixedname); The resulting set of changes was then manually reviewed to exclude false positives and apply minor tweaks. It is likely that more occurrences of this pattern can be refactored in an identical way. This commit only takes care of the low-hanging fruit.
11126 lines
289 KiB
C
11126 lines
289 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <config.h>
|
|
#include <ctype.h>
|
|
|
|
#include <isc/counter.h>
|
|
#include <isc/log.h>
|
|
#include <isc/platform.h>
|
|
#include <isc/print.h>
|
|
#include <isc/string.h>
|
|
#include <isc/random.h>
|
|
#include <isc/socket.h>
|
|
#include <isc/stats.h>
|
|
#include <isc/task.h>
|
|
#include <isc/timer.h>
|
|
#include <isc/util.h>
|
|
|
|
#ifdef AES_CC
|
|
#include <isc/aes.h>
|
|
#else
|
|
#include <isc/hmacsha.h>
|
|
#endif
|
|
|
|
#include <dns/acl.h>
|
|
#include <dns/adb.h>
|
|
#include <dns/badcache.h>
|
|
#include <dns/cache.h>
|
|
#include <dns/db.h>
|
|
#include <dns/dispatch.h>
|
|
#include <dns/dnstap.h>
|
|
#include <dns/ds.h>
|
|
#include <dns/edns.h>
|
|
#include <dns/events.h>
|
|
#include <dns/forward.h>
|
|
#include <dns/keytable.h>
|
|
#include <dns/log.h>
|
|
#include <dns/message.h>
|
|
#include <dns/ncache.h>
|
|
#include <dns/nsec.h>
|
|
#include <dns/nsec3.h>
|
|
#include <dns/opcode.h>
|
|
#include <dns/peer.h>
|
|
#include <dns/rbt.h>
|
|
#include <dns/rcode.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataclass.h>
|
|
#include <dns/rdatalist.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/resolver.h>
|
|
#include <dns/result.h>
|
|
#include <dns/rootns.h>
|
|
#include <dns/stats.h>
|
|
#include <dns/tsig.h>
|
|
#include <dns/validator.h>
|
|
|
|
#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
|
|
#define RTRACE(m) do { UNUSED(m); } while (0)
|
|
#define RRTRACE(r, m) do { UNUSED(r); UNUSED(m); } while (0)
|
|
#define FCTXTRACE(m) do { UNUSED(m); } while (0)
|
|
#define FCTXTRACE2(m1, m2) do { UNUSED(m1); UNUSED(m2); } while (0)
|
|
#define FCTXTRACE3(m1, res) do { UNUSED(m1); UNUSED(res); } while (0)
|
|
#define FCTXTRACE4(m1, m2, res) \
|
|
do { UNUSED(m1); UNUSED(m2); UNUSED(res); } while (0)
|
|
#define FCTXTRACE5(m1, m2, v) \
|
|
do { UNUSED(m1); UNUSED(m2); UNUSED(v); } while (0)
|
|
#define FTRACE(m) do { UNUSED(m); } while (0)
|
|
#define QTRACE(m) do { UNUSED(m); } while (0)
|
|
#endif /* WANT_QUERYTRACE */
|
|
|
|
#define US_PER_SEC 1000000U
|
|
#define US_PER_MSEC 1000U
|
|
/*
|
|
* The maximum time we will wait for a single query.
|
|
*/
|
|
#define MAX_SINGLE_QUERY_TIMEOUT 9000U
|
|
#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_MSEC)
|
|
|
|
/*
|
|
* We need to allow a individual query time to complete / timeout.
|
|
*/
|
|
#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
|
|
|
|
/* The default time in seconds for the whole query to live. */
|
|
#ifndef DEFAULT_QUERY_TIMEOUT
|
|
#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT
|
|
#endif
|
|
|
|
/* The maximum time in seconds for the whole query to live. */
|
|
#ifndef MAXIMUM_QUERY_TIMEOUT
|
|
#define MAXIMUM_QUERY_TIMEOUT 30000
|
|
#endif
|
|
|
|
/* The default maximum number of recursions to follow before giving up. */
|
|
#ifndef DEFAULT_RECURSION_DEPTH
|
|
#define DEFAULT_RECURSION_DEPTH 7
|
|
#endif
|
|
|
|
/* The default maximum number of iterative queries to allow before giving up. */
|
|
#ifndef DEFAULT_MAX_QUERIES
|
|
#define DEFAULT_MAX_QUERIES 75
|
|
#endif
|
|
|
|
/* Number of hash buckets for zone counters */
|
|
#ifndef RES_DOMAIN_BUCKETS
|
|
#define RES_DOMAIN_BUCKETS 523
|
|
#endif
|
|
#define RES_NOBUCKET 0xffffffff
|
|
|
|
/*%
|
|
* Maximum EDNS0 input packet size.
|
|
*/
|
|
#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */
|
|
|
|
/*%
|
|
* This defines the maximum number of timeouts we will permit before we
|
|
* disable EDNS0 on the query.
|
|
*/
|
|
#define MAX_EDNS0_TIMEOUTS 3
|
|
|
|
#define DNS_RESOLVER_BADCACHESIZE 1021
|
|
#define DNS_RESOLVER_BADCACHETTL(fctx) \
|
|
(((fctx)->res->lame_ttl > 30 ) ? (fctx)->res->lame_ttl : 30)
|
|
|
|
typedef struct fetchctx fetchctx_t;
|
|
|
|
typedef struct query {
|
|
/* Locked by task event serialization. */
|
|
unsigned int magic;
|
|
fetchctx_t * fctx;
|
|
isc_mem_t * mctx;
|
|
dns_dispatchmgr_t * dispatchmgr;
|
|
dns_dispatch_t * dispatch;
|
|
isc_boolean_t exclusivesocket;
|
|
dns_adbaddrinfo_t * addrinfo;
|
|
isc_socket_t * tcpsocket;
|
|
isc_time_t start;
|
|
dns_messageid_t id;
|
|
dns_dispentry_t * dispentry;
|
|
ISC_LINK(struct query) link;
|
|
isc_buffer_t buffer;
|
|
isc_buffer_t *tsig;
|
|
dns_tsigkey_t *tsigkey;
|
|
isc_socketevent_t sendevent;
|
|
isc_dscp_t dscp;
|
|
int ednsversion;
|
|
unsigned int options;
|
|
unsigned int attributes;
|
|
unsigned int sends;
|
|
unsigned int connects;
|
|
unsigned int udpsize;
|
|
unsigned char data[512];
|
|
} resquery_t;
|
|
|
|
struct tried {
|
|
isc_sockaddr_t addr;
|
|
unsigned int count;
|
|
ISC_LINK(struct tried) link;
|
|
};
|
|
|
|
#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!')
|
|
#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC)
|
|
|
|
#define RESQUERY_ATTR_CANCELED 0x02
|
|
|
|
#define RESQUERY_CONNECTING(q) ((q)->connects > 0)
|
|
#define RESQUERY_CANCELED(q) (((q)->attributes & \
|
|
RESQUERY_ATTR_CANCELED) != 0)
|
|
#define RESQUERY_SENDING(q) ((q)->sends > 0)
|
|
|
|
typedef enum {
|
|
fetchstate_init = 0, /*%< Start event has not run yet. */
|
|
fetchstate_active,
|
|
fetchstate_done /*%< FETCHDONE events posted. */
|
|
} fetchstate;
|
|
|
|
typedef enum {
|
|
badns_unreachable = 0,
|
|
badns_response,
|
|
badns_validation
|
|
} badnstype_t;
|
|
|
|
struct fetchctx {
|
|
/*% Not locked. */
|
|
unsigned int magic;
|
|
dns_resolver_t * res;
|
|
dns_name_t name;
|
|
dns_rdatatype_t type;
|
|
unsigned int options;
|
|
unsigned int bucketnum;
|
|
unsigned int dbucketnum;
|
|
char * info;
|
|
isc_mem_t * mctx;
|
|
|
|
/*% Locked by appropriate bucket lock. */
|
|
fetchstate state;
|
|
isc_boolean_t want_shutdown;
|
|
isc_boolean_t cloned;
|
|
isc_boolean_t spilled;
|
|
unsigned int references;
|
|
isc_event_t control_event;
|
|
ISC_LINK(struct fetchctx) link;
|
|
ISC_LIST(dns_fetchevent_t) events;
|
|
/*% Locked by task event serialization. */
|
|
dns_name_t domain;
|
|
dns_rdataset_t nameservers;
|
|
unsigned int attributes;
|
|
isc_timer_t * timer;
|
|
isc_time_t expires;
|
|
isc_interval_t interval;
|
|
dns_message_t * qmessage;
|
|
dns_message_t * rmessage;
|
|
ISC_LIST(resquery_t) queries;
|
|
dns_adbfindlist_t finds;
|
|
dns_adbfind_t * find;
|
|
dns_adbfindlist_t altfinds;
|
|
dns_adbfind_t * altfind;
|
|
dns_adbaddrinfolist_t forwaddrs;
|
|
dns_adbaddrinfolist_t altaddrs;
|
|
dns_forwarderlist_t forwarders;
|
|
dns_fwdpolicy_t fwdpolicy;
|
|
isc_sockaddrlist_t bad;
|
|
ISC_LIST(struct tried) edns;
|
|
ISC_LIST(struct tried) edns512;
|
|
isc_sockaddrlist_t bad_edns;
|
|
dns_validator_t * validator;
|
|
ISC_LIST(dns_validator_t) validators;
|
|
dns_db_t * cache;
|
|
dns_adb_t * adb;
|
|
isc_boolean_t ns_ttl_ok;
|
|
isc_uint32_t ns_ttl;
|
|
isc_counter_t * qc;
|
|
|
|
/*%
|
|
* The number of events we're waiting for.
|
|
*/
|
|
unsigned int 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_name_t nsname;
|
|
dns_fetch_t * nsfetch;
|
|
dns_rdataset_t nsrrset;
|
|
|
|
/*%
|
|
* Number of queries that reference this context.
|
|
*/
|
|
unsigned int nqueries;
|
|
|
|
/*%
|
|
* The reason to print when logging a successful
|
|
* response to a query.
|
|
*/
|
|
const char * reason;
|
|
|
|
/*%
|
|
* Random numbers to use for mixing up server addresses.
|
|
*/
|
|
isc_uint32_t rand_buf;
|
|
isc_uint32_t rand_bits;
|
|
|
|
/*%
|
|
* Fetch-local statistics for detailed logging.
|
|
*/
|
|
isc_result_t result; /*%< fetch result */
|
|
isc_result_t vresult; /*%< validation result */
|
|
int exitline;
|
|
isc_time_t start;
|
|
isc_uint64_t duration;
|
|
isc_boolean_t 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;
|
|
isc_boolean_t timeout;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
const isc_sockaddr_t *client;
|
|
unsigned int depth;
|
|
};
|
|
|
|
#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!')
|
|
#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC)
|
|
|
|
#define FCTX_ATTR_HAVEANSWER 0x0001
|
|
#define FCTX_ATTR_GLUING 0x0002
|
|
#define FCTX_ATTR_ADDRWAIT 0x0004
|
|
#define FCTX_ATTR_SHUTTINGDOWN 0x0008
|
|
#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) (((f)->attributes & FCTX_ATTR_HAVEANSWER) != \
|
|
0)
|
|
#define GLUING(f) (((f)->attributes & FCTX_ATTR_GLUING) != \
|
|
0)
|
|
#define ADDRWAIT(f) (((f)->attributes & FCTX_ATTR_ADDRWAIT) != \
|
|
0)
|
|
#define SHUTTINGDOWN(f) (((f)->attributes & FCTX_ATTR_SHUTTINGDOWN) \
|
|
!= 0)
|
|
#define WANTCACHE(f) (((f)->attributes & FCTX_ATTR_WANTCACHE) != 0)
|
|
#define WANTNCACHE(f) (((f)->attributes & FCTX_ATTR_WANTNCACHE) != 0)
|
|
#define NEEDEDNS0(f) (((f)->attributes & FCTX_ATTR_NEEDEDNS0) != 0)
|
|
#define TRIEDFIND(f) (((f)->attributes & FCTX_ATTR_TRIEDFIND) != 0)
|
|
#define TRIEDALT(f) (((f)->attributes & FCTX_ATTR_TRIEDALT) != 0)
|
|
|
|
typedef struct {
|
|
dns_adbaddrinfo_t * addrinfo;
|
|
fetchctx_t * fctx;
|
|
} dns_valarg_t;
|
|
|
|
struct dns_fetch {
|
|
unsigned int magic;
|
|
isc_mem_t * mctx;
|
|
fetchctx_t * private;
|
|
};
|
|
|
|
#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h')
|
|
#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC)
|
|
|
|
typedef struct fctxbucket {
|
|
isc_task_t * task;
|
|
isc_mutex_t lock;
|
|
ISC_LIST(fetchctx_t) fctxs;
|
|
isc_boolean_t exiting;
|
|
isc_mem_t * mctx;
|
|
} fctxbucket_t;
|
|
|
|
typedef struct fctxcount fctxcount_t;
|
|
struct fctxcount {
|
|
dns_fixedname_t fdname;
|
|
dns_name_t *domain;
|
|
isc_uint32_t count;
|
|
isc_uint32_t allowed;
|
|
isc_uint32_t dropped;
|
|
isc_stdtime_t logged;
|
|
ISC_LINK(fctxcount_t) link;
|
|
};
|
|
|
|
typedef struct zonebucket {
|
|
isc_mutex_t lock;
|
|
isc_mem_t *mctx;
|
|
ISC_LIST(fctxcount_t) list;
|
|
} zonebucket_t;
|
|
|
|
typedef struct alternate {
|
|
isc_boolean_t 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 nlock;
|
|
isc_mutex_t primelock;
|
|
dns_rdataclass_t rdclass;
|
|
isc_socketmgr_t * socketmgr;
|
|
isc_timermgr_t * timermgr;
|
|
isc_taskmgr_t * taskmgr;
|
|
dns_view_t * view;
|
|
isc_boolean_t frozen;
|
|
unsigned int options;
|
|
dns_dispatchmgr_t * dispatchmgr;
|
|
dns_dispatchset_t * dispatches4;
|
|
isc_boolean_t exclusivev4;
|
|
dns_dispatchset_t * dispatches6;
|
|
isc_dscp_t querydscp4;
|
|
isc_dscp_t querydscp6;
|
|
isc_boolean_t exclusivev6;
|
|
unsigned int nbuckets;
|
|
fctxbucket_t * buckets;
|
|
zonebucket_t * dbuckets;
|
|
isc_uint32_t lame_ttl;
|
|
ISC_LIST(alternate_t) alternates;
|
|
isc_uint16_t udpsize;
|
|
#if USE_ALGLOCK
|
|
isc_rwlock_t alglock;
|
|
#endif
|
|
dns_rbt_t * algorithms;
|
|
dns_rbt_t * digests;
|
|
#if USE_MBSLOCK
|
|
isc_rwlock_t mbslock;
|
|
#endif
|
|
dns_rbt_t * mustbesecure;
|
|
unsigned int spillatmax;
|
|
unsigned int spillatmin;
|
|
isc_timer_t * spillattimer;
|
|
isc_boolean_t zero_no_soa_ttl;
|
|
unsigned int query_timeout;
|
|
unsigned int maxdepth;
|
|
unsigned int maxqueries;
|
|
isc_result_t quotaresp[2];
|
|
|
|
/* Additions for serve-stale feature. */
|
|
unsigned int retryinterval; /* in milliseconds */
|
|
unsigned int nonbackofftries;
|
|
|
|
/* Locked by lock. */
|
|
unsigned int references;
|
|
isc_boolean_t exiting;
|
|
isc_eventlist_t whenshutdown;
|
|
unsigned int activebuckets;
|
|
isc_boolean_t priming;
|
|
unsigned int spillat; /* clients-per-query */
|
|
unsigned int zspill; /* fetches-per-zone */
|
|
|
|
dns_badcache_t * badcache; /* Bad cache. */
|
|
|
|
/* Locked by primelock. */
|
|
dns_fetch_t * primefetch;
|
|
/* Locked by nlock. */
|
|
unsigned int nfctx;
|
|
};
|
|
|
|
#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!')
|
|
#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC)
|
|
|
|
/*%
|
|
* Private addrinfo flags. These must not conflict with DNS_FETCHOPT_NOEDNS0
|
|
* (0x008) which we also use as an addrinfo flag.
|
|
*/
|
|
#define FCTX_ADDRINFO_MARK 0x00001
|
|
#define FCTX_ADDRINFO_FORWARDER 0x01000
|
|
#define FCTX_ADDRINFO_EDNSOK 0x04000
|
|
#define FCTX_ADDRINFO_NOCOOKIE 0x08000
|
|
#define FCTX_ADDRINFO_BADCOOKIE 0x10000
|
|
|
|
#define 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 NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
|
|
#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
|
|
|
|
#ifdef ENABLE_AFL
|
|
static isc_boolean_t fuzzing_resolver = ISC_FALSE;
|
|
void dns_resolver_setfuzzing() {
|
|
fuzzing_resolver = ISC_TRUE;
|
|
}
|
|
#endif
|
|
|
|
static void destroy(dns_resolver_t *res);
|
|
static void empty_bucket(dns_resolver_t *res);
|
|
static isc_result_t resquery_send(resquery_t *query);
|
|
static void resquery_response(isc_task_t *task, isc_event_t *event);
|
|
static void resquery_connected(isc_task_t *task, isc_event_t *event);
|
|
static void fctx_try(fetchctx_t *fctx, isc_boolean_t retrying,
|
|
isc_boolean_t badcache);
|
|
static void fctx_destroy(fetchctx_t *fctx);
|
|
static isc_boolean_t fctx_unlink(fetchctx_t *fctx);
|
|
static isc_result_t ncache_adderesult(dns_message_t *message,
|
|
dns_db_t *cache, dns_dbnode_t *node,
|
|
dns_rdatatype_t covers,
|
|
isc_stdtime_t now, dns_ttl_t maxttl,
|
|
isc_boolean_t optout,
|
|
isc_boolean_t secure,
|
|
dns_rdataset_t *ardataset,
|
|
isc_result_t *eresultp);
|
|
static void validated(isc_task_t *task, isc_event_t *event);
|
|
static isc_boolean_t maybe_destroy(fetchctx_t *fctx, isc_boolean_t locked);
|
|
static void add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
|
|
isc_result_t reason, badnstype_t badtype);
|
|
static inline isc_result_t findnoqname(fetchctx_t *fctx, dns_name_t *name,
|
|
dns_rdatatype_t type,
|
|
dns_name_t **noqname);
|
|
static void fctx_increference(fetchctx_t *fctx);
|
|
static isc_boolean_t fctx_decreference(fetchctx_t *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()).
|
|
* - Handle delegation-only zones (rctx_delonly_zone()).
|
|
* - If RCODE and ANCOUNT suggest this is a positive answer, and
|
|
* if so, call rctx_answer(): go to step 2.
|
|
* - If RCODE and NSCOUNT suggest this is a negative answer or a
|
|
* referral, call rctx_answer_none(): go to step 4.
|
|
* - Check the additional section for data that should be cached
|
|
* (rctx_additional()).
|
|
* - Clean up and finish by calling rctx_done(): go to step 5.
|
|
*
|
|
* 2. rctx_answer():
|
|
* - If the answer appears to be positive, call rctx_answer_positive():
|
|
* go to step 3.
|
|
* - If the response is a malformed delegation (with glue or NS records
|
|
* in the answer section), call rctx_answer_none(): go to step 4.
|
|
*
|
|
* 3. rctx_answer_positive():
|
|
* - Initialize the portions of respctx_t needed for processing an answer
|
|
* (rctx_answer_init()).
|
|
* - Scan the answer section to find records that are responsive to the
|
|
* query (rctx_answer_scan()).
|
|
* - For whichever type of response was found, call a separate routine
|
|
* to handle it: matching QNAME/QTYPE (rctx_answer_match()),
|
|
* CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()),
|
|
* or any records returned in response to a query of type ANY
|
|
* (rctx_answer_any()).
|
|
* - Scan the authority section for NS or other records that may be
|
|
* included with a positive answer (rctx_authority_scan()).
|
|
*
|
|
* 4. rctx_answer_none():
|
|
* - Determine whether this is an NXDOMAIN, NXRRSET, or referral.
|
|
* - If referral, set up the resolver to follow the delegation
|
|
* (rctx_referral()).
|
|
* - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA
|
|
* records included with a negative response (rctx_authority_negative()),
|
|
* then for DNSSEC proof of nonexistence (rctx_authority_dnssec()).
|
|
*
|
|
* 5. rctx_done():
|
|
* - Set up chasing of DS records if needed (rctx_chaseds()).
|
|
* - If the response wasn't intended for us, wait for another response
|
|
* from the dispatcher (rctx_next()).
|
|
* - If there is a problem with the responding server, set up another
|
|
* query to a different server (rctx_nextserver()).
|
|
* - If there is a problem that might be temporary or dependent on
|
|
* EDNS options, set up another query to the same server with changed
|
|
* options (rctx_resend()).
|
|
* - Shut down the fetch context.
|
|
*/
|
|
|
|
typedef struct respctx {
|
|
isc_task_t *task;
|
|
dns_dispatchevent_t *devent;
|
|
resquery_t *query;
|
|
fetchctx_t *fctx;
|
|
isc_result_t result;
|
|
unsigned int retryopts; /* updated options to pass to
|
|
* fctx_query() when resending */
|
|
|
|
dns_rdatatype_t type; /* type being sought (set to
|
|
* ANY if qtype was SIG or RRSIG) */
|
|
isc_boolean_t aa; /* authoritative answer? */
|
|
dns_trust_t trust; /* answer trust level */
|
|
isc_boolean_t chaining; /* CNAME/DNAME processing? */
|
|
isc_boolean_t next_server; /* give up, try the next server */
|
|
|
|
badnstype_t broken_type; /* type of name server problem */
|
|
isc_result_t broken_server;
|
|
|
|
isc_boolean_t get_nameservers; /* get a new NS rrset at zone cut? */
|
|
isc_boolean_t resend; /* resend this query? */
|
|
isc_boolean_t nextitem; /* invalid response; keep
|
|
* listening for the correct one */
|
|
isc_boolean_t truncated; /* response was truncated */
|
|
isc_boolean_t no_response; /* no response was received */
|
|
isc_boolean_t glue_in_answer; /* glue may be in the answer section */
|
|
isc_boolean_t ns_in_answer; /* NS may be in the answer section */
|
|
isc_boolean_t negative; /* is this a negative response? */
|
|
|
|
isc_stdtime_t now; /* time info */
|
|
isc_time_t tnow;
|
|
isc_time_t *finish;
|
|
|
|
unsigned int dname_labels;
|
|
unsigned int domain_labels; /* range of permissible number of
|
|
* labels in a DNAME */
|
|
|
|
dns_name_t *aname; /* answer name */
|
|
dns_rdataset_t *ardataset; /* answer rdataset */
|
|
|
|
dns_name_t *cname; /* CNAME name */
|
|
dns_rdataset_t *crdataset; /* CNAME rdataset */
|
|
|
|
dns_name_t *dname; /* DNAME name */
|
|
dns_rdataset_t *drdataset; /* DNAME rdataset */
|
|
|
|
dns_name_t *ns_name; /* NS name */
|
|
dns_rdataset_t *ns_rdataset; /* NS rdataset */
|
|
|
|
dns_name_t *soa_name; /* SOA name in a negative answer */
|
|
dns_name_t *ds_name; /* DS name in a negative answer */
|
|
|
|
dns_name_t *found_name; /* invalid name in negative response */
|
|
dns_rdatatype_t found_type; /* invalid type in negative response */
|
|
|
|
dns_rdataset_t *opt; /* OPT rdataset */
|
|
} respctx_t;
|
|
|
|
static void
|
|
rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent,
|
|
resquery_t *query, fetchctx_t *fctx, respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_answer_init(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_answer_scan(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_authority_positive(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_any(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_match(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_cname(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_dname(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_positive(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_authority_negative(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_authority_dnssec(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_additional(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_referral(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_answer_none(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_nextserver(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo,
|
|
isc_result_t result);
|
|
|
|
static void
|
|
rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo);
|
|
|
|
static void
|
|
rctx_next(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_chaseds(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, isc_result_t result);
|
|
|
|
static void
|
|
rctx_done(respctx_t *rctx, isc_result_t result);
|
|
|
|
static void
|
|
rctx_logpacket(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_opt(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_edns(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_parse(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_badserver(respctx_t *rctx, isc_result_t result);
|
|
|
|
static isc_result_t
|
|
rctx_answer(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_lameserver(respctx_t *rctx);
|
|
|
|
static isc_result_t
|
|
rctx_dispfail(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_delonly_zone(respctx_t *rctx);
|
|
|
|
static void
|
|
rctx_ncache(respctx_t *rctx);
|
|
|
|
|
|
/*%
|
|
* Increment resolver-related statistics counters.
|
|
*/
|
|
static inline void
|
|
inc_stats(dns_resolver_t *res, isc_statscounter_t counter) {
|
|
if (res->view->resstats != NULL)
|
|
isc_stats_increment(res->view->resstats, counter);
|
|
}
|
|
|
|
static inline void
|
|
dec_stats(dns_resolver_t *res, isc_statscounter_t counter) {
|
|
if (res->view->resstats != NULL)
|
|
isc_stats_decrement(res->view->resstats, counter);
|
|
}
|
|
|
|
static isc_result_t
|
|
valcreate(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_name_t *name,
|
|
dns_rdatatype_t type, dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset, unsigned int valoptions,
|
|
isc_task_t *task)
|
|
{
|
|
dns_validator_t *validator = NULL;
|
|
dns_valarg_t *valarg;
|
|
isc_result_t result;
|
|
|
|
valarg = isc_mem_get(fctx->mctx, sizeof(*valarg));
|
|
if (valarg == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
valarg->fctx = fctx;
|
|
valarg->addrinfo = addrinfo;
|
|
|
|
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, fctx->rmessage,
|
|
valoptions, task, validated, valarg,
|
|
&validator);
|
|
if (result == ISC_R_SUCCESS) {
|
|
inc_stats(fctx->res, dns_resstatscounter_val);
|
|
if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
|
|
INSIST(fctx->validator == NULL);
|
|
fctx->validator = validator;
|
|
}
|
|
ISC_LIST_APPEND(fctx->validators, validator, link);
|
|
} else
|
|
isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
|
|
return (result);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
|
|
dns_namereln_t namereln;
|
|
dns_rdata_rrsig_t rrsig;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
int order;
|
|
isc_result_t result;
|
|
unsigned int labels;
|
|
|
|
for (result = dns_rdataset_first(rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset)) {
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
namereln = dns_name_fullcompare(&rrsig.signer, &fctx->domain,
|
|
&order, &labels);
|
|
if (namereln == dns_namereln_subdomain)
|
|
return (ISC_TRUE);
|
|
dns_rdata_reset(&rdata);
|
|
}
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) {
|
|
dns_name_t *name;
|
|
dns_name_t *domain = &fctx->domain;
|
|
dns_rdataset_t *rdataset;
|
|
dns_rdatatype_t type;
|
|
isc_result_t result;
|
|
isc_boolean_t keep_auth = ISC_FALSE;
|
|
|
|
if (message->rcode == dns_rcode_nxdomain)
|
|
return (ISC_FALSE);
|
|
|
|
/*
|
|
* A DS RRset can appear anywhere in a zone, even for a delegation-only
|
|
* zone. So a response to an explicit query for this type should be
|
|
* excluded from delegation-only fixup.
|
|
*
|
|
* SOA, NS, and DNSKEY can only exist at a zone apex, so a postive
|
|
* response to a query for these types can never violate the
|
|
* delegation-only assumption: if the query name is below a
|
|
* zone cut, the response should normally be a referral, which should
|
|
* be accepted; if the query name is below a zone cut but the server
|
|
* happens to have authority for the zone of the query name, the
|
|
* response is a (non-referral) answer. But this does not violate
|
|
* delegation-only because the query name must be in a different zone
|
|
* due to the "apex-only" nature of these types. Note that if the
|
|
* remote server happens to have authority for a child zone of a
|
|
* delegation-only zone, we may still incorrectly "fix" the response
|
|
* with NXDOMAIN for queries for other types. Unfortunately it's
|
|
* generally impossible to differentiate this case from violation of
|
|
* the delegation-only assumption. Once the resolver learns the
|
|
* correct zone cut, possibly via a separate query for an "apex-only"
|
|
* type, queries for other types will be resolved correctly.
|
|
*
|
|
* A query for type ANY will be accepted if it hits an exceptional
|
|
* type above in the answer section as it should be from a child
|
|
* zone.
|
|
*
|
|
* Also accept answers with RRSIG records from the child zone.
|
|
* Direct queries for RRSIG records should not be answered from
|
|
* the parent zone.
|
|
*/
|
|
|
|
if (message->counts[DNS_SECTION_ANSWER] != 0 &&
|
|
(fctx->type == dns_rdatatype_ns ||
|
|
fctx->type == dns_rdatatype_ds ||
|
|
fctx->type == dns_rdatatype_soa ||
|
|
fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_dnskey)) {
|
|
result = dns_message_firstname(message, DNS_SECTION_ANSWER);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_ANSWER,
|
|
&name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link)) {
|
|
if (!dns_name_equal(name, &fctx->name))
|
|
continue;
|
|
type = rdataset->type;
|
|
/*
|
|
* RRsig from child?
|
|
*/
|
|
if (type == dns_rdatatype_rrsig &&
|
|
rrsig_fromchildzone(fctx, rdataset))
|
|
return (ISC_FALSE);
|
|
/*
|
|
* Direct query for apex records or DS.
|
|
*/
|
|
if (fctx->type == type &&
|
|
(type == dns_rdatatype_ds ||
|
|
type == dns_rdatatype_ns ||
|
|
type == dns_rdatatype_soa ||
|
|
type == dns_rdatatype_dnskey))
|
|
return (ISC_FALSE);
|
|
/*
|
|
* Indirect query for apex records or DS.
|
|
*/
|
|
if (fctx->type == dns_rdatatype_any &&
|
|
(type == dns_rdatatype_ns ||
|
|
type == dns_rdatatype_ds ||
|
|
type == dns_rdatatype_soa ||
|
|
type == dns_rdatatype_dnskey))
|
|
return (ISC_FALSE);
|
|
}
|
|
result = dns_message_nextname(message,
|
|
DNS_SECTION_ANSWER);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A NODATA response to a DS query?
|
|
*/
|
|
if (fctx->type == dns_rdatatype_ds &&
|
|
message->counts[DNS_SECTION_ANSWER] == 0)
|
|
return (ISC_FALSE);
|
|
|
|
/* Look for referral or indication of answer from child zone? */
|
|
if (message->counts[DNS_SECTION_AUTHORITY] == 0)
|
|
goto munge;
|
|
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link)) {
|
|
type = rdataset->type;
|
|
if (type == dns_rdatatype_soa &&
|
|
dns_name_equal(name, domain))
|
|
keep_auth = ISC_TRUE;
|
|
|
|
if (type != dns_rdatatype_ns &&
|
|
type != dns_rdatatype_soa &&
|
|
type != dns_rdatatype_rrsig)
|
|
continue;
|
|
|
|
if (type == dns_rdatatype_rrsig) {
|
|
if (rrsig_fromchildzone(fctx, rdataset))
|
|
return (ISC_FALSE);
|
|
else
|
|
continue;
|
|
}
|
|
|
|
/* NS or SOA records. */
|
|
if (dns_name_equal(name, domain)) {
|
|
/*
|
|
* If a query for ANY causes a negative
|
|
* response, we can be sure that this is
|
|
* an empty node. For other type of queries
|
|
* we cannot differentiate an empty node
|
|
* from a node that just doesn't have that
|
|
* type of record. We only accept the former
|
|
* case.
|
|
*/
|
|
if (message->counts[DNS_SECTION_ANSWER] == 0 &&
|
|
fctx->type == dns_rdatatype_any)
|
|
return (ISC_FALSE);
|
|
} else if (dns_name_issubdomain(name, domain)) {
|
|
/* Referral or answer from child zone. */
|
|
return (ISC_FALSE);
|
|
}
|
|
}
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
|
|
}
|
|
|
|
munge:
|
|
message->rcode = dns_rcode_nxdomain;
|
|
message->counts[DNS_SECTION_ANSWER] = 0;
|
|
if (!keep_auth)
|
|
message->counts[DNS_SECTION_AUTHORITY] = 0;
|
|
message->counts[DNS_SECTION_ADDITIONAL] = 0;
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
static inline isc_result_t
|
|
fctx_starttimer(fetchctx_t *fctx) {
|
|
/*
|
|
* Start the lifetime timer for fctx.
|
|
*
|
|
* This is also used for stopping the idle timer; in that
|
|
* case we must purge events already posted to ensure that
|
|
* no further idle events are delivered.
|
|
*/
|
|
return (isc_timer_reset(fctx->timer, isc_timertype_once,
|
|
&fctx->expires, NULL, ISC_TRUE));
|
|
}
|
|
|
|
static inline void
|
|
fctx_stoptimer(fetchctx_t *fctx) {
|
|
isc_result_t result;
|
|
|
|
/*
|
|
* We don't return a result if resetting the timer to inactive fails
|
|
* since there's nothing to be done about it. Resetting to inactive
|
|
* should never fail anyway, since the code as currently written
|
|
* cannot fail in that case.
|
|
*/
|
|
result = isc_timer_reset(fctx->timer, isc_timertype_inactive,
|
|
NULL, NULL, ISC_TRUE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
|
"isc_timer_reset(): %s",
|
|
isc_result_totext(result));
|
|
}
|
|
}
|
|
|
|
static inline isc_result_t
|
|
fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) {
|
|
/*
|
|
* Start the idle timer for fctx. The lifetime timer continues
|
|
* to be in effect.
|
|
*/
|
|
return (isc_timer_reset(fctx->timer, isc_timertype_once,
|
|
&fctx->expires, interval, ISC_FALSE));
|
|
}
|
|
|
|
/*
|
|
* Stopping the idle timer is equivalent to calling fctx_starttimer(), but
|
|
* we use fctx_stopidletimer for readability in the code below.
|
|
*/
|
|
#define fctx_stopidletimer fctx_starttimer
|
|
|
|
static inline void
|
|
resquery_destroy(resquery_t **queryp) {
|
|
dns_resolver_t *res;
|
|
isc_boolean_t empty;
|
|
resquery_t *query;
|
|
fetchctx_t *fctx;
|
|
unsigned int bucket;
|
|
|
|
REQUIRE(queryp != NULL);
|
|
query = *queryp;
|
|
REQUIRE(!ISC_LINK_LINKED(query, link));
|
|
|
|
INSIST(query->tcpsocket == NULL);
|
|
|
|
fctx = query->fctx;
|
|
res = fctx->res;
|
|
bucket = fctx->bucketnum;
|
|
|
|
fctx->nqueries--;
|
|
|
|
LOCK(&res->buckets[bucket].lock);
|
|
empty = fctx_decreference(query->fctx);
|
|
UNLOCK(&res->buckets[bucket].lock);
|
|
|
|
query->magic = 0;
|
|
isc_mem_put(query->mctx, query, sizeof(*query));
|
|
*queryp = NULL;
|
|
|
|
if (empty)
|
|
empty_bucket(res);
|
|
}
|
|
|
|
static void
|
|
fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp,
|
|
isc_time_t *finish, isc_boolean_t no_response,
|
|
isc_boolean_t age_untried)
|
|
{
|
|
fetchctx_t *fctx;
|
|
resquery_t *query;
|
|
unsigned int rtt, rttms;
|
|
unsigned int factor;
|
|
dns_adbfind_t *find;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
isc_socket_t *sock;
|
|
isc_stdtime_t now;
|
|
|
|
query = *queryp;
|
|
fctx = query->fctx;
|
|
|
|
FCTXTRACE("cancelquery");
|
|
|
|
REQUIRE(!RESQUERY_CANCELED(query));
|
|
|
|
query->attributes |= RESQUERY_ATTR_CANCELED;
|
|
|
|
/*
|
|
* Should we update the RTT?
|
|
*/
|
|
if (finish != NULL || no_response) {
|
|
if (finish != NULL) {
|
|
/*
|
|
* We have both the start and finish times for this
|
|
* packet, so we can compute a real RTT.
|
|
*/
|
|
rtt = (unsigned int)isc_time_microdiff(finish,
|
|
&query->start);
|
|
factor = DNS_ADB_RTTADJDEFAULT;
|
|
|
|
rttms = rtt / 1000;
|
|
if (rttms < DNS_RESOLVER_QRYRTTCLASS0) {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt0);
|
|
} else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt1);
|
|
} else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt2);
|
|
} else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt3);
|
|
} else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt4);
|
|
} else {
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_queryrtt5);
|
|
}
|
|
} else {
|
|
isc_uint32_t value;
|
|
isc_uint32_t mask;
|
|
|
|
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0)
|
|
dns_adb_ednsto(fctx->adb, query->addrinfo,
|
|
query->udpsize);
|
|
else
|
|
dns_adb_timeout(fctx->adb, query->addrinfo);
|
|
|
|
/*
|
|
* 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);
|
|
isc_random_get(&value);
|
|
if (query->addrinfo->srtt > 800000)
|
|
mask = 0x3fff;
|
|
else if (query->addrinfo->srtt > 400000)
|
|
mask = 0x7fff;
|
|
else if (query->addrinfo->srtt > 200000)
|
|
mask = 0xffff;
|
|
else if (query->addrinfo->srtt > 100000)
|
|
mask = 0x1ffff;
|
|
else if (query->addrinfo->srtt > 50000)
|
|
mask = 0x3ffff;
|
|
else if (query->addrinfo->srtt > 25000)
|
|
mask = 0x7ffff;
|
|
else
|
|
mask = 0xfffff;
|
|
|
|
/*
|
|
* Don't adjust timeout on EDNS queries unless we have
|
|
* seen a EDNS response.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 &&
|
|
!EDNSOK(query->addrinfo)) {
|
|
mask >>= 2;
|
|
}
|
|
|
|
rtt = query->addrinfo->srtt + (value & mask);
|
|
if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US)
|
|
rtt = MAX_SINGLE_QUERY_TIMEOUT_US;
|
|
|
|
/*
|
|
* Replace the current RTT with our value.
|
|
*/
|
|
factor = DNS_ADB_RTTADJREPLACE;
|
|
}
|
|
|
|
dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor);
|
|
}
|
|
dns_adb_endudpfetch(fctx->adb, query->addrinfo);
|
|
|
|
/*
|
|
* Age RTTs of servers not tried.
|
|
*/
|
|
isc_stdtime_get(&now);
|
|
if (finish != NULL || age_untried)
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
if (UNMARKED(addrinfo))
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDFIND(fctx))
|
|
for (find = ISC_LIST_HEAD(fctx->finds);
|
|
find != NULL;
|
|
find = ISC_LIST_NEXT(find, publink))
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
if (UNMARKED(addrinfo))
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
|
|
if ((finish != NULL || age_untried) && TRIEDALT(fctx)) {
|
|
for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
if (UNMARKED(addrinfo))
|
|
dns_adb_agesrtt(fctx->adb, addrinfo, now);
|
|
for (find = ISC_LIST_HEAD(fctx->altfinds);
|
|
find != NULL;
|
|
find = ISC_LIST_NEXT(find, publink))
|
|
for (addrinfo = ISC_LIST_HEAD(find->list);
|
|
addrinfo != NULL;
|
|
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
|
if (UNMARKED(addrinfo))
|
|
dns_adb_agesrtt(fctx->adb, addrinfo,
|
|
now);
|
|
}
|
|
|
|
/*
|
|
* Check for any outstanding socket events. If they exist, cancel
|
|
* them and let the event handlers finish the cleanup. The resolver
|
|
* only needs to worry about managing the connect and send events;
|
|
* the dispatcher manages the recv events.
|
|
*/
|
|
if (RESQUERY_CONNECTING(query)) {
|
|
/*
|
|
* Cancel the connect.
|
|
*/
|
|
if (query->tcpsocket != NULL) {
|
|
isc_socket_cancel(query->tcpsocket, NULL,
|
|
ISC_SOCKCANCEL_CONNECT);
|
|
} else if (query->dispentry != NULL) {
|
|
INSIST(query->exclusivesocket);
|
|
sock = dns_dispatch_getentrysocket(query->dispentry);
|
|
if (sock != NULL)
|
|
isc_socket_cancel(sock, NULL,
|
|
ISC_SOCKCANCEL_CONNECT);
|
|
}
|
|
} else if (RESQUERY_SENDING(query)) {
|
|
/*
|
|
* Cancel the pending send.
|
|
*/
|
|
if (query->exclusivesocket && query->dispentry != NULL)
|
|
sock = dns_dispatch_getentrysocket(query->dispentry);
|
|
else
|
|
sock = dns_dispatch_getsocket(query->dispatch);
|
|
if (sock != NULL)
|
|
isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND);
|
|
}
|
|
|
|
if (query->dispentry != NULL)
|
|
dns_dispatch_removeresponse(&query->dispentry, deventp);
|
|
|
|
ISC_LIST_UNLINK(fctx->queries, query, link);
|
|
|
|
if (query->tsig != NULL)
|
|
isc_buffer_free(&query->tsig);
|
|
|
|
if (query->tsigkey != NULL)
|
|
dns_tsigkey_detach(&query->tsigkey);
|
|
|
|
if (query->dispatch != NULL)
|
|
dns_dispatch_detach(&query->dispatch);
|
|
|
|
if (! (RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query)))
|
|
/*
|
|
* It's safe to destroy the query now.
|
|
*/
|
|
resquery_destroy(&query);
|
|
}
|
|
|
|
static void
|
|
fctx_cancelqueries(fetchctx_t *fctx, isc_boolean_t no_response,
|
|
isc_boolean_t age_untried)
|
|
{
|
|
resquery_t *query, *next_query;
|
|
|
|
FCTXTRACE("cancelqueries");
|
|
|
|
for (query = ISC_LIST_HEAD(fctx->queries);
|
|
query != NULL;
|
|
query = next_query) {
|
|
next_query = ISC_LIST_NEXT(query, link);
|
|
fctx_cancelquery(&query, NULL, NULL, no_response,
|
|
age_untried);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_cleanupfinds(fetchctx_t *fctx) {
|
|
dns_adbfind_t *find, *next_find;
|
|
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
for (find = ISC_LIST_HEAD(fctx->finds);
|
|
find != NULL;
|
|
find = next_find)
|
|
{
|
|
next_find = ISC_LIST_NEXT(find, publink);
|
|
ISC_LIST_UNLINK(fctx->finds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
}
|
|
fctx->find = NULL;
|
|
}
|
|
|
|
static void
|
|
fctx_cleanupaltfinds(fetchctx_t *fctx) {
|
|
dns_adbfind_t *find, *next_find;
|
|
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
for (find = ISC_LIST_HEAD(fctx->altfinds);
|
|
find != NULL;
|
|
find = next_find)
|
|
{
|
|
next_find = ISC_LIST_NEXT(find, publink);
|
|
ISC_LIST_UNLINK(fctx->altfinds, find, publink);
|
|
dns_adb_destroyfind(&find);
|
|
}
|
|
fctx->altfind = NULL;
|
|
}
|
|
|
|
static void
|
|
fctx_cleanupforwaddrs(fetchctx_t *fctx) {
|
|
dns_adbaddrinfo_t *addr, *next_addr;
|
|
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
for (addr = ISC_LIST_HEAD(fctx->forwaddrs);
|
|
addr != NULL;
|
|
addr = next_addr)
|
|
{
|
|
next_addr = ISC_LIST_NEXT(addr, publink);
|
|
ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_cleanupaltaddrs(fetchctx_t *fctx) {
|
|
dns_adbaddrinfo_t *addr, *next_addr;
|
|
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
|
|
for (addr = ISC_LIST_HEAD(fctx->altaddrs);
|
|
addr != NULL;
|
|
addr = next_addr)
|
|
{
|
|
next_addr = ISC_LIST_NEXT(addr, publink);
|
|
ISC_LIST_UNLINK(fctx->altaddrs, addr, publink);
|
|
dns_adb_freeaddrinfo(fctx->adb, &addr);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
fctx_stopqueries(fetchctx_t *fctx, isc_boolean_t no_response,
|
|
isc_boolean_t age_untried)
|
|
{
|
|
FCTXTRACE("stopqueries");
|
|
fctx_cancelqueries(fctx, no_response, age_untried);
|
|
fctx_stoptimer(fctx);
|
|
}
|
|
|
|
static inline void
|
|
fctx_cleanupall(fetchctx_t *fctx) {
|
|
fctx_cleanupfinds(fctx);
|
|
fctx_cleanupaltfinds(fctx);
|
|
fctx_cleanupforwaddrs(fctx);
|
|
fctx_cleanupaltaddrs(fctx);
|
|
}
|
|
|
|
static void
|
|
fcount_logspill(fetchctx_t *fctx, fctxcount_t *counter) {
|
|
char dbuf[DNS_NAME_FORMATSIZE];
|
|
isc_stdtime_t now;
|
|
|
|
if (! isc_log_wouldlog(dns_lctx, ISC_LOG_INFO))
|
|
return;
|
|
|
|
isc_stdtime_get(&now);
|
|
if (counter->logged > now - 60)
|
|
return;
|
|
|
|
dns_name_format(&fctx->domain, dbuf, sizeof(dbuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"too many simultaneous fetches for %s "
|
|
"(allowed %d spilled %d)",
|
|
dbuf, counter->allowed, counter->dropped);
|
|
|
|
counter->logged = now;
|
|
}
|
|
|
|
static isc_result_t
|
|
fcount_incr(fetchctx_t *fctx, isc_boolean_t force) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
zonebucket_t *dbucket;
|
|
fctxcount_t *counter;
|
|
unsigned int bucketnum, spill;
|
|
|
|
REQUIRE(fctx != NULL);
|
|
REQUIRE(fctx->res != NULL);
|
|
|
|
INSIST(fctx->dbucketnum == RES_NOBUCKET);
|
|
bucketnum = dns_name_fullhash(&fctx->domain, ISC_FALSE)
|
|
% RES_DOMAIN_BUCKETS;
|
|
|
|
LOCK(&fctx->res->lock);
|
|
spill = fctx->res->zspill;
|
|
UNLOCK(&fctx->res->lock);
|
|
|
|
dbucket = &fctx->res->dbuckets[bucketnum];
|
|
|
|
LOCK(&dbucket->lock);
|
|
for (counter = ISC_LIST_HEAD(dbucket->list);
|
|
counter != NULL;
|
|
counter = ISC_LIST_NEXT(counter, link))
|
|
{
|
|
if (dns_name_equal(counter->domain, &fctx->domain))
|
|
break;
|
|
}
|
|
|
|
if (counter == NULL) {
|
|
counter = isc_mem_get(dbucket->mctx, sizeof(fctxcount_t));
|
|
if (counter == NULL)
|
|
result = ISC_R_NOMEMORY;
|
|
else {
|
|
ISC_LINK_INIT(counter, link);
|
|
counter->count = 1;
|
|
counter->logged = 0;
|
|
counter->allowed = 1;
|
|
counter->dropped = 0;
|
|
counter->domain =
|
|
dns_fixedname_initname(&counter->fdname);
|
|
dns_name_copy(&fctx->domain, counter->domain, NULL);
|
|
ISC_LIST_APPEND(dbucket->list, counter, link);
|
|
}
|
|
} else {
|
|
if (!force && spill != 0 && counter->count >= spill) {
|
|
counter->dropped++;
|
|
fcount_logspill(fctx, counter);
|
|
result = ISC_R_QUOTA;
|
|
} else {
|
|
counter->count++;
|
|
counter->allowed++;
|
|
}
|
|
}
|
|
UNLOCK(&dbucket->lock);
|
|
|
|
if (result == ISC_R_SUCCESS)
|
|
fctx->dbucketnum = bucketnum;
|
|
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
fcount_decr(fetchctx_t *fctx) {
|
|
zonebucket_t *dbucket;
|
|
fctxcount_t *counter;
|
|
|
|
REQUIRE(fctx != NULL);
|
|
|
|
if (fctx->dbucketnum == RES_NOBUCKET)
|
|
return;
|
|
|
|
dbucket = &fctx->res->dbuckets[fctx->dbucketnum];
|
|
|
|
LOCK(&dbucket->lock);
|
|
for (counter = ISC_LIST_HEAD(dbucket->list);
|
|
counter != NULL;
|
|
counter = ISC_LIST_NEXT(counter, link))
|
|
{
|
|
if (dns_name_equal(counter->domain, &fctx->domain))
|
|
break;
|
|
}
|
|
|
|
if (counter != NULL) {
|
|
INSIST(counter->count != 0);
|
|
counter->count--;
|
|
fctx->dbucketnum = RES_NOBUCKET;
|
|
|
|
if (counter->count == 0) {
|
|
ISC_LIST_UNLINK(dbucket->list, counter, link);
|
|
isc_mem_put(dbucket->mctx, counter, sizeof(*counter));
|
|
}
|
|
}
|
|
|
|
UNLOCK(&dbucket->lock);
|
|
}
|
|
|
|
static inline void
|
|
fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) {
|
|
dns_fetchevent_t *event, *next_event;
|
|
isc_task_t *task;
|
|
unsigned int count = 0;
|
|
isc_interval_t i;
|
|
isc_boolean_t logit = ISC_FALSE;
|
|
isc_time_t now;
|
|
unsigned int old_spillat;
|
|
unsigned int new_spillat = 0; /* initialized to silence
|
|
compiler warnings */
|
|
|
|
/*
|
|
* Caller must be holding the appropriate bucket lock.
|
|
*/
|
|
REQUIRE(fctx->state == fetchstate_done);
|
|
|
|
FCTXTRACE("sendevents");
|
|
|
|
/*
|
|
* Keep some record of fetch result for logging later (if required).
|
|
*/
|
|
fctx->result = result;
|
|
fctx->exitline = line;
|
|
TIME_NOW(&now);
|
|
fctx->duration = isc_time_microdiff(&now, &fctx->start);
|
|
|
|
for (event = ISC_LIST_HEAD(fctx->events);
|
|
event != NULL;
|
|
event = next_event) {
|
|
next_event = ISC_LIST_NEXT(event, ev_link);
|
|
ISC_LIST_UNLINK(fctx->events, event, ev_link);
|
|
task = event->ev_sender;
|
|
event->ev_sender = fctx;
|
|
event->vresult = fctx->vresult;
|
|
if (!HAVE_ANSWER(fctx))
|
|
event->result = result;
|
|
|
|
INSIST(event->result != ISC_R_SUCCESS ||
|
|
dns_rdataset_isassociated(event->rdataset) ||
|
|
fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig);
|
|
|
|
/*
|
|
* Negative results must be indicated in event->result.
|
|
*/
|
|
if (dns_rdataset_isassociated(event->rdataset) &&
|
|
NEGATIVE(event->rdataset)) {
|
|
INSIST(event->result == DNS_R_NCACHENXDOMAIN ||
|
|
event->result == DNS_R_NCACHENXRRSET);
|
|
}
|
|
|
|
isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event));
|
|
count++;
|
|
}
|
|
|
|
if ((fctx->attributes & FCTX_ATTR_HAVEANSWER) != 0 &&
|
|
fctx->spilled &&
|
|
(count < fctx->res->spillatmax || fctx->res->spillatmax == 0)) {
|
|
LOCK(&fctx->res->lock);
|
|
if (count == fctx->res->spillat && !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 = ISC_TRUE;
|
|
}
|
|
isc_interval_set(&i, 20 * 60, 0);
|
|
result = isc_timer_reset(fctx->res->spillattimer,
|
|
isc_timertype_ticker, NULL,
|
|
&i, ISC_TRUE);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
UNLOCK(&fctx->res->lock);
|
|
if (logit)
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"clients-per-query increased to %u",
|
|
new_spillat);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
log_edns(fetchctx_t *fctx) {
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
|
|
if (fctx->reason == NULL)
|
|
return;
|
|
|
|
/*
|
|
* We do not know if fctx->domain is the actual domain the record
|
|
* lives in or a parent domain so we have a '?' after it.
|
|
*/
|
|
dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_EDNS_DISABLED,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"success resolving '%s' (in '%s'?) after %s",
|
|
fctx->info, domainbuf, fctx->reason);
|
|
|
|
fctx->reason = NULL;
|
|
}
|
|
|
|
static void
|
|
fctx_done(fetchctx_t *fctx, isc_result_t result, int line) {
|
|
dns_resolver_t *res;
|
|
isc_boolean_t no_response = ISC_FALSE;
|
|
isc_boolean_t age_untried = ISC_FALSE;
|
|
|
|
REQUIRE(line >= 0);
|
|
|
|
FCTXTRACE("done");
|
|
|
|
res = fctx->res;
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
/*%
|
|
* Log any deferred EDNS timeout messages.
|
|
*/
|
|
log_edns(fctx);
|
|
no_response = ISC_TRUE;
|
|
} else if (result == ISC_R_TIMEDOUT)
|
|
age_untried = ISC_TRUE;
|
|
|
|
fctx->reason = NULL;
|
|
|
|
fctx_stopqueries(fctx, no_response, age_untried);
|
|
|
|
LOCK(&res->buckets[fctx->bucketnum].lock);
|
|
|
|
fctx->state = fetchstate_done;
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
fctx_sendevents(fctx, result, line);
|
|
|
|
UNLOCK(&res->buckets[fctx->bucketnum].lock);
|
|
}
|
|
|
|
static void
|
|
process_sendevent(resquery_t *query, isc_event_t *event) {
|
|
isc_socketevent_t *sevent = (isc_socketevent_t *)event;
|
|
isc_boolean_t destroy_query = ISC_FALSE;
|
|
isc_boolean_t retry = ISC_FALSE;
|
|
isc_result_t result;
|
|
fetchctx_t *fctx;
|
|
|
|
fctx = query->fctx;
|
|
|
|
if (RESQUERY_CANCELED(query)) {
|
|
if (query->sends == 0 && query->connects == 0) {
|
|
/*
|
|
* This query was canceled while the
|
|
* isc_socket_sendto/connect() was in progress.
|
|
*/
|
|
if (query->tcpsocket != NULL)
|
|
isc_socket_detach(&query->tcpsocket);
|
|
destroy_query = ISC_TRUE;
|
|
}
|
|
} else {
|
|
switch (sevent->result) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
|
|
case ISC_R_HOSTUNREACH:
|
|
case ISC_R_NETUNREACH:
|
|
case ISC_R_NOPERM:
|
|
case ISC_R_ADDRNOTAVAIL:
|
|
case ISC_R_CONNREFUSED:
|
|
FCTXTRACE3("query canceled in sendevent(): "
|
|
"no route to host; no response",
|
|
sevent->result);
|
|
|
|
/*
|
|
* No route to remote.
|
|
*/
|
|
add_bad(fctx, query->addrinfo, sevent->result,
|
|
badns_unreachable);
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_TRUE,
|
|
ISC_FALSE);
|
|
retry = ISC_TRUE;
|
|
break;
|
|
|
|
default:
|
|
FCTXTRACE3("query canceled in sendevent() due to "
|
|
"unexpected event result; responding",
|
|
sevent->result);
|
|
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE,
|
|
ISC_FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (event->ev_type == ISC_SOCKEVENT_CONNECT)
|
|
isc_event_free(&event);
|
|
|
|
if (retry) {
|
|
/*
|
|
* Behave as if the idle timer has expired. For TCP
|
|
* this may not actually reflect the latest timer.
|
|
*/
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
result = fctx_stopidletimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
else
|
|
fctx_try(fctx, ISC_TRUE, ISC_FALSE);
|
|
}
|
|
|
|
if (destroy_query)
|
|
resquery_destroy(&query);
|
|
}
|
|
|
|
static void
|
|
resquery_udpconnected(isc_task_t *task, isc_event_t *event) {
|
|
resquery_t *query = event->ev_arg;
|
|
|
|
REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
|
|
|
|
QTRACE("udpconnected");
|
|
|
|
UNUSED(task);
|
|
|
|
INSIST(RESQUERY_CONNECTING(query));
|
|
|
|
query->connects--;
|
|
|
|
process_sendevent(query, event);
|
|
}
|
|
|
|
static void
|
|
resquery_senddone(isc_task_t *task, isc_event_t *event) {
|
|
resquery_t *query = event->ev_arg;
|
|
|
|
REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE);
|
|
|
|
QTRACE("senddone");
|
|
|
|
/*
|
|
* XXXRTH
|
|
*
|
|
* Currently we don't wait for the senddone event before retrying
|
|
* a query. This means that if we get really behind, we may end
|
|
* up doing extra work!
|
|
*/
|
|
|
|
UNUSED(task);
|
|
|
|
INSIST(RESQUERY_SENDING(query));
|
|
|
|
query->sends--;
|
|
|
|
process_sendevent(query, event);
|
|
}
|
|
|
|
static inline isc_result_t
|
|
fctx_addopt(dns_message_t *message, unsigned int version,
|
|
isc_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 inline void
|
|
fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) {
|
|
unsigned int seconds;
|
|
unsigned int us;
|
|
|
|
us = fctx->res->retryinterval * 1000;
|
|
/*
|
|
* Exponential backoff after the first few tries.
|
|
*/
|
|
if (fctx->restarts > fctx->res->nonbackofftries) {
|
|
int shift = fctx->restarts - fctx->res->nonbackofftries;
|
|
if (shift > 6)
|
|
shift = 6;
|
|
us <<= shift;
|
|
}
|
|
|
|
/*
|
|
* Add a fudge factor to the expected rtt based on the current
|
|
* estimate.
|
|
*/
|
|
if (rtt < 50000)
|
|
rtt += 50000;
|
|
else if (rtt < 100000)
|
|
rtt += 100000;
|
|
else
|
|
rtt += 200000;
|
|
|
|
/*
|
|
* Always wait for at least the expected rtt.
|
|
*/
|
|
if (us < rtt)
|
|
us = rtt;
|
|
|
|
/*
|
|
* But don't ever wait for more than 10 seconds.
|
|
*/
|
|
if (us > MAX_SINGLE_QUERY_TIMEOUT_US)
|
|
us = MAX_SINGLE_QUERY_TIMEOUT_US;
|
|
|
|
seconds = us / US_PER_SEC;
|
|
us -= seconds * US_PER_SEC;
|
|
isc_interval_set(&fctx->interval, seconds, us * 1000);
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
|
|
unsigned int options)
|
|
{
|
|
dns_resolver_t *res;
|
|
isc_task_t *task;
|
|
isc_result_t result;
|
|
resquery_t *query;
|
|
isc_sockaddr_t addr;
|
|
isc_boolean_t have_addr = ISC_FALSE;
|
|
unsigned int srtt;
|
|
isc_dscp_t dscp = -1;
|
|
|
|
FCTXTRACE("query");
|
|
|
|
res = fctx->res;
|
|
task = res->buckets[fctx->bucketnum].task;
|
|
|
|
srtt = addrinfo->srtt;
|
|
|
|
/*
|
|
* A forwarder needs to make multiple queries. Give it at least
|
|
* a second to do these in.
|
|
*/
|
|
if (ISFORWARDER(addrinfo) && srtt < 1000000)
|
|
srtt = 1000000;
|
|
|
|
fctx_setretryinterval(fctx, srtt);
|
|
result = fctx_startidletimer(fctx, &fctx->interval);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
INSIST(ISC_LIST_EMPTY(fctx->validators));
|
|
|
|
dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE);
|
|
|
|
query = isc_mem_get(fctx->mctx, sizeof(*query));
|
|
if (query == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto stop_idle_timer;
|
|
}
|
|
query->mctx = fctx->mctx;
|
|
query->options = options;
|
|
query->attributes = 0;
|
|
query->sends = 0;
|
|
query->connects = 0;
|
|
query->dscp = addrinfo->dscp;
|
|
query->udpsize = 0;
|
|
/*
|
|
* Note that the caller MUST guarantee that 'addrinfo' will remain
|
|
* valid until this query is canceled.
|
|
*/
|
|
query->addrinfo = addrinfo;
|
|
TIME_NOW(&query->start);
|
|
|
|
/*
|
|
* If this is a TCP query, then we need to make a socket and
|
|
* a dispatch for it here. Otherwise we use the resolver's
|
|
* shared dispatch.
|
|
*/
|
|
query->dispatchmgr = res->dispatchmgr;
|
|
query->dispatch = NULL;
|
|
query->exclusivesocket = ISC_FALSE;
|
|
query->tcpsocket = NULL;
|
|
if (res->view->peers != NULL) {
|
|
dns_peer_t *peer = NULL;
|
|
isc_netaddr_t dstip;
|
|
isc_boolean_t usetcp = ISC_FALSE;
|
|
isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr);
|
|
result = dns_peerlist_peerbyaddr(res->view->peers,
|
|
&dstip, &peer);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_peer_getquerysource(peer, &addr);
|
|
if (result == ISC_R_SUCCESS)
|
|
have_addr = ISC_TRUE;
|
|
result = dns_peer_getquerydscp(peer, &dscp);
|
|
if (result == ISC_R_SUCCESS)
|
|
query->dscp = dscp;
|
|
result = dns_peer_getforcetcp(peer, &usetcp);
|
|
if (result == ISC_R_SUCCESS && usetcp)
|
|
query->options |= DNS_FETCHOPT_TCP;
|
|
}
|
|
}
|
|
|
|
dscp = -1;
|
|
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
|
|
int pf;
|
|
|
|
pf = isc_sockaddr_pf(&addrinfo->sockaddr);
|
|
if (!have_addr) {
|
|
switch (pf) {
|
|
case PF_INET:
|
|
result = dns_dispatch_getlocaladdress(
|
|
res->dispatches4->dispatches[0],
|
|
&addr);
|
|
dscp = dns_resolver_getquerydscp4(fctx->res);
|
|
break;
|
|
case PF_INET6:
|
|
result = dns_dispatch_getlocaladdress(
|
|
res->dispatches6->dispatches[0],
|
|
&addr);
|
|
dscp = dns_resolver_getquerydscp6(fctx->res);
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
break;
|
|
}
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_query;
|
|
}
|
|
isc_sockaddr_setport(&addr, 0);
|
|
if (query->dscp == -1)
|
|
query->dscp = dscp;
|
|
|
|
result = isc_socket_create(res->socketmgr, pf,
|
|
isc_sockettype_tcp,
|
|
&query->tcpsocket);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_query;
|
|
|
|
#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT
|
|
result = isc_socket_bind(query->tcpsocket, &addr, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_socket;
|
|
#endif
|
|
/*
|
|
* A dispatch will be created once the connect succeeds.
|
|
*/
|
|
} else {
|
|
if (have_addr) {
|
|
unsigned int attrs, attrmask;
|
|
attrs = DNS_DISPATCHATTR_UDP;
|
|
switch (isc_sockaddr_pf(&addr)) {
|
|
case AF_INET:
|
|
attrs |= DNS_DISPATCHATTR_IPV4;
|
|
dscp = dns_resolver_getquerydscp4(fctx->res);
|
|
break;
|
|
case AF_INET6:
|
|
attrs |= DNS_DISPATCHATTR_IPV6;
|
|
dscp = dns_resolver_getquerydscp6(fctx->res);
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
goto cleanup_query;
|
|
}
|
|
attrmask = DNS_DISPATCHATTR_UDP;
|
|
attrmask |= DNS_DISPATCHATTR_TCP;
|
|
attrmask |= DNS_DISPATCHATTR_IPV4;
|
|
attrmask |= DNS_DISPATCHATTR_IPV6;
|
|
result = dns_dispatch_getudp(res->dispatchmgr,
|
|
res->socketmgr,
|
|
res->taskmgr, &addr,
|
|
4096, 20000, 32768, 16411,
|
|
16433, attrs, attrmask,
|
|
&query->dispatch);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_query;
|
|
} else {
|
|
switch (isc_sockaddr_pf(&addrinfo->sockaddr)) {
|
|
case PF_INET:
|
|
dns_dispatch_attach(
|
|
dns_resolver_dispatchv4(res),
|
|
&query->dispatch);
|
|
query->exclusivesocket = res->exclusivev4;
|
|
dscp = dns_resolver_getquerydscp4(fctx->res);
|
|
break;
|
|
case PF_INET6:
|
|
dns_dispatch_attach(
|
|
dns_resolver_dispatchv6(res),
|
|
&query->dispatch);
|
|
query->exclusivesocket = res->exclusivev6;
|
|
dscp = dns_resolver_getquerydscp6(fctx->res);
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
goto cleanup_query;
|
|
}
|
|
}
|
|
|
|
if (query->dscp == -1)
|
|
query->dscp = dscp;
|
|
/*
|
|
* We should always have a valid dispatcher here. If we
|
|
* don't support a protocol family, then its dispatcher
|
|
* will be NULL, but we shouldn't be finding addresses for
|
|
* protocol types we don't support, so the dispatcher
|
|
* we found should never be NULL.
|
|
*/
|
|
INSIST(query->dispatch != NULL);
|
|
}
|
|
|
|
query->dispentry = NULL;
|
|
query->fctx = fctx; /* reference added by caller */
|
|
query->tsig = NULL;
|
|
query->tsigkey = NULL;
|
|
ISC_LINK_INIT(query, link);
|
|
query->magic = QUERY_MAGIC;
|
|
|
|
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
|
|
/*
|
|
* Connect to the remote server.
|
|
*
|
|
* XXXRTH Should we attach to the socket?
|
|
*/
|
|
if (query->dscp != -1)
|
|
isc_socket_dscp(query->tcpsocket, query->dscp);
|
|
result = isc_socket_connect(query->tcpsocket,
|
|
&addrinfo->sockaddr, task,
|
|
resquery_connected, query);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_socket;
|
|
query->connects++;
|
|
QTRACE("connecting via TCP");
|
|
} else {
|
|
if (dns_adbentry_overquota(addrinfo->entry))
|
|
goto cleanup_dispatch;
|
|
|
|
/* Inform the ADB that we're starting a fetch */
|
|
dns_adb_beginudpfetch(fctx->adb, addrinfo);
|
|
|
|
result = resquery_send(query);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_dispatch;
|
|
}
|
|
|
|
fctx->querysent++;
|
|
|
|
ISC_LIST_APPEND(fctx->queries, query, link);
|
|
query->fctx->nqueries++;
|
|
if (isc_sockaddr_pf(&addrinfo->sockaddr) == PF_INET)
|
|
inc_stats(res, dns_resstatscounter_queryv4);
|
|
else
|
|
inc_stats(res, dns_resstatscounter_queryv6);
|
|
if (res->view->resquerystats != NULL)
|
|
dns_rdatatypestats_increment(res->view->resquerystats,
|
|
fctx->type);
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup_socket:
|
|
isc_socket_detach(&query->tcpsocket);
|
|
|
|
cleanup_dispatch:
|
|
if (query->dispatch != NULL)
|
|
dns_dispatch_detach(&query->dispatch);
|
|
|
|
cleanup_query:
|
|
if (query->connects == 0) {
|
|
query->magic = 0;
|
|
isc_mem_put(fctx->mctx, query, sizeof(*query));
|
|
}
|
|
|
|
stop_idle_timer:
|
|
RUNTIME_CHECK(fctx_stopidletimer(fctx) == ISC_R_SUCCESS);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
isc_sockaddr_t *sa;
|
|
|
|
for (sa = ISC_LIST_HEAD(fctx->bad_edns);
|
|
sa != NULL;
|
|
sa = ISC_LIST_NEXT(sa, link)) {
|
|
if (isc_sockaddr_equal(sa, address))
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static void
|
|
add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
isc_sockaddr_t *sa;
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (fuzzing_resolver)
|
|
return;
|
|
#endif
|
|
if (bad_edns(fctx, address))
|
|
return;
|
|
|
|
sa = isc_mem_get(fctx->mctx, sizeof(*sa));
|
|
if (sa == NULL)
|
|
return;
|
|
|
|
*sa = *address;
|
|
ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link);
|
|
}
|
|
|
|
static struct tried *
|
|
triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
struct tried *tried;
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns);
|
|
tried != NULL;
|
|
tried = ISC_LIST_NEXT(tried, link)) {
|
|
if (isc_sockaddr_equal(&tried->addr, address))
|
|
return (tried);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
struct tried *tried;
|
|
|
|
tried = triededns(fctx, address);
|
|
if (tried != NULL) {
|
|
tried->count++;
|
|
return;
|
|
}
|
|
|
|
tried = isc_mem_get(fctx->mctx, sizeof(*tried));
|
|
if (tried == NULL)
|
|
return;
|
|
|
|
tried->addr = *address;
|
|
tried->count = 1;
|
|
ISC_LIST_INITANDAPPEND(fctx->edns, tried, link);
|
|
}
|
|
|
|
static struct tried *
|
|
triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
struct tried *tried;
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns512);
|
|
tried != NULL;
|
|
tried = ISC_LIST_NEXT(tried, link)) {
|
|
if (isc_sockaddr_equal(&tried->addr, address))
|
|
return (tried);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) {
|
|
struct tried *tried;
|
|
|
|
tried = triededns512(fctx, address);
|
|
if (tried != NULL) {
|
|
tried->count++;
|
|
return;
|
|
}
|
|
|
|
tried = isc_mem_get(fctx->mctx, sizeof(*tried));
|
|
if (tried == NULL)
|
|
return;
|
|
|
|
tried->addr = *address;
|
|
tried->count = 1;
|
|
ISC_LIST_INITANDAPPEND(fctx->edns512, tried, link);
|
|
}
|
|
|
|
static void
|
|
compute_cc(resquery_t *query, unsigned char *cookie, size_t len) {
|
|
#ifdef AES_CC
|
|
unsigned char digest[ISC_AES_BLOCK_LENGTH];
|
|
unsigned char input[16];
|
|
isc_netaddr_t netaddr;
|
|
unsigned int i;
|
|
|
|
INSIST(len >= 8U);
|
|
|
|
isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr);
|
|
switch (netaddr.family) {
|
|
case AF_INET:
|
|
memmove(input, (unsigned char *)&netaddr.type.in, 4);
|
|
memset(input + 4, 0, 12);
|
|
break;
|
|
case AF_INET6:
|
|
memmove(input, (unsigned char *)&netaddr.type.in6, 16);
|
|
break;
|
|
}
|
|
isc_aes128_crypt(query->fctx->res->view->secret, input, digest);
|
|
for (i = 0; i < 8; i++)
|
|
digest[i] ^= digest[i + 8];
|
|
memmove(cookie, digest, 8);
|
|
#endif
|
|
#ifdef HMAC_SHA1_CC
|
|
unsigned char digest[ISC_SHA1_DIGESTLENGTH];
|
|
isc_netaddr_t netaddr;
|
|
isc_hmacsha1_t hmacsha1;
|
|
|
|
INSIST(len >= 8U);
|
|
|
|
isc_hmacsha1_init(&hmacsha1, query->fctx->res->view->secret,
|
|
ISC_SHA1_DIGESTLENGTH);
|
|
isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr);
|
|
switch (netaddr.family) {
|
|
case AF_INET:
|
|
isc_hmacsha1_update(&hmacsha1,
|
|
(unsigned char *)&netaddr.type.in, 4);
|
|
break;
|
|
case AF_INET6:
|
|
isc_hmacsha1_update(&hmacsha1,
|
|
(unsigned char *)&netaddr.type.in6, 16);
|
|
break;
|
|
}
|
|
isc_hmacsha1_sign(&hmacsha1, digest, sizeof(digest));
|
|
memmove(cookie, digest, 8);
|
|
isc_hmacsha1_invalidate(&hmacsha1);
|
|
#endif
|
|
#ifdef HMAC_SHA256_CC
|
|
unsigned char digest[ISC_SHA256_DIGESTLENGTH];
|
|
isc_netaddr_t netaddr;
|
|
isc_hmacsha256_t hmacsha256;
|
|
|
|
INSIST(len >= 8U);
|
|
|
|
isc_hmacsha256_init(&hmacsha256, query->fctx->res->view->secret,
|
|
ISC_SHA256_DIGESTLENGTH);
|
|
isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr);
|
|
switch (netaddr.family) {
|
|
case AF_INET:
|
|
isc_hmacsha256_update(&hmacsha256,
|
|
(unsigned char *)&netaddr.type.in, 4);
|
|
break;
|
|
case AF_INET6:
|
|
isc_hmacsha256_update(&hmacsha256,
|
|
(unsigned char *)&netaddr.type.in6, 16);
|
|
break;
|
|
}
|
|
isc_hmacsha256_sign(&hmacsha256, digest, sizeof(digest));
|
|
memmove(cookie, digest, 8);
|
|
isc_hmacsha256_invalidate(&hmacsha256);
|
|
#endif
|
|
}
|
|
|
|
static isc_result_t
|
|
issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
|
|
isc_stdtime_t now, isc_boolean_t checknta,
|
|
isc_boolean_t *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, issecure));
|
|
}
|
|
|
|
static isc_boolean_t
|
|
wouldvalidate(fetchctx_t *fctx) {
|
|
isc_boolean_t secure_domain;
|
|
isc_result_t result;
|
|
isc_stdtime_t now;
|
|
|
|
if (!fctx->res->view->enablevalidation)
|
|
return (ISC_FALSE);
|
|
|
|
if (fctx->res->view->dlv != NULL)
|
|
return (ISC_TRUE);
|
|
|
|
isc_stdtime_get(&now);
|
|
result = dns_view_issecuredomain(fctx->res->view, &fctx->name,
|
|
now, ISC_TRUE, &secure_domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (ISC_FALSE);
|
|
return (secure_domain);
|
|
}
|
|
|
|
static isc_result_t
|
|
resquery_send(resquery_t *query) {
|
|
fetchctx_t *fctx;
|
|
isc_result_t result;
|
|
dns_name_t *qname = NULL;
|
|
dns_rdataset_t *qrdataset = NULL;
|
|
isc_region_t r;
|
|
dns_resolver_t *res;
|
|
isc_task_t *task;
|
|
isc_socket_t *sock;
|
|
isc_buffer_t tcpbuffer;
|
|
isc_sockaddr_t *address;
|
|
isc_buffer_t *buffer;
|
|
isc_netaddr_t ipaddr;
|
|
dns_tsigkey_t *tsigkey = NULL;
|
|
dns_peer_t *peer = NULL;
|
|
isc_boolean_t useedns;
|
|
dns_compress_t cctx;
|
|
isc_boolean_t cleanup_cctx = ISC_FALSE;
|
|
isc_boolean_t secure_domain;
|
|
isc_boolean_t tcp = ISC_TF((query->options & DNS_FETCHOPT_TCP) != 0);
|
|
dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
|
|
unsigned ednsopt = 0;
|
|
isc_uint16_t hint = 0, udpsize = 0; /* No EDNS */
|
|
#ifdef HAVE_DNSTAP
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
unsigned char zone[DNS_NAME_MAXWIRE];
|
|
dns_dtmsgtype_t dtmsgtype;
|
|
isc_region_t zr;
|
|
isc_buffer_t zb;
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
fctx = query->fctx;
|
|
QTRACE("send");
|
|
|
|
res = fctx->res;
|
|
task = res->buckets[fctx->bucketnum].task;
|
|
address = NULL;
|
|
|
|
if (tcp) {
|
|
/*
|
|
* Reserve space for the TCP message length.
|
|
*/
|
|
isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data));
|
|
isc_buffer_init(&query->buffer, query->data + 2,
|
|
sizeof(query->data) - 2);
|
|
buffer = &tcpbuffer;
|
|
} else {
|
|
isc_buffer_init(&query->buffer, query->data,
|
|
sizeof(query->data));
|
|
buffer = &query->buffer;
|
|
}
|
|
|
|
result = dns_message_gettempname(fctx->qmessage, &qname);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_temps;
|
|
result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_temps;
|
|
|
|
/*
|
|
* Get a query id from the dispatch.
|
|
*/
|
|
result = dns_dispatch_addresponse(query->dispatch,
|
|
0,
|
|
&query->addrinfo->sockaddr,
|
|
task,
|
|
resquery_response,
|
|
query,
|
|
&query->id,
|
|
&query->dispentry,
|
|
res->socketmgr);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_temps;
|
|
|
|
fctx->qmessage->opcode = dns_opcode_query;
|
|
|
|
/*
|
|
* Set up question.
|
|
*/
|
|
dns_name_init(qname, NULL);
|
|
dns_name_clone(&fctx->name, qname);
|
|
dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type);
|
|
ISC_LIST_APPEND(qname->list, qrdataset, link);
|
|
dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION);
|
|
qname = NULL;
|
|
qrdataset = NULL;
|
|
|
|
/*
|
|
* Set RD if the client has requested that we do a recursive query,
|
|
* or if we're sending to a forwarder.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 ||
|
|
ISFORWARDER(query->addrinfo))
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD;
|
|
|
|
/*
|
|
* Set CD if the client says not to validate, or if the
|
|
* question is under a secure entry point and this is a
|
|
* recursive/forward query -- unless the client said not to.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0)
|
|
/* Do nothing */
|
|
;
|
|
else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0)
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
|
|
else if (res->view->enablevalidation &&
|
|
((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0))
|
|
{
|
|
isc_boolean_t checknta =
|
|
ISC_TF((query->options & DNS_FETCHOPT_NONTA) == 0);
|
|
result = issecuredomain(res->view, &fctx->name, fctx->type,
|
|
isc_time_seconds(&query->start),
|
|
checknta, &secure_domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
secure_domain = ISC_FALSE;
|
|
if (res->view->dlv != NULL)
|
|
secure_domain = ISC_TRUE;
|
|
if (secure_domain)
|
|
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
|
|
}
|
|
|
|
/*
|
|
* We don't have to set opcode because it defaults to query.
|
|
*/
|
|
fctx->qmessage->id = query->id;
|
|
|
|
/*
|
|
* Convert the question to wire format.
|
|
*/
|
|
result = dns_compress_init(&cctx, -1, fctx->res->mctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
cleanup_cctx = ISC_TRUE;
|
|
|
|
result = dns_message_renderbegin(fctx->qmessage, &cctx,
|
|
&query->buffer);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
|
|
result = dns_message_rendersection(fctx->qmessage,
|
|
DNS_SECTION_QUESTION, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
|
|
peer = NULL;
|
|
isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr);
|
|
(void) dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer);
|
|
|
|
/*
|
|
* The ADB does not know about servers with "edns no". Check this,
|
|
* and then inform the ADB for future use.
|
|
*/
|
|
if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0 &&
|
|
peer != NULL &&
|
|
dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS &&
|
|
!useedns)
|
|
{
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
DNS_FETCHOPT_NOEDNS0,
|
|
DNS_FETCHOPT_NOEDNS0);
|
|
}
|
|
|
|
/* Sync NOEDNS0 flag in addrinfo->flags and options now. */
|
|
if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) != 0)
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
|
|
/* See if response history indicates that EDNS is not supported. */
|
|
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 &&
|
|
dns_adb_noedns(fctx->adb, query->addrinfo))
|
|
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 (fctx->timeouts > (MAX_EDNS0_TIMEOUTS * 2) &&
|
|
(!EDNSOK(query->addrinfo) || !wouldvalidate(fctx))) {
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
fctx->reason = "disabling EDNS";
|
|
} else if ((tried = triededns512(fctx, sockaddr)) != NULL &&
|
|
tried->count >= 2U &&
|
|
(!EDNSOK(query->addrinfo) || !wouldvalidate(fctx))) {
|
|
query->options |= DNS_FETCHOPT_NOEDNS0;
|
|
fctx->reason = "disabling EDNS";
|
|
} else if ((tried = triededns(fctx, sockaddr)) != NULL) {
|
|
if (tried->count == 1U) {
|
|
hint = dns_adb_getudpsize(fctx->adb,
|
|
query->addrinfo);
|
|
} else if (tried->count >= 2U) {
|
|
query->options |= DNS_FETCHOPT_EDNS512;
|
|
fctx->reason = "reducing the advertised EDNS "
|
|
"UDP packet size to 512 octets";
|
|
}
|
|
}
|
|
}
|
|
fctx->timeout = ISC_FALSE;
|
|
|
|
/*
|
|
* Use EDNS0, unless the caller doesn't want it, or we know that
|
|
* the remote server doesn't like it.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
|
|
if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) {
|
|
unsigned int version = DNS_EDNS_VERSION;
|
|
unsigned int flags = query->addrinfo->flags;
|
|
isc_boolean_t reqnsid = res->view->requestnsid;
|
|
isc_boolean_t sendcookie = res->view->sendcookie;
|
|
isc_boolean_t tcpkeepalive = ISC_FALSE;
|
|
unsigned char cookie[64];
|
|
isc_uint16_t padding = 0;
|
|
|
|
if ((flags & FCTX_ADDRINFO_EDNSOK) != 0 &&
|
|
(query->options & DNS_FETCHOPT_EDNS512) == 0) {
|
|
udpsize = dns_adb_probesize(fctx->adb,
|
|
query->addrinfo,
|
|
fctx->timeouts);
|
|
if (udpsize > res->udpsize)
|
|
udpsize = res->udpsize;
|
|
}
|
|
|
|
if (peer != NULL)
|
|
(void)dns_peer_getudpsize(peer, &udpsize);
|
|
|
|
if (udpsize == 0U && res->udpsize == 512U)
|
|
udpsize = 512;
|
|
|
|
/*
|
|
* Was the size forced to 512 in the configuration?
|
|
*/
|
|
if (udpsize == 512U)
|
|
query->options |= DNS_FETCHOPT_EDNS512;
|
|
|
|
/*
|
|
* We have talked to this server before.
|
|
*/
|
|
if (hint != 0U)
|
|
udpsize = hint;
|
|
|
|
/*
|
|
* We know nothing about the peer's capabilities
|
|
* so start with minimal EDNS UDP size.
|
|
*/
|
|
if (udpsize == 0U)
|
|
udpsize = 512;
|
|
|
|
if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) {
|
|
version = flags & DNS_FETCHOPT_EDNSVERSIONMASK;
|
|
version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT;
|
|
}
|
|
|
|
/* Request NSID/COOKIE/VERSION for current peer? */
|
|
if (peer != NULL) {
|
|
isc_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 = ISC_FALSE;
|
|
if (reqnsid) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_NSID;
|
|
ednsopts[ednsopt].length = 0;
|
|
ednsopts[ednsopt].value = NULL;
|
|
ednsopt++;
|
|
}
|
|
#if DNS_EDNS_VERSION > 0
|
|
/*
|
|
* Some EDNS(0) servers don't ignore unknown options
|
|
* as it was not a explict requirement of RFC 2671.
|
|
* Only send COOKIE to EDNS(1) servers.
|
|
*/
|
|
if (version < 1)
|
|
sendcookie = ISC_FALSE;
|
|
#endif
|
|
if (sendcookie) {
|
|
INSIST(ednsopt < DNS_EDNSOPTIONS);
|
|
ednsopts[ednsopt].code = DNS_OPT_COOKIE;
|
|
ednsopts[ednsopt].length = (isc_uint16_t)
|
|
dns_adb_getcookie(fctx->adb,
|
|
query->addrinfo,
|
|
cookie,
|
|
sizeof(cookie));
|
|
if (ednsopts[ednsopt].length != 0) {
|
|
ednsopts[ednsopt].value = cookie;
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_cookieout);
|
|
} else {
|
|
compute_cc(query, cookie, 8);
|
|
ednsopts[ednsopt].value = cookie;
|
|
ednsopts[ednsopt].length = 8;
|
|
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 choosen.
|
|
*/
|
|
query->udpsize = udpsize;
|
|
|
|
/*
|
|
* If we need EDNS0 to do this query and aren't using it, we lose.
|
|
*/
|
|
if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
|
|
result = DNS_R_SERVFAIL;
|
|
goto cleanup_message;
|
|
}
|
|
|
|
if (udpsize > 512U)
|
|
add_triededns(fctx, &query->addrinfo->sockaddr);
|
|
|
|
if (udpsize == 512U)
|
|
add_triededns512(fctx, &query->addrinfo->sockaddr);
|
|
|
|
/*
|
|
* Clear CD if EDNS is not in use.
|
|
*/
|
|
if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0)
|
|
fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD;
|
|
|
|
/*
|
|
* Add TSIG record tailored to the current recipient.
|
|
*/
|
|
result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
|
|
goto cleanup_message;
|
|
|
|
if (tsigkey != NULL) {
|
|
result = dns_message_settsigkey(fctx->qmessage, tsigkey);
|
|
dns_tsigkey_detach(&tsigkey);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
}
|
|
|
|
result = dns_message_rendersection(fctx->qmessage,
|
|
DNS_SECTION_ADDITIONAL, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
|
|
result = dns_message_renderend(fctx->qmessage);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
|
|
#ifdef HAVE_DNSTAP
|
|
memset(&zr, 0, sizeof(zr));
|
|
isc_buffer_init(&zb, zone, sizeof(zone));
|
|
dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
|
|
result = dns_name_towire(&fctx->domain, &cctx, &zb);
|
|
if (result == ISC_R_SUCCESS)
|
|
isc_buffer_usedregion(&zb, &zr);
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
dns_compress_invalidate(&cctx);
|
|
cleanup_cctx = ISC_FALSE;
|
|
|
|
if (dns_message_gettsigkey(fctx->qmessage) != NULL) {
|
|
dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage),
|
|
&query->tsigkey);
|
|
result = dns_message_getquerytsig(fctx->qmessage,
|
|
fctx->res->mctx,
|
|
&query->tsig);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
}
|
|
|
|
/*
|
|
* If using TCP, write the length of the message at the beginning
|
|
* of the buffer.
|
|
*/
|
|
if (tcp) {
|
|
isc_buffer_usedregion(&query->buffer, &r);
|
|
isc_buffer_putuint16(&tcpbuffer, (isc_uint16_t)r.length);
|
|
isc_buffer_add(&tcpbuffer, r.length);
|
|
}
|
|
|
|
/*
|
|
* Log the outgoing packet.
|
|
*/
|
|
dns_message_logfmtpacket(fctx->qmessage, "sending packet to",
|
|
&query->addrinfo->sockaddr,
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_PACKETS,
|
|
&dns_master_style_comment,
|
|
ISC_LOG_DEBUG(11),
|
|
fctx->res->mctx);
|
|
|
|
/*
|
|
* We're now done with the query message.
|
|
*/
|
|
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
|
|
|
|
if (query->exclusivesocket)
|
|
sock = dns_dispatch_getentrysocket(query->dispentry);
|
|
else
|
|
sock = dns_dispatch_getsocket(query->dispatch);
|
|
/*
|
|
* Send the query!
|
|
*/
|
|
if (!tcp) {
|
|
address = &query->addrinfo->sockaddr;
|
|
if (query->exclusivesocket) {
|
|
result = isc_socket_connect(sock, address, task,
|
|
resquery_udpconnected,
|
|
query);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_message;
|
|
query->connects++;
|
|
}
|
|
}
|
|
isc_buffer_usedregion(buffer, &r);
|
|
|
|
/*
|
|
* XXXRTH Make sure we don't send to ourselves! We should probably
|
|
* prune out these addresses when we get them from the ADB.
|
|
*/
|
|
memset(&query->sendevent, 0, sizeof(query->sendevent));
|
|
ISC_EVENT_INIT(&query->sendevent, sizeof(query->sendevent), 0, NULL,
|
|
ISC_SOCKEVENT_SENDDONE, resquery_senddone, query,
|
|
NULL, NULL, NULL);
|
|
|
|
if (query->dscp == -1) {
|
|
query->sendevent.attributes &= ~ISC_SOCKEVENTATTR_DSCP;
|
|
query->sendevent.dscp = 0;
|
|
} else {
|
|
query->sendevent.attributes |= ISC_SOCKEVENTATTR_DSCP;
|
|
query->sendevent.dscp = query->dscp;
|
|
if (tcp)
|
|
isc_socket_dscp(sock, query->dscp);
|
|
}
|
|
|
|
result = isc_socket_sendto2(sock, &r, task, address, NULL,
|
|
&query->sendevent, 0);
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
|
|
query->sends++;
|
|
|
|
QTRACE("sent");
|
|
|
|
#ifdef HAVE_DNSTAP
|
|
/*
|
|
* Log the outgoing query via dnstap.
|
|
*/
|
|
if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)
|
|
dtmsgtype = DNS_DTTYPE_FQ;
|
|
else
|
|
dtmsgtype = DNS_DTTYPE_RQ;
|
|
|
|
result = isc_socket_getsockname(sock, &localaddr);
|
|
if (result == ISC_R_SUCCESS)
|
|
la = &localaddr;
|
|
|
|
dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr,
|
|
tcp, &zr, &query->start, NULL, &query->buffer);
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup_message:
|
|
if (cleanup_cctx)
|
|
dns_compress_invalidate(&cctx);
|
|
|
|
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
|
|
|
|
/*
|
|
* Stop the dispatcher from listening.
|
|
*/
|
|
dns_dispatch_removeresponse(&query->dispentry, NULL);
|
|
|
|
cleanup_temps:
|
|
if (qname != NULL)
|
|
dns_message_puttempname(fctx->qmessage, &qname);
|
|
if (qrdataset != NULL)
|
|
dns_message_puttemprdataset(fctx->qmessage, &qrdataset);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
resquery_connected(isc_task_t *task, isc_event_t *event) {
|
|
isc_socketevent_t *sevent = (isc_socketevent_t *)event;
|
|
resquery_t *query = event->ev_arg;
|
|
isc_boolean_t retry = ISC_FALSE;
|
|
isc_interval_t interval;
|
|
isc_result_t result;
|
|
unsigned int attrs;
|
|
fetchctx_t *fctx;
|
|
|
|
REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
|
|
REQUIRE(VALID_QUERY(query));
|
|
|
|
QTRACE("connected");
|
|
|
|
UNUSED(task);
|
|
|
|
/*
|
|
* XXXRTH
|
|
*
|
|
* Currently we don't wait for the connect event before retrying
|
|
* a query. This means that if we get really behind, we may end
|
|
* up doing extra work!
|
|
*/
|
|
|
|
query->connects--;
|
|
fctx = query->fctx;
|
|
|
|
if (RESQUERY_CANCELED(query)) {
|
|
/*
|
|
* This query was canceled while the connect() was in
|
|
* progress.
|
|
*/
|
|
isc_socket_detach(&query->tcpsocket);
|
|
resquery_destroy(&query);
|
|
} else {
|
|
switch (sevent->result) {
|
|
case ISC_R_SUCCESS:
|
|
|
|
/*
|
|
* Extend the idle timer for TCP. 20 seconds
|
|
* should be long enough for a TCP connection to be
|
|
* established, a single DNS request to be sent,
|
|
* and the response received.
|
|
*/
|
|
isc_interval_set(&interval, 20, 0);
|
|
result = fctx_startidletimer(query->fctx, &interval);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("query canceled: idle timer failed; "
|
|
"responding");
|
|
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE,
|
|
ISC_FALSE);
|
|
fctx_done(fctx, result, __LINE__);
|
|
break;
|
|
}
|
|
/*
|
|
* We are connected. Create a dispatcher and
|
|
* send the query.
|
|
*/
|
|
attrs = 0;
|
|
attrs |= DNS_DISPATCHATTR_TCP;
|
|
attrs |= DNS_DISPATCHATTR_PRIVATE;
|
|
attrs |= DNS_DISPATCHATTR_CONNECTED;
|
|
if (isc_sockaddr_pf(&query->addrinfo->sockaddr) ==
|
|
AF_INET)
|
|
attrs |= DNS_DISPATCHATTR_IPV4;
|
|
else
|
|
attrs |= DNS_DISPATCHATTR_IPV6;
|
|
attrs |= DNS_DISPATCHATTR_MAKEQUERY;
|
|
|
|
result = dns_dispatch_createtcp(query->dispatchmgr,
|
|
query->tcpsocket,
|
|
query->fctx->res->taskmgr,
|
|
NULL, NULL,
|
|
4096, 2, 1, 1, 3,
|
|
attrs,
|
|
&query->dispatch);
|
|
|
|
/*
|
|
* Regardless of whether dns_dispatch_create()
|
|
* succeeded or not, we don't need our reference
|
|
* to the socket anymore.
|
|
*/
|
|
isc_socket_detach(&query->tcpsocket);
|
|
|
|
if (result == ISC_R_SUCCESS)
|
|
result = resquery_send(query);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("query canceled: "
|
|
"resquery_send() failed; responding");
|
|
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE, ISC_FALSE);
|
|
fctx_done(fctx, result, __LINE__);
|
|
}
|
|
break;
|
|
|
|
case ISC_R_NETUNREACH:
|
|
case ISC_R_HOSTUNREACH:
|
|
case ISC_R_CONNREFUSED:
|
|
case ISC_R_NOPERM:
|
|
case ISC_R_ADDRNOTAVAIL:
|
|
case ISC_R_CONNECTIONRESET:
|
|
FCTXTRACE3("query canceled in connected(): "
|
|
"no route to host; no response",
|
|
sevent->result);
|
|
|
|
/*
|
|
* No route to remote.
|
|
*/
|
|
isc_socket_detach(&query->tcpsocket);
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_TRUE, ISC_FALSE);
|
|
retry = ISC_TRUE;
|
|
break;
|
|
|
|
default:
|
|
FCTXTRACE3("query canceled in connected() due to "
|
|
"unexpected event result; responding",
|
|
sevent->result);
|
|
|
|
isc_socket_detach(&query->tcpsocket);
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE, ISC_FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
isc_event_free(&event);
|
|
|
|
if (retry) {
|
|
/*
|
|
* Behave as if the idle timer has expired. For TCP
|
|
* connections this may not actually reflect the latest timer.
|
|
*/
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
result = fctx_stopidletimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
else
|
|
fctx_try(fctx, ISC_TRUE, ISC_FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_finddone(isc_task_t *task, isc_event_t *event) {
|
|
fetchctx_t *fctx;
|
|
dns_adbfind_t *find;
|
|
dns_resolver_t *res;
|
|
isc_boolean_t want_try = ISC_FALSE;
|
|
isc_boolean_t want_done = ISC_FALSE;
|
|
isc_boolean_t bucket_empty = ISC_FALSE;
|
|
unsigned int bucketnum;
|
|
isc_boolean_t dodestroy = ISC_FALSE;
|
|
|
|
find = event->ev_sender;
|
|
fctx = event->ev_arg;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fctx->res;
|
|
|
|
UNUSED(task);
|
|
|
|
FCTXTRACE("finddone");
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
INSIST(fctx->pending > 0);
|
|
fctx->pending--;
|
|
|
|
if (ADDRWAIT(fctx)) {
|
|
/*
|
|
* The fetch is waiting for a name to be found.
|
|
*/
|
|
INSIST(!SHUTTINGDOWN(fctx));
|
|
if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) {
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
want_try = ISC_TRUE;
|
|
} else {
|
|
fctx->findfail++;
|
|
if (fctx->pending == 0) {
|
|
/*
|
|
* We've got nothing else to wait for and don't
|
|
* know the answer. There's nothing to do but
|
|
* fail the fctx.
|
|
*/
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
want_done = ISC_TRUE;
|
|
}
|
|
}
|
|
} else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 &&
|
|
fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) {
|
|
|
|
if (fctx->references == 0) {
|
|
bucket_empty = fctx_unlink(fctx);
|
|
dodestroy = ISC_TRUE;
|
|
}
|
|
}
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
|
|
isc_event_free(&event);
|
|
dns_adb_destroyfind(&find);
|
|
|
|
if (want_try) {
|
|
fctx_try(fctx, ISC_TRUE, ISC_FALSE);
|
|
} else if (want_done) {
|
|
FCTXTRACE("fetch failed in finddone(); return ISC_R_FAILURE");
|
|
fctx_done(fctx, ISC_R_FAILURE, __LINE__);
|
|
} else if (dodestroy) {
|
|
fctx_destroy(fctx);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
}
|
|
}
|
|
|
|
|
|
static inline isc_boolean_t
|
|
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 (ISC_TRUE);
|
|
}
|
|
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static inline isc_boolean_t
|
|
mark_bad(fetchctx_t *fctx) {
|
|
dns_adbfind_t *curr;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
isc_boolean_t all_bad = ISC_TRUE;
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (fuzzing_resolver)
|
|
return ISC_FALSE;
|
|
#endif
|
|
|
|
/*
|
|
* 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 = ISC_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 = ISC_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 = ISC_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 = ISC_FALSE;
|
|
}
|
|
|
|
return (all_bad);
|
|
}
|
|
|
|
static void
|
|
add_bad(fetchctx_t *fctx, 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 (fuzzing_resolver)
|
|
return;
|
|
#endif
|
|
|
|
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' */
|
|
}
|
|
}
|
|
|
|
if (bad_server(fctx, address)) {
|
|
/*
|
|
* We already know this server is bad.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
FCTXTRACE("add_bad");
|
|
|
|
sa = isc_mem_get(fctx->mctx, sizeof(*sa));
|
|
if (sa == NULL)
|
|
return;
|
|
*sa = *address;
|
|
ISC_LIST_INITANDAPPEND(fctx->bad, sa, link);
|
|
|
|
if (reason == DNS_R_LAME) /* already logged */
|
|
return;
|
|
|
|
if (reason == DNS_R_UNEXPECTEDRCODE &&
|
|
fctx->rmessage->rcode == dns_rcode_servfail &&
|
|
ISFORWARDER(addrinfo))
|
|
return;
|
|
|
|
if (reason == DNS_R_UNEXPECTEDRCODE) {
|
|
isc_buffer_init(&b, code, sizeof(code) - 1);
|
|
dns_rcode_totext(fctx->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)fctx->rmessage->opcode, &b);
|
|
code[isc_buffer_usedlength(&b)] = '\0';
|
|
spc = " ";
|
|
} else {
|
|
code[0] = '\0';
|
|
}
|
|
dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
|
|
dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf));
|
|
isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"%s%s%s resolving '%s/%s/%s': %s",
|
|
code, spc, dns_result_totext(reason),
|
|
namebuf, typebuf, classbuf, addrbuf);
|
|
}
|
|
|
|
/*
|
|
* Sort addrinfo list by RTT.
|
|
*/
|
|
static void
|
|
sort_adbfind(dns_adbfind_t *find, unsigned int bias) {
|
|
dns_adbaddrinfo_t *best, *curr;
|
|
dns_adbaddrinfolist_t sorted;
|
|
unsigned int best_srtt, curr_srtt;
|
|
|
|
/* Lame N^2 bubble sort. */
|
|
ISC_LIST_INIT(sorted);
|
|
while (!ISC_LIST_EMPTY(find->list)) {
|
|
best = ISC_LIST_HEAD(find->list);
|
|
best_srtt = best->srtt;
|
|
if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6)
|
|
best_srtt += bias;
|
|
curr = ISC_LIST_NEXT(best, publink);
|
|
while (curr != NULL) {
|
|
curr_srtt = curr->srtt;
|
|
if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6)
|
|
curr_srtt += bias;
|
|
if (curr_srtt < best_srtt) {
|
|
best = curr;
|
|
best_srtt = curr_srtt;
|
|
}
|
|
curr = ISC_LIST_NEXT(curr, publink);
|
|
}
|
|
ISC_LIST_UNLINK(find->list, best, publink);
|
|
ISC_LIST_APPEND(sorted, best, publink);
|
|
}
|
|
find->list = sorted;
|
|
}
|
|
|
|
/*
|
|
* Sort a list of finds by server RTT.
|
|
*/
|
|
static void
|
|
sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
|
|
dns_adbfind_t *best, *curr;
|
|
dns_adbfindlist_t sorted;
|
|
dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
|
|
unsigned int best_srtt, curr_srtt;
|
|
|
|
/* Sort each find's addrinfo list by SRTT. */
|
|
for (curr = ISC_LIST_HEAD(*findlist);
|
|
curr != NULL;
|
|
curr = ISC_LIST_NEXT(curr, publink))
|
|
sort_adbfind(curr, bias);
|
|
|
|
/* Lame N^2 bubble sort. */
|
|
ISC_LIST_INIT(sorted);
|
|
while (!ISC_LIST_EMPTY(*findlist)) {
|
|
best = ISC_LIST_HEAD(*findlist);
|
|
bestaddrinfo = ISC_LIST_HEAD(best->list);
|
|
INSIST(bestaddrinfo != NULL);
|
|
best_srtt = bestaddrinfo->srtt;
|
|
if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6)
|
|
best_srtt += bias;
|
|
curr = ISC_LIST_NEXT(best, publink);
|
|
while (curr != NULL) {
|
|
addrinfo = ISC_LIST_HEAD(curr->list);
|
|
INSIST(addrinfo != NULL);
|
|
curr_srtt = addrinfo->srtt;
|
|
if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6)
|
|
curr_srtt += bias;
|
|
if (curr_srtt < best_srtt) {
|
|
best = curr;
|
|
best_srtt = curr_srtt;
|
|
}
|
|
curr = ISC_LIST_NEXT(curr, publink);
|
|
}
|
|
ISC_LIST_UNLINK(*findlist, best, publink);
|
|
ISC_LIST_APPEND(sorted, best, publink);
|
|
}
|
|
*findlist = sorted;
|
|
}
|
|
|
|
static void
|
|
findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
|
|
unsigned int options, unsigned int flags, isc_stdtime_t now,
|
|
isc_boolean_t *overquota, isc_boolean_t *need_alternate)
|
|
{
|
|
dns_adbaddrinfo_t *ai;
|
|
dns_adbfind_t *find;
|
|
dns_resolver_t *res;
|
|
isc_boolean_t unshared;
|
|
isc_result_t result;
|
|
|
|
res = fctx->res;
|
|
unshared = ISC_TF((fctx->options & DNS_FETCHOPT_UNSHARED) != 0);
|
|
/*
|
|
* If this name is a subdomain of the query domain, tell
|
|
* the ADB to start looking using zone/hint data. This keeps us
|
|
* from getting stuck if the nameserver is beneath the zone cut
|
|
* and we don't know its address (e.g. because the A record has
|
|
* expired).
|
|
*/
|
|
if (dns_name_issubdomain(name, &fctx->domain))
|
|
options |= DNS_ADBFIND_STARTATZONE;
|
|
options |= DNS_ADBFIND_GLUEOK;
|
|
options |= DNS_ADBFIND_HINTOK;
|
|
|
|
/*
|
|
* See what we know about this address.
|
|
*/
|
|
find = NULL;
|
|
result = dns_adb_createfind(fctx->adb,
|
|
res->buckets[fctx->bucketnum].task,
|
|
fctx_finddone, fctx, name,
|
|
&fctx->name, fctx->type,
|
|
options, now, NULL,
|
|
res->view->dstport,
|
|
fctx->depth + 1, fctx->qc, &find);
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (result == DNS_R_ALIAS) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
/*
|
|
* XXXRTH Follow the CNAME/DNAME chain?
|
|
*/
|
|
dns_adb_destroyfind(&find);
|
|
fctx->adberr++;
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"skipping nameserver '%s' because it "
|
|
"is a CNAME, while resolving '%s'",
|
|
namebuf, fctx->info);
|
|
}
|
|
} else if (!ISC_LIST_EMPTY(find->list)) {
|
|
/*
|
|
* We have at least some of the addresses for the
|
|
* name.
|
|
*/
|
|
INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0);
|
|
if (flags != 0 || port != 0) {
|
|
for (ai = ISC_LIST_HEAD(find->list);
|
|
ai != NULL;
|
|
ai = ISC_LIST_NEXT(ai, publink)) {
|
|
ai->flags |= flags;
|
|
if (port != 0)
|
|
isc_sockaddr_setport(&ai->sockaddr,
|
|
port);
|
|
}
|
|
}
|
|
if ((flags & FCTX_ADDRINFO_FORWARDER) != 0)
|
|
ISC_LIST_APPEND(fctx->altfinds, find, publink);
|
|
else
|
|
ISC_LIST_APPEND(fctx->finds, find, publink);
|
|
} else {
|
|
/*
|
|
* We don't know any of the addresses for this
|
|
* name.
|
|
*/
|
|
if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
|
|
/*
|
|
* We're looking for them and will get an
|
|
* event about it later.
|
|
*/
|
|
fctx->pending++;
|
|
/*
|
|
* Bootstrap.
|
|
*/
|
|
if (need_alternate != NULL &&
|
|
!*need_alternate && unshared &&
|
|
((res->dispatches4 == NULL &&
|
|
find->result_v6 != DNS_R_NXDOMAIN) ||
|
|
(res->dispatches6 == NULL &&
|
|
find->result_v4 != DNS_R_NXDOMAIN)))
|
|
*need_alternate = ISC_TRUE;
|
|
} else {
|
|
if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
|
|
if (overquota != NULL)
|
|
*overquota = ISC_TRUE;
|
|
fctx->quotacount++; /* quota exceeded */
|
|
}
|
|
else if ((find->options & DNS_ADBFIND_LAMEPRUNED) != 0)
|
|
fctx->lamecount++; /* cached lame server */
|
|
else
|
|
fctx->adberr++; /* unreachable server, etc. */
|
|
|
|
/*
|
|
* If we know there are no addresses for
|
|
* the family we are using then try to add
|
|
* an alternative server.
|
|
*/
|
|
if (need_alternate != NULL && !*need_alternate &&
|
|
((res->dispatches4 == NULL &&
|
|
find->result_v6 == DNS_R_NXRRSET) ||
|
|
(res->dispatches6 == NULL &&
|
|
find->result_v4 == DNS_R_NXRRSET)))
|
|
*need_alternate = ISC_TRUE;
|
|
dns_adb_destroyfind(&find);
|
|
}
|
|
}
|
|
}
|
|
|
|
static isc_boolean_t
|
|
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 (ISC_TF(namereln == dns_namereln_subdomain));
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) {
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
isc_result_t result;
|
|
dns_resolver_t *res;
|
|
isc_stdtime_t now;
|
|
unsigned int stdoptions = 0;
|
|
dns_forwarder_t *fwd;
|
|
dns_adbaddrinfo_t *ai;
|
|
isc_boolean_t all_bad;
|
|
dns_rdata_ns_t ns;
|
|
isc_boolean_t need_alternate = ISC_FALSE;
|
|
isc_boolean_t all_spilled = ISC_TRUE;
|
|
|
|
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 this fctx has forwarders, use them; otherwise use any
|
|
* selective forwarders specified in the view; otherwise use the
|
|
* resolver's forwarders (if any).
|
|
*/
|
|
fwd = ISC_LIST_HEAD(fctx->forwarders);
|
|
if (fwd == NULL) {
|
|
dns_forwarders_t *forwarders = NULL;
|
|
dns_name_t *name = &fctx->name;
|
|
dns_name_t suffix;
|
|
unsigned int labels;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *domain;
|
|
|
|
/*
|
|
* DS records are found in the parent server.
|
|
* Strip label to get the correct forwarder (if any).
|
|
*/
|
|
if (dns_rdatatype_atparent(fctx->type) &&
|
|
dns_name_countlabels(name) > 1) {
|
|
dns_name_init(&suffix, NULL);
|
|
labels = dns_name_countlabels(name);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
name = &suffix;
|
|
}
|
|
|
|
domain = dns_fixedname_initname(&fixed);
|
|
result = dns_fwdtable_find(res->view->fwdtable, name,
|
|
domain, &forwarders);
|
|
if (result == ISC_R_SUCCESS) {
|
|
fwd = ISC_LIST_HEAD(forwarders->fwdrs);
|
|
fctx->fwdpolicy = forwarders->fwdpolicy;
|
|
if (fctx->fwdpolicy == dns_fwdpolicy_only &&
|
|
isstrictsubdomain(domain, &fctx->domain)) {
|
|
fcount_decr(fctx);
|
|
dns_name_free(&fctx->domain, fctx->mctx);
|
|
dns_name_init(&fctx->domain, NULL);
|
|
result = dns_name_dup(domain, fctx->mctx,
|
|
&fctx->domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
result = fcount_incr(fctx, ISC_TRUE);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (fwd != NULL) {
|
|
if ((isc_sockaddr_pf(&fwd->addr) == AF_INET &&
|
|
res->dispatches4 == NULL) ||
|
|
(isc_sockaddr_pf(&fwd->addr) == AF_INET6 &&
|
|
res->dispatches6 == NULL))
|
|
{
|
|
fwd = ISC_LIST_NEXT(fwd, link);
|
|
continue;
|
|
}
|
|
ai = NULL;
|
|
result = dns_adb_findaddrinfo(fctx->adb, &fwd->addr, &ai, 0);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_adbaddrinfo_t *cur;
|
|
ai->flags |= FCTX_ADDRINFO_FORWARDER;
|
|
ai->dscp = fwd->dscp;
|
|
cur = ISC_LIST_HEAD(fctx->forwaddrs);
|
|
while (cur != NULL && cur->srtt < ai->srtt)
|
|
cur = ISC_LIST_NEXT(cur, publink);
|
|
if (cur != NULL)
|
|
ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur,
|
|
ai, publink);
|
|
else
|
|
ISC_LIST_APPEND(fctx->forwaddrs, ai, publink);
|
|
}
|
|
fwd = ISC_LIST_NEXT(fwd, link);
|
|
}
|
|
|
|
/*
|
|
* If the forwarding policy is "only", we don't need the addresses
|
|
* of the nameservers.
|
|
*/
|
|
if (fctx->fwdpolicy == dns_fwdpolicy_only)
|
|
goto out;
|
|
|
|
/*
|
|
* Normal nameservers.
|
|
*/
|
|
|
|
stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT;
|
|
if (fctx->restarts == 1) {
|
|
/*
|
|
* To avoid sending out a flood of queries likely to
|
|
* result in NXRRSET, we suppress fetches for address
|
|
* families we don't have the first time through,
|
|
* provided that we have addresses in some family we
|
|
* can use.
|
|
*
|
|
* We don't want to set this option all the time, since
|
|
* if fctx->restarts > 1, we've clearly been having trouble
|
|
* with the addresses we had, so getting more could help.
|
|
*/
|
|
stdoptions |= DNS_ADBFIND_AVOIDFETCHES;
|
|
}
|
|
if (res->dispatches4 != NULL)
|
|
stdoptions |= DNS_ADBFIND_INET;
|
|
if (res->dispatches6 != NULL)
|
|
stdoptions |= DNS_ADBFIND_INET6;
|
|
|
|
if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0)
|
|
return (DNS_R_SERVFAIL);
|
|
|
|
isc_stdtime_get(&now);
|
|
|
|
INSIST(ISC_LIST_EMPTY(fctx->finds));
|
|
INSIST(ISC_LIST_EMPTY(fctx->altfinds));
|
|
|
|
for (result = dns_rdataset_first(&fctx->nameservers);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&fctx->nameservers))
|
|
{
|
|
isc_boolean_t overquota = ISC_FALSE;
|
|
|
|
dns_rdataset_current(&fctx->nameservers, &rdata);
|
|
/*
|
|
* Extract the name from the NS record.
|
|
*/
|
|
result = dns_rdata_tostruct(&rdata, &ns, NULL);
|
|
if (result != ISC_R_SUCCESS)
|
|
continue;
|
|
|
|
findname(fctx, &ns.name, 0, stdoptions, 0, now,
|
|
&overquota, &need_alternate);
|
|
|
|
if (!overquota)
|
|
all_spilled = ISC_FALSE;
|
|
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdata_freestruct(&ns);
|
|
}
|
|
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_FORWARDER,
|
|
now, 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;
|
|
cur = ISC_LIST_HEAD(fctx->altaddrs);
|
|
while (cur != NULL && cur->srtt < ai->srtt)
|
|
cur = ISC_LIST_NEXT(cur, publink);
|
|
if (cur != NULL)
|
|
ISC_LIST_INSERTBEFORE(fctx->altaddrs,
|
|
cur, ai, publink);
|
|
else
|
|
ISC_LIST_APPEND(fctx->altaddrs, ai,
|
|
publink);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* Mark all known bad servers.
|
|
*/
|
|
all_bad = mark_bad(fctx);
|
|
|
|
/*
|
|
* How are we doing?
|
|
*/
|
|
if (all_bad) {
|
|
/*
|
|
* We've got no addresses.
|
|
*/
|
|
if (fctx->pending > 0) {
|
|
/*
|
|
* We're fetching the addresses, but don't have any
|
|
* yet. Tell the caller to wait for an answer.
|
|
*/
|
|
result = DNS_R_WAIT;
|
|
} else {
|
|
isc_time_t expire;
|
|
isc_interval_t i;
|
|
/*
|
|
* We've lost completely. We don't know any
|
|
* addresses, and the ADB has told us it can't get
|
|
* them.
|
|
*/
|
|
FCTXTRACE("no addresses");
|
|
isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
|
|
result = isc_time_nowplusinterval(&expire, &i);
|
|
if (badcache &&
|
|
(fctx->type == dns_rdatatype_dnskey ||
|
|
fctx->type == dns_rdatatype_dlv ||
|
|
fctx->type == dns_rdatatype_ds) &&
|
|
result == ISC_R_SUCCESS)
|
|
dns_resolver_addbadcache(res, &fctx->name,
|
|
fctx->type, &expire);
|
|
|
|
result = ISC_R_FAILURE;
|
|
|
|
/*
|
|
* If all of the addresses found were over the
|
|
* fetches-per-server quota, return the configured
|
|
* response.
|
|
*/
|
|
if (all_spilled) {
|
|
result = res->quotaresp[dns_quotatype_server];
|
|
inc_stats(res, dns_resstatscounter_serverquota);
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* We've found some addresses. We might still be looking
|
|
* for more addresses.
|
|
*/
|
|
sort_finds(&fctx->finds, res->view->v6bias);
|
|
sort_finds(&fctx->altfinds, 0);
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
static inline void
|
|
possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
|
|
isc_netaddr_t na;
|
|
char buf[ISC_NETADDR_FORMATSIZE];
|
|
isc_sockaddr_t *sa;
|
|
isc_boolean_t aborted = ISC_FALSE;
|
|
isc_boolean_t bogus;
|
|
dns_acl_t *blackhole;
|
|
isc_netaddr_t ipaddr;
|
|
dns_peer_t *peer = NULL;
|
|
dns_resolver_t *res;
|
|
const char *msg = NULL;
|
|
|
|
sa = &addr->sockaddr;
|
|
|
|
res = fctx->res;
|
|
isc_netaddr_fromsockaddr(&ipaddr, sa);
|
|
blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr);
|
|
(void) dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer);
|
|
|
|
if (blackhole != NULL) {
|
|
int match;
|
|
|
|
if (dns_acl_match(&ipaddr, NULL, NULL, 0, NULL, blackhole,
|
|
&res->view->aclenv,
|
|
&match, NULL) == ISC_R_SUCCESS &&
|
|
match > 0)
|
|
aborted = ISC_TRUE;
|
|
}
|
|
|
|
if (peer != NULL &&
|
|
dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS &&
|
|
bogus)
|
|
aborted = ISC_TRUE;
|
|
|
|
if (aborted) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring blackholed / bogus server: ";
|
|
} else if (isc_sockaddr_isnetzero(sa)) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring net zero address: ";
|
|
} else if (isc_sockaddr_ismulticast(sa)) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring multicast address: ";
|
|
} else if (isc_sockaddr_isexperimental(sa)) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring experimental address: ";
|
|
} else if (sa->type.sa.sa_family != AF_INET6) {
|
|
return;
|
|
} else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring IPv6 mapped IPV4 address: ";
|
|
} else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) {
|
|
addr->flags |= FCTX_ADDRINFO_MARK;
|
|
msg = "ignoring IPv6 compatibility IPV4 address: ";
|
|
} else
|
|
return;
|
|
|
|
if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
|
|
isc_netaddr_fromsockaddr(&na, sa);
|
|
isc_netaddr_format(&na, buf, sizeof(buf));
|
|
FCTXTRACE2(msg, buf);
|
|
}
|
|
}
|
|
|
|
static inline 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;
|
|
return (addrinfo);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No forwarders. Move to the next find.
|
|
*/
|
|
|
|
fctx->attributes |= 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->attributes |= 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, isc_boolean_t retrying, isc_boolean_t badcache) {
|
|
isc_result_t result;
|
|
dns_adbaddrinfo_t *addrinfo = NULL;
|
|
dns_resolver_t *res;
|
|
unsigned int bucketnum;
|
|
isc_boolean_t bucket_empty;
|
|
|
|
FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc));
|
|
|
|
REQUIRE(!ADDRWAIT(fctx));
|
|
|
|
res = fctx->res;
|
|
|
|
/* We've already exceeded maximum query count */
|
|
if (isc_counter_used(fctx->qc) > res->maxqueries) {
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"exceeded max queries resolving '%s' "
|
|
"(querycount=%u, maxqueries=%u)",
|
|
fctx->info,
|
|
isc_counter_used(fctx->qc), res->maxqueries);
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
/* Try to find an address that isn't over quota */
|
|
while (addrinfo != NULL && dns_adbentry_overquota(addrinfo->entry))
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
if (addrinfo == NULL) {
|
|
/* We have no more addresses. Start over. */
|
|
fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE);
|
|
fctx_cleanupall(fctx);
|
|
result = fctx_getaddresses(fctx, badcache);
|
|
if (result == DNS_R_WAIT) {
|
|
/*
|
|
* Sleep waiting for addresses.
|
|
*/
|
|
FCTXTRACE("addrwait");
|
|
fctx->attributes |= FCTX_ATTR_ADDRWAIT;
|
|
return;
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
/*
|
|
* Something bad happened.
|
|
*/
|
|
fctx_done(fctx, result, __LINE__);
|
|
return;
|
|
}
|
|
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
while (addrinfo != NULL &&
|
|
dns_adbentry_overquota(addrinfo->entry))
|
|
addrinfo = fctx_nextaddress(fctx);
|
|
|
|
/*
|
|
* While we may have addresses from the ADB, they
|
|
* might be bad ones. In this case, return SERVFAIL.
|
|
*/
|
|
if (addrinfo == NULL) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dns_name_countlabels(&fctx->domain) > 2) {
|
|
result = isc_counter_increment(fctx->qc);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
|
"exceeded max queries resolving '%s'",
|
|
fctx->info);
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
fctx_increference(fctx);
|
|
result = fctx_query(fctx, addrinfo, fctx->options);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, result, __LINE__);
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
bucket_empty = fctx_decreference(fctx);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
} else if (retrying)
|
|
inc_stats(res, dns_resstatscounter_retry);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
fctx_unlink(fetchctx_t *fctx) {
|
|
dns_resolver_t *res;
|
|
unsigned int bucketnum;
|
|
|
|
/*
|
|
* Caller must be holding the bucket lock.
|
|
*/
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->state == fetchstate_done ||
|
|
fctx->state == fetchstate_init);
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->events));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->finds));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
|
|
REQUIRE(fctx->pending == 0);
|
|
REQUIRE(fctx->references == 0);
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->validators));
|
|
|
|
FCTXTRACE("unlink");
|
|
|
|
res = fctx->res;
|
|
bucketnum = fctx->bucketnum;
|
|
|
|
ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link);
|
|
|
|
LOCK(&res->nlock);
|
|
res->nfctx--;
|
|
UNLOCK(&res->nlock);
|
|
dec_stats(res, dns_resstatscounter_nfetch);
|
|
|
|
if (res->buckets[bucketnum].exiting &&
|
|
ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs))
|
|
return (ISC_TRUE);
|
|
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static void
|
|
fctx_destroy(fetchctx_t *fctx) {
|
|
isc_sockaddr_t *sa, *next_sa;
|
|
struct tried *tried;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(fctx->state == fetchstate_done ||
|
|
fctx->state == fetchstate_init);
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->events));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->finds));
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->altfinds));
|
|
REQUIRE(fctx->pending == 0);
|
|
REQUIRE(fctx->references == 0);
|
|
REQUIRE(ISC_LIST_EMPTY(fctx->validators));
|
|
REQUIRE(!ISC_LINK_LINKED(fctx, link));
|
|
|
|
FCTXTRACE("destroy");
|
|
|
|
/*
|
|
* Free bad.
|
|
*/
|
|
for (sa = ISC_LIST_HEAD(fctx->bad);
|
|
sa != NULL;
|
|
sa = next_sa) {
|
|
next_sa = ISC_LIST_NEXT(sa, link);
|
|
ISC_LIST_UNLINK(fctx->bad, sa, link);
|
|
isc_mem_put(fctx->mctx, sa, sizeof(*sa));
|
|
}
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns);
|
|
tried != NULL;
|
|
tried = ISC_LIST_HEAD(fctx->edns)) {
|
|
ISC_LIST_UNLINK(fctx->edns, tried, link);
|
|
isc_mem_put(fctx->mctx, tried, sizeof(*tried));
|
|
}
|
|
|
|
for (tried = ISC_LIST_HEAD(fctx->edns512);
|
|
tried != NULL;
|
|
tried = ISC_LIST_HEAD(fctx->edns512)) {
|
|
ISC_LIST_UNLINK(fctx->edns512, tried, link);
|
|
isc_mem_put(fctx->mctx, tried, sizeof(*tried));
|
|
}
|
|
|
|
for (sa = ISC_LIST_HEAD(fctx->bad_edns);
|
|
sa != NULL;
|
|
sa = next_sa) {
|
|
next_sa = ISC_LIST_NEXT(sa, link);
|
|
ISC_LIST_UNLINK(fctx->bad_edns, sa, link);
|
|
isc_mem_put(fctx->mctx, sa, sizeof(*sa));
|
|
}
|
|
|
|
isc_counter_detach(&fctx->qc);
|
|
fcount_decr(fctx);
|
|
isc_timer_detach(&fctx->timer);
|
|
dns_message_destroy(&fctx->rmessage);
|
|
dns_message_destroy(&fctx->qmessage);
|
|
if (dns_name_countlabels(&fctx->domain) > 0)
|
|
dns_name_free(&fctx->domain, fctx->mctx);
|
|
if (dns_rdataset_isassociated(&fctx->nameservers))
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
dns_name_free(&fctx->name, fctx->mctx);
|
|
dns_db_detach(&fctx->cache);
|
|
dns_adb_detach(&fctx->adb);
|
|
isc_mem_free(fctx->mctx, fctx->info);
|
|
isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx));
|
|
}
|
|
|
|
/*
|
|
* Fetch event handlers.
|
|
*/
|
|
|
|
static void
|
|
fctx_timeout(isc_task_t *task, isc_event_t *event) {
|
|
fetchctx_t *fctx = event->ev_arg;
|
|
isc_timerevent_t *tevent = (isc_timerevent_t *)event;
|
|
resquery_t *query;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
UNUSED(task);
|
|
|
|
FCTXTRACE("timeout");
|
|
|
|
inc_stats(fctx->res, dns_resstatscounter_querytimeout);
|
|
|
|
if (event->ev_type == ISC_TIMEREVENT_LIFE) {
|
|
fctx->reason = NULL;
|
|
fctx_done(fctx, ISC_R_TIMEDOUT, __LINE__);
|
|
} else {
|
|
isc_result_t result;
|
|
|
|
fctx->timeouts++;
|
|
fctx->timeout = ISC_TRUE;
|
|
|
|
/*
|
|
* We could cancel the running queries here, or we could let
|
|
* them keep going. Since we normally use separate sockets for
|
|
* different queries, we adopt the former approach to reduce
|
|
* the number of open sockets: cancel the oldest query if it
|
|
* expired after the query had started (this is usually the
|
|
* case but is not always so, depending on the task schedule
|
|
* timing).
|
|
*/
|
|
query = ISC_LIST_HEAD(fctx->queries);
|
|
if (query != NULL &&
|
|
isc_time_compare(&tevent->due, &query->start) >= 0)
|
|
{
|
|
FCTXTRACE("query timed out; no response");
|
|
fctx_cancelquery(&query, NULL, NULL, ISC_TRUE, ISC_FALSE);
|
|
}
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
|
|
/*
|
|
* Our timer has triggered. Reestablish the fctx lifetime
|
|
* timer.
|
|
*/
|
|
result = fctx_starttimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
else
|
|
/*
|
|
* Keep trying.
|
|
*/
|
|
fctx_try(fctx, ISC_TRUE, ISC_FALSE);
|
|
}
|
|
|
|
isc_event_free(&event);
|
|
}
|
|
|
|
static void
|
|
fctx_shutdown(fetchctx_t *fctx) {
|
|
isc_event_t *cevent;
|
|
|
|
/*
|
|
* Start the shutdown process for fctx, if it isn't already underway.
|
|
*/
|
|
|
|
FCTXTRACE("shutdown");
|
|
|
|
/*
|
|
* The caller must be holding the appropriate bucket lock.
|
|
*/
|
|
|
|
if (fctx->want_shutdown)
|
|
return;
|
|
|
|
fctx->want_shutdown = ISC_TRUE;
|
|
|
|
/*
|
|
* Unless we're still initializing (in which case the
|
|
* control event is still outstanding), we need to post
|
|
* the control event to tell the fetch we want it to
|
|
* exit.
|
|
*/
|
|
if (fctx->state != fetchstate_init) {
|
|
cevent = &fctx->control_event;
|
|
isc_task_send(fctx->res->buckets[fctx->bucketnum].task,
|
|
&cevent);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
|
|
fetchctx_t *fctx = event->ev_arg;
|
|
isc_boolean_t bucket_empty = ISC_FALSE;
|
|
dns_resolver_t *res;
|
|
unsigned int bucketnum;
|
|
dns_validator_t *validator;
|
|
isc_boolean_t dodestroy = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
UNUSED(task);
|
|
|
|
res = fctx->res;
|
|
bucketnum = fctx->bucketnum;
|
|
|
|
FCTXTRACE("doshutdown");
|
|
|
|
/*
|
|
* An fctx that is shutting down is no longer in ADDRWAIT mode.
|
|
*/
|
|
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
|
|
|
|
/*
|
|
* Cancel all pending validators. Note that this must be done
|
|
* without the bucket lock held, since that could cause deadlock.
|
|
*/
|
|
validator = ISC_LIST_HEAD(fctx->validators);
|
|
while (validator != NULL) {
|
|
dns_validator_cancel(validator);
|
|
validator = ISC_LIST_NEXT(validator, link);
|
|
}
|
|
|
|
if (fctx->nsfetch != NULL)
|
|
dns_resolver_cancelfetch(fctx->nsfetch);
|
|
|
|
/*
|
|
* Shut down anything still running on behalf of this
|
|
* fetch, and clean up finds and addresses. To avoid deadlock
|
|
* with the ADB, we must do this before we lock the bucket lock.
|
|
*/
|
|
fctx_stopqueries(fctx, ISC_FALSE, ISC_FALSE);
|
|
fctx_cleanupall(fctx);
|
|
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN;
|
|
|
|
INSIST(fctx->state == fetchstate_active ||
|
|
fctx->state == fetchstate_done);
|
|
INSIST(fctx->want_shutdown);
|
|
|
|
if (fctx->state != fetchstate_done) {
|
|
fctx->state = fetchstate_done;
|
|
fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
|
|
}
|
|
|
|
if (fctx->references == 0 && fctx->pending == 0 &&
|
|
fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) {
|
|
bucket_empty = fctx_unlink(fctx);
|
|
dodestroy = ISC_TRUE;
|
|
}
|
|
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
|
|
if (dodestroy) {
|
|
fctx_destroy(fctx);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fctx_start(isc_task_t *task, isc_event_t *event) {
|
|
fetchctx_t *fctx = event->ev_arg;
|
|
isc_boolean_t done = ISC_FALSE, bucket_empty = ISC_FALSE;
|
|
dns_resolver_t *res;
|
|
unsigned int bucketnum;
|
|
isc_boolean_t dodestroy = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
UNUSED(task);
|
|
|
|
res = fctx->res;
|
|
bucketnum = fctx->bucketnum;
|
|
|
|
FCTXTRACE("start");
|
|
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
INSIST(fctx->state == fetchstate_init);
|
|
if (fctx->want_shutdown) {
|
|
/*
|
|
* We haven't started this fctx yet, and we've been requested
|
|
* to shut it down.
|
|
*/
|
|
fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN;
|
|
fctx->state = fetchstate_done;
|
|
fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__);
|
|
/*
|
|
* Since we haven't started, we INSIST that we have no
|
|
* pending ADB finds and no pending validations.
|
|
*/
|
|
INSIST(fctx->pending == 0);
|
|
INSIST(fctx->nqueries == 0);
|
|
INSIST(ISC_LIST_EMPTY(fctx->validators));
|
|
if (fctx->references == 0) {
|
|
/*
|
|
* It's now safe to destroy this fctx.
|
|
*/
|
|
bucket_empty = fctx_unlink(fctx);
|
|
dodestroy = ISC_TRUE;
|
|
}
|
|
done = ISC_TRUE;
|
|
} else {
|
|
/*
|
|
* Normal fctx startup.
|
|
*/
|
|
fctx->state = fetchstate_active;
|
|
/*
|
|
* Reset the control event for later use in shutting down
|
|
* the fctx.
|
|
*/
|
|
ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
|
|
DNS_EVENT_FETCHCONTROL, fctx_doshutdown, fctx,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
|
|
if (!done) {
|
|
isc_result_t result;
|
|
|
|
INSIST(!dodestroy);
|
|
|
|
/*
|
|
* All is well. Start working on the fetch.
|
|
*/
|
|
result = fctx_starttimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
else
|
|
fctx_try(fctx, ISC_FALSE, ISC_FALSE);
|
|
} else if (dodestroy) {
|
|
fctx_destroy(fctx);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Fetch Creation, Joining, and Cancelation.
|
|
*/
|
|
|
|
static inline isc_result_t
|
|
fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
|
|
dns_messageid_t id, isc_taskaction_t action, void *arg,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
|
|
dns_fetch_t *fetch)
|
|
{
|
|
isc_task_t *tclone;
|
|
dns_fetchevent_t *event;
|
|
|
|
FCTXTRACE("join");
|
|
|
|
/*
|
|
* We store the task we're going to send this event to in the
|
|
* sender field. We'll make the fetch the sender when we actually
|
|
* send the event.
|
|
*/
|
|
tclone = NULL;
|
|
isc_task_attach(task, &tclone);
|
|
event = (dns_fetchevent_t *)
|
|
isc_event_allocate(fctx->res->mctx, tclone, DNS_EVENT_FETCHDONE,
|
|
action, arg, sizeof(*event));
|
|
if (event == NULL) {
|
|
isc_task_detach(&tclone);
|
|
return (ISC_R_NOMEMORY);
|
|
}
|
|
event->result = DNS_R_SERVFAIL;
|
|
event->qtype = fctx->type;
|
|
event->db = NULL;
|
|
event->node = NULL;
|
|
event->rdataset = rdataset;
|
|
event->sigrdataset = sigrdataset;
|
|
event->fetch = fetch;
|
|
event->client = client;
|
|
event->id = id;
|
|
dns_fixedname_init(&event->foundname);
|
|
|
|
/*
|
|
* Make sure that we can store the sigrdataset in the
|
|
* first event if it is needed by any of the events.
|
|
*/
|
|
if (event->sigrdataset != NULL)
|
|
ISC_LIST_PREPEND(fctx->events, event, ev_link);
|
|
else
|
|
ISC_LIST_APPEND(fctx->events, event, ev_link);
|
|
fctx->references++;
|
|
fctx->client = client;
|
|
|
|
fetch->magic = DNS_FETCH_MAGIC;
|
|
fetch->private = fctx;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static inline void
|
|
log_ns_ttl(fetchctx_t *fctx, const char *where) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
|
|
dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
|
|
"log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u",
|
|
fctx, where, namebuf, domainbuf,
|
|
fctx->ns_ttl_ok, fctx->ns_ttl);
|
|
}
|
|
|
|
static isc_result_t
|
|
fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
|
|
const dns_name_t *domain, dns_rdataset_t *nameservers,
|
|
unsigned int options, unsigned int bucketnum, unsigned int depth,
|
|
isc_counter_t *qc, fetchctx_t **fctxp)
|
|
{
|
|
fetchctx_t *fctx;
|
|
isc_result_t result;
|
|
isc_result_t iresult;
|
|
isc_interval_t interval;
|
|
dns_fixedname_t fixed;
|
|
unsigned int findoptions = 0;
|
|
char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
isc_mem_t *mctx;
|
|
|
|
/*
|
|
* Caller must be holding the lock for bucket number 'bucketnum'.
|
|
*/
|
|
REQUIRE(fctxp != NULL && *fctxp == NULL);
|
|
|
|
mctx = res->buckets[bucketnum].mctx;
|
|
fctx = isc_mem_get(mctx, sizeof(*fctx));
|
|
if (fctx == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
fctx->qc = NULL;
|
|
if (qc != NULL) {
|
|
isc_counter_attach(qc, &fctx->qc);
|
|
} else {
|
|
result = isc_counter_create(res->mctx,
|
|
res->maxqueries, &fctx->qc);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_fetch;
|
|
}
|
|
|
|
/*
|
|
* Make fctx->info point to a copy of a formatted string
|
|
* "name/type".
|
|
*/
|
|
dns_name_format(name, buf, sizeof(buf));
|
|
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
|
|
strlcat(buf, "/", sizeof(buf));
|
|
strlcat(buf, typebuf, sizeof(buf));
|
|
fctx->info = isc_mem_strdup(mctx, buf);
|
|
if (fctx->info == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup_counter;
|
|
}
|
|
|
|
FCTXTRACE("create");
|
|
dns_name_init(&fctx->name, NULL);
|
|
result = dns_name_dup(name, mctx, &fctx->name);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_info;
|
|
dns_name_init(&fctx->domain, NULL);
|
|
dns_rdataset_init(&fctx->nameservers);
|
|
|
|
fctx->type = type;
|
|
fctx->options = options;
|
|
/*
|
|
* Note! We do not attach to the task. We are relying on the
|
|
* resolver to ensure that this task doesn't go away while we are
|
|
* using it.
|
|
*/
|
|
fctx->res = res;
|
|
fctx->references = 0;
|
|
fctx->bucketnum = bucketnum;
|
|
fctx->dbucketnum = RES_NOBUCKET;
|
|
fctx->state = fetchstate_init;
|
|
fctx->want_shutdown = ISC_FALSE;
|
|
fctx->cloned = ISC_FALSE;
|
|
fctx->depth = depth;
|
|
ISC_LIST_INIT(fctx->queries);
|
|
ISC_LIST_INIT(fctx->finds);
|
|
ISC_LIST_INIT(fctx->altfinds);
|
|
ISC_LIST_INIT(fctx->forwaddrs);
|
|
ISC_LIST_INIT(fctx->altaddrs);
|
|
ISC_LIST_INIT(fctx->forwarders);
|
|
fctx->fwdpolicy = dns_fwdpolicy_none;
|
|
ISC_LIST_INIT(fctx->bad);
|
|
ISC_LIST_INIT(fctx->edns);
|
|
ISC_LIST_INIT(fctx->edns512);
|
|
ISC_LIST_INIT(fctx->bad_edns);
|
|
ISC_LIST_INIT(fctx->validators);
|
|
fctx->validator = NULL;
|
|
fctx->find = NULL;
|
|
fctx->altfind = NULL;
|
|
fctx->pending = 0;
|
|
fctx->restarts = 0;
|
|
fctx->querysent = 0;
|
|
fctx->referrals = 0;
|
|
TIME_NOW(&fctx->start);
|
|
fctx->timeouts = 0;
|
|
fctx->lamecount = 0;
|
|
fctx->quotacount = 0;
|
|
fctx->adberr = 0;
|
|
fctx->neterr = 0;
|
|
fctx->badresp = 0;
|
|
fctx->findfail = 0;
|
|
fctx->valfail = 0;
|
|
fctx->result = ISC_R_FAILURE;
|
|
fctx->vresult = ISC_R_SUCCESS;
|
|
fctx->exitline = -1; /* sentinel */
|
|
fctx->logged = ISC_FALSE;
|
|
fctx->attributes = 0;
|
|
fctx->spilled = ISC_FALSE;
|
|
fctx->nqueries = 0;
|
|
fctx->reason = NULL;
|
|
fctx->rand_buf = 0;
|
|
fctx->rand_bits = 0;
|
|
fctx->timeout = ISC_FALSE;
|
|
fctx->addrinfo = NULL;
|
|
fctx->client = NULL;
|
|
fctx->ns_ttl = 0;
|
|
fctx->ns_ttl_ok = ISC_FALSE;
|
|
|
|
dns_name_init(&fctx->nsname, NULL);
|
|
fctx->nsfetch = NULL;
|
|
dns_rdataset_init(&fctx->nsrrset);
|
|
|
|
if (domain == NULL) {
|
|
dns_forwarders_t *forwarders = NULL;
|
|
unsigned int labels;
|
|
const dns_name_t *fwdname = name;
|
|
dns_name_t suffix;
|
|
dns_name_t *fname;
|
|
|
|
/*
|
|
* DS records are found in the parent server. Strip one
|
|
* leading label from the name (to be used in finding
|
|
* the forwarder).
|
|
*/
|
|
if (dns_rdatatype_atparent(fctx->type) &&
|
|
dns_name_countlabels(name) > 1) {
|
|
dns_name_init(&suffix, NULL);
|
|
labels = dns_name_countlabels(name);
|
|
dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
|
|
fwdname = &suffix;
|
|
}
|
|
|
|
/* Find the forwarder for this name. */
|
|
fname = dns_fixedname_initname(&fixed);
|
|
result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname,
|
|
fname, &forwarders);
|
|
if (result == ISC_R_SUCCESS)
|
|
fctx->fwdpolicy = forwarders->fwdpolicy;
|
|
|
|
if (fctx->fwdpolicy != dns_fwdpolicy_only) {
|
|
/*
|
|
* The caller didn't supply a query domain and
|
|
* nameservers, and we're not in forward-only mode,
|
|
* so find the best nameservers to use.
|
|
*/
|
|
if (dns_rdatatype_atparent(fctx->type))
|
|
findoptions |= DNS_DBFIND_NOEXACT;
|
|
result = dns_view_findzonecut(res->view, name, fname,
|
|
0, findoptions, ISC_TRUE,
|
|
ISC_TRUE,
|
|
&fctx->nameservers,
|
|
NULL);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_nameservers;
|
|
|
|
result = dns_name_dup(fname, mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_nameservers;
|
|
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = ISC_TRUE;
|
|
} else {
|
|
/*
|
|
* We're in forward-only mode. Set the query domain.
|
|
*/
|
|
result = dns_name_dup(fname, mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_name;
|
|
}
|
|
} else {
|
|
result = dns_name_dup(domain, mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_name;
|
|
dns_rdataset_clone(nameservers, &fctx->nameservers);
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = ISC_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Are there too many simultaneous queries for this domain?
|
|
*/
|
|
result = fcount_incr(fctx, ISC_FALSE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
result = fctx->res->quotaresp[dns_quotatype_zone];
|
|
inc_stats(res, dns_resstatscounter_zonequota);
|
|
goto cleanup_domain;
|
|
}
|
|
|
|
log_ns_ttl(fctx, "fctx_create");
|
|
|
|
INSIST(dns_name_issubdomain(&fctx->name, &fctx->domain));
|
|
|
|
fctx->qmessage = NULL;
|
|
result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER,
|
|
&fctx->qmessage);
|
|
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_fcount;
|
|
|
|
fctx->rmessage = NULL;
|
|
result = dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE,
|
|
&fctx->rmessage);
|
|
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_qmessage;
|
|
|
|
/*
|
|
* Compute an expiration time for the entire fetch.
|
|
*/
|
|
isc_interval_set(&interval, res->query_timeout / 1000,
|
|
res->query_timeout % 1000 * 1000000);
|
|
iresult = isc_time_nowplusinterval(&fctx->expires, &interval);
|
|
if (iresult != ISC_R_SUCCESS) {
|
|
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
|
"isc_time_nowplusinterval: %s",
|
|
isc_result_totext(iresult));
|
|
result = ISC_R_UNEXPECTED;
|
|
goto cleanup_rmessage;
|
|
}
|
|
|
|
/*
|
|
* Default retry interval initialization. We set the interval now
|
|
* mostly so it won't be uninitialized. It will be set to the
|
|
* correct value before a query is issued.
|
|
*/
|
|
isc_interval_set(&fctx->interval, 2, 0);
|
|
|
|
/*
|
|
* Create an inactive timer. It will be made active when the fetch
|
|
* is actually started.
|
|
*/
|
|
fctx->timer = NULL;
|
|
iresult = isc_timer_create(res->timermgr, isc_timertype_inactive,
|
|
NULL, NULL,
|
|
res->buckets[bucketnum].task, fctx_timeout,
|
|
fctx, &fctx->timer);
|
|
if (iresult != ISC_R_SUCCESS) {
|
|
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
|
"isc_timer_create: %s",
|
|
isc_result_totext(iresult));
|
|
result = ISC_R_UNEXPECTED;
|
|
goto cleanup_rmessage;
|
|
}
|
|
|
|
/*
|
|
* Attach to the view's cache and adb.
|
|
*/
|
|
fctx->cache = NULL;
|
|
dns_db_attach(res->view->cachedb, &fctx->cache);
|
|
fctx->adb = NULL;
|
|
dns_adb_attach(res->view->adb, &fctx->adb);
|
|
fctx->mctx = NULL;
|
|
isc_mem_attach(mctx, &fctx->mctx);
|
|
|
|
ISC_LIST_INIT(fctx->events);
|
|
ISC_LINK_INIT(fctx, link);
|
|
fctx->magic = FCTX_MAGIC;
|
|
|
|
ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link);
|
|
|
|
LOCK(&res->nlock);
|
|
res->nfctx++;
|
|
UNLOCK(&res->nlock);
|
|
inc_stats(res, dns_resstatscounter_nfetch);
|
|
|
|
*fctxp = fctx;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup_rmessage:
|
|
dns_message_destroy(&fctx->rmessage);
|
|
|
|
cleanup_qmessage:
|
|
dns_message_destroy(&fctx->qmessage);
|
|
|
|
cleanup_fcount:
|
|
fcount_decr(fctx);
|
|
|
|
cleanup_domain:
|
|
if (dns_name_countlabels(&fctx->domain) > 0)
|
|
dns_name_free(&fctx->domain, mctx);
|
|
|
|
cleanup_nameservers:
|
|
if (dns_rdataset_isassociated(&fctx->nameservers))
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
|
|
cleanup_name:
|
|
dns_name_free(&fctx->name, mctx);
|
|
|
|
cleanup_info:
|
|
isc_mem_free(mctx, fctx->info);
|
|
|
|
cleanup_counter:
|
|
isc_counter_detach(&fctx->qc);
|
|
|
|
cleanup_fetch:
|
|
isc_mem_put(mctx, fctx, sizeof(*fctx));
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Handle Responses
|
|
*/
|
|
static inline isc_boolean_t
|
|
is_lame(fetchctx_t *fctx) {
|
|
dns_message_t *message = fctx->rmessage;
|
|
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 (ISC_FALSE);
|
|
|
|
if (message->counts[DNS_SECTION_ANSWER] != 0)
|
|
return (ISC_FALSE);
|
|
|
|
if (message->counts[DNS_SECTION_AUTHORITY] == 0)
|
|
return (ISC_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 (ISC_FALSE);
|
|
if (namereln == dns_namereln_subdomain)
|
|
return (ISC_FALSE);
|
|
return (ISC_TRUE);
|
|
}
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
|
|
}
|
|
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static inline 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 inline void
|
|
log_formerr(fetchctx_t *fctx, const char *format, ...) {
|
|
char nsbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char clbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
const char *clmsg = "";
|
|
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));
|
|
|
|
if (fctx->client != NULL) {
|
|
clmsg = " for client ";
|
|
isc_sockaddr_format(fctx->client, clbuf, sizeof(clbuf));
|
|
} else {
|
|
clbuf[0] = '\0';
|
|
}
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"DNS format error from %s resolving %s%s%s: %s",
|
|
nsbuf, fctx->info, clmsg, clbuf, msgbuf);
|
|
}
|
|
|
|
static isc_result_t
|
|
same_question(fetchctx_t *fctx) {
|
|
isc_result_t result;
|
|
dns_message_t *message = fctx->rmessage;
|
|
dns_name_t *name;
|
|
dns_rdataset_t *rdataset;
|
|
|
|
/*
|
|
* Caller must be holding the fctx lock.
|
|
*/
|
|
|
|
/*
|
|
* XXXRTH Currently we support only one question.
|
|
*/
|
|
if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] == 0)) {
|
|
if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) {
|
|
/*
|
|
* If TC=1 and the question section is empty, we
|
|
* accept the reply message as a truncated
|
|
* answer, to be retried over TCP.
|
|
*
|
|
* It is really a FORMERR condition, but this is
|
|
* a workaround to accept replies from some
|
|
* implementations.
|
|
*
|
|
* Because the question section matching is not
|
|
* performed, the worst that could happen is
|
|
* that an attacker who gets past the ID and
|
|
* source port checks can force the use of
|
|
* TCP. This is considered an acceptable risk.
|
|
*/
|
|
log_formerr(fctx,
|
|
"empty question section, "
|
|
"accepting it anyway as TC=1");
|
|
return (ISC_R_SUCCESS);
|
|
} else {
|
|
log_formerr(fctx, "empty question section");
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
} else if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] > 1)) {
|
|
log_formerr(fctx, "too many questions");
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
|
|
result = dns_message_firstname(message, DNS_SECTION_QUESTION);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_QUESTION, &name);
|
|
rdataset = ISC_LIST_HEAD(name->list);
|
|
INSIST(rdataset != NULL);
|
|
INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
|
|
|
|
if (fctx->type != rdataset->type ||
|
|
fctx->res->rdclass != rdataset->rdclass ||
|
|
!dns_name_equal(&fctx->name, name)) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char classbuf[DNS_RDATACLASS_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdataclass_format(rdataset->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
|
log_formerr(fctx, "question section mismatch: got %s/%s/%s",
|
|
namebuf, classbuf, typebuf);
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
clone_results(fetchctx_t *fctx) {
|
|
dns_fetchevent_t *event, *hevent;
|
|
isc_result_t result;
|
|
dns_name_t *name, *hname;
|
|
|
|
FCTXTRACE("clone_results");
|
|
|
|
/*
|
|
* Set up any other events to have the same data as the first
|
|
* event.
|
|
*
|
|
* Caller must be holding the appropriate lock.
|
|
*/
|
|
|
|
fctx->cloned = ISC_TRUE;
|
|
hevent = ISC_LIST_HEAD(fctx->events);
|
|
if (hevent == NULL)
|
|
return;
|
|
hname = dns_fixedname_name(&hevent->foundname);
|
|
for (event = ISC_LIST_NEXT(hevent, ev_link);
|
|
event != NULL;
|
|
event = ISC_LIST_NEXT(event, ev_link)) {
|
|
name = dns_fixedname_name(&event->foundname);
|
|
result = dns_name_copy(hname, name, NULL);
|
|
if (result != ISC_R_SUCCESS)
|
|
event->result = result;
|
|
else
|
|
event->result = hevent->result;
|
|
dns_db_attach(hevent->db, &event->db);
|
|
dns_db_attachnode(hevent->db, hevent->node, &event->node);
|
|
INSIST(hevent->rdataset != NULL);
|
|
INSIST(event->rdataset != NULL);
|
|
if (dns_rdataset_isassociated(hevent->rdataset))
|
|
dns_rdataset_clone(hevent->rdataset, event->rdataset);
|
|
INSIST(! (hevent->sigrdataset == NULL &&
|
|
event->sigrdataset != NULL));
|
|
if (hevent->sigrdataset != NULL &&
|
|
dns_rdataset_isassociated(hevent->sigrdataset) &&
|
|
event->sigrdataset != NULL)
|
|
dns_rdataset_clone(hevent->sigrdataset,
|
|
event->sigrdataset);
|
|
}
|
|
}
|
|
|
|
#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0)
|
|
#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0)
|
|
#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0)
|
|
#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0)
|
|
#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0)
|
|
#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0)
|
|
#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0)
|
|
|
|
/*
|
|
* Destroy '*fctx' if it is ready to be destroyed (i.e., if it has
|
|
* no references and is no longer waiting for any events).
|
|
*
|
|
* Requires:
|
|
* '*fctx' is shutting down.
|
|
*
|
|
* Returns:
|
|
* true if the resolver is exiting and this is the last fctx in the bucket.
|
|
*/
|
|
static isc_boolean_t
|
|
maybe_destroy(fetchctx_t *fctx, isc_boolean_t locked) {
|
|
unsigned int bucketnum;
|
|
isc_boolean_t bucket_empty = ISC_FALSE;
|
|
dns_resolver_t *res = fctx->res;
|
|
dns_validator_t *validator, *next_validator;
|
|
isc_boolean_t dodestroy = ISC_FALSE;
|
|
|
|
REQUIRE(SHUTTINGDOWN(fctx));
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
if (!locked)
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
if (fctx->pending != 0 || fctx->nqueries != 0)
|
|
goto unlock;
|
|
|
|
for (validator = ISC_LIST_HEAD(fctx->validators);
|
|
validator != NULL; validator = next_validator) {
|
|
next_validator = ISC_LIST_NEXT(validator, link);
|
|
dns_validator_cancel(validator);
|
|
}
|
|
|
|
if (fctx->references == 0 && ISC_LIST_EMPTY(fctx->validators)) {
|
|
bucket_empty = fctx_unlink(fctx);
|
|
dodestroy = ISC_TRUE;
|
|
}
|
|
unlock:
|
|
if (!locked)
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
if (dodestroy)
|
|
fctx_destroy(fctx);
|
|
return (bucket_empty);
|
|
}
|
|
|
|
/*
|
|
* The validator has finished.
|
|
*/
|
|
static void
|
|
validated(isc_task_t *task, isc_event_t *event) {
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_dbnode_t *nsnode = NULL;
|
|
dns_fetchevent_t *hevent;
|
|
dns_name_t *name;
|
|
dns_rdataset_t *ardataset = NULL;
|
|
dns_rdataset_t *asigrdataset = NULL;
|
|
dns_rdataset_t *rdataset;
|
|
dns_rdataset_t *sigrdataset;
|
|
dns_resolver_t *res;
|
|
dns_valarg_t *valarg;
|
|
dns_validatorevent_t *vevent;
|
|
fetchctx_t *fctx;
|
|
isc_boolean_t chaining;
|
|
isc_boolean_t negative;
|
|
isc_boolean_t sentresponse;
|
|
isc_result_t eresult = ISC_R_SUCCESS;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
isc_stdtime_t now;
|
|
isc_uint32_t ttl;
|
|
unsigned options;
|
|
isc_uint32_t bucketnum;
|
|
dns_fixedname_t fwild;
|
|
dns_name_t *wild = NULL;
|
|
|
|
UNUSED(task); /* for now */
|
|
|
|
REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE);
|
|
valarg = event->ev_arg;
|
|
fctx = valarg->fctx;
|
|
res = fctx->res;
|
|
addrinfo = valarg->addrinfo;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(!ISC_LIST_EMPTY(fctx->validators));
|
|
|
|
vevent = (dns_validatorevent_t *)event;
|
|
fctx->vresult = vevent->result;
|
|
|
|
FCTXTRACE("received validation completion event");
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
ISC_LIST_UNLINK(fctx->validators, vevent->validator, link);
|
|
fctx->validator = NULL;
|
|
|
|
/*
|
|
* Destroy the validator early so that we can
|
|
* destroy the fctx if necessary. Save the wildcard name.
|
|
*/
|
|
if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
|
|
wild = dns_fixedname_initname(&fwild);
|
|
dns_name_copy(dns_fixedname_name(&vevent->validator->wild),
|
|
wild, NULL);
|
|
}
|
|
dns_validator_destroy(&vevent->validator);
|
|
isc_mem_put(fctx->mctx, valarg, sizeof(*valarg));
|
|
|
|
negative = ISC_TF(vevent->rdataset == NULL);
|
|
|
|
sentresponse = ISC_TF((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) {
|
|
isc_boolean_t bucket_empty;
|
|
bucket_empty = maybe_destroy(fctx, ISC_TRUE);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
goto cleanup_event;
|
|
}
|
|
|
|
isc_stdtime_get(&now);
|
|
|
|
/*
|
|
* If chaining, we need to make sure that the right result code is
|
|
* returned, and that the rdatasets are bound.
|
|
*/
|
|
if (vevent->result == ISC_R_SUCCESS &&
|
|
!negative &&
|
|
vevent->rdataset != NULL &&
|
|
CHAINING(vevent->rdataset))
|
|
{
|
|
if (vevent->rdataset->type == dns_rdatatype_cname)
|
|
eresult = DNS_R_CNAME;
|
|
else {
|
|
INSIST(vevent->rdataset->type == dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
chaining = ISC_TRUE;
|
|
} else
|
|
chaining = ISC_FALSE;
|
|
|
|
/*
|
|
* Either we're not shutting down, or we are shutting down but want
|
|
* to cache the result anyway (if this was a validation started by
|
|
* a query with cd set)
|
|
*/
|
|
|
|
hevent = ISC_LIST_HEAD(fctx->events);
|
|
if (hevent != NULL) {
|
|
if (!negative && !chaining &&
|
|
(fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig)) {
|
|
/*
|
|
* Don't bind rdatasets; the caller
|
|
* will iterate the node.
|
|
*/
|
|
} else {
|
|
ardataset = hevent->rdataset;
|
|
asigrdataset = hevent->sigrdataset;
|
|
}
|
|
}
|
|
|
|
if (vevent->result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("validation failed");
|
|
inc_stats(res, dns_resstatscounter_valfail);
|
|
fctx->valfail++;
|
|
fctx->vresult = vevent->result;
|
|
if (fctx->vresult != DNS_R_BROKENCHAIN) {
|
|
result = ISC_R_NOTFOUND;
|
|
if (vevent->rdataset != NULL)
|
|
result = dns_db_findnode(fctx->cache,
|
|
vevent->name,
|
|
ISC_TRUE, &node);
|
|
if (result == ISC_R_SUCCESS)
|
|
(void)dns_db_deleterdataset(fctx->cache, node,
|
|
NULL,
|
|
vevent->type, 0);
|
|
if (result == ISC_R_SUCCESS &&
|
|
vevent->sigrdataset != NULL)
|
|
(void)dns_db_deleterdataset(fctx->cache, node,
|
|
NULL,
|
|
dns_rdatatype_rrsig,
|
|
vevent->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 (vevent->rdataset != NULL)
|
|
result = dns_db_findnode(fctx->cache,
|
|
vevent->name,
|
|
ISC_TRUE, &node);
|
|
if (result == ISC_R_SUCCESS) {
|
|
(void)dns_db_addrdataset(fctx->cache, node,
|
|
NULL, now,
|
|
vevent->rdataset, 0,
|
|
NULL);
|
|
}
|
|
if (result == ISC_R_SUCCESS &&
|
|
vevent->sigrdataset != NULL)
|
|
(void)dns_db_addrdataset(fctx->cache, node,
|
|
NULL, now,
|
|
vevent->sigrdataset,
|
|
0, NULL);
|
|
if (result == ISC_R_SUCCESS)
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
result = fctx->vresult;
|
|
add_bad(fctx, addrinfo, result, badns_validation);
|
|
isc_event_free(&event);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
INSIST(fctx->validator == NULL);
|
|
fctx->validator = ISC_LIST_HEAD(fctx->validators);
|
|
if (fctx->validator != NULL)
|
|
dns_validator_send(fctx->validator);
|
|
else if (sentresponse)
|
|
fctx_done(fctx, result, __LINE__); /* Locks bucket. */
|
|
else if (result == DNS_R_BROKENCHAIN) {
|
|
isc_result_t tresult;
|
|
isc_time_t expire;
|
|
isc_interval_t i;
|
|
|
|
isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0);
|
|
tresult = isc_time_nowplusinterval(&expire, &i);
|
|
if (negative &&
|
|
(fctx->type == dns_rdatatype_dnskey ||
|
|
fctx->type == dns_rdatatype_dlv ||
|
|
fctx->type == dns_rdatatype_ds) &&
|
|
tresult == ISC_R_SUCCESS)
|
|
dns_resolver_addbadcache(res, &fctx->name,
|
|
fctx->type, &expire);
|
|
fctx_done(fctx, result, __LINE__); /* Locks bucket. */
|
|
} else
|
|
fctx_try(fctx, ISC_TRUE, ISC_TRUE); /* Locks bucket. */
|
|
return;
|
|
}
|
|
|
|
|
|
if (negative) {
|
|
dns_rdatatype_t covers;
|
|
FCTXTRACE("nonexistence validation OK");
|
|
|
|
inc_stats(res, dns_resstatscounter_valnegsuccess);
|
|
|
|
/*
|
|
* Cache DS NXDOMAIN seperately to other types.
|
|
*/
|
|
if (fctx->rmessage->rcode == dns_rcode_nxdomain &&
|
|
fctx->type != dns_rdatatype_ds)
|
|
covers = dns_rdatatype_any;
|
|
else
|
|
covers = fctx->type;
|
|
|
|
result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE,
|
|
&node);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto noanswer_response;
|
|
|
|
/*
|
|
* If we are asking for a SOA record set the cache time
|
|
* to zero to facilitate locating the containing zone of
|
|
* a arbitrary zone.
|
|
*/
|
|
ttl = res->view->maxncachettl;
|
|
if (fctx->type == dns_rdatatype_soa &&
|
|
covers == dns_rdatatype_any && res->zero_no_soa_ttl)
|
|
ttl = 0;
|
|
|
|
result = ncache_adderesult(fctx->rmessage, fctx->cache, node,
|
|
covers, now, ttl, vevent->optout,
|
|
vevent->secure, ardataset, &eresult);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto noanswer_response;
|
|
goto answer_response;
|
|
} else
|
|
inc_stats(res, dns_resstatscounter_valsuccess);
|
|
|
|
FCTXTRACE("validation OK");
|
|
|
|
if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
|
|
result = dns_rdataset_addnoqname(vevent->rdataset,
|
|
vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
INSIST(vevent->sigrdataset != NULL);
|
|
vevent->sigrdataset->ttl = vevent->rdataset->ttl;
|
|
if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
|
|
result = dns_rdataset_addclosest(vevent->rdataset,
|
|
vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
} else if (vevent->rdataset->trust == dns_trust_answer &&
|
|
vevent->rdataset->type != dns_rdatatype_rrsig)
|
|
{
|
|
isc_result_t tresult;
|
|
dns_name_t *noqname = NULL;
|
|
tresult = findnoqname(fctx, vevent->name,
|
|
vevent->rdataset->type, &noqname);
|
|
if (tresult == ISC_R_SUCCESS && noqname != NULL) {
|
|
tresult = dns_rdataset_addnoqname(vevent->rdataset,
|
|
noqname);
|
|
RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The data was already cached as pending data.
|
|
* Re-cache it as secure and bind the cached
|
|
* rdatasets to the first event on the fetch
|
|
* event list.
|
|
*/
|
|
result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto noanswer_response;
|
|
|
|
options = 0;
|
|
if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0)
|
|
options = DNS_DBADD_PREFETCH;
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
|
|
vevent->rdataset, options, ardataset);
|
|
if (result != ISC_R_SUCCESS &&
|
|
result != DNS_R_UNCHANGED)
|
|
goto noanswer_response;
|
|
if (ardataset != NULL && NEGATIVE(ardataset)) {
|
|
if (NXDOMAIN(ardataset))
|
|
eresult = DNS_R_NCACHENXDOMAIN;
|
|
else
|
|
eresult = DNS_R_NCACHENXRRSET;
|
|
} else if (vevent->sigrdataset != NULL) {
|
|
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
|
|
vevent->sigrdataset, options,
|
|
asigrdataset);
|
|
if (result != ISC_R_SUCCESS &&
|
|
result != DNS_R_UNCHANGED)
|
|
goto noanswer_response;
|
|
}
|
|
|
|
if (sentresponse) {
|
|
isc_boolean_t bucket_empty = ISC_FALSE;
|
|
/*
|
|
* If we only deferred the destroy because we wanted to cache
|
|
* the data, destroy now.
|
|
*/
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
if (SHUTTINGDOWN(fctx))
|
|
bucket_empty = maybe_destroy(fctx, ISC_TRUE);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
goto cleanup_event;
|
|
}
|
|
|
|
if (!ISC_LIST_EMPTY(fctx->validators)) {
|
|
INSIST(!negative);
|
|
INSIST(fctx->type == dns_rdatatype_any ||
|
|
fctx->type == dns_rdatatype_rrsig ||
|
|
fctx->type == dns_rdatatype_sig);
|
|
/*
|
|
* Don't send a response yet - we have
|
|
* more rdatasets that still need to
|
|
* be validated.
|
|
*/
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
dns_validator_send(ISC_LIST_HEAD(fctx->validators));
|
|
goto cleanup_event;
|
|
}
|
|
|
|
answer_response:
|
|
/*
|
|
* Cache any SOA/NS/NSEC records that happened to be validated.
|
|
*/
|
|
result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(fctx->rmessage, DNS_SECTION_AUTHORITY,
|
|
&name);
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link)) {
|
|
if ((rdataset->type != dns_rdatatype_ns &&
|
|
rdataset->type != dns_rdatatype_soa &&
|
|
rdataset->type != dns_rdatatype_nsec) ||
|
|
rdataset->trust != dns_trust_secure)
|
|
continue;
|
|
for (sigrdataset = ISC_LIST_HEAD(name->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) {
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != rdataset->type)
|
|
continue;
|
|
break;
|
|
}
|
|
if (sigrdataset == NULL ||
|
|
sigrdataset->trust != dns_trust_secure)
|
|
continue;
|
|
result = dns_db_findnode(fctx->cache, name, ISC_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(fctx->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
}
|
|
|
|
/*
|
|
* Add the wild card entry.
|
|
*/
|
|
if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL &&
|
|
vevent->rdataset != NULL &&
|
|
dns_rdataset_isassociated(vevent->rdataset) &&
|
|
vevent->rdataset->trust == dns_trust_secure &&
|
|
vevent->sigrdataset != NULL &&
|
|
dns_rdataset_isassociated(vevent->sigrdataset) &&
|
|
vevent->sigrdataset->trust == dns_trust_secure &&
|
|
wild != NULL)
|
|
{
|
|
dns_dbnode_t *wnode = NULL;
|
|
|
|
result = dns_db_findnode(fctx->cache, wild, ISC_TRUE, &wnode);
|
|
if (result == ISC_R_SUCCESS)
|
|
result = dns_db_addrdataset(fctx->cache, wnode, NULL,
|
|
now, vevent->rdataset, 0,
|
|
NULL);
|
|
if (result == ISC_R_SUCCESS)
|
|
(void)dns_db_addrdataset(fctx->cache, wnode, NULL, now,
|
|
vevent->sigrdataset, 0, NULL);
|
|
if (wnode != NULL)
|
|
dns_db_detachnode(fctx->cache, &wnode);
|
|
}
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
/*
|
|
* Respond with an answer, positive or negative,
|
|
* as opposed to an error. 'node' must be non-NULL.
|
|
*/
|
|
|
|
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
|
|
|
|
if (hevent != NULL) {
|
|
/*
|
|
* Negative results must be indicated in event->result.
|
|
*/
|
|
if (dns_rdataset_isassociated(hevent->rdataset) &&
|
|
NEGATIVE(hevent->rdataset)) {
|
|
INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
|
|
eresult == DNS_R_NCACHENXRRSET);
|
|
}
|
|
hevent->result = eresult;
|
|
RUNTIME_CHECK(dns_name_copy(vevent->name,
|
|
dns_fixedname_name(&hevent->foundname), NULL)
|
|
== ISC_R_SUCCESS);
|
|
dns_db_attach(fctx->cache, &hevent->db);
|
|
dns_db_transfernode(fctx->cache, &node, &hevent->node);
|
|
clone_results(fctx);
|
|
}
|
|
|
|
noanswer_response:
|
|
if (node != NULL)
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
fctx_done(fctx, result, __LINE__); /* Locks bucket. */
|
|
|
|
cleanup_event:
|
|
INSIST(node == NULL);
|
|
isc_event_free(&event);
|
|
}
|
|
|
|
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 inline isc_result_t
|
|
findnoqname(fetchctx_t *fctx, 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(fctx->rmessage, section);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(fctx->rmessage, section)) {
|
|
dns_name_t *nsec = NULL;
|
|
dns_message_currentname(fctx->rmessage, section, &nsec);
|
|
for (nrdataset = ISC_LIST_HEAD(nsec->list);
|
|
nrdataset != NULL; nrdataset = next) {
|
|
isc_boolean_t data = ISC_FALSE, exists = ISC_FALSE;
|
|
isc_boolean_t optout = ISC_FALSE, unknown = ISC_FALSE;
|
|
isc_boolean_t setclosest = ISC_FALSE;
|
|
isc_boolean_t setnearest = ISC_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 inline isc_result_t
|
|
cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo,
|
|
isc_stdtime_t now)
|
|
{
|
|
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
|
|
dns_rdataset_t *addedrdataset = NULL;
|
|
dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL;
|
|
dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL;
|
|
dns_dbnode_t *node = NULL, **anodep = NULL;
|
|
dns_db_t **adbp = NULL;
|
|
dns_name_t *aname = NULL;
|
|
dns_resolver_t *res = fctx->res;
|
|
isc_boolean_t need_validation = ISC_FALSE;
|
|
isc_boolean_t secure_domain = ISC_FALSE;
|
|
isc_boolean_t have_answer = ISC_FALSE;
|
|
isc_result_t result, eresult = ISC_R_SUCCESS;
|
|
dns_fetchevent_t *event = NULL;
|
|
unsigned int options;
|
|
isc_task_t *task;
|
|
isc_boolean_t fail;
|
|
unsigned int valoptions = 0;
|
|
isc_boolean_t checknta = ISC_TRUE;
|
|
|
|
/*
|
|
* The appropriate bucket lock must be held.
|
|
*/
|
|
task = res->buckets[fctx->bucketnum].task;
|
|
|
|
/*
|
|
* Is DNSSEC validation required for this name?
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NONTA;
|
|
checknta = ISC_FALSE;
|
|
}
|
|
|
|
if (res->view->enablevalidation) {
|
|
result = issecuredomain(res->view, name, fctx->type,
|
|
now, checknta, &secure_domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
if (!secure_domain && res->view->dlv != NULL) {
|
|
valoptions |= DNS_VALIDATOR_DLV;
|
|
secure_domain = ISC_TRUE;
|
|
}
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NOCDFLAG;
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) {
|
|
need_validation = ISC_FALSE;
|
|
} else {
|
|
need_validation = secure_domain;
|
|
}
|
|
|
|
if (((name->attributes & DNS_NAMEATTR_ANSWER) != 0) &&
|
|
(!need_validation))
|
|
{
|
|
have_answer = ISC_TRUE;
|
|
event = ISC_LIST_HEAD(fctx->events);
|
|
if (event != NULL) {
|
|
adbp = &event->db;
|
|
aname = dns_fixedname_name(&event->foundname);
|
|
result = dns_name_copy(name, aname, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
anodep = &event->node;
|
|
/*
|
|
* If this is an ANY, SIG or RRSIG query, we're not
|
|
* going to return any rdatasets, unless we encountered
|
|
* a CNAME or DNAME as "the answer". In this case,
|
|
* we're going to return DNS_R_CNAME or DNS_R_DNAME
|
|
* and we must set up the rdatasets.
|
|
*/
|
|
if ((fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_rrsig &&
|
|
fctx->type != dns_rdatatype_sig) ||
|
|
(name->attributes & DNS_NAMEATTR_CHAINING) != 0)
|
|
{
|
|
ardataset = event->rdataset;
|
|
asigrdataset = event->sigrdataset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find or create the cache node.
|
|
*/
|
|
node = NULL;
|
|
result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Cache or validate each cacheable rdataset.
|
|
*/
|
|
fail = ISC_TF((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;
|
|
}
|
|
|
|
/*
|
|
* 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, name,
|
|
rdataset->type,
|
|
&noqname);
|
|
if (tresult == ISC_R_SUCCESS &&
|
|
noqname != NULL)
|
|
{
|
|
(void) dns_rdataset_addnoqname(
|
|
rdataset, noqname);
|
|
}
|
|
}
|
|
if ((fctx->options &
|
|
DNS_FETCHOPT_PREFETCH) != 0)
|
|
{
|
|
options = DNS_DBADD_PREFETCH;
|
|
}
|
|
if ((fctx->options &
|
|
DNS_FETCHOPT_NOCACHED) != 0)
|
|
{
|
|
options |= DNS_DBADD_FORCE;
|
|
}
|
|
addedrdataset = ardataset;
|
|
result = dns_db_addrdataset(fctx->cache, node,
|
|
NULL, now, rdataset,
|
|
options,
|
|
addedrdataset);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
if (!need_validation &&
|
|
ardataset != NULL &&
|
|
NEGATIVE(ardataset))
|
|
{
|
|
/*
|
|
* The answer in the cache is
|
|
* better than the answer we
|
|
* found, and is a negative
|
|
* cache entry, so we must set
|
|
* eresult appropriately.
|
|
*/
|
|
if (NXDOMAIN(ardataset)) {
|
|
eresult =
|
|
DNS_R_NCACHENXDOMAIN;
|
|
} else {
|
|
eresult =
|
|
DNS_R_NCACHENXRRSET;
|
|
}
|
|
/*
|
|
* We have a negative response
|
|
* from the cache so don't
|
|
* attempt to add the RRSIG
|
|
* rrset.
|
|
*/
|
|
continue;
|
|
}
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
if (sigrdataset != NULL) {
|
|
addedrdataset = asigrdataset;
|
|
result = dns_db_addrdataset(fctx->cache,
|
|
node, NULL, now,
|
|
sigrdataset,
|
|
options,
|
|
addedrdataset);
|
|
if (result == DNS_R_UNCHANGED) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
} else if (!ANSWER(rdataset)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ANSWER(rdataset) && need_validation) {
|
|
if (fctx->type != dns_rdatatype_any &&
|
|
fctx->type != dns_rdatatype_rrsig &&
|
|
fctx->type != dns_rdatatype_sig)
|
|
{
|
|
/*
|
|
* This is The Answer. We will
|
|
* validate it, but first we cache
|
|
* the rest of the response - it may
|
|
* contain useful keys.
|
|
*/
|
|
INSIST(valrdataset == NULL &&
|
|
valsigrdataset == NULL);
|
|
valrdataset = rdataset;
|
|
valsigrdataset = sigrdataset;
|
|
} else {
|
|
/*
|
|
* This is one of (potentially)
|
|
* multiple answers to an ANY
|
|
* or SIG query. To keep things
|
|
* simple, we just start the
|
|
* validator right away rather
|
|
* than caching first and
|
|
* having to remember which
|
|
* rdatasets needed validation.
|
|
*/
|
|
result = valcreate(fctx, addrinfo,
|
|
name, rdataset->type,
|
|
rdataset,
|
|
sigrdataset,
|
|
valoptions, task);
|
|
}
|
|
} else if (CHAINING(rdataset)) {
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
eresult = DNS_R_CNAME;
|
|
} else {
|
|
INSIST(rdataset->type ==
|
|
dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
}
|
|
} else if (!EXTERNAL(rdataset)) {
|
|
/*
|
|
* It's OK to cache this rdataset now.
|
|
*/
|
|
if (ANSWER(rdataset)) {
|
|
addedrdataset = ardataset;
|
|
} else if (ANSWERSIG(rdataset)) {
|
|
addedrdataset = asigrdataset;
|
|
} else {
|
|
addedrdataset = NULL;
|
|
}
|
|
if (CHAINING(rdataset)) {
|
|
if (rdataset->type == dns_rdatatype_cname) {
|
|
eresult = DNS_R_CNAME;
|
|
} else {
|
|
INSIST(rdataset->type ==
|
|
dns_rdatatype_dname);
|
|
eresult = DNS_R_DNAME;
|
|
}
|
|
}
|
|
if (rdataset->trust == dns_trust_glue &&
|
|
(rdataset->type == dns_rdatatype_ns ||
|
|
(rdataset->type == dns_rdatatype_rrsig &&
|
|
rdataset->covers == dns_rdatatype_ns)))
|
|
{
|
|
/*
|
|
* If the trust level is 'dns_trust_glue'
|
|
* then we are adding data from a referral
|
|
* we got while executing the search algorithm.
|
|
* New referral data always takes precedence
|
|
* over the existing cache contents.
|
|
*/
|
|
options = DNS_DBADD_FORCE;
|
|
} else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0)
|
|
{
|
|
options = DNS_DBADD_PREFETCH;
|
|
} else {
|
|
options = 0;
|
|
}
|
|
|
|
if (ANSWER(rdataset) &&
|
|
rdataset->type != dns_rdatatype_rrsig)
|
|
{
|
|
isc_result_t tresult;
|
|
dns_name_t *noqname = NULL;
|
|
tresult = findnoqname(fctx, 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, addrinfo, name, vtype, valrdataset,
|
|
valsigrdataset, valoptions, task);
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS && have_answer) {
|
|
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
|
|
if (event != NULL) {
|
|
/*
|
|
* Negative results must be indicated in event->result.
|
|
*/
|
|
if (dns_rdataset_isassociated(event->rdataset) &&
|
|
NEGATIVE(event->rdataset))
|
|
{
|
|
INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
|
|
eresult == DNS_R_NCACHENXRRSET);
|
|
}
|
|
event->result = eresult;
|
|
if (adbp != NULL && *adbp != NULL) {
|
|
if (anodep != NULL && *anodep != NULL) {
|
|
dns_db_detachnode(*adbp, anodep);
|
|
}
|
|
dns_db_detach(adbp);
|
|
}
|
|
dns_db_attach(fctx->cache, adbp);
|
|
dns_db_transfernode(fctx->cache, &node, anodep);
|
|
clone_results(fctx);
|
|
}
|
|
}
|
|
|
|
if (node != NULL) {
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
static inline isc_result_t
|
|
cache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now)
|
|
{
|
|
isc_result_t result;
|
|
dns_section_t section;
|
|
dns_name_t *name;
|
|
|
|
FCTXTRACE("cache_message");
|
|
|
|
fctx->attributes &= ~FCTX_ATTR_WANTCACHE;
|
|
|
|
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
|
|
for (section = DNS_SECTION_ANSWER;
|
|
section <= DNS_SECTION_ADDITIONAL;
|
|
section++) {
|
|
result = dns_message_firstname(fctx->rmessage, section);
|
|
while (result == ISC_R_SUCCESS) {
|
|
name = NULL;
|
|
dns_message_currentname(fctx->rmessage, section,
|
|
&name);
|
|
if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {
|
|
result = cache_name(fctx, name, addrinfo, now);
|
|
if (result != ISC_R_SUCCESS)
|
|
break;
|
|
}
|
|
result = dns_message_nextname(fctx->rmessage, section);
|
|
}
|
|
if (result != ISC_R_NOMORE)
|
|
break;
|
|
}
|
|
if (result == ISC_R_NOMORE)
|
|
result = ISC_R_SUCCESS;
|
|
|
|
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Do what dns_ncache_addoptout() does, and then compute an appropriate eresult.
|
|
*/
|
|
static isc_result_t
|
|
ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
|
|
isc_boolean_t optout, isc_boolean_t 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, maxttl, optout, ardataset);
|
|
else
|
|
result = dns_ncache_add(message, cache, node, covers, now,
|
|
maxttl, ardataset);
|
|
if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
|
|
/*
|
|
* If the cache now contains a negative entry and we
|
|
* care about whether it is DNS_R_NCACHENXDOMAIN or
|
|
* DNS_R_NCACHENXRRSET then extract it.
|
|
*/
|
|
if (NEGATIVE(ardataset)) {
|
|
/*
|
|
* The cache data is a negative cache entry.
|
|
*/
|
|
if (NXDOMAIN(ardataset))
|
|
*eresultp = DNS_R_NCACHENXDOMAIN;
|
|
else
|
|
*eresultp = DNS_R_NCACHENXRRSET;
|
|
} else {
|
|
/*
|
|
* Either we don't care about the nature of the
|
|
* cache rdataset (because no fetch is interested
|
|
* in the outcome), or the cache rdataset is not
|
|
* a negative cache entry. Whichever case it is,
|
|
* we can return success.
|
|
*
|
|
* XXXRTH There's a CNAME/DNAME problem here.
|
|
*/
|
|
*eresultp = ISC_R_SUCCESS;
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset))
|
|
dns_rdataset_disassociate(ardataset);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static inline isc_result_t
|
|
ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
|
|
dns_rdatatype_t covers, isc_stdtime_t now)
|
|
{
|
|
isc_result_t result, eresult;
|
|
dns_name_t *name;
|
|
dns_resolver_t *res;
|
|
dns_db_t **adbp;
|
|
dns_dbnode_t *node, **anodep;
|
|
dns_rdataset_t *ardataset;
|
|
isc_boolean_t need_validation, secure_domain;
|
|
dns_name_t *aname;
|
|
dns_fetchevent_t *event;
|
|
isc_uint32_t ttl;
|
|
unsigned int valoptions = 0;
|
|
isc_boolean_t checknta = ISC_TRUE;
|
|
|
|
FCTXTRACE("ncache_message");
|
|
|
|
fctx->attributes &= ~FCTX_ATTR_WANTNCACHE;
|
|
|
|
res = fctx->res;
|
|
need_validation = ISC_FALSE;
|
|
POST(need_validation);
|
|
secure_domain = ISC_FALSE;
|
|
eresult = ISC_R_SUCCESS;
|
|
name = &fctx->name;
|
|
node = NULL;
|
|
|
|
/*
|
|
* XXXMPA remove when we follow cnames and adjust the setting
|
|
* of FCTX_ATTR_WANTNCACHE in rctx_answer_none().
|
|
*/
|
|
INSIST(fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0);
|
|
|
|
/*
|
|
* Is DNSSEC validation required for this name?
|
|
*/
|
|
if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) {
|
|
valoptions |= DNS_VALIDATOR_NONTA;
|
|
checknta = ISC_FALSE;
|
|
}
|
|
|
|
if (fctx->res->view->enablevalidation) {
|
|
result = issecuredomain(res->view, name, fctx->type,
|
|
now, checknta, &secure_domain);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (!secure_domain && res->view->dlv != NULL) {
|
|
valoptions |= DNS_VALIDATOR_DLV;
|
|
secure_domain = ISC_TRUE;
|
|
}
|
|
}
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0)
|
|
valoptions |= DNS_VALIDATOR_NOCDFLAG;
|
|
|
|
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0)
|
|
need_validation = ISC_FALSE;
|
|
else
|
|
need_validation = secure_domain;
|
|
|
|
if (secure_domain) {
|
|
/*
|
|
* Mark all rdatasets as pending.
|
|
*/
|
|
dns_rdataset_t *trdataset;
|
|
dns_name_t *tname;
|
|
|
|
result = dns_message_firstname(fctx->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
while (result == ISC_R_SUCCESS) {
|
|
tname = NULL;
|
|
dns_message_currentname(fctx->rmessage,
|
|
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(fctx->rmessage,
|
|
DNS_SECTION_AUTHORITY);
|
|
}
|
|
if (result != ISC_R_NOMORE)
|
|
return (result);
|
|
|
|
}
|
|
|
|
if (need_validation) {
|
|
/*
|
|
* Do negative response validation.
|
|
*/
|
|
result = valcreate(fctx, addrinfo, name, fctx->type,
|
|
NULL, NULL, valoptions,
|
|
res->buckets[fctx->bucketnum].task);
|
|
/*
|
|
* If validation is necessary, return now. Otherwise continue
|
|
* to process the message, letting the validation complete
|
|
* in its own good time.
|
|
*/
|
|
return (result);
|
|
}
|
|
|
|
LOCK(&res->buckets[fctx->bucketnum].lock);
|
|
|
|
adbp = NULL;
|
|
aname = NULL;
|
|
anodep = NULL;
|
|
ardataset = NULL;
|
|
if (!HAVE_ANSWER(fctx)) {
|
|
event = ISC_LIST_HEAD(fctx->events);
|
|
if (event != NULL) {
|
|
adbp = &event->db;
|
|
aname = dns_fixedname_name(&event->foundname);
|
|
result = dns_name_copy(name, aname, NULL);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto unlock;
|
|
anodep = &event->node;
|
|
ardataset = event->rdataset;
|
|
}
|
|
} else
|
|
event = NULL;
|
|
|
|
result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto unlock;
|
|
|
|
/*
|
|
* If we are asking for a SOA record set the cache time
|
|
* to zero to facilitate locating the containing zone of
|
|
* a arbitrary zone.
|
|
*/
|
|
ttl = fctx->res->view->maxncachettl;
|
|
if (fctx->type == dns_rdatatype_soa &&
|
|
covers == dns_rdatatype_any &&
|
|
fctx->res->zero_no_soa_ttl)
|
|
ttl = 0;
|
|
|
|
result = ncache_adderesult(fctx->rmessage, fctx->cache, node,
|
|
covers, now, ttl, ISC_FALSE,
|
|
ISC_FALSE, ardataset, &eresult);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto unlock;
|
|
|
|
if (!HAVE_ANSWER(fctx)) {
|
|
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
|
|
if (event != NULL) {
|
|
event->result = eresult;
|
|
if (adbp != NULL && *adbp != NULL) {
|
|
if (anodep != NULL && *anodep != NULL)
|
|
dns_db_detachnode(*adbp, anodep);
|
|
dns_db_detach(adbp);
|
|
}
|
|
dns_db_attach(fctx->cache, adbp);
|
|
dns_db_transfernode(fctx->cache, &node, anodep);
|
|
clone_results(fctx);
|
|
}
|
|
}
|
|
|
|
unlock:
|
|
UNLOCK(&res->buckets[fctx->bucketnum].lock);
|
|
|
|
if (node != NULL)
|
|
dns_db_detachnode(fctx->cache, &node);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static inline void
|
|
mark_related(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
isc_boolean_t external, isc_boolean_t gluing)
|
|
{
|
|
name->attributes |= DNS_NAMEATTR_CACHE;
|
|
if (gluing) {
|
|
rdataset->trust = dns_trust_glue;
|
|
/*
|
|
* Glue with 0 TTL causes problems. We force the TTL to
|
|
* 1 second to prevent this.
|
|
*/
|
|
if (rdataset->ttl == 0)
|
|
rdataset->ttl = 1;
|
|
} else
|
|
rdataset->trust = dns_trust_additional;
|
|
/*
|
|
* Avoid infinite loops by only marking new rdatasets.
|
|
*/
|
|
if (!CACHE(rdataset)) {
|
|
name->attributes |= DNS_NAMEATTR_CHASE;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CHASE;
|
|
}
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
if (external)
|
|
rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
|
|
}
|
|
|
|
static isc_result_t
|
|
check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
|
|
dns_section_t section)
|
|
{
|
|
fetchctx_t *fctx = arg;
|
|
isc_result_t result;
|
|
dns_name_t *name = NULL;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
isc_boolean_t external;
|
|
dns_rdatatype_t rtype;
|
|
isc_boolean_t 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
|
|
|
|
gluing = ISC_TF(GLUING(fctx) ||
|
|
(fctx->type == dns_rdatatype_ns &&
|
|
dns_name_equal(&fctx->name, dns_rootname)));
|
|
|
|
result = dns_message_findname(fctx->rmessage, section, addname,
|
|
dns_rdatatype_any, 0, &name, NULL);
|
|
if (result == ISC_R_SUCCESS) {
|
|
external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
|
|
if (type == dns_rdatatype_a) {
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link)) {
|
|
if (rdataset->type == dns_rdatatype_rrsig)
|
|
rtype = rdataset->covers;
|
|
else
|
|
rtype = rdataset->type;
|
|
if (rtype == dns_rdatatype_a ||
|
|
rtype == dns_rdatatype_aaaa)
|
|
mark_related(name, rdataset, external,
|
|
gluing);
|
|
}
|
|
} else {
|
|
result = dns_message_findtype(name, type, 0,
|
|
&rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
mark_related(name, rdataset, external, gluing);
|
|
/*
|
|
* Do we have its SIG too?
|
|
*/
|
|
rdataset = NULL;
|
|
result = dns_message_findtype(name,
|
|
dns_rdatatype_rrsig,
|
|
type, &rdataset);
|
|
if (result == ISC_R_SUCCESS)
|
|
mark_related(name, rdataset, external,
|
|
gluing);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
check_related(void *arg, const dns_name_t *addname, dns_rdatatype_t type) {
|
|
return (check_section(arg, addname, type, DNS_SECTION_ADDITIONAL));
|
|
}
|
|
|
|
#ifndef CHECK_FOR_GLUE_IN_ANSWER
|
|
#define CHECK_FOR_GLUE_IN_ANSWER 0
|
|
#endif
|
|
#if CHECK_FOR_GLUE_IN_ANSWER
|
|
static isc_result_t
|
|
check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type) {
|
|
return (check_section(arg, addname, type, DNS_SECTION_ANSWER));
|
|
}
|
|
#endif
|
|
|
|
static isc_boolean_t
|
|
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 (ISC_TRUE);
|
|
|
|
/*
|
|
* If the owner name matches one in the exclusion list, either exactly
|
|
* or partially, allow it.
|
|
*/
|
|
if (view->answeracl_exclude != NULL) {
|
|
dns_rbtnode_t *node = NULL;
|
|
|
|
result = dns_rbt_findnode(view->answeracl_exclude, name, NULL,
|
|
&node, NULL, 0, NULL, NULL);
|
|
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
|
|
return (ISC_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, NULL, 0, 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 (ISC_FALSE);
|
|
}
|
|
}
|
|
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
|
|
dns_rdataset_t *rdataset, isc_boolean_t *chainingp)
|
|
{
|
|
isc_result_t result;
|
|
dns_rbtnode_t *node = NULL;
|
|
char qnamebuf[DNS_NAME_FORMATSIZE];
|
|
char tnamebuf[DNS_NAME_FORMATSIZE];
|
|
char classbuf[64];
|
|
char typebuf[64];
|
|
dns_name_t *tname = NULL;
|
|
dns_rdata_cname_t cname;
|
|
dns_rdata_dname_t dname;
|
|
dns_view_t *view = fctx->res->view;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
unsigned int nlabels;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t prefix;
|
|
|
|
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 (ISC_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:
|
|
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(qname) -
|
|
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)
|
|
return (ISC_TRUE);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
break;
|
|
default:
|
|
INSIST(0);
|
|
}
|
|
|
|
if (chainingp != NULL)
|
|
*chainingp = ISC_TRUE;
|
|
|
|
if (view->denyanswernames == NULL)
|
|
return (ISC_TRUE);
|
|
|
|
/*
|
|
* If the owner name matches one in the exclusion list, either exactly
|
|
* or partially, allow it.
|
|
*/
|
|
if (view->answernames_exclude != NULL) {
|
|
result = dns_rbt_findnode(view->answernames_exclude, qname,
|
|
NULL, &node, NULL, 0, NULL, NULL);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
/*
|
|
* If the target name is a subdomain of the search domain, allow it.
|
|
*/
|
|
if (dns_name_issubdomain(tname, &fctx->domain))
|
|
return (ISC_TRUE);
|
|
|
|
/*
|
|
* Otherwise, apply filters.
|
|
*/
|
|
result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node,
|
|
NULL, 0, NULL, NULL);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
dns_name_format(qname, qnamebuf, sizeof(qnamebuf));
|
|
dns_name_format(tname, tnamebuf, sizeof(tnamebuf));
|
|
dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
|
|
dns_rdataclass_format(view->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"%s target %s denied for %s/%s",
|
|
typebuf, tnamebuf, qnamebuf, classbuf);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
static void
|
|
trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) {
|
|
char ns_namebuf[DNS_NAME_FORMATSIZE];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) {
|
|
dns_name_format(name, ns_namebuf, sizeof(ns_namebuf));
|
|
dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10),
|
|
"fctx %p: trimming ttl of %s/NS for %s/%s: "
|
|
"%u -> %u", fctx, ns_namebuf, namebuf, tbuf,
|
|
rdataset->ttl, fctx->ns_ttl);
|
|
rdataset->ttl = fctx->ns_ttl;
|
|
}
|
|
}
|
|
|
|
static isc_boolean_t
|
|
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 (ISC_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 (ISC_FALSE);
|
|
}
|
|
if (rdataset->rdclass != fctx->res->rdclass) {
|
|
log_formerr(fctx, "Mismatched class in answer");
|
|
return (ISC_FALSE);
|
|
}
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
static void
|
|
fctx_increference(fetchctx_t *fctx) {
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
fctx->references++;
|
|
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
fctx_decreference(fetchctx_t *fctx) {
|
|
isc_boolean_t bucket_empty = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
|
|
INSIST(fctx->references > 0);
|
|
fctx->references--;
|
|
if (fctx->references == 0) {
|
|
/*
|
|
* No one cares about the result of this fetch anymore.
|
|
*/
|
|
if (fctx->pending == 0 && fctx->nqueries == 0 &&
|
|
ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) {
|
|
/*
|
|
* This fctx is already shutdown; we were just
|
|
* waiting for the last reference to go away.
|
|
*/
|
|
bucket_empty = fctx_unlink(fctx);
|
|
fctx_destroy(fctx);
|
|
} else {
|
|
/*
|
|
* Initiate shutdown.
|
|
*/
|
|
fctx_shutdown(fctx);
|
|
}
|
|
}
|
|
return (bucket_empty);
|
|
}
|
|
|
|
static void
|
|
resume_dslookup(isc_task_t *task, isc_event_t *event) {
|
|
dns_fetchevent_t *fevent;
|
|
dns_resolver_t *res;
|
|
fetchctx_t *fctx;
|
|
isc_result_t result;
|
|
isc_boolean_t bucket_empty;
|
|
isc_boolean_t locked = ISC_FALSE;
|
|
unsigned int bucketnum;
|
|
dns_rdataset_t nameservers;
|
|
dns_fixedname_t fixed;
|
|
dns_name_t *domain;
|
|
|
|
REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
|
|
fevent = (dns_fetchevent_t *)event;
|
|
fctx = event->ev_arg;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fctx->res;
|
|
|
|
UNUSED(task);
|
|
FCTXTRACE("resume_dslookup");
|
|
|
|
if (fevent->node != NULL)
|
|
dns_db_detachnode(fevent->db, &fevent->node);
|
|
if (fevent->db != NULL)
|
|
dns_db_detach(&fevent->db);
|
|
|
|
dns_rdataset_init(&nameservers);
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
|
|
/*
|
|
* Note: fevent->rdataset must be disassociated and
|
|
* isc_event_free(&event) be called before resuming
|
|
* processing of the 'fctx' to prevent use-after-free.
|
|
* 'fevent' is set to NULL so as to not have a dangling
|
|
* pointer.
|
|
*/
|
|
if (fevent->result == ISC_R_CANCELED) {
|
|
if (dns_rdataset_isassociated(fevent->rdataset)) {
|
|
dns_rdataset_disassociate(fevent->rdataset);
|
|
}
|
|
fevent = NULL;
|
|
isc_event_free(&event);
|
|
|
|
dns_resolver_destroyfetch(&fctx->nsfetch);
|
|
fctx_done(fctx, ISC_R_CANCELED, __LINE__);
|
|
} else if (fevent->result == ISC_R_SUCCESS) {
|
|
FCTXTRACE("resuming DS lookup");
|
|
|
|
dns_resolver_destroyfetch(&fctx->nsfetch);
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
dns_rdataset_clone(fevent->rdataset, &fctx->nameservers);
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = ISC_TRUE;
|
|
log_ns_ttl(fctx, "resume_dslookup");
|
|
|
|
if (dns_rdataset_isassociated(fevent->rdataset)) {
|
|
dns_rdataset_disassociate(fevent->rdataset);
|
|
}
|
|
fevent = NULL;
|
|
isc_event_free(&event);
|
|
|
|
fcount_decr(fctx);
|
|
dns_name_free(&fctx->domain, fctx->mctx);
|
|
dns_name_init(&fctx->domain, NULL);
|
|
result = dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
goto cleanup;
|
|
}
|
|
result = fcount_incr(fctx, ISC_TRUE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
goto cleanup;
|
|
}
|
|
/*
|
|
* Try again.
|
|
*/
|
|
fctx_try(fctx, ISC_TRUE, ISC_FALSE);
|
|
} else {
|
|
unsigned int n;
|
|
dns_rdataset_t *nsrdataset = NULL;
|
|
|
|
/*
|
|
* Retrieve state from fctx->nsfetch before we destroy it.
|
|
*/
|
|
domain = dns_fixedname_initname(&fixed);
|
|
dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL);
|
|
if (dns_name_equal(&fctx->nsname, domain)) {
|
|
if (dns_rdataset_isassociated(fevent->rdataset)) {
|
|
dns_rdataset_disassociate(fevent->rdataset);
|
|
}
|
|
fevent = NULL;
|
|
isc_event_free(&event);
|
|
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
dns_resolver_destroyfetch(&fctx->nsfetch);
|
|
goto cleanup;
|
|
}
|
|
if (dns_rdataset_isassociated(
|
|
&fctx->nsfetch->private->nameservers)) {
|
|
dns_rdataset_clone(
|
|
&fctx->nsfetch->private->nameservers,
|
|
&nameservers);
|
|
nsrdataset = &nameservers;
|
|
} else
|
|
domain = NULL;
|
|
dns_resolver_destroyfetch(&fctx->nsfetch);
|
|
n = dns_name_countlabels(&fctx->nsname);
|
|
dns_name_getlabelsequence(&fctx->nsname, 1, n - 1,
|
|
&fctx->nsname);
|
|
|
|
if (dns_rdataset_isassociated(fevent->rdataset))
|
|
dns_rdataset_disassociate(fevent->rdataset);
|
|
fevent = NULL;
|
|
isc_event_free(&event);
|
|
|
|
FCTXTRACE("continuing to look for parent's NS records");
|
|
|
|
result = dns_resolver_createfetch(fctx->res, &fctx->nsname,
|
|
dns_rdatatype_ns, domain,
|
|
nsrdataset, NULL, NULL, 0,
|
|
fctx->options, 0, NULL, task,
|
|
resume_dslookup, fctx,
|
|
&fctx->nsrrset, NULL,
|
|
&fctx->nsfetch);
|
|
/*
|
|
* fevent->rdataset (a.k.a. fctx->nsrrset) must not be
|
|
* accessed below this point to prevent races with
|
|
* another thread concurrently processing the fetch.
|
|
*/
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, result, __LINE__);
|
|
} else {
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
locked = ISC_TRUE;
|
|
fctx->references++;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
INSIST(event == NULL);
|
|
INSIST(fevent == NULL);
|
|
if (dns_rdataset_isassociated(&nameservers))
|
|
dns_rdataset_disassociate(&nameservers);
|
|
if (!locked)
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
bucket_empty = fctx_decreference(fctx);
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
}
|
|
|
|
static inline 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,
|
|
ISC_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];
|
|
isc_uint16_t buflen, i;
|
|
unsigned char *p, *nsid;
|
|
unsigned char *buf = NULL, *pbuf = NULL;
|
|
|
|
/* Allocate buffer for storing hex version of the NSID */
|
|
buflen = (isc_uint16_t)nsid_len * 2 + 1;
|
|
buf = isc_mem_get(mctx, buflen);
|
|
if (buf == NULL)
|
|
goto cleanup;
|
|
pbuf = isc_mem_get(mctx, nsid_len + 1);
|
|
if (pbuf == NULL)
|
|
goto cleanup;
|
|
|
|
/* Convert to hex */
|
|
p = buf;
|
|
nsid = isc_buffer_current(opt);
|
|
for (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 (i = 0; i < nsid_len; i++) {
|
|
if (isprint(nsid[i]))
|
|
*p++ = nsid[i];
|
|
else
|
|
*p++ = '.';
|
|
}
|
|
*p = '\0';
|
|
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, level,
|
|
"received NSID %s (\"%s\") from %s", buf, pbuf, addrbuf);
|
|
cleanup:
|
|
if (pbuf != NULL)
|
|
isc_mem_put(mctx, pbuf, nsid_len + 1);
|
|
if (buf != NULL)
|
|
isc_mem_put(mctx, buf, buflen);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
iscname(fetchctx_t *fctx) {
|
|
isc_result_t result;
|
|
|
|
result = dns_message_findname(fctx->rmessage, DNS_SECTION_ANSWER,
|
|
&fctx->name, dns_rdatatype_cname, 0,
|
|
NULL, NULL);
|
|
return (result == ISC_R_SUCCESS ? ISC_TRUE : ISC_FALSE);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
betterreferral(fetchctx_t *fctx) {
|
|
isc_result_t result;
|
|
dns_name_t *name;
|
|
dns_rdataset_t *rdataset;
|
|
dns_message_t *message = fctx->rmessage;
|
|
|
|
for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) {
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
|
|
if (!isstrictsubdomain(name, &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 (ISC_TRUE);
|
|
}
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
/*
|
|
* resquery_response():
|
|
* Handles responses received in response to iterative queries sent by
|
|
* resquery_send(). Sets up a response context (respctx_t).
|
|
*/
|
|
static void
|
|
resquery_response(isc_task_t *task, isc_event_t *event) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
resquery_t *query = event->ev_arg;
|
|
dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
|
|
fetchctx_t *fctx;
|
|
respctx_t rctx;
|
|
|
|
REQUIRE(VALID_QUERY(query));
|
|
fctx = query->fctx;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
REQUIRE(event->ev_type == DNS_EVENT_DISPATCH);
|
|
|
|
QTRACE("response");
|
|
|
|
if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET)
|
|
inc_stats(fctx->res, dns_resstatscounter_responsev4);
|
|
else
|
|
inc_stats(fctx->res, dns_resstatscounter_responsev6);
|
|
|
|
(void)isc_timer_touch(fctx->timer);
|
|
|
|
rctx_respinit(task, devent, query, fctx, &rctx);
|
|
|
|
if (fctx->res->exiting) {
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
FCTXTRACE("resolver shutting down");
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
fctx->timeouts = 0;
|
|
fctx->timeout = ISC_FALSE;
|
|
fctx->addrinfo = query->addrinfo;
|
|
|
|
/*
|
|
* Check whether the dispatcher has failed; if so we're done
|
|
*/
|
|
result = rctx_dispfail(&rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return;
|
|
}
|
|
|
|
if (query->tsig != NULL) {
|
|
result = dns_message_setquerytsig(fctx->rmessage, query->tsig);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("unable to set query tsig", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (query->tsigkey) {
|
|
result = dns_message_settsigkey(fctx->rmessage, query->tsigkey);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("unable to set tsig key", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
dns_message_setclass(fctx->rmessage, fctx->res->rdclass);
|
|
|
|
if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) {
|
|
if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
|
|
dns_adb_setudpsize(fctx->adb, query->addrinfo,
|
|
isc_buffer_usedlength(&devent->buffer));
|
|
} else {
|
|
dns_adb_plainresponse(fctx->adb, query->addrinfo);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse response message.
|
|
*/
|
|
result = rctx_parse(&rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Log the incoming packet.
|
|
*/
|
|
rctx_logpacket(&rctx);
|
|
|
|
if (fctx->rmessage->rdclass != fctx->res->rdclass) {
|
|
rctx.resend = ISC_TRUE;
|
|
FCTXTRACE("bad class");
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Process receive opt record.
|
|
*/
|
|
rctx.opt = dns_message_getopt(fctx->rmessage);
|
|
if (rctx.opt != NULL) {
|
|
rctx_opt(&rctx);
|
|
}
|
|
|
|
if (fctx->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 = ISC_TRUE;
|
|
if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
isc_sockaddr_format(&query->addrinfo->sockaddr,
|
|
addrbuf, sizeof(addrbuf));
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"bad cookie from %s", addrbuf);
|
|
}
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Is the question the same as the one we asked?
|
|
* NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must have
|
|
* the same question.
|
|
* FORMERR/NOTIMP if they have a question section then it must match.
|
|
*/
|
|
switch (fctx->rmessage->rcode) {
|
|
case dns_rcode_notimp:
|
|
case dns_rcode_formerr:
|
|
if (fctx->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);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("response did not match question", result);
|
|
rctx.nextitem = ISC_TRUE;
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the message is signed, check the signature. If not, this
|
|
* returns success anyway.
|
|
*/
|
|
result = dns_message_checksig(fctx->rmessage, fctx->res->view);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("signature check failed", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The dispatcher should ensure we only get responses with QR set.
|
|
*/
|
|
INSIST((fctx->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0);
|
|
/*
|
|
* INSIST() that the message comes from the place we sent it to,
|
|
* since the dispatch code should ensure this.
|
|
*
|
|
* INSIST() that the message id is correct (this should also be
|
|
* ensured by the dispatch code).
|
|
*/
|
|
|
|
rctx_edns(&rctx);
|
|
|
|
/*
|
|
* Deal with truncated responses by retrying using TCP.
|
|
*/
|
|
if ((fctx->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) {
|
|
rctx.truncated = ISC_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 = ISC_TRUE;
|
|
} else {
|
|
rctx.retryopts |= DNS_FETCHOPT_TCP;
|
|
rctx.resend = ISC_TRUE;
|
|
}
|
|
FCTXTRACE3("message truncated", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Is it a query response?
|
|
*/
|
|
if (fctx->rmessage->opcode != dns_opcode_query) {
|
|
rctx.broken_server = DNS_R_UNEXPECTEDOPCODE;
|
|
rctx.next_server = ISC_TRUE;
|
|
FCTXTRACE("invalid message opcode");
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Update statistics about erroneous responses.
|
|
*/
|
|
switch (fctx->rmessage->rcode) {
|
|
case dns_rcode_noerror:
|
|
/* no error */
|
|
break;
|
|
case dns_rcode_nxdomain:
|
|
inc_stats(fctx->res, dns_resstatscounter_nxdomain);
|
|
break;
|
|
case dns_rcode_servfail:
|
|
inc_stats(fctx->res, dns_resstatscounter_servfail);
|
|
break;
|
|
case dns_rcode_formerr:
|
|
inc_stats(fctx->res, dns_resstatscounter_formerr);
|
|
break;
|
|
case dns_rcode_refused:
|
|
inc_stats(fctx->res, dns_resstatscounter_refused);
|
|
break;
|
|
case dns_rcode_badvers:
|
|
inc_stats(fctx->res, dns_resstatscounter_badvers);
|
|
break;
|
|
case dns_rcode_badcookie:
|
|
inc_stats(fctx->res, dns_resstatscounter_badcookie);
|
|
break;
|
|
default:
|
|
inc_stats(fctx->res, dns_resstatscounter_othererror);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Bad server?
|
|
*/
|
|
result = rctx_badserver(&rctx, result);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Lame server?
|
|
*/
|
|
result = rctx_lameserver(&rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Handle delegation-only zones like NET or COM.
|
|
*/
|
|
rctx_delonly_zone(&rctx);
|
|
|
|
/*
|
|
* Optionally call dns_rdata_checkowner() and dns_rdata_checknames()
|
|
* to validate the names in the response message.
|
|
*/
|
|
if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) {
|
|
checknames(fctx->rmessage);
|
|
}
|
|
|
|
/*
|
|
* Clear cache bits.
|
|
*/
|
|
fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE);
|
|
|
|
/*
|
|
* Did we get any answers?
|
|
*/
|
|
if (fctx->rmessage->counts[DNS_SECTION_ANSWER] > 0 &&
|
|
(fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->rmessage->rcode == dns_rcode_yxdomain ||
|
|
fctx->rmessage->rcode == dns_rcode_nxdomain))
|
|
{
|
|
result = rctx_answer(&rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return;
|
|
}
|
|
} else if (fctx->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 ||
|
|
fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->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:
|
|
result = ISC_R_SUCCESS;
|
|
break;
|
|
default:
|
|
/*
|
|
* Something has gone wrong.
|
|
*/
|
|
if (result == DNS_R_FORMERR)
|
|
rctx.next_server = ISC_TRUE;
|
|
FCTXTRACE3("rctx_answer_none", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
} else {
|
|
/*
|
|
* The server is insane.
|
|
*/
|
|
/* XXXRTH Log */
|
|
rctx.broken_server = DNS_R_UNEXPECTEDRCODE;
|
|
rctx.next_server = ISC_TRUE;
|
|
FCTXTRACE("broken server: unexpected rcode");
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Follow additional section data chains.
|
|
*/
|
|
rctx_additional(&rctx);
|
|
|
|
/*
|
|
* Cache the cacheable parts of the message. This may also cause
|
|
* work to be queued to the DNSSEC validator.
|
|
*/
|
|
if (WANTCACHE(fctx)) {
|
|
result = cache_message(fctx, query->addrinfo, rctx.now);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("cache_message complete", result);
|
|
rctx_done(&rctx, result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Negative caching
|
|
*/
|
|
rctx_ncache(&rctx);
|
|
|
|
rctx_done(&rctx, result);
|
|
}
|
|
|
|
/*
|
|
* rctx_respinit():
|
|
* Initialize the response context structure 'rctx' to all zeroes, then set
|
|
* the task, event, query and fctx information from resquery_response().
|
|
*/
|
|
static void
|
|
rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent,
|
|
resquery_t *query, fetchctx_t *fctx, respctx_t *rctx)
|
|
{
|
|
memset(rctx, 0, sizeof(*rctx));
|
|
|
|
rctx->task = task;
|
|
rctx->devent = devent;
|
|
rctx->query = query;
|
|
rctx->fctx = fctx;
|
|
rctx->broken_type = badns_response;
|
|
rctx->retryopts = query->options;
|
|
|
|
/*
|
|
* XXXRTH We should really get the current time just once. We
|
|
* need a routine to convert from an isc_time_t to an
|
|
* isc_stdtime_t.
|
|
*/
|
|
TIME_NOW(&rctx->tnow);
|
|
rctx->finish = &rctx->tnow;
|
|
isc_stdtime_get(&rctx->now);
|
|
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_init():
|
|
* Clear and reinitialize those portions of 'rctx' that will be needed
|
|
* when scanning the answer section of the response message. This can be
|
|
* called more than once if scanning needs to be restarted (though currently
|
|
* there are no cases in which this occurs).
|
|
*/
|
|
static void
|
|
rctx_answer_init(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
rctx->aa = ISC_TF((fctx->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0);
|
|
if (rctx->aa) {
|
|
rctx->trust = dns_trust_authanswer;
|
|
} else {
|
|
rctx->trust = dns_trust_answer;
|
|
}
|
|
|
|
/*
|
|
* There can be multiple RRSIG and SIG records at a name so
|
|
* we treat these types as a subset of ANY.
|
|
*/
|
|
rctx->type = fctx->type;
|
|
if (rctx->type == dns_rdatatype_rrsig ||
|
|
rctx->type == dns_rdatatype_sig)
|
|
{
|
|
rctx->type = dns_rdatatype_any;
|
|
}
|
|
|
|
/*
|
|
* Bigger than any valid DNAME label count.
|
|
*/
|
|
rctx->dname_labels = dns_name_countlabels(&fctx->name);
|
|
rctx->domain_labels = dns_name_countlabels(&fctx->domain);
|
|
|
|
rctx->found_type = dns_rdatatype_none;
|
|
|
|
rctx->aname = NULL;
|
|
rctx->ardataset = NULL;
|
|
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
|
|
rctx->dname = NULL;
|
|
rctx->drdataset = NULL;
|
|
|
|
rctx->ns_name = NULL;
|
|
rctx->ns_rdataset = NULL;
|
|
|
|
rctx->soa_name = NULL;
|
|
rctx->ds_name = NULL;
|
|
rctx->found_name = NULL;
|
|
}
|
|
|
|
/*
|
|
* rctx_dispfail():
|
|
* Handle the case where the dispatcher failed
|
|
*/
|
|
static isc_result_t
|
|
rctx_dispfail(respctx_t *rctx) {
|
|
dns_dispatchevent_t *devent = rctx->devent;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
if (devent->result == ISC_R_SUCCESS) {
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
if (devent->result == ISC_R_EOF &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) {
|
|
/*
|
|
* The problem might be that they don't understand EDNS0.
|
|
* Turn it off and try again.
|
|
*/
|
|
rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
|
|
rctx->resend = ISC_TRUE;
|
|
add_bad_edns(fctx, &query->addrinfo->sockaddr);
|
|
} else {
|
|
/*
|
|
* There's no hope for this response.
|
|
*/
|
|
rctx->next_server = ISC_TRUE;
|
|
|
|
/*
|
|
* If this is a network error on an exclusive query
|
|
* socket, mark the server as bad so that we won't try
|
|
* it for this fetch again. Also adjust finish and
|
|
* no_response so that we penalize this address in SRTT
|
|
* adjustment later.
|
|
*/
|
|
if (query->exclusivesocket &&
|
|
(devent->result == ISC_R_HOSTUNREACH ||
|
|
devent->result == ISC_R_NETUNREACH ||
|
|
devent->result == ISC_R_CONNREFUSED ||
|
|
devent->result == ISC_R_CANCELED))
|
|
{
|
|
rctx->broken_server = devent->result;
|
|
rctx->broken_type = badns_unreachable;
|
|
rctx->finish = NULL;
|
|
rctx->no_response = ISC_TRUE;
|
|
}
|
|
}
|
|
FCTXTRACE3("dispatcher failure", devent->result);
|
|
rctx_done(rctx, ISC_R_SUCCESS);
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
/*
|
|
* rctx_parse():
|
|
* Parse the response message.
|
|
*/
|
|
static isc_result_t
|
|
rctx_parse(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
result = dns_message_parse(fctx->rmessage, &rctx->devent->buffer, 0);
|
|
if (result == ISC_R_SUCCESS) {
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
FCTXTRACE3("message failed to parse", result);
|
|
|
|
switch (result) {
|
|
case ISC_R_UNEXPECTEDEND:
|
|
if (fctx->rmessage->question_ok &&
|
|
(fctx->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 = ISC_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 = ISC_TRUE;
|
|
add_bad_edns(fctx, &query->addrinfo->sockaddr);
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_edns0fail);
|
|
} else {
|
|
rctx->broken_server = result;
|
|
rctx->next_server = ISC_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 = ISC_TRUE;
|
|
add_bad_edns(fctx, &query->addrinfo->sockaddr);
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_edns0fail);
|
|
} else {
|
|
rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
|
|
rctx->next_server = ISC_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;
|
|
isc_uint16_t optcode;
|
|
isc_uint16_t optlen;
|
|
unsigned char *optvalue;
|
|
dns_adbaddrinfo_t *addrinfo;
|
|
unsigned char cookie[8];
|
|
isc_boolean_t seen_cookie = ISC_FALSE;
|
|
isc_boolean_t seen_nsid = ISC_FALSE;
|
|
|
|
result = dns_rdataset_first(rctx->opt);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_rdata_init(&rdata);
|
|
dns_rdataset_current(rctx->opt, &rdata);
|
|
isc_buffer_init(&optbuf, rdata.data, rdata.length);
|
|
isc_buffer_add(&optbuf, rdata.length);
|
|
while (isc_buffer_remaininglength(&optbuf) >= 4) {
|
|
optcode = isc_buffer_getuint16(&optbuf);
|
|
optlen = isc_buffer_getuint16(&optbuf);
|
|
INSIST(optlen <= isc_buffer_remaininglength(&optbuf));
|
|
switch (optcode) {
|
|
case DNS_OPT_NSID:
|
|
if (!seen_nsid &&
|
|
(query->options &
|
|
DNS_FETCHOPT_WANTNSID) != 0)
|
|
{
|
|
log_nsid(&optbuf, optlen, query,
|
|
ISC_LOG_DEBUG(3),
|
|
fctx->res->mctx);
|
|
}
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
seen_nsid = ISC_TRUE;
|
|
break;
|
|
case DNS_OPT_COOKIE:
|
|
/*
|
|
* Only process the first cookie option.
|
|
*/
|
|
if (seen_cookie) {
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
break;
|
|
}
|
|
optvalue = isc_buffer_current(&optbuf);
|
|
compute_cc(query, cookie, sizeof(cookie));
|
|
INSIST(fctx->rmessage->cc_bad == 0 &&
|
|
fctx->rmessage->cc_ok == 0);
|
|
if (optlen >= 8U &&
|
|
memcmp(cookie, optvalue, 8) == 0) {
|
|
fctx->rmessage->cc_ok = 1;
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_cookieok);
|
|
addrinfo = query->addrinfo;
|
|
dns_adb_setcookie(fctx->adb,
|
|
addrinfo, optvalue,
|
|
optlen);
|
|
} else
|
|
fctx->rmessage->cc_bad = 1;
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
inc_stats(fctx->res,
|
|
dns_resstatscounter_cookiein);
|
|
seen_cookie = ISC_TRUE;
|
|
break;
|
|
default:
|
|
isc_buffer_forward(&optbuf, optlen);
|
|
break;
|
|
}
|
|
}
|
|
INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_edns():
|
|
* Determine whether the remote server is using EDNS correctly or
|
|
* incorrectly and record that information if needed.
|
|
*/
|
|
static void
|
|
rctx_edns(respctx_t *rctx) {
|
|
resquery_t *query = rctx->query;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
/*
|
|
* We have an affirmative response to the query and we have
|
|
* previously got a response from this server which indicated
|
|
* EDNS may not be supported so we can now cache the lack of
|
|
* EDNS support.
|
|
*/
|
|
if (rctx->opt == NULL && !EDNSOK(query->addrinfo) &&
|
|
(fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->rmessage->rcode == dns_rcode_nxdomain ||
|
|
fctx->rmessage->rcode == dns_rcode_refused ||
|
|
fctx->rmessage->rcode == dns_rcode_yxdomain) &&
|
|
bad_edns(fctx, &query->addrinfo->sockaddr))
|
|
{
|
|
dns_message_logpacket(fctx->rmessage,
|
|
"received packet (bad edns) from",
|
|
&query->addrinfo->sockaddr,
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3),
|
|
fctx->res->mctx);
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
DNS_FETCHOPT_NOEDNS0,
|
|
DNS_FETCHOPT_NOEDNS0);
|
|
} else if (rctx->opt == NULL &&
|
|
(fctx->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 &&
|
|
!EDNSOK(query->addrinfo) &&
|
|
(fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->rmessage->rcode == dns_rcode_nxdomain) &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
|
|
{
|
|
/*
|
|
* We didn't get a OPT record in response to a EDNS query.
|
|
*
|
|
* Old versions of named incorrectly drop the OPT record
|
|
* when there is a signed, truncated response so we check
|
|
* that TC is not set.
|
|
*
|
|
* Record that the server is not talking EDNS. While this
|
|
* should be safe to do for any rcode we limit it to NOERROR
|
|
* and NXDOMAIN.
|
|
*/
|
|
dns_message_logpacket(fctx->rmessage,
|
|
"received packet (no opt) from",
|
|
&query->addrinfo->sockaddr,
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER,
|
|
ISC_LOG_DEBUG(3), fctx->res->mctx);
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
DNS_FETCHOPT_NOEDNS0,
|
|
DNS_FETCHOPT_NOEDNS0);
|
|
}
|
|
|
|
/*
|
|
* If we get a non error EDNS response record the fact so we
|
|
* won't fallback to plain DNS in the future for this server.
|
|
*/
|
|
if (rctx->opt != NULL && !EDNSOK(query->addrinfo) &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 &&
|
|
(fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->rmessage->rcode == dns_rcode_nxdomain ||
|
|
fctx->rmessage->rcode == dns_rcode_refused ||
|
|
fctx->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 ((fctx->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(fctx) &&
|
|
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(fctx)) {
|
|
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 = ISC_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 = ISC_TRUE;
|
|
result = rctx_answer_none(rctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE3("rctx_answer_none", result);
|
|
}
|
|
}
|
|
|
|
if (result != DNS_R_DELEGATION) {
|
|
/*
|
|
* 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 = ISC_TRUE;
|
|
rctx_done(rctx, result);
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (result == DNS_R_FORMERR) {
|
|
rctx->next_server = ISC_TRUE;
|
|
}
|
|
rctx_done(rctx, result);
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_positive():
|
|
* Handles positive responses. Depending which type of answer this is
|
|
* (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine
|
|
* to handle it (rctx_answer_match(), rctx_answer_cname(),
|
|
* rctx_answer_dname(), rctx_answer_any()).
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_positive(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
FCTXTRACE("rctx_answer");
|
|
|
|
rctx_answer_init(rctx);
|
|
rctx_answer_scan(rctx);
|
|
|
|
/*
|
|
* Determine which type of positive answer this is:
|
|
* type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE.
|
|
* Call the appropriate routine to handle the answer type.
|
|
*/
|
|
if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) {
|
|
result = rctx_answer_any(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
} else if (rctx->aname != NULL) {
|
|
result = rctx_answer_match(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
} else if (rctx->cname != NULL) {
|
|
result = rctx_answer_cname(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
} else if (rctx->dname != NULL) {
|
|
result = rctx_answer_dname(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
} else {
|
|
log_formerr(fctx, "reply has no answer");
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
|
|
/*
|
|
* This response is now potentially cacheable.
|
|
*/
|
|
fctx->attributes |= 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 (fctx->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(fctx->rmessage, DNS_SECTION_ANSWER);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(fctx->rmessage, DNS_SECTION_ANSWER))
|
|
{
|
|
int order;
|
|
unsigned int nlabels;
|
|
dns_namereln_t namereln;
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(fctx->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:
|
|
/*
|
|
* In-scope DNAME records must have at least
|
|
* as many labels as the domain being queried.
|
|
* They also must be less that qname's labels
|
|
* and any previously found dname.
|
|
*/
|
|
if (nlabels >= rctx->dname_labels ||
|
|
nlabels < rctx->domain_labels)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We are looking for the shortest DNAME if there
|
|
* are multiple ones (which there shouldn't be).
|
|
*/
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type != dns_rdatatype_dname) {
|
|
continue;
|
|
}
|
|
rctx->dname = name;
|
|
rctx->drdataset = rdataset;
|
|
rctx->dname_labels = nlabels;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If a DNAME was found, then any CNAME or other answer matching
|
|
* QNAME that may also have been found must be ignored. Similarly,
|
|
* if a matching answer was found along with a CNAME, the CNAME
|
|
* must be ignored.
|
|
*/
|
|
if (rctx->dname != NULL) {
|
|
rctx->aname = NULL;
|
|
rctx->ardataset = NULL;
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
} else if (rctx->aname != NULL) {
|
|
rctx->cname = NULL;
|
|
rctx->crdataset = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_any():
|
|
* Handle responses to queries of type ANY. Scan the answer section,
|
|
* and as long as each RRset is of a type that is valid in the answer
|
|
* section, and the rdata isn't filtered, cache it.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_any(respctx_t *rctx) {
|
|
dns_rdataset_t *rdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
for (rdataset = ISC_LIST_HEAD(rctx->aname->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (!validinanswer(rdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if ((fctx->type == dns_rdatatype_sig ||
|
|
fctx->type == dns_rdatatype_rrsig) &&
|
|
rdataset->type != fctx->type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((rdataset->type == dns_rdatatype_a ||
|
|
rdataset->type == dns_rdatatype_aaaa) &&
|
|
!is_answeraddress_allowed(fctx->res->view,
|
|
rctx->aname, rdataset))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if ((rdataset->type == dns_rdatatype_cname ||
|
|
rdataset->type == dns_rdatatype_dname) &&
|
|
!is_answertarget_allowed(fctx, &fctx->name, rctx->aname,
|
|
rdataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
|
|
rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
|
|
rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rdataset->trust = rctx->trust;
|
|
|
|
(void)dns_rdataset_additionaldata(rdataset, check_related,
|
|
fctx);
|
|
}
|
|
|
|
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) &&
|
|
!is_answertarget_allowed(fctx, &fctx->name, rctx->aname,
|
|
rctx->ardataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
rctx->aname->attributes |= DNS_NAMEATTR_CACHE;
|
|
rctx->aname->attributes |= DNS_NAMEATTR_ANSWER;
|
|
rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->ardataset->trust = rctx->trust;
|
|
(void)dns_rdataset_additionaldata(rctx->ardataset, check_related, fctx);
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != rctx->type)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_cname():
|
|
* Handle answers containing a CNAME. Cache the CNAME, and flag that
|
|
* there may be additional chain answers to find.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_cname(respctx_t *rctx) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->crdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (rctx->type == dns_rdatatype_rrsig ||
|
|
rctx->type == dns_rdatatype_key ||
|
|
rctx->type == dns_rdatatype_nsec)
|
|
{
|
|
char buf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdatatype_format(rctx->type, buf, sizeof(buf));
|
|
log_formerr(fctx, "CNAME response for %s RR", buf);
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (!is_answertarget_allowed(fctx, &fctx->name,
|
|
rctx->cname, rctx->crdataset, NULL))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
rctx->cname->attributes |= DNS_NAMEATTR_CACHE;
|
|
rctx->cname->attributes |= DNS_NAMEATTR_ANSWER;
|
|
rctx->cname->attributes |= DNS_NAMEATTR_CHAINING;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
|
|
rctx->crdataset->trust = rctx->trust;
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != dns_rdatatype_cname)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
rctx->chaining = ISC_TRUE;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* rctx_answer_dname():
|
|
* Handle responses with covering DNAME records.
|
|
*/
|
|
static isc_result_t
|
|
rctx_answer_dname(respctx_t *rctx) {
|
|
dns_rdataset_t *sigrdataset = NULL;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!validinanswer(rctx->drdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
|
|
}
|
|
|
|
if (!is_answertarget_allowed(fctx, &fctx->name, rctx->dname,
|
|
rctx->drdataset, &rctx->chaining))
|
|
{
|
|
rctx->result = DNS_R_SERVFAIL;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
rctx->dname->attributes |= DNS_NAMEATTR_CACHE;
|
|
rctx->dname->attributes |= DNS_NAMEATTR_ANSWER;
|
|
rctx->dname->attributes |= DNS_NAMEATTR_CHAINING;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
|
|
rctx->drdataset->trust = rctx->trust;
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (!validinanswer(sigrdataset, fctx)) {
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (sigrdataset->type != dns_rdatatype_rrsig ||
|
|
sigrdataset->covers != dns_rdatatype_dname)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
|
|
sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
sigrdataset->trust = rctx->trust;
|
|
break;
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* rctx_authority_positive():
|
|
* Examine the records in the authority section (if there are any) for a
|
|
* positive answer. We expect the names for all rdatasets in this section
|
|
* to be subdomains of the domain being queried; any that are not are
|
|
* skipped. We expect to find only *one* owner name; any names
|
|
* after the first one processed are ignored. We expect to find only
|
|
* rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
|
|
* remains can be cached at trust level authauthority or additional
|
|
* (depending on whether the AA bit was set on the answer).
|
|
*/
|
|
static void
|
|
rctx_authority_positive(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_boolean_t done = ISC_FALSE;
|
|
isc_result_t result;
|
|
|
|
result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY);
|
|
while (!done && result == ISC_R_SUCCESS) {
|
|
dns_name_t *name = NULL;
|
|
isc_boolean_t external;
|
|
|
|
dns_message_currentname(fctx->rmessage, DNS_SECTION_AUTHORITY,
|
|
&name);
|
|
external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
|
|
|
|
if (!external) {
|
|
dns_rdataset_t *rdataset = NULL;
|
|
|
|
/*
|
|
* We expect to find NS or SIG NS rdatasets, and
|
|
* nothing else.
|
|
*/
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == dns_rdatatype_ns ||
|
|
(rdataset->type == dns_rdatatype_rrsig &&
|
|
rdataset->covers == dns_rdatatype_ns))
|
|
{
|
|
name->attributes |= DNS_NAMEATTR_CACHE;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_CACHE;
|
|
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else {
|
|
rdataset->trust =
|
|
dns_trust_additional;
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_ns)
|
|
{
|
|
rctx->ns_name = name;
|
|
rctx->ns_rdataset = rdataset;
|
|
}
|
|
/*
|
|
* Mark any additional data related
|
|
* to this rdataset.
|
|
*/
|
|
(void)dns_rdataset_additionaldata(
|
|
rdataset,
|
|
check_related,
|
|
fctx);
|
|
done = ISC_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
result = dns_message_nextname(fctx->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 (fctx->rmessage->rcode == dns_rcode_nxdomain ||
|
|
(fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0 &&
|
|
fctx->rmessage->counts[DNS_SECTION_AUTHORITY] == 0))
|
|
{
|
|
rctx->negative = ISC_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 = ISC_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Process DNSSEC records in the authority section.
|
|
*/
|
|
result = rctx_authority_dnssec(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
|
|
/*
|
|
* Trigger lookups for DNS nameservers.
|
|
*/
|
|
if (rctx->negative && fctx->rmessage->rcode == dns_rcode_noerror &&
|
|
fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL &&
|
|
dns_name_equal(rctx->soa_name, &fctx->name) &&
|
|
!dns_name_equal(&fctx->name, dns_rootname))
|
|
{
|
|
return (DNS_R_CHASEDSSERVERS);
|
|
}
|
|
|
|
/*
|
|
* Did we find anything?
|
|
*/
|
|
if (!rctx->negative && rctx->ns_name == NULL) {
|
|
/*
|
|
* The responder is insane.
|
|
*/
|
|
if (rctx->found_name == NULL) {
|
|
log_formerr(fctx, "invalid response");
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
if (!dns_name_issubdomain(rctx->found_name, &fctx->domain)) {
|
|
char nbuf[DNS_NAME_FORMATSIZE];
|
|
char dbuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_rdatatype_format(rctx->found_type, tbuf,
|
|
sizeof(tbuf));
|
|
dns_name_format(rctx->found_name, nbuf, sizeof(nbuf));
|
|
dns_name_format(&fctx->domain, dbuf,
|
|
sizeof(dbuf));
|
|
|
|
log_formerr(fctx, "Name %s (%s) not subdomain"
|
|
" of zone %s -- invalid response",
|
|
nbuf, tbuf, dbuf);
|
|
} else {
|
|
log_formerr(fctx, "invalid response");
|
|
}
|
|
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
|
|
/*
|
|
* If we found both NS and SOA, they should be the same name.
|
|
*/
|
|
if (rctx->ns_name != NULL && rctx->soa_name != NULL &&
|
|
rctx->ns_name != rctx->soa_name)
|
|
{
|
|
log_formerr(fctx, "NS/SOA mismatch");
|
|
return (DNS_R_FORMERR);
|
|
}
|
|
|
|
/*
|
|
* Handle a referral.
|
|
*/
|
|
result = rctx_referral(rctx);
|
|
if (result == ISC_R_COMPLETE) {
|
|
return (rctx->result);
|
|
}
|
|
|
|
/*
|
|
* Since we're not doing a referral, we don't want to cache any
|
|
* NS RRs we may have found.
|
|
*/
|
|
if (rctx->ns_name != NULL) {
|
|
rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE;
|
|
}
|
|
|
|
if (rctx->negative) {
|
|
fctx->attributes |= 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;
|
|
isc_boolean_t finished = ISC_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(fctx->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
while (!finished) {
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(fctx->rmessage, section, &name);
|
|
result = dns_message_nextname(fctx->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
finished = ISC_TRUE;
|
|
}
|
|
|
|
if (!dns_name_issubdomain(name, &fctx->domain)) {
|
|
continue;
|
|
}
|
|
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
dns_rdatatype_t type = rdataset->type;
|
|
if (type == dns_rdatatype_rrsig) {
|
|
type = rdataset->covers;
|
|
}
|
|
if (((type == dns_rdatatype_ns ||
|
|
type == dns_rdatatype_soa) &&
|
|
!dns_name_issubdomain(&fctx->name, name)))
|
|
{
|
|
char qbuf[DNS_NAME_FORMATSIZE];
|
|
char nbuf[DNS_NAME_FORMATSIZE];
|
|
char tbuf[DNS_RDATATYPE_FORMATSIZE];
|
|
dns_rdatatype_format(type, tbuf, sizeof(tbuf));
|
|
dns_name_format(name, nbuf, sizeof(nbuf));
|
|
dns_name_format(&fctx->name, qbuf,
|
|
sizeof(qbuf));
|
|
log_formerr(fctx,
|
|
"unrelated %s %s in "
|
|
"%s authority section",
|
|
tbuf, nbuf, qbuf);
|
|
break;
|
|
}
|
|
|
|
switch (type) {
|
|
case dns_rdatatype_ns:
|
|
/*
|
|
* NS or RRSIG NS.
|
|
*
|
|
* Only one set of NS RRs is allowed.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_ns) {
|
|
if (rctx->ns_name != NULL &&
|
|
name != rctx->ns_name)
|
|
{
|
|
log_formerr(fctx,
|
|
"multiple NS RRsets "
|
|
"in authority section");
|
|
rctx->result =
|
|
DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
rctx->ns_name = name;
|
|
rctx->ns_rdataset = rdataset;
|
|
}
|
|
name->attributes |= DNS_NAMEATTR_CACHE;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
rdataset->trust = dns_trust_glue;
|
|
break;
|
|
case dns_rdatatype_soa:
|
|
/*
|
|
* SOA, or RRSIG SOA.
|
|
*
|
|
* Only one SOA is allowed.
|
|
*/
|
|
if (rdataset->type == dns_rdatatype_soa) {
|
|
if (rctx->soa_name != NULL &&
|
|
name != rctx->soa_name)
|
|
{
|
|
log_formerr(fctx,
|
|
"multiple SOA RRs "
|
|
"in authority "
|
|
"section");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
rctx->soa_name = name;
|
|
}
|
|
name->attributes |= DNS_NAMEATTR_NCACHE;
|
|
rdataset->attributes |= DNS_RDATASETATTR_NCACHE;
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else if (ISFORWARDER(fctx->addrinfo)) {
|
|
rdataset->trust = dns_trust_answer;
|
|
} else {
|
|
rdataset->trust = dns_trust_additional;
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* rctx_ncache():
|
|
* Cache the negatively cacheable parts of the message. This may
|
|
* also cause work to be queued to the DNSSEC validator.
|
|
*/
|
|
static void
|
|
rctx_ncache(respctx_t *rctx) {
|
|
isc_result_t result;
|
|
dns_rdatatype_t covers;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (!WANTNCACHE(fctx)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Cache DS NXDOMAIN seperately to other types.
|
|
*/
|
|
if (fctx->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->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_section_t section;
|
|
dns_rdataset_t *rdataset = NULL;
|
|
isc_boolean_t finished = ISC_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(fctx->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
while (!finished) {
|
|
dns_name_t *name = NULL;
|
|
|
|
dns_message_currentname(fctx->rmessage, section, &name);
|
|
result = dns_message_nextname(fctx->rmessage, section);
|
|
if (result != ISC_R_SUCCESS) {
|
|
finished = ISC_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))
|
|
{
|
|
dns_rdatatype_t type = rdataset->type;
|
|
if (type == dns_rdatatype_rrsig) {
|
|
type = rdataset->covers;
|
|
}
|
|
|
|
switch (type) {
|
|
case dns_rdatatype_nsec:
|
|
case dns_rdatatype_nsec3:
|
|
if (rctx->negative) {
|
|
name->attributes |= DNS_NAMEATTR_NCACHE;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_NCACHE;
|
|
} else if (type == dns_rdatatype_nsec) {
|
|
name->attributes |= DNS_NAMEATTR_CACHE;
|
|
rdataset->attributes |=
|
|
DNS_RDATASETATTR_CACHE;
|
|
}
|
|
|
|
if (rctx->aa) {
|
|
rdataset->trust =
|
|
dns_trust_authauthority;
|
|
} else if (ISFORWARDER(fctx->addrinfo)) {
|
|
rdataset->trust = dns_trust_answer;
|
|
} else {
|
|
rdataset->trust = dns_trust_additional;
|
|
}
|
|
/*
|
|
* No additional data needs to be marked.
|
|
*/
|
|
break;
|
|
case dns_rdatatype_ds:
|
|
/*
|
|
* DS or SIG DS.
|
|
*
|
|
* These should only be here if this is a
|
|
* referral, and there should only be one
|
|
* DS RRset.
|
|
*/
|
|
if (rctx->ns_name == NULL) {
|
|
log_formerr(fctx,
|
|
"DS with no referral");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_ds) {
|
|
if (rctx->ds_name != NULL &&
|
|
name != rctx->ds_name)
|
|
{
|
|
log_formerr(fctx,
|
|
"DS doesn't match "
|
|
"referral (NS)");
|
|
rctx->result = DNS_R_FORMERR;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
rctx->ds_name = name;
|
|
}
|
|
|
|
name->attributes |= DNS_NAMEATTR_CACHE;
|
|
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
|
|
if (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->attributes |= FCTX_ATTR_GLUING;
|
|
(void)dns_rdataset_additionaldata(rctx->ns_rdataset,
|
|
check_related, fctx);
|
|
#if CHECK_FOR_GLUE_IN_ANSWER
|
|
/*
|
|
* Look in the answer section for "glue" that is incorrectly
|
|
* returned as a answer. This is needed if the server also
|
|
* minimizes the response size by not adding records to the
|
|
* additional section that are in the answer section or if
|
|
* the record gets dropped due to message size constraints.
|
|
*/
|
|
if (rctx->glue_in_answer &&
|
|
(fctx->type == dns_rdatatype_aaaa ||
|
|
fctx->type == dns_rdatatype_a))
|
|
{
|
|
(void)dns_rdataset_additionaldata(rctx->ns_rdataset,
|
|
check_answer, fctx);
|
|
}
|
|
#endif
|
|
fctx->attributes &= ~FCTX_ATTR_GLUING;
|
|
|
|
/*
|
|
* NS rdatasets with 0 TTL cause problems.
|
|
* dns_view_findzonecut() will not find them when we
|
|
* try to follow the referral, and we'll SERVFAIL
|
|
* because the best nameservers are now above QDOMAIN.
|
|
* We force the TTL to 1 second to prevent this.
|
|
*/
|
|
if (rctx->ns_rdataset->ttl == 0) {
|
|
rctx->ns_rdataset->ttl = 1;
|
|
}
|
|
|
|
/*
|
|
* Set the current query domain to the referral name.
|
|
*
|
|
* XXXRTH We should check if we're in forward-only mode, and
|
|
* if so we should bail out.
|
|
*/
|
|
INSIST(dns_name_countlabels(&fctx->domain) > 0);
|
|
fcount_decr(fctx);
|
|
|
|
dns_name_free(&fctx->domain, fctx->mctx);
|
|
if (dns_rdataset_isassociated(&fctx->nameservers)) {
|
|
dns_rdataset_disassociate(&fctx->nameservers);
|
|
}
|
|
|
|
dns_name_init(&fctx->domain, NULL);
|
|
result = dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
rctx->result = result;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
result = fcount_incr(fctx, ISC_TRUE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
rctx->result = result;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
fctx->attributes |= FCTX_ATTR_WANTCACHE;
|
|
fctx->ns_ttl_ok = ISC_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.
|
|
*
|
|
*/
|
|
rctx->get_nameservers = ISC_TRUE;
|
|
rctx->next_server = ISC_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) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_boolean_t rescan;
|
|
dns_section_t section = DNS_SECTION_ADDITIONAL;
|
|
isc_result_t result;
|
|
|
|
again:
|
|
rescan = ISC_FALSE;
|
|
|
|
for (result = dns_message_firstname(fctx->rmessage, section);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(fctx->rmessage, section)) {
|
|
dns_name_t *name = NULL;
|
|
dns_rdataset_t *rdataset;
|
|
dns_message_currentname(fctx->rmessage, DNS_SECTION_ADDITIONAL,
|
|
&name);
|
|
if ((name->attributes & DNS_NAMEATTR_CHASE) == 0)
|
|
continue;
|
|
name->attributes &= ~DNS_NAMEATTR_CHASE;
|
|
for (rdataset = ISC_LIST_HEAD(name->list);
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link)) {
|
|
if (CHASE(rdataset)) {
|
|
rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
|
|
(void)dns_rdataset_additionaldata(rdataset,
|
|
check_related,
|
|
fctx);
|
|
rescan = ISC_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_adbaddrinfo_t *addrinfo,
|
|
isc_result_t result)
|
|
{
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
|
|
if (result == DNS_R_FORMERR)
|
|
rctx->broken_server = DNS_R_FORMERR;
|
|
if (rctx->broken_server != ISC_R_SUCCESS) {
|
|
/*
|
|
* Add this server to the list of bad servers for
|
|
* this fctx.
|
|
*/
|
|
add_bad(fctx, addrinfo, rctx->broken_server,
|
|
rctx->broken_type);
|
|
}
|
|
|
|
if (rctx->get_nameservers) {
|
|
dns_fixedname_t foundname;
|
|
dns_name_t *name, *fname;
|
|
unsigned int findoptions = 0;
|
|
|
|
fname = dns_fixedname_initname(&foundname);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
if (dns_rdatatype_atparent(fctx->type))
|
|
findoptions |= DNS_DBFIND_NOEXACT;
|
|
if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) {
|
|
name = &fctx->name;
|
|
} else {
|
|
name = &fctx->domain;
|
|
}
|
|
result = dns_view_findzonecut(fctx->res->view,
|
|
name, fname,
|
|
rctx->now, findoptions,
|
|
ISC_TRUE, ISC_TRUE,
|
|
&fctx->nameservers,
|
|
NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
FCTXTRACE("couldn't find a zonecut");
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
if (!dns_name_issubdomain(fname, &fctx->domain)) {
|
|
/*
|
|
* The best nameservers are now above our QDOMAIN.
|
|
*/
|
|
FCTXTRACE("nameservers now above QDOMAIN");
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
|
|
fcount_decr(fctx);
|
|
dns_name_free(&fctx->domain, fctx->mctx);
|
|
dns_name_init(&fctx->domain, NULL);
|
|
result = dns_name_dup(fname, fctx->mctx, &fctx->domain);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
result = fcount_incr(fctx, ISC_TRUE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
}
|
|
fctx->ns_ttl = fctx->nameservers.ttl;
|
|
fctx->ns_ttl_ok = ISC_TRUE;
|
|
fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE);
|
|
fctx_cleanupall(fctx);
|
|
}
|
|
|
|
/*
|
|
* Try again.
|
|
*/
|
|
fctx_try(fctx, !rctx->get_nameservers, ISC_FALSE);
|
|
}
|
|
|
|
/*
|
|
* rctx_resend():
|
|
*
|
|
* Resend the query, probably with the options changed. Calls fctx_query(),
|
|
* passing rctx->retryopts (which is based on query->options, but may have been
|
|
* updated since the last time fctx_query() was called).
|
|
*/
|
|
static void
|
|
rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_boolean_t bucket_empty;
|
|
|
|
FCTXTRACE("resend");
|
|
inc_stats(fctx->res, dns_resstatscounter_retry);
|
|
fctx_increference(fctx);
|
|
result = fctx_query(fctx, addrinfo, rctx->retryopts);
|
|
if (result == ISC_R_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
fctx_done(fctx, result, __LINE__);
|
|
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
bucket_empty = fctx_decreference(fctx);
|
|
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
|
|
if (bucket_empty) {
|
|
empty_bucket(fctx->res);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_next():
|
|
* We got what appeared to be a response but it didn't match the question
|
|
* or the cookie; it may have been meant for someone else, or it may be a
|
|
* spoofing attack. Drop it and continue listening for the response we
|
|
* wanted.
|
|
*/
|
|
static void
|
|
rctx_next(respctx_t *rctx) {
|
|
#ifdef WANT_QUERYTRACE
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
#endif
|
|
isc_result_t result;
|
|
|
|
FCTXTRACE("nextitem");
|
|
inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem);
|
|
INSIST(rctx->query->dispentry != NULL);
|
|
dns_message_reset(rctx->fctx->rmessage, DNS_MESSAGE_INTENTPARSE);
|
|
result = dns_dispatch_getnext(rctx->query->dispentry, &rctx->devent);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fctx_done(rctx->fctx, result, __LINE__);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_chaseds():
|
|
* Look up the parent zone's NS records so that DS records can be fetched.
|
|
*/
|
|
static void
|
|
rctx_chaseds(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, isc_result_t result)
|
|
{
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
unsigned int n;
|
|
|
|
add_bad(fctx, addrinfo, result, rctx->broken_type);
|
|
fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE);
|
|
fctx_cleanupfinds(fctx);
|
|
fctx_cleanupforwaddrs(fctx);
|
|
|
|
n = dns_name_countlabels(&fctx->name);
|
|
dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname);
|
|
|
|
FCTXTRACE("suspending DS lookup to find parent's NS records");
|
|
|
|
result = dns_resolver_createfetch(fctx->res, &fctx->nsname,
|
|
dns_rdatatype_ns,
|
|
NULL, NULL, NULL, NULL, 0,
|
|
fctx->options, 0, NULL, rctx->task,
|
|
resume_dslookup, fctx,
|
|
&fctx->nsrrset, NULL,
|
|
&fctx->nsfetch);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
else {
|
|
fctx_increference(fctx);
|
|
result = fctx_stopidletimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rctx_done():
|
|
* This resolver query response is finished, either because we encountered
|
|
* a problem or because we've gotten all the information from it that we
|
|
* can. We either wait for another response, resend the query to the
|
|
* same server, resend to a new server, or clean up and shut down the fetch.
|
|
*/
|
|
static void
|
|
rctx_done(respctx_t *rctx, isc_result_t result) {
|
|
resquery_t *query = rctx->query;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
dns_adbaddrinfo_t *addrinfo = query->addrinfo;
|
|
|
|
FCTXTRACE4("query canceled in response(); ",
|
|
rctx->no_response ? "no response" : "responding",
|
|
result);
|
|
|
|
/*
|
|
* Cancel the query.
|
|
*
|
|
* XXXRTH Don't cancel the query if waiting for validation?
|
|
*/
|
|
if (!rctx->nextitem) {
|
|
fctx_cancelquery(&query, &rctx->devent, rctx->finish,
|
|
rctx->no_response, ISC_FALSE);
|
|
}
|
|
|
|
#ifdef ENABLE_AFL
|
|
if (fuzzing_resolver &&
|
|
(rctx->next_server || rctx->resend || rctx->nextitem))
|
|
{
|
|
if (rctx->nextitem) {
|
|
fctx_cancelquery(&query, &rctx->devent, rctx->finish,
|
|
rctx->no_response, ISC_FALSE);
|
|
}
|
|
fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
|
|
return;
|
|
} else
|
|
#endif
|
|
if (rctx->next_server) {
|
|
rctx_nextserver(rctx, addrinfo, result);
|
|
} else if (rctx->resend) {
|
|
rctx_resend(rctx, addrinfo);
|
|
} else if (rctx->nextitem) {
|
|
rctx_next(rctx);
|
|
} else if (result == DNS_R_CHASEDSSERVERS) {
|
|
rctx_chaseds(rctx, 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, ISC_TRUE, ISC_FALSE);
|
|
/*
|
|
* We must not retransmit while the validator is working;
|
|
* it has references to the current rmessage.
|
|
*/
|
|
result = fctx_stopidletimer(fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
fctx_done(fctx, result, __LINE__);
|
|
} else {
|
|
/*
|
|
* We're done.
|
|
*/
|
|
fctx_done(fctx, result, __LINE__);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* rctx_logpacket():
|
|
* Log the incoming packet; also log to DNSTAP if configured.
|
|
*/
|
|
static void
|
|
rctx_logpacket(respctx_t *rctx) {
|
|
#ifdef HAVE_DNSTAP
|
|
isc_result_t result;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
isc_socket_t *sock = NULL;
|
|
isc_sockaddr_t localaddr, *la = NULL;
|
|
unsigned char zone[DNS_NAME_MAXWIRE];
|
|
dns_dtmsgtype_t dtmsgtype;
|
|
dns_compress_t cctx;
|
|
isc_region_t zr;
|
|
isc_buffer_t zb;
|
|
#endif /* HAVE_DNSTAP */
|
|
|
|
dns_message_logfmtpacket(rctx->fctx->rmessage,
|
|
"received packet from",
|
|
&rctx->query->addrinfo->sockaddr,
|
|
DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_PACKETS,
|
|
&dns_master_style_comment,
|
|
ISC_LOG_DEBUG(10),
|
|
rctx->fctx->res->mctx);
|
|
|
|
#ifdef HAVE_DNSTAP
|
|
/*
|
|
* Log the response via dnstap.
|
|
*/
|
|
memset(&zr, 0, sizeof(zr));
|
|
result = dns_compress_init(&cctx, -1, fctx->res->mctx);
|
|
if (result == ISC_R_SUCCESS) {
|
|
isc_buffer_init(&zb, zone, sizeof(zone));
|
|
dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
|
|
result = dns_name_towire(&fctx->domain, &cctx, &zb);
|
|
if (result == ISC_R_SUCCESS)
|
|
isc_buffer_usedregion(&zb, &zr);
|
|
dns_compress_invalidate(&cctx);
|
|
}
|
|
|
|
if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
|
|
dtmsgtype = DNS_DTTYPE_FR;
|
|
} else {
|
|
dtmsgtype = DNS_DTTYPE_RR;
|
|
}
|
|
|
|
if (rctx->query->exclusivesocket) {
|
|
sock = dns_dispatch_getentrysocket(rctx->query->dispentry);
|
|
} else {
|
|
sock = dns_dispatch_getsocket(rctx->query->dispatch);
|
|
}
|
|
|
|
if (sock != NULL) {
|
|
result = isc_socket_getsockname(sock, &localaddr);
|
|
if (result == ISC_R_SUCCESS) {
|
|
la = &localaddr;
|
|
}
|
|
}
|
|
|
|
dns_dt_send(fctx->res->view, dtmsgtype, la,
|
|
&rctx->query->addrinfo->sockaddr,
|
|
ISC_TF((rctx->query->options & DNS_FETCHOPT_TCP) != 0),
|
|
&zr, &rctx->query->start, NULL, &rctx->devent->buffer);
|
|
#endif /* HAVE_DNSTAP */
|
|
}
|
|
|
|
/*
|
|
* rctx_badserver():
|
|
* Is the remote server broken, or does it dislike us?
|
|
*/
|
|
static isc_result_t
|
|
rctx_badserver(respctx_t *rctx, isc_result_t result) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
isc_buffer_t b;
|
|
char code[64];
|
|
unsigned char cookie[64];
|
|
|
|
if (fctx->rmessage->rcode == dns_rcode_noerror ||
|
|
fctx->rmessage->rcode == dns_rcode_yxdomain ||
|
|
fctx->rmessage->rcode == dns_rcode_nxdomain)
|
|
{
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Some servers do not ignore unknown EDNS options.
|
|
*/
|
|
if (!NOCOOKIE(query->addrinfo) &&
|
|
(fctx->rmessage->rcode == dns_rcode_formerr ||
|
|
fctx->rmessage->rcode == dns_rcode_notimp ||
|
|
fctx->rmessage->rcode == dns_rcode_refused) &&
|
|
dns_adb_getcookie(fctx->adb, query->addrinfo,
|
|
cookie, sizeof(cookie)) == 0U)
|
|
{
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
FCTX_ADDRINFO_NOCOOKIE,
|
|
FCTX_ADDRINFO_NOCOOKIE);
|
|
rctx->resend = ISC_TRUE;
|
|
} else if ((fctx->rmessage->rcode == dns_rcode_formerr ||
|
|
fctx->rmessage->rcode == dns_rcode_notimp ||
|
|
(fctx->rmessage->rcode == dns_rcode_servfail &&
|
|
dns_message_getopt(fctx->rmessage) == NULL)) &&
|
|
(rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0)
|
|
{
|
|
/*
|
|
* It's very likely they don't like EDNS0.
|
|
* If the response code is SERVFAIL, also check if the
|
|
* response contains an OPT RR and don't cache the
|
|
* failure since it can be returned for various other
|
|
* reasons.
|
|
*
|
|
* XXXRTH We should check if the question
|
|
* we're asking requires EDNS0, and
|
|
* if so, we should bail out.
|
|
*/
|
|
rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
|
|
rctx->resend = ISC_TRUE;
|
|
/*
|
|
* Remember that they may not like EDNS0.
|
|
*/
|
|
add_bad_edns(fctx, &query->addrinfo->sockaddr);
|
|
inc_stats(fctx->res, dns_resstatscounter_edns0fail);
|
|
} else if (fctx->rmessage->rcode == dns_rcode_formerr) {
|
|
if (ISFORWARDER(query->addrinfo)) {
|
|
/*
|
|
* This forwarder doesn't understand us,
|
|
* but other forwarders might. Keep trying.
|
|
*/
|
|
rctx->broken_server = DNS_R_REMOTEFORMERR;
|
|
rctx->next_server = ISC_TRUE;
|
|
} else {
|
|
/*
|
|
* The server doesn't understand us. Since
|
|
* all servers for a zone need similar
|
|
* capabilities, we assume that we will get
|
|
* FORMERR from all servers, and thus we
|
|
* cannot make any more progress with this
|
|
* fetch.
|
|
*/
|
|
log_formerr(fctx, "server sent FORMERR");
|
|
result = DNS_R_FORMERR;
|
|
}
|
|
} else if (fctx->rmessage->rcode == dns_rcode_badvers) {
|
|
unsigned int version;
|
|
#if DNS_EDNS_VERSION > 0
|
|
unsigned int flags, mask;
|
|
#else
|
|
isc_boolean_t setnocookie = ISC_FALSE;
|
|
#endif
|
|
|
|
/*
|
|
* Some servers return BADVERS to unknown
|
|
* EDNS options. This cannot be long term
|
|
* strategy. Do not disable COOKIE if we have
|
|
* already have received a COOKIE from this
|
|
* server.
|
|
*/
|
|
if (dns_adb_getcookie(fctx->adb, query->addrinfo,
|
|
cookie, sizeof(cookie)) == 0U) {
|
|
#if DNS_EDNS_VERSION <= 0
|
|
if (!NOCOOKIE(query->addrinfo))
|
|
setnocookie = ISC_TRUE;
|
|
#endif
|
|
dns_adb_changeflags(fctx->adb, query->addrinfo,
|
|
FCTX_ADDRINFO_NOCOOKIE,
|
|
FCTX_ADDRINFO_NOCOOKIE);
|
|
}
|
|
|
|
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
|
|
|
|
/*
|
|
* 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 = ISC_TRUE;
|
|
} else {
|
|
rctx->broken_server = DNS_R_BADVERS;
|
|
rctx->next_server = ISC_TRUE;
|
|
}
|
|
#else
|
|
if (version == 0U && setnocookie) {
|
|
rctx->resend = ISC_TRUE;
|
|
} else {
|
|
rctx->broken_server = DNS_R_BADVERS;
|
|
rctx->next_server = ISC_TRUE;
|
|
}
|
|
#endif
|
|
} else if (fctx->rmessage->rcode == dns_rcode_badcookie &&
|
|
fctx->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 = ISC_TRUE;
|
|
} else {
|
|
rctx->broken_server = DNS_R_UNEXPECTEDRCODE;
|
|
rctx->next_server = ISC_TRUE;
|
|
}
|
|
|
|
isc_buffer_init(&b, code, sizeof(code) - 1);
|
|
dns_rcode_totext(fctx->rmessage->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;
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
resquery_t *query = rctx->query;
|
|
|
|
if (fctx->res->lame_ttl == 0 || ISFORWARDER(query->addrinfo) ||
|
|
!is_lame(fctx))
|
|
{
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
inc_stats(fctx->res, dns_resstatscounter_lame);
|
|
log_lame(fctx, query->addrinfo);
|
|
result = dns_adb_marklame(fctx->adb, query->addrinfo,
|
|
&fctx->name, fctx->type,
|
|
rctx->now + fctx->res->lame_ttl);
|
|
if (result != ISC_R_SUCCESS)
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR,
|
|
"could not mark server as lame: %s",
|
|
isc_result_totext(result));
|
|
rctx->broken_server = DNS_R_LAME;
|
|
rctx->next_server = ISC_TRUE;
|
|
FCTXTRACE("lame server");
|
|
rctx_done(rctx, result);
|
|
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
/*
|
|
* rctx_delonly_zone():
|
|
* Handle delegation-only zones like NET and COM.
|
|
*/
|
|
static void
|
|
rctx_delonly_zone(respctx_t *rctx) {
|
|
fetchctx_t *fctx = rctx->fctx;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char classbuf[64];
|
|
char typebuf[64];
|
|
|
|
if (ISFORWARDER(rctx->query->addrinfo) ||
|
|
!dns_view_isdelegationonly(fctx->res->view, &fctx->domain) ||
|
|
dns_name_equal(&fctx->domain, &fctx->name) ||
|
|
!fix_mustbedelegationornxdomain(fctx->rmessage, fctx))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
|
|
dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
|
|
dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf));
|
|
dns_rdataclass_format(fctx->res->rdclass, classbuf,
|
|
sizeof(classbuf));
|
|
isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf,
|
|
sizeof(addrbuf));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"enforced delegation-only for '%s' (%s/%s/%s) from %s",
|
|
domainbuf, namebuf, typebuf, classbuf, addrbuf);
|
|
}
|
|
|
|
/***
|
|
*** Resolver Methods
|
|
***/
|
|
static void
|
|
destroy(dns_resolver_t *res) {
|
|
unsigned int i;
|
|
alternate_t *a;
|
|
|
|
REQUIRE(res->references == 0);
|
|
REQUIRE(!res->priming);
|
|
REQUIRE(res->primefetch == NULL);
|
|
|
|
RTRACE("destroy");
|
|
|
|
INSIST(res->nfctx == 0);
|
|
|
|
DESTROYLOCK(&res->primelock);
|
|
DESTROYLOCK(&res->nlock);
|
|
DESTROYLOCK(&res->lock);
|
|
for (i = 0; i < res->nbuckets; i++) {
|
|
INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs));
|
|
isc_task_shutdown(res->buckets[i].task);
|
|
isc_task_detach(&res->buckets[i].task);
|
|
DESTROYLOCK(&res->buckets[i].lock);
|
|
isc_mem_detach(&res->buckets[i].mctx);
|
|
}
|
|
isc_mem_put(res->mctx, res->buckets,
|
|
res->nbuckets * sizeof(fctxbucket_t));
|
|
for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
|
|
INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list));
|
|
isc_mem_detach(&res->dbuckets[i].mctx);
|
|
DESTROYLOCK(&res->dbuckets[i].lock);
|
|
}
|
|
isc_mem_put(res->mctx, res->dbuckets,
|
|
RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
|
|
if (res->dispatches4 != NULL)
|
|
dns_dispatchset_destroy(&res->dispatches4);
|
|
if (res->dispatches6 != NULL)
|
|
dns_dispatchset_destroy(&res->dispatches6);
|
|
while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) {
|
|
ISC_LIST_UNLINK(res->alternates, a, link);
|
|
if (!a->isaddress)
|
|
dns_name_free(&a->_u._n.name, res->mctx);
|
|
isc_mem_put(res->mctx, a, sizeof(*a));
|
|
}
|
|
dns_resolver_reset_algorithms(res);
|
|
dns_resolver_reset_ds_digests(res);
|
|
dns_badcache_destroy(&res->badcache);
|
|
dns_resolver_resetmustbesecure(res);
|
|
#if USE_ALGLOCK
|
|
isc_rwlock_destroy(&res->alglock);
|
|
#endif
|
|
#if USE_MBSLOCK
|
|
isc_rwlock_destroy(&res->mbslock);
|
|
#endif
|
|
isc_timer_detach(&res->spillattimer);
|
|
res->magic = 0;
|
|
isc_mem_put(res->mctx, res, sizeof(*res));
|
|
}
|
|
|
|
static void
|
|
send_shutdown_events(dns_resolver_t *res) {
|
|
isc_event_t *event, *next_event;
|
|
isc_task_t *etask;
|
|
|
|
/*
|
|
* Caller must be holding the resolver lock.
|
|
*/
|
|
|
|
for (event = ISC_LIST_HEAD(res->whenshutdown);
|
|
event != NULL;
|
|
event = next_event) {
|
|
next_event = ISC_LIST_NEXT(event, ev_link);
|
|
ISC_LIST_UNLINK(res->whenshutdown, event, ev_link);
|
|
etask = event->ev_sender;
|
|
event->ev_sender = res;
|
|
isc_task_sendanddetach(&etask, &event);
|
|
}
|
|
}
|
|
|
|
static void
|
|
empty_bucket(dns_resolver_t *res) {
|
|
RTRACE("empty_bucket");
|
|
|
|
LOCK(&res->lock);
|
|
|
|
INSIST(res->activebuckets > 0);
|
|
res->activebuckets--;
|
|
if (res->activebuckets == 0)
|
|
send_shutdown_events(res);
|
|
|
|
UNLOCK(&res->lock);
|
|
}
|
|
|
|
static void
|
|
spillattimer_countdown(isc_task_t *task, isc_event_t *event) {
|
|
dns_resolver_t *res = event->ev_arg;
|
|
isc_result_t result;
|
|
unsigned int count;
|
|
isc_boolean_t logit = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
UNUSED(task);
|
|
|
|
LOCK(&res->lock);
|
|
INSIST(!res->exiting);
|
|
if (res->spillat > res->spillatmin) {
|
|
res->spillat--;
|
|
logit = ISC_TRUE;
|
|
}
|
|
if (res->spillat <= res->spillatmin) {
|
|
result = isc_timer_reset(res->spillattimer,
|
|
isc_timertype_inactive, NULL,
|
|
NULL, ISC_TRUE);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
count = res->spillat;
|
|
UNLOCK(&res->lock);
|
|
if (logit)
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
|
|
"clients-per-query decreased to %u", count);
|
|
|
|
isc_event_free(&event);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_create(dns_view_t *view,
|
|
isc_taskmgr_t *taskmgr,
|
|
unsigned int ntasks, unsigned int ndisp,
|
|
isc_socketmgr_t *socketmgr,
|
|
isc_timermgr_t *timermgr,
|
|
unsigned int options,
|
|
dns_dispatchmgr_t *dispatchmgr,
|
|
dns_dispatch_t *dispatchv4,
|
|
dns_dispatch_t *dispatchv6,
|
|
dns_resolver_t **resp)
|
|
{
|
|
dns_resolver_t *res;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
unsigned int i, buckets_created = 0, dbuckets_created = 0;
|
|
isc_task_t *task = NULL;
|
|
char name[16];
|
|
unsigned dispattr;
|
|
|
|
/*
|
|
* Create a resolver.
|
|
*/
|
|
|
|
REQUIRE(DNS_VIEW_VALID(view));
|
|
REQUIRE(ntasks > 0);
|
|
REQUIRE(ndisp > 0);
|
|
REQUIRE(resp != NULL && *resp == NULL);
|
|
REQUIRE(dispatchmgr != NULL);
|
|
REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL);
|
|
|
|
res = isc_mem_get(view->mctx, sizeof(*res));
|
|
if (res == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
RTRACE("create");
|
|
res->mctx = view->mctx;
|
|
res->rdclass = view->rdclass;
|
|
res->socketmgr = socketmgr;
|
|
res->timermgr = timermgr;
|
|
res->taskmgr = taskmgr;
|
|
res->dispatchmgr = dispatchmgr;
|
|
res->view = view;
|
|
res->options = options;
|
|
res->lame_ttl = 0;
|
|
ISC_LIST_INIT(res->alternates);
|
|
res->udpsize = RECV_BUFFER_SIZE;
|
|
res->algorithms = NULL;
|
|
res->digests = NULL;
|
|
res->badcache = NULL;
|
|
dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE,
|
|
&res->badcache);
|
|
res->mustbesecure = NULL;
|
|
res->spillatmin = res->spillat = 10;
|
|
res->spillatmax = 100;
|
|
res->spillattimer = NULL;
|
|
res->zspill = 0;
|
|
res->zero_no_soa_ttl = ISC_FALSE;
|
|
res->retryinterval = 30000;
|
|
res->nonbackofftries = 3;
|
|
res->query_timeout = DEFAULT_QUERY_TIMEOUT;
|
|
res->maxdepth = DEFAULT_RECURSION_DEPTH;
|
|
res->maxqueries = DEFAULT_MAX_QUERIES;
|
|
res->quotaresp[dns_quotatype_zone] = DNS_R_DROP;
|
|
res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL;
|
|
res->nbuckets = ntasks;
|
|
if (view->resstats != NULL)
|
|
isc_stats_set(view->resstats, ntasks,
|
|
dns_resstatscounter_buckets);
|
|
res->activebuckets = ntasks;
|
|
res->buckets = isc_mem_get(view->mctx,
|
|
ntasks * sizeof(fctxbucket_t));
|
|
if (res->buckets == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup_res;
|
|
}
|
|
for (i = 0; i < ntasks; i++) {
|
|
result = isc_mutex_init(&res->buckets[i].lock);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_buckets;
|
|
res->buckets[i].task = NULL;
|
|
result = isc_task_create(taskmgr, 0, &res->buckets[i].task);
|
|
if (result != ISC_R_SUCCESS) {
|
|
DESTROYLOCK(&res->buckets[i].lock);
|
|
goto cleanup_buckets;
|
|
}
|
|
res->buckets[i].mctx = NULL;
|
|
snprintf(name, sizeof(name), "res%u", i);
|
|
#ifdef ISC_PLATFORM_USETHREADS
|
|
/*
|
|
* Use a separate memory context for each bucket to reduce
|
|
* contention among multiple threads. Do this only when
|
|
* enabling threads because it will be require more memory.
|
|
*/
|
|
result = isc_mem_create(0, 0, &res->buckets[i].mctx);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_task_detach(&res->buckets[i].task);
|
|
DESTROYLOCK(&res->buckets[i].lock);
|
|
goto cleanup_buckets;
|
|
}
|
|
isc_mem_setname(res->buckets[i].mctx, name, NULL);
|
|
#else
|
|
isc_mem_attach(view->mctx, &res->buckets[i].mctx);
|
|
#endif
|
|
isc_task_setname(res->buckets[i].task, name, res);
|
|
ISC_LIST_INIT(res->buckets[i].fctxs);
|
|
res->buckets[i].exiting = ISC_FALSE;
|
|
buckets_created++;
|
|
}
|
|
|
|
res->dbuckets = isc_mem_get(view->mctx,
|
|
RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
|
|
if (res->dbuckets == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup_buckets;
|
|
}
|
|
for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
|
|
ISC_LIST_INIT(res->dbuckets[i].list);
|
|
res->dbuckets[i].mctx = NULL;
|
|
isc_mem_attach(view->mctx, &res->dbuckets[i].mctx);
|
|
result = isc_mutex_init(&res->dbuckets[i].lock);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_detach(&res->dbuckets[i].mctx);
|
|
goto cleanup_dbuckets;
|
|
}
|
|
dbuckets_created++;
|
|
}
|
|
|
|
res->dispatches4 = NULL;
|
|
if (dispatchv4 != NULL) {
|
|
dns_dispatchset_create(view->mctx, socketmgr, taskmgr,
|
|
dispatchv4, &res->dispatches4, ndisp);
|
|
dispattr = dns_dispatch_getattributes(dispatchv4);
|
|
res->exclusivev4 =
|
|
ISC_TF((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0);
|
|
}
|
|
|
|
res->dispatches6 = NULL;
|
|
if (dispatchv6 != NULL) {
|
|
dns_dispatchset_create(view->mctx, socketmgr, taskmgr,
|
|
dispatchv6, &res->dispatches6, ndisp);
|
|
dispattr = dns_dispatch_getattributes(dispatchv6);
|
|
res->exclusivev6 =
|
|
ISC_TF((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0);
|
|
}
|
|
|
|
res->querydscp4 = -1;
|
|
res->querydscp6 = -1;
|
|
res->references = 1;
|
|
res->exiting = ISC_FALSE;
|
|
res->frozen = ISC_FALSE;
|
|
ISC_LIST_INIT(res->whenshutdown);
|
|
res->priming = ISC_FALSE;
|
|
res->primefetch = NULL;
|
|
res->nfctx = 0;
|
|
|
|
result = isc_mutex_init(&res->lock);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_dispatches;
|
|
|
|
result = isc_mutex_init(&res->nlock);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_lock;
|
|
|
|
result = isc_mutex_init(&res->primelock);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_nlock;
|
|
|
|
task = NULL;
|
|
result = isc_task_create(taskmgr, 0, &task);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_primelock;
|
|
isc_task_setname(task, "resolver_task", NULL);
|
|
|
|
result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL,
|
|
task, spillattimer_countdown, res,
|
|
&res->spillattimer);
|
|
isc_task_detach(&task);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_primelock;
|
|
|
|
#if USE_ALGLOCK
|
|
result = isc_rwlock_init(&res->alglock, 0, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup_spillattimer;
|
|
#endif
|
|
#if USE_MBSLOCK
|
|
result = isc_rwlock_init(&res->mbslock, 0, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
#if USE_ALGLOCK
|
|
goto cleanup_alglock;
|
|
#else
|
|
goto cleanup_spillattimer;
|
|
#endif
|
|
#endif
|
|
|
|
res->magic = RES_MAGIC;
|
|
|
|
*resp = res;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
#if USE_ALGLOCK && USE_MBSLOCK
|
|
cleanup_alglock:
|
|
isc_rwlock_destroy(&res->alglock);
|
|
#endif
|
|
|
|
#if USE_ALGLOCK || USE_MBSLOCK
|
|
cleanup_spillattimer:
|
|
isc_timer_detach(&res->spillattimer);
|
|
#endif
|
|
|
|
cleanup_primelock:
|
|
DESTROYLOCK(&res->primelock);
|
|
|
|
cleanup_nlock:
|
|
DESTROYLOCK(&res->nlock);
|
|
|
|
cleanup_lock:
|
|
DESTROYLOCK(&res->lock);
|
|
|
|
cleanup_dispatches:
|
|
if (res->dispatches6 != NULL)
|
|
dns_dispatchset_destroy(&res->dispatches6);
|
|
if (res->dispatches4 != NULL)
|
|
dns_dispatchset_destroy(&res->dispatches4);
|
|
|
|
cleanup_dbuckets:
|
|
for (i = 0; i < dbuckets_created; i++) {
|
|
DESTROYLOCK(&res->dbuckets[i].lock);
|
|
isc_mem_detach(&res->dbuckets[i].mctx);
|
|
}
|
|
isc_mem_put(view->mctx, res->dbuckets,
|
|
RES_DOMAIN_BUCKETS * sizeof(zonebucket_t));
|
|
|
|
cleanup_buckets:
|
|
for (i = 0; i < buckets_created; i++) {
|
|
isc_mem_detach(&res->buckets[i].mctx);
|
|
DESTROYLOCK(&res->buckets[i].lock);
|
|
isc_task_shutdown(res->buckets[i].task);
|
|
isc_task_detach(&res->buckets[i].task);
|
|
}
|
|
isc_mem_put(view->mctx, res->buckets,
|
|
res->nbuckets * sizeof(fctxbucket_t));
|
|
|
|
cleanup_res:
|
|
isc_mem_put(view->mctx, res, sizeof(*res));
|
|
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
prime_done(isc_task_t *task, isc_event_t *event) {
|
|
dns_resolver_t *res;
|
|
dns_fetchevent_t *fevent;
|
|
dns_fetch_t *fetch;
|
|
dns_db_t *db = NULL;
|
|
|
|
REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
|
|
fevent = (dns_fetchevent_t *)event;
|
|
res = event->ev_arg;
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
|
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
|
"resolver priming query complete");
|
|
|
|
UNUSED(task);
|
|
|
|
LOCK(&res->lock);
|
|
|
|
INSIST(res->priming);
|
|
res->priming = ISC_FALSE;
|
|
LOCK(&res->primelock);
|
|
fetch = res->primefetch;
|
|
res->primefetch = NULL;
|
|
UNLOCK(&res->primelock);
|
|
|
|
UNLOCK(&res->lock);
|
|
|
|
if (fevent->result == ISC_R_SUCCESS &&
|
|
res->view->cache != NULL && res->view->hints != NULL) {
|
|
dns_cache_attachdb(res->view->cache, &db);
|
|
dns_root_checkhints(res->view, res->view->hints, db);
|
|
dns_db_detach(&db);
|
|
}
|
|
|
|
if (fevent->node != NULL)
|
|
dns_db_detachnode(fevent->db, &fevent->node);
|
|
if (fevent->db != NULL)
|
|
dns_db_detach(&fevent->db);
|
|
if (dns_rdataset_isassociated(fevent->rdataset))
|
|
dns_rdataset_disassociate(fevent->rdataset);
|
|
INSIST(fevent->sigrdataset == NULL);
|
|
|
|
isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset));
|
|
|
|
isc_event_free(&event);
|
|
dns_resolver_destroyfetch(&fetch);
|
|
}
|
|
|
|
void
|
|
dns_resolver_prime(dns_resolver_t *res) {
|
|
isc_boolean_t want_priming = ISC_FALSE;
|
|
dns_rdataset_t *rdataset;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(res->frozen);
|
|
|
|
RTRACE("dns_resolver_prime");
|
|
|
|
LOCK(&res->lock);
|
|
|
|
if (!res->exiting && !res->priming) {
|
|
INSIST(res->primefetch == NULL);
|
|
res->priming = ISC_TRUE;
|
|
want_priming = ISC_TRUE;
|
|
}
|
|
|
|
UNLOCK(&res->lock);
|
|
|
|
if (want_priming) {
|
|
/*
|
|
* To avoid any possible recursive locking problems, we
|
|
* start the priming fetch like any other fetch, and holding
|
|
* no resolver locks. No one else will try to start it
|
|
* because we're the ones who set res->priming to true.
|
|
* Any other callers of dns_resolver_prime() while we're
|
|
* running will see that res->priming is already true and
|
|
* do nothing.
|
|
*/
|
|
RTRACE("priming");
|
|
rdataset = isc_mem_get(res->mctx, sizeof(*rdataset));
|
|
if (rdataset == NULL) {
|
|
LOCK(&res->lock);
|
|
INSIST(res->priming);
|
|
INSIST(res->primefetch == NULL);
|
|
res->priming = ISC_FALSE;
|
|
UNLOCK(&res->lock);
|
|
return;
|
|
}
|
|
dns_rdataset_init(rdataset);
|
|
LOCK(&res->primelock);
|
|
result = dns_resolver_createfetch(res, dns_rootname,
|
|
dns_rdatatype_ns,
|
|
NULL, NULL, NULL, NULL, 0, 0,
|
|
0, NULL,
|
|
res->buckets[0].task,
|
|
prime_done,
|
|
res, rdataset, NULL,
|
|
&res->primefetch);
|
|
UNLOCK(&res->primelock);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_put(res->mctx, rdataset, sizeof(*rdataset));
|
|
LOCK(&res->lock);
|
|
INSIST(res->priming);
|
|
res->priming = ISC_FALSE;
|
|
UNLOCK(&res->lock);
|
|
}
|
|
inc_stats(res, dns_resstatscounter_priming);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_resolver_freeze(dns_resolver_t *res) {
|
|
/*
|
|
* Freeze resolver.
|
|
*/
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
res->frozen = ISC_TRUE;
|
|
}
|
|
|
|
void
|
|
dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) {
|
|
REQUIRE(VALID_RESOLVER(source));
|
|
REQUIRE(targetp != NULL && *targetp == NULL);
|
|
|
|
RRTRACE(source, "attach");
|
|
LOCK(&source->lock);
|
|
REQUIRE(!source->exiting);
|
|
|
|
INSIST(source->references > 0);
|
|
source->references++;
|
|
INSIST(source->references != 0);
|
|
UNLOCK(&source->lock);
|
|
|
|
*targetp = source;
|
|
}
|
|
|
|
void
|
|
dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
|
|
isc_event_t **eventp)
|
|
{
|
|
isc_task_t *tclone;
|
|
isc_event_t *event;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(eventp != NULL);
|
|
|
|
event = *eventp;
|
|
*eventp = NULL;
|
|
|
|
LOCK(&res->lock);
|
|
|
|
if (res->exiting && res->activebuckets == 0) {
|
|
/*
|
|
* We're already shutdown. Send the event.
|
|
*/
|
|
event->ev_sender = res;
|
|
isc_task_send(task, &event);
|
|
} else {
|
|
tclone = NULL;
|
|
isc_task_attach(task, &tclone);
|
|
event->ev_sender = tclone;
|
|
ISC_LIST_APPEND(res->whenshutdown, event, ev_link);
|
|
}
|
|
|
|
UNLOCK(&res->lock);
|
|
}
|
|
|
|
void
|
|
dns_resolver_shutdown(dns_resolver_t *res) {
|
|
unsigned int i;
|
|
fetchctx_t *fctx;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
RTRACE("shutdown");
|
|
|
|
LOCK(&res->lock);
|
|
|
|
if (!res->exiting) {
|
|
RTRACE("exiting");
|
|
res->exiting = ISC_TRUE;
|
|
|
|
for (i = 0; i < res->nbuckets; i++) {
|
|
LOCK(&res->buckets[i].lock);
|
|
for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs);
|
|
fctx != NULL;
|
|
fctx = ISC_LIST_NEXT(fctx, link))
|
|
fctx_shutdown(fctx);
|
|
if (res->dispatches4 != NULL && !res->exclusivev4) {
|
|
dns_dispatchset_cancelall(res->dispatches4,
|
|
res->buckets[i].task);
|
|
}
|
|
if (res->dispatches6 != NULL && !res->exclusivev6) {
|
|
dns_dispatchset_cancelall(res->dispatches6,
|
|
res->buckets[i].task);
|
|
}
|
|
res->buckets[i].exiting = ISC_TRUE;
|
|
if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) {
|
|
INSIST(res->activebuckets > 0);
|
|
res->activebuckets--;
|
|
}
|
|
UNLOCK(&res->buckets[i].lock);
|
|
}
|
|
if (res->activebuckets == 0)
|
|
send_shutdown_events(res);
|
|
result = isc_timer_reset(res->spillattimer,
|
|
isc_timertype_inactive, NULL,
|
|
NULL, ISC_TRUE);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
}
|
|
|
|
UNLOCK(&res->lock);
|
|
}
|
|
|
|
void
|
|
dns_resolver_detach(dns_resolver_t **resp) {
|
|
dns_resolver_t *res;
|
|
isc_boolean_t need_destroy = ISC_FALSE;
|
|
|
|
REQUIRE(resp != NULL);
|
|
res = *resp;
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
|
|
RTRACE("detach");
|
|
|
|
LOCK(&res->lock);
|
|
|
|
INSIST(res->references > 0);
|
|
res->references--;
|
|
if (res->references == 0) {
|
|
INSIST(res->exiting && res->activebuckets == 0);
|
|
need_destroy = ISC_TRUE;
|
|
}
|
|
|
|
UNLOCK(&res->lock);
|
|
|
|
if (need_destroy)
|
|
destroy(res);
|
|
|
|
*resp = NULL;
|
|
}
|
|
|
|
static inline isc_boolean_t
|
|
fctx_match(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type,
|
|
unsigned int options)
|
|
{
|
|
/*
|
|
* Don't match fetch contexts that are shutting down.
|
|
*/
|
|
if (fctx->cloned || fctx->state == fetchstate_done ||
|
|
ISC_LIST_EMPTY(fctx->events))
|
|
return (ISC_FALSE);
|
|
|
|
if (fctx->type != type || fctx->options != options)
|
|
return (ISC_FALSE);
|
|
return (dns_name_equal(&fctx->name, name));
|
|
}
|
|
|
|
static inline 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);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
|
|
dns_rdatatype_t type,
|
|
const dns_name_t *domain, dns_rdataset_t *nameservers,
|
|
dns_forwarders_t *forwarders,
|
|
const isc_sockaddr_t *client, dns_messageid_t id,
|
|
unsigned int options, unsigned int depth,
|
|
isc_counter_t *qc, isc_task_t *task,
|
|
isc_taskaction_t action, void *arg,
|
|
dns_rdataset_t *rdataset,
|
|
dns_rdataset_t *sigrdataset,
|
|
dns_fetch_t **fetchp)
|
|
{
|
|
dns_fetch_t *fetch;
|
|
fetchctx_t *fctx = NULL;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
unsigned int bucketnum;
|
|
isc_boolean_t new_fctx = ISC_FALSE;
|
|
isc_event_t *event;
|
|
unsigned int count = 0;
|
|
unsigned int spillat;
|
|
unsigned int spillatmin;
|
|
isc_boolean_t dodestroy = ISC_FALSE;
|
|
|
|
UNUSED(forwarders);
|
|
|
|
REQUIRE(VALID_RESOLVER(res));
|
|
REQUIRE(res->frozen);
|
|
/* XXXRTH Check for meta type */
|
|
if (domain != NULL) {
|
|
REQUIRE(DNS_RDATASET_VALID(nameservers));
|
|
REQUIRE(nameservers->type == dns_rdatatype_ns);
|
|
} else
|
|
REQUIRE(nameservers == NULL);
|
|
REQUIRE(forwarders == NULL);
|
|
REQUIRE(!dns_rdataset_isassociated(rdataset));
|
|
REQUIRE(sigrdataset == NULL ||
|
|
!dns_rdataset_isassociated(sigrdataset));
|
|
REQUIRE(fetchp != NULL && *fetchp == NULL);
|
|
|
|
log_fetch(name, type);
|
|
|
|
/*
|
|
* XXXRTH use a mempool?
|
|
*/
|
|
fetch = isc_mem_get(res->mctx, sizeof(*fetch));
|
|
if (fetch == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
fetch->mctx = NULL;
|
|
isc_mem_attach(res->mctx, &fetch->mctx);
|
|
|
|
bucketnum = dns_name_fullhash(name, ISC_FALSE) % res->nbuckets;
|
|
|
|
LOCK(&res->lock);
|
|
spillat = res->spillat;
|
|
spillatmin = res->spillatmin;
|
|
UNLOCK(&res->lock);
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
if (res->buckets[bucketnum].exiting) {
|
|
result = ISC_R_SHUTTINGDOWN;
|
|
goto unlock;
|
|
}
|
|
|
|
if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
|
|
for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs);
|
|
fctx != NULL;
|
|
fctx = ISC_LIST_NEXT(fctx, link)) {
|
|
if (fctx_match(fctx, name, type, options))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is this a duplicate?
|
|
*/
|
|
if (fctx != NULL && client != NULL) {
|
|
dns_fetchevent_t *fevent;
|
|
for (fevent = ISC_LIST_HEAD(fctx->events);
|
|
fevent != NULL;
|
|
fevent = ISC_LIST_NEXT(fevent, ev_link)) {
|
|
if (fevent->client != NULL && fevent->id == id &&
|
|
isc_sockaddr_equal(fevent->client, client)) {
|
|
result = DNS_R_DUPLICATE;
|
|
goto unlock;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
if (count >= spillatmin && spillatmin != 0) {
|
|
INSIST(fctx != NULL);
|
|
if (count >= spillat)
|
|
fctx->spilled = ISC_TRUE;
|
|
if (fctx->spilled) {
|
|
result = DNS_R_DROP;
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
if (fctx == NULL) {
|
|
result = fctx_create(res, name, type, domain, nameservers,
|
|
options, bucketnum, depth, qc, &fctx);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto unlock;
|
|
new_fctx = ISC_TRUE;
|
|
} else if (fctx->depth > depth)
|
|
fctx->depth = depth;
|
|
|
|
result = fctx_join(fctx, task, client, id, action, arg,
|
|
rdataset, sigrdataset, fetch);
|
|
if (new_fctx) {
|
|
if (result == ISC_R_SUCCESS) {
|
|
/*
|
|
* Launch this fctx.
|
|
*/
|
|
event = &fctx->control_event;
|
|
ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
|
|
DNS_EVENT_FETCHCONTROL,
|
|
fctx_start, fctx, NULL,
|
|
NULL, NULL);
|
|
isc_task_send(res->buckets[bucketnum].task, &event);
|
|
} else {
|
|
/*
|
|
* We don't care about the result of fctx_unlink()
|
|
* since we know we're not exiting.
|
|
*/
|
|
(void)fctx_unlink(fctx);
|
|
dodestroy = ISC_TRUE;
|
|
}
|
|
}
|
|
|
|
unlock:
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
|
|
if (dodestroy)
|
|
fctx_destroy(fctx);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
FTRACE("created");
|
|
*fetchp = fetch;
|
|
} else
|
|
isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_resolver_cancelfetch(dns_fetch_t *fetch) {
|
|
fetchctx_t *fctx;
|
|
dns_resolver_t *res;
|
|
dns_fetchevent_t *event, *next_event;
|
|
isc_task_t *etask;
|
|
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fctx->res;
|
|
|
|
FTRACE("cancelfetch");
|
|
|
|
LOCK(&res->buckets[fctx->bucketnum].lock);
|
|
|
|
/*
|
|
* Find the completion event for this fetch (as opposed
|
|
* to those for other fetches that have joined the same
|
|
* fctx) and send it with result = ISC_R_CANCELED.
|
|
*/
|
|
event = NULL;
|
|
if (fctx->state != fetchstate_done) {
|
|
for (event = ISC_LIST_HEAD(fctx->events);
|
|
event != NULL;
|
|
event = next_event) {
|
|
next_event = ISC_LIST_NEXT(event, ev_link);
|
|
if (event->fetch == fetch) {
|
|
ISC_LIST_UNLINK(fctx->events, event, ev_link);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (event != NULL) {
|
|
etask = event->ev_sender;
|
|
event->ev_sender = fctx;
|
|
event->result = ISC_R_CANCELED;
|
|
isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event));
|
|
}
|
|
/*
|
|
* The fctx continues running even if no fetches remain;
|
|
* the answer is still cached.
|
|
*/
|
|
|
|
UNLOCK(&res->buckets[fctx->bucketnum].lock);
|
|
}
|
|
|
|
void
|
|
dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
|
|
dns_fetch_t *fetch;
|
|
dns_resolver_t *res;
|
|
dns_fetchevent_t *event, *next_event;
|
|
fetchctx_t *fctx;
|
|
unsigned int bucketnum;
|
|
isc_boolean_t bucket_empty;
|
|
|
|
REQUIRE(fetchp != NULL);
|
|
fetch = *fetchp;
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fctx->res;
|
|
|
|
FTRACE("destroyfetch");
|
|
|
|
bucketnum = fctx->bucketnum;
|
|
LOCK(&res->buckets[bucketnum].lock);
|
|
|
|
/*
|
|
* Sanity check: the caller should have gotten its event before
|
|
* trying to destroy the fetch.
|
|
*/
|
|
event = NULL;
|
|
if (fctx->state != fetchstate_done) {
|
|
for (event = ISC_LIST_HEAD(fctx->events);
|
|
event != NULL;
|
|
event = next_event) {
|
|
next_event = ISC_LIST_NEXT(event, ev_link);
|
|
RUNTIME_CHECK(event->fetch != fetch);
|
|
}
|
|
}
|
|
|
|
bucket_empty = fctx_decreference(fctx);
|
|
|
|
UNLOCK(&res->buckets[bucketnum].lock);
|
|
|
|
isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch));
|
|
*fetchp = NULL;
|
|
|
|
if (bucket_empty)
|
|
empty_bucket(res);
|
|
}
|
|
|
|
void
|
|
dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx,
|
|
isc_logcategory_t *category, isc_logmodule_t *module,
|
|
int level, isc_boolean_t duplicateok)
|
|
{
|
|
fetchctx_t *fctx;
|
|
dns_resolver_t *res;
|
|
char domainbuf[DNS_NAME_FORMATSIZE];
|
|
|
|
REQUIRE(DNS_FETCH_VALID(fetch));
|
|
fctx = fetch->private;
|
|
REQUIRE(VALID_FCTX(fctx));
|
|
res = fctx->res;
|
|
|
|
LOCK(&res->buckets[fctx->bucketnum].lock);
|
|
|
|
INSIST(fctx->exitline >= 0);
|
|
if (!fctx->logged || duplicateok) {
|
|
dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
|
|
isc_log_write(lctx, category, module, level,
|
|
"fetch completed at %s:%d for %s in "
|
|
"%" ISC_PRINT_QUADFORMAT "u."
|
|
"%06" ISC_PRINT_QUADFORMAT "u: %s/%s "
|
|
"[domain:%s,referral:%u,restart:%u,qrysent:%u,"
|
|
"timeout:%u,lame:%u,quota:%u,neterr:%u,"
|
|
"badresp:%u,adberr:%u,findfail:%u,valfail:%u]",
|
|
__FILE__, fctx->exitline, fctx->info,
|
|
fctx->duration / US_PER_SEC,
|
|
fctx->duration % US_PER_SEC,
|
|
isc_result_totext(fctx->result),
|
|
isc_result_totext(fctx->vresult), domainbuf,
|
|
fctx->referrals, fctx->restarts,
|
|
fctx->querysent, fctx->timeouts,
|
|
fctx->lamecount, fctx->quotacount,
|
|
fctx->neterr, fctx->badresp, fctx->adberr,
|
|
fctx->findfail, fctx->valfail);
|
|
fctx->logged = ISC_TRUE;
|
|
}
|
|
|
|
UNLOCK(&res->buckets[fctx->bucketnum].lock);
|
|
}
|
|
|
|
dns_dispatchmgr_t *
|
|
dns_resolver_dispatchmgr(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->dispatchmgr);
|
|
}
|
|
|
|
dns_dispatch_t *
|
|
dns_resolver_dispatchv4(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (dns_dispatchset_get(resolver->dispatches4));
|
|
}
|
|
|
|
dns_dispatch_t *
|
|
dns_resolver_dispatchv6(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (dns_dispatchset_get(resolver->dispatches6));
|
|
}
|
|
|
|
isc_socketmgr_t *
|
|
dns_resolver_socketmgr(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->socketmgr);
|
|
}
|
|
|
|
isc_taskmgr_t *
|
|
dns_resolver_taskmgr(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->taskmgr);
|
|
}
|
|
|
|
isc_uint32_t
|
|
dns_resolver_getlamettl(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->lame_ttl);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setlamettl(dns_resolver_t *resolver, isc_uint32_t lame_ttl) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
resolver->lame_ttl = lame_ttl;
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_nrunning(dns_resolver_t *resolver) {
|
|
unsigned int n;
|
|
LOCK(&resolver->nlock);
|
|
n = resolver->nfctx;
|
|
UNLOCK(&resolver->nlock);
|
|
return (n);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt,
|
|
const dns_name_t *name, in_port_t port) {
|
|
alternate_t *a;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(!resolver->frozen);
|
|
REQUIRE((alt == NULL) ^ (name == NULL));
|
|
|
|
a = isc_mem_get(resolver->mctx, sizeof(*a));
|
|
if (a == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
if (alt != NULL) {
|
|
a->isaddress = ISC_TRUE;
|
|
a->_u.addr = *alt;
|
|
} else {
|
|
a->isaddress = ISC_FALSE;
|
|
a->_u._n.port = port;
|
|
dns_name_init(&a->_u._n.name, NULL);
|
|
result = dns_name_dup(name, resolver->mctx, &a->_u._n.name);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_put(resolver->mctx, a, sizeof(*a));
|
|
return (result);
|
|
}
|
|
}
|
|
ISC_LINK_INIT(a, link);
|
|
ISC_LIST_APPEND(resolver->alternates, a, link);
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setudpsize(dns_resolver_t *resolver, isc_uint16_t udpsize) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
resolver->udpsize = udpsize;
|
|
}
|
|
|
|
isc_uint16_t
|
|
dns_resolver_getudpsize(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->udpsize);
|
|
}
|
|
|
|
void
|
|
dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name) {
|
|
if (name != NULL)
|
|
dns_badcache_flushname(resolver->badcache, name);
|
|
else
|
|
dns_badcache_flush(resolver->badcache);
|
|
}
|
|
|
|
void
|
|
dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name) {
|
|
dns_badcache_flushtree(resolver->badcache, name);
|
|
}
|
|
|
|
void
|
|
dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
|
|
dns_rdatatype_t type, isc_time_t *expire)
|
|
{
|
|
#ifdef ENABLE_AFL
|
|
if (!fuzzing_resolver)
|
|
#endif
|
|
{
|
|
dns_badcache_add(resolver->badcache, name, type,
|
|
ISC_FALSE, 0, expire);
|
|
}
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
|
|
dns_rdatatype_t type, isc_time_t *now)
|
|
{
|
|
return (dns_badcache_find(resolver->badcache, name, type, NULL, now));
|
|
}
|
|
|
|
void
|
|
dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) {
|
|
(void) dns_badcache_print(resolver->badcache, "Bad cache", fp);
|
|
}
|
|
|
|
static void
|
|
free_algorithm(void *node, void *arg) {
|
|
unsigned char *algorithms = node;
|
|
isc_mem_t *mctx = arg;
|
|
|
|
isc_mem_put(mctx, algorithms, *algorithms);
|
|
}
|
|
|
|
void
|
|
dns_resolver_reset_algorithms(dns_resolver_t *resolver) {
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->algorithms != NULL)
|
|
dns_rbt_destroy(&resolver->algorithms);
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_disable_algorithm(dns_resolver_t *resolver,
|
|
const dns_name_t *name,
|
|
unsigned int alg)
|
|
{
|
|
unsigned int len, mask;
|
|
unsigned char *tmp;
|
|
unsigned char *algorithms;
|
|
isc_result_t result;
|
|
dns_rbtnode_t *node = NULL;
|
|
|
|
/*
|
|
* Whether an algorithm is disabled (or not) is stored in a
|
|
* per-name bitfield that is stored as the node data of an
|
|
* RBT.
|
|
*/
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
if (alg > 255)
|
|
return (ISC_R_RANGE);
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->algorithms == NULL) {
|
|
result = dns_rbt_create(resolver->mctx, free_algorithm,
|
|
resolver->mctx, &resolver->algorithms);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup;
|
|
}
|
|
|
|
len = alg/8 + 2;
|
|
mask = 1 << (alg%8);
|
|
|
|
result = dns_rbt_addnode(resolver->algorithms, name, &node);
|
|
|
|
if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
|
|
algorithms = node->data;
|
|
/*
|
|
* If algorithms is set, algorithms[0] contains its
|
|
* length.
|
|
*/
|
|
if (algorithms == NULL || len > *algorithms) {
|
|
/*
|
|
* If no bitfield exists in the node data, or if
|
|
* it is not long enough, allocate a new
|
|
* bitfield and copy the old (smaller) bitfield
|
|
* into it if one exists.
|
|
*/
|
|
tmp = isc_mem_get(resolver->mctx, len);
|
|
if (tmp == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
memset(tmp, 0, len);
|
|
if (algorithms != NULL)
|
|
memmove(tmp, algorithms, *algorithms);
|
|
tmp[len-1] |= mask;
|
|
/* 'tmp[0]' should contain the length of 'tmp'. */
|
|
*tmp = len;
|
|
node->data = tmp;
|
|
/* Free the older bitfield. */
|
|
if (algorithms != NULL)
|
|
isc_mem_put(resolver->mctx, algorithms,
|
|
*algorithms);
|
|
} else
|
|
algorithms[len-1] |= mask;
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
cleanup:
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
return (result);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_resolver_algorithm_supported(dns_resolver_t *resolver,
|
|
const dns_name_t *name, unsigned int alg)
|
|
{
|
|
unsigned int len, mask;
|
|
unsigned char *algorithms;
|
|
void *data = NULL;
|
|
isc_result_t result;
|
|
isc_boolean_t found = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
/*
|
|
* DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1.
|
|
*/
|
|
if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT))
|
|
return (ISC_FALSE);
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_read);
|
|
#endif
|
|
if (resolver->algorithms == NULL)
|
|
goto unlock;
|
|
result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
len = alg/8 + 2;
|
|
mask = 1 << (alg%8);
|
|
algorithms = data;
|
|
if (len <= *algorithms && (algorithms[len-1] & mask) != 0)
|
|
found = ISC_TRUE;
|
|
}
|
|
unlock:
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_read);
|
|
#endif
|
|
if (found)
|
|
return (ISC_FALSE);
|
|
|
|
return (dst_algorithm_supported(alg));
|
|
}
|
|
|
|
static void
|
|
free_digest(void *node, void *arg) {
|
|
unsigned char *digests = node;
|
|
isc_mem_t *mctx = arg;
|
|
|
|
isc_mem_put(mctx, digests, *digests);
|
|
}
|
|
|
|
void
|
|
dns_resolver_reset_ds_digests(dns_resolver_t *resolver) {
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->digests != NULL)
|
|
dns_rbt_destroy(&resolver->digests);
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_disable_ds_digest(dns_resolver_t *resolver,
|
|
const dns_name_t *name,
|
|
unsigned int digest_type)
|
|
{
|
|
unsigned int len, mask;
|
|
unsigned char *tmp;
|
|
unsigned char *digests;
|
|
isc_result_t result;
|
|
dns_rbtnode_t *node = NULL;
|
|
|
|
/*
|
|
* Whether a digest is disabled (or not) is stored in a per-name
|
|
* bitfield that is stored as the node data of an RBT.
|
|
*/
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
if (digest_type > 255)
|
|
return (ISC_R_RANGE);
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->digests == NULL) {
|
|
result = dns_rbt_create(resolver->mctx, free_digest,
|
|
resolver->mctx, &resolver->digests);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup;
|
|
}
|
|
|
|
len = digest_type/8 + 2;
|
|
mask = 1 << (digest_type%8);
|
|
|
|
result = dns_rbt_addnode(resolver->digests, name, &node);
|
|
|
|
if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) {
|
|
digests = node->data;
|
|
/* If digests is set, digests[0] contains its length. */
|
|
if (digests == NULL || len > *digests) {
|
|
/*
|
|
* If no bitfield exists in the node data, or if
|
|
* it is not long enough, allocate a new
|
|
* bitfield and copy the old (smaller) bitfield
|
|
* into it if one exists.
|
|
*/
|
|
tmp = isc_mem_get(resolver->mctx, len);
|
|
if (tmp == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
memset(tmp, 0, len);
|
|
if (digests != NULL)
|
|
memmove(tmp, digests, *digests);
|
|
tmp[len-1] |= mask;
|
|
/* tmp[0] should contain the length of 'tmp'. */
|
|
*tmp = len;
|
|
node->data = tmp;
|
|
/* Free the older bitfield. */
|
|
if (digests != NULL)
|
|
isc_mem_put(resolver->mctx, digests,
|
|
*digests);
|
|
} else
|
|
digests[len-1] |= mask;
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
cleanup:
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_write);
|
|
#endif
|
|
return (result);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_resolver_ds_digest_supported(dns_resolver_t *resolver,
|
|
const dns_name_t *name,
|
|
unsigned int digest_type)
|
|
{
|
|
unsigned int len, mask;
|
|
unsigned char *digests;
|
|
void *data = NULL;
|
|
isc_result_t result;
|
|
isc_boolean_t found = ISC_FALSE;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_ALGLOCK
|
|
RWLOCK(&resolver->alglock, isc_rwlocktype_read);
|
|
#endif
|
|
if (resolver->digests == NULL)
|
|
goto unlock;
|
|
result = dns_rbt_findname(resolver->digests, name, 0, NULL, &data);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
|
|
len = digest_type/8 + 2;
|
|
mask = 1 << (digest_type%8);
|
|
digests = data;
|
|
if (len <= *digests && (digests[len-1] & mask) != 0)
|
|
found = ISC_TRUE;
|
|
}
|
|
unlock:
|
|
#if USE_ALGLOCK
|
|
RWUNLOCK(&resolver->alglock, isc_rwlocktype_read);
|
|
#endif
|
|
if (found)
|
|
return (ISC_FALSE);
|
|
return (dst_ds_digest_supported(digest_type));
|
|
}
|
|
|
|
void
|
|
dns_resolver_resetmustbesecure(dns_resolver_t *resolver) {
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_MBSLOCK
|
|
RWLOCK(&resolver->mbslock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->mustbesecure != NULL)
|
|
dns_rbt_destroy(&resolver->mustbesecure);
|
|
#if USE_MBSLOCK
|
|
RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write);
|
|
#endif
|
|
}
|
|
|
|
static isc_boolean_t yes = ISC_TRUE, no = ISC_FALSE;
|
|
|
|
isc_result_t
|
|
dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
|
|
isc_boolean_t value)
|
|
{
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_MBSLOCK
|
|
RWLOCK(&resolver->mbslock, isc_rwlocktype_write);
|
|
#endif
|
|
if (resolver->mustbesecure == NULL) {
|
|
result = dns_rbt_create(resolver->mctx, NULL, NULL,
|
|
&resolver->mustbesecure);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup;
|
|
}
|
|
result = dns_rbt_addname(resolver->mustbesecure, name,
|
|
value ? &yes : &no);
|
|
cleanup:
|
|
#if USE_MBSLOCK
|
|
RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write);
|
|
#endif
|
|
return (result);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) {
|
|
void *data = NULL;
|
|
isc_boolean_t value = ISC_FALSE;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
#if USE_MBSLOCK
|
|
RWLOCK(&resolver->mbslock, isc_rwlocktype_read);
|
|
#endif
|
|
if (resolver->mustbesecure == NULL)
|
|
goto unlock;
|
|
result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data);
|
|
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
|
|
value = *(isc_boolean_t*)data;
|
|
unlock:
|
|
#if USE_MBSLOCK
|
|
RWUNLOCK(&resolver->mbslock, isc_rwlocktype_read);
|
|
#endif
|
|
return (value);
|
|
}
|
|
|
|
void
|
|
dns_resolver_getclientsperquery(dns_resolver_t *resolver, isc_uint32_t *cur,
|
|
isc_uint32_t *min, isc_uint32_t *max)
|
|
{
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
LOCK(&resolver->lock);
|
|
if (cur != NULL)
|
|
*cur = resolver->spillat;
|
|
if (min != NULL)
|
|
*min = resolver->spillatmin;
|
|
if (max != NULL)
|
|
*max = resolver->spillatmax;
|
|
UNLOCK(&resolver->lock);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setclientsperquery(dns_resolver_t *resolver, isc_uint32_t min,
|
|
isc_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, isc_uint32_t clients)
|
|
{
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
LOCK(&resolver->lock);
|
|
resolver->zspill = clients;
|
|
UNLOCK(&resolver->lock);
|
|
}
|
|
|
|
|
|
isc_boolean_t
|
|
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, isc_boolean_t state) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
resolver->zero_no_soa_ttl = state;
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_getoptions(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return (resolver->options);
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_gettimeout(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return (resolver->query_timeout);
|
|
}
|
|
|
|
void
|
|
dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
if (timeout <= 300)
|
|
timeout *= 1000;
|
|
|
|
if (timeout == 0)
|
|
timeout = DEFAULT_QUERY_TIMEOUT;
|
|
if (timeout > MAXIMUM_QUERY_TIMEOUT)
|
|
timeout = MAXIMUM_QUERY_TIMEOUT;
|
|
if (timeout < MINIMUM_QUERY_TIMEOUT)
|
|
timeout = MINIMUM_QUERY_TIMEOUT;
|
|
|
|
resolver->query_timeout = timeout;
|
|
}
|
|
|
|
void
|
|
dns_resolver_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
resolver->querydscp4 = dscp;
|
|
}
|
|
|
|
isc_dscp_t
|
|
dns_resolver_getquerydscp4(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->querydscp4);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
resolver->querydscp6 = dscp;
|
|
}
|
|
|
|
isc_dscp_t
|
|
dns_resolver_getquerydscp6(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->querydscp6);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
resolver->maxdepth = maxdepth;
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_getmaxdepth(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->maxdepth);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
resolver->maxqueries = queries;
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_getmaxqueries(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
return (resolver->maxqueries);
|
|
}
|
|
|
|
void
|
|
dns_resolver_dumpfetches(dns_resolver_t *resolver,
|
|
isc_statsformat_t format, FILE *fp)
|
|
{
|
|
int i;
|
|
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(fp != NULL);
|
|
REQUIRE(format == isc_statsformat_file);
|
|
|
|
for (i = 0; i < RES_DOMAIN_BUCKETS; i++) {
|
|
fctxcount_t *fc;
|
|
LOCK(&resolver->dbuckets[i].lock);
|
|
for (fc = ISC_LIST_HEAD(resolver->dbuckets[i].list);
|
|
fc != NULL;
|
|
fc = ISC_LIST_NEXT(fc, link))
|
|
{
|
|
dns_name_print(fc->domain, fp);
|
|
fprintf(fp, ": %u active (%u spilled, %u allowed)\n",
|
|
fc->count, fc->dropped, fc->allowed);
|
|
}
|
|
UNLOCK(&resolver->dbuckets[i].lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
dns_resolver_setquotaresponse(dns_resolver_t *resolver,
|
|
dns_quotatype_t which, isc_result_t resp)
|
|
{
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
|
|
REQUIRE(resp == DNS_R_DROP || resp == DNS_R_SERVFAIL);
|
|
|
|
resolver->quotaresp[which] = resp;
|
|
}
|
|
|
|
isc_result_t
|
|
dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which)
|
|
{
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server);
|
|
|
|
return (resolver->quotaresp[which]);
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_getretryinterval(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return (resolver->retryinterval);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval)
|
|
{
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(interval > 0);
|
|
|
|
resolver->retryinterval = ISC_MIN(interval, 2000);
|
|
}
|
|
|
|
unsigned int
|
|
dns_resolver_getnonbackofftries(dns_resolver_t *resolver) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
|
|
return (resolver->nonbackofftries);
|
|
}
|
|
|
|
void
|
|
dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) {
|
|
REQUIRE(VALID_RESOLVER(resolver));
|
|
REQUIRE(tries > 0);
|
|
|
|
resolver->nonbackofftries = tries;
|
|
}
|