#include "validator/val_neg.h"
#include "services/cache/dns.h"
#include "services/cache/infra.h"
+#include "services/authzone.h"
#include "util/module.h"
#include "util/netevent.h"
#include "util/net_help.h"
if(!stub)
return 0;
stub_dp = stub->dp;
+ /* if we have an auth_zone dp, and stub is equal, don't prime stub
+ * yet, unless we want to fallback and avoid the auth_zone */
+ if(!iq->auth_zone_avoid && iq->dp && iq->dp->auth_dp &&
+ query_dname_compare(iq->dp->name, stub_dp->name) == 0)
+ return 0;
/* is it a noprime stub (always use) */
if(stub->noprime) {
return 1;
}
+/**
+ * Generate a delegation point for an auth zone (unless cached dp is better)
+ * false on alloc failure.
+ */
+static int
+auth_zone_delegpt(struct module_qstate* qstate, struct iter_qstate* iq,
+ uint8_t* delname, size_t delnamelen)
+{
+ struct auth_zone* z;
+ if(iq->auth_zone_avoid)
+ return 1;
+ if(!delname) {
+ delname = iq->qchase.qname;
+ delnamelen = iq->qchase.qname_len;
+ }
+ lock_rw_rdlock(&qstate->env->auth_zones->lock);
+ z = auth_zones_find_zone(qstate->env->auth_zones, delname, delnamelen,
+ qstate->qinfo.qclass);
+ if(!z) {
+ lock_rw_unlock(&qstate->env->auth_zones->lock);
+ return 1;
+ }
+ lock_rw_rdlock(&z->lock);
+ lock_rw_unlock(&qstate->env->auth_zones->lock);
+ if(z->for_upstream) {
+ if(iq->dp==NULL || dname_subdomain_c(z->name, iq->dp->name)) {
+ struct delegpt* dp;
+ dp = (struct delegpt*)regional_alloc_zero(
+ qstate->region, sizeof(*dp));
+ if(!dp) {
+ log_err("alloc failure");
+ if(z->fallback_enabled) {
+ lock_rw_unlock(&z->lock);
+ return 1; /* just fallback */
+ }
+ lock_rw_unlock(&z->lock);
+ return 0;
+ }
+ dp->name = regional_alloc_init(qstate->region,
+ z->name, z->namelen);
+ if(!dp->name) {
+ log_err("alloc failure");
+ if(z->fallback_enabled) {
+ lock_rw_unlock(&z->lock);
+ return 1; /* just fallback */
+ }
+ lock_rw_unlock(&z->lock);
+ return 0;
+ }
+ dp->namelen = z->namelen;
+ dp->namelabs = z->namelabs;
+ dp->auth_dp = 1;
+ iq->dp = dp;
+ }
+ }
+
+ lock_rw_unlock(&z->lock);
+ return 1;
+}
+
/**
* Generate A and AAAA checks for glue that is in-zone for the referral
* we just got to obtain authoritative information on the addresses.
iq->response = msg;
return final_state(iq);
}
-
+
/* attempt to forward the request */
if(forward_request(qstate, iq))
{
/* If the cache has returned nothing, then we have a
* root priming situation. */
if(iq->dp == NULL) {
+ int r;
+ /* if under auth zone, no prime needed */
+ if(!auth_zone_delegpt(qstate, iq, delname, delnamelen))
+ return error_response(qstate, id,
+ LDNS_RCODE_SERVFAIL);
+ if(iq->dp) /* use auth zone dp */
+ return next_state(iq, INIT_REQUEST_2_STATE);
/* if there is a stub, then no root prime needed */
- int r = prime_stub(qstate, iq, id, delname,
+ r = prime_stub(qstate, iq, id, delname,
iq->qchase.qclass);
if(r == 2)
break; /* got noprime-stub-zone, continue */
dname_remove_label(&delname, &delnamelen);
iq->refetch_glue = 0; /* if CNAME causes restart, no refetch */
}
+
+ /* see if we have an auth zone to answer from, improves dp from cache
+ * (if any dp from cache) with auth zone dp, if that is lower */
+ if(!auth_zone_delegpt(qstate, iq, delname, delnamelen))
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+
/* Check to see if we need to prime a stub zone. */
if(prime_stub(qstate, iq, id, delname, iq->qchase.qclass)) {
/* A priming sub request was made */
int tf_policy;
struct delegpt_addr* target;
struct outbound_entry* outq;
+ int auth_fallback = 0;
/* NOTE: a request will encounter this state for each target it
* needs to send a query to. That is, at least one per referral,
return 0;
}
+ if(iq->minimisation_state == INIT_MINIMISE_STATE) {
+ /* (Re)set qinfo_out to (new) delegation point, except when
+ * qinfo_out is already a subdomain of dp. This happens when
+ * increasing by more than one label at once (QNAMEs with more
+ * than MAX_MINIMISE_COUNT labels). */
+ if(!(iq->qinfo_out.qname_len
+ && dname_subdomain_c(iq->qchase.qname,
+ iq->qinfo_out.qname)
+ && dname_subdomain_c(iq->qinfo_out.qname,
+ iq->dp->name))) {
+ iq->qinfo_out.qname = iq->dp->name;
+ iq->qinfo_out.qname_len = iq->dp->namelen;
+ iq->qinfo_out.qtype = LDNS_RR_TYPE_A;
+ iq->qinfo_out.qclass = iq->qchase.qclass;
+ iq->qinfo_out.local_alias = NULL;
+ iq->minimise_count = 0;
+ }
+
+ iq->minimisation_state = MINIMISE_STATE;
+ }
+ if(iq->minimisation_state == MINIMISE_STATE) {
+ int qchaselabs = dname_count_labels(iq->qchase.qname);
+ int labdiff = qchaselabs -
+ dname_count_labels(iq->qinfo_out.qname);
+
+ iq->qinfo_out.qname = iq->qchase.qname;
+ iq->qinfo_out.qname_len = iq->qchase.qname_len;
+ iq->minimise_count++;
+ iq->minimise_timeout_count = 0;
+
+ iter_dec_attempts(iq->dp, 1);
+
+ /* Limit number of iterations for QNAMEs with more
+ * than MAX_MINIMISE_COUNT labels. Send first MINIMISE_ONE_LAB
+ * labels of QNAME always individually.
+ */
+ if(qchaselabs > MAX_MINIMISE_COUNT && labdiff > 1 &&
+ iq->minimise_count > MINIMISE_ONE_LAB) {
+ if(iq->minimise_count < MAX_MINIMISE_COUNT) {
+ int multilabs = qchaselabs - 1 -
+ MINIMISE_ONE_LAB;
+ int extralabs = multilabs /
+ MINIMISE_MULTIPLE_LABS;
+
+ if (MAX_MINIMISE_COUNT - iq->minimise_count >=
+ multilabs % MINIMISE_MULTIPLE_LABS)
+ /* Default behaviour is to add 1 label
+ * every iteration. Therefore, decrement
+ * the extralabs by 1 */
+ extralabs--;
+ if (extralabs < labdiff)
+ labdiff -= extralabs;
+ else
+ labdiff = 1;
+ }
+ /* Last minimised iteration, send all labels with
+ * QTYPE=NS */
+ else
+ labdiff = 1;
+ }
+
+ if(labdiff > 1) {
+ verbose(VERB_QUERY, "removing %d labels", labdiff-1);
+ dname_remove_labels(&iq->qinfo_out.qname,
+ &iq->qinfo_out.qname_len,
+ labdiff-1);
+ }
+ if(labdiff < 1 || (labdiff < 2
+ && (iq->qchase.qtype == LDNS_RR_TYPE_DS
+ || iq->qchase.qtype == LDNS_RR_TYPE_A)))
+ /* Stop minimising this query, resolve "as usual" */
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ else if(!qstate->no_cache_lookup) {
+ struct dns_msg* msg = dns_cache_lookup(qstate->env,
+ iq->qinfo_out.qname, iq->qinfo_out.qname_len,
+ iq->qinfo_out.qtype, iq->qinfo_out.qclass,
+ qstate->query_flags, qstate->region,
+ qstate->env->scratch, 0);
+ if(msg && msg->rep->an_numrrsets == 0
+ && FLAGS_GET_RCODE(msg->rep->flags) ==
+ LDNS_RCODE_NOERROR)
+ /* no need to send query if it is already
+ * cached as NOERROR/NODATA */
+ return 1;
+ }
+ }
+ if(iq->minimisation_state == SKIP_MINIMISE_STATE) {
+ if(iq->minimise_timeout_count < MAX_MINIMISE_TIMEOUT_COUNT)
+ /* Do not increment qname, continue incrementing next
+ * iteration */
+ iq->minimisation_state = MINIMISE_STATE;
+ else if(!qstate->env->cfg->qname_minimisation_strict)
+ /* Too many time-outs detected for this QNAME and QTYPE.
+ * We give up, disable QNAME minimisation. */
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ }
+ if(iq->minimisation_state == DONOT_MINIMISE_STATE)
+ iq->qinfo_out = iq->qchase;
+
+ /* now find an answer to this query */
+ /* see if authority zones have an answer */
+ /* now we know the dp, we can check the auth zone for locally hosted
+ * contents */
+ if(!iq->auth_zone_avoid && qstate->blacklist) {
+ if(auth_zones_can_fallback(qstate->env->auth_zones,
+ iq->dp->name, iq->dp->namelen, iq->qinfo_out.qclass)) {
+ /* if cache is blacklisted and this zone allows us
+ * to fallback to the internet, then do so, and
+ * fetch results from the internet servers */
+ iq->auth_zone_avoid = 1;
+ }
+ }
+ if(iq->auth_zone_avoid) {
+ iq->auth_zone_avoid = 0;
+ auth_fallback = 1;
+ } else if(auth_zones_lookup(qstate->env->auth_zones, &iq->qinfo_out,
+ qstate->region, &iq->response, &auth_fallback, iq->dp->name,
+ iq->dp->namelen)) {
+ /* use this as a response to be processed by the iterator */
+ if(verbosity >= VERB_ALGO) {
+ log_dns_msg("msg from auth zone",
+ &iq->response->qinfo, iq->response->rep);
+ }
+ iq->num_current_queries++;
+ iq->chase_to_rd = 0;
+ iq->dnssec_lame_query = 0;
+ iq->auth_zone_response = 1;
+ return next_state(iq, QUERY_RESP_STATE);
+ }
+ iq->auth_zone_response = 0;
+ if(auth_fallback == 0) {
+ /* like we got servfail from the auth zone lookup, and
+ * no internet fallback */
+ verbose(VERB_ALGO, "auth zone lookup failed, no fallback,"
+ " servfail");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ if(iq->dp && iq->dp->auth_dp) {
+ /* we wanted to fallback, but had no delegpt, only the
+ * auth zone generated delegpt, create an actual one */
+ iq->auth_zone_avoid = 1;
+ return next_state(iq, INIT_REQUEST_STATE);
+ }
+ /* but mostly, fallback==1 (like, when no such auth zone exists)
+ * and we continue with lookups */
+
tf_policy = 0;
/* < not <=, because although the array is large enough for <=, the
* generated query will immediately be discarded due to depth and
}
}
- if(iq->minimisation_state == INIT_MINIMISE_STATE) {
- /* (Re)set qinfo_out to (new) delegation point, except when
- * qinfo_out is already a subdomain of dp. This happens when
- * increasing by more than one label at once (QNAMEs with more
- * than MAX_MINIMISE_COUNT labels). */
- if(!(iq->qinfo_out.qname_len
- && dname_subdomain_c(iq->qchase.qname,
- iq->qinfo_out.qname)
- && dname_subdomain_c(iq->qinfo_out.qname,
- iq->dp->name))) {
- iq->qinfo_out.qname = iq->dp->name;
- iq->qinfo_out.qname_len = iq->dp->namelen;
- iq->qinfo_out.qtype = LDNS_RR_TYPE_A;
- iq->qinfo_out.qclass = iq->qchase.qclass;
- iq->qinfo_out.local_alias = NULL;
- iq->minimise_count = 0;
- }
-
- iq->minimisation_state = MINIMISE_STATE;
- }
- if(iq->minimisation_state == MINIMISE_STATE) {
- int qchaselabs = dname_count_labels(iq->qchase.qname);
- int labdiff = qchaselabs -
- dname_count_labels(iq->qinfo_out.qname);
-
- iq->qinfo_out.qname = iq->qchase.qname;
- iq->qinfo_out.qname_len = iq->qchase.qname_len;
- iq->minimise_count++;
- iq->minimise_timeout_count = 0;
-
- iter_dec_attempts(iq->dp, 1);
-
- /* Limit number of iterations for QNAMEs with more
- * than MAX_MINIMISE_COUNT labels. Send first MINIMISE_ONE_LAB
- * labels of QNAME always individually.
- */
- if(qchaselabs > MAX_MINIMISE_COUNT && labdiff > 1 &&
- iq->minimise_count > MINIMISE_ONE_LAB) {
- if(iq->minimise_count < MAX_MINIMISE_COUNT) {
- int multilabs = qchaselabs - 1 -
- MINIMISE_ONE_LAB;
- int extralabs = multilabs /
- MINIMISE_MULTIPLE_LABS;
-
- if (MAX_MINIMISE_COUNT - iq->minimise_count >=
- multilabs % MINIMISE_MULTIPLE_LABS)
- /* Default behaviour is to add 1 label
- * every iteration. Therefore, decrement
- * the extralabs by 1 */
- extralabs--;
- if (extralabs < labdiff)
- labdiff -= extralabs;
- else
- labdiff = 1;
- }
- /* Last minimised iteration, send all labels with
- * QTYPE=NS */
- else
- labdiff = 1;
- }
-
- if(labdiff > 1) {
- verbose(VERB_QUERY, "removing %d labels", labdiff-1);
- dname_remove_labels(&iq->qinfo_out.qname,
- &iq->qinfo_out.qname_len,
- labdiff-1);
- }
- if(labdiff < 1 || (labdiff < 2
- && (iq->qchase.qtype == LDNS_RR_TYPE_DS
- || iq->qchase.qtype == LDNS_RR_TYPE_A)))
- /* Stop minimising this query, resolve "as usual" */
- iq->minimisation_state = DONOT_MINIMISE_STATE;
- else if(!qstate->no_cache_lookup) {
- struct dns_msg* msg = dns_cache_lookup(qstate->env,
- iq->qinfo_out.qname, iq->qinfo_out.qname_len,
- iq->qinfo_out.qtype, iq->qinfo_out.qclass,
- qstate->query_flags, qstate->region,
- qstate->env->scratch, 0);
- if(msg && msg->rep->an_numrrsets == 0
- && FLAGS_GET_RCODE(msg->rep->flags) ==
- LDNS_RCODE_NOERROR)
- /* no need to send query if it is already
- * cached as NOERROR/NODATA */
- return 1;
- }
- }
- if(iq->minimisation_state == SKIP_MINIMISE_STATE) {
- if(iq->minimise_timeout_count < MAX_MINIMISE_TIMEOUT_COUNT)
- /* Do not increment qname, continue incrementing next
- * iteration */
- iq->minimisation_state = MINIMISE_STATE;
- else if(!qstate->env->cfg->qname_minimisation_strict)
- /* Too many time-outs detected for this QNAME and QTYPE.
- * We give up, disable QNAME minimisation. */
- iq->minimisation_state = DONOT_MINIMISE_STATE;
- }
- if(iq->minimisation_state == DONOT_MINIMISE_STATE)
- iq->qinfo_out = iq->qchase;
-
/* We have a valid target. */
if(verbosity >= VERB_QUERY) {
log_query_info(VERB_QUERY, "sending query:", &iq->qinfo_out);
iq->deleg_msg = NULL;
iq->dp = NULL;
iq->dsns_point = NULL;
+ iq->auth_zone_response = 0;
/* Note the query restart. */
iq->query_restart_count++;
iq->sent_count = 0;
if (qstate->env->cfg->qname_minimisation &&
!qstate->env->cfg->qname_minimisation_strict)
iq->minimisation_state = DONOT_MINIMISE_STATE;
+ if(iq->auth_zone_response) {
+ /* can we fallback? */
+ iq->auth_zone_response = 0;
+ if(!auth_zones_can_fallback(qstate->env->auth_zones,
+ iq->dp->name, iq->dp->namelen, qstate->qinfo.qclass)) {
+ verbose(VERB_ALGO, "auth zone response bad, and no"
+ " fallback possible, servfail");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ verbose(VERB_ALGO, "auth zone response was bad, "
+ "fallback enabled");
+ iq->auth_zone_avoid = 1;
+ if(iq->dp->auth_dp) {
+ /* we are using a dp for the auth zone, with no
+ * nameservers, get one first */
+ iq->dp = NULL;
+ return next_state(iq, INIT_REQUEST_STATE);
+ }
+ }
return next_state(iq, QUERYTARGETS_STATE);
}
return rbtree_find_less_equal(&az->ztree, &key, (rbnode_type**)z);
}
+
/** find the auth zone that is above the given qname */
struct auth_zone*
-auth_zones_find_zone(struct auth_zones* az, struct query_info* qinfo)
+auth_zones_find_zone(struct auth_zones* az, uint8_t* qname, size_t qname_len,
+ uint16_t qclass)
{
- uint8_t* nm = qinfo->qname;
- size_t nmlen = qinfo->qname_len;
+ uint8_t* nm = qname;
+ size_t nmlen = qname_len;
struct auth_zone* z;
- if(auth_zone_find_less_equal(az, nm, nmlen, qinfo->qclass, &z)) {
+ if(auth_zone_find_less_equal(az, nm, nmlen, qclass, &z)) {
/* exact match */
return z;
} else {
if(!z) return NULL; /* nothing smaller, nothing above it */
/* we found smaller name; smaller may be above the qname,
* but not below it. */
- nm = dname_get_shared_topdomain(z->name, qinfo->qname);
+ nm = dname_get_shared_topdomain(z->name, qname);
dname_count_size_labels(nm, &nmlen);
}
/* search up */
while(!z && !dname_is_root(nm)) {
dname_remove_label(&nm, &nmlen);
- z = auth_zone_find(az, nm, nmlen, qinfo->qclass);
+ z = auth_zone_find(az, nm, nmlen, qclass);
}
return z;
}
{
int r;
struct auth_zone* z;
- /* TODO: in iterator, after cache lookup, before network lookup,
- * call this to get answer */
-
/* find the zone that should contain the answer. */
lock_rw_rdlock(&az->lock);
z = auth_zone_find(az, dp_nm, dp_nmlen, qinfo->qclass);
lock_rw_rdlock(&z->lock);
lock_rw_unlock(&az->lock);
+ /* if not for upstream queries, fallback */
+ if(!z->for_upstream) {
+ lock_rw_unlock(&z->lock);
+ *fallback = 1;
+ return 0;
+ }
/* see what answer that zone would generate */
r = auth_zone_generate_answer(z, qinfo, region, msg, fallback);
lock_rw_unlock(&z->lock);
lock_rw_unlock(&az->lock);
return 0;
}
- z = auth_zones_find_zone(az, qinfo);
+ if(qinfo->qtype == LDNS_RR_TYPE_DS) {
+ uint8_t* delname = qinfo->qname;
+ size_t delnamelen = qinfo->qname_len;
+ dname_remove_label(&delname, &delnamelen);
+ z = auth_zones_find_zone(az, delname, delnamelen,
+ qinfo->qclass);
+ } else {
+ z = auth_zones_find_zone(az, qinfo->qname, qinfo->qname_len,
+ qinfo->qclass);
+ }
if(!z) {
/* no zone above it */
lock_rw_unlock(&az->lock);
return 1;
}
+int auth_zones_can_fallback(struct auth_zones* az, uint8_t* nm, size_t nmlen,
+ uint16_t dclass)
+{
+ int r;
+ struct auth_zone* z;
+ lock_rw_rdlock(&az->lock);
+ z = auth_zone_find(az, nm, nmlen, dclass);
+ if(!z) {
+ lock_rw_unlock(&az->lock);
+ /* no such auth zone, fallback */
+ return 1;
+ }
+ lock_rw_rdlock(&z->lock);
+ lock_rw_unlock(&az->lock);
+ r = z->fallback_enabled || (!z->for_upstream);
+ lock_rw_unlock(&z->lock);
+ return r;
+}
+
/** set a zone expired */
static void
auth_xfer_set_expired(struct auth_xfer* xfr, struct module_env* env,