]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[master] refactor resquery_response() and related functions
authorEvan Hunt <each@isc.org>
Fri, 4 Aug 2017 23:07:33 +0000 (16:07 -0700)
committerEvan Hunt <each@isc.org>
Fri, 4 Aug 2017 23:08:11 +0000 (16:08 -0700)
4669. [func] Iterative query logic in resolver.c has been
refactored into smaller functions and commented,
for improved readability, maintainability and
testability. [RT #45362]

CHANGES
bin/named/query.c
bin/tests/system/dnssec/ns4/named4.conf
bin/tests/system/dnssec/ns4/named5.conf [deleted file]
bin/tests/system/dnssec/tests.sh
bin/tests/system/dnstap/ns3/named.conf
bin/tests/system/dnstap/tests.sh
doc/arm/notes.xml
lib/dns/resolver.c

diff --git a/CHANGES b/CHANGES
index d3de6fb3393db3644f3fed9652558ca028f2c519..8693dfe68fff7ca199464b88889a0eca43b9c69b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+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]
 
index 9d7eff69511709ec6cf00a5485dea3c1554b9fe9..5321a73c87a4e063b07fe496cc6acb0acdaeeb6e 100644 (file)
@@ -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
index eac9a3b8e6fdb3bc4665f654cc160964e2ee3a55..138e534c87b64d4da158a02399fac8b535f4a6a0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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;
diff --git a/bin/tests/system/dnssec/ns4/named5.conf b/bin/tests/system/dnssec/ns4/named5.conf
deleted file mode 100644 (file)
index 7d1d1a0..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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; };
-       };
-};
index eea8f358f01ca768a70c344c9af4935b788ee4ef..11c46d8ae7907073be6017452180ab44a8bb47dd 100644 (file)
@@ -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
 
index f6a74a7bf0a6f81a8e5cda53ab1e0da243849c35..05c84d2182097f2c7013c2c39637c319626dc968 100644 (file)
@@ -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; };
index 4c06b7c2515b9f527760ec38928617bd250d9326..014d2acf737b302ef5802673a641b968084e4aeb 100644 (file)
@@ -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
index 6729459a771453b8caafa3c48d8422275819226d..1fd4fa67f3404b7f710268254706a3f7691dba6a 100644 (file)
       </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
index f362c3bcddb472bf8aa1aec6900f6b9f5fb6b1db..5b655308fad5d968b9e762f92876e66ab040f397 100644 (file)
@@ -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;
-                            rdataset = ISC_LIST_NEXT(rdataset, link)) {
-                               type = rdataset->type;
-                               if (type == dns_rdatatype_rrsig)
-                                       type = rdataset->covers;
-                               if (((type == dns_rdatatype_ns ||
-                                     type == dns_rdatatype_soa) &&
-                                    !dns_name_issubdomain(qname, 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(qname, qbuf,
-                                                            sizeof(qbuf));
-                                       log_formerr(fctx,
-                                                   "unrelated %s %s in "
-                                                   "%s authority section",
-                                                   tbuf, nbuf, qbuf);
-                                       goto nextname;
-                               }
-                               if (type == dns_rdatatype_ns) {
-                                       /*
-                                        * NS or RRSIG NS.
-                                        *
-                                        * Only one set of NS RRs is allowed.
-                                        */
-                                       if (rdataset->type ==
-                                           dns_rdatatype_ns) {
-                                               if (ns_name != NULL &&
-                                                   name != ns_name) {
-                                                       log_formerr(fctx,
-                                                               "multiple NS "
-                                                               "RRsets in "
-                                                               "authority "
-                                                               "section");
-                                                       return (DNS_R_FORMERR);
-                                               }
-                                               ns_name = name;
-                                               ns_rdataset = rdataset;
-                                       }
-                                       name->attributes |=
-                                               DNS_NAMEATTR_CACHE;
-                                       rdataset->attributes |=
-                                               DNS_RDATASETATTR_CACHE;
-                                       rdataset->trust = dns_trust_glue;
-                               }
-                               if (type == dns_rdatatype_soa) {
-                                       /*
-                                        * SOA, or RRSIG SOA.
-                                        *
-                                        * Only one SOA is allowed.
-                                        */
-                                       if (rdataset->type ==
-                                           dns_rdatatype_soa) {
-                                               if (soa_name != NULL &&
-                                                   name != soa_name) {
-                                                       log_formerr(fctx,
-                                                               "multiple SOA "
-                                                               "RRs in "
-                                                               "authority "
-                                                               "section");
-                                                       return (DNS_R_FORMERR);
-                                               }
-                                               soa_name = name;
-                                       }
-                                       name->attributes |=
-                                               DNS_NAMEATTR_NCACHE;
-                                       rdataset->attributes |=
-                                               DNS_RDATASETATTR_NCACHE;
-                                       if (aa)
-                                               rdataset->trust =
-                                                   dns_trust_authauthority;
-                                       else if (ISFORWARDER(fctx->addrinfo))
-                                               rdataset->trust =
-                                                       dns_trust_answer;
-                                       else
-                                               rdataset->trust =
-                                                       dns_trust_additional;
-                               }
-                       }
+                       bucket_empty = fctx_unlink(fctx);
+                       fctx_destroy(fctx);
+               } else {
+                       /*
+                        * Initiate shutdown.
+                        */
+                       fctx_shutdown(fctx);
                }
- nextname:
-               result = dns_message_nextname(message, section);
-               if (result == ISC_R_NOMORE)
-                       break;
-               else if (result != ISC_R_SUCCESS)
-                       return (result);
        }
+       return (bucket_empty);
+}
 
-       log_ns_ttl(fctx, "noanswer_response");
+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;
 
-       if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) &&
-           !dns_name_equal(ns_name, dns_rootname))
-               trim_ns_ttl(fctx, ns_name, ns_rdataset);
+       REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+       fevent = (dns_fetchevent_t *)event;
+       fctx = event->ev_arg;
+       REQUIRE(VALID_FCTX(fctx));
+       res = fctx->res;
 
