From: Wouter Wijngaards Date: Thu, 31 May 2007 12:51:36 +0000 (+0000) Subject: query targets state. X-Git-Tag: release-0.4~114 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=80391ee2b8ec56bd1fc8a321d51cd8702e095252;p=thirdparty%2Funbound.git query targets state. git-svn-id: file:///svn/unbound/trunk@352 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 017e685ca..6f97eede3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,10 @@ +31 May 2007: Wouter + - querytargets state. + - dname_subdomain_c() routine. + - server selection, based on RTT. ip6 is filtered out if not available, + and lameness is checked too. + - delegation point copy routine. + 30 May 2007: Wouter - removed FLAG_CD from message and rrset caches. This was useful for an agnostic forwarder, but not for a sophisticated (trust value per diff --git a/iterator/iter_delegpt.c b/iterator/iter_delegpt.c index e4e0569b8..0483bd4c4 100644 --- a/iterator/iter_delegpt.c +++ b/iterator/iter_delegpt.c @@ -55,6 +55,27 @@ delegpt_create(struct region* region) return dp; } +struct delegpt* delegpt_copy(struct delegpt* dp, struct region* region) +{ + struct delegpt* copy = delegpt_create(region); + struct delegpt_ns* ns; + struct delegpt_addr* a; + if(!copy) + return NULL; + if(!delegpt_set_name(copy, region, dp->name)) + return NULL; + for(ns = dp->nslist; ns; ns = ns->next) { + if(!delegpt_add_ns(copy, region, ns->name)) + return NULL; + copy->nslist->resolved = ns->resolved; + } + for(a = dp->target_list; a; a = a->next_target) { + if(!delegpt_add_addr(copy, region, &a->addr, a->addrlen)) + return NULL; + } + return copy; +} + int delegpt_set_name(struct delegpt* dp, struct region* region, uint8_t* name) { @@ -144,3 +165,26 @@ void delegpt_log(struct delegpt* dp) log_addr(" ", &a->addr, a->addrlen); } } + +void +delegpt_add_unused_targets(struct delegpt* dp) +{ + struct delegpt_addr* usa = dp->usable_list; + dp->usable_list = NULL; + while(usa) { + usa->next_result = dp->result_list; + dp->result_list = usa; + usa = usa->next_usable; + } +} + +size_t +delegpt_count_missing_targets(struct delegpt* dp) +{ + struct delegpt_ns* ns; + size_t n = 0; + for(ns = dp->nslist; ns; ns = ns->next) + if(!ns->resolved) + n++; + return n; +} diff --git a/iterator/iter_delegpt.h b/iterator/iter_delegpt.h index 827f93b64..b8ec9ab54 100644 --- a/iterator/iter_delegpt.h +++ b/iterator/iter_delegpt.h @@ -62,7 +62,8 @@ struct delegpt { struct delegpt_ns* nslist; /** the target addresses for delegation */ struct delegpt_addr* target_list; - /** the list of usable targets; subset of target_list */ + /** the list of usable targets; subset of target_list + * the items in this list are not part of the result list. */ struct delegpt_addr* usable_list; /** the list of returned targets; subset of target_list */ struct delegpt_addr* result_list; @@ -109,6 +110,14 @@ struct delegpt_addr { */ struct delegpt* delegpt_create(struct region* region); +/** + * Create a copy of a delegation point. + * @param dp: delegation point to copy. + * @param region: where to allocate it. + * @return new delegation point or NULL on error. + */ +struct delegpt* delegpt_copy(struct delegpt* dp, struct region* region); + /** * Set name of delegation point. * @param dp: delegation point. @@ -159,4 +168,17 @@ int delegpt_add_addr(struct delegpt* dp, struct region* region, */ void delegpt_log(struct delegpt* dp); +/** + * Add all usable targets to the result list. + * @param dp: delegation point. + */ +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. + * @return number of missing targets (or 0). + */ +size_t delegpt_count_missing_targets(struct delegpt* dp); + #endif /* ITERATOR_ITER_DELEGPT_H */ diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index 9e140ed5d..598293280 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -43,10 +43,14 @@ #include "iterator/iter_utils.h" #include "iterator/iterator.h" #include "iterator/iter_hints.h" +#include "iterator/iter_delegpt.h" +#include "services/cache/infra.h" #include "util/net_help.h" +#include "util/module.h" #include "util/log.h" #include "util/config_file.h" - +#include "util/region-allocator.h" + int iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) { @@ -87,3 +91,63 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) return 1; } +/** filter out unsuitable targets, return rtt or -1 */ +static int +iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env, + struct delegpt_addr* a, uint8_t* name, size_t namelen, time_t now) +{ + int rtt; + int lame; + /* TODO: check ie->donotqueryaddrs for a */ + if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr)) { + return -1; + } + /* check lameness - need zone , class info */ + if(infra_get_lame_rtt(env->infra_cache, &a->addr, a->addrlen, + name, namelen, &lame, &rtt, now)) { + if(lame) + return -1; + else return rtt; + } + /* no server information present */ + return UNKNOWN_SERVER_NICENESS; +} + +struct delegpt_addr* iter_server_selection(struct iter_env* iter_env, + struct module_env* env, struct delegpt* dp, + uint8_t* name, size_t namelen) +{ + int got_one = 0, got_rtt = 0; + struct delegpt_addr* got = NULL, *got_prev = NULL, *a, *prev = NULL; + time_t now = time(NULL); + + for(a = dp->result_list; a; a = a->next_result) { + /* filter out unsuitable targets */ + int thisrtt = iter_filter_unsuitable(iter_env, env, a, name, + namelen, now); + if(thisrtt == -1) { + prev = a; + continue; + } + if(!got_one) { + got_rtt = thisrtt; + got = a; + got_prev = prev; + got_one = 1; + } else { + if(thisrtt < got_rtt) { + got_rtt = thisrtt; + got = a; + got_prev = prev; + } + } + prev = a; + } + if(got) { + /* remove it from list */ + if(got_prev) + got_prev->next_result = got->next_result; + else dp->result_list = got->next_result; + } + return got; +} diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h index b3e6d77a1..b0768dc06 100644 --- a/iterator/iter_utils.h +++ b/iterator/iter_utils.h @@ -44,6 +44,9 @@ #define ITERATOR_ITER_UTILS_H struct iter_env; struct config_file; +struct module_env; +struct delegpt_addr; +struct delegpt; /** * Process config options and set iterator module state. @@ -54,4 +57,21 @@ struct config_file; */ int iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg); +/** + * Select a valid, nice target to send query to. + * Sorting and removing unsuitable targets is combined. + * + * @param iter_env: iterator module global state, with ip6 enabled and + * do-not-query-addresses. + * @param env: environment with infra cache (lameness, rtt info). + * @param dp: delegation point with result list. + * @param name: zone name (for lameness check). + * @param namelen: length of name. + * @return best target or NULL if no target. + * if not null, that target is removed from the result list in the dp. + */ +struct delegpt_addr* iter_server_selection(struct iter_env* iter_env, + struct module_env* env, struct delegpt* dp, uint8_t* name, + size_t namelen); + #endif /* ITERATOR_ITER_UTILS_H */ diff --git a/iterator/iterator.c b/iterator/iterator.c index 38111ab8c..b6722319a 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -100,13 +100,14 @@ iter_new(struct module_qstate* qstate, int id) iq->prepend_list = NULL; iq->prepend_last = NULL; iq->dp = NULL; - iq->current_target = NULL; iq->num_target_queries = -1; /* default our targetQueries counter. */ iq->num_current_queries = 0; iq->query_restart_count = 0; iq->referral_count = 0; iq->priming_stub = 0; iq->orig_qflags = qstate->query_flags; + /* remove all weird bits from the query flags */ + qstate->query_flags &= (BIT_RD | BIT_CD); outbound_list_init(&iq->outlist); return 1; } @@ -798,8 +799,7 @@ processInitRequest2(struct module_qstate* qstate, struct iter_qstate* iq, * * @param qstate: query state. * @param iq: iterator query state. - * @return true if the event needs more request processing immediately, - * false if not. + * @return true, advancing the event to the QUERYTARGETS_STATE. */ static int processInitRequest3(struct module_qstate* qstate, struct iter_qstate* iq) @@ -821,15 +821,250 @@ processInitRequest3(struct module_qstate* qstate, struct iter_qstate* iq) return next_state(qstate, iq, QUERYTARGETS_STATE); } -#if 0 -/** TODO */ +/** + * Given a basic query, generate a "target" query. These are subordinate + * queries for missing delegation point target addresses. + * + * @param qstate: query state. + * @param iq: iterator query state. + * @param id: module id. + * @param name: target qname. + * @param namelen: target qname length. + * @param qtype: target qtype (either A or AAAA). + * @param qclass: target qclass. + * @return true on success, false on failure. + */ +static int +generate_target_query(struct module_qstate* qstate, struct iter_qstate* iq, + int id, uint8_t* name, size_t namelen, uint16_t qtype, uint16_t qclass) +{ + struct module_qstate* subq = generate_sub_request(name, namelen, qtype, + qclass, qstate, id, INIT_REQUEST_STATE, TARGET_RESP_STATE); + struct iter_qstate* subiq; + if(!subq) + return 0; + subiq = (struct iter_qstate*)subq->minfo[id]; + subiq->dp = delegpt_copy(iq->dp, subq->region); + if(!subiq->dp) { + subq->ext_state[id] = module_error; + return 0; + } + return 1; +} + +/** + * Given an event at a certain state, generate zero or more target queries + * for it's current delegation point. + * + * @param qstate: query state. + * @param iq: iterator query state. + * @param ie: iterator shared global environment. + * @param id: module id. + * @param maxtargets: The maximum number of targets to query for. + * if it is negative, there is no maximum number of targets. + * @param num: returns the number of queries generated and processed, + * which may be zero if there were no missing targets. + * @return false on error. + */ +static int +query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, + struct iter_env* ie, int id, int maxtargets, int* num) +{ + int query_count = 0; + int target_count = 0; + struct delegpt_ns* ns = iq->dp->nslist; + + /* Generate target requests. Basically, any missing targets + * are queried for here, regardless if it is necessary to do + * so to continue processing. */ + + /* loop over missing targets */ + for(ns = iq->dp->nslist; ns; ns = ns->next) { + if(ns->resolved) + continue; + + /* Sanity check: if the target name is at or *below* the + * delegation point itself, then this will be (potentially) + * unresolvable. This is the one case where glue *must* + * have been present. + * FIXME: at this point, this *may* be resolvable, so + * perhaps we should issue the query anyway and let it fail.*/ + if(dname_subdomain_c(ns->name, iq->dp->name)) { + log_nametypeclass("skipping target name because " + "it should have been glue", ns->name, + LDNS_RR_TYPE_NS, qstate->qinfo.qclass); + continue; + } + + if(ie->supports_ipv6) { + /* Send the AAAA request. */ + if(!generate_target_query(qstate, iq, id, + ns->name, ns->namelen, + LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass)) + return 0; + query_count++; + } + /* Send the A request. */ + if(!generate_target_query(qstate, iq, id, + ns->name, ns->namelen, + LDNS_RR_TYPE_A, qstate->qinfo.qclass)) + return 0; + query_count++; + + /* mark this target as in progress. */ + ns->resolved = 1; + + /* if maxtargets is negative, there is no maximum, + * otherwise only query for ntarget names. */ + if(maxtargets > 0 && ++target_count > maxtargets) + break; + } + *num = query_count; + + return 1; +} + +/** + * This is the request event state where the request will be sent to one of + * its current query targets. This state also handles issuing target lookup + * queries for missing target IP addresses. Queries typically iterate on + * this state, both when they are just trying different targets for a given + * delegation point, and when they change delegation points. This state + * roughly corresponds to RFC 1034 algorithm steps 3 and 4. + * + * @param qstate: query state. + * @param iq: iterator query state. + * @param ie: iterator shared global environment. + * @param id: module id. + * @return true if the event requires more request processing immediately, + * false if not. This state only returns true when it is generating + * a SERVFAIL response because the query has hit a dead end. + */ static int processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { + int tf_policy, d; + struct delegpt_addr* target; + struct outbound_entry* outq; + + /* NOTE: a request will encounter this state for each target it + * needs to send a query to. That is, at least one per referral, + * more if some targets timeout or return throwaway answers. */ + + log_nametypeclass("processQueryTargets:", qstate->qinfo.qname, + qstate->qinfo.qtype, qstate->qinfo.qclass); + verbose(VERB_ALGO, "processQueryTargets: targetqueries %d, " + "currentqueries %d", iq->num_target_queries, + iq->num_current_queries); + + /* Make sure that we haven't run away */ + /* FIXME: is this check even necessary? */ + if(iq->referral_count > MAX_REFERRAL_COUNT) { + verbose(VERB_ALGO, "request has exceeded the maximum " + "number of referrrals with %d", iq->referral_count); + return error_response(qstate, iq, LDNS_RCODE_SERVFAIL); + } + + tf_policy = 0; + d = module_subreq_depth(qstate); + if(d <= ie->max_dependency_depth) { + tf_policy = ie->target_fetch_policy[d]; + } + + /* if there is a policy to fetch missing targets + * opportunistically, do it. we rely on the fact that once a + * query (or queries) for a missing name have been issued, + * they will not be show up again. */ + if(tf_policy != 0) { + if(!query_for_targets(qstate, iq, ie, id, tf_policy, + &iq->num_target_queries)) { + return error_response(qstate, iq, LDNS_RCODE_SERVFAIL); + } + } else { + iq->num_target_queries = 0; + } + + /* Add the current set of unused targets to our queue. */ + delegpt_add_unused_targets(iq->dp); + + /* Select the next usable target, filtering out unsuitable targets. */ + target = iter_server_selection(ie, qstate->env, iq->dp, + iq->dp->name, iq->dp->namelen); + + /* If no usable target was selected... */ + if(!target) { + /* Here we distinguish between three states: generate a new + * target query, just wait, or quit (with a SERVFAIL). + * We have the following information: number of active + * target queries, number of active current queries, + * the presence of missing targets at this delegation + * point, and the given query target policy. */ + + /* Check for the wait condition. If this is true, then + * an action must be taken. */ + if(iq->num_target_queries==0 && iq->num_current_queries==0) { + /* 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) { + verbose(VERB_ALGO, "querying for next " + "missing target"); + if(!query_for_targets(qstate, iq, ie, id, + 1, &iq->num_target_queries)) { + return error_response(qstate, iq, + LDNS_RCODE_SERVFAIL); + } + } + /* Since a target query might have been made, we + * need to check again. */ + if(iq->num_target_queries == 0) { + verbose(VERB_ALGO, "out of query targets -- " + "returning SERVFAIL"); + /* fail -- no more targets, no more hope + * of targets, no hope of a response. */ + return error_response(qstate, iq, + LDNS_RCODE_SERVFAIL); + } + } + + /* 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); + else if(iq->num_target_queries>0) + verbose(VERB_ALGO, "no current targets -- waiting " + "for %d targets to resolve.", + iq->num_target_queries); + else verbose(VERB_ALGO, "no current targets -- waiting " + "for %d outstanding queries to respond.", + iq->num_current_queries); + return false; + } + + /* We have a valid target. */ + log_nametypeclass("sending query:", qstate->qinfo.qname, + qstate->qinfo.qtype, qstate->qinfo.qclass); + log_addr("sending to target:", &target->addr, target->addrlen); + outq = (*qstate->env->send_query)( + qstate->qinfo.qname, qstate->qinfo.qname_len, + qstate->qinfo.qtype, qstate->qinfo.qclass, + qstate->query_flags, 1, &target->addr, target->addrlen, + qstate); + if(!outq) { + log_err("out of memory sending query to auth server"); + return error_response(qstate, iq, LDNS_RCODE_SERVFAIL); + } + outbound_list_insert(&iq->outlist, outq); + iq->num_current_queries++; + return 0; } +#if 0 /** TODO */ static int processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, @@ -893,10 +1128,10 @@ iter_handle(struct module_qstate* qstate, struct iter_qstate* iq, case INIT_REQUEST_3_STATE: cont = processInitRequest3(qstate, iq); break; -#if 0 case QUERYTARGETS_STATE: cont = processQueryTargets(qstate, iq, ie, id); break; +#if 0 case QUERY_RESP_STATE: cont = processQueryResponse(qstate, iq, ie, id); break; diff --git a/iterator/iterator.h b/iterator/iterator.h index f71c4054e..f29ac480a 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -50,6 +50,10 @@ struct iter_prep_list; /** max number of query restarts. Determines max number of CNAME chain. */ #define MAX_RESTART_COUNT 8 +/** max number of referrals. Makes sure resolver does not run away */ +#define MAX_REFERRAL_COUNT 30 +/** how nice is a server without further information, in msec */ +#define UNKNOWN_SERVER_NICENESS 3000 /** * Global state for the iterator. @@ -186,11 +190,6 @@ struct iter_qstate { */ struct delegpt* dp; - /** - * Current address target. - */ - struct delegpt_addr* current_target; - /** Current delegation message - returned for non-RD queries */ struct dns_msg* deleg_msg; diff --git a/services/cache/infra.c b/services/cache/infra.c index 5e9c861bc..8a4a723d1 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -464,3 +464,30 @@ infra_edns_update(struct infra_cache* infra, else { lock_rw_unlock(&e->lock); } return 1; } + +int +infra_get_lame_rtt(struct infra_cache* infra, + struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* name, size_t namelen, int* lame, int* rtt, time_t timenow) +{ + struct infra_host_data* host; + struct lruhash_entry* e = infra_lookup_host_nottl(infra, addr, + addrlen, 0); + if(!e) + return 0; + host = (struct infra_host_data*)e->data; + *rtt = rtt_timeout(&host->rtt); + /* check lameness first, if so, ttl on host does not matter anymore */ + if(infra_lookup_lame(host, name, namelen, timenow)) { + lock_rw_unlock(&e->lock); + *lame = 1; + return 1; + } + *lame = 0; + if(timenow > host->ttl) { + lock_rw_unlock(&e->lock); + return 0; + } + lock_rw_unlock(&e->lock); + return 1; +} diff --git a/services/cache/infra.h b/services/cache/infra.h index 6fd29c613..a008b2057 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -218,4 +218,20 @@ int infra_edns_update(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, int edns_version, time_t timenow); +/** + * Get Lameness information and average RTT if host is in the cache. + * @param infra: infrastructure cache. + * @param addr: host address. + * @param addrlen: length of addr. + * @param name: zone name. + * @param namelen: zone name length. + * @param lame: if function returns true, this returns lameness of the zone. + * @param rtt: if function returns true, this returns avg rtt of the server. + * @param timenow: what time it is now. + * @return if found in cache, or false if not (or TTL bad). + */ +int infra_get_lame_rtt(struct infra_cache* infra, + struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* name, size_t namelen, int* lame, int* rtt, time_t timenow); + #endif /* SERVICES_CACHE_INFRA_H */ diff --git a/services/outside_network.c b/services/outside_network.c index 25b318385..22b764744 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -659,20 +659,6 @@ new_pending(struct outside_network* outnet, ldns_buffer* packet, return pend; } -/** - * Checkout address family. - * @param addr: the sockaddr to examine. - * return: true if sockaddr is ip6. - */ -static int -addr_is_ip6(struct sockaddr_storage* addr) -{ - short family = *(short*)addr; - if(family == AF_INET6) - return 1; - else return 0; -} - /** * Select outgoing comm point for a query. Fills in c. * @param outnet: network structure that has arrays of ports to choose from. diff --git a/util/data/dname.c b/util/data/dname.c index 89d1247dd..c3542cbbc 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -553,3 +553,21 @@ dname_strict_subdomain_c(uint8_t* d1, uint8_t* d2) return dname_strict_subdomain(d1, dname_count_labels(d1), d2, dname_count_labels(d2)); } + +int +dname_subdomain_c(uint8_t* d1, uint8_t* d2) +{ + int m; + /* check subdomain: d1: www.example.com. and d2: example.com. */ + /* or d1: example.com. and d2: example.com. */ + int labs1 = dname_count_labels(d1); + int labs2 = dname_count_labels(d2); + if(labs2 > labs1) + return 0; + if(dname_lab_cmp(d1, labs1, d2, labs2, &m) < 0) { + /* must have been example.com , www.example.com - wrong */ + /* or otherwise different dnames */ + return 0; + } + return (m == labs2); +} diff --git a/util/data/dname.h b/util/data/dname.h index 7d35cb0bd..d5a8ccaaf 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -181,6 +181,14 @@ int dname_strict_subdomain(uint8_t* d1, int labs1, uint8_t* d2, int labs2); */ int dname_strict_subdomain_c(uint8_t* d1, uint8_t* d2); +/** + * Counts labels. Tests is d1 is a subdomain of d2. + * @param d1: domain name, uncompressed wireformat + * @param d2: domain name, uncompressed wireformat + * @return true if d1 is a subdomain of d2. + */ +int dname_subdomain_c(uint8_t* d1, uint8_t* d2); + /** * Debug helper. Print wireformat dname to output. * @param out: like stdout or a file. diff --git a/util/net_help.c b/util/net_help.c index 8f22c5d2b..d0f1790f9 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -189,3 +189,12 @@ log_nametypeclass(const char* str, uint8_t* name, uint16_t type, ldns_lookup_by_id(ldns_rr_classes, (int)dclass)? ldns_lookup_by_id(ldns_rr_classes, (int)dclass)->name:"??"); } + +int +addr_is_ip6(struct sockaddr_storage* addr) +{ + short family = *(short*)addr; + if(family == AF_INET6) + return 1; + else return 0; +} diff --git a/util/net_help.h b/util/net_help.h index ced5180ed..af37ceabd 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -148,4 +148,11 @@ int ipstrtoaddr(const char* ip, int port, struct sockaddr_storage* addr, void log_nametypeclass(const char* str, uint8_t* name, uint16_t type, uint16_t dclass); +/** + * Checkout address family. + * @param addr: the sockaddr to examine. + * return: true if sockaddr is ip6. + */ +int addr_is_ip6(struct sockaddr_storage* addr); + #endif /* NET_HELP_H */