Commit 61367c60 authored by Evan Hunt's avatar Evan Hunt
Browse files

[master] refactor resquery_response() and related functions

4669.	[func]		Iterative query logic in resolver.c has been
			refactored into smaller functions and commented,
			for improved readability, maintainability and
			testability. [RT #45362]
parent 592d2ea9
4669. [func] Iterative query logic in resolver.c has been
refactored into smaller functions and commented,
for improved readability, maintainability and
testability. [RT #45362]
4668. [bug] Use localtime_r and gmtime_r for thread safety.
[RT #45664]
......
......@@ -232,7 +232,7 @@ rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult,
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
/*%
* The structure and functions define below implement the query logic
* The structure and functions defined below implement the query logic
* that previously lived in the single very complex function query_find().
* The query_ctx_t structure maintains state from function to function.
* The call flow for the general query processing algorithm is described
......
/*
* Copyright (C) 2013, 2016, 2017 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 2017 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
......@@ -18,12 +18,6 @@ options {
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
recursion yes;
acache-enable no;
dnssec-enable yes;
dnssec-validation auto;
bindkeys-file "managed.conf";
dnssec-accept-expired yes;
};
key rndc_key {
......@@ -35,11 +29,6 @@ controls {
inet 10.53.0.4 port 9953 allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../common/root.hint";
}
key auth {
secret "1234abcd8765";
algorithm hmac-sha256;
......
/*
* Copyright (C) 2017 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/.
*/
// NS4
controls { /* empty */ };
options {
query-source address 10.53.0.4;
notify-source 10.53.0.4;
transfer-source 10.53.0.4;
port 5300;
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.4 port 9953 allow { any; } keys { rndc_key; };
};
key auth {
secret "1234abcd8765";
algorithm hmac-sha256;
};
include "trusted.conf";
view rec {
match-recursive-only yes;
recursion yes;
acache-enable yes;
dnssec-validation yes;
dnssec-accept-expired yes;
zone "." {
type hint;
file "../../common/root.hint";
};
zone secure.example {
type static-stub;
server-addresses { 10.53.0.4; };
};
zone insecure.secure.example {
type static-stub;
server-addresses { 10.53.0.4; };
};
};
view auth {
recursion no;
allow-recursion { none; };
zone secure.example {
type slave;
masters { 10.53.0.3; };
};
zone insecure.secure.example {
type slave;
masters { 10.53.0.2; };
};
};
......@@ -2705,10 +2705,6 @@ n=`expr $n + 1`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
cp ns4/named4.conf ns4/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /'
sleep 3
echo "I:testing TTL is capped at RRSIG expiry time for records in the additional section with dnssec-accept-expired yes; ($n)"
ret=0
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 flush
......@@ -2726,27 +2722,6 @@ n=`expr $n + 1`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
cp ns4/named4.conf ns4/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /'
sleep 3
echo "I:testing TTL is capped at RRSIG expiry time for records in the additional section with acache off; ($n)"
ret=0
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 flush
$DIG +noall +additional +dnssec +cd -p 5300 expiring.example mx @10.53.0.4 > dig.out.ns4.1.$n
$DIG +noall +additional +dnssec -p 5300 expiring.example mx @10.53.0.4 > dig.out.ns4.2.$n
ttls=`awk '$1 != ";;" {print $2}' dig.out.ns4.1.$n`
ttls2=`awk '$1 != ";;" {print $2}' dig.out.ns4.2.$n`
for ttl in ${ttls:-300}; do
[ $ttl -eq 300 ] || ret=1
done
for ttl in ${ttls2:-0}; do
[ $ttl -le 120 -a $ttl -gt 60 ] || ret=1
done
n=`expr $n + 1`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:testing DNSKEY lookup via CNAME ($n)"
ret=0
$DIG $DIGOPTS +noauth cnameandkey.secure.example. \
......@@ -2917,7 +2892,7 @@ n=`expr $n + 1`
if test "$before" = "$after" ; then echo "I:failed"; ret=1; fi
status=`expr $status + $ret`
cp ns4/named5.conf ns4/named.conf
cp ns4/named4.conf ns4/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /'
sleep 3
......
......@@ -25,6 +25,7 @@ options {
dnstap { all; };
send-cookie no;
require-server-cookie no;
minimal-responses no;
};
server 10.53.0.1 { tcp-only yes; };
......
......@@ -358,6 +358,7 @@ if [ $HAS_PYYAML -ne 0 ] ; then
fi
echo "I:checking dnstap-read hex output"
ret=0
hex=`$DNSTAPREAD -x ns3/dnstap.out | tail -1`
echo $hex | $WIRETEST > dnstap.hex
grep 'status: NOERROR' dnstap.hex > /dev/null 2>&1 || ret=1
......
......@@ -273,11 +273,32 @@
</listitem>
<listitem>
<para>
Query logic has been substantially refactored (e.g. query_find
function has been split into smaller functions) for improved
readability, maintainability and testability. [RT #43929]
</para>
</listitem>
<listitem>
<para>
Several areas of code have been refactored for improved
readability, maintainability, and testability:
</para>
<itemizedlist>
<listitem>
<para>
The <command>named</command> query logic implemented in
<command>query_find()</command> has been split into
smaller functions with a context structure to maintain state
between them, and extensive comments have been added.
[RT #43929]
</para>
</listitem>
<listitem>
<para>
Similarly the iterative query logic implemented in
<command>resquery_response()</command> function has been
split into smaller functions and comments added. [RT #45362]
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
<command>dnstap</command> logfiles can now be configured to
......
......@@ -573,6 +573,221 @@ static inline isc_result_t findnoqname(fetchctx_t *fctx, dns_name_t *name,
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.
*/
......@@ -5901,7 +6116,7 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
/*
* XXXMPA remove when we follow cnames and adjust the setting
* of FCTX_ATTR_WANTNCACHE in noanswer_response().
* of FCTX_ATTR_WANTNCACHE in rctx_answer_none().
*/
INSIST(fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0);
......@@ -6145,41 +6360,6 @@ check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type) {
}
#endif
static void
chase_additional(fetchctx_t *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;
}
static isc_boolean_t
is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
dns_rdataset_t *rdataset)
......@@ -6376,754 +6556,1197 @@ trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) {
}
}
/*
* Handle a no-answer response (NXDOMAIN, NXRRSET, or referral).
* If look_in_options has LOOK_FOR_NS_IN_ANSWER then we look in the answer
* section for the NS RRset if the query type is NS; if it has
* LOOK_FOR_GLUE_IN_ANSWER we look for glue incorrectly returned in the answer
* section for A and AAAA queries.
*/
#define LOOK_FOR_NS_IN_ANSWER 0x1
#define LOOK_FOR_GLUE_IN_ANSWER 0x2
static isc_result_t
noanswer_response(fetchctx_t *fctx, dns_name_t *oqname,
unsigned int look_in_options)
{
isc_result_t result;
dns_message_t *message;
dns_name_t *name, *qname, *ns_name, *soa_name, *ds_name, *save_name;
dns_rdataset_t *rdataset, *ns_rdataset;
isc_boolean_t aa, negative_response;
dns_rdatatype_t type, save_type;
dns_section_t section;
FCTXTRACE("noanswer_response");
if ((look_in_options & LOOK_FOR_NS_IN_ANSWER) != 0) {
INSIST(fctx->type == dns_rdatatype_ns);
section = DNS_SECTION_ANSWER;
} else
section = DNS_SECTION_AUTHORITY;
message = fctx->rmessage;
/*
* Setup qname.
*/
if (oqname == NULL) {
/*
* We have a normal, non-chained negative response or
* referral.
*/
if ((message->flags & DNS_MESSAGEFLAG_AA) != 0)
aa = ISC_TRUE;
else
aa = ISC_FALSE;
qname = &fctx->name;
} else {
static isc_boolean_t
validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
if (rdataset->type == dns_rdatatype_nsec3) {
/*
* We're being invoked by answer_response() after it has
* followed a CNAME/DNAME chain.
* NSEC3 records are not allowed to
* appear in the answer section.
*/
qname = oqname;
aa = ISC_FALSE;
log_formerr(fctx, "NSEC3 in answer");
return (ISC_FALSE);
}
if (rdataset->type == dns_rdatatype_tkey) {
/*
* If the current qname is not a subdomain of the query
* domain, there's no point in looking at the authority
* section without doing DNSSEC validation.
*
* Until we do that validation, we'll just return success
* in this case.
* TKEY is not a valid record in a
* response to any query we can make.
*/
if (!dns_name_issubdomain(qname, &fctx->domain))
return (ISC_R_SUCCESS);
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);
}
/*
* We have to figure out if this is a negative response, or a
* referral.
*/
static void
fctx_increference(fetchctx_t *fctx) {
REQUIRE(VALID_FCTX(fctx));
/*
* Sometimes we can tell if its a negative response by looking at
* the message header.
*/
negative_response = ISC_FALSE;
if (message->rcode == dns_rcode_nxdomain ||
(message->counts[DNS_SECTION_ANSWER] == 0 &&
message->counts[DNS_SECTION_AUTHORITY] == 0))
negative_response = ISC_TRUE;
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
fctx->references++;
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
}
/*
* Process the authority section.
*/
ns_name = NULL;
ns_rdataset = NULL;
soa_name = NULL;
ds_name = NULL;
save_name = NULL;
save_type = dns_rdatatype_none;
result = dns_message_firstname(message, section);
while (result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, section, &name);
if (dns_name_issubdomain(name, &fctx->domain)) {
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)) {
/*
* Look for NS/SOA RRsets first.
* This fctx is already shutdown; we were just
* waiting for the last reference to go away.
*/
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;