Merge pull request #706 from NLnetLabs/nxns-fallback

NXNS fallback
This commit is contained in:
Yorgos Thessalonikefs 2022-07-01 16:24:33 +02:00 committed by GitHub
commit b0ce31b4e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1091 additions and 57 deletions

View file

@ -8,6 +8,9 @@
- Fix #704: [FR] Statistics counter for number of outgoing UDP queries
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'.

View file

@ -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,

View file

@ -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);

View file

@ -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 */

View file

@ -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);
}
@ -2158,6 +2172,32 @@ processDSNSFind(struct module_qstate* qstate, struct iter_qstate* iq, int id)
return 0;
}
/**
* Check if we wait responses for sent queries and update the iterator's
* external state.
*/
static void
check_waiting_queries(struct iter_qstate* iq, struct module_qstate* qstate,
int id)
{
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;
}
}
/**
* This is the request event state where the request will be sent to one of
@ -2211,12 +2251,91 @@ 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) {
check_waiting_queries(iq, qstate, id);
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) {
verbose(VERB_ALGO, "parent-side information is "
"already present for the delegation point, no "
"fallback possible");
errinf(qstate, "exceeded the maximum nameserver nxdomains");
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
verbose(VERB_ALGO, "initiating parent-side fallback for "
"nxdomain nameserver lookups");
/* 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) {
verbose(VERB_ALGO, "out of memory while initiating "
"fallback");
errinf(qstate, "exceeded the maximum nameserver "
"nxdomains (malloc)");
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) {
check_waiting_queries(iq, qstate, id);
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 +2553,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 +2642,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 +2663,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 +2675,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),
@ -2595,23 +2714,8 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
/* otherwise, we have no current targets, so submerge
* until one of the target or direct queries return. */
if(iq->num_target_queries>0 && iq->num_current_queries>0) {
verbose(VERB_ALGO, "no current targets -- 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, "no current targets -- waiting "
"for %d targets to resolve.",
iq->num_target_queries);
qstate->ext_state[id] = module_wait_subquery;
} else {
verbose(VERB_ALGO, "no current targets -- waiting "
"for %d outstanding queries to respond.",
iq->num_current_queries);
qstate->ext_state[id] = module_wait_reply;
}
verbose(VERB_ALGO, "no current targets");
check_waiting_queries(iq, qstate, id);
/* undo qname minimise step because we'll get back here
* to do it again */
if(qout_orig && iq->minimise_count > 0) {
@ -3383,8 +3487,11 @@ processTargetResponse(struct module_qstate* qstate, int id,
delegpt_mark_neg(dpns, qstate->qinfo.qtype);
dpns->resolved = 1; /* fail the target */
if((dpns->got4 == 2 || !ie->supports_ipv4) &&
(dpns->got6 == 2 || !ie->supports_ipv6))
(dpns->got6 == 2 || !ie->supports_ipv6) &&
/* do not count cached answers */
(qstate->reply_origin && qstate->reply_origin->len != 0)) {
target_count_increase_nx(foriq, 1);
}
}
}
@ -4002,8 +4109,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;

View file

@ -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;

384
testdata/iter_nxns_cached.rpl vendored Normal file
View file