-       /*
-        * 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 (soa_name != NULL)
-               negative_response = ISC_TRUE;
+       UNUSED(task);
+       FCTXTRACE("resume_dslookup");
 
-       result = dns_message_firstname(message, section);
-       while (result == ISC_R_SUCCESS) {
+       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;
+       if (fevent->result == ISC_R_CANCELED) {
+               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");
+
+               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.
+                */
+               dns_fixedname_init(&fixed);
+               domain = dns_fixedname_name(&fixed);
+               dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL);
+               if (dns_name_equal(&fctx->nsname, domain)) {
+                       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);
+               FCTXTRACE("continuing to look for parent's NS records");
+               result = dns_resolver_createfetch(fctx->res, &fctx->nsname,
+                                                 dns_rdatatype_ns, domain,
+                                                 nsrdataset, NULL,
+                                                 fctx->options, task,
+                                                 resume_dslookup, fctx,
+                                                 &fctx->nsrrset, NULL,
+                                                 &fctx->nsfetch);
+               if (result != ISC_R_SUCCESS)
+                       fctx_done(fctx, result, __LINE__);
+               else {
+                       LOCK(&res->buckets[bucketnum].lock);
+                       locked = ISC_TRUE;
+                       fctx->references++;
+               }
+       }
+
+ cleanup:
+       if (dns_rdataset_isassociated(&nameservers))
+               dns_rdataset_disassociate(&nameservers);
+       if (dns_rdataset_isassociated(fevent->rdataset))
+               dns_rdataset_disassociate(fevent->rdataset);
+       INSIST(fevent->sigrdataset == NULL);
+       isc_event_free(&event);
+       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);
-               if (dns_name_issubdomain(name, &fctx->domain)) {
-                       for (rdataset = ISC_LIST_HEAD(name->list);
-                            rdataset != NULL;
-                            rdataset = ISC_LIST_NEXT(rdataset, link)) {
-                               type = rdataset->type;
-                               if (type == dns_rdatatype_rrsig)
-                                       type = rdataset->covers;
-                               if (type == dns_rdatatype_nsec ||
-                                   type == dns_rdatatype_nsec3) {
-                                       /*
-                                        * NSEC or RRSIG NSEC.
-                                        */
-                                       if (negative_response) {
-                                               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 (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.
-                                        */
-                               } else if (type == 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 (ns_name == NULL) {
-                                               log_formerr(fctx,
-                                                           "DS with no "
-                                                           "referral");
-                                               return (DNS_R_FORMERR);
-                                       }
-                                       if (rdataset->type ==
-                                           dns_rdatatype_ds) {
-                                               if (ds_name != NULL &&
-                                                   name != ds_name) {
-                                                       log_formerr(fctx,
-                                                               "DS doesn't "
-                                                               "match "
-                                                               "referral "
-                                                               "(NS)");
-                                                       return (DNS_R_FORMERR);
-                                               }
-                                               ds_name = name;
-                                       }
-                                       name->attributes |=
-                                               DNS_NAMEATTR_CACHE;
+               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_CACHE;
-                                       if (aa)
-                                               rdataset->trust =
-                                                   dns_trust_authauthority;
-                                       else if (ISFORWARDER(fctx->addrinfo))
-                                               rdataset->trust =
-                                                       dns_trust_answer;
-                                       else
-                                               rdataset->trust =
-                                                       dns_trust_additional;
+                                               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);
+
+       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;
+
+       rctx_respinit(task, devent, query, fctx, &rctx);
+
+       /*
+        * 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;
+       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 {
-                       save_name = name;
-                       save_type = ISC_LIST_HEAD(name->list)->type;
+                       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;
                }
-               result = dns_message_nextname(message, section);
-               if (result == ISC_R_NOMORE)
-                       break;
-               else if (result != ISC_R_SUCCESS)
-                       return (result);
        }
 
        /*
-        * Trigger lookups for DNS nameservers.
+        * Negative caching
         */
-       if (negative_response && message->rcode == dns_rcode_noerror &&
-           fctx->type == dns_rdatatype_ds && soa_name != NULL &&
-           dns_name_equal(soa_name, qname) &&
-           !dns_name_equal(qname, dns_rootname))
-               return (DNS_R_CHASEDSSERVERS);
+       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;
 
        /*
-        * Did we find anything?
+        * 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.
         */
-       if (!negative_response && ns_name == NULL) {
-               /*
-                * Nope.
-                */
-               if (oqname != NULL) {
-                       /*
-                        * We've already got a partial CNAME/DNAME chain,
-                        * and haven't found else anything useful here, but
-                        * no error has occurred since we have an answer.
-                        */
-                       return (ISC_R_SUCCESS);
-               } else {
-                       /*
-                        * The responder is insane.
-                        */
-                       if (save_name == NULL) {
-                               log_formerr(fctx, "invalid response");
-                               return (DNS_R_FORMERR);
-                       }
-                       if (!dns_name_issubdomain(save_name, &fctx->domain)) {
-                               char nbuf[DNS_NAME_FORMATSIZE];
-                               char dbuf[DNS_NAME_FORMATSIZE];
-                               char tbuf[DNS_RDATATYPE_FORMATSIZE];
+       TIME_NOW(&rctx->tnow);
+       rctx->finish = &rctx->tnow;
+       isc_stdtime_get(&rctx->now);
 
-                               dns_rdatatype_format(save_type, tbuf,
-                                       sizeof(tbuf));
-                               dns_name_format(save_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);
-               }
+/*
+ * 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;
        }
 
        /*
-        * If we found both NS and SOA, they should be the same name.
+        * There can be multiple RRSIG and SIG records at a name so
+        * we treat these types as a subset of ANY.
         */
-       if (ns_name != NULL && soa_name != NULL && ns_name != soa_name) {
-               log_formerr(fctx, "NS/SOA mismatch");
-               return (DNS_R_FORMERR);
+       rctx->type = fctx->type;
+       if (rctx->type == dns_rdatatype_rrsig ||
+           rctx->type == dns_rdatatype_sig)
+       {
+               rctx->type = dns_rdatatype_any;
        }
 
        /*
-        * Do we have a referral?  (We only want to follow a referral if
-        * we're not following a chain.)
+        * Bigger than any valid DNAME label count.
         */
-       if (!negative_response && ns_name != NULL && oqname == NULL) {
-               /*
-                * 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(ns_name, &fctx->domain)) {
-                       log_formerr(fctx, "non-improving referral");
-                       return (DNS_R_FORMERR);
-               }
+       rctx->dname_labels = dns_name_countlabels(&fctx->name);
+       rctx->domain_labels = dns_name_countlabels(&fctx->domain);
 
-               /*
-                * If the referral name is not a parent of the query
-                * name, consider the responder insane.
-                */
-               if (! dns_name_issubdomain(&fctx->name, ns_name)) {
-                       /* Logged twice */
-                       log_formerr(fctx, "referral to non-parent");
-                       FCTXTRACE("referral to non-parent");
-                       return (DNS_R_FORMERR);
-               }
+       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) {
                /*
-                * Mark any additional data related to this rdataset.
-                * It's important that we do this before we change the
-                * query domain.
-                */
-               INSIST(ns_rdataset != NULL);
-               fctx->attributes |= FCTX_ATTR_GLUING;
-               (void)dns_rdataset_additionaldata(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.
+                * The problem might be that they don't understand EDNS0.
+                * Turn it off and try again.
                 */
-               if ((look_in_options & LOOK_FOR_GLUE_IN_ANSWER) != 0 &&
-                   (fctx->type == dns_rdatatype_aaaa ||
-                    fctx->type == dns_rdatatype_a))
-                       (void)dns_rdataset_additionaldata(ns_rdataset,
-                                                         check_answer, fctx);
-#endif
-               fctx->attributes &= ~FCTX_ATTR_GLUING;
+               rctx->retryopts |= DNS_FETCHOPT_NOEDNS0;
+               rctx->resend = ISC_TRUE;
+               add_bad_edns(fctx, &query->addrinfo->sockaddr);
+       } else {
                /*
-                * 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.
+                * There's no hope for this response.
                 */
-               if (ns_rdataset->ttl == 0)
-                       ns_rdataset->ttl = 1;
+               rctx->next_server = ISC_TRUE;
+
                /*
-                * 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.
+                * 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.
                 */
-               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(ns_name, fctx->mctx, &fctx->domain);
-               if (result != ISC_R_SUCCESS)
-                       return (result);
-               result = fcount_incr(fctx, ISC_TRUE);
-               if (result != ISC_R_SUCCESS)
-                       return (result);
-               fctx->attributes |= FCTX_ATTR_WANTCACHE;
-               fctx->ns_ttl_ok = ISC_FALSE;
-               log_ns_ttl(fctx, "DELEGATION");
-               return (DNS_R_DELEGATION);
+               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);
+}
 
-       /*
-        * Since we're not doing a referral, we don't want to cache any
-        * NS RRs we may have found.
-        */
-       if (ns_name != NULL)
-               ns_name->attributes &= ~DNS_NAMEATTR_CACHE;
+/*
+ * 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;
 
-       if (negative_response && oqname == NULL)
-               fctx->attributes |= FCTX_ATTR_WANTNCACHE;
+       result = dns_message_parse(fctx->rmessage, &rctx->devent->buffer, 0);
+       if (result == ISC_R_SUCCESS) {
+               return (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);
+               }
 
-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.
+                * 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.
                 */
-               log_formerr(fctx, "NSEC3 in answer");
-               return (ISC_FALSE);
-       }
-       if (rdataset->type == dns_rdatatype_tkey) {
+               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:
                /*
-                * TKEY is not a valid record in a
-                * response to any query we can make.
+                * Something bad has happened.
                 */
-               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);
+               rctx_done(rctx, result);
+               break;
        }
-       return (ISC_TRUE);
+
+       return (ISC_R_COMPLETE);
 }
 
-static isc_result_t
-answer_response(fetchctx_t *fctx) {
+/*
+ * 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;
-       dns_message_t *message = NULL;
-       dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL;
-       dns_name_t *aname = NULL, *cname = NULL, *dname = NULL;
-       dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
-       dns_rdataset_t *ardataset = NULL, *crdataset = NULL;
-       dns_rdataset_t *drdataset = NULL, *ns_rdataset = NULL;
-       isc_boolean_t done = ISC_FALSE, aa;
-       unsigned int dname_labels, domain_labels;
-       isc_boolean_t chaining = ISC_FALSE;
-       dns_rdatatype_t type;
-       dns_view_t *view = NULL;
-       dns_trust_t trust;
+       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;
 
-       REQUIRE(VALID_FCTX(fctx));
+       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);
+       }
+}
 
-       FCTXTRACE("answer_response");
+/*
+ * 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;
 
-       message = fctx->rmessage;
-       qname = &fctx->name;
-       view = fctx->res->view;
-       type = fctx->type;
+       /*
+        * 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_logpacket2(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_logpacket2(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);
+       }
 
        /*
-        * There can be multiple RRSIG and SIG records at a name so
-        * we treat these types as a subset of ANY.
+        * 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 (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) {
-               type = dns_rdatatype_any;
+       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);
        }
+}
 
-       /*
-        * Bigger than any valid DNAME label count.
-        */
-       dname_labels = dns_name_countlabels(qname);
-       domain_labels = dns_name_countlabels(&fctx->domain);
+/*
+ * 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;
 
-       /*
-        * Perform a single pass looking for the answer, cname or covering
-        * dname.
-        */
-       for (result = dns_message_firstname(message, DNS_SECTION_ANSWER);
-            result == ISC_R_SUCCESS;
-            result = dns_message_nextname(message, DNS_SECTION_ANSWER))
+       if ((fctx->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
+           ISFORWARDER(query->addrinfo))
        {
-               int order;
-               unsigned int nlabels;
-               dns_namereln_t namereln;
+               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
+                */
 
-               name = NULL;
-               dns_message_currentname(message, DNS_SECTION_ANSWER, &name);
-               namereln = dns_name_fullcompare(qname, 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 == type ||
-                                   type == dns_rdatatype_any)
-                               {
-                                       aname = name;
-                                       if (type != dns_rdatatype_any) {
-                                               ardataset = rdataset;
-                                       }
-                                       break;
-                               }
-                               if (rdataset->type == dns_rdatatype_cname) {
-                                       cname = name;
-                                       crdataset = rdataset;
-                                       break;
-                               }
+               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);
                        }
-                       break;
-
-               case dns_namereln_subdomain:
+               } else {
                        /*
-                        * 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.
+                        * 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.
                         */
-                       if (nlabels >= dname_labels || nlabels < domain_labels)
-                       {
-                               continue;
+                       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) {
                        /*
-                        * We are looking for the shortest DNAME if there
-                        * are multiple ones (which there shouldn't be).
+                        * 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.
                         */
-                       for (rdataset = ISC_LIST_HEAD(name->list);
-                            rdataset != NULL;
-                            rdataset = ISC_LIST_NEXT(rdataset, link))
-                       {
-                               if (rdataset->type != dns_rdatatype_dname) {
-                                       continue;
-                               }
-                               dname = name;
-                               drdataset = rdataset;
-                               dname_labels = nlabels;
-                               break;
-                       }
-                       break;
-               default:
-                       break;
+                       rctx->broken_server = DNS_R_LAME;
+                       rctx->next_server = ISC_TRUE;
+                       rctx_done(rctx, result);
+                       return (ISC_R_COMPLETE);
                }
        }
 
-       if (dname != NULL) {
-               aname = NULL;
-               ardataset = NULL;
-               cname = NULL;
-               crdataset = NULL;
-       } else if (aname != NULL) {
-               cname = NULL;
-               crdataset = NULL;
+       if (result != ISC_R_SUCCESS) {
+               if (result == DNS_R_FORMERR) {
+                       rctx->next_server = ISC_TRUE;
+               }
+               rctx_done(rctx, result);
+               return (ISC_R_COMPLETE);
        }
 
-       aa = ISC_TF((message->flags & DNS_MESSAGEFLAG_AA) != 0);
-       trust = aa ? dns_trust_authanswer : dns_trust_answer;
+       return (ISC_R_SUCCESS);
+}
 
-       if (aname != NULL && type == dns_rdatatype_any) {
-               for (rdataset = ISC_LIST_HEAD(aname->list);
-                    rdataset != NULL;
-                    rdataset = ISC_LIST_NEXT(rdataset, link))
-               {
-                       if (!validinanswer(rdataset, fctx)) {
-                               return (DNS_R_FORMERR);
-                       }
-                       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(view, aname, rdataset))
-                       {
-                               return (DNS_R_SERVFAIL);
-                       }
-                       if ((rdataset->type == dns_rdatatype_cname ||
-                            rdataset->type == dns_rdatatype_dname) &&
-                            !is_answertarget_allowed(fctx, qname, aname,
-                                                     rdataset, NULL))
-                       {
-                               return (DNS_R_SERVFAIL);
-                       }
-                       aname->attributes |= DNS_NAMEATTR_CACHE;
-                       aname->attributes |= DNS_NAMEATTR_ANSWER;
-                       rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
-                       rdataset->attributes |= DNS_RDATASETATTR_CACHE;
-                       rdataset->trust = trust;
-                       (void)dns_rdataset_additionaldata(rdataset,
-                                                         check_related,
-                                                         fctx);
-               }
-       } else if (aname != NULL) {
-               if (!validinanswer(ardataset, fctx))
-                       return (DNS_R_FORMERR);
-               if ((ardataset->type == dns_rdatatype_a ||
-                    ardataset->type == dns_rdatatype_aaaa) &&
-                   !is_answeraddress_allowed(view, aname, ardataset)) {
-                       return (DNS_R_SERVFAIL);
-               }
-               if ((ardataset->type == dns_rdatatype_cname ||
-                    ardataset->type == dns_rdatatype_dname) &&
-                    !is_answertarget_allowed(fctx, qname, aname, ardataset,
-                                             NULL))
-               {
-                       return (DNS_R_SERVFAIL);
-               }
-               aname->attributes |= DNS_NAMEATTR_CACHE;
-               aname->attributes |= DNS_NAMEATTR_ANSWER;
-               ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
-               ardataset->attributes |= DNS_RDATASETATTR_CACHE;
-               ardataset->trust = trust;
-               (void)dns_rdataset_additionaldata(ardataset, check_related,
-                                                 fctx);
-               for (sigrdataset = ISC_LIST_HEAD(aname->list);
-                    sigrdataset != NULL;
-                    sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) {
-                       if (!validinanswer(sigrdataset, fctx))
-                               return (DNS_R_FORMERR);
-                       if (sigrdataset->type != dns_rdatatype_rrsig ||
-                           sigrdataset->covers != type)
-                               continue;
-                       sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
-                       sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
-                       sigrdataset->trust = trust;
-                       break;
-               }
-       } else if (cname != NULL) {
-               if (!validinanswer(crdataset, fctx)) {
-                       return (DNS_R_FORMERR);
-               }
-               if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key ||
-                   type == dns_rdatatype_nsec)
-               {
-                       char buf[DNS_RDATATYPE_FORMATSIZE];
-                       dns_rdatatype_format(type, buf, sizeof(buf));
-                       log_formerr(fctx, "CNAME response for %s RR", buf);
-                       return (DNS_R_FORMERR);
+/*
+ * 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);
                }
-               if (!is_answertarget_allowed(fctx, qname, cname, crdataset,
-                                            NULL))
-               {
-                       return (DNS_R_SERVFAIL);
-               }
-               cname->attributes |= DNS_NAMEATTR_CACHE;
-               cname->attributes |= DNS_NAMEATTR_ANSWER;
-               cname->attributes |= DNS_NAMEATTR_CHAINING;
-               crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
-               crdataset->attributes |= DNS_RDATASETATTR_CACHE;
-               crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
-               crdataset->trust = trust;
-               for (sigrdataset = ISC_LIST_HEAD(cname->list);
-                    sigrdataset != NULL;
-                    sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
-               {
-                       if (!validinanswer(sigrdataset, fctx)) {
-                               return (DNS_R_FORMERR);
-                       }
-                       if (sigrdataset->type != dns_rdatatype_rrsig ||
-                           sigrdataset->covers != dns_rdatatype_cname)
-                       {
-                               continue;
-                       }
-                       sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
-                       sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
-                       sigrdataset->trust = trust;
-                       break;
+       } else if (rctx->aname != NULL) {
+               result = rctx_answer_match(rctx);
+               if (result == ISC_R_COMPLETE) {
+                       return (rctx->result);
                }
-               chaining = ISC_TRUE;
-       } else if (dname != NULL) {
-               if (!validinanswer(drdataset, fctx)) {
-                       return (DNS_R_FORMERR);
+       } else if (rctx->cname != NULL) {
+               result = rctx_answer_cname(rctx);
+               if (result == ISC_R_COMPLETE) {
+                       return (rctx->result);
                }
-               if (!is_answertarget_allowed(fctx, qname, dname, drdataset,
-                                            &chaining)) {
-                       return (DNS_R_SERVFAIL);
-               }
-               dname->attributes |= DNS_NAMEATTR_CACHE;
-               dname->attributes |= DNS_NAMEATTR_ANSWER;
-               dname->attributes |= DNS_NAMEATTR_CHAINING;
-               drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
-               drdataset->attributes |= DNS_RDATASETATTR_CACHE;
-               drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
-               drdataset->trust = trust;
-               for (sigrdataset = ISC_LIST_HEAD(dname->list);
-                    sigrdataset != NULL;
-                    sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
-               {
-                       if (!validinanswer(sigrdataset, fctx)) {
-                               return (DNS_R_FORMERR);
-                       }
-                       if (sigrdataset->type != dns_rdatatype_rrsig ||
-                           sigrdataset->covers != dns_rdatatype_dname)
-                       {
-                               continue;
-                       }
-                       sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
-                       sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
-                       sigrdataset->trust = trust;
-                       break;
+       } 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");
@@ -7138,7 +7761,7 @@ answer_response(fetchctx_t *fctx) {
        /*
         * Did chaining end before we got the final answer?
         */
-       if (chaining) {
+       if (rctx->chaining) {
                return (ISC_R_SUCCESS);
        }
 
@@ -7146,1317 +7769,1241 @@ answer_response(fetchctx_t *fctx) {
         * We didn't end with an incomplete chain, so the rcode should be
         * "no error".
         */
-       if (message->rcode != dns_rcode_noerror) {
+       if (fctx->rmessage->rcode != dns_rcode_noerror) {
                log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE "
                                  "indicates error");
                return (DNS_R_FORMERR);
        }
 
        /*
-        * Examine the authority section (if there is one).
-        *
-        * We expect there to be only one owner name for all the rdatasets
-        * in this section, and we expect that it is not external.
+        * Cache records in the authority section, if
+        * there are any suitable for caching.
         */
-       result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
-       while (!done && result == ISC_R_SUCCESS) {
-               isc_boolean_t external;
-               name = NULL;
-               dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
-               external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
-               if (!external) {
-                       /*
-                        * 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 (aa && !chaining) {
-                                               rdataset->trust =
-                                                   dns_trust_authauthority;
-                                       } else {
-                                               rdataset->trust =
-                                                   dns_trust_additional;
-                                       }
-
-                                       if (rdataset->type == dns_rdatatype_ns)
-                                       {
-                                               ns_name = name;
-                                               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(message, DNS_SECTION_AUTHORITY);
-       }
-       if (result == ISC_R_NOMORE)
-               result = ISC_R_SUCCESS;
+       rctx_authority_positive(rctx);
 
-       log_ns_ttl(fctx, "answer_response");
+       log_ns_ttl(fctx, "rctx_answer");
 
-       if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) &&
-           !dns_name_equal(ns_name, dns_rootname))
-               trim_ns_ttl(fctx, ns_name, ns_rdataset);
+       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 (result);
+       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
-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);
-}
+rctx_answer_scan(respctx_t *rctx) {
+       isc_result_t result;
+       fetchctx_t *fctx = rctx->fctx;
+       dns_rdataset_t *rdataset = NULL;
 
-static isc_boolean_t
-fctx_decreference(fetchctx_t *fctx) {
-       isc_boolean_t bucket_empty = ISC_FALSE;
+       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;
 
-       REQUIRE(VALID_FCTX(fctx));
+               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;
 
-       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)) {
+               case dns_namereln_subdomain:
                        /*
-                        * This fctx is already shutdown; we were just
-                        * waiting for the last reference to go away.
+                        * 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.
                         */
-                       bucket_empty = fctx_unlink(fctx);
-                       fctx_destroy(fctx);
-               } else {
+                       if (nlabels >= rctx->dname_labels ||
+                           nlabels < rctx->domain_labels)
+                       {
+                               continue;
+                       }
+
                        /*
-                        * Initiate shutdown.
+                        * We are looking for the shortest DNAME if there
+                        * are multiple ones (which there shouldn't be).
                         */
-                       fctx_shutdown(fctx);
+                       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;
                }
        }
-       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;
+       /*
+        * 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;
+       }
+}
 
-       REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
-       fevent = (dns_fetchevent_t *)event;
-       fctx = event->ev_arg;
-       REQUIRE(VALID_FCTX(fctx));
-       res = fctx->res;
+/*
+ * 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;
 
-       UNUSED(task);
-       FCTXTRACE("resume_dslookup");
+       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 (fevent->node != NULL)
-               dns_db_detachnode(fevent->db, &fevent->node);
-       if (fevent->db != NULL)
-               dns_db_detach(&fevent->db);
+               if ((fctx->type == dns_rdatatype_sig ||
+                    fctx->type == dns_rdatatype_rrsig) &&
+                   rdataset->type != fctx->type)
+               {
+                       continue;
+               }
 
-       dns_rdataset_init(&nameservers);
+               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);
+               }
 
-       bucketnum = fctx->bucketnum;
-       if (fevent->result == ISC_R_CANCELED) {
-               dns_resolver_destroyfetch(&fctx->nsfetch);
-               fctx_done(fctx, ISC_R_CANCELED, __LINE__);
-       } else if (fevent->result == ISC_R_SUCCESS) {
+               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);
+               }
 
-               FCTXTRACE("resuming DS lookup");
+               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;
 
-               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");
+               (void)dns_rdataset_additionaldata(rdataset, check_related,
+                                                 fctx);
+       }
 
-               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;
+       return (ISC_R_SUCCESS);
+}
 
-               /*
-                * Retrieve state from fctx->nsfetch before we destroy it.
-                */
-               dns_fixedname_init(&fixed);
-               domain = dns_fixedname_name(&fixed);
-               dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL);
-               if (dns_name_equal(&fctx->nsname, domain)) {
-                       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);
+/*
+ * 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 (dns_rdataset_isassociated(fevent->rdataset))
-                       dns_rdataset_disassociate(fevent->rdataset);
-               FCTXTRACE("continuing to look for parent's NS records");
-               result = dns_resolver_createfetch(fctx->res, &fctx->nsname,
-                                                 dns_rdatatype_ns, domain,
-                                                 nsrdataset, NULL,
-                                                 fctx->options, task,
-                                                 resume_dslookup, fctx,
-                                                 &fctx->nsrrset, NULL,
-                                                 &fctx->nsfetch);
-               if (result != ISC_R_SUCCESS)
-                       fctx_done(fctx, result, __LINE__);
-               else {
-                       LOCK(&res->buckets[bucketnum].lock);
-                       locked = ISC_TRUE;
-                       fctx->references++;
-               }
+       if (!validinanswer(rctx->ardataset, fctx)) {
+               rctx->result = DNS_R_FORMERR;
+               return (ISC_R_COMPLETE);
        }
 
- cleanup:
-       if (dns_rdataset_isassociated(&nameservers))
-               dns_rdataset_disassociate(&nameservers);
-       if (dns_rdataset_isassociated(fevent->rdataset))
-               dns_rdataset_disassociate(fevent->rdataset);
-       INSIST(fevent->sigrdataset == NULL);
-       isc_event_free(&event);
-       if (!locked)
-               LOCK(&res->buckets[bucketnum].lock);
-       bucket_empty = fctx_decreference(fctx);
-       UNLOCK(&res->buckets[bucketnum].lock);
-       if (bucket_empty)
-               empty_bucket(res);
-}
+       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);
+       }
 
-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;
+       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 (result = dns_message_firstname(message, section);
-            result == ISC_R_SUCCESS;
-            result = dns_message_nextname(message, section))
+       for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list);
+            sigrdataset != NULL;
+            sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
        {
-               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);
-                       }
+               if (!validinanswer(sigrdataset, fctx)) {
+                       rctx->result = DNS_R_FORMERR;
+                       return (ISC_R_COMPLETE);
                }
-       }
-}
 
-static void
-checknames(dns_message_t *message) {
+               if (sigrdataset->type != dns_rdatatype_rrsig ||
+                   sigrdataset->covers != rctx->type)
+               {
+                       continue;
+               }
 
-       checknamessection(message, DNS_SECTION_ANSWER);
-       checknamessection(message, DNS_SECTION_AUTHORITY);
-       checknamessection(message, DNS_SECTION_ADDITIONAL);
+               sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+               sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+               sigrdataset->trust = rctx->trust;
+               break;
+       }
+
+       return (ISC_R_SUCCESS);
 }
 
 /*
- * Log server NSID at log level 'level'
+ * rctx_answer_cname():
+ * Handle answers containing a CNAME. Cache the CNAME, and flag that
+ * there may be additional chain answers to find.
  */
-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;
+static isc_result_t
+rctx_answer_cname(respctx_t *rctx) {
+       dns_rdataset_t *sigrdataset = NULL;
+       fetchctx_t *fctx = rctx->fctx;
 
-       /* 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;
+       if (!validinanswer(rctx->crdataset, fctx)) {
+               rctx->result = DNS_R_FORMERR;
+               return (ISC_R_COMPLETE);
+       }
 
-       /* 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];
+       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);
        }
-       *p = '\0';
 
-       /* Make printable version */
-       p = pbuf;
-       for (i = 0; i < nsid_len; i++) {
-               if (isprint(nsid[i]))
-                       *p++ = nsid[i];
-               else
-                       *p++ = '.';
+       if (!is_answertarget_allowed(fctx, &fctx->name,
+                                    rctx->cname, rctx->crdataset, NULL))
+       {
+               rctx->result = DNS_R_SERVFAIL;
+               return (ISC_R_COMPLETE);
        }
-       *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);
+       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);
 }
 
-static isc_boolean_t
-iscname(fetchctx_t *fctx) {
-       isc_result_t result;
+/*
+ * 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);
 
-       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;
+       if (!is_answertarget_allowed(fctx, &fctx->name, rctx->dname,
+                                    rctx->drdataset, &rctx->chaining))
+       {
+               rctx->result = DNS_R_SERVFAIL;
+               return (ISC_R_COMPLETE);
+       }
 
-       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))
+       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;
-               for (rdataset = ISC_LIST_HEAD(name->list);
-                    rdataset != NULL;
-                    rdataset = ISC_LIST_NEXT(rdataset, link))
-                       if (rdataset->type == dns_rdatatype_ns)
-                               return (ISC_TRUE);
+               }
+
+               sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
+               sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
+               sigrdataset->trust = rctx->trust;
+               break;
        }
-       return (ISC_FALSE);
+
+       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
-process_opt(resquery_t *query, dns_rdataset_t *opt) {
-       dns_rdata_t rdata;
-       isc_buffer_t optbuf;
+rctx_authority_positive(respctx_t *rctx) {
+       fetchctx_t *fctx = rctx->fctx;
+       isc_boolean_t done = ISC_FALSE;
        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(opt);
-       if (result == ISC_R_SUCCESS) {
-               dns_rdata_init(&rdata);
-               dns_rdataset_current(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)
-                                       log_nsid(&optbuf, optlen, query,
-                                                ISC_LOG_DEBUG(3),
-                                                query->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;
+       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;
                                }
-                               optvalue = isc_buffer_current(&optbuf);
-                               compute_cc(query, cookie, sizeof(cookie));
-                               INSIST(query->fctx->rmessage->cc_bad == 0 &&
-                                      query->fctx->rmessage->cc_ok == 0);
-                               if (optlen >= 8U &&
-                                   memcmp(cookie, optvalue, 8) == 0) {
-                                       query->fctx->rmessage->cc_ok = 1;
-                                       inc_stats(query->fctx->res,
-                                                 dns_resstatscounter_cookieok);
-                                       addrinfo = query->addrinfo;
-                                       dns_adb_setcookie(query->fctx->adb,
-                                                         addrinfo, optvalue,
-                                                         optlen);
-                               } else
-                                       query->fctx->rmessage->cc_bad = 1;
-                               isc_buffer_forward(&optbuf, optlen);
-                               inc_stats(query->fctx->res,
-                                         dns_resstatscounter_cookiein);
-                               seen_cookie = ISC_TRUE;
-                               break;
-                       default:
-                               isc_buffer_forward(&optbuf, optlen);
-                               break;
                        }
                }
-               INSIST(isc_buffer_remaininglength(&optbuf) == 0U);
+
+               result = dns_message_nextname(fctx->rmessage,
+                                             DNS_SECTION_AUTHORITY);
        }
 }
 
-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;
-       isc_boolean_t keep_trying, get_nameservers, resend, nextitem;
-       isc_boolean_t truncated;
-       dns_message_t *message;
-       dns_rdataset_t *opt;
-       fetchctx_t *fctx;
-       dns_name_t *fname;
-       dns_fixedname_t foundname;
-       isc_stdtime_t now;
-       isc_time_t tnow, *finish;
-       dns_adbaddrinfo_t *addrinfo;
-       unsigned int options;
-       unsigned int findoptions;
-       isc_result_t broken_server;
-       badnstype_t broken_type = badns_response;
-       isc_boolean_t no_response;
-       unsigned int bucketnum;
-       dns_resolver_t *res;
-       isc_boolean_t bucket_empty;
-#ifdef HAVE_DNSTAP
-       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 */
+/*
+ * 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;
 
-       REQUIRE(VALID_QUERY(query));
-       fctx = query->fctx;
-       options = query->options;
-       REQUIRE(VALID_FCTX(fctx));
-       REQUIRE(event->ev_type == DNS_EVENT_DISPATCH);
+       FCTXTRACE("rctx_answer_none");
 
-       QTRACE("response");
+       rctx_answer_init(rctx);
 
-       res = fctx->res;
-       if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET)
-               inc_stats(res, dns_resstatscounter_responsev4);
-       else
-               inc_stats(res, dns_resstatscounter_responsev6);
+       /*
+        * 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;
+       }
 
-       (void)isc_timer_touch(fctx->timer);
+       /*
+        * Process the authority section
+        */
+       result = rctx_authority_negative(rctx);
+       if (result == ISC_R_COMPLETE) {
+               return (rctx->result);
+       }
 
-       keep_trying = ISC_FALSE;
-       broken_server = ISC_R_SUCCESS;
-       get_nameservers = ISC_FALSE;
-       resend = ISC_FALSE;
-       nextitem = ISC_FALSE;
-       truncated = ISC_FALSE;
-       finish = NULL;
-       no_response = ISC_FALSE;
+       log_ns_ttl(fctx, "rctx_answer_none");
 
-       if (res->exiting) {
-               result = ISC_R_SHUTTINGDOWN;
-               FCTXTRACE("resolver shutting down");
-               goto done;
+       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);
        }
 
-       fctx->timeouts = 0;
-       fctx->timeout = ISC_FALSE;
-       fctx->addrinfo = query->addrinfo;
+       /*
+        * 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;
+       }
 
        /*
-        * 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.
+        * Process DNSSEC records in the authority section.
         */
-       TIME_NOW(&tnow);
-       finish = &tnow;
-       isc_stdtime_get(&now);
+       result = rctx_authority_dnssec(rctx);
+       if (result == ISC_R_COMPLETE) {
+               return (rctx->result);
+       }
 
        /*
-        * Did the dispatcher have a problem?
+        * Trigger lookups for DNS nameservers.
         */
-       if (devent->result != ISC_R_SUCCESS) {
-               if (devent->result == ISC_R_EOF &&
-                   (query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
-                       /*
-                        * The problem might be that they
-                        * don't understand EDNS0.  Turn it
-                        * off and try again.
-                        */
-                       options |= DNS_FETCHOPT_NOEDNS0;
-                       resend = ISC_TRUE;
-                       add_bad_edns(fctx, &query->addrinfo->sockaddr);
+       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 {
-                       /*
-                        * There's no hope for this query.
-                        */
-                       keep_trying = ISC_TRUE;
+                       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 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)) {
-                                   broken_server = devent->result;
-                                   broken_type = badns_unreachable;
-                                   finish = NULL;
-                                   no_response = ISC_TRUE;
-                       }
-               }
-               FCTXTRACE3("dispatcher failure", devent->result);
-               goto done;
+       if (rctx->ns_in_answer) {
+               INSIST(fctx->type == dns_rdatatype_ns);
+               section = DNS_SECTION_ANSWER;
+       } else {
+               section = DNS_SECTION_AUTHORITY;
        }
 
-       message = fctx->rmessage;
+       result = dns_message_firstname(fctx->rmessage, section);
+       if (result != ISC_R_SUCCESS) {
+               return (ISC_R_SUCCESS);
+       }
 
-       if (query->tsig != NULL) {
-               result = dns_message_setquerytsig(message, query->tsig);
+       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) {
-                       FCTXTRACE3("unable to set query tsig", result);
-                       goto done;
+                       finished = ISC_TRUE;
                }
-       }
 
-       if (query->tsigkey) {
-               result = dns_message_settsigkey(message, query->tsigkey);
-               if (result != ISC_R_SUCCESS) {
-                       FCTXTRACE3("unable to set tsig key", result);
-                       goto done;
+               if (!dns_name_issubdomain(name, &fctx->domain)) {
+                       continue;
                }
-       }
 
-       dns_message_setclass(message, res->rdclass);
+               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;
+                       }
 
-       if ((options & DNS_FETCHOPT_TCP) == 0) {
-               if ((options & DNS_FETCHOPT_NOEDNS0) == 0)
-                       dns_adb_setudpsize(fctx->adb, query->addrinfo,
-                                  isc_buffer_usedlength(&devent->buffer));
-               else
-                       dns_adb_plainresponse(fctx->adb, query->addrinfo);
-       }
-       result = dns_message_parse(message, &devent->buffer, 0);
-       if (result != ISC_R_SUCCESS) {
-               FCTXTRACE3("message failed to parse", result);
-               switch (result) {
-               case ISC_R_UNEXPECTEDEND:
-                       if (!message->question_ok ||
-                           (message->flags & DNS_MESSAGEFLAG_TC) == 0 ||
-                           (options & DNS_FETCHOPT_TCP) != 0) {
+                       switch (type) {
+                       case dns_rdatatype_ns:
                                /*
-                                * 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.
+                                * NS or RRSIG NS.
+                                *
+                                * Only one set of NS RRs is allowed.
                                 */
-                               if ((query->options & DNS_FETCHOPT_NOEDNS0)
-                                   == 0) {
-                                       /*
-                                        * The problem might be that they
-                                        * don't understand EDNS0.  Turn it
-                                        * off and try again.
-                                        */
-                                       options |= DNS_FETCHOPT_NOEDNS0;
-                                       resend = ISC_TRUE;
-                                       add_bad_edns(fctx,
-                                                   &query->addrinfo->sockaddr);
-                                       inc_stats(res,
-                                                dns_resstatscounter_edns0fail);
-                               } else {
-                                       broken_server = result;
-                                       keep_trying = ISC_TRUE;
+                               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;
                                }
-                               goto done;
-                       }
-                       /*
-                        * We defer retrying via TCP for a bit so we can
-                        * check out this message further.
-                        */
-                       truncated = ISC_TRUE;
-                       break;
-               case DNS_R_FORMERR:
-                       if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
+                               name->attributes |= DNS_NAMEATTR_CACHE;
+                               rdataset->attributes |= DNS_RDATASETATTR_CACHE;
+                               rdataset->trust = dns_trust_glue;
+                               break;
+                       case dns_rdatatype_soa:
                                /*
-                                * The problem might be that they
-                                * don't understand EDNS0.  Turn it
-                                * off and try again.
+                                * SOA, or RRSIG SOA.
+                                *
+                                * Only one SOA is allowed.
                                 */
-                               options |= DNS_FETCHOPT_NOEDNS0;
-                               resend = ISC_TRUE;
-                               add_bad_edns(fctx, &query->addrinfo->sockaddr);
-                               inc_stats(res, dns_resstatscounter_edns0fail);
-                       } else {
-                               broken_server = DNS_R_UNEXPECTEDRCODE;
-                               keep_trying = ISC_TRUE;
+                               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;
                        }
-                       goto done;
-               default:
-                       /*
-                        * Something bad has happened.
-                        */
-                       goto done;
                }
        }
 
+       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;
+       }
+
        /*
-        * Log the incoming packet.
+        * Cache DS NXDOMAIN seperately to other types.
         */
-       dns_message_logfmtpacket2(message, "received packet from",
-                                 &query->addrinfo->sockaddr,
-                                 DNS_LOGCATEGORY_RESOLVER,
-                                 DNS_LOGMODULE_PACKETS,
-                                 &dns_master_style_comment,
-                                 ISC_LOG_DEBUG(10), res->mctx);
+       if (fctx->rmessage->rcode == dns_rcode_nxdomain &&
+           fctx->type != dns_rdatatype_ds)
+       {
+               covers = dns_rdatatype_any;
+       } else {
+               covers = fctx->type;
+       }
 
-#ifdef HAVE_DNSTAP
        /*
-        * Log the response via dnstap.
+        * Cache any negative cache entries in the message.
         */
-       memset(&zr, 0, sizeof(zr));
-       result = dns_compress_init(&cctx, -1, 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);
+       result = ncache_message(fctx, rctx->query->addrinfo, covers, rctx->now);
+       if (result != ISC_R_SUCCESS) {
+               FCTXTRACE3("ncache_message complete", result);
        }
+}
 
-       if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) {
-               dtmsgtype = DNS_DTTYPE_FR;
+/*
+ * 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 {
-               dtmsgtype = DNS_DTTYPE_RR;
+               section = DNS_SECTION_AUTHORITY;
        }
 
-       if (query->exclusivesocket) {
-               sock = dns_dispatch_getentrysocket(query->dispentry);
-       } else {
-               sock = dns_dispatch_getsocket(query->dispatch);
+       result = dns_message_firstname(fctx->rmessage, section);
+       if (result != ISC_R_SUCCESS) {
+               return (ISC_R_SUCCESS);
        }
 
-       if (sock != NULL) {
-               result = isc_socket_getsockname(sock, &localaddr);
-               if (result == ISC_R_SUCCESS) {
-                       la = &localaddr;
+       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;
                }
-       }
 
-       dns_dt_send(res->view, dtmsgtype, la, &query->addrinfo->sockaddr,
-                   ISC_TF((query->options & DNS_FETCHOPT_TCP) != 0),
-                   &zr, &query->start, NULL, &devent->buffer);
-#endif /* HAVE_DNSTAP */
+               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;
+               }
 
-       if (message->rdclass != res->rdclass) {
-               resend = ISC_TRUE;
-               FCTXTRACE("bad class");
-               goto done;
-       }
+               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;
+                       }
 
