diff options
Diffstat (limited to 'external/unbound/daemon')
-rw-r--r-- | external/unbound/daemon/acl_list.c | 180 | ||||
-rw-r--r-- | external/unbound/daemon/acl_list.h | 129 | ||||
-rw-r--r-- | external/unbound/daemon/cachedump.c | 886 | ||||
-rw-r--r-- | external/unbound/daemon/cachedump.h | 107 | ||||
-rw-r--r-- | external/unbound/daemon/daemon.c | 693 | ||||
-rw-r--r-- | external/unbound/daemon/daemon.h | 164 | ||||
-rw-r--r-- | external/unbound/daemon/remote.c | 2449 | ||||
-rw-r--r-- | external/unbound/daemon/remote.h | 189 | ||||
-rw-r--r-- | external/unbound/daemon/stats.c | 321 | ||||
-rw-r--r-- | external/unbound/daemon/stats.h | 246 | ||||
-rw-r--r-- | external/unbound/daemon/unbound.c | 780 | ||||
-rw-r--r-- | external/unbound/daemon/worker.c | 1452 | ||||
-rw-r--r-- | external/unbound/daemon/worker.h | 173 |
13 files changed, 7769 insertions, 0 deletions
diff --git a/external/unbound/daemon/acl_list.c b/external/unbound/daemon/acl_list.c new file mode 100644 index 000000000..84d099ca5 --- /dev/null +++ b/external/unbound/daemon/acl_list.c @@ -0,0 +1,180 @@ +/* + * daemon/acl_list.h - client access control storage for the server. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file helps the server keep out queries from outside sources, that + * should not be answered. + */ +#include "config.h" +#include "daemon/acl_list.h" +#include "util/regional.h" +#include "util/log.h" +#include "util/config_file.h" +#include "util/net_help.h" + +struct acl_list* +acl_list_create(void) +{ + struct acl_list* acl = (struct acl_list*)calloc(1, + sizeof(struct acl_list)); + if(!acl) + return NULL; + acl->region = regional_create(); + if(!acl->region) { + acl_list_delete(acl); + return NULL; + } + return acl; +} + +void +acl_list_delete(struct acl_list* acl) +{ + if(!acl) + return; + regional_destroy(acl->region); + free(acl); +} + +/** insert new address into acl_list structure */ +static int +acl_list_insert(struct acl_list* acl, struct sockaddr_storage* addr, + socklen_t addrlen, int net, enum acl_access control, + int complain_duplicates) +{ + struct acl_addr* node = regional_alloc(acl->region, + sizeof(struct acl_addr)); + if(!node) + return 0; + node->control = control; + if(!addr_tree_insert(&acl->tree, &node->node, addr, addrlen, net)) { + if(complain_duplicates) + verbose(VERB_QUERY, "duplicate acl address ignored."); + } + return 1; +} + +/** apply acl_list string */ +static int +acl_list_str_cfg(struct acl_list* acl, const char* str, const char* s2, + int complain_duplicates) +{ + struct sockaddr_storage addr; + int net; + socklen_t addrlen; + enum acl_access control; + if(strcmp(s2, "allow") == 0) + control = acl_allow; + else if(strcmp(s2, "deny") == 0) + control = acl_deny; + else if(strcmp(s2, "refuse") == 0) + control = acl_refuse; + else if(strcmp(s2, "deny_non_local") == 0) + control = acl_deny_non_local; + else if(strcmp(s2, "refuse_non_local") == 0) + control = acl_refuse_non_local; + else if(strcmp(s2, "allow_snoop") == 0) + control = acl_allow_snoop; + else { + log_err("access control type %s unknown", str); + return 0; + } + if(!netblockstrtoaddr(str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) { + log_err("cannot parse access control: %s %s", str, s2); + return 0; + } + if(!acl_list_insert(acl, &addr, addrlen, net, control, + complain_duplicates)) { + log_err("out of memory"); + return 0; + } + return 1; +} + +/** read acl_list config */ +static int +read_acl_list(struct acl_list* acl, struct config_file* cfg) +{ + struct config_str2list* p; + for(p = cfg->acls; p; p = p->next) { + log_assert(p->str && p->str2); + if(!acl_list_str_cfg(acl, p->str, p->str2, 1)) + return 0; + } + return 1; +} + +int +acl_list_apply_cfg(struct acl_list* acl, struct config_file* cfg) +{ + regional_free_all(acl->region); + addr_tree_init(&acl->tree); + if(!read_acl_list(acl, cfg)) + return 0; + /* insert defaults, with '0' to ignore them if they are duplicates */ + if(!acl_list_str_cfg(acl, "0.0.0.0/0", "refuse", 0)) + return 0; + if(!acl_list_str_cfg(acl, "127.0.0.0/8", "allow", 0)) + return 0; + if(cfg->do_ip6) { + if(!acl_list_str_cfg(acl, "::0/0", "refuse", 0)) + return 0; + if(!acl_list_str_cfg(acl, "::1", "allow", 0)) + return 0; + if(!acl_list_str_cfg(acl, "::ffff:127.0.0.1", "allow", 0)) + return 0; + } + addr_tree_init_parents(&acl->tree); + return 1; +} + +enum acl_access +acl_list_lookup(struct acl_list* acl, struct sockaddr_storage* addr, + socklen_t addrlen) +{ + struct acl_addr* r = (struct acl_addr*)addr_tree_lookup(&acl->tree, + addr, addrlen); + if(r) return r->control; + return acl_deny; +} + +size_t +acl_list_get_mem(struct acl_list* acl) +{ + if(!acl) return 0; + return sizeof(*acl) + regional_get_mem(acl->region); +} diff --git a/external/unbound/daemon/acl_list.h b/external/unbound/daemon/acl_list.h new file mode 100644 index 000000000..2323697d5 --- /dev/null +++ b/external/unbound/daemon/acl_list.h @@ -0,0 +1,129 @@ +/* + * daemon/acl_list.h - client access control storage for the server. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file keeps track of the list of clients that are allowed to + * access the server. + */ + +#ifndef DAEMON_ACL_LIST_H +#define DAEMON_ACL_LIST_H +#include "util/storage/dnstree.h" +struct config_file; +struct regional; + +/** + * Enumeration of access control options for an address range. + * Allow or deny access. + */ +enum acl_access { + /** disallow any access whatsoever, drop it */ + acl_deny = 0, + /** disallow access, send a polite 'REFUSED' reply */ + acl_refuse, + /** disallow any access to zones that aren't local, drop it */ + acl_deny_non_local, + /** disallow access to zones that aren't local, 'REFUSED' reply */ + acl_refuse_non_local, + /** allow full access for recursion (+RD) queries */ + acl_allow, + /** allow full access for all queries, recursion and cache snooping */ + acl_allow_snoop +}; + +/** + * Access control storage structure + */ +struct acl_list { + /** regional for allocation */ + struct regional* region; + /** + * Tree of the addresses that are allowed/blocked. + * contents of type acl_addr. + */ + rbtree_t tree; +}; + +/** + * + * An address span with access control information + */ +struct acl_addr { + /** node in address tree */ + struct addr_tree_node node; + /** access control on this netblock */ + enum acl_access control; +}; + +/** + * Create acl structure + * @return new structure or NULL on error. + */ +struct acl_list* acl_list_create(void); + +/** + * Delete acl structure. + * @param acl: to delete. + */ +void acl_list_delete(struct acl_list* acl); + +/** + * Process access control config. + * @param acl: where to store. + * @param cfg: config options. + * @return 0 on error. + */ +int acl_list_apply_cfg(struct acl_list* acl, struct config_file* cfg); + +/** + * Lookup address to see its access control status. + * @param acl: structure for address storage. + * @param addr: address to check + * @param addrlen: length of addr. + * @return: what to do with message from this address. + */ +enum acl_access acl_list_lookup(struct acl_list* acl, + struct sockaddr_storage* addr, socklen_t addrlen); + +/** + * Get memory used by acl structure. + * @param acl: structure for address storage. + * @return bytes in use. + */ +size_t acl_list_get_mem(struct acl_list* acl); + +#endif /* DAEMON_ACL_LIST_H */ diff --git a/external/unbound/daemon/cachedump.c b/external/unbound/daemon/cachedump.c new file mode 100644 index 000000000..cf5b1a12c --- /dev/null +++ b/external/unbound/daemon/cachedump.c @@ -0,0 +1,886 @@ +/* + * daemon/cachedump.c - dump the cache to text format. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions to read and write the cache(s) + * to text format. + */ +#include "config.h" +#include <openssl/ssl.h> +#include "daemon/cachedump.h" +#include "daemon/remote.h" +#include "daemon/worker.h" +#include "services/cache/rrset.h" +#include "services/cache/dns.h" +#include "services/cache/infra.h" +#include "util/data/msgreply.h" +#include "util/regional.h" +#include "util/net_help.h" +#include "util/data/dname.h" +#include "iterator/iterator.h" +#include "iterator/iter_delegpt.h" +#include "iterator/iter_utils.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" +#include "ldns/sbuffer.h" +#include "ldns/wire2str.h" +#include "ldns/str2wire.h" + +/** dump one rrset zonefile line */ +static int +dump_rrset_line(SSL* ssl, struct ub_packed_rrset_key* k, time_t now, size_t i) +{ + char s[65535]; + if(!packed_rr_to_string(k, i, now, s, sizeof(s))) { + return ssl_printf(ssl, "BADRR\n"); + } + return ssl_printf(ssl, "%s", s); +} + +/** dump rrset key and data info */ +static int +dump_rrset(SSL* ssl, struct ub_packed_rrset_key* k, + struct packed_rrset_data* d, time_t now) +{ + size_t i; + /* rd lock held by caller */ + if(!k || !d) return 1; + if(d->ttl < now) return 1; /* expired */ + + /* meta line */ + if(!ssl_printf(ssl, ";rrset%s " ARG_LL "d %u %u %d %d\n", + (k->rk.flags & PACKED_RRSET_NSEC_AT_APEX)?" nsec_apex":"", + (long long)(d->ttl - now), + (unsigned)d->count, (unsigned)d->rrsig_count, + (int)d->trust, (int)d->security + )) + return 0; + for(i=0; i<d->count + d->rrsig_count; i++) { + if(!dump_rrset_line(ssl, k, now, i)) + return 0; + } + return 1; +} + +/** dump lruhash rrset cache */ +static int +dump_rrset_lruhash(SSL* ssl, struct lruhash* h, time_t now) +{ + struct lruhash_entry* e; + /* lruhash already locked by caller */ + /* walk in order of lru; best first */ + for(e=h->lru_start; e; e = e->lru_next) { + lock_rw_rdlock(&e->lock); + if(!dump_rrset(ssl, (struct ub_packed_rrset_key*)e->key, + (struct packed_rrset_data*)e->data, now)) { + lock_rw_unlock(&e->lock); + return 0; + } + lock_rw_unlock(&e->lock); + } + return 1; +} + +/** dump rrset cache */ +static int +dump_rrset_cache(SSL* ssl, struct worker* worker) +{ + struct rrset_cache* r = worker->env.rrset_cache; + size_t slab; + if(!ssl_printf(ssl, "START_RRSET_CACHE\n")) return 0; + for(slab=0; slab<r->table.size; slab++) { + lock_quick_lock(&r->table.array[slab]->lock); + if(!dump_rrset_lruhash(ssl, r->table.array[slab], + *worker->env.now)) { + lock_quick_unlock(&r->table.array[slab]->lock); + return 0; + } + lock_quick_unlock(&r->table.array[slab]->lock); + } + return ssl_printf(ssl, "END_RRSET_CACHE\n"); +} + +/** dump message to rrset reference */ +static int +dump_msg_ref(SSL* ssl, struct ub_packed_rrset_key* k) +{ + char* nm, *tp, *cl; + nm = sldns_wire2str_dname(k->rk.dname, k->rk.dname_len); + tp = sldns_wire2str_type(ntohs(k->rk.type)); + cl = sldns_wire2str_class(ntohs(k->rk.rrset_class)); + if(!nm || !cl || !tp) { + free(nm); + free(tp); + free(cl); + return ssl_printf(ssl, "BADREF\n"); + } + if(!ssl_printf(ssl, "%s %s %s %d\n", nm, cl, tp, (int)k->rk.flags)) { + free(nm); + free(tp); + free(cl); + return 0; + } + free(nm); + free(tp); + free(cl); + + return 1; +} + +/** dump message entry */ +static int +dump_msg(SSL* ssl, struct query_info* k, struct reply_info* d, + time_t now) +{ + size_t i; + char* nm, *tp, *cl; + if(!k || !d) return 1; + if(d->ttl < now) return 1; /* expired */ + + nm = sldns_wire2str_dname(k->qname, k->qname_len); + tp = sldns_wire2str_type(k->qtype); + cl = sldns_wire2str_class(k->qclass); + if(!nm || !tp || !cl) { + free(nm); + free(tp); + free(cl); + return 1; /* skip this entry */ + } + if(!rrset_array_lock(d->ref, d->rrset_count, now)) { + /* rrsets have timed out or do not exist */ + free(nm); + free(tp); + free(cl); + return 1; /* skip this entry */ + } + + /* meta line */ + if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u\n", + nm, cl, tp, + (int)d->flags, (int)d->qdcount, + (long long)(d->ttl-now), (int)d->security, + (unsigned)d->an_numrrsets, + (unsigned)d->ns_numrrsets, + (unsigned)d->ar_numrrsets)) { + free(nm); + free(tp); + free(cl); + rrset_array_unlock(d->ref, d->rrset_count); + return 0; + } + free(nm); + free(tp); + free(cl); + + for(i=0; i<d->rrset_count; i++) { + if(!dump_msg_ref(ssl, d->rrsets[i])) { + rrset_array_unlock(d->ref, d->rrset_count); + return 0; + } + } + rrset_array_unlock(d->ref, d->rrset_count); + + return 1; +} + +/** copy msg to worker pad */ +static int +copy_msg(struct regional* region, struct lruhash_entry* e, + struct query_info** k, struct reply_info** d) +{ + struct reply_info* rep = (struct reply_info*)e->data; + *d = (struct reply_info*)regional_alloc_init(region, e->data, + sizeof(struct reply_info) + + sizeof(struct rrset_ref) * (rep->rrset_count-1) + + sizeof(struct ub_packed_rrset_key*) * rep->rrset_count); + if(!*d) + return 0; + (*d)->rrsets = (struct ub_packed_rrset_key**)(void *)( + (uint8_t*)(&((*d)->ref[0])) + + sizeof(struct rrset_ref) * rep->rrset_count); + *k = (struct query_info*)regional_alloc_init(region, + e->key, sizeof(struct query_info)); + if(!*k) + return 0; + (*k)->qname = regional_alloc_init(region, + (*k)->qname, (*k)->qname_len); + return (*k)->qname != NULL; +} + +/** dump lruhash msg cache */ +static int +dump_msg_lruhash(SSL* ssl, struct worker* worker, struct lruhash* h) +{ + struct lruhash_entry* e; + struct query_info* k; + struct reply_info* d; + + /* lruhash already locked by caller */ + /* walk in order of lru; best first */ + for(e=h->lru_start; e; e = e->lru_next) { + regional_free_all(worker->scratchpad); + lock_rw_rdlock(&e->lock); + /* make copy of rrset in worker buffer */ + if(!copy_msg(worker->scratchpad, e, &k, &d)) { + lock_rw_unlock(&e->lock); + return 0; + } + lock_rw_unlock(&e->lock); + /* release lock so we can lookup the rrset references + * in the rrset cache */ + if(!dump_msg(ssl, k, d, *worker->env.now)) { + return 0; + } + } + return 1; +} + +/** dump msg cache */ +static int +dump_msg_cache(SSL* ssl, struct worker* worker) +{ + struct slabhash* sh = worker->env.msg_cache; + size_t slab; + if(!ssl_printf(ssl, "START_MSG_CACHE\n")) return 0; + for(slab=0; slab<sh->size; slab++) { + lock_quick_lock(&sh->array[slab]->lock); + if(!dump_msg_lruhash(ssl, worker, sh->array[slab])) { + lock_quick_unlock(&sh->array[slab]->lock); + return 0; + } + lock_quick_unlock(&sh->array[slab]->lock); + } + return ssl_printf(ssl, "END_MSG_CACHE\n"); +} + +int +dump_cache(SSL* ssl, struct worker* worker) +{ + if(!dump_rrset_cache(ssl, worker)) + return 0; + if(!dump_msg_cache(ssl, worker)) + return 0; + return ssl_printf(ssl, "EOF\n"); +} + +/** read a line from ssl into buffer */ +static int +ssl_read_buf(SSL* ssl, sldns_buffer* buf) +{ + return ssl_read_line(ssl, (char*)sldns_buffer_begin(buf), + sldns_buffer_capacity(buf)); +} + +/** check fixed text on line */ +static int +read_fixed(SSL* ssl, sldns_buffer* buf, const char* str) +{ + if(!ssl_read_buf(ssl, buf)) return 0; + return (strcmp((char*)sldns_buffer_begin(buf), str) == 0); +} + +/** load an RR into rrset */ +static int +load_rr(SSL* ssl, sldns_buffer* buf, struct regional* region, + struct ub_packed_rrset_key* rk, struct packed_rrset_data* d, + unsigned int i, int is_rrsig, int* go_on, time_t now) +{ + uint8_t rr[LDNS_RR_BUF_SIZE]; + size_t rr_len = sizeof(rr), dname_len = 0; + int status; + + /* read the line */ + if(!ssl_read_buf(ssl, buf)) + return 0; + if(strncmp((char*)sldns_buffer_begin(buf), "BADRR\n", 6) == 0) { + *go_on = 0; + return 1; + } + status = sldns_str2wire_rr_buf((char*)sldns_buffer_begin(buf), rr, + &rr_len, &dname_len, 3600, NULL, 0, NULL, 0); + if(status != 0) { + log_warn("error cannot parse rr: %s: %s", + sldns_get_errorstr_parse(status), + (char*)sldns_buffer_begin(buf)); + return 0; + } + if(is_rrsig && sldns_wirerr_get_type(rr, rr_len, dname_len) + != LDNS_RR_TYPE_RRSIG) { + log_warn("error expected rrsig but got %s", + (char*)sldns_buffer_begin(buf)); + return 0; + } + + /* convert ldns rr into packed_rr */ + d->rr_ttl[i] = (time_t)sldns_wirerr_get_ttl(rr, rr_len, dname_len) + now; + sldns_buffer_clear(buf); + d->rr_len[i] = sldns_wirerr_get_rdatalen(rr, rr_len, dname_len)+2; + d->rr_data[i] = (uint8_t*)regional_alloc_init(region, + sldns_wirerr_get_rdatawl(rr, rr_len, dname_len), d->rr_len[i]); + if(!d->rr_data[i]) { + log_warn("error out of memory"); + return 0; + } + + /* if first entry, fill the key structure */ + if(i==0) { + rk->rk.type = htons(sldns_wirerr_get_type(rr, rr_len, dname_len)); + rk->rk.rrset_class = htons(sldns_wirerr_get_class(rr, rr_len, dname_len)); + rk->rk.dname_len = dname_len; + rk->rk.dname = regional_alloc_init(region, rr, dname_len); + if(!rk->rk.dname) { + log_warn("error out of memory"); + return 0; + } + } + + return 1; +} + +/** move entry into cache */ +static int +move_into_cache(struct ub_packed_rrset_key* k, + struct packed_rrset_data* d, struct worker* worker) +{ + struct ub_packed_rrset_key* ak; + struct packed_rrset_data* ad; + size_t s, i, num = d->count + d->rrsig_count; + struct rrset_ref ref; + uint8_t* p; + + ak = alloc_special_obtain(&worker->alloc); + if(!ak) { + log_warn("error out of memory"); + return 0; + } + ak->entry.data = NULL; + ak->rk = k->rk; + ak->entry.hash = rrset_key_hash(&k->rk); + ak->rk.dname = (uint8_t*)memdup(k->rk.dname, k->rk.dname_len); + if(!ak->rk.dname) { + log_warn("error out of memory"); + ub_packed_rrset_parsedelete(ak, &worker->alloc); + return 0; + } + s = sizeof(*ad) + (sizeof(size_t) + sizeof(uint8_t*) + + sizeof(time_t))* num; + for(i=0; i<num; i++) + s += d->rr_len[i]; + ad = (struct packed_rrset_data*)malloc(s); + if(!ad) { + log_warn("error out of memory"); + ub_packed_rrset_parsedelete(ak, &worker->alloc); + return 0; + } + p = (uint8_t*)ad; + memmove(p, d, sizeof(*ad)); + p += sizeof(*ad); + memmove(p, &d->rr_len[0], sizeof(size_t)*num); + p += sizeof(size_t)*num; + memmove(p, &d->rr_data[0], sizeof(uint8_t*)*num); + p += sizeof(uint8_t*)*num; + memmove(p, &d->rr_ttl[0], sizeof(time_t)*num); + p += sizeof(time_t)*num; + for(i=0; i<num; i++) { + memmove(p, d->rr_data[i], d->rr_len[i]); + p += d->rr_len[i]; + } + packed_rrset_ptr_fixup(ad); + + ak->entry.data = ad; + + ref.key = ak; + ref.id = ak->id; + (void)rrset_cache_update(worker->env.rrset_cache, &ref, + &worker->alloc, *worker->env.now); + return 1; +} + +/** load an rrset entry */ +static int +load_rrset(SSL* ssl, sldns_buffer* buf, struct worker* worker) +{ + char* s = (char*)sldns_buffer_begin(buf); + struct regional* region = worker->scratchpad; + struct ub_packed_rrset_key* rk; + struct packed_rrset_data* d; + unsigned int rr_count, rrsig_count, trust, security; + long long ttl; + unsigned int i; + int go_on = 1; + regional_free_all(region); + + rk = (struct ub_packed_rrset_key*)regional_alloc_zero(region, + sizeof(*rk)); + d = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*d)); + if(!rk || !d) { + log_warn("error out of memory"); + return 0; + } + + if(strncmp(s, ";rrset", 6) != 0) { + log_warn("error expected ';rrset' but got %s", s); + return 0; + } + s += 6; + if(strncmp(s, " nsec_apex", 10) == 0) { + s += 10; + rk->rk.flags |= PACKED_RRSET_NSEC_AT_APEX; + } + if(sscanf(s, " " ARG_LL "d %u %u %u %u", &ttl, &rr_count, &rrsig_count, + &trust, &security) != 5) { + log_warn("error bad rrset spec %s", s); + return 0; + } + if(rr_count == 0 && rrsig_count == 0) { + log_warn("bad rrset without contents"); + return 0; + } + d->count = (size_t)rr_count; + d->rrsig_count = (size_t)rrsig_count; + d->security = (enum sec_status)security; + d->trust = (enum rrset_trust)trust; + d->ttl = (time_t)ttl + *worker->env.now; + + d->rr_len = regional_alloc_zero(region, + sizeof(size_t)*(d->count+d->rrsig_count)); + d->rr_ttl = regional_alloc_zero(region, + sizeof(time_t)*(d->count+d->rrsig_count)); + d->rr_data = regional_alloc_zero(region, + sizeof(uint8_t*)*(d->count+d->rrsig_count)); + if(!d->rr_len || !d->rr_ttl || !d->rr_data) { + log_warn("error out of memory"); + return 0; + } + + /* read the rr's themselves */ + for(i=0; i<rr_count; i++) { + if(!load_rr(ssl, buf, region, rk, d, i, 0, + &go_on, *worker->env.now)) { + log_warn("could not read rr %u", i); + return 0; + } + } + for(i=0; i<rrsig_count; i++) { + if(!load_rr(ssl, buf, region, rk, d, i+rr_count, 1, + &go_on, *worker->env.now)) { + log_warn("could not read rrsig %u", i); + return 0; + } + } + if(!go_on) { + /* skip this entry */ + return 1; + } + + return move_into_cache(rk, d, worker); +} + +/** load rrset cache */ +static int +load_rrset_cache(SSL* ssl, struct worker* worker) +{ + sldns_buffer* buf = worker->env.scratch_buffer; + if(!read_fixed(ssl, buf, "START_RRSET_CACHE")) return 0; + while(ssl_read_buf(ssl, buf) && + strcmp((char*)sldns_buffer_begin(buf), "END_RRSET_CACHE")!=0) { + if(!load_rrset(ssl, buf, worker)) + return 0; + } + return 1; +} + +/** read qinfo from next three words */ +static char* +load_qinfo(char* str, struct query_info* qinfo, struct regional* region) +{ + /* s is part of the buf */ + char* s = str; + uint8_t rr[LDNS_RR_BUF_SIZE]; + size_t rr_len = sizeof(rr), dname_len = 0; + int status; + + /* skip three words */ + s = strchr(str, ' '); + if(s) s = strchr(s+1, ' '); + if(s) s = strchr(s+1, ' '); + if(!s) { + log_warn("error line too short, %s", str); + return NULL; + } + s[0] = 0; + s++; + + /* parse them */ + status = sldns_str2wire_rr_question_buf(str, rr, &rr_len, &dname_len, + NULL, 0, NULL, 0); + if(status != 0) { + log_warn("error cannot parse: %s %s", + sldns_get_errorstr_parse(status), str); + return NULL; + } + qinfo->qtype = sldns_wirerr_get_type(rr, rr_len, dname_len); + qinfo->qclass = sldns_wirerr_get_class(rr, rr_len, dname_len); + qinfo->qname_len = dname_len; + qinfo->qname = (uint8_t*)regional_alloc_init(region, rr, dname_len); + if(!qinfo->qname) { + log_warn("error out of memory"); + return NULL; + } + + return s; +} + +/** load a msg rrset reference */ +static int +load_ref(SSL* ssl, sldns_buffer* buf, struct worker* worker, + struct regional *region, struct ub_packed_rrset_key** rrset, + int* go_on) +{ + char* s = (char*)sldns_buffer_begin(buf); + struct query_info qinfo; + unsigned int flags; + struct ub_packed_rrset_key* k; + + /* read line */ + if(!ssl_read_buf(ssl, buf)) + return 0; + if(strncmp(s, "BADREF", 6) == 0) { + *go_on = 0; /* its bad, skip it and skip message */ + return 1; + } + + s = load_qinfo(s, &qinfo, region); + if(!s) { + return 0; + } + if(sscanf(s, " %u", &flags) != 1) { + log_warn("error cannot parse flags: %s", s); + return 0; + } + + /* lookup in cache */ + k = rrset_cache_lookup(worker->env.rrset_cache, qinfo.qname, + qinfo.qname_len, qinfo.qtype, qinfo.qclass, + (uint32_t)flags, *worker->env.now, 0); + if(!k) { + /* not found or expired */ + *go_on = 0; + return 1; + } + + /* store in result */ + *rrset = packed_rrset_copy_region(k, region, *worker->env.now); + lock_rw_unlock(&k->entry.lock); + + return (*rrset != NULL); +} + +/** load a msg entry */ +static int +load_msg(SSL* ssl, sldns_buffer* buf, struct worker* worker) +{ + struct regional* region = worker->scratchpad; + struct query_info qinf; + struct reply_info rep; + char* s = (char*)sldns_buffer_begin(buf); + unsigned int flags, qdcount, security, an, ns, ar; + long long ttl; + size_t i; + int go_on = 1; + + regional_free_all(region); + + if(strncmp(s, "msg ", 4) != 0) { + log_warn("error expected msg but got %s", s); + return 0; + } + s += 4; + s = load_qinfo(s, &qinf, region); + if(!s) { + return 0; + } + + /* read remainder of line */ + if(sscanf(s, " %u %u " ARG_LL "d %u %u %u %u", &flags, &qdcount, &ttl, + &security, &an, &ns, &ar) != 7) { + log_warn("error cannot parse numbers: %s", s); + return 0; + } + rep.flags = (uint16_t)flags; + rep.qdcount = (uint16_t)qdcount; + rep.ttl = (time_t)ttl; + rep.prefetch_ttl = PREFETCH_TTL_CALC(rep.ttl); + rep.security = (enum sec_status)security; + rep.an_numrrsets = (size_t)an; + rep.ns_numrrsets = (size_t)ns; + rep.ar_numrrsets = (size_t)ar; + rep.rrset_count = (size_t)an+(size_t)ns+(size_t)ar; + rep.rrsets = (struct ub_packed_rrset_key**)regional_alloc_zero( + region, sizeof(struct ub_packed_rrset_key*)*rep.rrset_count); + + /* fill repinfo with references */ + for(i=0; i<rep.rrset_count; i++) { + if(!load_ref(ssl, buf, worker, region, &rep.rrsets[i], + &go_on)) { + return 0; + } + } + + if(!go_on) + return 1; /* skip this one, not all references satisfied */ + + if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0, 0, NULL)) { + log_warn("error out of memory"); + return 0; + } + return 1; +} + +/** load msg cache */ +static int +load_msg_cache(SSL* ssl, struct worker* worker) +{ + sldns_buffer* buf = worker->env.scratch_buffer; + if(!read_fixed(ssl, buf, "START_MSG_CACHE")) return 0; + while(ssl_read_buf(ssl, buf) && + strcmp((char*)sldns_buffer_begin(buf), "END_MSG_CACHE")!=0) { + if(!load_msg(ssl, buf, worker)) + return 0; + } + return 1; +} + +int +load_cache(SSL* ssl, struct worker* worker) +{ + if(!load_rrset_cache(ssl, worker)) + return 0; + if(!load_msg_cache(ssl, worker)) + return 0; + return read_fixed(ssl, worker->env.scratch_buffer, "EOF"); +} + +/** print details on a delegation point */ +static void +print_dp_details(SSL* ssl, struct worker* worker, struct delegpt* dp) +{ + char buf[257]; + struct delegpt_addr* a; + int lame, dlame, rlame, rto, edns_vs, to, delay, + tA = 0, tAAAA = 0, tother = 0; + long long entry_ttl; + struct rtt_info ri; + uint8_t edns_lame_known; + for(a = dp->target_list; a; a = a->next_target) { + addr_to_str(&a->addr, a->addrlen, buf, sizeof(buf)); + if(!ssl_printf(ssl, "%-16s\t", buf)) + return; + if(a->bogus) { + if(!ssl_printf(ssl, "Address is BOGUS. ")) + return; + } + /* lookup in infra cache */ + delay=0; + entry_ttl = infra_get_host_rto(worker->env.infra_cache, + &a->addr, a->addrlen, dp->name, dp->namelen, + &ri, &delay, *worker->env.now, &tA, &tAAAA, &tother); + if(entry_ttl == -2 && ri.rto >= USEFUL_SERVER_TOP_TIMEOUT) { + if(!ssl_printf(ssl, "expired, rto %d msec, tA %d " + "tAAAA %d tother %d.\n", ri.rto, tA, tAAAA, + tother)) + return; + continue; + } + if(entry_ttl == -1 || entry_ttl == -2) { + if(!ssl_printf(ssl, "not in infra cache.\n")) + return; + continue; /* skip stuff not in infra cache */ + } + + /* uses type_A because most often looked up, but other + * lameness won't be reported then */ + if(!infra_get_lame_rtt(worker->env.infra_cache, + &a->addr, a->addrlen, dp->name, dp->namelen, + LDNS_RR_TYPE_A, &lame, &dlame, &rlame, &rto, + *worker->env.now)) { + if(!ssl_printf(ssl, "not in infra cache.\n")) + return; + continue; /* skip stuff not in infra cache */ + } + if(!ssl_printf(ssl, "%s%s%s%srto %d msec, ttl " ARG_LL "d, " + "ping %d var %d rtt %d, tA %d, tAAAA %d, tother %d", + lame?"LAME ":"", dlame?"NoDNSSEC ":"", + a->lame?"AddrWasParentSide ":"", + rlame?"NoAuthButRecursive ":"", rto, entry_ttl, + ri.srtt, ri.rttvar, rtt_notimeout(&ri), + tA, tAAAA, tother)) + return; + if(delay) + if(!ssl_printf(ssl, ", probedelay %d", delay)) + return; + if(infra_host(worker->env.infra_cache, &a->addr, a->addrlen, + dp->name, dp->namelen, *worker->env.now, &edns_vs, + &edns_lame_known, &to)) { + if(edns_vs == -1) { + if(!ssl_printf(ssl, ", noEDNS%s.", + edns_lame_known?" probed":" assumed")) + return; + } else { + if(!ssl_printf(ssl, ", EDNS %d%s.", edns_vs, + edns_lame_known?" probed":" assumed")) + return; + } + } + if(!ssl_printf(ssl, "\n")) + return; + } +} + +/** print main dp info */ +static void +print_dp_main(SSL* ssl, struct delegpt* dp, struct dns_msg* msg) +{ + size_t i, n_ns, n_miss, n_addr, n_res, n_avail; + + /* print the dp */ + if(msg) + for(i=0; i<msg->rep->rrset_count; i++) { + struct ub_packed_rrset_key* k = msg->rep->rrsets[i]; + struct packed_rrset_data* d = + (struct packed_rrset_data*)k->entry.data; + if(d->security == sec_status_bogus) { + if(!ssl_printf(ssl, "Address is BOGUS:\n")) + return; + } + if(!dump_rrset(ssl, k, d, 0)) + return; + } + delegpt_count_ns(dp, &n_ns, &n_miss); + delegpt_count_addr(dp, &n_addr, &n_res, &n_avail); + /* since dp has not been used by iterator, all are available*/ + if(!ssl_printf(ssl, "Delegation with %d names, of which %d " + "can be examined to query further addresses.\n" + "%sIt provides %d IP addresses.\n", + (int)n_ns, (int)n_miss, (dp->bogus?"It is BOGUS. ":""), + (int)n_addr)) + return; +} + +int print_deleg_lookup(SSL* ssl, struct worker* worker, uint8_t* nm, + size_t nmlen, int ATTR_UNUSED(nmlabs)) +{ + /* deep links into the iterator module */ + struct delegpt* dp; + struct dns_msg* msg; + struct regional* region = worker->scratchpad; + char b[260]; + struct query_info qinfo; + struct iter_hints_stub* stub; + regional_free_all(region); + qinfo.qname = nm; + qinfo.qname_len = nmlen; + qinfo.qtype = LDNS_RR_TYPE_A; + qinfo.qclass = LDNS_RR_CLASS_IN; + + dname_str(nm, b); + if(!ssl_printf(ssl, "The following name servers are used for lookup " + "of %s\n", b)) + return 0; + + dp = forwards_lookup(worker->env.fwds, nm, qinfo.qclass); + if(dp) { + if(!ssl_printf(ssl, "forwarding request:\n")) + return 0; + print_dp_main(ssl, dp, NULL); + print_dp_details(ssl, worker, dp); + return 1; + } + + while(1) { + dp = dns_cache_find_delegation(&worker->env, nm, nmlen, + qinfo.qtype, qinfo.qclass, region, &msg, + *worker->env.now); + if(!dp) { + return ssl_printf(ssl, "no delegation from " + "cache; goes to configured roots\n"); + } + /* go up? */ + if(iter_dp_is_useless(&qinfo, BIT_RD, dp)) { + print_dp_main(ssl, dp, msg); + print_dp_details(ssl, worker, dp); + if(!ssl_printf(ssl, "cache delegation was " + "useless (no IP addresses)\n")) + return 0; + if(dname_is_root(nm)) { + /* goes to root config */ + return ssl_printf(ssl, "no delegation from " + "cache; goes to configured roots\n"); + } else { + /* useless, goes up */ + nm = dp->name; + nmlen = dp->namelen; + dname_remove_label(&nm, &nmlen); + dname_str(nm, b); + if(!ssl_printf(ssl, "going up, lookup %s\n", b)) + return 0; + continue; + } + } + stub = hints_lookup_stub(worker->env.hints, nm, qinfo.qclass, + dp); + if(stub) { + if(stub->noprime) { + if(!ssl_printf(ssl, "The noprime stub servers " + "are used:\n")) + return 0; + } else { + if(!ssl_printf(ssl, "The stub is primed " + "with servers:\n")) + return 0; + } + print_dp_main(ssl, stub->dp, NULL); + print_dp_details(ssl, worker, stub->dp); + } else { + print_dp_main(ssl, dp, msg); + print_dp_details(ssl, worker, dp); + } + break; + } + + return 1; +} diff --git a/external/unbound/daemon/cachedump.h b/external/unbound/daemon/cachedump.h new file mode 100644 index 000000000..0f2feabcb --- /dev/null +++ b/external/unbound/daemon/cachedump.h @@ -0,0 +1,107 @@ +/* + * daemon/cachedump.h - dump the cache to text format. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions to read and write the cache(s) + * to text format. + * + * The format of the file is as follows: + * [RRset cache] + * [Message cache] + * EOF -- fixed string "EOF" before end of the file. + * + * The RRset cache is: + * START_RRSET_CACHE + * [rrset]* + * END_RRSET_CACHE + * + * rrset is: + * ;rrset [nsec_apex] TTL rr_count rrsig_count trust security + * resource records, one per line, in zonefile format + * rrsig records, one per line, in zonefile format + * If the text conversion fails, BADRR is printed on the line. + * + * The Message cache is: + * START_MSG_CACHE + * [msg]* + * END_MSG_CACHE + * + * msg is: + * msg name class type flags qdcount ttl security an ns ar + * list of rrset references, one per line. If conversion fails, BADREF + * reference is: + * name class type flags + * + * Expired cache entries are not printed. + */ + +#ifndef DAEMON_DUMPCACHE_H +#define DAEMON_DUMPCACHE_H +struct worker; + +/** + * Dump cache(s) to text + * @param ssl: to print to + * @param worker: worker that is available (buffers, etc) and has + * ptrs to the caches. + * @return false on ssl print error. + */ +int dump_cache(SSL* ssl, struct worker* worker); + +/** + * Load cache(s) from text + * @param ssl: to read from + * @param worker: worker that is available (buffers, etc) and has + * ptrs to the caches. + * @return false on ssl error. + */ +int load_cache(SSL* ssl, struct worker* worker); + +/** + * Print the delegation used to lookup for this name. + * @param ssl: to read from + * @param worker: worker that is available (buffers, etc) and has + * ptrs to the caches. + * @param nm: name to lookup + * @param nmlen: length of name. + * @param nmlabs: labels in name. + * @return false on ssl error. + */ +int print_deleg_lookup(SSL* ssl, struct worker* worker, uint8_t* nm, + size_t nmlen, int nmlabs); + +#endif /* DAEMON_DUMPCACHE_H */ diff --git a/external/unbound/daemon/daemon.c b/external/unbound/daemon/daemon.c new file mode 100644 index 000000000..f693a0285 --- /dev/null +++ b/external/unbound/daemon/daemon.c @@ -0,0 +1,693 @@ +/* + * daemon/daemon.c - collection of workers that handles requests. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * The daemon consists of global settings and a number of workers. + */ + +#include "config.h" +#ifdef HAVE_OPENSSL_ERR_H +#include <openssl/err.h> +#endif + +#ifdef HAVE_OPENSSL_RAND_H +#include <openssl/rand.h> +#endif + +#ifdef HAVE_OPENSSL_CONF_H +#include <openssl/conf.h> +#endif + +#ifdef HAVE_OPENSSL_ENGINE_H +#include <openssl/engine.h> +#endif + +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#include <sys/time.h> + +#ifdef HAVE_NSS +/* nss3 */ +#include "nss.h" +#endif + +#include "daemon/daemon.h" +#include "daemon/worker.h" +#include "daemon/remote.h" +#include "daemon/acl_list.h" +#include "util/log.h" +#include "util/config_file.h" +#include "util/data/msgreply.h" +#include "util/storage/lookup3.h" +#include "util/storage/slabhash.h" +#include "services/listen_dnsport.h" +#include "services/cache/rrset.h" +#include "services/cache/infra.h" +#include "services/localzone.h" +#include "services/modstack.h" +#include "util/module.h" +#include "util/random.h" +#include "util/tube.h" +#include "util/net_help.h" +#include "ldns/keyraw.h" +#include <signal.h> + +/** How many quit requests happened. */ +static int sig_record_quit = 0; +/** How many reload requests happened. */ +static int sig_record_reload = 0; + +#if HAVE_DECL_SSL_COMP_GET_COMPRESSION_METHODS +/** cleaner ssl memory freeup */ +static void* comp_meth = NULL; +#endif +#ifdef LEX_HAS_YYLEX_DESTROY +/** remove buffers for parsing and init */ +int ub_c_lex_destroy(void); +#endif + +/** used when no other sighandling happens, so we don't die + * when multiple signals in quick succession are sent to us. + * @param sig: signal number. + * @return signal handler return type (void or int). + */ +static RETSIGTYPE record_sigh(int sig) +{ +#ifdef LIBEVENT_SIGNAL_PROBLEM + /* cannot log, verbose here because locks may be held */ + /* quit on signal, no cleanup and statistics, + because installed libevent version is not threadsafe */ + exit(0); +#endif + switch(sig) + { + case SIGTERM: +#ifdef SIGQUIT + case SIGQUIT: +#endif +#ifdef SIGBREAK + case SIGBREAK: +#endif + case SIGINT: + sig_record_quit++; + break; +#ifdef SIGHUP + case SIGHUP: + sig_record_reload++; + break; +#endif +#ifdef SIGPIPE + case SIGPIPE: + break; +#endif + default: + /* ignoring signal */ + break; + } +} + +/** + * Signal handling during the time when netevent is disabled. + * Stores signals to replay later. + */ +static void +signal_handling_record(void) +{ + if( signal(SIGTERM, record_sigh) == SIG_ERR || +#ifdef SIGQUIT + signal(SIGQUIT, record_sigh) == SIG_ERR || +#endif +#ifdef SIGBREAK + signal(SIGBREAK, record_sigh) == SIG_ERR || +#endif +#ifdef SIGHUP + signal(SIGHUP, record_sigh) == SIG_ERR || +#endif +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN) == SIG_ERR || +#endif + signal(SIGINT, record_sigh) == SIG_ERR + ) + log_err("install sighandler: %s", strerror(errno)); +} + +/** + * Replay old signals. + * @param wrk: worker that handles signals. + */ +static void +signal_handling_playback(struct worker* wrk) +{ +#ifdef SIGHUP + if(sig_record_reload) + worker_sighandler(SIGHUP, wrk); +#endif + if(sig_record_quit) + worker_sighandler(SIGTERM, wrk); + sig_record_quit = 0; + sig_record_reload = 0; +} + +struct daemon* +daemon_init(void) +{ + struct daemon* daemon = (struct daemon*)calloc(1, + sizeof(struct daemon)); +#ifdef USE_WINSOCK + int r; + WSADATA wsa_data; +#endif + if(!daemon) + return NULL; +#ifdef USE_WINSOCK + r = WSAStartup(MAKEWORD(2,2), &wsa_data); + if(r != 0) { + fatal_exit("could not init winsock. WSAStartup: %s", + wsa_strerror(r)); + } +#endif /* USE_WINSOCK */ + signal_handling_record(); + checklock_start(); +#ifdef HAVE_SSL + ERR_load_crypto_strings(); + ERR_load_SSL_strings(); +# ifdef HAVE_OPENSSL_CONFIG + OPENSSL_config("unbound"); +# endif +# ifdef USE_GOST + (void)sldns_key_EVP_load_gost_id(); +# endif + OpenSSL_add_all_algorithms(); +# if HAVE_DECL_SSL_COMP_GET_COMPRESSION_METHODS + /* grab the COMP method ptr because openssl leaks it */ + comp_meth = (void*)SSL_COMP_get_compression_methods(); +# endif + (void)SSL_library_init(); +# if defined(HAVE_SSL) && defined(OPENSSL_THREADS) && !defined(THREADS_DISABLED) + if(!ub_openssl_lock_init()) + fatal_exit("could not init openssl locks"); +# endif +#elif defined(HAVE_NSS) + if(NSS_NoDB_Init(NULL) != SECSuccess) + fatal_exit("could not init NSS"); +#endif /* HAVE_SSL or HAVE_NSS */ +#ifdef HAVE_TZSET + /* init timezone info while we are not chrooted yet */ + tzset(); +#endif + /* open /dev/random if needed */ + ub_systemseed((unsigned)time(NULL)^(unsigned)getpid()^0xe67); + daemon->need_to_exit = 0; + modstack_init(&daemon->mods); + if(!(daemon->env = (struct module_env*)calloc(1, + sizeof(*daemon->env)))) { + free(daemon); + return NULL; + } + alloc_init(&daemon->superalloc, NULL, 0); + daemon->acl = acl_list_create(); + if(!daemon->acl) { + free(daemon->env); + free(daemon); + return NULL; + } + if(gettimeofday(&daemon->time_boot, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); + daemon->time_last_stat = daemon->time_boot; + return daemon; +} + +int +daemon_open_shared_ports(struct daemon* daemon) +{ + log_assert(daemon); + if(daemon->cfg->port != daemon->listening_port) { + size_t i; + struct listen_port* p0; + daemon->reuseport = 0; + /* free and close old ports */ + if(daemon->ports != NULL) { + for(i=0; i<daemon->num_ports; i++) + listening_ports_free(daemon->ports[i]); + free(daemon->ports); + daemon->ports = NULL; + } + /* see if we want to reuseport */ +#ifdef SO_REUSEPORT + if(daemon->cfg->so_reuseport && daemon->cfg->num_threads > 0) + daemon->reuseport = 1; +#endif + /* try to use reuseport */ + p0 = listening_ports_open(daemon->cfg, &daemon->reuseport); + if(!p0) { + listening_ports_free(p0); + return 0; + } + if(daemon->reuseport) { + /* reuseport was successful, allocate for it */ + daemon->num_ports = (size_t)daemon->cfg->num_threads; + } else { + /* do the normal, singleportslist thing, + * reuseport not enabled or did not work */ + daemon->num_ports = 1; + } + if(!(daemon->ports = (struct listen_port**)calloc( + daemon->num_ports, sizeof(*daemon->ports)))) { + listening_ports_free(p0); + return 0; + } + daemon->ports[0] = p0; + if(daemon->reuseport) { + /* continue to use reuseport */ + for(i=1; i<daemon->num_ports; i++) { + if(!(daemon->ports[i]= + listening_ports_open(daemon->cfg, + &daemon->reuseport)) + || !daemon->reuseport ) { + for(i=0; i<daemon->num_ports; i++) + listening_ports_free(daemon->ports[i]); + free(daemon->ports); + daemon->ports = NULL; + return 0; + } + } + } + daemon->listening_port = daemon->cfg->port; + } + if(!daemon->cfg->remote_control_enable && daemon->rc_port) { + listening_ports_free(daemon->rc_ports); + daemon->rc_ports = NULL; + daemon->rc_port = 0; + } + if(daemon->cfg->remote_control_enable && + daemon->cfg->control_port != daemon->rc_port) { + listening_ports_free(daemon->rc_ports); + if(!(daemon->rc_ports=daemon_remote_open_ports(daemon->cfg))) + return 0; + daemon->rc_port = daemon->cfg->control_port; + } + return 1; +} + +/** + * Setup modules. setup module stack. + * @param daemon: the daemon + */ +static void daemon_setup_modules(struct daemon* daemon) +{ + daemon->env->cfg = daemon->cfg; + daemon->env->alloc = &daemon->superalloc; + daemon->env->worker = NULL; + daemon->env->need_to_validate = 0; /* set by module init below */ + if(!modstack_setup(&daemon->mods, daemon->cfg->module_conf, + daemon->env)) { + fatal_exit("failed to setup modules"); + } +} + +/** + * Obtain allowed port numbers, concatenate the list, and shuffle them + * (ready to be handed out to threads). + * @param daemon: the daemon. Uses rand and cfg. + * @param shufport: the portlist output. + * @return number of ports available. + */ +static int daemon_get_shufport(struct daemon* daemon, int* shufport) +{ + int i, n, k, temp; + int avail = 0; + for(i=0; i<65536; i++) { + if(daemon->cfg->outgoing_avail_ports[i]) { + shufport[avail++] = daemon->cfg-> + outgoing_avail_ports[i]; + } + } + if(avail == 0) + fatal_exit("no ports are permitted for UDP, add " + "with outgoing-port-permit"); + /* Knuth shuffle */ + n = avail; + while(--n > 0) { + k = ub_random_max(daemon->rand, n+1); /* 0<= k<= n */ + temp = shufport[k]; + shufport[k] = shufport[n]; + shufport[n] = temp; + } + return avail; +} + +/** + * Allocate empty worker structures. With backptr and thread-number, + * from 0..numthread initialised. Used as user arguments to new threads. + * Creates the daemon random generator if it does not exist yet. + * The random generator stays existing between reloads with a unique state. + * @param daemon: the daemon with (new) config settings. + */ +static void +daemon_create_workers(struct daemon* daemon) +{ + int i, numport; + int* shufport; + log_assert(daemon && daemon->cfg); + if(!daemon->rand) { + unsigned int seed = (unsigned int)time(NULL) ^ + (unsigned int)getpid() ^ 0x438; + daemon->rand = ub_initstate(seed, NULL); + if(!daemon->rand) + fatal_exit("could not init random generator"); + } + hash_set_raninit((uint32_t)ub_random(daemon->rand)); + shufport = (int*)calloc(65536, sizeof(int)); + if(!shufport) + fatal_exit("out of memory during daemon init"); + numport = daemon_get_shufport(daemon, shufport); + verbose(VERB_ALGO, "total of %d outgoing ports available", numport); + + daemon->num = (daemon->cfg->num_threads?daemon->cfg->num_threads:1); + daemon->workers = (struct worker**)calloc((size_t)daemon->num, + sizeof(struct worker*)); + if(daemon->cfg->dnstap) { +#ifdef USE_DNSTAP + daemon->dtenv = dt_create(daemon->cfg->dnstap_socket_path, + (unsigned int)daemon->num); + if (!daemon->dtenv) + fatal_exit("dt_create failed"); + dt_apply_cfg(daemon->dtenv, daemon->cfg); +#else + fatal_exit("dnstap enabled in config but not built with dnstap support"); +#endif + } + for(i=0; i<daemon->num; i++) { + if(!(daemon->workers[i] = worker_create(daemon, i, + shufport+numport*i/daemon->num, + numport*(i+1)/daemon->num - numport*i/daemon->num))) + /* the above is not ports/numthr, due to rounding */ + fatal_exit("could not create worker"); + } + free(shufport); +} + +#ifdef THREADS_DISABLED +/** + * Close all pipes except for the numbered thread. + * @param daemon: daemon to close pipes in. + * @param thr: thread number 0..num-1 of thread to skip. + */ +static void close_other_pipes(struct daemon* daemon, int thr) +{ + int i; + for(i=0; i<daemon->num; i++) + if(i!=thr) { + if(i==0) { + /* only close read part, need to write stats */ + tube_close_read(daemon->workers[i]->cmd); + } else { + /* complete close channel to others */ + tube_delete(daemon->workers[i]->cmd); + daemon->workers[i]->cmd = NULL; + } + } +} +#endif /* THREADS_DISABLED */ + +/** + * Function to start one thread. + * @param arg: user argument. + * @return: void* user return value could be used for thread_join results. + */ +static void* +thread_start(void* arg) +{ + struct worker* worker = (struct worker*)arg; + int port_num = 0; + log_thread_set(&worker->thread_num); + ub_thread_blocksigs(); +#ifdef THREADS_DISABLED + /* close pipe ends used by main */ + tube_close_write(worker->cmd); + close_other_pipes(worker->daemon, worker->thread_num); +#endif +#ifdef SO_REUSEPORT + if(worker->daemon->cfg->so_reuseport) + port_num = worker->thread_num; + else + port_num = 0; +#endif + if(!worker_init(worker, worker->daemon->cfg, + worker->daemon->ports[port_num], 0)) + fatal_exit("Could not initialize thread"); + + worker_work(worker); + return NULL; +} + +/** + * Fork and init the other threads. Main thread returns for special handling. + * @param daemon: the daemon with other threads to fork. + */ +static void +daemon_start_others(struct daemon* daemon) +{ + int i; + log_assert(daemon); + verbose(VERB_ALGO, "start threads"); + /* skip i=0, is this thread */ + for(i=1; i<daemon->num; i++) { + ub_thread_create(&daemon->workers[i]->thr_id, + thread_start, daemon->workers[i]); +#ifdef THREADS_DISABLED + /* close pipe end of child */ + tube_close_read(daemon->workers[i]->cmd); +#endif /* no threads */ + } +} + +/** + * Stop the other threads. + * @param daemon: the daemon with other threads. + */ +static void +daemon_stop_others(struct daemon* daemon) +{ + int i; + log_assert(daemon); + verbose(VERB_ALGO, "stop threads"); + /* skip i=0, is this thread */ + /* use i=0 buffer for sending cmds; because we are #0 */ + for(i=1; i<daemon->num; i++) { + worker_send_cmd(daemon->workers[i], worker_cmd_quit); + } + /* wait for them to quit */ + for(i=1; i<daemon->num; i++) { + /* join it to make sure its dead */ + verbose(VERB_ALGO, "join %d", i); + ub_thread_join(daemon->workers[i]->thr_id); + verbose(VERB_ALGO, "join success %d", i); + } +} + +void +daemon_fork(struct daemon* daemon) +{ + log_assert(daemon); + if(!acl_list_apply_cfg(daemon->acl, daemon->cfg)) + fatal_exit("Could not setup access control list"); + if(!(daemon->local_zones = local_zones_create())) + fatal_exit("Could not create local zones: out of memory"); + if(!local_zones_apply_cfg(daemon->local_zones, daemon->cfg)) + fatal_exit("Could not set up local zones"); + + /* setup modules */ + daemon_setup_modules(daemon); + + /* first create all the worker structures, so we can pass + * them to the newly created threads. + */ + daemon_create_workers(daemon); + +#if defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP) + /* in libev the first inited base gets signals */ + if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], 1)) + fatal_exit("Could not initialize main thread"); +#endif + + /* Now create the threads and init the workers. + * By the way, this is thread #0 (the main thread). + */ + daemon_start_others(daemon); + + /* Special handling for the main thread. This is the thread + * that handles signals and remote control. + */ +#if !(defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP)) + /* libevent has the last inited base get signals (or any base) */ + if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], 1)) + fatal_exit("Could not initialize main thread"); +#endif + signal_handling_playback(daemon->workers[0]); + + /* Start resolver service on main thread. */ + log_info("start of service (%s).", PACKAGE_STRING); + worker_work(daemon->workers[0]); + log_info("service stopped (%s).", PACKAGE_STRING); + + /* we exited! a signal happened! Stop other threads */ + daemon_stop_others(daemon); + + daemon->need_to_exit = daemon->workers[0]->need_to_exit; +} + +void +daemon_cleanup(struct daemon* daemon) +{ + int i; + log_assert(daemon); + /* before stopping main worker, handle signals ourselves, so we + don't die on multiple reload signals for example. */ + signal_handling_record(); + log_thread_set(NULL); + /* clean up caches because + * a) RRset IDs will be recycled after a reload, causing collisions + * b) validation config can change, thus rrset, msg, keycache clear + * The infra cache is kept, the timing and edns info is still valid */ + slabhash_clear(&daemon->env->rrset_cache->table); + slabhash_clear(daemon->env->msg_cache); + local_zones_delete(daemon->local_zones); + daemon->local_zones = NULL; + /* key cache is cleared by module desetup during next daemon_init() */ + daemon_remote_clear(daemon->rc); + for(i=0; i<daemon->num; i++) + worker_delete(daemon->workers[i]); + free(daemon->workers); + daemon->workers = NULL; + daemon->num = 0; +#ifdef USE_DNSTAP + dt_delete(daemon->dtenv); +#endif + daemon->cfg = NULL; +} + +void +daemon_delete(struct daemon* daemon) +{ + size_t i; + if(!daemon) + return; + modstack_desetup(&daemon->mods, daemon->env); + daemon_remote_delete(daemon->rc); + for(i = 0; i < daemon->num_ports; i++) + listening_ports_free(daemon->ports[i]); + free(daemon->ports); + listening_ports_free(daemon->rc_ports); + if(daemon->env) { + slabhash_delete(daemon->env->msg_cache); + rrset_cache_delete(daemon->env->rrset_cache); + infra_delete(daemon->env->infra_cache); + } + ub_randfree(daemon->rand); + alloc_clear(&daemon->superalloc); + acl_list_delete(daemon->acl); + free(daemon->chroot); + free(daemon->pidfile); + free(daemon->env); +#ifdef HAVE_SSL + SSL_CTX_free((SSL_CTX*)daemon->listen_sslctx); + SSL_CTX_free((SSL_CTX*)daemon->connect_sslctx); +#endif + free(daemon); +#ifdef LEX_HAS_YYLEX_DESTROY + /* lex cleanup */ + ub_c_lex_destroy(); +#endif + /* libcrypto cleanup */ +#ifdef HAVE_SSL +# if defined(USE_GOST) && defined(HAVE_LDNS_KEY_EVP_UNLOAD_GOST) + sldns_key_EVP_unload_gost(); +# endif +# if HAVE_DECL_SSL_COMP_GET_COMPRESSION_METHODS && HAVE_DECL_SK_SSL_COMP_POP_FREE +# ifndef S_SPLINT_S + sk_SSL_COMP_pop_free(comp_meth, (void(*)())CRYPTO_free); +# endif +# endif +# ifdef HAVE_OPENSSL_CONFIG + EVP_cleanup(); + ENGINE_cleanup(); + CONF_modules_free(); +# endif + CRYPTO_cleanup_all_ex_data(); /* safe, no more threads right now */ + ERR_remove_state(0); + ERR_free_strings(); + RAND_cleanup(); +# if defined(HAVE_SSL) && defined(OPENSSL_THREADS) && !defined(THREADS_DISABLED) + ub_openssl_lock_delete(); +# endif +#elif defined(HAVE_NSS) + NSS_Shutdown(); +#endif /* HAVE_SSL or HAVE_NSS */ + checklock_stop(); +#ifdef USE_WINSOCK + if(WSACleanup() != 0) { + log_err("Could not WSACleanup: %s", + wsa_strerror(WSAGetLastError())); + } +#endif +} + +void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg) +{ + daemon->cfg = cfg; + config_apply(cfg); + if(!daemon->env->msg_cache || + cfg->msg_cache_size != slabhash_get_size(daemon->env->msg_cache) || + cfg->msg_cache_slabs != daemon->env->msg_cache->size) { + slabhash_delete(daemon->env->msg_cache); + daemon->env->msg_cache = slabhash_create(cfg->msg_cache_slabs, + HASH_DEFAULT_STARTARRAY, cfg->msg_cache_size, + msgreply_sizefunc, query_info_compare, + query_entry_delete, reply_info_delete, NULL); + if(!daemon->env->msg_cache) { + fatal_exit("malloc failure updating config settings"); + } + } + if((daemon->env->rrset_cache = rrset_cache_adjust( + daemon->env->rrset_cache, cfg, &daemon->superalloc)) == 0) + fatal_exit("malloc failure updating config settings"); + if((daemon->env->infra_cache = infra_adjust(daemon->env->infra_cache, + cfg))==0) + fatal_exit("malloc failure updating config settings"); +} diff --git a/external/unbound/daemon/daemon.h b/external/unbound/daemon/daemon.h new file mode 100644 index 000000000..86ddab1df --- /dev/null +++ b/external/unbound/daemon/daemon.h @@ -0,0 +1,164 @@ +/* + * daemon/daemon.h - collection of workers that handles requests. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * The daemon consists of global settings and a number of workers. + */ + +#ifndef DAEMON_H +#define DAEMON_H + +#include "util/locks.h" +#include "util/alloc.h" +#include "services/modstack.h" +#ifdef UB_ON_WINDOWS +# include "util/winsock_event.h" +#endif +struct config_file; +struct worker; +struct listen_port; +struct slabhash; +struct module_env; +struct rrset_cache; +struct acl_list; +struct local_zones; +struct ub_randstate; +struct daemon_remote; + +#include "dnstap/dnstap_config.h" +#ifdef USE_DNSTAP +struct dt_env; +#endif + +/** + * Structure holding worker list. + * Holds globally visible information. + */ +struct daemon { + /** The config settings */ + struct config_file* cfg; + /** the chroot dir in use, NULL if none */ + char* chroot; + /** pidfile that is used */ + char* pidfile; + /** port number that has ports opened. */ + int listening_port; + /** array of listening ports, opened. Listening ports per worker, + * or just one element[0] shared by the worker threads. */ + struct listen_port** ports; + /** size of ports array */ + size_t num_ports; + /** reuseport is enabled if true */ + int reuseport; + /** port number for remote that has ports opened. */ + int rc_port; + /** listening ports for remote control */ + struct listen_port* rc_ports; + /** remote control connections management (for first worker) */ + struct daemon_remote* rc; + /** ssl context for listening to dnstcp over ssl, and connecting ssl */ + void* listen_sslctx, *connect_sslctx; + /** num threads allocated */ + int num; + /** the worker entries */ + struct worker** workers; + /** do we need to exit unbound (or is it only a reload?) */ + int need_to_exit; + /** master random table ; used for port div between threads on reload*/ + struct ub_randstate* rand; + /** master allocation cache */ + struct alloc_cache superalloc; + /** the module environment master value, copied and changed by threads*/ + struct module_env* env; + /** stack of module callbacks */ + struct module_stack mods; + /** access control, which client IPs are allowed to connect */ + struct acl_list* acl; + /** local authority zones */ + struct local_zones* local_zones; + /** last time of statistics printout */ + struct timeval time_last_stat; + /** time when daemon started */ + struct timeval time_boot; +#ifdef USE_DNSTAP + /** the dnstap environment master value, copied and changed by threads*/ + struct dt_env* dtenv; +#endif +}; + +/** + * Initialize daemon structure. + * @return: The daemon structure, or NULL on error. + */ +struct daemon* daemon_init(void); + +/** + * Open shared listening ports (if needed). + * The cfg member pointer must have been set for the daemon. + * @param daemon: the daemon. + * @return: false on error. + */ +int daemon_open_shared_ports(struct daemon* daemon); + +/** + * Fork workers and start service. + * When the routine exits, it is no longer forked. + * @param daemon: the daemon. + */ +void daemon_fork(struct daemon* daemon); + +/** + * Close off the worker thread information. + * Bring the daemon back into state ready for daemon_fork again. + * @param daemon: the daemon. + */ +void daemon_cleanup(struct daemon* daemon); + +/** + * Delete workers, close listening ports. + * @param daemon: the daemon. + */ +void daemon_delete(struct daemon* daemon); + +/** + * Apply config settings. + * @param daemon: the daemon. + * @param cfg: new config settings. + */ +void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg); + +#endif /* DAEMON_H */ diff --git a/external/unbound/daemon/remote.c b/external/unbound/daemon/remote.c new file mode 100644 index 000000000..100aa8be1 --- /dev/null +++ b/external/unbound/daemon/remote.c @@ -0,0 +1,2449 @@ +/* + * daemon/remote.c - remote control for the unbound daemon. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains the remote control functionality for the daemon. + * The remote control can be performed using either the commandline + * unbound-control tool, or a SSLv3/TLS capable web browser. + * The channel is secured using SSLv3 or TLSv1, and certificates. + * Both the server and the client(control tool) have their own keys. + */ +#include "config.h" +#ifdef HAVE_OPENSSL_ERR_H +#include <openssl/err.h> +#endif +#include <ctype.h> +#include "daemon/remote.h" +#include "daemon/worker.h" +#include "daemon/daemon.h" +#include "daemon/stats.h" +#include "daemon/cachedump.h" +#include "util/log.h" +#include "util/config_file.h" +#include "util/net_help.h" +#include "util/module.h" +#include "services/listen_dnsport.h" +#include "services/cache/rrset.h" +#include "services/cache/infra.h" +#include "services/mesh.h" +#include "services/localzone.h" +#include "util/storage/slabhash.h" +#include "util/fptr_wlist.h" +#include "util/data/dname.h" +#include "validator/validator.h" +#include "validator/val_kcache.h" +#include "validator/val_kentry.h" +#include "validator/val_anchor.h" +#include "iterator/iterator.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" +#include "iterator/iter_delegpt.h" +#include "services/outbound_list.h" +#include "services/outside_network.h" +#include "ldns/str2wire.h" +#include "ldns/parseutil.h" +#include "ldns/wire2str.h" +#include "ldns/sbuffer.h" + +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +/* just for portability */ +#ifdef SQ +#undef SQ +#endif + +/** what to put on statistics lines between var and value, ": " or "=" */ +#define SQ "=" +/** if true, inhibits a lot of =0 lines from the stats output */ +static const int inhibit_zero = 1; + +/** subtract timers and the values do not overflow or become negative */ +static void +timeval_subtract(struct timeval* d, const struct timeval* end, + const struct timeval* start) +{ +#ifndef S_SPLINT_S + time_t end_usec = end->tv_usec; + d->tv_sec = end->tv_sec - start->tv_sec; + if(end_usec < start->tv_usec) { + end_usec += 1000000; + d->tv_sec--; + } + d->tv_usec = end_usec - start->tv_usec; +#endif +} + +/** divide sum of timers to get average */ +static void +timeval_divide(struct timeval* avg, const struct timeval* sum, size_t d) +{ +#ifndef S_SPLINT_S + size_t leftover; + if(d == 0) { + avg->tv_sec = 0; + avg->tv_usec = 0; + return; + } + avg->tv_sec = sum->tv_sec / d; + avg->tv_usec = sum->tv_usec / d; + /* handle fraction from seconds divide */ + leftover = sum->tv_sec - avg->tv_sec*d; + avg->tv_usec += (leftover*1000000)/d; +#endif +} + +struct daemon_remote* +daemon_remote_create(struct config_file* cfg) +{ + char* s_cert; + char* s_key; + struct daemon_remote* rc = (struct daemon_remote*)calloc(1, + sizeof(*rc)); + if(!rc) { + log_err("out of memory in daemon_remote_create"); + return NULL; + } + rc->max_active = 10; + + if(!cfg->remote_control_enable) { + rc->ctx = NULL; + return rc; + } + rc->ctx = SSL_CTX_new(SSLv23_server_method()); + if(!rc->ctx) { + log_crypto_err("could not SSL_CTX_new"); + free(rc); + return NULL; + } + /* no SSLv2 because has defects */ + if(!(SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2)){ + log_crypto_err("could not set SSL_OP_NO_SSLv2"); + daemon_remote_delete(rc); + return NULL; + } + s_cert = fname_after_chroot(cfg->server_cert_file, cfg, 1); + s_key = fname_after_chroot(cfg->server_key_file, cfg, 1); + if(!s_cert || !s_key) { + log_err("out of memory in remote control fname"); + goto setup_error; + } + verbose(VERB_ALGO, "setup SSL certificates"); + if (!SSL_CTX_use_certificate_file(rc->ctx,s_cert,SSL_FILETYPE_PEM)) { + log_err("Error for server-cert-file: %s", s_cert); + log_crypto_err("Error in SSL_CTX use_certificate_file"); + goto setup_error; + } + if(!SSL_CTX_use_PrivateKey_file(rc->ctx,s_key,SSL_FILETYPE_PEM)) { + log_err("Error for server-key-file: %s", s_key); + log_crypto_err("Error in SSL_CTX use_PrivateKey_file"); + goto setup_error; + } + if(!SSL_CTX_check_private_key(rc->ctx)) { + log_err("Error for server-key-file: %s", s_key); + log_crypto_err("Error in SSL_CTX check_private_key"); + goto setup_error; + } + if(!SSL_CTX_load_verify_locations(rc->ctx, s_cert, NULL)) { + log_crypto_err("Error setting up SSL_CTX verify locations"); + setup_error: + free(s_cert); + free(s_key); + daemon_remote_delete(rc); + return NULL; + } + SSL_CTX_set_client_CA_list(rc->ctx, SSL_load_client_CA_file(s_cert)); + SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL); + free(s_cert); + free(s_key); + + return rc; +} + +void daemon_remote_clear(struct daemon_remote* rc) +{ + struct rc_state* p, *np; + if(!rc) return; + /* but do not close the ports */ + listen_list_delete(rc->accept_list); + rc->accept_list = NULL; + /* do close these sockets */ + p = rc->busy_list; + while(p) { + np = p->next; + if(p->ssl) + SSL_free(p->ssl); + comm_point_delete(p->c); + free(p); + p = np; + } + rc->busy_list = NULL; + rc->active = 0; + rc->worker = NULL; +} + +void daemon_remote_delete(struct daemon_remote* rc) +{ + if(!rc) return; + daemon_remote_clear(rc); + if(rc->ctx) { + SSL_CTX_free(rc->ctx); + } + free(rc); +} + +/** + * Add and open a new control port + * @param ip: ip str + * @param nr: port nr + * @param list: list head + * @param noproto_is_err: if lack of protocol support is an error. + * @return false on failure. + */ +static int +add_open(const char* ip, int nr, struct listen_port** list, int noproto_is_err) +{ + struct addrinfo hints; + struct addrinfo* res; + struct listen_port* n; + int noproto; + int fd, r; + char port[15]; + snprintf(port, sizeof(port), "%d", nr); + port[sizeof(port)-1]=0; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; + if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) { +#ifdef USE_WINSOCK + if(!noproto_is_err && r == EAI_NONAME) { + /* tried to lookup the address as name */ + return 1; /* return success, but do nothing */ + } +#endif /* USE_WINSOCK */ + log_err("control interface %s:%s getaddrinfo: %s %s", + ip?ip:"default", port, gai_strerror(r), +#ifdef EAI_SYSTEM + r==EAI_SYSTEM?(char*)strerror(errno):"" +#else + "" +#endif + ); + return 0; + } + + /* open fd */ + fd = create_tcp_accept_sock(res, 1, &noproto, 0); + freeaddrinfo(res); + if(fd == -1 && noproto) { + if(!noproto_is_err) + return 1; /* return success, but do nothing */ + log_err("cannot open control interface %s %d : " + "protocol not supported", ip, nr); + return 0; + } + if(fd == -1) { + log_err("cannot open control interface %s %d", ip, nr); + return 0; + } + + /* alloc */ + n = (struct listen_port*)calloc(1, sizeof(*n)); + if(!n) { +#ifndef USE_WINSOCK + close(fd); +#else + closesocket(fd); +#endif + log_err("out of memory"); + return 0; + } + n->next = *list; + *list = n; + n->fd = fd; + return 1; +} + +struct listen_port* daemon_remote_open_ports(struct config_file* cfg) +{ + struct listen_port* l = NULL; + log_assert(cfg->remote_control_enable && cfg->control_port); + if(cfg->control_ifs) { + struct config_strlist* p; + for(p = cfg->control_ifs; p; p = p->next) { + if(!add_open(p->str, cfg->control_port, &l, 1)) { + listening_ports_free(l); + return NULL; + } + } + } else { + /* defaults */ + if(cfg->do_ip6 && + !add_open("::1", cfg->control_port, &l, 0)) { + listening_ports_free(l); + return NULL; + } + if(cfg->do_ip4 && + !add_open("127.0.0.1", cfg->control_port, &l, 1)) { + listening_ports_free(l); + return NULL; + } + } + return l; +} + +/** open accept commpoint */ +static int +accept_open(struct daemon_remote* rc, int fd) +{ + struct listen_list* n = (struct listen_list*)malloc(sizeof(*n)); + if(!n) { + log_err("out of memory"); + return 0; + } + n->next = rc->accept_list; + rc->accept_list = n; + /* open commpt */ + n->com = comm_point_create_raw(rc->worker->base, fd, 0, + &remote_accept_callback, rc); + if(!n->com) + return 0; + /* keep this port open, its fd is kept in the rc portlist */ + n->com->do_not_close = 1; + return 1; +} + +int daemon_remote_open_accept(struct daemon_remote* rc, + struct listen_port* ports, struct worker* worker) +{ + struct listen_port* p; + rc->worker = worker; + for(p = ports; p; p = p->next) { + if(!accept_open(rc, p->fd)) { + log_err("could not create accept comm point"); + return 0; + } + } + return 1; +} + +void daemon_remote_stop_accept(struct daemon_remote* rc) +{ + struct listen_list* p; + for(p=rc->accept_list; p; p=p->next) { + comm_point_stop_listening(p->com); + } +} + +void daemon_remote_start_accept(struct daemon_remote* rc) +{ + struct listen_list* p; + for(p=rc->accept_list; p; p=p->next) { + comm_point_start_listening(p->com, -1, -1); + } +} + +int remote_accept_callback(struct comm_point* c, void* arg, int err, + struct comm_reply* ATTR_UNUSED(rep)) +{ + struct daemon_remote* rc = (struct daemon_remote*)arg; + struct sockaddr_storage addr; + socklen_t addrlen; + int newfd; + struct rc_state* n; + if(err != NETEVENT_NOERROR) { + log_err("error %d on remote_accept_callback", err); + return 0; + } + /* perform the accept */ + newfd = comm_point_perform_accept(c, &addr, &addrlen); + if(newfd == -1) + return 0; + /* create new commpoint unless we are servicing already */ + if(rc->active >= rc->max_active) { + log_warn("drop incoming remote control: too many connections"); + close_exit: +#ifndef USE_WINSOCK + close(newfd); +#else + closesocket(newfd); +#endif + return 0; + } + + /* setup commpoint to service the remote control command */ + n = (struct rc_state*)calloc(1, sizeof(*n)); + if(!n) { + log_err("out of memory"); + goto close_exit; + } + /* start in reading state */ + n->c = comm_point_create_raw(rc->worker->base, newfd, 0, + &remote_control_callback, n); + if(!n->c) { + log_err("out of memory"); + free(n); + goto close_exit; + } + log_addr(VERB_QUERY, "new control connection from", &addr, addrlen); + n->c->do_not_close = 0; + comm_point_stop_listening(n->c); + comm_point_start_listening(n->c, -1, REMOTE_CONTROL_TCP_TIMEOUT); + memcpy(&n->c->repinfo.addr, &addr, addrlen); + n->c->repinfo.addrlen = addrlen; + n->shake_state = rc_hs_read; + n->ssl = SSL_new(rc->ctx); + if(!n->ssl) { + log_crypto_err("could not SSL_new"); + comm_point_delete(n->c); + free(n); + goto close_exit; + } + SSL_set_accept_state(n->ssl); + (void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(n->ssl, newfd)) { + log_crypto_err("could not SSL_set_fd"); + SSL_free(n->ssl); + comm_point_delete(n->c); + free(n); + goto close_exit; + } + + n->rc = rc; + n->next = rc->busy_list; + rc->busy_list = n; + rc->active ++; + + /* perform the first nonblocking read already, for windows, + * so it can return wouldblock. could be faster too. */ + (void)remote_control_callback(n->c, n, NETEVENT_NOERROR, NULL); + return 0; +} + +/** delete from list */ +static void +state_list_remove_elem(struct rc_state** list, struct comm_point* c) +{ + while(*list) { + if( (*list)->c == c) { + *list = (*list)->next; + return; + } + list = &(*list)->next; + } +} + +/** decrease active count and remove commpoint from busy list */ +static void +clean_point(struct daemon_remote* rc, struct rc_state* s) +{ + state_list_remove_elem(&rc->busy_list, s->c); + rc->active --; + if(s->ssl) { + SSL_shutdown(s->ssl); + SSL_free(s->ssl); + } + comm_point_delete(s->c); + free(s); +} + +int +ssl_print_text(SSL* ssl, const char* text) +{ + int r; + if(!ssl) + return 0; + ERR_clear_error(); + if((r=SSL_write(ssl, text, (int)strlen(text))) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { + verbose(VERB_QUERY, "warning, in SSL_write, peer " + "closed connection"); + return 0; + } + log_crypto_err("could not SSL_write"); + return 0; + } + return 1; +} + +/** print text over the ssl connection */ +static int +ssl_print_vmsg(SSL* ssl, const char* format, va_list args) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), format, args); + return ssl_print_text(ssl, msg); +} + +/** printf style printing to the ssl connection */ +int ssl_printf(SSL* ssl, const char* format, ...) +{ + va_list args; + int ret; + va_start(args, format); + ret = ssl_print_vmsg(ssl, format, args); + va_end(args); + return ret; +} + +int +ssl_read_line(SSL* ssl, char* buf, size_t max) +{ + int r; + size_t len = 0; + if(!ssl) + return 0; + while(len < max) { + ERR_clear_error(); + if((r=SSL_read(ssl, buf+len, 1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { + buf[len] = 0; + return 1; + } + log_crypto_err("could not SSL_read"); + return 0; + } + if(buf[len] == '\n') { + /* return string without \n */ + buf[len] = 0; + return 1; + } + len++; + } + buf[max-1] = 0; + log_err("control line too long (%d): %s", (int)max, buf); + return 0; +} + +/** skip whitespace, return new pointer into string */ +static char* +skipwhite(char* str) +{ + /* EOS \0 is not a space */ + while( isspace(*str) ) + str++; + return str; +} + +/** send the OK to the control client */ +static void send_ok(SSL* ssl) +{ + (void)ssl_printf(ssl, "ok\n"); +} + +/** do the stop command */ +static void +do_stop(SSL* ssl, struct daemon_remote* rc) +{ + rc->worker->need_to_exit = 1; + comm_base_exit(rc->worker->base); + send_ok(ssl); +} + +/** do the reload command */ +static void +do_reload(SSL* ssl, struct daemon_remote* rc) +{ + rc->worker->need_to_exit = 0; + comm_base_exit(rc->worker->base); + send_ok(ssl); +} + +/** do the verbosity command */ +static void +do_verbosity(SSL* ssl, char* str) +{ + int val = atoi(str); + if(val == 0 && strcmp(str, "0") != 0) { + ssl_printf(ssl, "error in verbosity number syntax: %s\n", str); + return; + } + verbosity = val; + send_ok(ssl); +} + +/** print stats from statinfo */ +static int +print_stats(SSL* ssl, const char* nm, struct stats_info* s) +{ + struct timeval avg; + if(!ssl_printf(ssl, "%s.num.queries"SQ"%lu\n", nm, + (unsigned long)s->svr.num_queries)) return 0; + if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm, + (unsigned long)(s->svr.num_queries + - s->svr.num_queries_missed_cache))) return 0; + if(!ssl_printf(ssl, "%s.num.cachemiss"SQ"%lu\n", nm, + (unsigned long)s->svr.num_queries_missed_cache)) return 0; + if(!ssl_printf(ssl, "%s.num.prefetch"SQ"%lu\n", nm, + (unsigned long)s->svr.num_queries_prefetch)) return 0; + if(!ssl_printf(ssl, "%s.num.recursivereplies"SQ"%lu\n", nm, + (unsigned long)s->mesh_replies_sent)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.avg"SQ"%g\n", nm, + (s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)? + (double)s->svr.sum_query_list_size/ + (s->svr.num_queries_missed_cache+ + s->svr.num_queries_prefetch) : 0.0)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.max"SQ"%lu\n", nm, + (unsigned long)s->svr.max_query_list_size)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.overwritten"SQ"%lu\n", nm, + (unsigned long)s->mesh_jostled)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.exceeded"SQ"%lu\n", nm, + (unsigned long)s->mesh_dropped)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.current.all"SQ"%lu\n", nm, + (unsigned long)s->mesh_num_states)) return 0; + if(!ssl_printf(ssl, "%s.requestlist.current.user"SQ"%lu\n", nm, + (unsigned long)s->mesh_num_reply_states)) return 0; + timeval_divide(&avg, &s->mesh_replies_sum_wait, s->mesh_replies_sent); + if(!ssl_printf(ssl, "%s.recursion.time.avg"SQ ARG_LL "d.%6.6d\n", nm, + (long long)avg.tv_sec, (int)avg.tv_usec)) return 0; + if(!ssl_printf(ssl, "%s.recursion.time.median"SQ"%g\n", nm, + s->mesh_time_median)) return 0; + return 1; +} + +/** print stats for one thread */ +static int +print_thread_stats(SSL* ssl, int i, struct stats_info* s) +{ + char nm[16]; + snprintf(nm, sizeof(nm), "thread%d", i); + nm[sizeof(nm)-1]=0; + return print_stats(ssl, nm, s); +} + +/** print long number */ +static int +print_longnum(SSL* ssl, const char* desc, size_t x) +{ + if(x > 1024*1024*1024) { + /* more than a Gb */ + size_t front = x / (size_t)1000000; + size_t back = x % (size_t)1000000; + return ssl_printf(ssl, "%s%u%6.6u\n", desc, + (unsigned)front, (unsigned)back); + } else { + return ssl_printf(ssl, "%s%lu\n", desc, (unsigned long)x); + } +} + +/** print mem stats */ +static int +print_mem(SSL* ssl, struct worker* worker, struct daemon* daemon) +{ + int m; + size_t msg, rrset, val, iter; +#ifdef HAVE_SBRK + extern void* unbound_start_brk; + void* cur = sbrk(0); + if(!print_longnum(ssl, "mem.total.sbrk"SQ, + (size_t)((char*)cur - (char*)unbound_start_brk))) return 0; +#endif /* HAVE_SBRK */ + msg = slabhash_get_mem(daemon->env->msg_cache); + rrset = slabhash_get_mem(&daemon->env->rrset_cache->table); + val=0; + iter=0; + m = modstack_find(&worker->env.mesh->mods, "validator"); + if(m != -1) { + fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> + mods.mod[m]->get_mem)); + val = (*worker->env.mesh->mods.mod[m]->get_mem) + (&worker->env, m); + } + m = modstack_find(&worker->env.mesh->mods, "iterator"); + if(m != -1) { + fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> + mods.mod[m]->get_mem)); + iter = (*worker->env.mesh->mods.mod[m]->get_mem) + (&worker->env, m); + } + + if(!print_longnum(ssl, "mem.cache.rrset"SQ, rrset)) + return 0; + if(!print_longnum(ssl, "mem.cache.message"SQ, msg)) + return 0; + if(!print_longnum(ssl, "mem.mod.iterator"SQ, iter)) + return 0; + if(!print_longnum(ssl, "mem.mod.validator"SQ, val)) + return 0; + return 1; +} + +/** print uptime stats */ +static int +print_uptime(SSL* ssl, struct worker* worker, int reset) +{ + struct timeval now = *worker->env.now_tv; + struct timeval up, dt; + timeval_subtract(&up, &now, &worker->daemon->time_boot); + timeval_subtract(&dt, &now, &worker->daemon->time_last_stat); + if(reset) + worker->daemon->time_last_stat = now; + if(!ssl_printf(ssl, "time.now"SQ ARG_LL "d.%6.6d\n", + (long long)now.tv_sec, (unsigned)now.tv_usec)) return 0; + if(!ssl_printf(ssl, "time.up"SQ ARG_LL "d.%6.6d\n", + (long long)up.tv_sec, (unsigned)up.tv_usec)) return 0; + if(!ssl_printf(ssl, "time.elapsed"SQ ARG_LL "d.%6.6d\n", + (long long)dt.tv_sec, (unsigned)dt.tv_usec)) return 0; + return 1; +} + +/** print extended histogram */ +static int +print_hist(SSL* ssl, struct stats_info* s) +{ + struct timehist* hist; + size_t i; + hist = timehist_setup(); + if(!hist) { + log_err("out of memory"); + return 0; + } + timehist_import(hist, s->svr.hist, NUM_BUCKETS_HIST); + for(i=0; i<hist->num; i++) { + if(!ssl_printf(ssl, + "histogram.%6.6d.%6.6d.to.%6.6d.%6.6d=%lu\n", + (int)hist->buckets[i].lower.tv_sec, + (int)hist->buckets[i].lower.tv_usec, + (int)hist->buckets[i].upper.tv_sec, + (int)hist->buckets[i].upper.tv_usec, + (unsigned long)hist->buckets[i].count)) { + timehist_delete(hist); + return 0; + } + } + timehist_delete(hist); + return 1; +} + +/** print extended stats */ +static int +print_ext(SSL* ssl, struct stats_info* s) +{ + int i; + char nm[16]; + const sldns_rr_descriptor* desc; + const sldns_lookup_table* lt; + /* TYPE */ + for(i=0; i<STATS_QTYPE_NUM; i++) { + if(inhibit_zero && s->svr.qtype[i] == 0) + continue; + desc = sldns_rr_descript((uint16_t)i); + if(desc && desc->_name) { + snprintf(nm, sizeof(nm), "%s", desc->_name); + } else if (i == LDNS_RR_TYPE_IXFR) { + snprintf(nm, sizeof(nm), "IXFR"); + } else if (i == LDNS_RR_TYPE_AXFR) { + snprintf(nm, sizeof(nm), "AXFR"); + } else if (i == LDNS_RR_TYPE_MAILA) { + snprintf(nm, sizeof(nm), "MAILA"); + } else if (i == LDNS_RR_TYPE_MAILB) { + snprintf(nm, sizeof(nm), "MAILB"); + } else if (i == LDNS_RR_TYPE_ANY) { + snprintf(nm, sizeof(nm), "ANY"); + } else { + snprintf(nm, sizeof(nm), "TYPE%d", i); + } + if(!ssl_printf(ssl, "num.query.type.%s"SQ"%lu\n", + nm, (unsigned long)s->svr.qtype[i])) return 0; + } + if(!inhibit_zero || s->svr.qtype_big) { + if(!ssl_printf(ssl, "num.query.type.other"SQ"%lu\n", + (unsigned long)s->svr.qtype_big)) return 0; + } + /* CLASS */ + for(i=0; i<STATS_QCLASS_NUM; i++) { + if(inhibit_zero && s->svr.qclass[i] == 0) + continue; + lt = sldns_lookup_by_id(sldns_rr_classes, i); + if(lt && lt->name) { + snprintf(nm, sizeof(nm), "%s", lt->name); + } else { + snprintf(nm, sizeof(nm), "CLASS%d", i); + } + if(!ssl_printf(ssl, "num.query.class.%s"SQ"%lu\n", + nm, (unsigned long)s->svr.qclass[i])) return 0; + } + if(!inhibit_zero || s->svr.qclass_big) { + if(!ssl_printf(ssl, "num.query.class.other"SQ"%lu\n", + (unsigned long)s->svr.qclass_big)) return 0; + } + /* OPCODE */ + for(i=0; i<STATS_OPCODE_NUM; i++) { + if(inhibit_zero && s->svr.qopcode[i] == 0) + continue; + lt = sldns_lookup_by_id(sldns_opcodes, i); + if(lt && lt->name) { + snprintf(nm, sizeof(nm), "%s", lt->name); + } else { + snprintf(nm, sizeof(nm), "OPCODE%d", i); + } + if(!ssl_printf(ssl, "num.query.opcode.%s"SQ"%lu\n", + nm, (unsigned long)s->svr.qopcode[i])) return 0; + } + /* transport */ + if(!ssl_printf(ssl, "num.query.tcp"SQ"%lu\n", + (unsigned long)s->svr.qtcp)) return 0; + if(!ssl_printf(ssl, "num.query.tcpout"SQ"%lu\n", + (unsigned long)s->svr.qtcp_outgoing)) return 0; + if(!ssl_printf(ssl, "num.query.ipv6"SQ"%lu\n", + (unsigned long)s->svr.qipv6)) return 0; + /* flags */ + if(!ssl_printf(ssl, "num.query.flags.QR"SQ"%lu\n", + (unsigned long)s->svr.qbit_QR)) return 0; + if(!ssl_printf(ssl, "num.query.flags.AA"SQ"%lu\n", + (unsigned long)s->svr.qbit_AA)) return 0; + if(!ssl_printf(ssl, "num.query.flags.TC"SQ"%lu\n", + (unsigned long)s->svr.qbit_TC)) return 0; + if(!ssl_printf(ssl, "num.query.flags.RD"SQ"%lu\n", + (unsigned long)s->svr.qbit_RD)) return 0; + if(!ssl_printf(ssl, "num.query.flags.RA"SQ"%lu\n", + (unsigned long)s->svr.qbit_RA)) return 0; + if(!ssl_printf(ssl, "num.query.flags.Z"SQ"%lu\n", + (unsigned long)s->svr.qbit_Z)) return 0; + if(!ssl_printf(ssl, "num.query.flags.AD"SQ"%lu\n", + (unsigned long)s->svr.qbit_AD)) return 0; + if(!ssl_printf(ssl, "num.query.flags.CD"SQ"%lu\n", + (unsigned long)s->svr.qbit_CD)) return 0; + if(!ssl_printf(ssl, "num.query.edns.present"SQ"%lu\n", + (unsigned long)s->svr.qEDNS)) return 0; + if(!ssl_printf(ssl, "num.query.edns.DO"SQ"%lu\n", + (unsigned long)s->svr.qEDNS_DO)) return 0; + + /* RCODE */ + for(i=0; i<STATS_RCODE_NUM; i++) { + if(inhibit_zero && s->svr.ans_rcode[i] == 0) + continue; + lt = sldns_lookup_by_id(sldns_rcodes, i); + if(lt && lt->name) { + snprintf(nm, sizeof(nm), "%s", lt->name); + } else { + snprintf(nm, sizeof(nm), "RCODE%d", i); + } + if(!ssl_printf(ssl, "num.answer.rcode.%s"SQ"%lu\n", + nm, (unsigned long)s->svr.ans_rcode[i])) return 0; + } + if(!inhibit_zero || s->svr.ans_rcode_nodata) { + if(!ssl_printf(ssl, "num.answer.rcode.nodata"SQ"%lu\n", + (unsigned long)s->svr.ans_rcode_nodata)) return 0; + } + /* validation */ + if(!ssl_printf(ssl, "num.answer.secure"SQ"%lu\n", + (unsigned long)s->svr.ans_secure)) return 0; + if(!ssl_printf(ssl, "num.answer.bogus"SQ"%lu\n", + (unsigned long)s->svr.ans_bogus)) return 0; + if(!ssl_printf(ssl, "num.rrset.bogus"SQ"%lu\n", + (unsigned long)s->svr.rrset_bogus)) return 0; + /* threat detection */ + if(!ssl_printf(ssl, "unwanted.queries"SQ"%lu\n", + (unsigned long)s->svr.unwanted_queries)) return 0; + if(!ssl_printf(ssl, "unwanted.replies"SQ"%lu\n", + (unsigned long)s->svr.unwanted_replies)) return 0; + /* cache counts */ + if(!ssl_printf(ssl, "msg.cache.count"SQ"%u\n", + (unsigned)s->svr.msg_cache_count)) return 0; + if(!ssl_printf(ssl, "rrset.cache.count"SQ"%u\n", + (unsigned)s->svr.rrset_cache_count)) return 0; + if(!ssl_printf(ssl, "infra.cache.count"SQ"%u\n", + (unsigned)s->svr.infra_cache_count)) return 0; + if(!ssl_printf(ssl, "key.cache.count"SQ"%u\n", + (unsigned)s->svr.key_cache_count)) return 0; + return 1; +} + +/** do the stats command */ +static void +do_stats(SSL* ssl, struct daemon_remote* rc, int reset) +{ + struct daemon* daemon = rc->worker->daemon; + struct stats_info total; + struct stats_info s; + int i; + log_assert(daemon->num > 0); + /* gather all thread statistics in one place */ + for(i=0; i<daemon->num; i++) { + server_stats_obtain(rc->worker, daemon->workers[i], &s, reset); + if(!print_thread_stats(ssl, i, &s)) + return; + if(i == 0) + total = s; + else server_stats_add(&total, &s); + } + /* print the thread statistics */ + total.mesh_time_median /= (double)daemon->num; + if(!print_stats(ssl, "total", &total)) + return; + if(!print_uptime(ssl, rc->worker, reset)) + return; + if(daemon->cfg->stat_extended) { + if(!print_mem(ssl, rc->worker, daemon)) + return; + if(!print_hist(ssl, &total)) + return; + if(!print_ext(ssl, &total)) + return; + } +} + +/** parse commandline argument domain name */ +static int +parse_arg_name(SSL* ssl, char* str, uint8_t** res, size_t* len, int* labs) +{ + uint8_t nm[LDNS_MAX_DOMAINLEN+1]; + size_t nmlen = sizeof(nm); + int status; + *res = NULL; + *len = 0; + *labs = 0; + status = sldns_str2wire_dname_buf(str, nm, &nmlen); + if(status != 0) { + ssl_printf(ssl, "error cannot parse name %s at %d: %s\n", str, + LDNS_WIREPARSE_OFFSET(status), + sldns_get_errorstr_parse(status)); + return 0; + } + *res = memdup(nm, nmlen); + if(!*res) { + ssl_printf(ssl, "error out of memory\n"); + return 0; + } + *labs = dname_count_size_labels(*res, len); + return 1; +} + +/** find second argument, modifies string */ +static int +find_arg2(SSL* ssl, char* arg, char** arg2) +{ + char* as = strchr(arg, ' '); + char* at = strchr(arg, '\t'); + if(as && at) { + if(at < as) + as = at; + as[0]=0; + *arg2 = skipwhite(as+1); + } else if(as) { + as[0]=0; + *arg2 = skipwhite(as+1); + } else if(at) { + at[0]=0; + *arg2 = skipwhite(at+1); + } else { + ssl_printf(ssl, "error could not find next argument " + "after %s\n", arg); + return 0; + } + return 1; +} + +/** Add a new zone */ +static void +do_zone_add(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + char* arg2; + enum localzone_type t; + struct local_zone* z; + if(!find_arg2(ssl, arg, &arg2)) + return; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + if(!local_zone_str2type(arg2, &t)) { + ssl_printf(ssl, "error not a zone type. %s\n", arg2); + free(nm); + return; + } + lock_rw_wrlock(&worker->daemon->local_zones->lock); + if((z=local_zones_find(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN))) { + /* already present in tree */ + lock_rw_wrlock(&z->lock); + z->type = t; /* update type anyway */ + lock_rw_unlock(&z->lock); + free(nm); + lock_rw_unlock(&worker->daemon->local_zones->lock); + send_ok(ssl); + return; + } + if(!local_zones_add_zone(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN, t)) { + lock_rw_unlock(&worker->daemon->local_zones->lock); + ssl_printf(ssl, "error out of memory\n"); + return; + } + lock_rw_unlock(&worker->daemon->local_zones->lock); + send_ok(ssl); +} + +/** Remove a zone */ +static void +do_zone_remove(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + struct local_zone* z; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + lock_rw_wrlock(&worker->daemon->local_zones->lock); + if((z=local_zones_find(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN))) { + /* present in tree */ + local_zones_del_zone(worker->daemon->local_zones, z); + } + lock_rw_unlock(&worker->daemon->local_zones->lock); + free(nm); + send_ok(ssl); +} + +/** Add new RR data */ +static void +do_data_add(SSL* ssl, struct worker* worker, char* arg) +{ + if(!local_zones_add_RR(worker->daemon->local_zones, arg)) { + ssl_printf(ssl,"error in syntax or out of memory, %s\n", arg); + return; + } + send_ok(ssl); +} + +/** Remove RR data */ +static void +do_data_remove(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + local_zones_del_data(worker->daemon->local_zones, nm, + nmlen, nmlabs, LDNS_RR_CLASS_IN); + free(nm); + send_ok(ssl); +} + +/** cache lookup of nameservers */ +static void +do_lookup(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + (void)print_deleg_lookup(ssl, worker, nm, nmlen, nmlabs); + free(nm); +} + +/** flush something from rrset and msg caches */ +static void +do_cache_remove(struct worker* worker, uint8_t* nm, size_t nmlen, + uint16_t t, uint16_t c) +{ + hashvalue_t h; + struct query_info k; + rrset_cache_remove(worker->env.rrset_cache, nm, nmlen, t, c, 0); + if(t == LDNS_RR_TYPE_SOA) + rrset_cache_remove(worker->env.rrset_cache, nm, nmlen, t, c, + PACKED_RRSET_SOA_NEG); + k.qname = nm; + k.qname_len = nmlen; + k.qtype = t; + k.qclass = c; + h = query_info_hash(&k); + slabhash_remove(worker->env.msg_cache, h, &k); +} + +/** flush a type */ +static void +do_flush_type(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + char* arg2; + uint16_t t; + if(!find_arg2(ssl, arg, &arg2)) + return; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + t = sldns_get_rr_type_by_name(arg2); + do_cache_remove(worker, nm, nmlen, t, LDNS_RR_CLASS_IN); + + free(nm); + send_ok(ssl); +} + +/** flush statistics */ +static void +do_flush_stats(SSL* ssl, struct worker* worker) +{ + worker_stats_clear(worker); + send_ok(ssl); +} + +/** + * Local info for deletion functions + */ +struct del_info { + /** worker */ + struct worker* worker; + /** name to delete */ + uint8_t* name; + /** length */ + size_t len; + /** labels */ + int labs; + /** now */ + time_t now; + /** time to invalidate to */ + time_t expired; + /** number of rrsets removed */ + size_t num_rrsets; + /** number of msgs removed */ + size_t num_msgs; + /** number of key entries removed */ + size_t num_keys; + /** length of addr */ + socklen_t addrlen; + /** socket address for host deletion */ + struct sockaddr_storage addr; +}; + +/** callback to delete hosts in infra cache */ +static void +infra_del_host(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct infra_key* k = (struct infra_key*)e->key; + if(sockaddr_cmp(&inf->addr, inf->addrlen, &k->addr, k->addrlen) == 0) { + struct infra_data* d = (struct infra_data*)e->data; + d->probedelay = 0; + d->timeout_A = 0; + d->timeout_AAAA = 0; + d->timeout_other = 0; + rtt_init(&d->rtt); + if(d->ttl >= inf->now) { + d->ttl = inf->expired; + inf->num_keys++; + } + } +} + +/** flush infra cache */ +static void +do_flush_infra(SSL* ssl, struct worker* worker, char* arg) +{ + struct sockaddr_storage addr; + socklen_t len; + struct del_info inf; + if(strcmp(arg, "all") == 0) { + slabhash_clear(worker->env.infra_cache->hosts); + send_ok(ssl); + return; + } + if(!ipstrtoaddr(arg, UNBOUND_DNS_PORT, &addr, &len)) { + (void)ssl_printf(ssl, "error parsing ip addr: '%s'\n", arg); + return; + } + /* delete all entries from cache */ + /* what we do is to set them all expired */ + inf.worker = worker; + inf.name = 0; + inf.len = 0; + inf.labs = 0; + inf.now = *worker->env.now; + inf.expired = *worker->env.now; + inf.expired -= 3; /* handle 3 seconds skew between threads */ + inf.num_rrsets = 0; + inf.num_msgs = 0; + inf.num_keys = 0; + inf.addrlen = len; + memmove(&inf.addr, &addr, len); + slabhash_traverse(worker->env.infra_cache->hosts, 1, &infra_del_host, + &inf); + send_ok(ssl); +} + +/** flush requestlist */ +static void +do_flush_requestlist(SSL* ssl, struct worker* worker) +{ + mesh_delete_all(worker->env.mesh); + send_ok(ssl); +} + +/** callback to delete rrsets in a zone */ +static void +zone_del_rrset(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key; + if(dname_subdomain_c(k->rk.dname, inf->name)) { + struct packed_rrset_data* d = + (struct packed_rrset_data*)e->data; + if(d->ttl >= inf->now) { + d->ttl = inf->expired; + inf->num_rrsets++; + } + } +} + +/** callback to delete messages in a zone */ +static void +zone_del_msg(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct msgreply_entry* k = (struct msgreply_entry*)e->key; + if(dname_subdomain_c(k->key.qname, inf->name)) { + struct reply_info* d = (struct reply_info*)e->data; + if(d->ttl >= inf->now) { + d->ttl = inf->expired; + inf->num_msgs++; + } + } +} + +/** callback to delete keys in zone */ +static void +zone_del_kcache(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct key_entry_key* k = (struct key_entry_key*)e->key; + if(dname_subdomain_c(k->name, inf->name)) { + struct key_entry_data* d = (struct key_entry_data*)e->data; + if(d->ttl >= inf->now) { + d->ttl = inf->expired; + inf->num_keys++; + } + } +} + +/** remove all rrsets and keys from zone from cache */ +static void +do_flush_zone(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + struct del_info inf; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + /* delete all RRs and key entries from zone */ + /* what we do is to set them all expired */ + inf.worker = worker; + inf.name = nm; + inf.len = nmlen; + inf.labs = nmlabs; + inf.now = *worker->env.now; + inf.expired = *worker->env.now; + inf.expired -= 3; /* handle 3 seconds skew between threads */ + inf.num_rrsets = 0; + inf.num_msgs = 0; + inf.num_keys = 0; + slabhash_traverse(&worker->env.rrset_cache->table, 1, + &zone_del_rrset, &inf); + + slabhash_traverse(worker->env.msg_cache, 1, &zone_del_msg, &inf); + + /* and validator cache */ + if(worker->env.key_cache) { + slabhash_traverse(worker->env.key_cache->slab, 1, + &zone_del_kcache, &inf); + } + + free(nm); + + (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages " + "and %lu key entries\n", (unsigned long)inf.num_rrsets, + (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys); +} + +/** callback to delete bogus rrsets */ +static void +bogus_del_rrset(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct packed_rrset_data* d = (struct packed_rrset_data*)e->data; + if(d->security == sec_status_bogus) { + d->ttl = inf->expired; + inf->num_rrsets++; + } +} + +/** callback to delete bogus messages */ +static void +bogus_del_msg(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct reply_info* d = (struct reply_info*)e->data; + if(d->security == sec_status_bogus) { + d->ttl = inf->expired; + inf->num_msgs++; + } +} + +/** callback to delete bogus keys */ +static void +bogus_del_kcache(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct key_entry_data* d = (struct key_entry_data*)e->data; + if(d->isbad) { + d->ttl = inf->expired; + inf->num_keys++; + } +} + +/** remove all bogus rrsets, msgs and keys from cache */ +static void +do_flush_bogus(SSL* ssl, struct worker* worker) +{ + struct del_info inf; + /* what we do is to set them all expired */ + inf.worker = worker; + inf.now = *worker->env.now; + inf.expired = *worker->env.now; + inf.expired -= 3; /* handle 3 seconds skew between threads */ + inf.num_rrsets = 0; + inf.num_msgs = 0; + inf.num_keys = 0; + slabhash_traverse(&worker->env.rrset_cache->table, 1, + &bogus_del_rrset, &inf); + + slabhash_traverse(worker->env.msg_cache, 1, &bogus_del_msg, &inf); + + /* and validator cache */ + if(worker->env.key_cache) { + slabhash_traverse(worker->env.key_cache->slab, 1, + &bogus_del_kcache, &inf); + } + + (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages " + "and %lu key entries\n", (unsigned long)inf.num_rrsets, + (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys); +} + +/** callback to delete negative and servfail rrsets */ +static void +negative_del_rrset(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key; + struct packed_rrset_data* d = (struct packed_rrset_data*)e->data; + /* delete the parentside negative cache rrsets, + * these are namerserver rrsets that failed lookup, rdata empty */ + if((k->rk.flags & PACKED_RRSET_PARENT_SIDE) && d->count == 1 && + d->rrsig_count == 0 && d->rr_len[0] == 0) { + d->ttl = inf->expired; + inf->num_rrsets++; + } +} + +/** callback to delete negative and servfail messages */ +static void +negative_del_msg(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct reply_info* d = (struct reply_info*)e->data; + /* rcode not NOERROR: NXDOMAIN, SERVFAIL, ..: an nxdomain or error + * or NOERROR rcode with ANCOUNT==0: a NODATA answer */ + if(FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) { + d->ttl = inf->expired; + inf->num_msgs++; + } +} + +/** callback to delete negative key entries */ +static void +negative_del_kcache(struct lruhash_entry* e, void* arg) +{ + /* entry is locked */ + struct del_info* inf = (struct del_info*)arg; + struct key_entry_data* d = (struct key_entry_data*)e->data; + /* could be bad because of lookup failure on the DS, DNSKEY, which + * was nxdomain or servfail, and thus a result of negative lookups */ + if(d->isbad) { + d->ttl = inf->expired; + inf->num_keys++; + } +} + +/** remove all negative(NODATA,NXDOMAIN), and servfail messages from cache */ +static void +do_flush_negative(SSL* ssl, struct worker* worker) +{ + struct del_info inf; + /* what we do is to set them all expired */ + inf.worker = worker; + inf.now = *worker->env.now; + inf.expired = *worker->env.now; + inf.expired -= 3; /* handle 3 seconds skew between threads */ + inf.num_rrsets = 0; + inf.num_msgs = 0; + inf.num_keys = 0; + slabhash_traverse(&worker->env.rrset_cache->table, 1, + &negative_del_rrset, &inf); + + slabhash_traverse(worker->env.msg_cache, 1, &negative_del_msg, &inf); + + /* and validator cache */ + if(worker->env.key_cache) { + slabhash_traverse(worker->env.key_cache->slab, 1, + &negative_del_kcache, &inf); + } + + (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages " + "and %lu key entries\n", (unsigned long)inf.num_rrsets, + (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys); +} + +/** remove name rrset from cache */ +static void +do_flush_name(SSL* ssl, struct worker* w, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_AAAA, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SOA, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_CNAME, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_DNAME, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_MX, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_PTR, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SRV, LDNS_RR_CLASS_IN); + do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_NAPTR, LDNS_RR_CLASS_IN); + + free(nm); + send_ok(ssl); +} + +/** printout a delegation point info */ +static int +ssl_print_name_dp(SSL* ssl, const char* str, uint8_t* nm, uint16_t dclass, + struct delegpt* dp) +{ + char buf[257]; + struct delegpt_ns* ns; + struct delegpt_addr* a; + int f = 0; + if(str) { /* print header for forward, stub */ + char* c = sldns_wire2str_class(dclass); + dname_str(nm, buf); + if(!ssl_printf(ssl, "%s %s %s ", buf, (c?c:"CLASS??"), str)) { + free(c); + return 0; + } + free(c); + } + for(ns = dp->nslist; ns; ns = ns->next) { + dname_str(ns->name, buf); + if(!ssl_printf(ssl, "%s%s", (f?" ":""), buf)) + return 0; + f = 1; + } + for(a = dp->target_list; a; a = a->next_target) { + addr_to_str(&a->addr, a->addrlen, buf, sizeof(buf)); + if(!ssl_printf(ssl, "%s%s", (f?" ":""), buf)) + return 0; + f = 1; + } + return ssl_printf(ssl, "\n"); +} + + +/** print root forwards */ +static int +print_root_fwds(SSL* ssl, struct iter_forwards* fwds, uint8_t* root) +{ + struct delegpt* dp; + dp = forwards_lookup(fwds, root, LDNS_RR_CLASS_IN); + if(!dp) + return ssl_printf(ssl, "off (using root hints)\n"); + /* if dp is returned it must be the root */ + log_assert(query_dname_compare(dp->name, root)==0); + return ssl_print_name_dp(ssl, NULL, root, LDNS_RR_CLASS_IN, dp); +} + +/** parse args into delegpt */ +static struct delegpt* +parse_delegpt(SSL* ssl, char* args, uint8_t* nm, int allow_names) +{ + /* parse args and add in */ + char* p = args; + char* todo; + struct delegpt* dp = delegpt_create_mlc(nm); + struct sockaddr_storage addr; + socklen_t addrlen; + if(!dp) { + (void)ssl_printf(ssl, "error out of memory\n"); + return NULL; + } + while(p) { + todo = p; + p = strchr(p, ' '); /* find next spot, if any */ + if(p) { + *p++ = 0; /* end this spot */ + p = skipwhite(p); /* position at next spot */ + } + /* parse address */ + if(!extstrtoaddr(todo, &addr, &addrlen)) { + if(allow_names) { + uint8_t* n = NULL; + size_t ln; + int lb; + if(!parse_arg_name(ssl, todo, &n, &ln, &lb)) { + (void)ssl_printf(ssl, "error cannot " + "parse IP address or name " + "'%s'\n", todo); + delegpt_free_mlc(dp); + return NULL; + } + if(!delegpt_add_ns_mlc(dp, n, 0)) { + (void)ssl_printf(ssl, "error out of memory\n"); + free(n); + delegpt_free_mlc(dp); + return NULL; + } + free(n); + + } else { + (void)ssl_printf(ssl, "error cannot parse" + " IP address '%s'\n", todo); + delegpt_free_mlc(dp); + return NULL; + } + } else { + /* add address */ + if(!delegpt_add_addr_mlc(dp, &addr, addrlen, 0, 0)) { + (void)ssl_printf(ssl, "error out of memory\n"); + delegpt_free_mlc(dp); + return NULL; + } + } + } + return dp; +} + +/** do the status command */ +static void +do_forward(SSL* ssl, struct worker* worker, char* args) +{ + struct iter_forwards* fwd = worker->env.fwds; + uint8_t* root = (uint8_t*)"\000"; + if(!fwd) { + (void)ssl_printf(ssl, "error: structure not allocated\n"); + return; + } + if(args == NULL || args[0] == 0) { + (void)print_root_fwds(ssl, fwd, root); + return; + } + /* set root forwards for this thread. since we are in remote control + * the actual mesh is not running, so we can freely edit it. */ + /* delete all the existing queries first */ + mesh_delete_all(worker->env.mesh); + if(strcmp(args, "off") == 0) { + forwards_delete_zone(fwd, LDNS_RR_CLASS_IN, root); + } else { + struct delegpt* dp; + if(!(dp = parse_delegpt(ssl, args, root, 0))) + return; + if(!forwards_add_zone(fwd, LDNS_RR_CLASS_IN, dp)) { + (void)ssl_printf(ssl, "error out of memory\n"); + return; + } + } + send_ok(ssl); +} + +static int +parse_fs_args(SSL* ssl, char* args, uint8_t** nm, struct delegpt** dp, + int* insecure, int* prime) +{ + char* zonename; + char* rest; + size_t nmlen; + int nmlabs; + /* parse all -x args */ + while(args[0] == '+') { + if(!find_arg2(ssl, args, &rest)) + return 0; + while(*(++args) != 0) { + if(*args == 'i' && insecure) + *insecure = 1; + else if(*args == 'p' && prime) + *prime = 1; + else { + (void)ssl_printf(ssl, "error: unknown option %s\n", args); + return 0; + } + } + args = rest; + } + /* parse name */ + if(dp) { + if(!find_arg2(ssl, args, &rest)) + return 0; + zonename = args; + args = rest; + } else zonename = args; + if(!parse_arg_name(ssl, zonename, nm, &nmlen, &nmlabs)) + return 0; + + /* parse dp */ + if(dp) { + if(!(*dp = parse_delegpt(ssl, args, *nm, 1))) { + free(*nm); + return 0; + } + } + return 1; +} + +/** do the forward_add command */ +static void +do_forward_add(SSL* ssl, struct worker* worker, char* args) +{ + struct iter_forwards* fwd = worker->env.fwds; + int insecure = 0; + uint8_t* nm = NULL; + struct delegpt* dp = NULL; + if(!parse_fs_args(ssl, args, &nm, &dp, &insecure, NULL)) + return; + if(insecure && worker->env.anchors) { + if(!anchors_add_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, + nm)) { + (void)ssl_printf(ssl, "error out of memory\n"); + delegpt_free_mlc(dp); + free(nm); + return; + } + } + if(!forwards_add_zone(fwd, LDNS_RR_CLASS_IN, dp)) { + (void)ssl_printf(ssl, "error out of memory\n"); + free(nm); + return; + } + free(nm); + send_ok(ssl); +} + +/** do the forward_remove command */ +static void +do_forward_remove(SSL* ssl, struct worker* worker, char* args) +{ + struct iter_forwards* fwd = worker->env.fwds; + int insecure = 0; + uint8_t* nm = NULL; + if(!parse_fs_args(ssl, args, &nm, NULL, &insecure, NULL)) + return; + if(insecure && worker->env.anchors) + anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, + nm); + forwards_delete_zone(fwd, LDNS_RR_CLASS_IN, nm); + free(nm); + send_ok(ssl); +} + +/** do the stub_add command */ +static void +do_stub_add(SSL* ssl, struct worker* worker, char* args) +{ + struct iter_forwards* fwd = worker->env.fwds; + int insecure = 0, prime = 0; + uint8_t* nm = NULL; + struct delegpt* dp = NULL; + if(!parse_fs_args(ssl, args, &nm, &dp, &insecure, &prime)) + return; + if(insecure && worker->env.anchors) { + if(!anchors_add_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, + nm)) { + (void)ssl_printf(ssl, "error out of memory\n"); + delegpt_free_mlc(dp); + free(nm); + return; + } + } + if(!forwards_add_stub_hole(fwd, LDNS_RR_CLASS_IN, nm)) { + if(insecure && worker->env.anchors) + anchors_delete_insecure(worker->env.anchors, + LDNS_RR_CLASS_IN, nm); + (void)ssl_printf(ssl, "error out of memory\n"); + delegpt_free_mlc(dp); + free(nm); + return; + } + if(!hints_add_stub(worker->env.hints, LDNS_RR_CLASS_IN, dp, !prime)) { + (void)ssl_printf(ssl, "error out of memory\n"); + forwards_delete_stub_hole(fwd, LDNS_RR_CLASS_IN, nm); + if(insecure && worker->env.anchors) + anchors_delete_insecure(worker->env.anchors, + LDNS_RR_CLASS_IN, nm); + free(nm); + return; + } + free(nm); + send_ok(ssl); +} + +/** do the stub_remove command */ +static void +do_stub_remove(SSL* ssl, struct worker* worker, char* args) +{ + struct iter_forwards* fwd = worker->env.fwds; + int insecure = 0; + uint8_t* nm = NULL; + if(!parse_fs_args(ssl, args, &nm, NULL, &insecure, NULL)) + return; + if(insecure && worker->env.anchors) + anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, + nm); + forwards_delete_stub_hole(fwd, LDNS_RR_CLASS_IN, nm); + hints_delete_stub(worker->env.hints, LDNS_RR_CLASS_IN, nm); + free(nm); + send_ok(ssl); +} + +/** do the insecure_add command */ +static void +do_insecure_add(SSL* ssl, struct worker* worker, char* arg) +{ + size_t nmlen; + int nmlabs; + uint8_t* nm = NULL; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + if(worker->env.anchors) { + if(!anchors_add_insecure(worker->env.anchors, + LDNS_RR_CLASS_IN, nm)) { + (void)ssl_printf(ssl, "error out of memory\n"); + free(nm); + return; + } + } + free(nm); + send_ok(ssl); +} + +/** do the insecure_remove command */ +static void +do_insecure_remove(SSL* ssl, struct worker* worker, char* arg) +{ + size_t nmlen; + int nmlabs; + uint8_t* nm = NULL; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + if(worker->env.anchors) + anchors_delete_insecure(worker->env.anchors, + LDNS_RR_CLASS_IN, nm); + free(nm); + send_ok(ssl); +} + +/** do the status command */ +static void +do_status(SSL* ssl, struct worker* worker) +{ + int i; + time_t uptime; + if(!ssl_printf(ssl, "version: %s\n", PACKAGE_VERSION)) + return; + if(!ssl_printf(ssl, "verbosity: %d\n", verbosity)) + return; + if(!ssl_printf(ssl, "threads: %d\n", worker->daemon->num)) + return; + if(!ssl_printf(ssl, "modules: %d [", worker->daemon->mods.num)) + return; + for(i=0; i<worker->daemon->mods.num; i++) { + if(!ssl_printf(ssl, " %s", worker->daemon->mods.mod[i]->name)) + return; + } + if(!ssl_printf(ssl, " ]\n")) + return; + uptime = (time_t)time(NULL) - (time_t)worker->daemon->time_boot.tv_sec; + if(!ssl_printf(ssl, "uptime: " ARG_LL "d seconds\n", (long long)uptime)) + return; + if(!ssl_printf(ssl, "options:%s%s\n" , + (worker->daemon->reuseport?" reuseport":""), + (worker->daemon->rc->accept_list?" control(ssl)":""))) + return; + if(!ssl_printf(ssl, "unbound (pid %d) is running...\n", + (int)getpid())) + return; +} + +/** get age for the mesh state */ +static void +get_mesh_age(struct mesh_state* m, char* buf, size_t len, + struct module_env* env) +{ + if(m->reply_list) { + struct timeval d; + struct mesh_reply* r = m->reply_list; + /* last reply is the oldest */ + while(r && r->next) + r = r->next; + timeval_subtract(&d, env->now_tv, &r->start_time); + snprintf(buf, len, ARG_LL "d.%6.6d", + (long long)d.tv_sec, (int)d.tv_usec); + } else { + snprintf(buf, len, "-"); + } +} + +/** get status of a mesh state */ +static void +get_mesh_status(struct mesh_area* mesh, struct mesh_state* m, + char* buf, size_t len) +{ + enum module_ext_state s = m->s.ext_state[m->s.curmod]; + const char *modname = mesh->mods.mod[m->s.curmod]->name; + size_t l; + if(strcmp(modname, "iterator") == 0 && s == module_wait_reply && + m->s.minfo[m->s.curmod]) { + /* break into iterator to find out who its waiting for */ + struct iter_qstate* qstate = (struct iter_qstate*) + m->s.minfo[m->s.curmod]; + struct outbound_list* ol = &qstate->outlist; + struct outbound_entry* e; + snprintf(buf, len, "%s wait for", modname); + l = strlen(buf); + buf += l; len -= l; + if(ol->first == NULL) + snprintf(buf, len, " (empty_list)"); + for(e = ol->first; e; e = e->next) { + snprintf(buf, len, " "); + l = strlen(buf); + buf += l; len -= l; + addr_to_str(&e->qsent->addr, e->qsent->addrlen, + buf, len); + l = strlen(buf); + buf += l; len -= l; + } + } else if(s == module_wait_subquery) { + /* look in subs from mesh state to see what */ + char nm[257]; + struct mesh_state_ref* sub; + snprintf(buf, len, "%s wants", modname); + l = strlen(buf); + buf += l; len -= l; + if(m->sub_set.count == 0) + snprintf(buf, len, " (empty_list)"); + RBTREE_FOR(sub, struct mesh_state_ref*, &m->sub_set) { + char* t = sldns_wire2str_type(sub->s->s.qinfo.qtype); + char* c = sldns_wire2str_class(sub->s->s.qinfo.qclass); + dname_str(sub->s->s.qinfo.qname, nm); + snprintf(buf, len, " %s %s %s", (t?t:"TYPE??"), + (c?c:"CLASS??"), nm); + l = strlen(buf); + buf += l; len -= l; + free(t); + free(c); + } + } else { + snprintf(buf, len, "%s is %s", modname, strextstate(s)); + } +} + +/** do the dump_requestlist command */ +static void +do_dump_requestlist(SSL* ssl, struct worker* worker) +{ + struct mesh_area* mesh; + struct mesh_state* m; + int num = 0; + char buf[257]; + char timebuf[32]; + char statbuf[10240]; + if(!ssl_printf(ssl, "thread #%d\n", worker->thread_num)) + return; + if(!ssl_printf(ssl, "# type cl name seconds module status\n")) + return; + /* show worker mesh contents */ + mesh = worker->env.mesh; + if(!mesh) return; + RBTREE_FOR(m, struct mesh_state*, &mesh->all) { + char* t = sldns_wire2str_type(m->s.qinfo.qtype); + char* c = sldns_wire2str_class(m->s.qinfo.qclass); + dname_str(m->s.qinfo.qname, buf); + get_mesh_age(m, timebuf, sizeof(timebuf), &worker->env); + get_mesh_status(mesh, m, statbuf, sizeof(statbuf)); + if(!ssl_printf(ssl, "%3d %4s %2s %s %s %s\n", + num, (t?t:"TYPE??"), (c?c:"CLASS??"), buf, timebuf, + statbuf)) { + free(t); + free(c); + return; + } + num++; + free(t); + free(c); + } +} + +/** structure for argument data for dump infra host */ +struct infra_arg { + /** the infra cache */ + struct infra_cache* infra; + /** the SSL connection */ + SSL* ssl; + /** the time now */ + time_t now; + /** ssl failure? stop writing and skip the rest. If the tcp + * connection is broken, and writes fail, we then stop writing. */ + int ssl_failed; +}; + +/** callback for every host element in the infra cache */ +static void +dump_infra_host(struct lruhash_entry* e, void* arg) +{ + struct infra_arg* a = (struct infra_arg*)arg; + struct infra_key* k = (struct infra_key*)e->key; + struct infra_data* d = (struct infra_data*)e->data; + char ip_str[1024]; + char name[257]; + if(a->ssl_failed) + return; + addr_to_str(&k->addr, k->addrlen, ip_str, sizeof(ip_str)); + dname_str(k->zonename, name); + /* skip expired stuff (only backed off) */ + if(d->ttl < a->now) { + if(d->rtt.rto >= USEFUL_SERVER_TOP_TIMEOUT) { + if(!ssl_printf(a->ssl, "%s %s expired rto %d\n", ip_str, + name, d->rtt.rto)) { + a->ssl_failed = 1; + return; + } + } + return; + } + if(!ssl_printf(a->ssl, "%s %s ttl %lu ping %d var %d rtt %d rto %d " + "tA %d tAAAA %d tother %d " + "ednsknown %d edns %d delay %d lame dnssec %d rec %d A %d " + "other %d\n", ip_str, name, (unsigned long)(d->ttl - a->now), + d->rtt.srtt, d->rtt.rttvar, rtt_notimeout(&d->rtt), d->rtt.rto, + d->timeout_A, d->timeout_AAAA, d->timeout_other, + (int)d->edns_lame_known, (int)d->edns_version, + (int)(a->now<d->probedelay?d->probedelay-a->now:0), + (int)d->isdnsseclame, (int)d->rec_lame, (int)d->lame_type_A, + (int)d->lame_other)) { + a->ssl_failed = 1; + return; + } +} + +/** do the dump_infra command */ +static void +do_dump_infra(SSL* ssl, struct worker* worker) +{ + struct infra_arg arg; + arg.infra = worker->env.infra_cache; + arg.ssl = ssl; + arg.now = *worker->env.now; + arg.ssl_failed = 0; + slabhash_traverse(arg.infra->hosts, 0, &dump_infra_host, (void*)&arg); +} + +/** do the log_reopen command */ +static void +do_log_reopen(SSL* ssl, struct worker* worker) +{ + struct config_file* cfg = worker->env.cfg; + send_ok(ssl); + log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir); +} + +/** do the set_option command */ +static void +do_set_option(SSL* ssl, struct worker* worker, char* arg) +{ + char* arg2; + if(!find_arg2(ssl, arg, &arg2)) + return; + if(!config_set_option(worker->env.cfg, arg, arg2)) { + (void)ssl_printf(ssl, "error setting option\n"); + return; + } + send_ok(ssl); +} + +/* routine to printout option values over SSL */ +void remote_get_opt_ssl(char* line, void* arg) +{ + SSL* ssl = (SSL*)arg; + (void)ssl_printf(ssl, "%s\n", line); +} + +/** do the get_option command */ +static void +do_get_option(SSL* ssl, struct worker* worker, char* arg) +{ + int r; + r = config_get_option(worker->env.cfg, arg, remote_get_opt_ssl, ssl); + if(!r) { + (void)ssl_printf(ssl, "error unknown option\n"); + return; + } +} + +/** do the list_forwards command */ +static void +do_list_forwards(SSL* ssl, struct worker* worker) +{ + /* since its a per-worker structure no locks needed */ + struct iter_forwards* fwds = worker->env.fwds; + struct iter_forward_zone* z; + struct trust_anchor* a; + int insecure; + RBTREE_FOR(z, struct iter_forward_zone*, fwds->tree) { + if(!z->dp) continue; /* skip empty marker for stub */ + + /* see if it is insecure */ + insecure = 0; + if(worker->env.anchors && + (a=anchor_find(worker->env.anchors, z->name, + z->namelabs, z->namelen, z->dclass))) { + if(!a->keylist && !a->numDS && !a->numDNSKEY) + insecure = 1; + lock_basic_unlock(&a->lock); + } + + if(!ssl_print_name_dp(ssl, (insecure?"forward +i":"forward"), + z->name, z->dclass, z->dp)) + return; + } +} + +/** do the list_stubs command */ +static void +do_list_stubs(SSL* ssl, struct worker* worker) +{ + struct iter_hints_stub* z; + struct trust_anchor* a; + int insecure; + char str[32]; + RBTREE_FOR(z, struct iter_hints_stub*, &worker->env.hints->tree) { + + /* see if it is insecure */ + insecure = 0; + if(worker->env.anchors && + (a=anchor_find(worker->env.anchors, z->node.name, + z->node.labs, z->node.len, z->node.dclass))) { + if(!a->keylist && !a->numDS && !a->numDNSKEY) + insecure = 1; + lock_basic_unlock(&a->lock); + } + + snprintf(str, sizeof(str), "stub %sprime%s", + (z->noprime?"no":""), (insecure?" +i":"")); + if(!ssl_print_name_dp(ssl, str, z->node.name, + z->node.dclass, z->dp)) + return; + } +} + +/** do the list_local_zones command */ +static void +do_list_local_zones(SSL* ssl, struct worker* worker) +{ + struct local_zones* zones = worker->daemon->local_zones; + struct local_zone* z; + char buf[257]; + lock_rw_rdlock(&zones->lock); + RBTREE_FOR(z, struct local_zone*, &zones->ztree) { + lock_rw_rdlock(&z->lock); + dname_str(z->name, buf); + if(!ssl_printf(ssl, "%s %s\n", buf, + local_zone_type2str(z->type))) { + /* failure to print */ + lock_rw_unlock(&z->lock); + lock_rw_unlock(&zones->lock); + return; + } + lock_rw_unlock(&z->lock); + } + lock_rw_unlock(&zones->lock); +} + +/** do the list_local_data command */ +static void +do_list_local_data(SSL* ssl, struct worker* worker) +{ + struct local_zones* zones = worker->daemon->local_zones; + struct local_zone* z; + struct local_data* d; + struct local_rrset* p; + char* s = (char*)sldns_buffer_begin(worker->env.scratch_buffer); + size_t slen = sldns_buffer_capacity(worker->env.scratch_buffer); + lock_rw_rdlock(&zones->lock); + RBTREE_FOR(z, struct local_zone*, &zones->ztree) { + lock_rw_rdlock(&z->lock); + RBTREE_FOR(d, struct local_data*, &z->data) { + for(p = d->rrsets; p; p = p->next) { + struct packed_rrset_data* d = + (struct packed_rrset_data*)p->rrset->entry.data; + size_t i; + for(i=0; i<d->count + d->rrsig_count; i++) { + if(!packed_rr_to_string(p->rrset, i, + 0, s, slen)) { + if(!ssl_printf(ssl, "BADRR\n")) + return; + } + if(!ssl_printf(ssl, "%s\n", s)) + return; + } + } + } + lock_rw_unlock(&z->lock); + } + lock_rw_unlock(&zones->lock); +} + +/** tell other processes to execute the command */ +static void +distribute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd) +{ + int i; + if(!cmd || !ssl) + return; + /* skip i=0 which is me */ + for(i=1; i<rc->worker->daemon->num; i++) { + worker_send_cmd(rc->worker->daemon->workers[i], + worker_cmd_remote); + if(!tube_write_msg(rc->worker->daemon->workers[i]->cmd, + (uint8_t*)cmd, strlen(cmd)+1, 0)) { + ssl_printf(ssl, "error could not distribute cmd\n"); + return; + } + } +} + +/** check for name with end-of-string, space or tab after it */ +static int +cmdcmp(char* p, const char* cmd, size_t len) +{ + return strncmp(p,cmd,len)==0 && (p[len]==0||p[len]==' '||p[len]=='\t'); +} + +/** execute a remote control command */ +static void +execute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd, + struct worker* worker) +{ + char* p = skipwhite(cmd); + /* compare command */ + if(cmdcmp(p, "stop", 4)) { + do_stop(ssl, rc); + return; + } else if(cmdcmp(p, "reload", 6)) { + do_reload(ssl, rc); + return; + } else if(cmdcmp(p, "stats_noreset", 13)) { + do_stats(ssl, rc, 0); + return; + } else if(cmdcmp(p, "stats", 5)) { + do_stats(ssl, rc, 1); + return; + } else if(cmdcmp(p, "status", 6)) { + do_status(ssl, worker); + return; + } else if(cmdcmp(p, "dump_cache", 10)) { + (void)dump_cache(ssl, worker); + return; + } else if(cmdcmp(p, "load_cache", 10)) { + if(load_cache(ssl, worker)) send_ok(ssl); + return; + } else if(cmdcmp(p, "list_forwards", 13)) { + do_list_forwards(ssl, worker); + return; + } else if(cmdcmp(p, "list_stubs", 10)) { + do_list_stubs(ssl, worker); + return; + } else if(cmdcmp(p, "list_local_zones", 16)) { + do_list_local_zones(ssl, worker); + return; + } else if(cmdcmp(p, "list_local_data", 15)) { + do_list_local_data(ssl, worker); + return; + } else if(cmdcmp(p, "stub_add", 8)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_stub_add(ssl, worker, skipwhite(p+8)); + return; + } else if(cmdcmp(p, "stub_remove", 11)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_stub_remove(ssl, worker, skipwhite(p+11)); + return; + } else if(cmdcmp(p, "forward_add", 11)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_forward_add(ssl, worker, skipwhite(p+11)); + return; + } else if(cmdcmp(p, "forward_remove", 14)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_forward_remove(ssl, worker, skipwhite(p+14)); + return; + } else if(cmdcmp(p, "insecure_add", 12)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_insecure_add(ssl, worker, skipwhite(p+12)); + return; + } else if(cmdcmp(p, "insecure_remove", 15)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_insecure_remove(ssl, worker, skipwhite(p+15)); + return; + } else if(cmdcmp(p, "forward", 7)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_forward(ssl, worker, skipwhite(p+7)); + return; + } else if(cmdcmp(p, "flush_stats", 11)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_flush_stats(ssl, worker); + return; + } else if(cmdcmp(p, "flush_requestlist", 17)) { + /* must always distribute this cmd */ + if(rc) distribute_cmd(rc, ssl, cmd); + do_flush_requestlist(ssl, worker); + return; + } else if(cmdcmp(p, "lookup", 6)) { + do_lookup(ssl, worker, skipwhite(p+6)); + return; + } + +#ifdef THREADS_DISABLED + /* other processes must execute the command as well */ + /* commands that should not be distributed, returned above. */ + if(rc) { /* only if this thread is the master (rc) thread */ + /* done before the code below, which may split the string */ + distribute_cmd(rc, ssl, cmd); + } +#endif + if(cmdcmp(p, "verbosity", 9)) { + do_verbosity(ssl, skipwhite(p+9)); + } else if(cmdcmp(p, "local_zone_remove", 17)) { + do_zone_remove(ssl, worker, skipwhite(p+17)); + } else if(cmdcmp(p, "local_zone", 10)) { + do_zone_add(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "local_data_remove", 17)) { + do_data_remove(ssl, worker, skipwhite(p+17)); + } else if(cmdcmp(p, "local_data", 10)) { + do_data_add(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "flush_zone", 10)) { + do_flush_zone(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "flush_type", 10)) { + do_flush_type(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "flush_infra", 11)) { + do_flush_infra(ssl, worker, skipwhite(p+11)); + } else if(cmdcmp(p, "flush", 5)) { + do_flush_name(ssl, worker, skipwhite(p+5)); + } else if(cmdcmp(p, "dump_requestlist", 16)) { + do_dump_requestlist(ssl, worker); + } else if(cmdcmp(p, "dump_infra", 10)) { + do_dump_infra(ssl, worker); + } else if(cmdcmp(p, "log_reopen", 10)) { + do_log_reopen(ssl, worker); + } else if(cmdcmp(p, "set_option", 10)) { + do_set_option(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "get_option", 10)) { + do_get_option(ssl, worker, skipwhite(p+10)); + } else if(cmdcmp(p, "flush_bogus", 11)) { + do_flush_bogus(ssl, worker); + } else if(cmdcmp(p, "flush_negative", 14)) { + do_flush_negative(ssl, worker); + } else { + (void)ssl_printf(ssl, "error unknown command '%s'\n", p); + } +} + +void +daemon_remote_exec(struct worker* worker) +{ + /* read the cmd string */ + uint8_t* msg = NULL; + uint32_t len = 0; + if(!tube_read_msg(worker->cmd, &msg, &len, 0)) { + log_err("daemon_remote_exec: tube_read_msg failed"); + return; + } + verbose(VERB_ALGO, "remote exec distributed: %s", (char*)msg); + execute_cmd(NULL, NULL, (char*)msg, worker); + free(msg); +} + +/** handle remote control request */ +static void +handle_req(struct daemon_remote* rc, struct rc_state* s, SSL* ssl) +{ + int r; + char pre[10]; + char magic[7]; + char buf[1024]; +#ifdef USE_WINSOCK + /* makes it possible to set the socket blocking again. */ + /* basically removes it from winsock_event ... */ + WSAEventSelect(s->c->fd, NULL, 0); +#endif + fd_set_block(s->c->fd); + + /* try to read magic UBCT[version]_space_ string */ + ERR_clear_error(); + if((r=SSL_read(ssl, magic, (int)sizeof(magic)-1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) + return; + log_crypto_err("could not SSL_read"); + return; + } + magic[6] = 0; + if( r != 6 || strncmp(magic, "UBCT", 4) != 0) { + verbose(VERB_QUERY, "control connection has bad magic string"); + /* probably wrong tool connected, ignore it completely */ + return; + } + + /* read the command line */ + if(!ssl_read_line(ssl, buf, sizeof(buf))) { + return; + } + snprintf(pre, sizeof(pre), "UBCT%d ", UNBOUND_CONTROL_VERSION); + if(strcmp(magic, pre) != 0) { + verbose(VERB_QUERY, "control connection had bad " + "version %s, cmd: %s", magic, buf); + ssl_printf(ssl, "error version mismatch\n"); + return; + } + verbose(VERB_DETAIL, "control cmd: %s", buf); + + /* figure out what to do */ + execute_cmd(rc, ssl, buf, rc->worker); +} + +int remote_control_callback(struct comm_point* c, void* arg, int err, + struct comm_reply* ATTR_UNUSED(rep)) +{ + struct rc_state* s = (struct rc_state*)arg; + struct daemon_remote* rc = s->rc; + int r; + if(err != NETEVENT_NOERROR) { + if(err==NETEVENT_TIMEOUT) + log_err("remote control timed out"); + clean_point(rc, s); + return 0; + } + /* (continue to) setup the SSL connection */ + ERR_clear_error(); + r = SSL_do_handshake(s->ssl); + if(r != 1) { + int r2 = SSL_get_error(s->ssl, r); + if(r2 == SSL_ERROR_WANT_READ) { + if(s->shake_state == rc_hs_read) { + /* try again later */ + return 0; + } + s->shake_state = rc_hs_read; + comm_point_listen_for_rw(c, 1, 0); + return 0; + } else if(r2 == SSL_ERROR_WANT_WRITE) { + if(s->shake_state == rc_hs_write) { + /* try again later */ + return 0; + } + s->shake_state = rc_hs_write; + comm_point_listen_for_rw(c, 0, 1); + return 0; + } else { + if(r == 0) + log_err("remote control connection closed prematurely"); + log_addr(1, "failed connection from", + &s->c->repinfo.addr, s->c->repinfo.addrlen); + log_crypto_err("remote control failed ssl"); + clean_point(rc, s); + return 0; + } + } + s->shake_state = rc_none; + + /* once handshake has completed, check authentication */ + if(SSL_get_verify_result(s->ssl) == X509_V_OK) { + X509* x = SSL_get_peer_certificate(s->ssl); + if(!x) { + verbose(VERB_DETAIL, "remote control connection " + "provided no client certificate"); + clean_point(rc, s); + return 0; + } + verbose(VERB_ALGO, "remote control connection authenticated"); + X509_free(x); + } else { + verbose(VERB_DETAIL, "remote control connection failed to " + "authenticate with client certificate"); + clean_point(rc, s); + return 0; + } + + /* if OK start to actually handle the request */ + handle_req(rc, s, s->ssl); + + verbose(VERB_ALGO, "remote control operation completed"); + clean_point(rc, s); + return 0; +} diff --git a/external/unbound/daemon/remote.h b/external/unbound/daemon/remote.h new file mode 100644 index 000000000..cc670b701 --- /dev/null +++ b/external/unbound/daemon/remote.h @@ -0,0 +1,189 @@ +/* + * daemon/remote.h - remote control for the unbound daemon. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains the remote control functionality for the daemon. + * The remote control can be performed using either the commandline + * unbound-control tool, or a SSLv3/TLS capable web browser. + * The channel is secured using SSLv3 or TLSv1, and certificates. + * Both the server and the client(control tool) have their own keys. + */ + +#ifndef DAEMON_REMOTE_H +#define DAEMON_REMOTE_H +#ifdef HAVE_OPENSSL_SSL_H +#include "openssl/ssl.h" +#endif +struct config_file; +struct listen_list; +struct listen_port; +struct worker; +struct comm_reply; +struct comm_point; +struct daemon_remote; + +/** number of seconds timeout on incoming remote control handshake */ +#define REMOTE_CONTROL_TCP_TIMEOUT 120 + +/** + * a busy control command connection, SSL state + */ +struct rc_state { + /** the next item in list */ + struct rc_state* next; + /** the commpoint */ + struct comm_point* c; + /** in the handshake part */ + enum { rc_none, rc_hs_read, rc_hs_write } shake_state; +#ifdef HAVE_SSL + /** the ssl state */ + SSL* ssl; +#endif + /** the rc this is part of */ + struct daemon_remote* rc; +}; + +/** + * The remote control tool state. + * The state is only created for the first thread, other threads + * are called from this thread. Only the first threads listens to + * the control port. The other threads do not, but are called on the + * command channel(pipe) from the first thread. + */ +struct daemon_remote { + /** the worker for this remote control */ + struct worker* worker; + /** commpoints for accepting remote control connections */ + struct listen_list* accept_list; + /** number of active commpoints that are handling remote control */ + int active; + /** max active commpoints */ + int max_active; + /** current commpoints busy; should be a short list, malloced */ + struct rc_state* busy_list; +#ifdef HAVE_SSL + /** the SSL context for creating new SSL streams */ + SSL_CTX* ctx; +#endif +}; + +/** + * Create new remote control state for the daemon. + * @param cfg: config file with key file settings. + * @return new state, or NULL on failure. + */ +struct daemon_remote* daemon_remote_create(struct config_file* cfg); + +/** + * remote control state to delete. + * @param rc: state to delete. + */ +void daemon_remote_delete(struct daemon_remote* rc); + +/** + * remote control state to clear up. Busy and accept points are closed. + * Does not delete the rc itself, or the ssl context (with its keys). + * @param rc: state to clear. + */ +void daemon_remote_clear(struct daemon_remote* rc); + +/** + * Open and create listening ports for remote control. + * @param cfg: config options. + * @return list of ports or NULL on failure. + * can be freed with listening_ports_free(). + */ +struct listen_port* daemon_remote_open_ports(struct config_file* cfg); + +/** + * Setup comm points for accepting remote control connections. + * @param rc: state + * @param ports: already opened ports. + * @param worker: worker with communication base. and links to command channels. + * @return false on error. + */ +int daemon_remote_open_accept(struct daemon_remote* rc, + struct listen_port* ports, struct worker* worker); + +/** + * Stop accept handlers for TCP (until enabled again) + * @param rc: state + */ +void daemon_remote_stop_accept(struct daemon_remote* rc); + +/** + * Stop accept handlers for TCP (until enabled again) + * @param rc: state + */ +void daemon_remote_start_accept(struct daemon_remote* rc); + +/** + * Handle nonthreaded remote cmd execution. + * @param worker: this worker (the remote worker). + */ +void daemon_remote_exec(struct worker* worker); + +#ifdef HAVE_SSL +/** + * Print fixed line of text over ssl connection in blocking mode + * @param ssl: print to + * @param text: the text. + * @return false on connection failure. + */ +int ssl_print_text(SSL* ssl, const char* text); + +/** + * printf style printing to the ssl connection + * @param ssl: the SSL connection to print to. Blocking. + * @param format: printf style format string. + * @return success or false on a network failure. + */ +int ssl_printf(SSL* ssl, const char* format, ...) + ATTR_FORMAT(printf, 2, 3); + +/** + * Read until \n is encountered + * If SSL signals EOF, the string up to then is returned (without \n). + * @param ssl: the SSL connection to read from. blocking. + * @param buf: buffer to read to. + * @param max: size of buffer. + * @return false on connection failure. + */ +int ssl_read_line(SSL* ssl, char* buf, size_t max); +#endif /* HAVE_SSL */ + +#endif /* DAEMON_REMOTE_H */ diff --git a/external/unbound/daemon/stats.c b/external/unbound/daemon/stats.c new file mode 100644 index 000000000..d3f41de03 --- /dev/null +++ b/external/unbound/daemon/stats.c @@ -0,0 +1,321 @@ +/* + * daemon/stats.c - collect runtime performance indicators. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file describes the data structure used to collect runtime performance + * numbers. These 'statistics' may be of interest to the operator. + */ +#include "config.h" +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#include <sys/time.h> +#include <sys/types.h> +#include "daemon/stats.h" +#include "daemon/worker.h" +#include "daemon/daemon.h" +#include "services/mesh.h" +#include "services/outside_network.h" +#include "util/config_file.h" +#include "util/tube.h" +#include "util/timehist.h" +#include "util/net_help.h" +#include "validator/validator.h" +#include "ldns/sbuffer.h" +#include "services/cache/rrset.h" +#include "services/cache/infra.h" +#include "validator/val_kcache.h" + +/** add timers and the values do not overflow or become negative */ +static void +timeval_add(struct timeval* d, const struct timeval* add) +{ +#ifndef S_SPLINT_S + d->tv_sec += add->tv_sec; + d->tv_usec += add->tv_usec; + if(d->tv_usec > 1000000) { + d->tv_usec -= 1000000; + d->tv_sec++; + } +#endif +} + +void server_stats_init(struct server_stats* stats, struct config_file* cfg) +{ + memset(stats, 0, sizeof(*stats)); + stats->extended = cfg->stat_extended; +} + +void server_stats_querymiss(struct server_stats* stats, struct worker* worker) +{ + stats->num_queries_missed_cache++; + stats->sum_query_list_size += worker->env.mesh->all.count; + if(worker->env.mesh->all.count > stats->max_query_list_size) + stats->max_query_list_size = worker->env.mesh->all.count; +} + +void server_stats_prefetch(struct server_stats* stats, struct worker* worker) +{ + stats->num_queries_prefetch++; + /* changes the query list size so account that, like a querymiss */ + stats->sum_query_list_size += worker->env.mesh->all.count; + if(worker->env.mesh->all.count > stats->max_query_list_size) + stats->max_query_list_size = worker->env.mesh->all.count; +} + +void server_stats_log(struct server_stats* stats, struct worker* worker, + int threadnum) +{ + log_info("server stats for thread %d: %u queries, " + "%u answers from cache, %u recursions, %u prefetch", + threadnum, (unsigned)stats->num_queries, + (unsigned)(stats->num_queries - + stats->num_queries_missed_cache), + (unsigned)stats->num_queries_missed_cache, + (unsigned)stats->num_queries_prefetch); + log_info("server stats for thread %d: requestlist max %u avg %g " + "exceeded %u jostled %u", threadnum, + (unsigned)stats->max_query_list_size, + (stats->num_queries_missed_cache+stats->num_queries_prefetch)? + (double)stats->sum_query_list_size/ + (stats->num_queries_missed_cache+ + stats->num_queries_prefetch) : 0.0, + (unsigned)worker->env.mesh->stats_dropped, + (unsigned)worker->env.mesh->stats_jostled); +} + +/** get rrsets bogus number from validator */ +static size_t +get_rrset_bogus(struct worker* worker) +{ + int m = modstack_find(&worker->env.mesh->mods, "validator"); + struct val_env* ve; + size_t r; + if(m == -1) + return 0; + ve = (struct val_env*)worker->env.modinfo[m]; + lock_basic_lock(&ve->bogus_lock); + r = ve->num_rrset_bogus; + if(!worker->env.cfg->stat_cumulative) + ve->num_rrset_bogus = 0; + lock_basic_unlock(&ve->bogus_lock); + return r; +} + +void +server_stats_compile(struct worker* worker, struct stats_info* s, int reset) +{ + int i; + + s->svr = worker->stats; + s->mesh_num_states = worker->env.mesh->all.count; + s->mesh_num_reply_states = worker->env.mesh->num_reply_states; + s->mesh_jostled = worker->env.mesh->stats_jostled; + s->mesh_dropped = worker->env.mesh->stats_dropped; + s->mesh_replies_sent = worker->env.mesh->replies_sent; + s->mesh_replies_sum_wait = worker->env.mesh->replies_sum_wait; + s->mesh_time_median = timehist_quartile(worker->env.mesh->histogram, + 0.50); + + /* add in the values from the mesh */ + s->svr.ans_secure += worker->env.mesh->ans_secure; + s->svr.ans_bogus += worker->env.mesh->ans_bogus; + s->svr.ans_rcode_nodata += worker->env.mesh->ans_nodata; + for(i=0; i<16; i++) + s->svr.ans_rcode[i] += worker->env.mesh->ans_rcode[i]; + timehist_export(worker->env.mesh->histogram, s->svr.hist, + NUM_BUCKETS_HIST); + /* values from outside network */ + s->svr.unwanted_replies = worker->back->unwanted_replies; + s->svr.qtcp_outgoing = worker->back->num_tcp_outgoing; + + /* get and reset validator rrset bogus number */ + s->svr.rrset_bogus = get_rrset_bogus(worker); + + /* get cache sizes */ + s->svr.msg_cache_count = count_slabhash_entries(worker->env.msg_cache); + s->svr.rrset_cache_count = count_slabhash_entries(&worker->env.rrset_cache->table); + s->svr.infra_cache_count = count_slabhash_entries(worker->env.infra_cache->hosts); + if(worker->env.key_cache) + s->svr.key_cache_count = count_slabhash_entries(worker->env.key_cache->slab); + else s->svr.key_cache_count = 0; + + if(reset && !worker->env.cfg->stat_cumulative) { + worker_stats_clear(worker); + } +} + +void server_stats_obtain(struct worker* worker, struct worker* who, + struct stats_info* s, int reset) +{ + uint8_t *reply = NULL; + uint32_t len = 0; + if(worker == who) { + /* just fill it in */ + server_stats_compile(worker, s, reset); + return; + } + /* communicate over tube */ + verbose(VERB_ALGO, "write stats cmd"); + if(reset) + worker_send_cmd(who, worker_cmd_stats); + else worker_send_cmd(who, worker_cmd_stats_noreset); + verbose(VERB_ALGO, "wait for stats reply"); + if(!tube_read_msg(worker->cmd, &reply, &len, 0)) + fatal_exit("failed to read stats over cmd channel"); + if(len != (uint32_t)sizeof(*s)) + fatal_exit("stats on cmd channel wrong length %d %d", + (int)len, (int)sizeof(*s)); + memcpy(s, reply, (size_t)len); + free(reply); +} + +void server_stats_reply(struct worker* worker, int reset) +{ + struct stats_info s; + server_stats_compile(worker, &s, reset); + verbose(VERB_ALGO, "write stats replymsg"); + if(!tube_write_msg(worker->daemon->workers[0]->cmd, + (uint8_t*)&s, sizeof(s), 0)) + fatal_exit("could not write stat values over cmd channel"); +} + +void server_stats_add(struct stats_info* total, struct stats_info* a) +{ + total->svr.num_queries += a->svr.num_queries; + total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache; + total->svr.num_queries_prefetch += a->svr.num_queries_prefetch; + total->svr.sum_query_list_size += a->svr.sum_query_list_size; + /* the max size reached is upped to higher of both */ + if(a->svr.max_query_list_size > total->svr.max_query_list_size) + total->svr.max_query_list_size = a->svr.max_query_list_size; + + if(a->svr.extended) { + int i; + total->svr.qtype_big += a->svr.qtype_big; + total->svr.qclass_big += a->svr.qclass_big; + total->svr.qtcp += a->svr.qtcp; + total->svr.qtcp_outgoing += a->svr.qtcp_outgoing; + total->svr.qipv6 += a->svr.qipv6; + total->svr.qbit_QR += a->svr.qbit_QR; + total->svr.qbit_AA += a->svr.qbit_AA; + total->svr.qbit_TC += a->svr.qbit_TC; + total->svr.qbit_RD += a->svr.qbit_RD; + total->svr.qbit_RA += a->svr.qbit_RA; + total->svr.qbit_Z += a->svr.qbit_Z; + total->svr.qbit_AD += a->svr.qbit_AD; + total->svr.qbit_CD += a->svr.qbit_CD; + total->svr.qEDNS += a->svr.qEDNS; + total->svr.qEDNS_DO += a->svr.qEDNS_DO; + total->svr.ans_rcode_nodata += a->svr.ans_rcode_nodata; + total->svr.ans_secure += a->svr.ans_secure; + total->svr.ans_bogus += a->svr.ans_bogus; + total->svr.rrset_bogus += a->svr.rrset_bogus; + total->svr.unwanted_replies += a->svr.unwanted_replies; + total->svr.unwanted_queries += a->svr.unwanted_queries; + for(i=0; i<STATS_QTYPE_NUM; i++) + total->svr.qtype[i] += a->svr.qtype[i]; + for(i=0; i<STATS_QCLASS_NUM; i++) + total->svr.qclass[i] += a->svr.qclass[i]; + for(i=0; i<STATS_OPCODE_NUM; i++) + total->svr.qopcode[i] += a->svr.qopcode[i]; + for(i=0; i<STATS_RCODE_NUM; i++) + total->svr.ans_rcode[i] += a->svr.ans_rcode[i]; + for(i=0; i<NUM_BUCKETS_HIST; i++) + total->svr.hist[i] += a->svr.hist[i]; + } + + total->mesh_num_states += a->mesh_num_states; + total->mesh_num_reply_states += a->mesh_num_reply_states; + total->mesh_jostled += a->mesh_jostled; + total->mesh_dropped += a->mesh_dropped; + total->mesh_replies_sent += a->mesh_replies_sent; + timeval_add(&total->mesh_replies_sum_wait, &a->mesh_replies_sum_wait); + /* the medians are averaged together, this is not as accurate as + * taking the median over all of the data, but is good and fast + * added up here, division later*/ + total->mesh_time_median += a->mesh_time_median; +} + +void server_stats_insquery(struct server_stats* stats, struct comm_point* c, + uint16_t qtype, uint16_t qclass, struct edns_data* edns, + struct comm_reply* repinfo) +{ + uint16_t flags = sldns_buffer_read_u16_at(c->buffer, 2); + if(qtype < STATS_QTYPE_NUM) + stats->qtype[qtype]++; + else stats->qtype_big++; + if(qclass < STATS_QCLASS_NUM) + stats->qclass[qclass]++; + else stats->qclass_big++; + stats->qopcode[ LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) ]++; + if(c->type != comm_udp) + stats->qtcp++; + if(repinfo && addr_is_ip6(&repinfo->addr, repinfo->addrlen)) + stats->qipv6++; + if( (flags&BIT_QR) ) + stats->qbit_QR++; + if( (flags&BIT_AA) ) + stats->qbit_AA++; + if( (flags&BIT_TC) ) + stats->qbit_TC++; + if( (flags&BIT_RD) ) + stats->qbit_RD++; + if( (flags&BIT_RA) ) + stats->qbit_RA++; + if( (flags&BIT_Z) ) + stats->qbit_Z++; + if( (flags&BIT_AD) ) + stats->qbit_AD++; + if( (flags&BIT_CD) ) + stats->qbit_CD++; + if(edns->edns_present) { + stats->qEDNS++; + if( (edns->bits & EDNS_DO) ) + stats->qEDNS_DO++; + } +} + +void server_stats_insrcode(struct server_stats* stats, sldns_buffer* buf) +{ + if(stats->extended && sldns_buffer_limit(buf) != 0) { + int r = (int)LDNS_RCODE_WIRE( sldns_buffer_begin(buf) ); + stats->ans_rcode[r] ++; + if(r == 0 && LDNS_ANCOUNT( sldns_buffer_begin(buf) ) == 0) + stats->ans_rcode_nodata ++; + } +} diff --git a/external/unbound/daemon/stats.h b/external/unbound/daemon/stats.h new file mode 100644 index 000000000..5ea00a0da --- /dev/null +++ b/external/unbound/daemon/stats.h @@ -0,0 +1,246 @@ +/* + * daemon/stats.h - collect runtime performance indicators. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file describes the data structure used to collect runtime performance + * numbers. These 'statistics' may be of interest to the operator. + */ + +#ifndef DAEMON_STATS_H +#define DAEMON_STATS_H +#include "util/timehist.h" +struct worker; +struct config_file; +struct comm_point; +struct comm_reply; +struct edns_data; +struct sldns_buffer; + +/** number of qtype that is stored for in array */ +#define STATS_QTYPE_NUM 256 +/** number of qclass that is stored for in array */ +#define STATS_QCLASS_NUM 256 +/** number of rcodes in stats */ +#define STATS_RCODE_NUM 16 +/** number of opcodes in stats */ +#define STATS_OPCODE_NUM 16 + +/** per worker statistics */ +struct server_stats { + /** number of queries from clients received. */ + size_t num_queries; + /** number of queries that had a cache-miss. */ + size_t num_queries_missed_cache; + /** number of prefetch queries - cachehits with prefetch */ + size_t num_queries_prefetch; + + /** + * Sum of the querylistsize of the worker for + * every query that missed cache. To calculate average. + */ + size_t sum_query_list_size; + /** max value of query list size reached. */ + size_t max_query_list_size; + + /** Extended stats below (bool) */ + int extended; + + /** qtype stats */ + size_t qtype[STATS_QTYPE_NUM]; + /** bigger qtype values not in array */ + size_t qtype_big; + /** qclass stats */ + size_t qclass[STATS_QCLASS_NUM]; + /** bigger qclass values not in array */ + size_t qclass_big; + /** query opcodes */ + size_t qopcode[STATS_OPCODE_NUM]; + /** number of queries over TCP */ + size_t qtcp; + /** number of outgoing queries over TCP */ + size_t qtcp_outgoing; + /** number of queries over IPv6 */ + size_t qipv6; + /** number of queries with QR bit */ + size_t qbit_QR; + /** number of queries with AA bit */ + size_t qbit_AA; + /** number of queries with TC bit */ + size_t qbit_TC; + /** number of queries with RD bit */ + size_t qbit_RD; + /** number of queries with RA bit */ + size_t qbit_RA; + /** number of queries with Z bit */ + size_t qbit_Z; + /** number of queries with AD bit */ + size_t qbit_AD; + /** number of queries with CD bit */ + size_t qbit_CD; + /** number of queries with EDNS OPT record */ + size_t qEDNS; + /** number of queries with EDNS with DO flag */ + size_t qEDNS_DO; + /** answer rcodes */ + size_t ans_rcode[STATS_RCODE_NUM]; + /** answers with pseudo rcode 'nodata' */ + size_t ans_rcode_nodata; + /** answers that were secure (AD) */ + size_t ans_secure; + /** answers that were bogus (withheld as SERVFAIL) */ + size_t ans_bogus; + /** rrsets marked bogus by validator */ + size_t rrset_bogus; + /** unwanted traffic received on server-facing ports */ + size_t unwanted_replies; + /** unwanted traffic received on client-facing ports */ + size_t unwanted_queries; + + /** histogram data exported to array + * if the array is the same size, no data is lost, and + * if all histograms are same size (is so by default) then + * adding up works well. */ + size_t hist[NUM_BUCKETS_HIST]; + + /** number of message cache entries */ + size_t msg_cache_count; + /** number of rrset cache entries */ + size_t rrset_cache_count; + /** number of infra cache entries */ + size_t infra_cache_count; + /** number of key cache entries */ + size_t key_cache_count; +}; + +/** + * Statistics to send over the control pipe when asked + * This struct is made to be memcpied, sent in binary. + */ +struct stats_info { + /** the thread stats */ + struct server_stats svr; + + /** mesh stats: current number of states */ + size_t mesh_num_states; + /** mesh stats: current number of reply (user) states */ + size_t mesh_num_reply_states; + /** mesh stats: number of reply states overwritten with a new one */ + size_t mesh_jostled; + /** mesh stats: number of incoming queries dropped */ + size_t mesh_dropped; + /** mesh stats: replies sent */ + size_t mesh_replies_sent; + /** mesh stats: sum of waiting times for the replies */ + struct timeval mesh_replies_sum_wait; + /** mesh stats: median of waiting times for replies (in sec) */ + double mesh_time_median; +}; + +/** + * Initialize server stats to 0. + * @param stats: what to init (this is alloced by the caller). + * @param cfg: with extended statistics option. + */ +void server_stats_init(struct server_stats* stats, struct config_file* cfg); + +/** add query if it missed the cache */ +void server_stats_querymiss(struct server_stats* stats, struct worker* worker); + +/** add query if was cached and also resulted in a prefetch */ +void server_stats_prefetch(struct server_stats* stats, struct worker* worker); + +/** display the stats to the log */ +void server_stats_log(struct server_stats* stats, struct worker* worker, + int threadnum); + +/** + * Obtain the stats info for a given thread. Uses pipe to communicate. + * @param worker: the worker that is executing (the first worker). + * @param who: on who to get the statistics info. + * @param s: the stats block to fill in. + * @param reset: if stats can be reset. + */ +void server_stats_obtain(struct worker* worker, struct worker* who, + struct stats_info* s, int reset); + +/** + * Compile stats into structure for this thread worker. + * Also clears the statistics counters (if that is set by config file). + * @param worker: the worker to compile stats for, also the executing worker. + * @param s: stats block. + * @param reset: if true, depending on config stats are reset. + * if false, statistics are not reset. + */ +void server_stats_compile(struct worker* worker, struct stats_info* s, + int reset); + +/** + * Send stats over comm tube in reply to query cmd + * @param worker: this worker. + * @param reset: if true, depending on config stats are reset. + * if false, statistics are not reset. + */ +void server_stats_reply(struct worker* worker, int reset); + +/** + * Addup stat blocks. + * @param total: sum of the two entries. + * @param a: to add to it. + */ +void server_stats_add(struct stats_info* total, struct stats_info* a); + +/** + * Add stats for this query + * @param stats: the stats + * @param c: commpoint with type and buffer. + * @param qtype: query type + * @param qclass: query class + * @param edns: edns record + * @param repinfo: reply info with remote address + */ +void server_stats_insquery(struct server_stats* stats, struct comm_point* c, + uint16_t qtype, uint16_t qclass, struct edns_data* edns, + struct comm_reply* repinfo); + +/** + * Add rcode for this query. + * @param stats: the stats + * @param buf: buffer with rcode. If buffer is length0: not counted. + */ +void server_stats_insrcode(struct server_stats* stats, struct sldns_buffer* buf); + +#endif /* DAEMON_STATS_H */ diff --git a/external/unbound/daemon/unbound.c b/external/unbound/daemon/unbound.c new file mode 100644 index 000000000..a53fe954d --- /dev/null +++ b/external/unbound/daemon/unbound.c @@ -0,0 +1,780 @@ +/* + * daemon/unbound.c - main program for unbound DNS resolver daemon. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * \file + * + * Main program to start the DNS resolver daemon. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif +#include <sys/time.h> +#include "util/log.h" +#include "daemon/daemon.h" +#include "daemon/remote.h" +#include "util/config_file.h" +#include "util/storage/slabhash.h" +#include "services/listen_dnsport.h" +#include "services/cache/rrset.h" +#include "services/cache/infra.h" +#include "util/fptr_wlist.h" +#include "util/data/msgreply.h" +#include "util/module.h" +#include "util/net_help.h" +#include <signal.h> +#include <fcntl.h> +#include <openssl/crypto.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifndef S_SPLINT_S +/* splint chokes on this system header file */ +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif +#endif /* S_SPLINT_S */ +#ifdef HAVE_LOGIN_CAP_H +#include <login_cap.h> +#endif + +#ifdef USE_MINI_EVENT +# ifdef USE_WINSOCK +# include "util/winsock_event.h" +# else +# include "util/mini_event.h" +# endif +#else +# ifdef HAVE_EVENT_H +# include <event.h> +# else +# include "event2/event.h" +# include "event2/event_struct.h" +# include "event2/event_compat.h" +# endif +#endif + +#ifdef UB_ON_WINDOWS +# include "winrc/win_svc.h" +#endif + +#ifdef HAVE_NSS +/* nss3 */ +# include "nss.h" +#endif + +#ifdef HAVE_SBRK +/** global debug value to keep track of heap memory allocation */ +void* unbound_start_brk = 0; +#endif + +#if !defined(HAVE_EVENT_BASE_GET_METHOD) && (defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP)) +static const char* ev_backend2str(int b) +{ + switch(b) { + case EVBACKEND_SELECT: return "select"; + case EVBACKEND_POLL: return "poll"; + case EVBACKEND_EPOLL: return "epoll"; + case EVBACKEND_KQUEUE: return "kqueue"; + case EVBACKEND_DEVPOLL: return "devpoll"; + case EVBACKEND_PORT: return "evport"; + } + return "unknown"; +} +#endif + +/** get the event system in use */ +static void get_event_sys(const char** n, const char** s, const char** m) +{ +#ifdef USE_WINSOCK + *n = "event"; + *s = "winsock"; + *m = "WSAWaitForMultipleEvents"; +#elif defined(USE_MINI_EVENT) + *n = "mini-event"; + *s = "internal"; + *m = "select"; +#else + struct event_base* b; + *s = event_get_version(); +# ifdef HAVE_EVENT_BASE_GET_METHOD + *n = "libevent"; + b = event_base_new(); + *m = event_base_get_method(b); +# elif defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP) + *n = "libev"; + b = (struct event_base*)ev_default_loop(EVFLAG_AUTO); + *m = ev_backend2str(ev_backend((struct ev_loop*)b)); +# else + *n = "unknown"; + *m = "not obtainable"; + b = NULL; +# endif +# ifdef HAVE_EVENT_BASE_FREE + event_base_free(b); +# endif +#endif +} + +/** print usage. */ +static void usage() +{ + const char** m; + const char *evnm="event", *evsys="", *evmethod=""; + printf("usage: unbound [options]\n"); + printf(" start unbound daemon DNS resolver.\n"); + printf("-h this help\n"); + printf("-c file config file to read instead of %s\n", CONFIGFILE); + printf(" file format is described in unbound.conf(5).\n"); + printf("-d do not fork into the background.\n"); + printf("-v verbose (more times to increase verbosity)\n"); +#ifdef UB_ON_WINDOWS + printf("-w opt windows option: \n"); + printf(" install, remove - manage the services entry\n"); + printf(" service - used to start from services control panel\n"); +#endif + printf("Version %s\n", PACKAGE_VERSION); + get_event_sys(&evnm, &evsys, &evmethod); + printf("linked libs: %s %s (it uses %s), %s\n", + evnm, evsys, evmethod, +#ifdef HAVE_SSL + SSLeay_version(SSLEAY_VERSION) +#elif defined(HAVE_NSS) + NSS_GetVersion() +#endif + ); + printf("linked modules:"); + for(m = module_list_avail(); *m; m++) + printf(" %s", *m); + printf("\n"); + printf("BSD licensed, see LICENSE in source package for details.\n"); + printf("Report bugs to %s\n", PACKAGE_BUGREPORT); +} + +#ifndef unbound_testbound +int replay_var_compare(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + log_assert(0); + return 0; +} +#endif + +/** check file descriptor count */ +static void +checkrlimits(struct config_file* cfg) +{ +#ifndef S_SPLINT_S +#ifdef HAVE_GETRLIMIT + /* list has number of ports to listen to, ifs number addresses */ + int list = ((cfg->do_udp?1:0) + (cfg->do_tcp?1 + + (int)cfg->incoming_num_tcp:0)); + size_t listen_ifs = (size_t)(cfg->num_ifs==0? + ((cfg->do_ip4 && !cfg->if_automatic?1:0) + + (cfg->do_ip6?1:0)):cfg->num_ifs); + size_t listen_num = list*listen_ifs; + size_t outudpnum = (size_t)cfg->outgoing_num_ports; + size_t outtcpnum = cfg->outgoing_num_tcp; + size_t misc = 4; /* logfile, pidfile, stdout... */ + size_t perthread_noudp = listen_num + outtcpnum + + 2/*cmdpipe*/ + 2/*libevent*/ + misc; + size_t perthread = perthread_noudp + outudpnum; + +#if !defined(HAVE_PTHREAD) && !defined(HAVE_SOLARIS_THREADS) + int numthread = 1; /* it forks */ +#else + int numthread = (cfg->num_threads?cfg->num_threads:1); +#endif + size_t total = numthread * perthread + misc; + size_t avail; + struct rlimit rlim; + + if(total > 1024 && + strncmp(event_get_version(), "mini-event", 10) == 0) { + log_warn("too many file descriptors requested. The builtin" + "mini-event cannot handle more than 1024. Config " + "for less fds or compile with libevent"); + if(numthread*perthread_noudp+15 > 1024) + fatal_exit("too much tcp. not enough fds."); + cfg->outgoing_num_ports = (int)((1024 + - numthread*perthread_noudp + - 10 /* safety margin */) /numthread); + log_warn("continuing with less udp ports: %u", + cfg->outgoing_num_ports); + total = 1024; + } + if(perthread > 64 && + strncmp(event_get_version(), "winsock-event", 13) == 0) { + log_err("too many file descriptors requested. The winsock" + " event handler cannot handle more than 64 per " + " thread. Config for less fds"); + if(perthread_noudp+2 > 64) + fatal_exit("too much tcp. not enough fds."); + cfg->outgoing_num_ports = (int)((64 + - perthread_noudp + - 2/* safety margin */)); + log_warn("continuing with less udp ports: %u", + cfg->outgoing_num_ports); + total = numthread*(perthread_noudp+ + (size_t)cfg->outgoing_num_ports)+misc; + } + if(getrlimit(RLIMIT_NOFILE, &rlim) < 0) { + log_warn("getrlimit: %s", strerror(errno)); + return; + } + if(rlim.rlim_cur == (rlim_t)RLIM_INFINITY) + return; + if((size_t)rlim.rlim_cur < total) { + avail = (size_t)rlim.rlim_cur; + rlim.rlim_cur = (rlim_t)(total + 10); + rlim.rlim_max = (rlim_t)(total + 10); +#ifdef HAVE_SETRLIMIT + if(setrlimit(RLIMIT_NOFILE, &rlim) < 0) { + log_warn("setrlimit: %s", strerror(errno)); +#endif + log_warn("cannot increase max open fds from %u to %u", + (unsigned)avail, (unsigned)total+10); + /* check that calculation below does not underflow, + * with 15 as margin */ + if(numthread*perthread_noudp+15 > avail) + fatal_exit("too much tcp. not enough fds."); + cfg->outgoing_num_ports = (int)((avail + - numthread*perthread_noudp + - 10 /* safety margin */) /numthread); + log_warn("continuing with less udp ports: %u", + cfg->outgoing_num_ports); + log_warn("increase ulimit or decrease threads, " + "ports in config to remove this warning"); + return; +#ifdef HAVE_SETRLIMIT + } +#endif + log_warn("increased limit(open files) from %u to %u", + (unsigned)avail, (unsigned)total+10); + } +#else + (void)cfg; +#endif /* HAVE_GETRLIMIT */ +#endif /* S_SPLINT_S */ +} + +/** set verbosity, check rlimits, cache settings */ +static void +apply_settings(struct daemon* daemon, struct config_file* cfg, + int cmdline_verbose, int debug_mode) +{ + /* apply if they have changed */ + verbosity = cmdline_verbose + cfg->verbosity; + if (debug_mode > 1) { + cfg->use_syslog = 0; + cfg->logfile = NULL; + } + daemon_apply_cfg(daemon, cfg); + checkrlimits(cfg); +} + +#ifdef HAVE_KILL +/** Read existing pid from pidfile. + * @param file: file name of pid file. + * @return: the pid from the file or -1 if none. + */ +static pid_t +readpid (const char* file) +{ + int fd; + pid_t pid; + char pidbuf[32]; + char* t; + ssize_t l; + + if ((fd = open(file, O_RDONLY)) == -1) { + if(errno != ENOENT) + log_err("Could not read pidfile %s: %s", + file, strerror(errno)); + return -1; + } + + if (((l = read(fd, pidbuf, sizeof(pidbuf)))) == -1) { + if(errno != ENOENT) + log_err("Could not read pidfile %s: %s", + file, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + + /* Empty pidfile means no pidfile... */ + if (l == 0) { + return -1; + } + + pidbuf[sizeof(pidbuf)-1] = 0; + pid = (pid_t)strtol(pidbuf, &t, 10); + + if (*t && *t != '\n') { + return -1; + } + return pid; +} + +/** write pid to file. + * @param pidfile: file name of pid file. + * @param pid: pid to write to file. + */ +static void +writepid (const char* pidfile, pid_t pid) +{ + FILE* f; + + if ((f = fopen(pidfile, "w")) == NULL ) { + log_err("cannot open pidfile %s: %s", + pidfile, strerror(errno)); + return; + } + if(fprintf(f, "%lu\n", (unsigned long)pid) < 0) { + log_err("cannot write to pidfile %s: %s", + pidfile, strerror(errno)); + } + fclose(f); +} + +/** + * check old pid file. + * @param pidfile: the file name of the pid file. + * @param inchroot: if pidfile is inchroot and we can thus expect to + * be able to delete it. + */ +static void +checkoldpid(char* pidfile, int inchroot) +{ + pid_t old; + if((old = readpid(pidfile)) != -1) { + /* see if it is still alive */ + if(kill(old, 0) == 0 || errno == EPERM) + log_warn("unbound is already running as pid %u.", + (unsigned)old); + else if(inchroot) + log_warn("did not exit gracefully last time (%u)", + (unsigned)old); + } +} +#endif /* HAVE_KILL */ + +/** detach from command line */ +static void +detach(void) +{ +#if defined(HAVE_DAEMON) && !defined(DEPRECATED_DAEMON) + /* use POSIX daemon(3) function */ + if(daemon(1, 0) != 0) + fatal_exit("daemon failed: %s", strerror(errno)); +#else /* no HAVE_DAEMON */ +#ifdef HAVE_FORK + int fd; + /* Take off... */ + switch (fork()) { + case 0: + break; + case -1: + fatal_exit("fork failed: %s", strerror(errno)); + default: + /* exit interactive session */ + exit(0); + } + /* detach */ +#ifdef HAVE_SETSID + if(setsid() == -1) + fatal_exit("setsid() failed: %s", strerror(errno)); +#endif + if ((fd = open("/dev/null", O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close(fd); + } +#endif /* HAVE_FORK */ +#endif /* HAVE_DAEMON */ +} + +/** daemonize, drop user priviliges and chroot if needed */ +static void +perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, + const char** cfgfile) +{ +#ifdef HAVE_GETPWNAM + struct passwd *pwd = NULL; + uid_t uid; + gid_t gid; + /* initialize, but not to 0 (root) */ + memset(&uid, 112, sizeof(uid)); + memset(&gid, 112, sizeof(gid)); + log_assert(cfg); + + if(cfg->username && cfg->username[0]) { + if((pwd = getpwnam(cfg->username)) == NULL) + fatal_exit("user '%s' does not exist.", cfg->username); + uid = pwd->pw_uid; + gid = pwd->pw_gid; + /* endpwent below, in case we need pwd for setusercontext */ + } +#endif + + /* init syslog (as root) if needed, before daemonize, otherwise + * a fork error could not be printed since daemonize closed stderr.*/ + if(cfg->use_syslog) { + log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir); + } + /* if using a logfile, we cannot open it because the logfile would + * be created with the wrong permissions, we cannot chown it because + * we cannot chown system logfiles, so we do not open at all. + * So, using a logfile, the user does not see errors unless -d is + * given to unbound on the commandline. */ + + /* read ssl keys while superuser and outside chroot */ +#ifdef HAVE_SSL + if(!(daemon->rc = daemon_remote_create(cfg))) + fatal_exit("could not set up remote-control"); + if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { + if(!(daemon->listen_sslctx = listen_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) + fatal_exit("could not set up listen SSL_CTX"); + } + if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL, NULL))) + fatal_exit("could not set up connect SSL_CTX"); +#endif + +#ifdef HAVE_KILL + /* check old pid file before forking */ + if(cfg->pidfile && cfg->pidfile[0]) { + /* calculate position of pidfile */ + if(cfg->pidfile[0] == '/') + daemon->pidfile = strdup(cfg->pidfile); + else daemon->pidfile = fname_after_chroot(cfg->pidfile, + cfg, 1); + if(!daemon->pidfile) + fatal_exit("pidfile alloc: out of memory"); + checkoldpid(daemon->pidfile, + /* true if pidfile is inside chrootdir, or nochroot */ + !(cfg->chrootdir && cfg->chrootdir[0]) || + (cfg->chrootdir && cfg->chrootdir[0] && + strncmp(daemon->pidfile, cfg->chrootdir, + strlen(cfg->chrootdir))==0)); + } +#endif + + /* daemonize because pid is needed by the writepid func */ + if(!debug_mode && cfg->do_daemonize) { + detach(); + } + + /* write new pidfile (while still root, so can be outside chroot) */ +#ifdef HAVE_KILL + if(cfg->pidfile && cfg->pidfile[0]) { + writepid(daemon->pidfile, getpid()); + if(!(cfg->chrootdir && cfg->chrootdir[0]) || + (cfg->chrootdir && cfg->chrootdir[0] && + strncmp(daemon->pidfile, cfg->chrootdir, + strlen(cfg->chrootdir))==0)) { + /* delete of pidfile could potentially work, + * chown to get permissions */ + if(cfg->username && cfg->username[0]) { + if(chown(daemon->pidfile, uid, gid) == -1) { + log_err("cannot chown %u.%u %s: %s", + (unsigned)uid, (unsigned)gid, + daemon->pidfile, strerror(errno)); + } + } + } + } +#else + (void)daemon; +#endif + + /* Set user context */ +#ifdef HAVE_GETPWNAM + if(cfg->username && cfg->username[0]) { +#ifdef HAVE_SETUSERCONTEXT + /* setusercontext does initgroups, setuid, setgid, and + * also resource limits from login config, but we + * still call setresuid, setresgid to be sure to set all uid*/ + if(setusercontext(NULL, pwd, uid, (unsigned) + LOGIN_SETALL & ~LOGIN_SETUSER & ~LOGIN_SETGROUP) != 0) + log_warn("unable to setusercontext %s: %s", + cfg->username, strerror(errno)); +#endif /* HAVE_SETUSERCONTEXT */ + } +#endif /* HAVE_GETPWNAM */ + + /* box into the chroot */ +#ifdef HAVE_CHROOT + if(cfg->chrootdir && cfg->chrootdir[0]) { + if(chdir(cfg->chrootdir)) { + fatal_exit("unable to chdir to chroot %s: %s", + cfg->chrootdir, strerror(errno)); + } + verbose(VERB_QUERY, "chdir to %s", cfg->chrootdir); + if(chroot(cfg->chrootdir)) + fatal_exit("unable to chroot to %s: %s", + cfg->chrootdir, strerror(errno)); + if(chdir("/")) + fatal_exit("unable to chdir to / in chroot %s: %s", + cfg->chrootdir, strerror(errno)); + verbose(VERB_QUERY, "chroot to %s", cfg->chrootdir); + if(strncmp(*cfgfile, cfg->chrootdir, + strlen(cfg->chrootdir)) == 0) + (*cfgfile) += strlen(cfg->chrootdir); + + /* adjust stored pidfile for chroot */ + if(daemon->pidfile && daemon->pidfile[0] && + strncmp(daemon->pidfile, cfg->chrootdir, + strlen(cfg->chrootdir))==0) { + char* old = daemon->pidfile; + daemon->pidfile = strdup(old+strlen(cfg->chrootdir)); + free(old); + if(!daemon->pidfile) + log_err("out of memory in pidfile adjust"); + } + daemon->chroot = strdup(cfg->chrootdir); + if(!daemon->chroot) + log_err("out of memory in daemon chroot dir storage"); + } +#else + (void)cfgfile; +#endif + /* change to working directory inside chroot */ + if(cfg->directory && cfg->directory[0]) { + char* dir = cfg->directory; + if(cfg->chrootdir && cfg->chrootdir[0] && + strncmp(dir, cfg->chrootdir, + strlen(cfg->chrootdir)) == 0) + dir += strlen(cfg->chrootdir); + if(dir[0]) { + if(chdir(dir)) { + fatal_exit("Could not chdir to %s: %s", + dir, strerror(errno)); + } + verbose(VERB_QUERY, "chdir to %s", dir); + } + } + + /* drop permissions after chroot, getpwnam, pidfile, syslog done*/ +#ifdef HAVE_GETPWNAM + if(cfg->username && cfg->username[0]) { +# ifdef HAVE_INITGROUPS + if(initgroups(cfg->username, gid) != 0) + log_warn("unable to initgroups %s: %s", + cfg->username, strerror(errno)); +# endif /* HAVE_INITGROUPS */ + endpwent(); + +#ifdef HAVE_SETRESGID + if(setresgid(gid,gid,gid) != 0) +#elif defined(HAVE_SETREGID) && !defined(DARWIN_BROKEN_SETREUID) + if(setregid(gid,gid) != 0) +#else /* use setgid */ + if(setgid(gid) != 0) +#endif /* HAVE_SETRESGID */ + fatal_exit("unable to set group id of %s: %s", + cfg->username, strerror(errno)); +#ifdef HAVE_SETRESUID + if(setresuid(uid,uid,uid) != 0) +#elif defined(HAVE_SETREUID) && !defined(DARWIN_BROKEN_SETREUID) + if(setreuid(uid,uid) != 0) +#else /* use setuid */ + if(setuid(uid) != 0) +#endif /* HAVE_SETRESUID */ + fatal_exit("unable to set user id of %s: %s", + cfg->username, strerror(errno)); + verbose(VERB_QUERY, "drop user privileges, run as %s", + cfg->username); + } +#endif /* HAVE_GETPWNAM */ + /* file logging inited after chroot,chdir,setuid is done so that + * it would succeed on SIGHUP as well */ + if(!cfg->use_syslog) + log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir); +} + +/** + * Run the daemon. + * @param cfgfile: the config file name. + * @param cmdline_verbose: verbosity resulting from commandline -v. + * These increase verbosity as specified in the config file. + * @param debug_mode: if set, do not daemonize. + */ +static void +run_daemon(const char* cfgfile, int cmdline_verbose, int debug_mode) +{ + struct config_file* cfg = NULL; + struct daemon* daemon = NULL; + int done_setup = 0; + + if(!(daemon = daemon_init())) + fatal_exit("alloc failure"); + while(!daemon->need_to_exit) { + if(done_setup) + verbose(VERB_OPS, "Restart of %s.", PACKAGE_STRING); + else verbose(VERB_OPS, "Start of %s.", PACKAGE_STRING); + + /* config stuff */ + if(!(cfg = config_create())) + fatal_exit("Could not alloc config defaults"); + if(!config_read(cfg, cfgfile, daemon->chroot)) { + if(errno != ENOENT) + fatal_exit("Could not read config file: %s", + cfgfile); + log_warn("Continuing with default config settings"); + } + apply_settings(daemon, cfg, cmdline_verbose, debug_mode); + + /* prepare */ + if(!daemon_open_shared_ports(daemon)) + fatal_exit("could not open ports"); + if(!done_setup) { + perform_setup(daemon, cfg, debug_mode, &cfgfile); + done_setup = 1; + } else { + /* reopen log after HUP to facilitate log rotation */ + if(!cfg->use_syslog) + log_init(cfg->logfile, 0, cfg->chrootdir); + } + /* work */ + daemon_fork(daemon); + + /* clean up for restart */ + verbose(VERB_ALGO, "cleanup."); + daemon_cleanup(daemon); + config_delete(cfg); + } + verbose(VERB_ALGO, "Exit cleanup."); + /* this unlink may not work if the pidfile is located outside + * of the chroot/workdir or we no longer have permissions */ + if(daemon->pidfile) { + int fd; + /* truncate pidfile */ + fd = open(daemon->pidfile, O_WRONLY | O_TRUNC, 0644); + if(fd != -1) + close(fd); + /* delete pidfile */ + unlink(daemon->pidfile); + } + daemon_delete(daemon); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** + * main program. Set options given commandline arguments. + * @param argc: number of commandline arguments. + * @param argv: array of commandline arguments. + * @return: exit status of the program. + */ +int +main(int argc, char* argv[]) +{ + int c; + const char* cfgfile = CONFIGFILE; + const char* winopt = NULL; + int cmdline_verbose = 0; + int debug_mode = 0; +#ifdef UB_ON_WINDOWS + int cmdline_cfg = 0; +#endif + +#ifdef HAVE_SBRK + /* take debug snapshot of heap */ + unbound_start_brk = sbrk(0); +#endif + + log_init(NULL, 0, NULL); + log_ident_set(strrchr(argv[0],'/')?strrchr(argv[0],'/')+1:argv[0]); + /* parse the options */ + while( (c=getopt(argc, argv, "c:dhvw:")) != -1) { + switch(c) { + case 'c': + cfgfile = optarg; +#ifdef UB_ON_WINDOWS + cmdline_cfg = 1; +#endif + break; + case 'v': + cmdline_verbose ++; + verbosity++; + break; + case 'd': + debug_mode++; + break; + case 'w': + winopt = optarg; + break; + case '?': + case 'h': + default: + usage(); + return 1; + } + } + argc -= optind; + argv += optind; + + if(winopt) { +#ifdef UB_ON_WINDOWS + wsvc_command_option(winopt, cfgfile, cmdline_verbose, + cmdline_cfg); +#else + fatal_exit("option not supported"); +#endif + } + + if(argc != 0) { + usage(); + return 1; + } + + run_daemon(cfgfile, cmdline_verbose, debug_mode); + log_init(NULL, 0, NULL); /* close logfile */ + return 0; +} diff --git a/external/unbound/daemon/worker.c b/external/unbound/daemon/worker.c new file mode 100644 index 000000000..f90676213 --- /dev/null +++ b/external/unbound/daemon/worker.c @@ -0,0 +1,1452 @@ +/* + * daemon/worker.c - worker that handles a pending list of requests. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file implements the worker that handles callbacks on events, for + * pending requests. + */ +#include "config.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/random.h" +#include "daemon/worker.h" +#include "daemon/daemon.h" +#include "daemon/remote.h" +#include "daemon/acl_list.h" +#include "util/netevent.h" +#include "util/config_file.h" +#include "util/module.h" +#include "util/regional.h" +#include "util/storage/slabhash.h" +#include "services/listen_dnsport.h" +#include "services/outside_network.h" +#include "services/outbound_list.h" +#include "services/cache/rrset.h" +#include "services/cache/infra.h" +#include "services/cache/dns.h" +#include "services/mesh.h" +#include "services/localzone.h" +#include "util/data/msgparse.h" +#include "util/data/msgencode.h" +#include "util/data/dname.h" +#include "util/fptr_wlist.h" +#include "util/tube.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" +#include "validator/autotrust.h" +#include "validator/val_anchor.h" +#include "libunbound/context.h" +#include "libunbound/libworker.h" +#include "ldns/sbuffer.h" + +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#include <signal.h> +#ifdef UB_ON_WINDOWS +#include "winrc/win_svc.h" +#endif + +/** Size of an UDP datagram */ +#define NORMAL_UDP_SIZE 512 /* bytes */ + +/** + * seconds to add to prefetch leeway. This is a TTL that expires old rrsets + * earlier than they should in order to put the new update into the cache. + * This additional value is to make sure that if not all TTLs are equal in + * the message to be updated(and replaced), that rrsets with up to this much + * extra TTL are also replaced. This means that the resulting new message + * will have (most likely) this TTL at least, avoiding very small 'split + * second' TTLs due to operators choosing relative primes for TTLs (or so). + * Also has to be at least one to break ties (and overwrite cached entry). + */ +#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 + /* debug func in validator module */ + size_t total, front, back, mesh, msg, rrset, infra, ac, superac; + size_t me, iter, val, anch; + int i; + if(verbosity < VERB_ALGO) + return; + front = listen_get_mem(worker->front); + back = outnet_get_mem(worker->back); + msg = slabhash_get_mem(worker->env.msg_cache); + rrset = slabhash_get_mem(&worker->env.rrset_cache->table); + infra = infra_get_mem(worker->env.infra_cache); + mesh = mesh_get_mem(worker->env.mesh); + ac = alloc_get_mem(&worker->alloc); + superac = alloc_get_mem(&worker->daemon->superalloc); + anch = anchors_get_mem(worker->env.anchors); + iter = 0; + val = 0; + for(i=0; i<worker->env.mesh->mods.num; i++) { + fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> + mods.mod[i]->get_mem)); + if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) + val += (*worker->env.mesh->mods.mod[i]->get_mem) + (&worker->env, i); + else iter += (*worker->env.mesh->mods.mod[i]->get_mem) + (&worker->env, i); + } + me = sizeof(*worker) + sizeof(*worker->base) + sizeof(*worker->comsig) + + comm_point_get_mem(worker->cmd_com) + + sizeof(worker->rndstate) + + regional_get_mem(worker->scratchpad) + + sizeof(*worker->env.scratch_buffer) + + sldns_buffer_capacity(worker->env.scratch_buffer) + + forwards_get_mem(worker->env.fwds) + + hints_get_mem(worker->env.hints); + if(worker->thread_num == 0) + me += acl_list_get_mem(worker->daemon->acl); + if(cur_serv) { + me += serviced_get_mem(cur_serv); + } + total = front+back+mesh+msg+rrset+infra+iter+val+ac+superac+me; + 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", + (unsigned)total, (unsigned)front, (unsigned)back, + (unsigned)mesh, (unsigned)msg, (unsigned)rrset, + (unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch, + (unsigned)ac, (unsigned)superac, (unsigned)me); + debug_total_mem(total); +#else /* no UNBOUND_ALLOC_STATS */ + size_t val = 0; + int i; + if(verbosity < VERB_QUERY) + return; + for(i=0; i<worker->env.mesh->mods.num; i++) { + fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> + mods.mod[i]->get_mem)); + if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) + val += (*worker->env.mesh->mods.mod[i]->get_mem) + (&worker->env, i); + } + 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 /* UNBOUND_ALLOC_STATS */ +} + +void +worker_send_cmd(struct worker* worker, enum worker_commands cmd) +{ + uint32_t c = (uint32_t)htonl(cmd); + if(!tube_write_msg(worker->cmd, (uint8_t*)&c, sizeof(c), 0)) { + log_err("worker send cmd %d failed", (int)cmd); + } +} + +int +worker_handle_reply(struct comm_point* c, void* arg, int error, + struct comm_reply* reply_info) +{ + struct module_qstate* q = (struct module_qstate*)arg; + struct worker* worker = q->env->worker; + struct outbound_entry e; + e.qstate = q; + e.qsent = NULL; + + if(error != 0) { + mesh_report_reply(worker->env.mesh, &e, reply_info, error); + worker_mem_report(worker, NULL); + return 0; + } + /* sanity check. */ + if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer)) + || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) != + LDNS_PACKET_QUERY + || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) { + /* error becomes timeout for the module as if this reply + * never arrived. */ + mesh_report_reply(worker->env.mesh, &e, reply_info, + NETEVENT_TIMEOUT); + worker_mem_report(worker, NULL); + return 0; + } + mesh_report_reply(worker->env.mesh, &e, reply_info, NETEVENT_NOERROR); + worker_mem_report(worker, NULL); + return 0; +} + +int +worker_handle_service_reply(struct comm_point* c, void* arg, int error, + struct comm_reply* reply_info) +{ + struct outbound_entry* e = (struct outbound_entry*)arg; + struct worker* worker = e->qstate->env->worker; + struct serviced_query *sq = e->qsent; + + verbose(VERB_ALGO, "worker svcd callback for qstate %p", e->qstate); + if(error != 0) { + mesh_report_reply(worker->env.mesh, e, reply_info, error); + worker_mem_report(worker, sq); + return 0; + } + /* sanity check. */ + if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer)) + || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) != + LDNS_PACKET_QUERY + || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) { + /* error becomes timeout for the module as if this reply + * never arrived. */ + verbose(VERB_ALGO, "worker: bad reply handled as timeout"); + mesh_report_reply(worker->env.mesh, e, reply_info, + NETEVENT_TIMEOUT); + worker_mem_report(worker, sq); + return 0; + } + mesh_report_reply(worker->env.mesh, e, reply_info, NETEVENT_NOERROR); + worker_mem_report(worker, sq); + return 0; +} + +/** check request sanity. + * @param pkt: the wire packet to examine for sanity. + * @param worker: parameters for checking. + * @return error code, 0 OK, or -1 discard. +*/ +static int +worker_check_request(sldns_buffer* pkt, struct worker* worker) +{ + if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { + verbose(VERB_QUERY, "request too short, discarded"); + return -1; + } + if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE && + worker->daemon->cfg->harden_large_queries) { + verbose(VERB_QUERY, "request too large, discarded"); + return -1; + } + if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) { + verbose(VERB_QUERY, "request has QR bit on, discarded"); + return -1; + } + if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) { + LDNS_TC_CLR(sldns_buffer_begin(pkt)); + verbose(VERB_QUERY, "request bad, has TC bit on"); + return LDNS_RCODE_FORMERR; + } + if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) { + verbose(VERB_QUERY, "request unknown opcode %d", + LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt))); + return LDNS_RCODE_NOTIMPL; + } + if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) { + verbose(VERB_QUERY, "request wrong nr qd=%d", + LDNS_QDCOUNT(sldns_buffer_begin(pkt))); + return LDNS_RCODE_FORMERR; + } + if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0) { + verbose(VERB_QUERY, "request wrong nr an=%d", + LDNS_ANCOUNT(sldns_buffer_begin(pkt))); + return LDNS_RCODE_FORMERR; + } + if(LDNS_NSCOUNT(sldns_buffer_begin(pkt)) != 0) { + verbose(VERB_QUERY, "request wrong nr ns=%d", + LDNS_NSCOUNT(sldns_buffer_begin(pkt))); + return LDNS_RCODE_FORMERR; + } + if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) { + verbose(VERB_QUERY, "request wrong nr ar=%d", + LDNS_ARCOUNT(sldns_buffer_begin(pkt))); + return LDNS_RCODE_FORMERR; + } + return 0; +} + +void +worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg, + size_t len, int error, void* arg) +{ + struct worker* worker = (struct worker*)arg; + enum worker_commands cmd; + if(error != NETEVENT_NOERROR) { + free(msg); + if(error == NETEVENT_CLOSED) + comm_base_exit(worker->base); + else log_info("control event: %d", error); + return; + } + if(len != sizeof(uint32_t)) { + fatal_exit("bad control msg length %d", (int)len); + } + cmd = sldns_read_uint32(msg); + free(msg); + switch(cmd) { + case worker_cmd_quit: + verbose(VERB_ALGO, "got control cmd quit"); + comm_base_exit(worker->base); + break; + case worker_cmd_stats: + verbose(VERB_ALGO, "got control cmd stats"); + server_stats_reply(worker, 1); + break; + case worker_cmd_stats_noreset: + verbose(VERB_ALGO, "got control cmd stats_noreset"); + server_stats_reply(worker, 0); + break; + case worker_cmd_remote: + verbose(VERB_ALGO, "got control cmd remote"); + daemon_remote_exec(worker); + break; + default: + log_err("bad command %d", (int)cmd); + break; + } +} + +/** check if a delegation is secure */ +static enum sec_status +check_delegation_secure(struct reply_info *rep) +{ + /* return smallest security status */ + size_t i; + enum sec_status sec = sec_status_secure; + enum sec_status s; + size_t num = rep->an_numrrsets + rep->ns_numrrsets; + /* check if answer and authority are OK */ + for(i=0; i<num; i++) { + s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security; + if(s < sec) + sec = s; + } + /* in additional, only unchecked triggers revalidation */ + for(i=num; i<rep->rrset_count; i++) { + s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security; + if(s == sec_status_unchecked) + return s; + } + return sec; +} + +/** remove nonsecure from a delegation referral additional section */ +static void +deleg_remove_nonsecure_additional(struct reply_info* rep) +{ + /* we can simply edit it, since we are working in the scratch region */ + size_t i; + enum sec_status s; + + for(i = rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) { + s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security; + if(s != sec_status_secure) { + memmove(rep->rrsets+i, rep->rrsets+i+1, + sizeof(struct ub_packed_rrset_key*)* + (rep->rrset_count - i - 1)); + rep->ar_numrrsets--; + rep->rrset_count--; + i--; + } + } +} + +/** answer nonrecursive query from the cache */ +static int +answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, + uint16_t id, uint16_t flags, struct comm_reply* repinfo, + struct edns_data* edns) +{ + /* for a nonrecursive query return either: + * o an error (servfail; we try to avoid this) + * o a delegation (closest we have; this routine tries that) + * o the answer (checked by answer_from_cache) + * + * So, grab a delegation from the rrset cache. + * Then check if it needs validation, if so, this routine fails, + * so that iterator can prime and validator can verify rrsets. + */ + uint16_t udpsize = edns->udp_size; + int secure = 0; + time_t timenow = *worker->env.now; + int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd) + && worker->env.need_to_validate; + struct dns_msg *msg = NULL; + struct delegpt *dp; + + dp = dns_cache_find_delegation(&worker->env, qinfo->qname, + 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; + } + 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 */ + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + 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]++; + } + return 1; + case sec_status_secure: + /* all rrsets are secure */ + /* remove non-secure rrsets from the add. section*/ + if(worker->env.cfg->val_clean_additional) + deleg_remove_nonsecure_additional(msg->rep); + secure = 1; + break; + case sec_status_indeterminate: + case sec_status_insecure: + default: + /* not secure */ + secure = 0; + break; + } + } + /* return this delegation from the cache */ + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + 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)) { + 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); + } + return 1; +} + +/** answer query from the cache */ +static int +answer_from_cache(struct worker* worker, struct query_info* qinfo, + 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; + 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; + } + 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 == + htons(LDNS_RR_TYPE_DNAME))) { + if(!reply_check_cname_chain(rep)) { + /* cname chain invalid, redo iterator steps */ + verbose(VERB_ALGO, "Cache reply: cname chain broken"); + bail_out: + rrset_array_unlock_touch(worker->env.rrset_cache, + worker->scratchpad, rep->ref, rep->rrset_count); + regional_free_all(worker->scratchpad); + return 0; + } + } + /* check security status of the cached answer */ + if( rep->security == sec_status_bogus && must_validate) { + /* BAD cached */ + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + 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] ++; + } + return 1; + } else if( rep->security == sec_status_unchecked && must_validate) { + verbose(VERB_ALGO, "Cache reply: unchecked entry needs " + "validation"); + goto bail_out; /* need to validate cache entry first */ + } else if(rep->security == sec_status_secure) { + if(reply_all_rrsets_secure(rep)) + secure = 1; + else { + if(must_validate) { + verbose(VERB_ALGO, "Cache reply: secure entry" + " changed status"); + goto bail_out; /* rrset changed, re-verify */ + } + secure = 0; + } + } else secure = 0; + + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + if(!reply_info_answer_encode(qinfo, rep, id, flags, + repinfo->c->buffer, timenow, 1, worker->scratchpad, + udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, + qinfo, id, flags, edns); + } + /* cannot send the reply right now, because blocking network syscall + * 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); + } + /* go and return this buffer to the client */ + return 1; +} + +/** Reply to client and perform prefetch to keep cache up to date */ +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); + server_stats_prefetch(&worker->stats, worker); + + /* create the prefetch in the mesh as a normal lookup without + * client addrs waiting, which has the cache blacklisted (to bypass + * the cache and go to the network for the data). */ + /* this (potentially) runs the mesh for the new query */ + mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway + + PREFETCH_EXPIRY_ADD); +} + +/** + * Fill CH class answer into buffer. Keeps query. + * @param pkt: buffer + * @param str: string to put into text record (<255). + * @param edns: edns reply information. + */ +static void +chaos_replystr(sldns_buffer* pkt, const char* str, struct edns_data* edns) +{ + size_t len = strlen(str); + 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, 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); + sldns_buffer_flip(pkt); + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->bits &= EDNS_DO; + attach_edns_record(pkt, edns); +} + +/** + * Answer CH class queries. + * @param w: worker + * @param qinfo: query info. Pointer into packet buffer. + * @param edns: edns info from query. + * @param pkt: packet buffer. + * @return: true if a reply is to be sent. + */ +static int +answer_chaos(struct worker* w, struct query_info* qinfo, + struct edns_data* edns, sldns_buffer* pkt) +{ + struct config_file* cfg = w->env.cfg; + if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != LDNS_RR_TYPE_TXT) + return 0; + if(query_dname_compare(qinfo->qname, + (uint8_t*)"\002id\006server") == 0 || + query_dname_compare(qinfo->qname, + (uint8_t*)"\010hostname\004bind") == 0) + { + if(cfg->hide_identity) + return 0; + if(cfg->identity==NULL || cfg->identity[0]==0) { + char buf[MAXHOSTNAMELEN+1]; + if (gethostname(buf, MAXHOSTNAMELEN) == 0) { + buf[MAXHOSTNAMELEN] = 0; + chaos_replystr(pkt, buf, edns); + } else { + log_err("gethostname: %s", strerror(errno)); + chaos_replystr(pkt, "no hostname", edns); + } + } + else chaos_replystr(pkt, cfg->identity, edns); + return 1; + } + if(query_dname_compare(qinfo->qname, + (uint8_t*)"\007version\006server") == 0 || + query_dname_compare(qinfo->qname, + (uint8_t*)"\007version\004bind") == 0) + { + 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); + return 1; + } + return 0; +} + +static int +deny_refuse(struct comm_point* c, enum acl_access acl, + enum acl_access deny, enum acl_access refuse, + struct worker* worker, struct comm_reply* repinfo) +{ + if(acl == deny) { + comm_point_drop_reply(repinfo); + if(worker->stats.extended) + worker->stats.unwanted_queries++; + return 0; + } else if(acl == refuse) { + log_addr(VERB_ALGO, "refused query from", + &repinfo->addr, repinfo->addrlen); + log_buf(VERB_ALGO, "refuse", c->buffer); + if(worker->stats.extended) + worker->stats.unwanted_queries++; + if(worker_check_request(c->buffer, worker) == -1) { + comm_point_drop_reply(repinfo); + return 0; /* discard this */ + } + sldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE); + sldns_buffer_write_at(c->buffer, 4, + (uint8_t*)"\0\0\0\0\0\0\0\0", 8); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_REFUSED); + return 1; + } + + return -1; +} + +static int +deny_refuse_all(struct comm_point* c, enum acl_access acl, + struct worker* worker, struct comm_reply* repinfo) +{ + return deny_refuse(c, acl, acl_deny, acl_refuse, worker, repinfo); +} + +static int +deny_refuse_non_local(struct comm_point* c, enum acl_access acl, + struct worker* worker, struct comm_reply* repinfo) +{ + return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local, worker, repinfo); +} + +int +worker_handle_request(struct comm_point* c, void* arg, int error, + struct comm_reply* repinfo) +{ + struct worker* worker = (struct worker*)arg; + int ret; + hashvalue_t h; + struct lruhash_entry* e; + struct query_info qinfo; + struct edns_data edns; + enum acl_access acl; + int rc = 0; + + 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_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, + repinfo->addrlen); + if((ret=deny_refuse_all(c, acl, worker, repinfo)) != -1) + { + if(ret == 1) + goto send_reply; + return ret; + } + if((ret=worker_check_request(c->buffer, worker)) != 0) { + verbose(VERB_ALGO, "worker check request: bad query."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + if(ret != -1) { + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); + return 1; + } + comm_point_drop_reply(repinfo); + return 0; + } + worker->stats.num_queries++; + /* see if query is in the cache */ + if(!query_info_parse(&qinfo, c->buffer)) { + verbose(VERB_ALGO, "worker parse request: formerror."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + sldns_buffer_rewind(c->buffer); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_FORMERR); + server_stats_insrcode(&worker->stats, c->buffer); + goto send_reply; + } + if(worker->env.cfg->log_queries) { + char ip[128]; + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); + log_nametypeclass(0, ip, qinfo.qname, qinfo.qtype, qinfo.qclass); + } + if(qinfo.qtype == LDNS_RR_TYPE_AXFR || + qinfo.qtype == LDNS_RR_TYPE_IXFR) { + verbose(VERB_ALGO, "worker request: refused zone transfer."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + sldns_buffer_rewind(c->buffer); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_REFUSED); + 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)) != 0) { + verbose(VERB_ALGO, "worker parse edns: formerror."); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + sldns_buffer_rewind(c->buffer); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); + server_stats_insrcode(&worker->stats, c->buffer); + goto send_reply; + } + if(edns.edns_present && edns.edns_version != 0) { + edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4); + edns.edns_version = EDNS_ADVERTISED_VERSION; + edns.udp_size = EDNS_ADVERTISED_SIZE; + edns.bits &= EDNS_DO; + 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); + goto send_reply; + } + if(edns.edns_present && edns.udp_size < NORMAL_UDP_SIZE && + worker->daemon->cfg->harden_short_bufsize) { + verbose(VERB_QUERY, "worker request: EDNS bufsize %d ignored", + (int)edns.udp_size); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + edns.udp_size = NORMAL_UDP_SIZE; + } + if(edns.udp_size > worker->daemon->cfg->max_udp_size && + c->type == comm_udp) { + verbose(VERB_QUERY, + "worker request: max UDP reply size modified" + " (%d to max-udp-size)", (int)edns.udp_size); + log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); + edns.udp_size = worker->daemon->cfg->max_udp_size; + } + if(edns.udp_size < LDNS_HEADER_SIZE) { + verbose(VERB_ALGO, "worker request: edns is too small."); + log_addr(VERB_CLIENT, "from", &repinfo->addr, repinfo->addrlen); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_TC_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_SERVFAIL); + sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE); + sldns_buffer_write_at(c->buffer, 4, + (uint8_t*)"\0\0\0\0\0\0\0\0", 8); + sldns_buffer_flip(c->buffer); + goto send_reply; + } + if(worker->stats.extended) + server_stats_insquery(&worker->stats, c, qinfo.qtype, + qinfo.qclass, &edns, repinfo); + if(c->type != comm_udp) + edns.udp_size = 65535; /* max size for TCP replies */ + if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo, + &edns, c->buffer)) { + server_stats_insrcode(&worker->stats, c->buffer); + goto send_reply; + } + if(local_zones_answer(worker->daemon->local_zones, &qinfo, &edns, + c->buffer, worker->scratchpad)) { + regional_free_all(worker->scratchpad); + if(sldns_buffer_limit(c->buffer) == 0) { + comm_point_drop_reply(repinfo); + return 0; + } + server_stats_insrcode(&worker->stats, c->buffer); + goto send_reply; + } + + /* We've looked in our local zones. If the answer isn't there, we + * might need to bail out based on ACLs now. */ + if((ret=deny_refuse_non_local(c, acl, worker, repinfo)) != -1) + { + if(ret == 1) + goto send_reply; + return ret; + } + + /* If this request does not have the recursion bit set, verify + * ACLs allow the snooping. */ + if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) && + acl != acl_allow_snoop ) { + sldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE); + sldns_buffer_write_at(c->buffer, 4, + (uint8_t*)"\0\0\0\0\0\0\0\0", 8); + LDNS_QR_SET(sldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), + LDNS_RCODE_REFUSED); + sldns_buffer_flip(c->buffer); + 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); + 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; + 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; + } + 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; + } + verbose(VERB_ALGO, "answer norec from cache -- " + "need to validate or not primed"); + } + sldns_buffer_rewind(c->buffer); + server_stats_querymiss(&worker->stats, worker); + + if(verbosity >= VERB_CLIENT) { + if(c->type == comm_udp) + log_addr(VERB_CLIENT, "udp request from", + &repinfo->addr, repinfo->addrlen); + else log_addr(VERB_CLIENT, "tcp request from", + &repinfo->addr, repinfo->addrlen); + } + + /* grab a work request structure for this new request */ + mesh_new_client(worker->env.mesh, &qinfo, + sldns_buffer_read_u16_at(c->buffer, 2), + &edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer)); + worker_mem_report(worker, NULL); + return 0; + +send_reply: + rc = 1; +send_reply_rc: +#ifdef USE_DNSTAP + if(worker->dtenv.log_client_response_messages) + dt_msg_send_client_response(&worker->dtenv, &repinfo->addr, + c->type, c->buffer); +#endif + return rc; +} + +void +worker_sighandler(int sig, void* arg) +{ + /* note that log, print, syscalls here give race conditions. + * And cause hangups if the log-lock is held by the application. */ + struct worker* worker = (struct worker*)arg; + switch(sig) { +#ifdef SIGHUP + case SIGHUP: + comm_base_exit(worker->base); + break; +#endif + case SIGINT: + worker->need_to_exit = 1; + comm_base_exit(worker->base); + break; +#ifdef SIGQUIT + case SIGQUIT: + worker->need_to_exit = 1; + comm_base_exit(worker->base); + break; +#endif + case SIGTERM: + worker->need_to_exit = 1; + comm_base_exit(worker->base); + break; + default: + /* unknown signal, ignored */ + break; + } +} + +/** restart statistics timer for worker, if enabled */ +static void +worker_restart_timer(struct worker* worker) +{ + if(worker->env.cfg->stat_interval > 0) { + struct timeval tv; +#ifndef S_SPLINT_S + tv.tv_sec = worker->env.cfg->stat_interval; + tv.tv_usec = 0; +#endif + comm_timer_set(worker->stat_timer, &tv); + } +} + +void worker_stat_timer_cb(void* arg) +{ + struct worker* worker = (struct worker*)arg; + server_stats_log(&worker->stats, worker, worker->thread_num); + mesh_stats(worker->env.mesh, "mesh has"); + worker_mem_report(worker, NULL); + if(!worker->daemon->cfg->stat_cumulative) { + worker_stats_clear(worker); + } + /* start next timer */ + worker_restart_timer(worker); +} + +void worker_probe_timer_cb(void* arg) +{ + struct worker* worker = (struct worker*)arg; + struct timeval tv; +#ifndef S_SPLINT_S + tv.tv_sec = (time_t)autr_probe_timer(&worker->env); + tv.tv_usec = 0; +#endif + if(tv.tv_sec != 0) + comm_timer_set(worker->env.probe_timer, &tv); +} + +struct worker* +worker_create(struct daemon* daemon, int id, int* ports, int n) +{ + unsigned int seed; + struct worker* worker = (struct worker*)calloc(1, + sizeof(struct worker)); + if(!worker) + return NULL; + worker->numports = n; + worker->ports = (int*)memdup(ports, sizeof(int)*n); + if(!worker->ports) { + free(worker); + return NULL; + } + worker->daemon = daemon; + worker->thread_num = id; + if(!(worker->cmd = tube_create())) { + free(worker->ports); + free(worker); + return NULL; + } + /* create random state here to avoid locking trouble in RAND_bytes */ + seed = (unsigned int)time(NULL) ^ (unsigned int)getpid() ^ + (((unsigned int)worker->thread_num)<<17); + /* shift thread_num so it does not match out pid bits */ + if(!(worker->rndstate = ub_initstate(seed, daemon->rand))) { + seed = 0; + log_err("could not init random numbers."); + tube_delete(worker->cmd); + free(worker->ports); + free(worker); + return NULL; + } + seed = 0; +#ifdef USE_DNSTAP + if(daemon->cfg->dnstap) { + log_assert(daemon->dtenv != NULL); + memcpy(&worker->dtenv, daemon->dtenv, sizeof(struct dt_env)); + if(!dt_init(&worker->dtenv)) + fatal_exit("dt_init failed"); + } +#endif + return worker; +} + +int +worker_init(struct worker* worker, struct config_file *cfg, + struct listen_port* ports, int do_sigs) +{ +#ifdef USE_DNSTAP + struct dt_env* dtenv = &worker->dtenv; +#else + void* dtenv = NULL; +#endif + worker->need_to_exit = 0; + worker->base = comm_base_create(do_sigs); + if(!worker->base) { + log_err("could not create event handling base"); + worker_delete(worker); + return 0; + } + comm_base_set_slow_accept_handlers(worker->base, &worker_stop_accept, + &worker_start_accept, worker); + if(do_sigs) { +#ifdef SIGHUP + ub_thread_sig_unblock(SIGHUP); +#endif + ub_thread_sig_unblock(SIGINT); +#ifdef SIGQUIT + ub_thread_sig_unblock(SIGQUIT); +#endif + ub_thread_sig_unblock(SIGTERM); +#ifndef LIBEVENT_SIGNAL_PROBLEM + worker->comsig = comm_signal_create(worker->base, + worker_sighandler, worker); + if(!worker->comsig +#ifdef SIGHUP + || !comm_signal_bind(worker->comsig, SIGHUP) +#endif +#ifdef SIGQUIT + || !comm_signal_bind(worker->comsig, SIGQUIT) +#endif + || !comm_signal_bind(worker->comsig, SIGTERM) + || !comm_signal_bind(worker->comsig, SIGINT)) { + log_err("could not create signal handlers"); + worker_delete(worker); + return 0; + } +#endif /* LIBEVENT_SIGNAL_PROBLEM */ + if(!daemon_remote_open_accept(worker->daemon->rc, + worker->daemon->rc_ports, worker)) { + worker_delete(worker); + return 0; + } +#ifdef UB_ON_WINDOWS + wsvc_setup_worker(worker); +#endif /* UB_ON_WINDOWS */ + } else { /* !do_sigs */ + worker->comsig = NULL; + } + worker->front = listen_create(worker->base, ports, + cfg->msg_buffer_size, (int)cfg->incoming_num_tcp, + worker->daemon->listen_sslctx, dtenv, worker_handle_request, + worker); + if(!worker->front) { + log_err("could not create listening sockets"); + worker_delete(worker); + return 0; + } + worker->back = outside_network_create(worker->base, + cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports, + cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, + 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->do_udp, worker->daemon->connect_sslctx, cfg->delay_close, + dtenv); + if(!worker->back) { + log_err("could not create outgoing sockets"); + worker_delete(worker); + return 0; + } + /* start listening to commands */ + if(!tube_setup_bg_listen(worker->cmd, worker->base, + &worker_handle_control_cmd, worker)) { + log_err("could not create control compt."); + worker_delete(worker); + return 0; + } + worker->stat_timer = comm_timer_create(worker->base, + worker_stat_timer_cb, worker); + if(!worker->stat_timer) { + log_err("could not create statistics timer"); + } + + /* we use the msg_buffer_size as a good estimate for what the + * user wants for memory usage sizes */ + worker->scratchpad = regional_create_custom(cfg->msg_buffer_size); + if(!worker->scratchpad) { + log_err("malloc failure"); + worker_delete(worker); + return 0; + } + + server_stats_init(&worker->stats, cfg); + alloc_init(&worker->alloc, &worker->daemon->superalloc, + worker->thread_num); + alloc_set_id_cleanup(&worker->alloc, &worker_alloc_cleanup, worker); + worker->env = *worker->daemon->env; + comm_base_timept(worker->base, &worker->env.now, &worker->env.now_tv); + if(worker->thread_num == 0) + log_set_time(worker->env.now); + worker->env.worker = worker; + worker->env.send_query = &worker_send_query; + worker->env.alloc = &worker->alloc; + worker->env.rnd = worker->rndstate; + worker->env.scratch = worker->scratchpad; + worker->env.mesh = mesh_create(&worker->daemon->mods, &worker->env); + worker->env.detach_subs = &mesh_detach_subs; + worker->env.attach_sub = &mesh_attach_sub; + worker->env.kill_sub = &mesh_state_delete; + worker->env.detect_cycle = &mesh_detect_cycle; + worker->env.scratch_buffer = sldns_buffer_new(cfg->msg_buffer_size); + if(!(worker->env.fwds = forwards_create()) || + !forwards_apply_cfg(worker->env.fwds, cfg)) { + log_err("Could not set forward zones"); + worker_delete(worker); + return 0; + } + if(!(worker->env.hints = hints_create()) || + !hints_apply_cfg(worker->env.hints, cfg)) { + log_err("Could not set root or stub hints"); + worker_delete(worker); + return 0; + } + /* one probe timer per process -- if we have 5011 anchors */ + if(autr_get_num_anchors(worker->env.anchors) > 0 +#ifndef THREADS_DISABLED + && worker->thread_num == 0 +#endif + ) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + worker->env.probe_timer = comm_timer_create(worker->base, + worker_probe_timer_cb, worker); + if(!worker->env.probe_timer) { + log_err("could not create 5011-probe timer"); + } else { + /* let timer fire, then it can reset itself */ + comm_timer_set(worker->env.probe_timer, &tv); + } + } + if(!worker->env.mesh || !worker->env.scratch_buffer) { + worker_delete(worker); + return 0; + } + worker_mem_report(worker, NULL); + /* if statistics enabled start timer */ + if(worker->env.cfg->stat_interval > 0) { + verbose(VERB_ALGO, "set statistics interval %d secs", + worker->env.cfg->stat_interval); + worker_restart_timer(worker); + } + return 1; +} + +void +worker_work(struct worker* worker) +{ + comm_base_dispatch(worker->base); +} + +void +worker_delete(struct worker* worker) +{ + if(!worker) + return; + if(worker->env.mesh && verbosity >= VERB_OPS) { + server_stats_log(&worker->stats, worker, worker->thread_num); + mesh_stats(worker->env.mesh, "mesh has"); + worker_mem_report(worker, NULL); + } + outside_network_quit_prepare(worker->back); + mesh_delete(worker->env.mesh); + sldns_buffer_free(worker->env.scratch_buffer); + forwards_delete(worker->env.fwds); + hints_delete(worker->env.hints); + listen_delete(worker->front); + outside_network_delete(worker->back); + comm_signal_delete(worker->comsig); + tube_delete(worker->cmd); + comm_timer_delete(worker->stat_timer); + comm_timer_delete(worker->env.probe_timer); + free(worker->ports); + if(worker->thread_num == 0) { + log_set_time(NULL); +#ifdef UB_ON_WINDOWS + wsvc_desetup_worker(worker); +#endif /* UB_ON_WINDOWS */ + } + comm_base_delete(worker->base); + ub_randfree(worker->rndstate); + alloc_clear(&worker->alloc); + regional_destroy(worker->scratchpad); + free(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) +{ + struct worker* worker = q->env->worker; + struct outbound_entry* e = (struct outbound_entry*)regional_alloc( + q->region, sizeof(*e)); + 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); + if(!e->qsent) { + return NULL; + } + return e; +} + +void +worker_alloc_cleanup(void* arg) +{ + struct worker* worker = (struct worker*)arg; + slabhash_clear(&worker->env.rrset_cache->table); + slabhash_clear(worker->env.msg_cache); +} + +void worker_stats_clear(struct worker* worker) +{ + server_stats_init(&worker->stats, worker->env.cfg); + mesh_stats_clear(worker->env.mesh); + worker->back->unwanted_replies = 0; + worker->back->num_tcp_outgoing = 0; +} + +void worker_start_accept(void* arg) +{ + struct worker* worker = (struct worker*)arg; + listen_start_accept(worker->front); + if(worker->thread_num == 0) + daemon_remote_start_accept(worker->daemon->rc); +} + +void worker_stop_accept(void* arg) +{ + struct worker* worker = (struct worker*)arg; + listen_stop_accept(worker->front); + if(worker->thread_num == 0) + daemon_remote_stop_accept(worker->daemon->rc); +} + +/* --- 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)) +{ + log_assert(0); + return 0; +} + +int libworker_handle_reply(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +void libworker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), + uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len), + int ATTR_UNUSED(error), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void libworker_fg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), + sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), + char* ATTR_UNUSED(why_bogus)) +{ + log_assert(0); +} + +void libworker_bg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), + sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), + char* ATTR_UNUSED(why_bogus)) +{ + log_assert(0); +} + +void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), + sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), + char* ATTR_UNUSED(why_bogus)) +{ + log_assert(0); +} + +int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + log_assert(0); + return 0; +} + +int order_lock_cmp(const void* ATTR_UNUSED(e1), const void* ATTR_UNUSED(e2)) +{ + log_assert(0); + return 0; +} + +int codeline_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + log_assert(0); + return 0; +} + diff --git a/external/unbound/daemon/worker.h b/external/unbound/daemon/worker.h new file mode 100644 index 000000000..ff69bc1ac --- /dev/null +++ b/external/unbound/daemon/worker.h @@ -0,0 +1,173 @@ +/* + * daemon/worker.h - worker that handles a pending list of requests. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file describes the worker structure that holds a list of + * pending requests and handles them. + */ + +#ifndef DAEMON_WORKER_H +#define DAEMON_WORKER_H + +#include "libunbound/worker.h" +#include "util/netevent.h" +#include "util/locks.h" +#include "util/alloc.h" +#include "util/data/msgreply.h" +#include "util/data/msgparse.h" +#include "daemon/stats.h" +#include "util/module.h" +#include "dnstap/dnstap.h" +struct listen_dnsport; +struct outside_network; +struct config_file; +struct daemon; +struct listen_port; +struct ub_randstate; +struct regional; +struct tube; +struct daemon_remote; + +/** worker commands */ +enum worker_commands { + /** make the worker quit */ + worker_cmd_quit, + /** obtain statistics */ + worker_cmd_stats, + /** obtain statistics without statsclear */ + worker_cmd_stats_noreset, + /** execute remote control command */ + worker_cmd_remote +}; + +/** + * Structure holding working information for unbound. + * Holds globally visible information. + */ +struct worker { + /** the thread number (in daemon array). First in struct for debug. */ + int thread_num; + /** global shared daemon structure */ + struct daemon* daemon; + /** thread id */ + ub_thread_t thr_id; + /** pipe, for commands for this worker */ + struct tube* cmd; + /** the event base this worker works with */ + struct comm_base* base; + /** the frontside listening interface where request events come in */ + struct listen_dnsport* front; + /** the backside outside network interface to the auth servers */ + struct outside_network* back; + /** ports to be used by this worker. */ + int* ports; + /** number of ports for this worker */ + int numports; + /** the signal handler */ + struct comm_signal* comsig; + /** commpoint to listen to commands. */ + struct comm_point* cmd_com; + /** timer for statistics */ + struct comm_timer* stat_timer; + + /** random() table for this worker. */ + struct ub_randstate* rndstate; + /** do we need to restart or quit (on signal) */ + int need_to_exit; + /** allocation cache for this thread */ + struct alloc_cache alloc; + /** per thread statistics */ + struct server_stats stats; + /** thread scratch regional */ + struct regional* scratchpad; + + /** module environment passed to modules, changed for this thread */ + struct module_env env; + +#ifdef USE_DNSTAP + /** dnstap environment, changed for this thread */ + struct dt_env dtenv; +#endif +}; + +/** + * Create the worker structure. Bare bones version, zeroed struct, + * with backpointers only. Use worker_init on it later. + * @param daemon: the daemon that this worker thread is part of. + * @param id: the thread number from 0.. numthreads-1. + * @param ports: the ports it is allowed to use, array. + * @param n: the number of ports. + * @return: the new worker or NULL on alloc failure. + */ +struct worker* worker_create(struct daemon* daemon, int id, int* ports, int n); + +/** + * Initialize worker. + * Allocates event base, listens to ports + * @param worker: worker to initialize, created with worker_create. + * @param cfg: configuration settings. + * @param ports: list of shared query ports. + * @param do_sigs: if true, worker installs signal handlers. + * @return: false on error. + */ +int worker_init(struct worker* worker, struct config_file *cfg, + struct listen_port* ports, int do_sigs); + +/** + * Make worker work. + */ +void worker_work(struct worker* worker); + +/** + * Delete worker. + */ +void worker_delete(struct worker* worker); + +/** + * Send a command to a worker. Uses blocking writes. + * @param worker: worker to send command to. + * @param cmd: command to send. + */ +void worker_send_cmd(struct worker* worker, enum worker_commands cmd); + +/** + * Init worker stats - includes server_stats_init, outside network and mesh. + * @param worker: the worker to init + */ +void worker_stats_clear(struct worker* worker); + +#endif /* DAEMON_WORKER_H */ |