+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.
/* 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;
}
/* 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;
}
}
* 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
* 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;
}
* '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
};
/**
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 */
}
}
/* 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;
}
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;
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)
* @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.
/* 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) {
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; "
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 */
}
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 */
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
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
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;
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
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;
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) {
}
} 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;
}
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;
/** 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 */
* @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.
* @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.
* @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.
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.
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,
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);
--- /dev/null
+; 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
--- /dev/null
+; 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