-       /*
-        * Process receive opt record.
-        */
-       opt = dns_message_getopt(message);
-       if (opt != NULL)
-               process_opt(query, opt);
+                       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 (message->cc_bad && (options & DNS_FETCHOPT_TCP) == 0) {
-               /*
-                * If the COOKIE is bad, assume it is an attack and
-                * keep listening for a good answer.
-                */
-               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);
-               }
-               goto done;
-       }
+                               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);
+                               }
 
-       /*
-        * 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 (message->rcode) {
-       case dns_rcode_notimp:
-       case dns_rcode_formerr:
-               if (message->counts[DNS_SECTION_QUESTION] == 0)
-                       break;
-       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);
-                       nextitem = ISC_TRUE;
-                       goto done;
-               }
-               break;
-       }
+                               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;
+                               }
 
-       /*
-        * If the message is signed, check the signature.  If not, this
-        * returns success anyway.
-        */
-       result = dns_message_checksig(message, res->view);
-       if (result != ISC_R_SUCCESS) {
-               FCTXTRACE3("signature check failed", result);
-               goto done;
+                               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;
+                       }
+               }
        }
 
-       /*
-        * The dispatcher should ensure we only get responses with QR set.
-        */
-       INSIST((message->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).
-        */
+       return (ISC_R_SUCCESS);
+}
 
