diff --git a/Makefile.in b/Makefile.in index fe587f7de..f555d5981 100644 --- a/Makefile.in +++ b/Makefile.in @@ -680,9 +680,9 @@ iterator.lo iterator.o: $(srcdir)/iterator/iterator.c config.h $(srcdir)/iterato $(srcdir)/iterator/iter_delegpt.h $(srcdir)/iterator/iter_scrub.h $(srcdir)/iterator/iter_priv.h \ $(srcdir)/validator/val_neg.h $(srcdir)/services/cache/dns.h $(srcdir)/services/cache/infra.h \ $(srcdir)/util/rtt.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ - $(srcdir)/dnscrypt/cert.h $(srcdir)/util/net_help.h \ - $(srcdir)/util/regional.h $(srcdir)/util/data/dname.h $(srcdir)/util/data/msgencode.h \ - $(srcdir)/util/fptr_wlist.h $(srcdir)/util/tube.h $(srcdir)/services/mesh.h $(srcdir)/services/modstack.h \ + $(srcdir)/dnscrypt/cert.h $(srcdir)/services/authzone.h \ + $(srcdir)/services/mesh.h $(srcdir)/services/modstack.h $(srcdir)/util/net_help.h $(srcdir)/util/regional.h \ + $(srcdir)/util/data/dname.h $(srcdir)/util/data/msgencode.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/tube.h \ $(srcdir)/util/config_file.h $(srcdir)/util/random.h $(srcdir)/sldns/wire2str.h $(srcdir)/sldns/str2wire.h \ $(srcdir)/sldns/parseutil.h $(srcdir)/sldns/sbuffer.h iter_delegpt.lo iter_delegpt.o: $(srcdir)/iterator/iter_delegpt.c config.h $(srcdir)/iterator/iter_delegpt.h \ diff --git a/iterator/iter_delegpt.h b/iterator/iter_delegpt.h index 4bd79c81a..24f057490 100644 --- a/iterator/iter_delegpt.h +++ b/iterator/iter_delegpt.h @@ -83,6 +83,8 @@ struct delegpt { uint8_t dp_type_mlc; /** use SSL for upstream query */ uint8_t ssl_upstream; + /** delegpt from authoritative zone that is locally hosted */ + uint8_t auth_dp; }; /** diff --git a/iterator/iterator.c b/iterator/iterator.c index ddad1da9f..0caaf9875 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -53,6 +53,7 @@ #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" @@ -771,6 +772,11 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, 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) { @@ -831,6 +837,66 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, 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. @@ -1167,7 +1233,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, iq->response = msg; return final_state(iq); } - + /* attempt to forward the request */ if(forward_request(qstate, iq)) { @@ -1228,8 +1294,15 @@ processInitRequest(struct module_qstate* qstate, struct iter_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 */ @@ -1398,6 +1471,12 @@ processInitRequest2(struct module_qstate* qstate, struct iter_qstate* iq, 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 */ @@ -1882,6 +1961,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, 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, @@ -1926,6 +2006,152 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, 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 @@ -2093,105 +2319,6 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, } } - 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); @@ -2584,6 +2711,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, 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; @@ -2656,6 +2784,25 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, 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); } diff --git a/iterator/iterator.h b/iterator/iterator.h index 841a36436..67ffeb147 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -387,6 +387,11 @@ struct iter_qstate { * Count number of time-outs. Used to prevent resolving failures when * the QNAME minimisation QTYPE is blocked. */ int minimise_timeout_count; + + /** True if the current response is from auth_zone */ + int auth_zone_response; + /** True if the auth_zones should not be consulted for the query */ + int auth_zone_avoid; }; /** diff --git a/services/authzone.c b/services/authzone.c index c9fc2eb0b..ded654d54 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -441,14 +441,16 @@ auth_zone_find_less_equal(struct auth_zones* az, uint8_t* nm, size_t nmlen, 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 { @@ -456,13 +458,13 @@ auth_zones_find_zone(struct auth_zones* az, struct query_info* qinfo) 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; } @@ -3010,9 +3012,6 @@ int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, { 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); @@ -3026,6 +3025,12 @@ int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, 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); @@ -3094,7 +3099,16 @@ int auth_zones_answer(struct auth_zones* az, struct module_env* env, 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); @@ -3123,6 +3137,25 @@ int auth_zones_answer(struct auth_zones* az, struct module_env* env, 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, diff --git a/services/authzone.h b/services/authzone.h index 0f2fa39fb..dc2db6c6b 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -483,11 +483,14 @@ int auth_zones_answer(struct auth_zones* az, struct module_env* env, * Return NULL when there is no auth_zone above the give name, otherwise * returns the closest auth_zone above the qname that pertains to it. * @param az: auth zones structure. + * @param name: query to look up for. + * @param namelen: length of name. + * @param dclass: class of zone to find. * @param qinfo: query info to lookup. * @return NULL or auth_zone that pertains to the query. */ struct auth_zone* auth_zones_find_zone(struct auth_zones* az, - struct query_info* qinfo); + uint8_t* name, size_t namelen, uint16_t dclass); /** find an auth zone by name (exact match by name or NULL returned) */ struct auth_zone* auth_zone_find(struct auth_zones* az, uint8_t* nm, @@ -497,7 +500,6 @@ struct auth_zone* auth_zone_find(struct auth_zones* az, uint8_t* nm, struct auth_xfer* auth_xfer_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass); - /** create an auth zone. returns wrlocked zone. caller must have wrlock * on az. returns NULL on malloc failure */ struct auth_zone* auth_zone_create(struct auth_zones* az, uint8_t* nm, @@ -510,6 +512,18 @@ int auth_zone_set_zonefile(struct auth_zone* z, char* zonefile); * fallbackstr is "yes" or "no". false on parse failure. */ int auth_zone_set_fallback(struct auth_zone* z, char* fallbackstr); +/** see if the auth zone for the name can fallback + * @param az: auth zones + * @param nm: name of delegation point. + * @param nmlen: length of nm. + * @param dclass: class of zone to look for. + * @return true if fallback_enabled is true. false if not. + * if the zone does not exist, fallback is true (more lenient) + * also true if zone does not do upstream requests. + */ +int auth_zones_can_fallback(struct auth_zones* az, uint8_t* nm, size_t nmlen, + uint16_t dclass); + /** read auth zone from zonefile. caller must lock zone. false on failure */ int auth_zone_read_zonefile(struct auth_zone* z); diff --git a/testcode/replay.h b/testcode/replay.h index b33950304..acf4ca97e 100644 --- a/testcode/replay.h +++ b/testcode/replay.h @@ -49,6 +49,14 @@ * AUTOTRUST_FILE id * ; contents of that file * AUTOTRUST_END + * ; temp file names are echoed as "tmp/xxx.fname" + * TEMPFILE_NAME fname + * ; temp file contents, inline, deleted at end of run + * TEMPFILE_CONTENTS fname + * ; contents of that file + * ; this creates $INCLUDE /tmp/xxx.fname + * $INCLUDE_TEMPFILE fname + * TEMPFILE_END * CONFIG_END * ; comment line. * SCENARIO_BEGIN name_of_scenario diff --git a/testcode/testbound.c b/testcode/testbound.c index 20c99608f..5ec51ab3d 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -135,6 +135,65 @@ echo_cmdline(int argc, char* argv[]) fprintf(stderr, "\n"); } +/** spool temp file name */ +static void +spool_temp_file_name(int* lineno, FILE* cfg, char* id) +{ + char line[MAX_LINE_LEN]; + /* find filename for new file */ + while(isspace((unsigned char)*id)) + id++; + if(*id == '\0') + fatal_exit("TEMPFILE_NAME must have id, line %d", *lineno); + id[strlen(id)-1]=0; /* remove newline */ + fake_temp_file("_temp_", id, line, sizeof(line)); + fprintf(cfg, "\"%s\"\n", line); +} + +/** spool temp file */ +static void +spool_temp_file(FILE* in, int* lineno, char* id) +{ + char line[MAX_LINE_LEN]; + char* parse; + FILE* spool; + /* find filename for new file */ + while(isspace((unsigned char)*id)) + id++; + if(*id == '\0') + fatal_exit("TEMPFILE_CONTENTS must have id, line %d", *lineno); + id[strlen(id)-1]=0; /* remove newline */ + fake_temp_file("_temp_", id, line, sizeof(line)); + /* open file and spool to it */ + spool = fopen(line, "w"); + if(!spool) fatal_exit("could not open %s: %s", line, strerror(errno)); + fprintf(stderr, "testbound is spooling temp file: %s\n", line); + if(!cfg_strlist_insert(&cfgfiles, strdup(line))) + fatal_exit("out of memory"); + line[sizeof(line)-1] = 0; + while(fgets(line, MAX_LINE_LEN-1, in)) { + parse = line; + (*lineno)++; + while(isspace((unsigned char)*parse)) + parse++; + if(strncmp(parse, "$INCLUDE_TEMPFILE", 17) == 0) { + char l2[MAX_LINE_LEN]; + char* tid = parse+17; + while(isspace((unsigned char)*tid)) + tid++; + tid[strlen(tid)-1]=0; /* remove newline */ + fake_temp_file("_temp_", tid, l2, sizeof(l2)); + snprintf(line, sizeof(line), "$INCLUDE %s\n", l2); + } + if(strncmp(parse, "TEMPFILE_END", 12) == 0) { + fclose(spool); + return; + } + fputs(line, spool); + } + fatal_exit("no TEMPFILE_END in input file"); +} + /** spool autotrust file */ static void spool_auto_file(FILE* in, int* lineno, FILE* cfg, char* id) @@ -213,6 +272,14 @@ setup_config(FILE* in, int* lineno, int* pass_argc, char* pass_argv[]) spool_auto_file(in, lineno, cfg, parse+14); continue; } + if(strncmp(parse, "TEMPFILE_NAME", 13) == 0) { + spool_temp_file_name(lineno, cfg, parse+13); + continue; + } + if(strncmp(parse, "TEMPFILE_CONTENTS", 17) == 0) { + spool_temp_file(in, lineno, parse+17); + continue; + } if(strncmp(parse, "CONFIG_END", 10) == 0) { fclose(cfg); return; diff --git a/testcode/unitauth.c b/testcode/unitauth.c index 6ca0be9d8..7aa42acfe 100644 --- a/testcode/unitauth.c +++ b/testcode/unitauth.c @@ -521,6 +521,7 @@ addzone(struct auth_zones* az, const char* name, char* fname) lock_rw_unlock(&az->lock); if(!z) fatal_exit("cannot find zone"); auth_zone_set_zonefile(z, fname); + z->for_upstream = 1; if(!auth_zone_read_zonefile(z)) { fatal_exit("parse failure for auth zone %s", name); @@ -685,8 +686,12 @@ msgtostr(struct dns_msg* msg) char* str; sldns_buffer* buf = sldns_buffer_new(65535); if(!buf) fatal_exit("out of memory"); - pr_flags(buf, msg->rep->flags); - pr_rrs(buf, msg->rep); + if(!msg) { + sldns_buffer_printf(buf, "null packet\n"); + } else { + pr_flags(buf, msg->rep->flags); + pr_rrs(buf, msg->rep); + } str = strdup((char*)sldns_buffer_begin(buf)); if(!str) fatal_exit("out of memory"); diff --git a/testdata/auth_zonefile.rpl b/testdata/auth_zonefile.rpl new file mode 100644 index 000000000..5ca6a5cc3 --- /dev/null +++ b/testdata/auth_zonefile.rpl @@ -0,0 +1,187 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + +auth-zone: + name: "example.com." + ## zonefile (or none). + ## zonefile: "example.com.zone" + ## master by IP address or hostname + ## can list multiple masters, each on one line. + ## master: + ## url for http fetch + ## url: + ## queries from downstream clients get authoritative answers. + ## for-downstream: yes + ## queries are used to fetch authoritative answers from this zone, + ## instead of unbound itself sending queries there. + ## for-upstream: yes + ## on failures with for-upstream, fallback to sending queries to + ## the authority servers + ## fallback-enabled: no + + ## this line generates zonefile: \n"/tmp/xxx.example.com"\n + zonefile: +TEMPFILE_NAME example.com + ## this is the inline file /tmp/xxx.example.com + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS example.com +$ORIGIN com. +example 3600 IN SOA dns.example.de. hostmaster.dns.example.de. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.example.com. + 3600 IN NS ns2.example.com. +$ORIGIN example.com. +www 3600 IN A 1.2.3.4 +mail 3600 IN A 1.2.3.5 + 3600 IN AAAA ::5 +ns1 3600 IN A 1.2.3.4 +ns2 3600 IN AAAA ::2 +$INCLUDE_TEMPFILE example.inc +TEMPFILE_END +TEMPFILE_CONTENTS example.inc +other 7200 IN A 1.2.3.6 +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test authority zone with zonefile + +; 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 subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +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 subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.example.net. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.net. IN NS +SECTION ANSWER +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +ns.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN A +SECTION ANSWER +ns.example.net. IN A 1.2.3.44 +SECTION AUTHORITY +example.net. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN AAAA +SECTION AUTHORITY +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +www.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 1.2.3.4 +ENTRY_END + +SCENARIO_END