diff options
| author | Cy Schubert <cy@FreeBSD.org> | 2024-08-16 16:41:16 +0000 |
|---|---|---|
| committer | Cy Schubert <cy@FreeBSD.org> | 2024-08-16 16:41:16 +0000 |
| commit | 96ef46e5cff01648c80c09c4364d10bc6f58119d (patch) | |
| tree | a759010619ad11a8eaaaed7269bb06a9dfc2fa16 /iterator | |
| parent | c2a80056864d6eda0398fd127dc0ae515b39752b (diff) | |
Diffstat (limited to 'iterator')
| -rw-r--r-- | iterator/iter_scrub.c | 82 | ||||
| -rw-r--r-- | iterator/iter_utils.c | 3 | ||||
| -rw-r--r-- | iterator/iterator.c | 124 | ||||
| -rw-r--r-- | iterator/iterator.h | 6 |
4 files changed, 180 insertions, 35 deletions
diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 48867e50c557..f038ad69af0e 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -367,6 +367,47 @@ type_allowed_in_additional_section(uint16_t tp) return 0; } +/** Shorten RRset */ +static void +shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count) +{ + /* The too large NS RRset is shortened. This is so that too large + * content does not overwhelm the cache. It may make the rrset + * bogus if it was signed, and then the domain is not resolved any + * more, that is okay, the NS RRset was too large. During a referral + * it can be shortened and then the first part of the list could + * be used to resolve. The scrub continues to disallow glue for the + * removed nameserver RRs and removes that too. Because the glue + * is not marked as okay, since the RRs have been removed here. */ + int i; + struct rr_parse* rr = rrset->rr_first, *prev = NULL; + if(!rr) + return; + for(i=0; i<count; i++) { + prev = rr; + rr = rr->next; + if(!rr) + return; /* The RRset is already short. */ + } + if(verbosity >= VERB_QUERY + && rrset->dname_len <= LDNS_MAX_DOMAINLEN) { + uint8_t buf[LDNS_MAX_DOMAINLEN+1]; + dname_pkt_copy(pkt, buf, rrset->dname); + log_nametypeclass(VERB_QUERY, "normalize: shorten RRset:", buf, + rrset->type, ntohs(rrset->rrset_class)); + } + /* remove further rrs */ + rrset->rr_last = prev; + rrset->rr_count = count; + while(rr) { + rrset->size -= rr->size; + rr = rr->next; + } + if(rrset->rr_last) + rrset->rr_last->next = NULL; + else rrset->rr_first = NULL; +} + /** * This routine normalizes a response. This includes removing "irrelevant" * records from the answer and additional sections and (re)synthesizing @@ -387,6 +428,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, uint8_t* sname = qinfo->qname; size_t snamelen = qinfo->qname_len; struct rrset_parse* rrset, *prev, *nsset=NULL; + int cname_length = 0; /* number of CNAMEs, or DNAMEs */ if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN) @@ -401,6 +443,16 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, prev = NULL; rrset = msg->rrset_first; while(rrset && rrset->section == LDNS_SECTION_ANSWER) { + if(cname_length > 11 /* env->cfg.iter_scrub_cname */) { + /* Too many CNAMEs, or DNAMEs, from the authority + * server, scrub down the length to something + * shorter. This deletes everything after the limit + * is reached. The iterator is going to look up + * the content one by one anyway. */ + remove_rrset("normalize: removing because too many cnames:", + pkt, msg, prev, &rrset); + continue; + } if(rrset->type == LDNS_RR_TYPE_DNAME && pkt_strict_sub(pkt, sname, rrset->dname)) { /* check if next rrset is correct CNAME. else, @@ -420,6 +472,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, "too long"); return 0; } + cname_length++; if(nx && nx->type == LDNS_RR_TYPE_CNAME && dname_pkt_compare(pkt, sname, nx->dname) == 0) { /* check next cname */ @@ -460,6 +513,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, if(rrset->type == LDNS_RR_TYPE_CNAME) { struct rrset_parse* nx = rrset->rrset_all_next; uint8_t* oldsname = sname; + cname_length++; /* see if the next one is a DNAME, if so, swap them */ if(nx && nx->section == LDNS_SECTION_ANSWER && nx->type == LDNS_RR_TYPE_DNAME && @@ -507,6 +561,10 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, LDNS_SECTION_ANSWER && dname_pkt_compare(pkt, oldsname, rrset->dname) == 0) { + if(rrset->type == LDNS_RR_TYPE_NS && + rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) { + shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */); + } prev = rrset; rrset = rrset->rrset_all_next; } @@ -522,6 +580,11 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, continue; } + if(rrset->type == LDNS_RR_TYPE_NS && + rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) { + shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */); + } + /* Mark the additional names from relevant rrset as OK. */ /* only for RRsets that match the query name, other ones * will be removed by sanitize, so no additional for them */ @@ -578,6 +641,25 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, "RRset:", pkt, msg, prev, &rrset); continue; } + if(rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) { + /* If this is not a referral, and the NS RRset + * is signed, then remove it entirely, so + * that when it becomes bogus it does not + * make the message that is otherwise fine + * into a bogus message. */ + if(!(msg->an_rrsets == 0 && + FLAGS_GET_RCODE(msg->flags) == + LDNS_RCODE_NOERROR && + !soa_in_auth(msg) && + !(msg->flags & BIT_AA)) && + rrset->rrsig_count != 0) { + remove_rrset("normalize: removing too large NS " + "RRset:", pkt, msg, prev, &rrset); + continue; + } else { + shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */); + } + } } /* if this is type DS and we query for type DS we just got * a referral answer for our type DS query, fix packet */ diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index f291178d2319..1b4f5f6ebb4f 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -279,9 +279,10 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env, name, namelen, qtype, &lame, &dnsseclame, &reclame, &rtt, now)) { log_addr(VERB_ALGO, "servselect", &a->addr, a->addrlen); - verbose(VERB_ALGO, " rtt=%d%s%s%s%s", rtt, + verbose(VERB_ALGO, " rtt=%d%s%s%s%s%s", rtt, lame?" LAME":"", dnsseclame?" DNSSEC_LAME":"", + a->dnsseclame?" ADDR_DNSSEC_LAME":"", reclame?" REC_LAME":"", a->lame?" ADDR_LAME":""); if(lame) diff --git a/iterator/iterator.c b/iterator/iterator.c index 5732a414857e..228f5dfaef30 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -760,6 +760,14 @@ target_count_increase_nx(struct iter_qstate* iq, int num) iq->target_count[TARGET_COUNT_NX] += num; } +static void +target_count_increase_global_quota(struct iter_qstate* iq, int num) +{ + target_count_create(iq); + if(iq->target_count) + iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] += num; +} + /** * Generate a subrequest. * Generate a local request event. Local events are tied to this module, and @@ -1378,7 +1386,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, "restarts (eg. indirections)"); if(iq->qchase.qname) errinf_dname(qstate, "stop at", iq->qchase.qname); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* We enforce a maximum recursion/dependency depth -- in general, @@ -1560,6 +1568,11 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, errinf(qstate, "malloc failure for forward zone"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } + if(!cache_fill_missing(qstate->env, iq->qchase.qclass, + qstate->region, iq->dp)) { + errinf(qstate, "malloc failure, copy extra info into delegation point"); + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + } if((qstate->query_flags&BIT_RD)==0) { /* If the server accepts RD=0 queries and forwards * with RD=1, then if the server is listed as an NS @@ -1654,7 +1667,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, if(!iq->dp) { log_err("internal error: no hints dp"); errinf(qstate, "no hints for this class"); - return error_response(qstate, id, + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->dp = delegpt_copy(iq->dp, qstate->region); @@ -1974,7 +1987,8 @@ generate_target_query(struct module_qstate* qstate, struct iter_qstate* iq, * 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. + * @return 0 on success, nonzero on error. 1 means temporary failure and + * 2 means the failure can be cached. */ static int query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, @@ -1997,13 +2011,13 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, else toget = maxtargets; if(toget == 0) { *num = 0; - return 1; + return 0; } /* now that we are sure that a target query is going to be made, * check the limits. */ if(iq->depth == ie->max_dependency_depth) - return 0; + return 1; if(iq->depth > 0 && iq->target_count && iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) { char s[LDNS_MAX_DOMAINLEN+1]; @@ -2011,7 +2025,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d", s, iq->target_count[TARGET_COUNT_QUERIES]); - return 0; + return 2; } if(iq->dp_target_count > MAX_DP_TARGET_COUNT) { char s[LDNS_MAX_DOMAINLEN+1]; @@ -2019,7 +2033,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d to a single delegation point", s, iq->dp_target_count); - return 0; + return 2; } /* select 'toget' items from the total of 'missing' items */ @@ -2048,7 +2062,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, *num = query_count; if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; - return 0; + return 1; } query_count++; /* If the mesh query list is full, exit the loop here. @@ -2057,8 +2071,16 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, * increase, because the spawned state uses cpu and a * socket while this state waits for that spawned * state. Next time we can look up further targets */ - if(mesh_jostle_exceeded(qstate->env->mesh)) + if(mesh_jostle_exceeded(qstate->env->mesh)) { + /* If no ip4 query is possible, that makes + * this ns resolved. */ + if(!((ie->supports_ipv4 || ie->use_nat64) && + ((ns->lame && !ns->done_pside4) || + (!ns->lame && !ns->got4)))) { + ns->resolved = 1; + } break; + } } /* Send the A request. */ if((ie->supports_ipv4 || ie->use_nat64) && @@ -2070,12 +2092,17 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, *num = query_count; if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; - return 0; + return 1; } query_count++; /* If the mesh query list is full, exit the loop. */ - if(mesh_jostle_exceeded(qstate->env->mesh)) + if(mesh_jostle_exceeded(qstate->env->mesh)) { + /* With the ip6 query already checked for, + * this makes the ns resolved. It is no longer + * a missing target. */ + ns->resolved = 1; break; + } } /* mark this target as in progress. */ @@ -2089,7 +2116,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; - return 1; + return 0; } /** @@ -2180,12 +2207,14 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, } /* query for an extra name added by the parent-NS record */ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) { - int qs = 0; + int qs = 0, ret; verbose(VERB_ALGO, "try parent-side target name"); - if(!query_for_targets(qstate, iq, ie, id, 1, &qs)) { + if((ret=query_for_targets(qstate, iq, ie, id, 1, &qs))!=0) { errinf(qstate, "could not fetch nameserver"); errinf_dname(qstate, "at zone", iq->dp->name); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + if(ret == 1) + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += qs; target_count_increase(iq, qs); @@ -2414,13 +2443,13 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, verbose(VERB_QUERY, "request has exceeded the maximum " "number of referrrals with %d", iq->referral_count); errinf(qstate, "exceeded the maximum of referrals"); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->sent_count > ie->max_sent_count) { verbose(VERB_QUERY, "request has exceeded the maximum " "number of sends with %d", iq->sent_count); errinf(qstate, "exceeded the maximum number of sends"); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* Check if we reached MAX_TARGET_NX limit without a fallback activation. */ @@ -2450,7 +2479,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, "already present for the delegation point, no " "fallback possible"); errinf(qstate, "exceeded the maximum nameserver nxdomains"); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } verbose(VERB_ALGO, "initiating parent-side fallback for " "nxdomain nameserver lookups"); @@ -2493,7 +2522,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, "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); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(!iq->dp->has_parent_side_NS) { @@ -2707,7 +2736,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, verbose(VERB_ALGO, "auth zone lookup failed, no fallback," " servfail"); errinf(qstate, "auth zone lookup failed, fallback is off"); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->dp->auth_dp) { /* we wanted to fallback, but had no delegpt, only the @@ -2736,11 +2765,13 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, /* if in 0x20 fallback get as many targets as possible */ if(iq->caps_fallback) { - int extra = 0; + int extra = 0, ret; size_t naddr, nres, navail; - if(!query_for_targets(qstate, iq, ie, id, -1, &extra)) { + if((ret=query_for_targets(qstate, iq, ie, id, -1, &extra))!=0) { errinf(qstate, "could not fetch nameservers for 0x20 fallback"); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + if(ret == 1) + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += extra; target_count_increase(iq, extra); @@ -2883,14 +2914,17 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, * to distinguish between generating (a) new target * query, or failing. */ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) { - int qs = 0; + int qs = 0, ret; verbose(VERB_ALGO, "querying for next " "missing target"); - if(!query_for_targets(qstate, iq, ie, id, - 1, &qs)) { + if((ret=query_for_targets(qstate, iq, ie, id, + 1, &qs))!=0) { errinf(qstate, "could not fetch nameserver"); errinf_dname(qstate, "at zone", iq->dp->name); - return error_response(qstate, id, + if(ret == 1) + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(qs == 0 && @@ -2902,6 +2936,17 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, * so this is not a loop. */ return 1; } + if(qs == 0) { + /* There should be targets now, and + * if there are not, it should not + * wait for no targets. Stop it from + * waiting forever, or looping to + * here, as a safeguard. */ + errinf(qstate, "could not generate nameserver lookups"); + errinf_dname(qstate, "at zone", iq->dp->name); + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + } iq->num_target_queries += qs; target_count_increase(iq, qs); } @@ -2976,6 +3021,17 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, } } + target_count_increase_global_quota(iq, 1); + if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] + > MAX_GLOBAL_QUOTA) { + char s[LDNS_MAX_DOMAINLEN+1]; + dname_str(qstate->qinfo.qname, s); + verbose(VERB_QUERY, "request %s has exceeded the maximum " + "global quota on number of upstream queries %d", s, + iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); + } + /* Do not check ratelimit for forwarding queries or if we already got a * pass. */ sq_check_ratelimit = (!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok); @@ -3025,7 +3081,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, qstate->was_ratelimited = 1; errinf_dname(qstate, "exceeded ratelimit for zone", iq->dp->name); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } log_addr(VERB_QUERY, "error sending query to auth server", &real_addr, real_addrlen); @@ -3247,7 +3303,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iter_scrub_nxdomain(iq->response); return final_state(iq); } - return error_response(qstate, id, + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* Best effort qname-minimisation. @@ -3582,7 +3638,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, " fallback possible, servfail"); errinf_dname(qstate, "response is bad, no fallback, " "for auth zone", iq->dp->name); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } verbose(VERB_ALGO, "auth zone response was bad, " "fallback enabled"); @@ -3990,7 +4046,7 @@ processCollectClass(struct module_qstate* qstate, int id) if(iq->num_current_queries == 0) { verbose(VERB_ALGO, "No root hints or fwds, giving up " "on qclass ANY"); - return error_response(qstate, id, LDNS_RCODE_REFUSED); + return error_response_cache(qstate, id, LDNS_RCODE_REFUSED); } /* return false, wait for queries to return */ } @@ -4357,7 +4413,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq, "getting different replies, failed"); outbound_list_remove(&iq->outlist, outbound); errinf(qstate, "0x20 failed, then got different replies in fallback"); - (void)error_response(qstate, id, + (void)error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); return; } @@ -4457,8 +4513,8 @@ iter_get_mem(struct module_env* env, int id) */ static struct module_func_block iter_block = { "iterator", - &iter_init, &iter_deinit, &iter_operate, &iter_inform_super, - &iter_clear, &iter_get_mem + NULL, NULL, &iter_init, &iter_deinit, &iter_operate, + &iter_inform_super, &iter_clear, &iter_get_mem }; struct module_func_block* diff --git a/iterator/iterator.h b/iterator/iterator.h index e253f3f7e2bd..70b11df7ebcf 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -55,6 +55,9 @@ struct rbtree_type; /** max number of targets spawned for a query and its subqueries */ #define MAX_TARGET_COUNT 64 +/** max number of upstream queries for a query and its subqueries, it is + * never reset. */ +#define MAX_GLOBAL_QUOTA 128 /** max number of target lookups per qstate, per delegation point */ #define MAX_DP_TARGET_COUNT 16 /** max number of nxdomains allowed for target lookups for a query and @@ -248,6 +251,9 @@ enum target_count_variables { TARGET_COUNT_QUERIES, /** Number of nxdomain responses encountered. */ TARGET_COUNT_NX, + /** Global quota on number of queries to upstream servers per + * client request, that is never reset. */ + TARGET_COUNT_GLOBAL_QUOTA, /** This should stay last here, it is used for the allocation */ TARGET_COUNT_MAX, |