-       /*
-        * 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 (opt == NULL && !EDNSOK(query->addrinfo) &&
-           (message->rcode == dns_rcode_noerror ||
-            message->rcode == dns_rcode_nxdomain ||
-            message->rcode == dns_rcode_refused ||
-            message->rcode == dns_rcode_yxdomain) &&
-            bad_edns(fctx, &query->addrinfo->sockaddr)) {
-               dns_message_logpacket2(message,
-                                      "received packet (bad edns) from",
-                                      &query->addrinfo->sockaddr,
-                                      DNS_LOGCATEGORY_RESOLVER,
-                                      DNS_LOGMODULE_RESOLVER,
-                                      ISC_LOG_DEBUG(3),
-                                      res->mctx);
-               dns_adb_changeflags(fctx->adb, query->addrinfo,
-                                   DNS_FETCHOPT_NOEDNS0,
-                                   DNS_FETCHOPT_NOEDNS0);
-       } else if (opt == NULL && (message->flags & DNS_MESSAGEFLAG_TC) == 0 &&
-                  !EDNSOK(query->addrinfo) &&
-                  (message->rcode == dns_rcode_noerror ||
-                   message->rcode == dns_rcode_nxdomain) &&
-                  (query->options & 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_logpacket2(message, "received packet (no opt) from",
-                                      &query->addrinfo->sockaddr,
-                                      DNS_LOGCATEGORY_RESOLVER,
-                                      DNS_LOGMODULE_RESOLVER,
-                                      ISC_LOG_DEBUG(3), res->mctx);
-               dns_adb_changeflags(fctx->adb, query->addrinfo,
-                                   DNS_FETCHOPT_NOEDNS0,
-                                   DNS_FETCHOPT_NOEDNS0);
+/*
+ * 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);
        }
 
        /*
-        * 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.
+        * 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 (opt != NULL && !EDNSOK(query->addrinfo) &&
-           (query->options & DNS_FETCHOPT_NOEDNS0) == 0 &&
-           (message->rcode == dns_rcode_noerror ||
-            message->rcode == dns_rcode_nxdomain ||
-            message->rcode == dns_rcode_refused ||
-            message->rcode == dns_rcode_yxdomain)) {
-               dns_adb_changeflags(fctx->adb, query->addrinfo,
-                                   FCTX_ADDRINFO_EDNSOK,
-                                   FCTX_ADDRINFO_EDNSOK);
+       if (dns_name_equal(rctx->ns_name, &fctx->domain)) {
+               log_formerr(fctx, "non-improving referral");
+               rctx->result = DNS_R_FORMERR;
+               return (ISC_R_COMPLETE);
        }
 
        /*
-        * Deal with truncated responses by retrying using TCP.
+        * If the referral name is not a parent of the query
+        * name, consider the responder insane.
         */
