]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Recursion lame is detected and last resort is used to resolve.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 13 Oct 2008 09:11:42 +0000 (09:11 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 13 Oct 2008 09:11:42 +0000 (09:11 +0000)
git-svn-id: file:///svn/unbound/trunk@1294 be551aaa-1e26-0410-a405-d3ace91eadb9

13 files changed:
doc/Changelog
iterator/iter_resptype.c
iterator/iter_resptype.h
iterator/iter_utils.c
iterator/iter_utils.h
iterator/iterator.c
iterator/iterator.h
services/cache/infra.c
services/cache/infra.h
services/outside_network.c
testcode/unitmain.c
testdata/iter_reclame_one.rpl [new file with mode: 0644]
testdata/iter_reclame_two.rpl [new file with mode: 0644]

index 8d185c3a0cd7403b106ac06d98aaf8f626a94c0b..0494dde764512cb13fe5d00726228f0dda795bf6 100644 (file)
@@ -1,3 +1,8 @@
+13 October 2008: Wouter
+       - fixed recursion servers deployed as authoritative detection, so
+         that as a last resort, a +RD query is sent there to get the 
+         correct answer.
+
 10 October 2008: Wouter
        - fixup tests - the negative cache contained the correct NSEC3s for
          two tests that are supposed to fail to validate.
index 17d5469861eb5448e61efe4e7b8982098c5a94ed..40d71358b5ef3e7f401ae1abfd5c76b2f2c566b0 100644 (file)
@@ -116,7 +116,7 @@ response_type_from_server(int rdset,
                /* make sure its not recursive when we don't want it to */
                if( (msg->rep->flags&BIT_RA) &&
                        !(msg->rep->flags&BIT_AA) && !rdset)
-                               return RESPONSE_TYPE_LAME;
+                               return RESPONSE_TYPE_REC_LAME;
                return RESPONSE_TYPE_ANSWER;
        }
        
@@ -200,7 +200,7 @@ response_type_from_server(int rdset,
                        /* we do our own recursion, thank you */
                        if( (msg->rep->flags&BIT_RA) &&
                                !(msg->rep->flags&BIT_AA) && !rdset)
-                               return RESPONSE_TYPE_LAME;
+                               return RESPONSE_TYPE_REC_LAME;
                        return RESPONSE_TYPE_ANSWER;
                }
        }
@@ -222,13 +222,13 @@ response_type_from_server(int rdset,
                                 * deployed and is responding nonAA */
                                if( (msg->rep->flags&BIT_RA) &&
                                        !(msg->rep->flags&BIT_AA) && !rdset)
-                                       return RESPONSE_TYPE_LAME;
+                                       return RESPONSE_TYPE_REC_LAME;
                                /* Or if a lame server is deployed,
                                 * which gives ns==zone delegation from cache 
                                 * without AA bit as well, with nodata nosoa*/
                                if(msg->rep->an_numrrsets==0 &&
                                        !(msg->rep->flags&BIT_AA) && !rdset)
-                                       return RESPONSE_TYPE_LAME;
+                                       return RESPONSE_TYPE_REC_LAME;
                                return RESPONSE_TYPE_ANSWER;
                        }
                        /* If we are getting a referral upwards (or to 
@@ -259,6 +259,6 @@ response_type_from_server(int rdset,
         * be an entirely empty message) */
        /* check if recursive answer; saying it has empty cache */
        if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
-               return RESPONSE_TYPE_LAME;
+               return RESPONSE_TYPE_REC_LAME;
        return RESPONSE_TYPE_ANSWER;
 }
