diff --git a/doc/Changelog b/doc/Changelog index 4695802e9..6911f6eef 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,8 @@ 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'. diff --git a/iterator/iter_delegpt.c b/iterator/iter_delegpt.c index 80148e810..4bffa1b3a 100644 --- a/iterator/iter_delegpt.c +++ b/iterator/iter_delegpt.c @@ -185,6 +185,10 @@ delegpt_add_target(struct delegpt* dp, struct regional* region, 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, @@ -338,13 +342,16 @@ delegpt_count_targets(struct delegpt* dp) } 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; } @@ -694,6 +701,10 @@ int delegpt_add_target_mlc(struct delegpt* dp, uint8_t* name, size_t namelen, 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, diff --git a/iterator/iter_delegpt.h b/iterator/iter_delegpt.h index 998b98cd8..62c8edc51 100644 --- a/iterator/iter_delegpt.h +++ b/iterator/iter_delegpt.h @@ -330,9 +330,10 @@ void delegpt_add_unused_targets(struct delegpt* dp); /** * 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); diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index f3bea46d6..6d159157a 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -367,6 +367,7 @@ iter_filter_order(struct iter_env* iter_env, struct module_env* env, 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; @@ -376,7 +377,10 @@ iter_filter_order(struct iter_env* iter_env, struct module_env* env, 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 */ diff --git a/iterator/iterator.c b/iterator/iterator.c index 2e64c3945..ea0b37cbf 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -253,8 +253,9 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super) 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 */ @@ -678,15 +679,20 @@ is_caps_whitelisted(struct iter_env* ie, struct iter_qstate* iq) 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*)); + } } } @@ -695,7 +701,7 @@ target_count_increase(struct iter_qstate* iq, int num) { target_count_create(iq); if(iq->target_count) - iq->target_count[1] += num; + iq->target_count[TARGET_COUNT_QUERIES] += num; iq->dp_target_count++; } @@ -704,7 +710,7 @@ target_count_increase_nx(struct iter_qstate* iq, int num) { target_count_create(iq); if(iq->target_count) - iq->target_count[2] += num; + iq->target_count[TARGET_COUNT_NX] += num; } /** @@ -799,8 +805,10 @@ generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, 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; @@ -1832,7 +1840,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, 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 @@ -1851,11 +1859,12 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, 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) { @@ -1883,7 +1892,9 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, 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, @@ -1896,7 +1907,9 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, 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)) { @@ -2006,7 +2019,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, 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)) { @@ -2027,11 +2040,12 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, 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); } @@ -2211,12 +2225,112 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, 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 @@ -2434,7 +2548,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, * 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]; } @@ -2523,9 +2637,9 @@ 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, + 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); @@ -2544,7 +2658,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, /* 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"); @@ -2556,7 +2670,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, 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), @@ -4005,8 +4119,11 @@ iter_clear(struct module_qstate* qstate, int id) 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; diff --git a/iterator/iterator.h b/iterator/iterator.h index 8b840528d..62f4768ea 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -60,6 +60,9 @@ struct rbtree_type; /** 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 */ @@ -217,6 +220,21 @@ enum iter_state { 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. */ @@ -310,15 +328,20 @@ struct iter_qstate { /** 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; diff --git a/testdata/iter_nxns_fallback.rpl b/testdata/iter_nxns_fallback.rpl new file mode 100644 index 000000000..324068604 --- /dev/null +++ b/testdata/iter_nxns_fallback.rpl @@ -0,0 +1,380 @@ +; 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 diff --git a/testdata/iter_nxns_parentside.rpl b/testdata/iter_nxns_parentside.rpl new file mode 100644 index 000000000..94a5a6f1b --- /dev/null +++ b/testdata/iter_nxns_parentside.rpl @@ -0,0 +1,118 @@ +; 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