-       if ((message->flags & DNS_MESSAGEFLAG_TC) != 0)
-               truncated = ISC_TRUE;
-
-       if (truncated) {
-               inc_stats(res, dns_resstatscounter_truncated);
-               if ((options & DNS_FETCHOPT_TCP) != 0) {
-                       broken_server = DNS_R_TRUNCATEDTCP;
-                       keep_trying = ISC_TRUE;
-               } else {
-                       options |= DNS_FETCHOPT_TCP;
-                       resend = ISC_TRUE;
-               }
-               FCTXTRACE3("message truncated", result);
-               goto done;
+       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);
        }
 
        /*
-        * Is it a query response?
+        * Mark any additional data related to this rdataset.
+        * It's important that we do this before we change the
+        * query domain.
         */
-       if (message->opcode != dns_opcode_query) {
-               /* XXXRTH Log */
-               broken_server = DNS_R_UNEXPECTEDOPCODE;
-               keep_trying = ISC_TRUE;
-               FCTXTRACE("invalid message opcode");
-               goto done;
+       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;
 
        /*
-        * Update statistics about erroneous responses.
+        * 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 (message->rcode != dns_rcode_noerror) {
-               switch (message->rcode) {
-               case dns_rcode_nxdomain:
-                       inc_stats(res, dns_resstatscounter_nxdomain);
-                       break;
-               case dns_rcode_servfail:
-                       inc_stats(res, dns_resstatscounter_servfail);
-                       break;
-               case dns_rcode_formerr:
-                       inc_stats(res, dns_resstatscounter_formerr);
-                       break;
-               case dns_rcode_refused:
-                       inc_stats(res, dns_resstatscounter_refused);
-                       break;
-               case dns_rcode_badvers:
-                       inc_stats(res, dns_resstatscounter_badvers);
-                       break;
-               case dns_rcode_badcookie:
-                       inc_stats(res, dns_resstatscounter_badcookie);
-                       break;
-               default:
-                       inc_stats(res, dns_resstatscounter_othererror);
-                       break;
-               }
+       if (rctx->ns_rdataset->ttl == 0) {
+               rctx->ns_rdataset->ttl = 1;
        }
 
        /*
-        * Is the remote server broken, or does it dislike us?
+        * 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.
         */
-       if (message->rcode != dns_rcode_noerror &&
-           message->rcode != dns_rcode_yxdomain &&
-           message->rcode != dns_rcode_nxdomain) {
-               isc_buffer_t b;
-               char code[64];
-               unsigned char cookie[64];
-
-               /*
-                * Some servers do not ignore unknown EDNS options.
-                */
-               if (!NOCOOKIE(query->addrinfo) &&
-                   (message->rcode == dns_rcode_formerr ||
-                    message->rcode == dns_rcode_notimp ||
-                    message->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);
-                       resend = ISC_TRUE;
-               } else if ((message->rcode == dns_rcode_formerr ||
-                           message->rcode == dns_rcode_notimp ||
-                           (message->rcode == dns_rcode_servfail &&
-                            dns_message_getopt(message) == NULL)) &&
-                          (query->options & 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.
-                        */
-                       options |= DNS_FETCHOPT_NOEDNS0;
-                       resend = ISC_TRUE;
-                       /*
-                        * Remember that they may not like EDNS0.
-                        */
-                       add_bad_edns(fctx, &query->addrinfo->sockaddr);
-                       inc_stats(res, dns_resstatscounter_edns0fail);
-               } else if (message->rcode == dns_rcode_formerr) {
-                       if (ISFORWARDER(query->addrinfo)) {
-                               /*
-                                * This forwarder doesn't understand us,
-                                * but other forwarders might.  Keep trying.
-                                */
-                               broken_server = DNS_R_REMOTEFORMERR;
-                               keep_trying = 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 (message->rcode == dns_rcode_badvers) {
-                       unsigned int version;
-                       isc_boolean_t setnocookie = ISC_FALSE;
-#if DNS_EDNS_VERSION > 0
-                       unsigned int flags, mask;
-#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 (!NOCOOKIE(query->addrinfo))
-                                       setnocookie = ISC_TRUE;
-                               dns_adb_changeflags(fctx->adb, query->addrinfo,
-                                                   FCTX_ADDRINFO_NOCOOKIE,
-                                                   FCTX_ADDRINFO_NOCOOKIE);
-                       }
-
-                       INSIST(opt != NULL);
-                       version = (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);
-                       }
+       INSIST(dns_name_countlabels(&fctx->domain) > 0);
+       fcount_decr(fctx);
 
-                       /*
-                        * 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);
-                               resend = ISC_TRUE;
-                       } else {
-                               broken_server = DNS_R_BADVERS;
-                               keep_trying = ISC_TRUE;
-                       }
-#else
-                       if (version == 0U && setnocookie) {
-                               resend = ISC_TRUE;
-                       } else {
-                               broken_server = DNS_R_BADVERS;
-                               keep_trying = ISC_TRUE;
-                       }
-#endif
-               } else if (message->rcode == dns_rcode_badcookie &&
-                          message->cc_ok) {
-                       /*
-                        * We have recorded the new cookie.
-                        */
-                       if (BADCOOKIE(query->addrinfo))
-                               query->options |= DNS_FETCHOPT_TCP;
-                       query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE;
-                       resend = ISC_TRUE;
-               } else {
-                       /*
-                        * XXXRTH log.
-                        */
-                       broken_server = DNS_R_UNEXPECTEDRCODE;
-                       INSIST(broken_server != ISC_R_SUCCESS);
-                       keep_trying = ISC_TRUE;
-               }
+       dns_name_free(&fctx->domain, fctx->mctx);
+       if (dns_rdataset_isassociated(&fctx->nameservers)) {
+               dns_rdataset_disassociate(&fctx->nameservers);
+       }
 
-               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);
-               goto done;
+       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);
        }
 