index bfacd6148bf4287bfc1e30cc02f81963b7d16234..3bb3eedb2ef6d62c5df9dac342fd12735d765eb8 100644 (file)
@@ -81,7 +81,14 @@ enum response_type {
         * 'lame' means that this particular response indicates that 
         * the nameserver knew nothing about the question.
         */
-       RESPONSE_TYPE_LAME
+       RESPONSE_TYPE_LAME,
+
+       /**
+        * Recursion lame means that the nameserver is some sort of
+        * open recursor, and not authoritative for the question.
+        * It may know something, but not authoritatively.
+        */
+       RESPONSE_TYPE_REC_LAME
 };
 
 /**
index 4c894219912d23f1665ad2a8110b9f7eb10bb63f..24251a5c026321024c57d8227fad9d16d76318fd 100644 (file)
@@ -140,9 +140,7 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env,
        uint8_t* name, size_t namelen, uint16_t qtype, uint32_t now, 
        struct delegpt_addr* a)
 {
-       int rtt;
-       int lame;
-       int dnsseclame;
+       int rtt, lame, reclame, dnsseclame;
        if(donotq_lookup(iter_env->donotq, &a->addr, a->addrlen)) {
                return -1; /* server is on the donotquery list */
        }
@@ -151,12 +149,15 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env,
        }
        /* check lameness - need zone , class info */
        if(infra_get_lame_rtt(env->infra_cache, &a->addr, a->addrlen, 
-               name, namelen, qtype, &lame, &dnsseclame, &rtt, now)) {
+               name, namelen, qtype, &lame, &dnsseclame, &reclame, 
+               &rtt, now)) {
                if(lame)
                        return -1; /* server is lame */
                else if(rtt >= USEFUL_SERVER_TOP_TIMEOUT)
                        return -1; /* server is unresponsive */
-               else if(dnsseclame)
+               else if(reclame)
+                       return rtt+USEFUL_SERVER_TOP_TIMEOUT*2; /* nonpref */
+               else if(dnsseclame )
                        return rtt+USEFUL_SERVER_TOP_TIMEOUT; /* nonpref */
                else    return rtt;
        }
