This will also allow MAX_TARGET_NX more NXDOMAINs.
sent; introduces 'num.query.udpout' to the 'unbound-control stats'
command.
- Fix to not count cached NXDOMAIN for MAX_TARGET_NX.
+ - Allow fallback to the parent side when MAX_TARGET_NX is reached.
+ This will also allow MAX_TARGET_NX more NXDOMAINs.
28 June 2022: George
- Show the output of the exact .rpl run that failed with 'make test'.
else ns->got4 = 1;
if(ns->got4 && ns->got6)
ns->resolved = 1;
+ } else {
+ if(addr_is_ip6(addr, addrlen))
+ ns->done_pside6 = 1;
+ else ns->done_pside4 = 1;
}
log_assert(ns->port>0);
return delegpt_add_addr(dp, region, addr, addrlen, bogus, lame,
}
size_t
-delegpt_count_missing_targets(struct delegpt* dp)
+delegpt_count_missing_targets(struct delegpt* dp, int* alllame)
{
struct delegpt_ns* ns;
- size_t n = 0;
- for(ns = dp->nslist; ns; ns = ns->next)
- if(!ns->resolved)
- n++;
+ size_t n = 0, nlame = 0;
+ for(ns = dp->nslist; ns; ns = ns->next) {
+ if(ns->resolved) continue;
+ n++;
+ if(ns->lame) nlame++;
+ }
+ if(alllame && n == nlame) *alllame = 1;
return n;
}
else ns->got4 = 1;
if(ns->got4 && ns->got6)
ns->resolved = 1;
+ } else {
+ if(addr_is_ip6(addr, addrlen))
+ ns->done_pside6 = 1;
+ else ns->done_pside4 = 1;
}
log_assert(ns->port>0);
return delegpt_add_addr_mlc(dp, addr, addrlen, bogus, lame,
/**
* Count number of missing targets. These are ns names with no resolved flag.
* @param dp: delegation point.
+ * @param alllame: if set, check if all the missing targets are lame.
* @return number of missing targets (or 0).
*/
-size_t delegpt_count_missing_targets(struct delegpt* dp);
+size_t delegpt_count_missing_targets(struct delegpt* dp, int* alllame);
/** count total number of targets in dp */
size_t delegpt_count_targets(struct delegpt* dp);
struct sock_list* blacklist, time_t prefetch)
{
int got_num = 0, low_rtt = 0, swap_to_front, rtt_band = RTT_BAND, nth;
+ int alllame = 0;
size_t num_results;
struct delegpt_addr* a, *n, *prev=NULL;
if(got_num == 0)
return 0;
if(low_rtt >= USEFUL_SERVER_TOP_TIMEOUT &&
- (delegpt_count_missing_targets(dp) > 0 || open_target > 0)) {
+ /* If all missing (or not fully resolved) targets are lame,
+ * then use the remaining lame address. */
+ ((delegpt_count_missing_targets(dp, &alllame) > 0 && !alllame) ||
+ open_target > 0)) {
verbose(VERB_ALGO, "Bad choices, trying to get more choice");
return 0; /* we want more choice. The best choice is a bad one.
return 0 to force the caller to fetch more */
delegpt_mark_neg(dpns, qstate->qinfo.qtype);
dpns->resolved = 1; /* mark as failed */
if((dpns->got4 == 2 || !ie->supports_ipv4) &&
- (dpns->got6 == 2 || !ie->supports_ipv6))
+ (dpns->got6 == 2 || !ie->supports_ipv6)) {
target_count_increase_nx(super_iq, 1);
+ }
}
if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS) {
/* prime failed to get delegation */
iq->qchase.qclass) != NULL;
}
-/** create target count structure for this query */
+/**
+ * Create target count structure for this query. This is always explicitly
+ * created for the parent query.
+ */
static void
target_count_create(struct iter_qstate* iq)
{
if(!iq->target_count) {
- iq->target_count = (int*)calloc(3, sizeof(int));
+ iq->target_count = (int*)calloc(TARGET_COUNT_MAX, sizeof(int));
/* if calloc fails we simply do not track this number */
- if(iq->target_count)
- iq->target_count[0] = 1;
+ if(iq->target_count) {
+ iq->target_count[TARGET_COUNT_REF] = 1;
+ iq->nxns_dp = (uint8_t**)calloc(1, sizeof(uint8_t*));
+ }
}
}
{
target_count_create(iq);
if(iq->target_count)
- iq->target_count[1] += num;
+ iq->target_count[TARGET_COUNT_QUERIES] += num;
iq->dp_target_count++;
}
{
target_count_create(iq);
if(iq->target_count)
- iq->target_count[2] += num;
+ iq->target_count[TARGET_COUNT_NX] += num;
}
/**
subiq->num_target_queries = 0;
target_count_create(iq);
subiq->target_count = iq->target_count;
- if(iq->target_count)
- iq->target_count[0] ++; /* extra reference */
+ if(iq->target_count) {
+ iq->target_count[TARGET_COUNT_REF] ++; /* extra reference */
+ subiq->nxns_dp = iq->nxns_dp;
+ }
subiq->dp_target_count = 0;
subiq->num_current_queries = 0;
subiq->depth = iq->depth+1;
int toget = 0;
iter_mark_cycle_targets(qstate, iq->dp);
- missing = (int)delegpt_count_missing_targets(iq->dp);
+ missing = (int)delegpt_count_missing_targets(iq->dp, NULL);
log_assert(maxtargets != 0); /* that would not be useful */
/* Generate target requests. Basically, any missing targets
if(iq->depth == ie->max_dependency_depth)
return 0;
if(iq->depth > 0 && iq->target_count &&
- iq->target_count[1] > MAX_TARGET_COUNT) {
+ iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) {
char s[LDNS_MAX_DOMAINLEN+1];
dname_str(qstate->qinfo.qname, s);
verbose(VERB_QUERY, "request %s has exceeded the maximum "
- "number of glue fetches %d", s, iq->target_count[1]);
+ "number of glue fetches %d", s,
+ iq->target_count[TARGET_COUNT_QUERIES]);
return 0;
}
if(iq->dp_target_count > MAX_DP_TARGET_COUNT) {
continue;
}
- if(ie->supports_ipv6 && !ns->got6) {
+ if(ie->supports_ipv6 &&
+ ((ns->lame && !ns->done_pside6) ||
+ (!ns->lame && !ns->got6))) {
/* Send the AAAA request. */
if(!generate_target_query(qstate, iq, id,
ns->name, ns->namelen,
query_count++;
}
/* Send the A request. */
- if(ie->supports_ipv4 && !ns->got4) {
+ if(ie->supports_ipv4 &&
+ ((ns->lame && !ns->done_pside4) ||
+ (!ns->lame && !ns->got4))) {
if(!generate_target_query(qstate, iq, id,
ns->name, ns->namelen,
LDNS_RR_TYPE_A, iq->qchase.qclass)) {
return next_state(iq, QUERYTARGETS_STATE);
}
/* query for an extra name added by the parent-NS record */
- if(delegpt_count_missing_targets(iq->dp) > 0) {
+ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) {
int qs = 0;
verbose(VERB_ALGO, "try parent-side target name");
if(!query_for_targets(qstate, iq, ie, id, 1, &qs)) {
return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
}
if(iq->depth > 0 && iq->target_count &&
- iq->target_count[1] > MAX_TARGET_COUNT) {
+ iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) {
char s[LDNS_MAX_DOMAINLEN+1];
dname_str(qstate->qinfo.qname, s);
verbose(VERB_QUERY, "request %s has exceeded the maximum "
- "number of glue fetches %d", s, iq->target_count[1]);
+ "number of glue fetches %d", s,
+ iq->target_count[TARGET_COUNT_QUERIES]);
errinf(qstate, "exceeded the maximum number of glue fetches");
return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
}
errinf(qstate, "exceeded the maximum number of sends");
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
- if(iq->target_count && iq->target_count[2] > MAX_TARGET_NX) {
- verbose(VERB_QUERY, "request has exceeded the maximum "
- " number of nxdomain nameserver lookups with %d",
- iq->target_count[2]);
- errinf(qstate, "exceeded the maximum nameserver nxdomains");
- return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ /* Check if we reached MAX_TARGET_NX limit without a fallback activation. */
+ if(iq->target_count && !*iq->nxns_dp &&
+ iq->target_count[TARGET_COUNT_NX] > MAX_TARGET_NX) {
+ struct delegpt_ns* ns;
+ /* If we can wait for resolution, do so. */
+ if(iq->num_target_queries>0 || iq->num_current_queries>0) {
+ if(iq->num_target_queries>0 && iq->num_current_queries>0) {
+ verbose(VERB_ALGO, "waiting for %d targets to "
+ "resolve or %d outstanding queries to "
+ "respond", iq->num_target_queries,
+ iq->num_current_queries);
+ qstate->ext_state[id] = module_wait_reply;
+ } else if(iq->num_target_queries>0) {
+ verbose(VERB_ALGO, "waiting for %d targets to "
+ "resolve", iq->num_target_queries);
+ qstate->ext_state[id] = module_wait_subquery;
+ } else {
+ verbose(VERB_ALGO, "waiting for %d "
+ "outstanding queries to respond",
+ iq->num_current_queries);
+ qstate->ext_state[id] = module_wait_reply;
+ }
+ return 0;
+ }
+ verbose(VERB_ALGO, "request has exceeded the maximum "
+ "number of nxdomain nameserver lookups (%d) with %d",
+ MAX_TARGET_NX, iq->target_count[TARGET_COUNT_NX]);
+ /* Check for dp because we require one below */
+ if(!iq->dp) {
+ verbose(VERB_QUERY, "Failed to get a delegation, "
+ "giving up");
+ errinf(qstate, "failed to get a delegation (eg. prime "
+ "failure)");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ /* We reached the limit but we already have parent side
+ * information; stop resolution */
+ if(iq->dp->has_parent_side_NS) {
+ errinf(qstate, "exceeded the maximum nameserver nxdomains");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ /* Mark all the current NSes as resolved to allow for parent
+ * fallback */
+ for(ns=iq->dp->nslist; ns; ns=ns->next) {
+ ns->resolved = 1;
+ }
+ /* Note the delegation point that triggered the NXNS fallback;
+ * no reason for shared queries to keep trying there.
+ * This also marks the fallback activation. */
+ *iq->nxns_dp = malloc(iq->dp->namelen);
+ if(!*iq->nxns_dp) {
+ errinf(qstate, "exceeded the maximum nameserver nxdomains");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ memcpy(*iq->nxns_dp, iq->dp->name, iq->dp->namelen);
+ } else if(iq->target_count && *iq->nxns_dp) {
+ /* Handle the NXNS fallback case. */
+ /* If we can wait for resolution, do so. */
+ if(iq->num_target_queries>0 || iq->num_current_queries>0) {
+ if(iq->num_target_queries>0 && iq->num_current_queries>0) {
+ verbose(VERB_ALGO, "waiting for %d targets to "
+ "resolve or %d outstanding queries to "
+ "respond", iq->num_target_queries,
+ iq->num_current_queries);
+ qstate->ext_state[id] = module_wait_reply;
+ } else if(iq->num_target_queries>0) {
+ verbose(VERB_ALGO, "waiting for %d targets to "
+ "resolve", iq->num_target_queries);
+ qstate->ext_state[id] = module_wait_subquery;
+ } else {
+ verbose(VERB_ALGO, "waiting for %d "
+ "outstanding queries to respond",
+ iq->num_current_queries);
+ qstate->ext_state[id] = module_wait_reply;
+ }
+ return 0;
+ }
+ /* Check for dp because we require one below */
+ if(!iq->dp) {
+ verbose(VERB_QUERY, "Failed to get a delegation, "
+ "giving up");
+ errinf(qstate, "failed to get a delegation (eg. prime "
+ "failure)");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+
+ if(iq->target_count[TARGET_COUNT_NX] > MAX_TARGET_NX_FALLBACK) {
+ verbose(VERB_ALGO, "request has exceeded the maximum "
+ "number of fallback nxdomain nameserver "
+ "lookups (%d) with %d", MAX_TARGET_NX_FALLBACK,
+ iq->target_count[TARGET_COUNT_NX]);
+ errinf(qstate, "exceeded the maximum nameserver nxdomains");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+
+ if(!iq->dp->has_parent_side_NS) {
+ struct delegpt_ns* ns;
+ if(!dname_canonical_compare(*iq->nxns_dp, iq->dp->name)) {
+ verbose(VERB_ALGO, "this delegation point "
+ "initiated the fallback, marking the "
+ "nslist as resolved");
+ for(ns=iq->dp->nslist; ns; ns=ns->next) {
+ ns->resolved = 1;
+ }
+ }
+ }
}
/* Make sure we have a delegation point, otherwise priming failed
* that servfail is cached, which is not good as opportunism goes. */
if(iq->depth < ie->max_dependency_depth
&& iq->num_target_queries == 0
- && (!iq->target_count || iq->target_count[2]==0)
+ && (!iq->target_count || iq->target_count[TARGET_COUNT_NX]==0)
&& iq->sent_count < TARGET_FETCH_STOP) {
tf_policy = ie->target_fetch_policy[iq->depth];
}
}
/* Select the next usable target, filtering out unsuitable targets. */
- target = iter_server_selection(ie, qstate->env, iq->dp,
+ target = iter_server_selection(ie, qstate->env, iq->dp,
iq->dp->name, iq->dp->namelen, iq->qchase.qtype,
- &iq->dnssec_lame_query, &iq->chase_to_rd,
+ &iq->dnssec_lame_query, &iq->chase_to_rd,
iq->num_target_queries, qstate->blacklist,
qstate->prefetch_leeway);
/* If there is nothing to wait for, then we need
* to distinguish between generating (a) new target
* query, or failing. */
- if(delegpt_count_missing_targets(iq->dp) > 0) {
+ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) {
int qs = 0;
verbose(VERB_ALGO, "querying for next "
"missing target");
LDNS_RCODE_SERVFAIL);
}
if(qs == 0 &&
- delegpt_count_missing_targets(iq->dp) == 0){
+ delegpt_count_missing_targets(iq->dp, NULL) == 0){
/* it looked like there were missing
* targets, but they did not turn up.
* Try the bad choices again (if any),
iq = (struct iter_qstate*)qstate->minfo[id];
if(iq) {
outbound_list_clear(&iq->outlist);
- if(iq->target_count && --iq->target_count[0] == 0)
+ if(iq->target_count && --iq->target_count[TARGET_COUNT_REF] == 0) {
free(iq->target_count);
+ if(*iq->nxns_dp) free(*iq->nxns_dp);
+ free(iq->nxns_dp);
+ }
iq->num_current_queries = 0;
}
qstate->minfo[id] = NULL;
/** max number of nxdomains allowed for target lookups for a query and
* its subqueries */
#define MAX_TARGET_NX 5
+/** max number of nxdomains allowed for target lookups for a query and
+ * its subqueries when fallback has kicked in */
+#define MAX_TARGET_NX_FALLBACK (MAX_TARGET_NX*2)
/** max number of query restarts. Determines max number of CNAME chain. */
#define MAX_RESTART_COUNT 11
/** max number of referrals. Makes sure resolver does not run away */
FINISHED_STATE
};
+/**
+ * Shared counters for queries.
+ */
+enum target_count_variables {
+ /** Reference count for the shared iter_qstate->target_count. */
+ TARGET_COUNT_REF = 0,
+ /** Number of target queries spawned for the query and subqueries. */
+ TARGET_COUNT_QUERIES,
+ /** Number of nxdomain responses encountered. */
+ TARGET_COUNT_NX,
+
+ /** This should stay last here, it is used for the allocation */
+ TARGET_COUNT_MAX,
+};
+
/**
* Per query state for the iterator module.
*/
/** number of queries fired off */
int sent_count;
- /** number of target queries spawned in [1], for this query and its
- * subqueries, the malloced-array is shared, [0] refcount.
- * in [2] the number of nxdomains is counted. */
+ /** malloced-array shared with this query and its subqueries. It keeps
+ * track of the defined enum target_count_variables counters. */
int* target_count;
/** number of target lookups per delegation point. Reset to 0 after
* receiving referral answer. Not shared with subqueries. */
int dp_target_count;
+ /** Delegation point that triggered the NXNS fallback; shared with
+ * this query and its subqueries, count-referenced by the reference
+ * counter in target_count.
+ * This also marks the fallback activation. */
+ uint8_t** nxns_dp;
+
/** if true, already tested for ratelimiting and passed the test */
int ratelimit_ok;
--- /dev/null
+; Check if fallback to the parent side works when MAX_TARGET_NX is reached.
+
+server:
+ module-config: "iterator"
+ trust-anchor-signaling: no
+ target-fetch-policy: "0 0 0 0 0"
+ verbosity: 3
+ access-control: 127.0.0.1 allow_snoop
+ qname-minimisation: no
+ minimal-responses: no
+ rrset-roundrobin: no
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test the NXNS fallback
+
+; 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 subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ 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
+
+ ENTRY_BEGIN
+ MATCH opcode subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ nonexistant.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 subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ example.com. IN A
+ SECTION AUTHORITY
+ example.com. IN NS ns.example.com.
+ SECTION ADDITIONAL
+ ns.example.com. 10 IN A 1.2.3.4
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ nonexistant.com. IN A
+ SECTION AUTHORITY
+ nonexistant.com. IN NS ns.example.com.
+ SECTION ADDITIONAL
+ ns.example.com. 10 IN A 1.2.3.4
+ 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 NOERROR
+ SECTION QUESTION
+ example.com. IN NS
+ SECTION ANSWER
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ example.com. IN NS ns9.nonexistant.com.
+ example.com. IN NS ns10.nonexistant.com.
+ example.com. IN NS ns11.nonexistant.com.
+ example.com. IN NS ns12.nonexistant.com.
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ ns.example.com. IN A
+ SECTION ANSWER
+ ns.example.com. 10 IN A 1.2.3.4
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ ns.example.com. IN AAAA
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NXDOMAIN
+ SECTION QUESTION
+ nonexistant.com. IN A
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ a.example.com. IN A
+ SECTION ANSWER
+ a.example.com. IN A 10.20.30.40
+ SECTION AUTHORITY
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ example.com. IN NS ns9.nonexistant.com.
+ example.com. IN NS ns10.nonexistant.com.
+ example.com. IN NS ns11.nonexistant.com.
+ example.com. IN NS ns12.nonexistant.com.
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ b.example.com. IN A
+ SECTION ANSWER
+ b.example.com. IN A 10.20.30.40
+ SECTION AUTHORITY
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ example.com. IN NS ns9.nonexistant.com.
+ example.com. IN NS ns10.nonexistant.com.
+ example.com. IN NS ns11.nonexistant.com.
+ example.com. IN NS ns12.nonexistant.com.
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ c.example.com. IN A
+ SECTION ANSWER
+ c.example.com. IN A 10.20.30.40
+ SECTION AUTHORITY
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ example.com. IN NS ns9.nonexistant.com.
+ example.com. IN NS ns10.nonexistant.com.
+ example.com. IN NS ns11.nonexistant.com.
+ example.com. IN NS ns12.nonexistant.com.
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode qtype qname
+ ADJUST copy_id
+ REPLY QR NOERROR
+ SECTION QUESTION
+ d.example.com. IN A
+ SECTION ANSWER
+ d.example.com. IN A 10.20.30.40
+ SECTION AUTHORITY
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ example.com. IN NS ns9.nonexistant.com.
+ example.com. IN NS ns10.nonexistant.com.
+ example.com. IN NS ns11.nonexistant.com.
+ example.com. IN NS ns12.nonexistant.com.
+ ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.example.com. IN A
+ENTRY_END
+
+; This was resolved by asking the parent side nameservers
+STEP 2 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.example.com. IN A
+SECTION ANSWER
+a.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns1.nonexistant.com.
+example.com. IN NS ns2.nonexistant.com.
+example.com. IN NS ns3.nonexistant.com.
+example.com. IN NS ns4.nonexistant.com.
+example.com. IN NS ns5.nonexistant.com.
+example.com. IN NS ns6.nonexistant.com.
+example.com. IN NS ns7.nonexistant.com.
+example.com. IN NS ns8.nonexistant.com.
+example.com. IN NS ns9.nonexistant.com.
+example.com. IN NS ns10.nonexistant.com.
+example.com. IN NS ns11.nonexistant.com.
+example.com. IN NS ns12.nonexistant.com.
+ENTRY_END
+
+; The child side nameservers are now known to Unbound
+
+; Query again, the child server nameservers will be asked now
+STEP 3 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+b.example.com. IN A
+ENTRY_END
+
+; This was resolved by falling back to the parent side nameservers
+STEP 4 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+b.example.com. IN A
+SECTION ANSWER
+b.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns1.nonexistant.com.
+example.com. IN NS ns2.nonexistant.com.
+example.com. IN NS ns3.nonexistant.com.
+example.com. IN NS ns4.nonexistant.com.
+example.com. IN NS ns5.nonexistant.com.
+example.com. IN NS ns6.nonexistant.com.
+example.com. IN NS ns7.nonexistant.com.
+example.com. IN NS ns8.nonexistant.com.
+example.com. IN NS ns9.nonexistant.com.
+example.com. IN NS ns10.nonexistant.com.
+example.com. IN NS ns11.nonexistant.com.
+example.com. IN NS ns12.nonexistant.com.
+ENTRY_END
+
+; Query a third time, this will get the cached NXDOMAINs (no NX counter for
+; those) and will go to the parent as a last resort. This query will test that
+; we will not have resolution for the lame(parent side) addresses that could
+; raise the NX counter because of no address addition to the delegation point
+; (the same addresses are already there).
+STEP 5 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+c.example.com. IN A
+ENTRY_END
+
+; This was resolved by going back to the parent side nameservers (child side
+; was exhausted from cache and queries < MAX_TARGET_NX).
+STEP 6 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+c.example.com. IN A
+SECTION ANSWER
+c.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns1.nonexistant.com.
+example.com. IN NS ns2.nonexistant.com.
+example.com. IN NS ns3.nonexistant.com.
+example.com. IN NS ns4.nonexistant.com.
+example.com. IN NS ns5.nonexistant.com.
+example.com. IN NS ns6.nonexistant.com.
+example.com. IN NS ns7.nonexistant.com.
+example.com. IN NS ns8.nonexistant.com.
+example.com. IN NS ns9.nonexistant.com.
+example.com. IN NS ns10.nonexistant.com.
+example.com. IN NS ns11.nonexistant.com.
+example.com. IN NS ns12.nonexistant.com.
+ENTRY_END
+
+; Allow for the nameserver glue to expire
+STEP 10 TIME_PASSES ELAPSE 11
+
+; Query again for the parent side fallback
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+d.example.com. IN A
+ENTRY_END
+
+; This was resolved by falling back to the parent side nameservers
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+d.example.com. IN A
+SECTION ANSWER
+d.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns1.nonexistant.com.
+example.com. IN NS ns2.nonexistant.com.
+example.com. IN NS ns3.nonexistant.com.
+example.com. IN NS ns4.nonexistant.com.
+example.com. IN NS ns5.nonexistant.com.
+example.com. IN NS ns6.nonexistant.com.
+example.com. IN NS ns7.nonexistant.com.
+example.com. IN NS ns8.nonexistant.com.
+example.com. IN NS ns9.nonexistant.com.
+example.com. IN NS ns10.nonexistant.com.
+example.com. IN NS ns11.nonexistant.com.
+example.com. IN NS ns12.nonexistant.com.
+ENTRY_END
+
+SCENARIO_END
--- /dev/null
+; Check if the NXNS fallback to the parent side does not mess with normal
+; parent side resolution. Parent side resolution should SERVFAIL when reaching
+; the MAX_TARGET_NX limit.
+
+server:
+ module-config: "iterator"
+ trust-anchor-signaling: no
+ target-fetch-policy: "0 0 0 0 0"
+ verbosity: 3
+ access-control: 127.0.0.1 allow_snoop
+ qname-minimisation: no
+ minimal-responses: no
+ rrset-roundrobin: no
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test that the NXNS fallback does not mess with parent side resolution
+
+; 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 subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ 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
+
+ ENTRY_BEGIN
+ MATCH opcode subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ nonexistant.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 subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NOERROR
+ SECTION QUESTION
+ example.com. IN A
+ SECTION AUTHORITY
+ example.com. IN NS ns1.nonexistant.com.
+ example.com. IN NS ns2.nonexistant.com.
+ example.com. IN NS ns3.nonexistant.com.
+ example.com. IN NS ns4.nonexistant.com.
+ example.com. IN NS ns5.nonexistant.com.
+ example.com. IN NS ns6.nonexistant.com.
+ example.com. IN NS ns7.nonexistant.com.
+ example.com. IN NS ns8.nonexistant.com.
+ ENTRY_END
+
+ ENTRY_BEGIN
+ MATCH opcode subdomain
+ ADJUST copy_id copy_query
+ REPLY QR NXDOMAIN
+ SECTION QUESTION
+ nonexistant.com. IN A
+ ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.example.com. IN A
+ENTRY_END
+
+STEP 2 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+a.example.com. IN A
+ENTRY_END
+
+SCENARIO_END