-       /*
-        * Is the server lame?
-        */
-       if (res->lame_ttl != 0 && !ISFORWARDER(query->addrinfo) &&
-           is_lame(fctx)) {
-               inc_stats(res, dns_resstatscounter_lame);
-               log_lame(fctx, query->addrinfo);
-               result = dns_adb_marklame(fctx->adb, query->addrinfo,
-                                         &fctx->name, fctx->type,
-                                         now + 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));
-               broken_server = DNS_R_LAME;
-               keep_trying = ISC_TRUE;
-               FCTXTRACE("lame server");
-               goto done;
+       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;
+
        /*
-        * Enforce delegations only zones like NET and COM.
+        * Reinitialize 'rctx' to prepare for following the delegation:
+        * set the get_nameservers and next_server flags appropriately and
+        * reset the fetch context counters.
+        *
         */
-       if (!ISFORWARDER(query->addrinfo) &&
-           dns_view_isdelegationonly(res->view, &fctx->domain) &&
-           !dns_name_equal(&fctx->domain, &fctx->name) &&
-           fix_mustbedelegationornxdomain(message, fctx)) {
-               char namebuf[DNS_NAME_FORMATSIZE];
-               char domainbuf[DNS_NAME_FORMATSIZE];
-               char addrbuf[ISC_SOCKADDR_FORMATSIZE];
-               char classbuf[64];
-               char typebuf[64];
+       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;
 
-               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(res->rdclass, classbuf,
-                                     sizeof(classbuf));
-               isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
-                                   sizeof(addrbuf));
+       return (ISC_R_COMPLETE);
+}
 