@@ -240,7 +241,8 @@ iter_filter_order(struct iter_env* iter_env, struct module_env* env,
 struct delegpt_addr* 
 iter_server_selection(struct iter_env* iter_env, 
        struct module_env* env, struct delegpt* dp, 
-       uint8_t* name, size_t namelen, uint16_t qtype, int* dnssec_expected)
+       uint8_t* name, size_t namelen, uint16_t qtype, int* dnssec_expected,
+       int* chase_to_rd)
 {
        int sel;
        int selrtt;
@@ -250,8 +252,12 @@ iter_server_selection(struct iter_env* iter_env,
 
        if(num == 0)
                return NULL;
-       if(selrtt >= USEFUL_SERVER_TOP_TIMEOUT)
+       if(selrtt >= USEFUL_SERVER_TOP_TIMEOUT*2) {
+               *chase_to_rd = 1;
+       }
+       if(selrtt >= USEFUL_SERVER_TOP_TIMEOUT) {
                *dnssec_expected = 0;
+       }
        if(num == 1) {
                a = dp->result_list;
                if(++a->attempts < OUTBOUND_MSG_RETRY)
index 1d3872294232f13b1d9cc69b464d9a3a20895d1e..dfaa367aa7f38feae689f3d0cf95f5f1e0268edb 100644 (file)
@@ -77,12 +77,15 @@ int iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg);
  * @param qtype: query type that we want to send.
  * @param dnssec_expected: set to 0, if a known dnssec-lame server is selected
  *     these are not preferred, but are used as a last resort.
+ * @param chase_to_rd: set to 1 if a known recursion lame server is selected
+ *     these are not preferred, but are used as a last resort.
  * @return best target or NULL if no target.
  *     if not null, that target is removed from the result list in the dp.
  */
 struct delegpt_addr* iter_server_selection(struct iter_env* iter_env, 
        struct module_env* env, struct delegpt* dp, uint8_t* name, 
-       size_t namelen, uint16_t qtype, int* dnssec_expected);
+       size_t namelen, uint16_t qtype, int* dnssec_expected,
+       int* chase_to_rd);
 
 /**
  * Allocate dns_msg from parsed msg, in regional.
index b5eb429c7159d3d51a3fa6cf7fbbcac13e3ab9cf..40fcbd45287b185be85bb9e45364e6621e8d1d7d 100644 (file)
@@ -1211,7 +1211,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
        /* Select the next usable target, filtering out unsuitable targets. */
        target = iter_server_selection(ie, qstate->env, iq->dp, 
                iq->dp->name, iq->dp->namelen, iq->qchase.qtype,
-               &iq->dnssec_expected);
+               &iq->dnssec_expected, &iq->chase_to_rd);
 
        /* If no usable target was selected... */
        if(!target) {
@@ -1276,7 +1276,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
        outq = (*qstate->env->send_query)(
                iq->qchase.qname, iq->qchase.qname_len, 
                iq->qchase.qtype, iq->qchase.qclass, 
-               iq->chase_flags, EDNS_DO|BIT_CD, 
+               iq->chase_flags | (iq->chase_to_rd?BIT_RD:0), EDNS_DO|BIT_CD, 
                &target->addr, target->addrlen, qstate);
        if(!outq) {
                verbose(VERB_OPS, "error sending query to auth server; "
@@ -1313,11 +1313,14 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
        enum response_type type;
        iq->num_current_queries--;
        if(iq->response == NULL) {
+               iq->chase_to_rd = 0;
                verbose(VERB_ALGO, "query response was timeout");
                return next_state(iq, QUERYTARGETS_STATE);
        }
-       type = response_type_from_server((int)(iq->chase_flags&BIT_RD),
+       type = response_type_from_server(
+               (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
                iq->response, &iq->qchase, iq->dp);
+       iq->chase_to_rd = 0;
        if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD)) {
                /* When forwarding (RD bit is set), we handle referrals 
                 * differently. No queries should be sent elsewhere */
@@ -1325,6 +1328,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
        }
        if(iq->dnssec_expected && !(iq->chase_flags&BIT_RD) 
                && type != RESPONSE_TYPE_LAME 
+               && type != RESPONSE_TYPE_REC_LAME 
                && type != RESPONSE_TYPE_THROWAWAY 
                && type != RESPONSE_TYPE_UNTYPED) {
                /* a possible answer, see if it is missing DNSSEC */
@@ -1458,11 +1462,24 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
                        if(!infra_set_lame(qstate->env->infra_cache, 
                                &qstate->reply->addr, qstate->reply->addrlen, 
                                iq->dp->name, iq->dp->namelen, 
-                               *qstate->env->now, dnsseclame, 
+                               *qstate->env->now, dnsseclame, 0,
                                iq->qchase.qtype))
                                log_err("mark host lame: out of memory");
                } else log_err("%slame response from cache",
                        dnsseclame?"DNSSEC ":"");
+       } else if(type == RESPONSE_TYPE_REC_LAME) {
+               /* Cache the LAMEness. */
+               verbose(VERB_DETAIL, "query response REC_LAME: "
+                       "recursive but not authoritative server");
+               if(qstate->reply) {
+                       /* need addr for lameness cache, but we may have
+                        * gotten this from cache, so test to be sure */
+                       if(!infra_set_lame(qstate->env->infra_cache, 
+                               &qstate->reply->addr, qstate->reply->addrlen, 
+                               iq->dp->name, iq->dp->namelen, 
+                               *qstate->env->now, 0, 1, iq->qchase.qtype))
+                               log_err("mark host lame: out of memory");
+               } 
        } else if(type == RESPONSE_TYPE_THROWAWAY) {
                /* LAME and THROWAWAY responses are handled the same way. 
                 * In this case, the event is just sent directly back to 
index f8b1b2ca068e5f1635b9620bdaed147154e86ba6..7a0a1e2cda357f8115cc93f70030d1b4c616a00d 100644 (file)
@@ -210,6 +210,8 @@ struct iter_qstate {
        struct query_info qchase;
        /** query flags to use when chasing the answer (i.e. RD flag) */
        uint16_t chase_flags;
+       /** true if we set RD bit because of last resort recursion lame query*/
+       int chase_to_rd;
 
        /** 
         * This is the current delegation point for an in-progress query. This
index dca5080cc00d2d1a199d3d8e1ef34373c970e452..29fca32807506955410a82bdd66c990fc863170d 100644 (file)
@@ -266,7 +266,7 @@ hash_lameness(uint8_t* name, size_t namelen)
 int 
 infra_lookup_lame(struct infra_host_data* host,
         uint8_t* name, size_t namelen, uint32_t timenow,
-       int* dlame, int* alame, int* olame)
+       int* dlame, int* rlame, int* alame, int* olame)
 {
        struct lruhash_entry* e;
        struct infra_lame_key k;
@@ -287,10 +287,11 @@ infra_lookup_lame(struct infra_host_data* host,
                return 0;
        }
        *dlame = d->isdnsseclame;
+       *rlame = d->rec_lame;
        *alame = d->lame_type_A;
        *olame = d->lame_other;
        lock_rw_unlock(&e->lock);
-       return *dlame || *alame || *olame;
+       return *dlame || *rlame || *alame || *olame;
 }
 
 size_t 
@@ -337,7 +338,7 @@ int
 infra_set_lame(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen,
         uint8_t* name, size_t namelen, uint32_t timenow, int dnsseclame,
-       uint16_t qtype)
+       int reclame, uint16_t qtype)
 {
        struct infra_host_data* data;
        struct lruhash_entry* e;
@@ -369,8 +370,9 @@ infra_set_lame(struct infra_cache* infra,
        k->entry.data = (void*)d;
        d->ttl = timenow + infra->lame_ttl;
        d->isdnsseclame = dnsseclame;
-       d->lame_type_A = (!dnsseclame && qtype == LDNS_RR_TYPE_A);
-       d->lame_other = (!dnsseclame && qtype != LDNS_RR_TYPE_A);
+       d->rec_lame = reclame;
+       d->lame_type_A = (!dnsseclame && !reclame && qtype == LDNS_RR_TYPE_A);
+       d->lame_other = (!dnsseclame  && !reclame && qtype != LDNS_RR_TYPE_A);
        k->namelen = namelen;
        e = infra_lookup_host_nottl(infra, addr, addrlen, 1);
        if(!e) {
@@ -404,11 +406,12 @@ infra_set_lame(struct infra_cache* infra,
                }
        } else {
                /* lookup existing lameness entry (if any) and merge data */
-               int dlame, alame, olame; 
+               int dlame, rlame, alame, olame; 
                if(infra_lookup_lame(data, name, namelen, timenow,
-                       &dlame, &alame, &olame)) { 
+                       &dlame, &rlame, &alame, &olame)) { 
                        /* merge data into new structure */
                        if(dlame) d->isdnsseclame = 1;
+                       if(rlame) d->rec_lame = 1;
                        if(alame) d->lame_type_A = 1;
                        if(olame) d->lame_other = 1;
                }
@@ -500,38 +503,49 @@ int
 infra_get_lame_rtt(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen,
         uint8_t* name, size_t namelen, uint16_t qtype, 
-       int* lame, int* dnsseclame, int* rtt, uint32_t timenow)
+       int* lame, int* dnsseclame, int* reclame, int* rtt, uint32_t timenow)
 {
        struct infra_host_data* host;
        struct lruhash_entry* e = infra_lookup_host_nottl(infra, addr, 
                addrlen, 0);
-       int dlm, alm, olm;
+       int dlm, rlm, alm, olm;
        if(!e) 
                return 0;
        host = (struct infra_host_data*)e->data;
        *rtt = rtt_unclamped(&host->rtt);
        /* check lameness first, if so, ttl on host does not matter anymore */
-       if(infra_lookup_lame(host, name, namelen, timenow, &dlm, &alm, &olm)) {
+       if(infra_lookup_lame(host, name, namelen, timenow, 
+               &dlm, &rlm, &alm, &olm)) {
                if(alm && qtype == LDNS_RR_TYPE_A) {
                        lock_rw_unlock(&e->lock);
                        *lame = 1;
                        *dnsseclame = 0;
+                       *reclame = 0;
                        return 1;
                } else if(olm && qtype != LDNS_RR_TYPE_A) {
                        lock_rw_unlock(&e->lock);
                        *lame = 1;
                        *dnsseclame = 0;
+                       *reclame = 0;
                        return 1;
                } else if(dlm) {
                        lock_rw_unlock(&e->lock);
                        *lame = 0;
                        *dnsseclame = 1;
+                       *reclame = 0;
+                       return 1;
+               } else if(rlm) {
+                       lock_rw_unlock(&e->lock);
+                       *lame = 0;
+                       *dnsseclame = 0;
+                       *reclame = 1;
                        return 1;
                }
                /* no lameness for this type of query */
        }
        *lame = 0;
        *dnsseclame = 0;
+       *reclame = 0;
        if(timenow > host->ttl) {
                lock_rw_unlock(&e->lock);
                return 0;
index 6417713c195e7367b8f11c7e6dadadfea91268f1..fa693cf4e08ec68dac72b30372e2535b16090999 100644 (file)
@@ -98,6 +98,8 @@ struct infra_lame_data {
        /** is the host lame (does not serve the zone authoritatively),
         * or is the host dnssec lame (does not serve DNSSEC data) */
        int isdnsseclame;
+       /** is the host recursion lame (not AA, but RA) */
+       int rec_lame;
        /** the host is lame (not authoritative) for A records */
        int lame_type_A;
        /** the host is lame (not authoritative) for other query types */
@@ -187,13 +189,14 @@ int infra_host(struct infra_cache* infra, struct sockaddr_storage* addr,
  * @param namelen: length of domain name.
  * @param timenow: what time it is now.
  * @param dlame: if the function returns true, is set true if dnssec lame.
+ * @param rlame: if the function returns true, is set true if recursion lame.
  * @param alame: if the function returns true, is set true if qtype A lame.
  * @param olame: if the function returns true, is set true if qtype other lame.
  * @return: 0 if not lame or unknown or timed out, 1 if lame
  */
 int infra_lookup_lame(struct infra_host_data* host,
        uint8_t* name, size_t namelen, uint32_t timenow,
-       int* dlame, int* alame, int* olame);
+       int* dlame, int* rlame, int* alame, int* olame);
 
 /**
  * Set a host to be lame for the given zone.
@@ -205,13 +208,15 @@ int infra_lookup_lame(struct infra_host_data* host,
  * @param timenow: what time it is now.
  * @param dnsseclame: if true the host is set dnssec lame.
  *     if false, the host is marked lame (not serving the zone).
+ * @param reclame: if true host is a recursor not AA server.
+ *      if false, dnsseclame or marked lame.
  * @param qtype: the query type for which it is lame.
  * @return: 0 on error.
  */
 int infra_set_lame(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen,
        uint8_t* name, size_t namelen, uint32_t timenow, int dnsseclame,
-       uint16_t qtype);
+       int reclame, uint16_t qtype);
 
 /**
  * Update rtt information for the host.
@@ -262,6 +267,7 @@ int infra_edns_update(struct infra_cache* infra,
  * @param lame: if function returns true, this returns lameness of the zone.
  * @param dnsseclame: if function returns true, this returns if the zone
  *     is dnssec-lame.
+ * @param reclame: if function returns true, this is if it is recursion lame.
  * @param rtt: if function returns true, this returns avg rtt of the server.
  *     The rtt value is unclamped and reflects recent timeouts.
  * @param timenow: what time it is now.
@@ -270,7 +276,7 @@ int infra_edns_update(struct infra_cache* infra,
 int infra_get_lame_rtt(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen, 
        uint8_t* name, size_t namelen, uint16_t qtype, 
-       int* lame, int* dnsseclame, int* rtt, uint32_t timenow);
+       int* lame, int* dnsseclame, int* reclame, int* rtt, uint32_t timenow);
 
 /**
  * Get memory used by the infra cache.
index 003dfb90231ff177bedf993330521cfaea14858f..02f6f74022c42f1892113b52188e7aa525e46331 100644 (file)
@@ -1688,3 +1688,4 @@ serviced_get_mem(struct serviced_query* sq)
        }
        return s;
 }
+
index b104fbe9850a138987af3b4c77733bf538506225..66fc5c73c862693ff9c39adf5139e3b75053994b 100644 (file)
@@ -320,7 +320,7 @@ infra_test()
        struct infra_host_key* k;
        struct infra_host_data* d;
        int init = 376;
-       int dlame, alame, olame;
+       int dlame, rlame, alame, olame;
 
        slab = infra_create(cfg);
        unit_assert( infra_host(slab, (struct sockaddr_storage*)&one, 
@@ -345,30 +345,30 @@ infra_test()
        unit_assert( vs == 0 && to == init && edns_lame == 0 );
        
        unit_assert( infra_set_lame(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), zone, zonelen,  now, 0, 
+               (socklen_t)sizeof(int), zone, zonelen,  now, 0, 0,
                LDNS_RR_TYPE_A) );
        unit_assert( (d=infra_lookup_host(slab, (struct sockaddr_storage*)&one,
                (socklen_t)sizeof(int), 0, now, &k)) );
        unit_assert( d->ttl == now+cfg->host_ttl );
        unit_assert( d->edns_version == 0 );
        unit_assert( infra_lookup_lame(d, zone, zonelen, now, 
-               &dlame, &alame, &olame) );
-       unit_assert(!dlame && alame && !olame);
+               &dlame, &rlame, &alame, &olame) );
+       unit_assert(!dlame && !rlame && alame && !olame);
        unit_assert( !infra_lookup_lame(d, zone, zonelen, 
-               now+cfg->lame_ttl+10, &dlame, &alame, &olame) );
+               now+cfg->lame_ttl+10, &dlame, &rlame, &alame, &olame) );
        unit_assert( !infra_lookup_lame(d, (uint8_t*)"\000", 1, now, 
-               &dlame, &alame, &olame) );
+               &dlame, &rlame, &alame, &olame) );
        lock_rw_unlock(&k->entry.lock);
 
        /* test merge of data */
        unit_assert( infra_set_lame(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), zone, zonelen,  now, 0, 
+               (socklen_t)sizeof(int), zone, zonelen,  now, 0, 0,
                LDNS_RR_TYPE_AAAA) );
        unit_assert( (d=infra_lookup_host(slab, (struct sockaddr_storage*)&one,
                (socklen_t)sizeof(int), 0, now, &k)) );
        unit_assert( infra_lookup_lame(d, zone, zonelen, now, 
-               &dlame, &alame, &olame) );
-       unit_assert(!dlame && alame && olame);
+               &dlame, &rlame, &alame, &olame) );
+       unit_assert(!dlame && !rlame && alame && olame);
        lock_rw_unlock(&k->entry.lock);
 
        infra_delete(slab);
diff --git a/testdata/iter_reclame_one.rpl b/testdata/iter_reclame_one.rpl
new file mode 100644 (file)
index 0000000..5ab5ddb
--- /dev/null
@@ -0,0 +1,162 @@
+; config options
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test iterative resolve with a recursion lame server.
+; The scenario has a domain with two servers, one is lame the other doesn't
+; so depending on the randomly chosen server that goes first, it may
+; select the nonlame or the lame server first.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    IN      A       193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+; lame.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.5
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/iter_reclame_two.rpl b/testdata/iter_reclame_two.rpl
new file mode 100644 (file)
index 0000000..f6acc27
--- /dev/null
@@ -0,0 +1,161 @@
+; config options
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test iterative resolve with two recursion lame servers.
+; both servers are recursion lame. The iterator tries both servers,
+; but they are both lame.  Then it concludes that it only has reclame.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    IN      A       193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+; lame.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.5
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   IN NS   lame.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+lame.example.com.              IN      A       1.2.3.5
+ENTRY_END
+
+SCENARIO_END