diff options
author | Erik de Castro Lopo <erikd@mega-nerd.com> | 2017-06-16 20:16:05 +1000 |
---|---|---|
committer | Erik de Castro Lopo <erikd@mega-nerd.com> | 2017-06-17 23:04:00 +1000 |
commit | a85b5759f34c0c4110a479a8b5fa606f15ed9b23 (patch) | |
tree | 518cb8346249a42fd2aa8a78c09c3631e14db6aa /external/unbound/daemon/worker.c | |
parent | Merge pull request #2059 (diff) | |
download | monero-a85b5759f34c0c4110a479a8b5fa606f15ed9b23.tar.xz |
Upgrade unbound library
These files were pulled from the 1.6.3 release tarball.
This new version builds against OpenSSL version 1.1 which will be
the default in the new Debian Stable which is due to be released
RealSoonNow (tm).
Diffstat (limited to 'external/unbound/daemon/worker.c')
-rw-r--r-- | external/unbound/daemon/worker.c | 672 |
1 files changed, 536 insertions, 136 deletions
diff --git a/external/unbound/daemon/worker.c b/external/unbound/daemon/worker.c index c90a65998..b1cc974aa 100644 --- a/external/unbound/daemon/worker.c +++ b/external/unbound/daemon/worker.c @@ -69,9 +69,13 @@ #include "iterator/iter_hints.h" #include "validator/autotrust.h" #include "validator/val_anchor.h" +#include "respip/respip.h" #include "libunbound/context.h" #include "libunbound/libworker.h" #include "sldns/sbuffer.h" +#include "sldns/wire2str.h" +#include "util/shm_side/shm_main.h" +#include "dnscrypt/dnscrypt.h" #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> @@ -101,61 +105,21 @@ */ #define PREFETCH_EXPIRY_ADD 60 -#ifdef UNBOUND_ALLOC_STATS -/** measure memory leakage */ -static void -debug_memleak(size_t accounted, size_t heap, - size_t total_alloc, size_t total_free) -{ - static int init = 0; - static size_t base_heap, base_accounted, base_alloc, base_free; - size_t base_af, cur_af, grow_af, grow_acc; - if(!init) { - init = 1; - base_heap = heap; - base_accounted = accounted; - base_alloc = total_alloc; - base_free = total_free; - } - base_af = base_alloc - base_free; - cur_af = total_alloc - total_free; - grow_af = cur_af - base_af; - grow_acc = accounted - base_accounted; - log_info("Leakage: %d leaked. growth: %u use, %u acc, %u heap", - (int)(grow_af - grow_acc), (unsigned)grow_af, - (unsigned)grow_acc, (unsigned)(heap - base_heap)); -} - -/** give debug heap size indication */ -static void -debug_total_mem(size_t calctotal) -{ -#ifdef HAVE_SBRK - extern void* unbound_start_brk; - extern size_t unbound_mem_alloc, unbound_mem_freed; - void* cur = sbrk(0); - int total = cur-unbound_start_brk; - log_info("Total heap memory estimate: %u total-alloc: %u " - "total-free: %u", (unsigned)total, - (unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed); - debug_memleak(calctotal, (size_t)total, - unbound_mem_alloc, unbound_mem_freed); -#else - (void)calctotal; -#endif /* HAVE_SBRK */ -} -#endif /* UNBOUND_ALLOC_STATS */ - /** Report on memory usage by this thread and global */ static void worker_mem_report(struct worker* ATTR_UNUSED(worker), struct serviced_query* ATTR_UNUSED(cur_serv)) { #ifdef UNBOUND_ALLOC_STATS + /* measure memory leakage */ + extern size_t unbound_mem_alloc, unbound_mem_freed; /* debug func in validator module */ size_t total, front, back, mesh, msg, rrset, infra, ac, superac; size_t me, iter, val, anch; int i; +#ifdef CLIENT_SUBNET + size_t subnet = 0; +#endif /* CLIENT_SUBNET */ if(verbosity < VERB_ALGO) return; front = listen_get_mem(worker->front); @@ -175,6 +139,12 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker), if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) val += (*worker->env.mesh->mods.mod[i]->get_mem) (&worker->env, i); +#ifdef CLIENT_SUBNET + else if(strcmp(worker->env.mesh->mods.mod[i]->name, + "subnet")==0) + subnet += (*worker->env.mesh->mods.mod[i]->get_mem) + (&worker->env, i); +#endif /* CLIENT_SUBNET */ else iter += (*worker->env.mesh->mods.mod[i]->get_mem) (&worker->env, i); } @@ -192,6 +162,17 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker), me += serviced_get_mem(cur_serv); } total = front+back+mesh+msg+rrset+infra+iter+val+ac+superac+me; +#ifdef CLIENT_SUBNET + total += subnet; + log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u " + "rrset=%u infra=%u iter=%u val=%u subnet=%u anchors=%u " + "alloccache=%u globalalloccache=%u me=%u", + (unsigned)total, (unsigned)front, (unsigned)back, + (unsigned)mesh, (unsigned)msg, (unsigned)rrset, (unsigned)infra, + (unsigned)iter, (unsigned)val, + (unsigned)subnet, (unsigned)anch, (unsigned)ac, + (unsigned)superac, (unsigned)me); +#else /* no CLIENT_SUBNET */ log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u " "rrset=%u infra=%u iter=%u val=%u anchors=%u " "alloccache=%u globalalloccache=%u me=%u", @@ -199,9 +180,15 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker), (unsigned)mesh, (unsigned)msg, (unsigned)rrset, (unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch, (unsigned)ac, (unsigned)superac, (unsigned)me); - debug_total_mem(total); +#endif /* CLIENT_SUBNET */ + log_info("Total heap memory estimate: %u total-alloc: %u " + "total-free: %u", (unsigned)total, + (unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed); #else /* no UNBOUND_ALLOC_STATS */ size_t val = 0; +#ifdef CLIENT_SUBNET + size_t subnet = 0; +#endif /* CLIENT_SUBNET */ int i; if(verbosity < VERB_QUERY) return; @@ -211,12 +198,27 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker), if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) val += (*worker->env.mesh->mods.mod[i]->get_mem) (&worker->env, i); +#ifdef CLIENT_SUBNET + else if(strcmp(worker->env.mesh->mods.mod[i]->name, + "subnet")==0) + subnet += (*worker->env.mesh->mods.mod[i]->get_mem) + (&worker->env, i); +#endif /* CLIENT_SUBNET */ } +#ifdef CLIENT_SUBNET + verbose(VERB_QUERY, "cache memory msg=%u rrset=%u infra=%u val=%u " + "subnet=%u", + (unsigned)slabhash_get_mem(worker->env.msg_cache), + (unsigned)slabhash_get_mem(&worker->env.rrset_cache->table), + (unsigned)infra_get_mem(worker->env.infra_cache), + (unsigned)val, (unsigned)subnet); +#else /* no CLIENT_SUBNET */ verbose(VERB_QUERY, "cache memory msg=%u rrset=%u infra=%u val=%u", (unsigned)slabhash_get_mem(worker->env.msg_cache), (unsigned)slabhash_get_mem(&worker->env.rrset_cache->table), (unsigned)infra_get_mem(worker->env.infra_cache), (unsigned)val); +#endif /* CLIENT_SUBNET */ #endif /* UNBOUND_ALLOC_STATS */ } @@ -483,15 +485,17 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, qinfo->qname_len, qinfo->qtype, qinfo->qclass, worker->scratchpad, &msg, timenow); if(!dp) { /* no delegation, need to reprime */ - regional_free_all(worker->scratchpad); return 0; } + /* In case we have a local alias, copy it into the delegation message. + * Shallow copy should be fine, as we'll be done with msg in this + * function. */ + msg->qinfo.local_alias = qinfo->local_alias; if(must_validate) { switch(check_delegation_secure(msg->rep)) { case sec_status_unchecked: /* some rrsets have not been verified yet, go and * let validator do that */ - regional_free_all(worker->scratchpad); return 0; case sec_status_bogus: /* some rrsets are bogus, reply servfail */ @@ -499,9 +503,11 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, + msg->rep, LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + return 0; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, &msg->qinfo, id, flags, edns); - regional_free_all(worker->scratchpad); if(worker->stats.extended) { worker->stats.ans_bogus++; worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL]++; @@ -527,14 +533,19 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; + if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep, + (int)(flags&LDNS_RCODE_MASK), edns, worker->scratchpad)) + return 0; msg->rep->flags |= BIT_QR|BIT_RA; if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags, repinfo->c->buffer, 0, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + edns->opt_list = NULL; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, &msg->qinfo, id, flags, edns); } - regional_free_all(worker->scratchpad); if(worker->stats.extended) { if(secure) worker->stats.ans_secure++; server_stats_insrcode(&worker->stats, repinfo->c->buffer); @@ -542,28 +553,93 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, return 1; } -/** answer query from the cache */ +/** Apply, if applicable, a response IP action to a cached answer. + * If the answer is rewritten as a result of an action, '*encode_repp' will + * point to the reply info containing the modified answer. '*encode_repp' will + * be intact otherwise. + * It returns 1 on success, 0 otherwise. */ +static int +apply_respip_action(struct worker* worker, const struct query_info* qinfo, + struct respip_client_info* cinfo, struct reply_info* rep, + struct comm_reply* repinfo, struct ub_packed_rrset_key** alias_rrset, + struct reply_info** encode_repp) +{ + struct respip_action_info actinfo = {respip_none, NULL}; + + if(qinfo->qtype != LDNS_RR_TYPE_A && + qinfo->qtype != LDNS_RR_TYPE_AAAA && + qinfo->qtype != LDNS_RR_TYPE_ANY) + return 1; + + if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo, + alias_rrset, 0, worker->scratchpad)) + return 0; + + /* xxx_deny actions mean dropping the reply, unless the original reply + * was redirected to response-ip data. */ + if((actinfo.action == respip_deny || + actinfo.action == respip_inform_deny) && + *encode_repp == rep) + *encode_repp = NULL; + + /* If address info is returned, it means the action should be an + * 'inform' variant and the information should be logged. */ + if(actinfo.addrinfo) { + respip_inform_print(actinfo.addrinfo, qinfo->qname, + qinfo->qtype, qinfo->qclass, qinfo->local_alias, + repinfo); + } + + return 1; +} + +/** answer query from the cache. + * Normally, the answer message will be built in repinfo->c->buffer; if the + * answer is supposed to be suppressed or the answer is supposed to be an + * incomplete CNAME chain, the buffer is explicitly cleared to signal the + * caller as such. In the latter case *partial_rep will point to the incomplete + * reply, and this function is (possibly) supposed to be called again with that + * *partial_rep value to complete the chain. In addition, if the query should + * be completely dropped, '*need_drop' will be set to 1. */ static int answer_from_cache(struct worker* worker, struct query_info* qinfo, + struct respip_client_info* cinfo, int* need_drop, + struct ub_packed_rrset_key** alias_rrset, + struct reply_info** partial_repp, struct reply_info* rep, uint16_t id, uint16_t flags, struct comm_reply* repinfo, struct edns_data* edns) { time_t timenow = *worker->env.now; uint16_t udpsize = edns->udp_size; + struct reply_info* encode_rep = rep; + struct reply_info* partial_rep = *partial_repp; int secure; int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd) && worker->env.need_to_validate; - /* see if it is possible */ - if(rep->ttl < timenow) { - /* the rrsets may have been updated in the meantime. - * we will refetch the message format from the - * authoritative server - */ - return 0; + *partial_repp = NULL; /* avoid accidental further pass */ + if(worker->env.cfg->serve_expired) { + /* always lock rrsets, rep->ttl is ignored */ + if(!rrset_array_lock(rep->ref, rep->rrset_count, 0)) + return 0; + /* below, rrsets with ttl before timenow become TTL 0 in + * the response */ + /* This response was served with zero TTL */ + if (timenow >= rep->ttl) { + worker->stats.zero_ttl_responses++; + } + } else { + /* see if it is possible */ + if(rep->ttl < timenow) { + /* the rrsets may have been updated in the meantime. + * we will refetch the message format from the + * authoritative server + */ + return 0; + } + if(!rrset_array_lock(rep->ref, rep->rrset_count, timenow)) + return 0; + /* locked and ids and ttls are OK. */ } - if(!rrset_array_lock(rep->ref, rep->rrset_count, timenow)) - return 0; - /* locked and ids and ttls are OK. */ /* check CNAME chain (if any) */ if(rep->an_numrrsets > 0 && (rep->rrsets[0]->rk.type == htons(LDNS_RR_TYPE_CNAME) || rep->rrsets[0]->rk.type == @@ -574,7 +650,6 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, bail_out: rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad, rep->ref, rep->rrset_count); - regional_free_all(worker->scratchpad); return 0; } } @@ -585,11 +660,13 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + goto bail_out; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, id, flags, edns); rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad, rep->ref, rep->rrset_count); - regional_free_all(worker->scratchpad); if(worker->stats.extended) { worker->stats.ans_bogus ++; worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL] ++; @@ -616,9 +693,41 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!reply_info_answer_encode(qinfo, rep, id, flags, + if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, rep, + (int)(flags&LDNS_RCODE_MASK), edns, worker->scratchpad)) + goto bail_out; + *alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */ + if(worker->daemon->use_response_ip && !partial_rep && + !apply_respip_action(worker, qinfo, cinfo, rep, repinfo, alias_rrset, + &encode_rep)) { + goto bail_out; + } else if(partial_rep && + !respip_merge_cname(partial_rep, qinfo, rep, cinfo, + must_validate, &encode_rep, worker->scratchpad)) { + goto bail_out; + } + if(encode_rep != rep) + secure = 0; /* if rewritten, it can't be considered "secure" */ + if(!encode_rep || *alias_rrset) { + sldns_buffer_clear(repinfo->c->buffer); + sldns_buffer_flip(repinfo->c->buffer); + if(!encode_rep) + *need_drop = 1; + else { + /* If a partial CNAME chain is found, we first need to + * make a copy of the reply in the scratchpad so we + * can release the locks and lookup the cache again. */ + *partial_repp = reply_info_copy(encode_rep, NULL, + worker->scratchpad); + if(!*partial_repp) + goto bail_out; + } + } else if(!reply_info_answer_encode(qinfo, encode_rep, id, flags, repinfo->c->buffer, timenow, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + edns->opt_list = NULL; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, id, flags, edns); } @@ -626,7 +735,6 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, * is bad while holding locks. */ rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad, rep->ref, rep->rrset_count); - regional_free_all(worker->scratchpad); if(worker->stats.extended) { if(secure) worker->stats.ans_secure++; server_stats_insrcode(&worker->stats, repinfo->c->buffer); @@ -635,14 +743,18 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, return 1; } -/** Reply to client and perform prefetch to keep cache up to date */ +/** Reply to client and perform prefetch to keep cache up to date. + * If the buffer for the reply is empty, it indicates that only prefetch is + * necessary and the reply should be suppressed (because it's dropped or + * being deferred). */ static void reply_and_prefetch(struct worker* worker, struct query_info* qinfo, uint16_t flags, struct comm_reply* repinfo, time_t leeway) { /* first send answer to client to keep its latency * as small as a cachereply */ - comm_point_send_reply(repinfo); + if(sldns_buffer_limit(repinfo->c->buffer) != 0) + comm_point_send_reply(repinfo); server_stats_prefetch(&worker->stats, worker); /* create the prefetch in the mesh as a normal lookup without @@ -657,41 +769,115 @@ reply_and_prefetch(struct worker* worker, struct query_info* qinfo, * Fill CH class answer into buffer. Keeps query. * @param pkt: buffer * @param str: string to put into text record (<255). + * array of strings, every string becomes a text record. + * @param num: number of strings in array. * @param edns: edns reply information. + * @param worker: worker with scratch region. */ static void -chaos_replystr(sldns_buffer* pkt, const char* str, struct edns_data* edns) +chaos_replystr(sldns_buffer* pkt, char** str, int num, struct edns_data* edns, + struct worker* worker) { - size_t len = strlen(str); + int i; unsigned int rd = LDNS_RD_WIRE(sldns_buffer_begin(pkt)); unsigned int cd = LDNS_CD_WIRE(sldns_buffer_begin(pkt)); - if(len>255) len=255; /* cap size of TXT record */ sldns_buffer_clear(pkt); sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip id */ sldns_buffer_write_u16(pkt, (uint16_t)(BIT_QR|BIT_RA)); if(rd) LDNS_RD_SET(sldns_buffer_begin(pkt)); if(cd) LDNS_CD_SET(sldns_buffer_begin(pkt)); sldns_buffer_write_u16(pkt, 1); /* qdcount */ - sldns_buffer_write_u16(pkt, 1); /* ancount */ + sldns_buffer_write_u16(pkt, (uint16_t)num); /* ancount */ sldns_buffer_write_u16(pkt, 0); /* nscount */ sldns_buffer_write_u16(pkt, 0); /* arcount */ (void)query_dname_len(pkt); /* skip qname */ sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip qtype */ sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip qclass */ - sldns_buffer_write_u16(pkt, 0xc00c); /* compr ptr to query */ - sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_TXT); - sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_CH); - sldns_buffer_write_u32(pkt, 0); /* TTL */ - sldns_buffer_write_u16(pkt, sizeof(uint8_t) + len); - sldns_buffer_write_u8(pkt, len); - sldns_buffer_write(pkt, str, len); + for(i=0; i<num; i++) { + size_t len = strlen(str[i]); + if(len>255) len=255; /* cap size of TXT record */ + sldns_buffer_write_u16(pkt, 0xc00c); /* compr ptr to query */ + sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_TXT); + sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_CH); + sldns_buffer_write_u32(pkt, 0); /* TTL */ + sldns_buffer_write_u16(pkt, sizeof(uint8_t) + len); + sldns_buffer_write_u8(pkt, len); + sldns_buffer_write(pkt, str[i], len); + } sldns_buffer_flip(pkt); edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->bits &= EDNS_DO; + if(!inplace_cb_reply_local_call(&worker->env, NULL, NULL, NULL, + LDNS_RCODE_NOERROR, edns, worker->scratchpad)) + edns->opt_list = NULL; attach_edns_record(pkt, edns); } +/** Reply with one string */ +static void +chaos_replyonestr(sldns_buffer* pkt, const char* str, struct edns_data* edns, + struct worker* worker) +{ + chaos_replystr(pkt, (char**)&str, 1, edns, worker); +} + +/** + * Create CH class trustanchor answer. + * @param pkt: buffer + * @param edns: edns reply information. + * @param w: worker with scratch region. + */ +static void +chaos_trustanchor(sldns_buffer* pkt, struct edns_data* edns, struct worker* w) +{ +#define TA_RESPONSE_MAX_TXT 16 /* max number of TXT records */ +#define TA_RESPONSE_MAX_TAGS 32 /* max number of tags printed per zone */ + char* str_array[TA_RESPONSE_MAX_TXT]; + uint16_t tags[TA_RESPONSE_MAX_TAGS]; + int num = 0; + struct trust_anchor* ta; + + if(!w->env.need_to_validate) { + /* no validator module, reply no trustanchors */ + chaos_replystr(pkt, NULL, 0, edns, w); + return; + } + + /* fill the string with contents */ + lock_basic_lock(&w->env.anchors->lock); + RBTREE_FOR(ta, struct trust_anchor*, w->env.anchors->tree) { + char* str; + size_t i, numtag, str_len = 255; + if(num == TA_RESPONSE_MAX_TXT) continue; + str = (char*)regional_alloc(w->scratchpad, str_len); + if(!str) continue; + lock_basic_lock(&ta->lock); + numtag = anchor_list_keytags(ta, tags, TA_RESPONSE_MAX_TAGS); + if(numtag == 0) { + /* empty, insecure point */ + lock_basic_unlock(&ta->lock); + continue; + } + str_array[num] = str; + num++; + + /* spool name of anchor */ + (void)sldns_wire2str_dname_buf(ta->name, ta->namelen, str, str_len); + str_len -= strlen(str); str += strlen(str); + /* spool tags */ + for(i=0; i<numtag; i++) { + snprintf(str, str_len, " %u", (unsigned)tags[i]); + str_len -= strlen(str); str += strlen(str); + } + lock_basic_unlock(&ta->lock); + } + lock_basic_unlock(&w->env.anchors->lock); + + chaos_replystr(pkt, str_array, num, edns, w); + regional_free_all(w->scratchpad); +} + /** * Answer CH class queries. * @param w: worker @@ -718,13 +904,13 @@ answer_chaos(struct worker* w, struct query_info* qinfo, char buf[MAXHOSTNAMELEN+1]; if (gethostname(buf, MAXHOSTNAMELEN) == 0) { buf[MAXHOSTNAMELEN] = 0; - chaos_replystr(pkt, buf, edns); + chaos_replyonestr(pkt, buf, edns, w); } else { log_err("gethostname: %s", strerror(errno)); - chaos_replystr(pkt, "no hostname", edns); + chaos_replyonestr(pkt, "no hostname", edns, w); } } - else chaos_replystr(pkt, cfg->identity, edns); + else chaos_replyonestr(pkt, cfg->identity, edns, w); return 1; } if(query_dname_compare(qinfo->qname, @@ -735,10 +921,19 @@ answer_chaos(struct worker* w, struct query_info* qinfo, if(cfg->hide_version) return 0; if(cfg->version==NULL || cfg->version[0]==0) - chaos_replystr(pkt, PACKAGE_STRING, edns); - else chaos_replystr(pkt, cfg->version, edns); + chaos_replyonestr(pkt, PACKAGE_STRING, edns, w); + else chaos_replyonestr(pkt, cfg->version, edns, w); return 1; } + if(query_dname_compare(qinfo->qname, + (uint8_t*)"\013trustanchor\007unbound") == 0) + { + if(cfg->hide_trustanchor) + return 0; + chaos_trustanchor(pkt, edns, w); + return 1; + } + return 0; } @@ -768,6 +963,8 @@ deny_refuse(struct comm_point* c, enum acl_access acl, LDNS_QR_SET(sldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), LDNS_RCODE_REFUSED); + sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE); + sldns_buffer_flip(c->buffer); return 1; } @@ -794,25 +991,75 @@ worker_handle_request(struct comm_point* c, void* arg, int error, { struct worker* worker = (struct worker*)arg; int ret; - hashvalue_t h; + hashvalue_type h; struct lruhash_entry* e; struct query_info qinfo; struct edns_data edns; enum acl_access acl; + struct acl_addr* acladdr; int rc = 0; + int need_drop = 0; + /* We might have to chase a CNAME chain internally, in which case + * we'll have up to two replies and combine them to build a complete + * answer. These variables control this case. */ + struct ub_packed_rrset_key* alias_rrset = NULL; + struct reply_info* partial_rep = NULL; + struct query_info* lookup_qinfo = &qinfo; + struct query_info qinfo_tmp; /* placeholdoer for lookup_qinfo */ + struct respip_client_info* cinfo = NULL, cinfo_tmp; if(error != NETEVENT_NOERROR) { /* some bad tcp query DNS formats give these error calls */ verbose(VERB_ALGO, "handle request called with err=%d", error); return 0; } +#ifdef USE_DNSCRYPT + repinfo->max_udp_size = worker->daemon->cfg->max_udp_size; + if(!dnsc_handle_curved_request(worker->daemon->dnscenv, repinfo)) { + worker->stats.num_query_dnscrypt_crypted_malformed++; + return 0; + } + if(c->dnscrypt && !repinfo->is_dnscrypted) { + char buf[LDNS_MAX_DOMAINLEN+1]; + // Check if this is unencrypted and asking for certs + if(worker_check_request(c->buffer, worker) != 0) { + verbose(VERB_ALGO, "dnscrypt: worker check request: bad query."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + comm_point_drop_reply(repinfo); + return 0; + } + if(!query_info_parse(&qinfo, c->buffer)) { + verbose(VERB_ALGO, "dnscrypt: worker parse request: formerror."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + comm_point_drop_reply(repinfo); + return 0; + } + dname_str(qinfo.qname, buf); + if(!(qinfo.qtype == LDNS_RR_TYPE_TXT && + strcasecmp(buf, worker->daemon->dnscenv->provider_name) == 0)) { + verbose(VERB_ALGO, + "dnscrypt: not TXT %s. Receive: %s %s", + worker->daemon->dnscenv->provider_name, + sldns_rr_descript(qinfo.qtype)->_name, + buf); + comm_point_drop_reply(repinfo); + worker->stats.num_query_dnscrypt_cleartext++; + return 0; + } + worker->stats.num_query_dnscrypt_cert++; + sldns_buffer_rewind(c->buffer); + } else if(c->dnscrypt && repinfo->is_dnscrypted) { + worker->stats.num_query_dnscrypt_crypted++; + } +#endif #ifdef USE_DNSTAP if(worker->dtenv.log_client_query_messages) dt_msg_send_client_query(&worker->dtenv, &repinfo->addr, c->type, c->buffer); #endif - acl = acl_list_lookup(worker->daemon->acl, &repinfo->addr, + acladdr = acl_addr_lookup(worker->daemon->acl, &repinfo->addr, repinfo->addrlen); + acl = acl_get_control(acladdr); if((ret=deny_refuse_all(c, acl, worker, repinfo)) != -1) { if(ret == 1) @@ -830,7 +1077,29 @@ worker_handle_request(struct comm_point* c, void* arg, int error, comm_point_drop_reply(repinfo); return 0; } + worker->stats.num_queries++; + + /* check if this query should be dropped based on source ip rate limiting */ + if(!infra_ip_ratelimit_inc(worker->env.infra_cache, repinfo, + *worker->env.now)) { + /* See if we are passed through with slip factor */ + if(worker->env.cfg->ip_ratelimit_factor != 0 && + ub_random_max(worker->env.rnd, + worker->env.cfg->ip_ratelimit_factor) == 1) { + + char addrbuf[128]; + addr_to_str(&repinfo->addr, repinfo->addrlen, + addrbuf, sizeof(addrbuf)); + verbose(VERB_OPS, "ip_ratelimit allowed through for ip address %s ", + addrbuf); + } else { + worker->stats.num_queries_ip_ratelimited++; + comm_point_drop_reply(repinfo); + return 0; + } + } + /* see if query is in the cache */ if(!query_info_parse(&qinfo, c->buffer)) { verbose(VERB_ALGO, "worker parse request: formerror."); @@ -865,7 +1134,29 @@ worker_handle_request(struct comm_point* c, void* arg, int error, } goto send_reply; } - if((ret=parse_edns_from_pkt(c->buffer, &edns)) != 0) { + if(qinfo.qtype == LDNS_RR_TYPE_OPT || + qinfo.qtype == LDNS_RR_TYPE_TSIG || + qinfo.qtype == LDNS_RR_TYPE_TKEY || + qinfo.qtype == LDNS_RR_TYPE_MAILA || + qinfo.qtype == LDNS_RR_TYPE_MAILB || + (qinfo.qtype >= 128 && qinfo.qtype <= 248)) { + verbose(VERB_ALGO, "worker request: formerror for meta-type."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) { + comm_point_drop_reply(repinfo); + return 0; + } + sldns_buffer_rewind(c->buffer); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_FORMERR); + if(worker->stats.extended) { + worker->stats.qtype[qinfo.qtype]++; + server_stats_insrcode(&worker->stats, c->buffer); + } + goto send_reply; + } + if((ret=parse_edns_from_pkt(c->buffer, &edns, worker->scratchpad)) != 0) { struct edns_data reply_edns; verbose(VERB_ALGO, "worker parse edns: formerror."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); @@ -876,6 +1167,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, error_encode(c->buffer, ret, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns); + regional_free_all(worker->scratchpad); server_stats_insrcode(&worker->stats, c->buffer); goto send_reply; } @@ -884,12 +1176,14 @@ worker_handle_request(struct comm_point* c, void* arg, int error, edns.edns_version = EDNS_ADVERTISED_VERSION; edns.udp_size = EDNS_ADVERTISED_SIZE; edns.bits &= EDNS_DO; + edns.opt_list = NULL; verbose(VERB_ALGO, "query with bad edns version."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), NULL); attach_edns_record(c->buffer, &edns); + regional_free_all(worker->scratchpad); goto send_reply; } if(edns.edns_present && edns.udp_size < NORMAL_UDP_SIZE && @@ -918,6 +1212,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, sldns_buffer_write_at(c->buffer, 4, (uint8_t*)"\0\0\0\0\0\0\0\0", 8); sldns_buffer_flip(c->buffer); + regional_free_all(worker->scratchpad); goto send_reply; } if(worker->stats.extended) @@ -928,10 +1223,15 @@ worker_handle_request(struct comm_point* c, void* arg, int error, if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo, &edns, c->buffer)) { server_stats_insrcode(&worker->stats, c->buffer); + regional_free_all(worker->scratchpad); goto send_reply; } - if(local_zones_answer(worker->daemon->local_zones, &qinfo, &edns, - c->buffer, worker->scratchpad, repinfo)) { + if(local_zones_answer(worker->daemon->local_zones, &worker->env, &qinfo, + &edns, c->buffer, worker->scratchpad, repinfo, acladdr->taglist, + acladdr->taglen, acladdr->tag_actions, + acladdr->tag_actions_size, acladdr->tag_datas, + acladdr->tag_datas_size, worker->daemon->cfg->tagname, + worker->daemon->cfg->num_tags, acladdr->view)) { regional_free_all(worker->scratchpad); if(sldns_buffer_limit(c->buffer) == 0) { comm_point_drop_reply(repinfo); @@ -945,6 +1245,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, * might need to bail out based on ACLs now. */ if((ret=deny_refuse_non_local(c, acl, worker, repinfo)) != -1) { + regional_free_all(worker->scratchpad); if(ret == 1) goto send_reply; return ret; @@ -961,46 +1262,125 @@ worker_handle_request(struct comm_point* c, void* arg, int error, LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), LDNS_RCODE_REFUSED); sldns_buffer_flip(c->buffer); + regional_free_all(worker->scratchpad); server_stats_insrcode(&worker->stats, c->buffer); log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from", &repinfo->addr, repinfo->addrlen); goto send_reply; } - h = query_info_hash(&qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); - if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) { - /* answer from cache - we have acquired a readlock on it */ - if(answer_from_cache(worker, &qinfo, - (struct reply_info*)e->data, - *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), - sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { - /* prefetch it if the prefetch TTL expired */ - if(worker->env.cfg->prefetch && *worker->env.now >= - ((struct reply_info*)e->data)->prefetch_ttl) { - time_t leeway = ((struct reply_info*)e-> - data)->ttl - *worker->env.now; + + /* If we've found a local alias, replace the qname with the alias + * target before resolving it. */ + if(qinfo.local_alias) { + struct ub_packed_rrset_key* rrset = qinfo.local_alias->rrset; + struct packed_rrset_data* d = rrset->entry.data; + + /* Sanity check: our current implementation only supports + * a single CNAME RRset as a local alias. */ + if(qinfo.local_alias->next || + rrset->rk.type != htons(LDNS_RR_TYPE_CNAME) || + d->count != 1) { + log_err("assumption failure: unexpected local alias"); + regional_free_all(worker->scratchpad); + return 0; /* drop it */ + } + qinfo.qname = d->rr_data[0] + 2; + qinfo.qname_len = d->rr_len[0] - 2; + } + + /* If we may apply IP-based actions to the answer, build the client + * information. As this can be expensive, skip it if there is + * absolutely no possibility of it. */ + if(worker->daemon->use_response_ip && + (qinfo.qtype == LDNS_RR_TYPE_A || + qinfo.qtype == LDNS_RR_TYPE_AAAA || + qinfo.qtype == LDNS_RR_TYPE_ANY)) { + cinfo_tmp.taglist = acladdr->taglist; + cinfo_tmp.taglen = acladdr->taglen; + cinfo_tmp.tag_actions = acladdr->tag_actions; + cinfo_tmp.tag_actions_size = acladdr->tag_actions_size; + cinfo_tmp.tag_datas = acladdr->tag_datas; + cinfo_tmp.tag_datas_size = acladdr->tag_datas_size; + cinfo_tmp.view = acladdr->view; + cinfo_tmp.respip_set = worker->daemon->respip_set; + cinfo = &cinfo_tmp; + } + +lookup_cache: + /* Lookup the cache. In case we chase an intermediate CNAME chain + * this is a two-pass operation, and lookup_qinfo is different for + * each pass. We should still pass the original qinfo to + * answer_from_cache(), however, since it's used to build the reply. */ + if(!edns_bypass_cache_stage(edns.opt_list, &worker->env)) { + h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); + if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) { + /* answer from cache - we have acquired a readlock on it */ + if(answer_from_cache(worker, &qinfo, + cinfo, &need_drop, &alias_rrset, &partial_rep, + (struct reply_info*)e->data, + *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), + sldns_buffer_read_u16_at(c->buffer, 2), repinfo, + &edns)) { + /* prefetch it if the prefetch TTL expired. + * Note that if there is more than one pass + * its qname must be that used for cache + * lookup. */ + if((worker->env.cfg->prefetch || worker->env.cfg->serve_expired) + && *worker->env.now >= + ((struct reply_info*)e->data)->prefetch_ttl) { + time_t leeway = ((struct reply_info*)e-> + data)->ttl - *worker->env.now; + if(((struct reply_info*)e->data)->ttl + < *worker->env.now) + leeway = 0; + lock_rw_unlock(&e->lock); + reply_and_prefetch(worker, lookup_qinfo, + sldns_buffer_read_u16_at(c->buffer, 2), + repinfo, leeway); + if(!partial_rep) { + rc = 0; + regional_free_all(worker->scratchpad); + goto send_reply_rc; + } + } else if(!partial_rep) { + lock_rw_unlock(&e->lock); + regional_free_all(worker->scratchpad); + goto send_reply; + } + /* We've found a partial reply ending with an + * alias. Replace the lookup qinfo for the + * alias target and lookup the cache again to + * (possibly) complete the reply. As we're + * passing the "base" reply, there will be no + * more alias chasing. */ lock_rw_unlock(&e->lock); - reply_and_prefetch(worker, &qinfo, - sldns_buffer_read_u16_at(c->buffer, 2), - repinfo, leeway); - rc = 0; - goto send_reply_rc; + memset(&qinfo_tmp, 0, sizeof(qinfo_tmp)); + get_cname_target(alias_rrset, &qinfo_tmp.qname, + &qinfo_tmp.qname_len); + if(!qinfo_tmp.qname) { + log_err("unexpected: invalid answer alias"); + regional_free_all(worker->scratchpad); + return 0; /* drop query */ + } + qinfo_tmp.qtype = qinfo.qtype; + qinfo_tmp.qclass = qinfo.qclass; + lookup_qinfo = &qinfo_tmp; + goto lookup_cache; } + verbose(VERB_ALGO, "answer from the cache failed"); lock_rw_unlock(&e->lock); - goto send_reply; } - verbose(VERB_ALGO, "answer from the cache failed"); - lock_rw_unlock(&e->lock); - } - if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { - if(answer_norec_from_cache(worker, &qinfo, - *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), - sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { - goto send_reply; + if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { + if(answer_norec_from_cache(worker, &qinfo, + *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), + sldns_buffer_read_u16_at(c->buffer, 2), repinfo, + &edns)) { + regional_free_all(worker->scratchpad); + goto send_reply; + } + verbose(VERB_ALGO, "answer norec from cache -- " + "need to validate or not primed"); } - verbose(VERB_ALGO, "answer norec from cache -- " - "need to validate or not primed"); } sldns_buffer_rewind(c->buffer); server_stats_querymiss(&worker->stats, worker); @@ -1014,20 +1394,36 @@ worker_handle_request(struct comm_point* c, void* arg, int error, } /* grab a work request structure for this new request */ - mesh_new_client(worker->env.mesh, &qinfo, + mesh_new_client(worker->env.mesh, &qinfo, cinfo, sldns_buffer_read_u16_at(c->buffer, 2), &edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer)); + regional_free_all(worker->scratchpad); worker_mem_report(worker, NULL); return 0; send_reply: rc = 1; send_reply_rc: + if(need_drop) { + comm_point_drop_reply(repinfo); + return 0; + } #ifdef USE_DNSTAP if(worker->dtenv.log_client_response_messages) dt_msg_send_client_response(&worker->dtenv, &repinfo->addr, c->type, c->buffer); #endif + if(worker->env.cfg->log_replies) + { + struct timeval tv = {0, 0}; + log_reply_info(0, &qinfo, &repinfo->addr, repinfo->addrlen, + tv, 1, c->buffer); + } +#ifdef USE_DNSCRYPT + if(!dnsc_handle_uncurved_request(repinfo)) { + return 0; + } +#endif return rc; } @@ -1083,6 +1479,10 @@ void worker_stat_timer_cb(void* arg) server_stats_log(&worker->stats, worker, worker->thread_num); mesh_stats(worker->env.mesh, "mesh has"); worker_mem_report(worker, NULL); + /* SHM is enabled, process data to SHM */ + if (worker->daemon->cfg->shm_enable) { + shm_main_run(worker); + } if(!worker->daemon->cfg->stat_cumulative) { worker_stats_clear(worker); } @@ -1217,7 +1617,8 @@ worker_init(struct worker* worker, struct config_file *cfg, cfg->do_tcp?cfg->outgoing_num_tcp:0, worker->daemon->env->infra_cache, worker->rndstate, cfg->use_caps_bits_for_id, worker->ports, worker->numports, - cfg->unwanted_threshold, &worker_alloc_cleanup, worker, + cfg->unwanted_threshold, cfg->outgoing_tcp_mss, + &worker_alloc_cleanup, worker, cfg->do_udp, worker->daemon->connect_sslctx, cfg->delay_close, dtenv); if(!worker->back) { @@ -1352,10 +1753,10 @@ worker_delete(struct worker* worker) } struct outbound_entry* -worker_send_query(uint8_t* qname, size_t qnamelen, uint16_t qtype, - uint16_t qclass, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, - uint8_t* zone, size_t zonelen, struct module_qstate* q) +worker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, + int want_dnssec, int nocaps, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, + struct module_qstate* q) { struct worker* worker = q->env->worker; struct outbound_entry* e = (struct outbound_entry*)regional_alloc( @@ -1363,11 +1764,10 @@ worker_send_query(uint8_t* qname, size_t qnamelen, uint16_t qtype, if(!e) return NULL; e->qstate = q; - e->qsent = outnet_serviced_query(worker->back, qname, - qnamelen, qtype, qclass, flags, dnssec, want_dnssec, nocaps, - q->env->cfg->tcp_upstream, q->env->cfg->ssl_upstream, addr, - addrlen, zone, zonelen, worker_handle_service_reply, e, - worker->back->udp_buff); + e->qsent = outnet_serviced_query(worker->back, qinfo, flags, dnssec, + want_dnssec, nocaps, q->env->cfg->tcp_upstream, + ssl_upstream, addr, addrlen, zone, zonelen, q, + worker_handle_service_reply, e, worker->back->udp_buff, q->env); if(!e->qsent) { return NULL; } @@ -1407,13 +1807,13 @@ void worker_stop_accept(void* arg) } /* --- fake callbacks for fptr_wlist to work --- */ -struct outbound_entry* libworker_send_query(uint8_t* ATTR_UNUSED(qname), - size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), - uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), - int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), - socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), - size_t ATTR_UNUSED(zonelen), struct module_qstate* ATTR_UNUSED(q)) +struct outbound_entry* libworker_send_query( + struct query_info* ATTR_UNUSED(qinfo), + uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), + int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), + uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), + int ATTR_UNUSED(ssl_upstream), struct module_qstate* ATTR_UNUSED(q)) { log_assert(0); return 0; |