-               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);
-       }
+/*
+ * 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;
 
-       if ((res->options & DNS_RESOLVER_CHECKNAMES) != 0)
-               checknames(message);
+ again:
+       rescan = ISC_FALSE;
 
-       /*
-        * Clear cache bits.
-        */
-       fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE);
+       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;
+}
 
-       /*
-        * Did we get any answers?
-        */
-       if (message->counts[DNS_SECTION_ANSWER] > 0 &&
-           (message->rcode == dns_rcode_noerror ||
-            message->rcode == dns_rcode_yxdomain ||
-            message->rcode == dns_rcode_nxdomain)) {
+/*
+ * 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) {
                /*
-                * [normal case]
-                * We've got answers.  If it has an authoritative answer or an
-                * answer from a forwarder, we're done.
+                * Add this server to the list of bad servers for
+                * this fctx.
                 */
-               if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 ||
-                   ISFORWARDER(query->addrinfo))
-               {
-                       result = answer_response(fctx);
-                       if (result != ISC_R_SUCCESS)
-                               FCTXTRACE3("answer_response (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 = answer_response(fctx);
-                       if (result != ISC_R_SUCCESS)
-                               FCTXTRACE3("answer_response (!ANY/!CNAME)",
-                                          result);
-               } else if (fctx->type != dns_rdatatype_ns &&
-                          !betterreferral(fctx)) {
-                       /*
-                        * Lame response !!!.
-                        */
-                       result = answer_response(fctx);
-                       if (result != ISC_R_SUCCESS)
-                               FCTXTRACE3("answer_response (!NS)", result);
+               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;
+
+               dns_fixedname_init(&foundname);
+               fname = dns_fixedname_name(&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 {
-                       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.
-                                */
-                               result = noanswer_response(fctx, NULL,
-                                                  LOOK_FOR_NS_IN_ANSWER);
-                               if (result != ISC_R_SUCCESS)
-                                       FCTXTRACE3("noanswer_response (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.  LOOK_FOR_GLUE_IN_ANSWER will handle
-                                * such a corner case.
-                                */
-                               result = noanswer_response(fctx, NULL,
-                                                  LOOK_FOR_GLUE_IN_ANSWER);
-                               if (result != ISC_R_SUCCESS)
-                                       FCTXTRACE3("noanswer_response", 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.
-                                */
-                               broken_server = DNS_R_LAME;
-                               keep_trying = ISC_TRUE;
-                               goto done;
-                       }
-                       goto force_referral;
+                       name = &fctx->domain;
                }
+               result = dns_view_findzonecut(fctx->res->view,
+                                             name, fname,
+                                             rctx->now, findoptions,
+                                             ISC_TRUE,
+                                             &fctx->nameservers,
+                                             NULL);
                if (result != ISC_R_SUCCESS) {
-                       if (result == DNS_R_FORMERR)
-                               keep_trying = ISC_TRUE;
-                       goto done;
+                       FCTXTRACE("couldn't find a zonecut");
+                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       return;
                }
-       } else if (message->counts[DNS_SECTION_AUTHORITY] > 0 ||
-                  message->rcode == dns_rcode_noerror ||
-                  message->rcode == dns_rcode_nxdomain) {
-               /*
-                * NXDOMAIN, NXRDATASET, or referral.
-                */
-               result = noanswer_response(fctx, NULL, 0);
-               switch (result) {
-               case ISC_R_SUCCESS:
-               case DNS_R_CHASEDSSERVERS:
-                       break;
-               case DNS_R_DELEGATION:
- force_referral:
-                       /*
-                        * We don't have the answer, but we know a better
-                        * place to look.
-                        */
-                       get_nameservers = ISC_TRUE;
-                       keep_trying = ISC_TRUE;
-                       /*
-                        * We have a new set of name servers, and it
-                        * has not experienced any restarts yet.
-                        */
-                       fctx->restarts = 0;
-
+               if (!dns_name_issubdomain(fname, &fctx->domain)) {
                        /*
-                        * Update local statistics counters collected for each
-                        * new zone.
+                        * The best nameservers are now above our QDOMAIN.
                         */
-                       fctx->referrals++;
-                       fctx->querysent = 0;
-                       fctx->lamecount = 0;
-                       fctx->quotacount = 0;
-                       fctx->neterr = 0;
-                       fctx->badresp = 0;
-                       fctx->adberr = 0;
+                       FCTXTRACE("nameservers now above QDOMAIN");
+                       fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
+                       return;
+               }
 
-                       result = ISC_R_SUCCESS;
-                       break;
-               default:
-                       /*
-                        * Something has gone wrong.
-                        */
-                       if (result == DNS_R_FORMERR)
-                               keep_trying = ISC_TRUE;
-                       FCTXTRACE3("noanswer_response", result);
-                       goto done;
+               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;
                }
-       } else {
-               /*
-                * The server is insane.
-                */
-               /* XXXRTH Log */
-               broken_server = DNS_R_UNEXPECTEDRCODE;
-               keep_trying = ISC_TRUE;
-               FCTXTRACE("broken server: unexpected rcode");
-               goto done;
+               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_cleanupfinds(fctx);
+               fctx_cleanupaltfinds(fctx);
+               fctx_cleanupforwaddrs(fctx);
+               fctx_cleanupaltaddrs(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) {
+       fetchctx_t *fctx = rctx->fctx;
+       isc_result_t result;
+
+       FCTXTRACE("nextitem");
+       inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem);
+       INSIST(rctx->query->dispentry != NULL);
+       dns_message_reset(rctx->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__);
        }
+}
 
-       /*
-        * Follow additional section data chains.
-        */
-       chase_additional(fctx);
+/*
+ * 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;
 
-       /*
-        * 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, now);
-               if (result != ISC_R_SUCCESS) {
-                       FCTXTRACE3("cache_message complete", result);
-                       goto done;
-               }
-       }
+       add_bad(fctx, addrinfo, result, rctx->broken_type);
+       fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE);
+       fctx_cleanupfinds(fctx);
+       fctx_cleanupforwaddrs(fctx);
 
-       /*
-        * Ncache the negatively cacheable parts of the message.  This may
-        * also cause work to be queued to the DNSSEC validator.
-        */
-       if (WANTNCACHE(fctx)) {
-               dns_rdatatype_t covers;
+       n = dns_name_countlabels(&fctx->name);
+       dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname);
 
-               /*
-                * Cache DS NXDOMAIN seperately to other types.
-                */
-               if (message->rcode == dns_rcode_nxdomain &&
-                   fctx->type != dns_rdatatype_ds)
-                       covers = dns_rdatatype_any;
-               else
-                       covers = fctx->type;
+       FCTXTRACE("suspending DS lookup to find parent's NS records");
 
-               /*
-                * Cache any negative cache entries in the message.
-                */
-               result = ncache_message(fctx, query->addrinfo, covers, now);
+       result = dns_resolver_createfetch(fctx->res, &fctx->nsname,
+                                         dns_rdatatype_ns,
+                                         NULL, NULL, NULL,
+                                         fctx->options, 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)
-                       FCTXTRACE3("ncache_message complete", result);
+                       fctx_done(fctx, result, __LINE__);
        }
+}
 