@ -0,0 +1,384 @@
; Check that cached NXDOMAIN replies for nameservers do not count towards 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
do-not-query-localhost: no
qname-minimisation: no
minimal-responses: no
rrset-roundrobin: no
stub-zone:
name: "example.com"
stub-addr: 127.0.0.2
stub-zone:
name: "nameservers.com"
stub-addr: 127.0.0.3
CONFIG_END
SCENARIO_BEGIN Test that the NXNS countermeasure is not triggered for cached NXDOMAIN
RANGE_BEGIN 0 100
ADDRESS 127.0.0.1
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
b.a.example.com. IN A
SECTION ANSWER
b.a.example.com. IN A 127.0.0.0
ENTRY_END
RANGE_END
RANGE_BEGIN 31 100
ADDRESS 127.0.0.3
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns1.nameservers.com. IN A
SECTION ANSWER
ns1.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns2.nameservers.com. IN A
SECTION ANSWER
ns2.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns3.nameservers.com. IN A
SECTION ANSWER
ns3.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns4.nameservers.com. IN A
SECTION ANSWER
ns4.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns5.nameservers.com. IN A
SECTION ANSWER
ns5.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns6.nameservers.com. IN A
SECTION ANSWER
ns6.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns7.nameservers.com. IN A
SECTION ANSWER
ns7.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns8.nameservers.com. IN A
SECTION ANSWER
ns8.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns9.nameservers.com. IN A
SECTION ANSWER
ns9.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns10.nameservers.com. IN A
SECTION ANSWER
ns10.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns11.nameservers.com. IN A
SECTION ANSWER
ns11.nameservers.com. IN A 127.0.0.1
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns12.nameservers.com. IN A
SECTION ANSWER
ns12.nameservers.com. IN A 127.0.0.1
ENTRY_END
; Reply no-data to AAAA queries
ENTRY_BEGIN
MATCH opcode subdomain
ADJUST copy_id copy_query
REPLY QR NOERROR
SECTION QUESTION
nameservers.com. IN A
ENTRY_END
RANGE_END
; Query for a domain
STEP 0 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
a.example.com. IN A
ENTRY_END
; Answer with delegation
STEP 1 REPLY
ENTRY_BEGIN
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
a.example.com. IN A
SECTION AUTHORITY
a.example.com. IN NS ns1.nameservers.com.
a.example.com. IN NS ns2.nameservers.com.
a.example.com. IN NS ns3.nameservers.com.
a.example.com. IN NS ns4.nameservers.com.
a.example.com. IN NS ns5.nameservers.com.
a.example.com. IN NS ns6.nameservers.com.
a.example.com. IN NS ns7.nameservers.com.
a.example.com. IN NS ns8.nameservers.com.
a.example.com. IN NS ns9.nameservers.com.
a.example.com. IN NS ns10.nameservers.com.
a.example.com. IN NS ns11.nameservers.com.
a.example.com. IN NS ns12.nameservers.com.
ENTRY_END
; Reply NXDOMAIN to MAX_TARGET_NX queries(6) x2 (A+AAAA)
STEP 2 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
SECTION AUTHORITY
example.com. IN SOA ns.example.com email.example.com 1 2 3 4 60
ENTRY_END
STEP 3 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
SECTION AUTHORITY
example.com. IN SOA ns.ns email.email 1 2 3 4 60
ENTRY_END
STEP 4 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 5 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 6 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 7 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 8 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 9 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 10 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 11 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 12 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 13 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
; We should receive SERVFAIL because MAX_TARGET_NX was reached
STEP 14 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA SERVFAIL
SECTION QUESTION
a.example.com. IN A
ENTRY_END
; Query for another domain in the same delegation
STEP 20 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
b.a.example.com. IN A
ENTRY_END
; We still have 6 NSes that Unbound didn't try to resolve
; Reply with NXDOMAIN for 5 of them
STEP 21 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 22 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 23 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 24 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 25 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 26 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 27 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 28 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 29 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
STEP 30 REPLY
ENTRY_BEGIN
ADJUST copy_id copy_query
REPLY QR NXDOMAIN
SECTION QUESTION
a.query. IN A
ENTRY_END
; Unbound will reach the upstream and get the answer for the final NS
; which has the answer for the client query.
STEP 40 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA NOERROR
SECTION QUESTION
b.a.example.com. IN A
SECTION ANSWER
b.a.example.com. IN A 127.0.0.0
ENTRY_END
; Allow for possible pending NS query (AAAA) to get answered
STEP 41 TRAFFIC
SCENARIO_END

380
testdata/iter_nxns_fallback.rpl vendored Normal file
View file

@ -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

118
testdata/iter_nxns_parentside.rpl vendored Normal file
View file

@ -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