- done:
-       /*
-        * Remember the query's addrinfo, in case we need to mark the
-        * server as broken.
-        */
-       addrinfo = query->addrinfo;
+/*
+ * 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(); ",
-                  no_response ? "no response" : "responding",
+                  rctx->no_response ? "no response" : "responding",
                   result);
 
        /*
@@ -8464,120 +9011,31 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
         *
         * XXXRTH  Don't cancel the query if waiting for validation?
         */
-       if (!nextitem)
-               fctx_cancelquery(&query, &devent, finish,
-                                no_response, ISC_FALSE);
+       if (!rctx->nextitem) {
+               fctx_cancelquery(&query, &rctx->devent, rctx->finish,
+                                rctx->no_response, ISC_FALSE);
+       }
 
 #ifdef ENABLE_AFL
-       if (fuzzing_resolver && (keep_trying || resend || nextitem)) {
-               if (nextitem) {
-                       fctx_cancelquery(&query, &devent, finish,
-                                        no_response, ISC_FALSE);
+       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 (keep_trying) {
-               if (result == DNS_R_FORMERR)
-                       broken_server = DNS_R_FORMERR;
-               if (broken_server != ISC_R_SUCCESS) {
-                       /*
-                        * Add this server to the list of bad servers for
-                        * this fctx.
-                        */
-                       add_bad(fctx, addrinfo, broken_server, broken_type);
-               }
-
-               if (get_nameservers) {
-                       dns_name_t *name;
-                       dns_fixedname_init(&foundname);
-                       fname = dns_fixedname_name(&foundname);
-                       if (result != ISC_R_SUCCESS) {
-                               fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
-                               return;
-                       }
-                       findoptions = 0;
-                       if (dns_rdatatype_atparent(fctx->type))
-                               findoptions |= DNS_DBFIND_NOEXACT;
-                       if ((options & DNS_FETCHOPT_UNSHARED) == 0)
-                               name = &fctx->name;
-                       else
-                               name = &fctx->domain;
-                       result = dns_view_findzonecut(res->view,
-                                                     name, fname,
-                                                     now, findoptions,
-                                                     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_cleanupfinds(fctx);
-                       fctx_cleanupaltfinds(fctx);
-                       fctx_cleanupforwaddrs(fctx);
-                       fctx_cleanupaltaddrs(fctx);
-               }
-               /*
-                * Try again.
-                */
-               fctx_try(fctx, !get_nameservers, ISC_FALSE);
-       } else if (resend) {
-               /*
-                * Resend (probably with changed options).
-                */
-               FCTXTRACE("resend");
-               inc_stats(res, dns_resstatscounter_retry);
-               bucketnum = fctx->bucketnum;
-               fctx_increference(fctx);
-               result = fctx_query(fctx, addrinfo, 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 (nextitem) {
-               /*
-                * Wait for next item.
-                */
-               FCTXTRACE("nextitem");
-               inc_stats(fctx->res, dns_resstatscounter_nextitem);
-               INSIST(query->dispentry != NULL);
-               dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE);
-               result = dns_dispatch_getnext(query->dispentry, &devent);
-               if (result != ISC_R_SUCCESS)
-                       fctx_done(fctx, result, __LINE__);
+       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
@@ -8592,39 +9050,319 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                result = fctx_stopidletimer(fctx);
                if (result != ISC_R_SUCCESS)
                        fctx_done(fctx, result, __LINE__);
-       } else if (result == DNS_R_CHASEDSSERVERS) {
-               unsigned int n;
-               add_bad(fctx, addrinfo, result, broken_type);
-               fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE);
-               fctx_cleanupfinds(fctx);
-               fctx_cleanupforwaddrs(fctx);
+       } else {
+               /*
+                * We're done.
+                */
+               fctx_done(fctx, result, __LINE__);
+       }
+
+}
 
-               n = dns_name_countlabels(&fctx->name);
-               dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname);
+/*
+ * 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 */
 
-               FCTXTRACE("suspending DS lookup to find parent's NS records");
+       dns_message_logfmtpacket2(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);
 
-               result = dns_resolver_createfetch(res, &fctx->nsname,
-                                                 dns_rdatatype_ns,
-                                                 NULL, NULL, NULL,
-                                                 fctx->options, 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__);
-               }
+#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)
+       {
                /*
-                * We're done.
+                * 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.
                 */
-               fctx_done(fctx, result, __LINE__);
+               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;
+               isc_boolean_t setnocookie = ISC_FALSE;
+#if DNS_EDNS_VERSION > 0
+               unsigned int flags, mask;
+#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 (!NOCOOKIE(query->addrinfo))
+                               setnocookie = ISC_TRUE;
+                       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);
 }
 
 /***