diff options
Diffstat (limited to 'external/unbound/services')
-rw-r--r-- | external/unbound/services/cache/dns.c | 28 | ||||
-rw-r--r-- | external/unbound/services/cache/dns.h | 16 | ||||
-rw-r--r-- | external/unbound/services/cache/infra.c | 139 | ||||
-rw-r--r-- | external/unbound/services/cache/infra.h | 50 | ||||
-rw-r--r-- | external/unbound/services/cache/rrset.c | 10 | ||||
-rw-r--r-- | external/unbound/services/cache/rrset.h | 2 | ||||
-rw-r--r-- | external/unbound/services/listen_dnsport.c | 348 | ||||
-rw-r--r-- | external/unbound/services/listen_dnsport.h | 27 | ||||
-rw-r--r-- | external/unbound/services/localzone.c | 797 | ||||
-rw-r--r-- | external/unbound/services/localzone.h | 198 | ||||
-rw-r--r-- | external/unbound/services/mesh.c | 364 | ||||
-rw-r--r-- | external/unbound/services/mesh.h | 72 | ||||
-rw-r--r-- | external/unbound/services/modstack.c | 23 | ||||
-rw-r--r-- | external/unbound/services/outside_network.c | 210 | ||||
-rw-r--r-- | external/unbound/services/outside_network.h | 57 | ||||
-rw-r--r-- | external/unbound/services/view.c | 212 | ||||
-rw-r--r-- | external/unbound/services/view.h | 137 |
17 files changed, 2288 insertions, 402 deletions
diff --git a/external/unbound/services/cache/dns.c b/external/unbound/services/cache/dns.c index e14e636db..a8fde9f28 100644 --- a/external/unbound/services/cache/dns.c +++ b/external/unbound/services/cache/dns.c @@ -106,7 +106,7 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, - hashvalue_t hash, struct reply_info* rep, time_t leeway, int pside, + hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, struct reply_info* qrep, struct regional* region) { struct msgreply_entry* e; @@ -188,12 +188,13 @@ msg_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, { struct lruhash_entry* e; struct query_info k; - hashvalue_t h; + hashvalue_type h; k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; + k.local_alias = NULL; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, wr); @@ -361,6 +362,7 @@ dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype, msg->qinfo.qname_len = qnamelen; msg->qinfo.qtype = qtype; msg->qinfo.qclass = qclass; + msg->qinfo.local_alias = NULL; /* non-packed reply_info, because it needs to grow the array */ msg->rep = (struct reply_info*)regional_alloc_zero(region, sizeof(struct reply_info)-sizeof(struct rrset_ref)); @@ -477,8 +479,7 @@ gen_dns_msg(struct regional* region, struct query_info* q, size_t num) return msg; } -/** generate dns_msg from cached message */ -static struct dns_msg* +struct dns_msg* tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, struct regional* region, time_t now, struct regional* scratch) { @@ -523,8 +524,11 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, return NULL; } } - rrset_array_unlock_touch(env->rrset_cache, scratch, r->ref, + if(env) + rrset_array_unlock_touch(env->rrset_cache, scratch, r->ref, r->rrset_count); + else + rrset_array_unlock(r->ref, r->rrset_count); return msg; } @@ -707,7 +711,7 @@ dns_cache_lookup(struct module_env* env, { struct lruhash_entry* e; struct query_info k; - hashvalue_t h; + hashvalue_type h; time_t now = *env->now; struct ub_packed_rrset_key* rrset; @@ -716,6 +720,7 @@ dns_cache_lookup(struct module_env* env, k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; + k.local_alias = NULL; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(e) { @@ -795,6 +800,12 @@ dns_cache_lookup(struct module_env* env, dname_remove_label(&k.qname, &k.qname_len); h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); + if(!e && k.qtype != LDNS_RR_TYPE_A && + env->cfg->qname_minimisation) { + k.qtype = LDNS_RR_TYPE_A; + h = query_info_hash(&k, flags); + e = slabhash_lookup(env->msg_cache, h, &k, 0); + } if(e) { struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg; @@ -810,7 +821,8 @@ dns_cache_lookup(struct module_env* env, } lock_rw_unlock(&e->lock); } - } + k.qtype = qtype; + } /* fill common RR types for ANY response to avoid requery */ if(qtype == LDNS_RR_TYPE_ANY) { @@ -855,7 +867,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, } else { /* store msg, and rrsets */ struct query_info qinf; - hashvalue_t h; + hashvalue_type h; qinf = *msgqinf; qinf.qname = memdup(msgqinf->qname, msgqinf->qname_len); diff --git a/external/unbound/services/cache/dns.h b/external/unbound/services/cache/dns.h index 69796c2eb..0dfb68874 100644 --- a/external/unbound/services/cache/dns.h +++ b/external/unbound/services/cache/dns.h @@ -106,7 +106,7 @@ int dns_cache_store(struct module_env* env, struct query_info* qinf, * @param region: to allocate into for qmsg. */ void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, - hashvalue_t hash, struct reply_info* rep, time_t leeway, int pside, + hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, struct reply_info* qrep, struct regional* region); /** @@ -126,6 +126,20 @@ struct delegpt* dns_cache_find_delegation(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region, struct dns_msg** msg, time_t timenow); +/** + * generate dns_msg from cached message + * @param env: module environment with the DNS cache. NULL if the LRU from cache + * does not need to be touched. + * @param q: query info, contains qname that will make up the dns message. + * @param r: reply info that, together with qname, will make up the dns message. + * @param region: where to allocate dns message. + * @param now: the time now, for check if TTL on cache entry is ok. + * @param scratch: where to allocate temporary data. + * */ +struct dns_msg* tomsg(struct module_env* env, struct query_info* q, + struct reply_info* r, struct regional* region, time_t now, + struct regional* scratch); + /** * Find cached message * @param env: module environment with the DNS cache. diff --git a/external/unbound/services/cache/infra.c b/external/unbound/services/cache/infra.c index c0049d8b6..314c85ef5 100644 --- a/external/unbound/services/cache/infra.c +++ b/external/unbound/services/cache/infra.c @@ -61,6 +61,10 @@ /** ratelimit value for delegation point */ int infra_dp_ratelimit = 0; +/** ratelimit value for client ip addresses, + * in queries per second. */ +int infra_ip_ratelimit = 0; + size_t infra_sizefunc(void* k, void* ATTR_UNUSED(d)) { @@ -244,11 +248,19 @@ infra_create(struct config_file* cfg) } name_tree_init_parents(&infra->domain_limits); } + infra_ip_ratelimit = cfg->ip_ratelimit; + infra->client_ip_rates = slabhash_create(cfg->ratelimit_slabs, + INFRA_HOST_STARTSIZE, cfg->ip_ratelimit_size, &ip_rate_sizefunc, + &ip_rate_compfunc, &ip_rate_delkeyfunc, &ip_rate_deldatafunc, NULL); + if(!infra->client_ip_rates) { + infra_delete(infra); + return NULL; + } return infra; } /** delete domain_limit entries */ -static void domain_limit_free(rbnode_t* n, void* ATTR_UNUSED(arg)) +static void domain_limit_free(rbnode_type* n, void* ATTR_UNUSED(arg)) { if(n) { free(((struct domain_limit_data*)n)->node.name); @@ -264,6 +276,7 @@ infra_delete(struct infra_cache* infra) slabhash_delete(infra->hosts); slabhash_delete(infra->domain_rates); traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); + slabhash_delete(infra->client_ip_rates); free(infra); } @@ -284,31 +297,38 @@ infra_adjust(struct infra_cache* infra, struct config_file* cfg) return infra; } -/** calculate the hash value for a host key */ -static hashvalue_t -hash_addr(struct sockaddr_storage* addr, socklen_t addrlen) +/** calculate the hash value for a host key + * set use_port to a non-0 number to use the port in + * the hash calculation; 0 to ignore the port.*/ +static hashvalue_type +hash_addr(struct sockaddr_storage* addr, socklen_t addrlen, + int use_port) { - hashvalue_t h = 0xab; + hashvalue_type h = 0xab; /* select the pieces to hash, some OS have changing data inside */ if(addr_is_ip6(addr, addrlen)) { struct sockaddr_in6* in6 = (struct sockaddr_in6*)addr; h = hashlittle(&in6->sin6_family, sizeof(in6->sin6_family), h); - h = hashlittle(&in6->sin6_port, sizeof(in6->sin6_port), h); + if(use_port){ + h = hashlittle(&in6->sin6_port, sizeof(in6->sin6_port), h); + } h = hashlittle(&in6->sin6_addr, INET6_SIZE, h); } else { struct sockaddr_in* in = (struct sockaddr_in*)addr; h = hashlittle(&in->sin_family, sizeof(in->sin_family), h); - h = hashlittle(&in->sin_port, sizeof(in->sin_port), h); + if(use_port){ + h = hashlittle(&in->sin_port, sizeof(in->sin_port), h); + } h = hashlittle(&in->sin_addr, INET_SIZE, h); } return h; } /** calculate infra hash for a key */ -static hashvalue_t +static hashvalue_type hash_infra(struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name) { - return dname_query_hash(name, hash_addr(addr, addrlen)); + return dname_query_hash(name, hash_addr(addr, addrlen, 1)); } /** lookup version that does not check host ttl (you check it) */ @@ -726,12 +746,36 @@ int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, return infra_dp_ratelimit; } +size_t ip_rate_sizefunc(void* k, void* ATTR_UNUSED(d)) +{ + struct ip_rate_key* key = (struct ip_rate_key*)k; + return sizeof(*key) + sizeof(struct ip_rate_data) + + lock_get_mem(&key->entry.lock); +} + +int ip_rate_compfunc(void* key1, void* key2) +{ + struct ip_rate_key* k1 = (struct ip_rate_key*)key1; + struct ip_rate_key* k2 = (struct ip_rate_key*)key2; + return sockaddr_cmp_addr(&k1->addr, k1->addrlen, + &k2->addr, k2->addrlen); +} + +void ip_rate_delkeyfunc(void* k, void* ATTR_UNUSED(arg)) +{ + struct ip_rate_key* key = (struct ip_rate_key*)k; + if(!key) + return; + lock_rw_destroy(&key->entry.lock); + free(key); +} + /** find data item in array, for write access, caller unlocks */ static struct lruhash_entry* infra_find_ratedata(struct infra_cache* infra, uint8_t* name, size_t namelen, int wr) { struct rate_key key; - hashvalue_t h = dname_query_hash(name, 0xab); + hashvalue_type h = dname_query_hash(name, 0xab); memset(&key, 0, sizeof(key)); key.name = name; key.namelen = namelen; @@ -739,11 +783,25 @@ static struct lruhash_entry* infra_find_ratedata(struct infra_cache* infra, return slabhash_lookup(infra->domain_rates, h, &key, wr); } +/** find data item in array for ip addresses */ +struct lruhash_entry* infra_find_ip_ratedata(struct infra_cache* infra, + struct comm_reply* repinfo, int wr) +{ + struct ip_rate_key key; + hashvalue_type h = hash_addr(&(repinfo->addr), + repinfo->addrlen, 0); + memset(&key, 0, sizeof(key)); + key.addr = repinfo->addr; + key.addrlen = repinfo->addrlen; + key.entry.hash = h; + return slabhash_lookup(infra->client_ip_rates, h, &key, wr); +} + /** create rate data item for name, number 1 in now */ static void infra_create_ratedata(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow) { - hashvalue_t h = dname_query_hash(name, 0xab); + hashvalue_type h = dname_query_hash(name, 0xab); struct rate_key* k = (struct rate_key*)calloc(1, sizeof(*k)); struct rate_data* d = (struct rate_data*)calloc(1, sizeof(*d)); if(!k || !d) { @@ -767,6 +825,30 @@ static void infra_create_ratedata(struct infra_cache* infra, slabhash_insert(infra->domain_rates, h, &k->entry, d, NULL); } +/** create rate data item for ip address */ +static void infra_ip_create_ratedata(struct infra_cache* infra, + struct comm_reply* repinfo, time_t timenow) +{ + hashvalue_type h = hash_addr(&(repinfo->addr), + repinfo->addrlen, 0); + struct ip_rate_key* k = (struct ip_rate_key*)calloc(1, sizeof(*k)); + struct ip_rate_data* d = (struct ip_rate_data*)calloc(1, sizeof(*d)); + if(!k || !d) { + free(k); + free(d); + return; /* alloc failure */ + } + k->addr = repinfo->addr; + k->addrlen = repinfo->addrlen; + lock_rw_init(&k->entry.lock); + k->entry.hash = h; + k->entry.key = k; + k->entry.data = d; + d->qps[0] = 1; + d->timestamp[0] = timenow; + slabhash_insert(infra->client_ip_rates, h, &k->entry, d, NULL); +} + /** find the second and return its rate counter, if none, remove oldest */ static int* infra_rate_find_second(void* data, time_t t) { @@ -875,6 +957,41 @@ infra_get_mem(struct infra_cache* infra) { size_t s = sizeof(*infra) + slabhash_get_mem(infra->hosts); if(infra->domain_rates) s += slabhash_get_mem(infra->domain_rates); + if(infra->client_ip_rates) s += slabhash_get_mem(infra->client_ip_rates); /* ignore domain_limits because walk through tree is big */ return s; } + +int infra_ip_ratelimit_inc(struct infra_cache* infra, + struct comm_reply* repinfo, time_t timenow) +{ + int max; + struct lruhash_entry* entry; + + /* not enabled */ + if(!infra_ip_ratelimit) { + return 1; + } + /* find or insert ratedata */ + entry = infra_find_ip_ratedata(infra, repinfo, 1); + if(entry) { + int premax = infra_rate_max(entry->data, timenow); + int* cur = infra_rate_find_second(entry->data, timenow); + (*cur)++; + max = infra_rate_max(entry->data, timenow); + lock_rw_unlock(&entry->lock); + + if(premax < infra_ip_ratelimit && max >= infra_ip_ratelimit) { + char client_ip[128]; + addr_to_str((struct sockaddr_storage *)&repinfo->addr, + repinfo->addrlen, client_ip, sizeof(client_ip)); + verbose(VERB_OPS, "ratelimit exceeded %s %d", client_ip, + infra_ip_ratelimit); + } + return (max <= infra_ip_ratelimit); + } + + /* create */ + infra_ip_create_ratedata(infra, repinfo, timenow); + return 1; +} diff --git a/external/unbound/services/cache/infra.h b/external/unbound/services/cache/infra.h index fc7abb7c4..6f9471a39 100644 --- a/external/unbound/services/cache/infra.h +++ b/external/unbound/services/cache/infra.h @@ -36,7 +36,10 @@ /** * \file * - * This file contains the infrastructure cache. + * This file contains the infrastructure cache, as well as rate limiting. + * Note that there are two sorts of rate-limiting here: + * - Pre-cache, per-query rate limiting (query ratelimits) + * - Post-cache, per-domain name rate limiting (infra-ratelimits) */ #ifndef SERVICES_CACHE_INFRA_H @@ -44,6 +47,8 @@ #include "util/storage/lruhash.h" #include "util/storage/dnstree.h" #include "util/rtt.h" +#include "util/netevent.h" +#include "util/data/msgreply.h" struct slabhash; struct config_file; @@ -112,7 +117,9 @@ struct infra_cache { /** hash table with query rates per name: rate_key, rate_data */ struct slabhash* domain_rates; /** ratelimit settings for domains, struct domain_limit_data */ - rbtree_t domain_limits; + rbtree_type domain_limits; + /** hash table with query rates per client ip: ip_rate_key, ip_rate_data */ + struct slabhash* client_ip_rates; }; /** ratelimit, unless overridden by domain_limits, 0 is off */ @@ -142,6 +149,21 @@ struct rate_key { size_t namelen; }; +/** ip ratelimit, 0 is off */ +extern int infra_ip_ratelimit; + +/** + * key for ip_ratelimit lookups, a source IP. + */ +struct ip_rate_key { + /** lruhash key entry */ + struct lruhash_entry entry; + /** client ip information */ + struct sockaddr_storage addr; + /** length of address */ + socklen_t addrlen; +}; + /** number of seconds to track qps rate */ #define RATE_WINDOW 2 @@ -160,6 +182,8 @@ struct rate_data { time_t timestamp[RATE_WINDOW]; }; +#define ip_rate_data rate_data + /** infra host cache default hash lookup size */ #define INFRA_HOST_STARTSIZE 32 /** bytes per zonename reserved in the hostcache, dnamelen(zonename.com.) */ @@ -381,6 +405,16 @@ int infra_rate_max(void* data, time_t now); int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, size_t namelen); +/** Update query ratelimit hash and decide + * whether or not a query should be dropped. + * @param infra: infra cache + * @param repinfo: information about client + * @param timenow: what time it is now. + * @return 1 if it could be incremented. 0 if the increment overshot the + * ratelimit and the query should be dropped. */ +int infra_ip_ratelimit_inc(struct infra_cache* infra, + struct comm_reply* repinfo, time_t timenow); + /** * Get memory used by the infra cache. * @param infra: infrastructure cache. @@ -413,4 +447,16 @@ void rate_delkeyfunc(void* k, void* arg); /** delete data */ void rate_deldatafunc(void* d, void* arg); +/* calculate size for the client ip hashtable */ +size_t ip_rate_sizefunc(void* k, void* d); + +/* compare two addresses */ +int ip_rate_compfunc(void* key1, void* key2); + +/* delete key, and destroy the lock */ +void ip_rate_delkeyfunc(void* d, void* arg); + +/* delete data */ +#define ip_rate_deldatafunc rate_deldatafunc + #endif /* SERVICES_CACHE_INFRA_H */ diff --git a/external/unbound/services/cache/rrset.c b/external/unbound/services/cache/rrset.c index 2f6a1b506..7e5732b76 100644 --- a/external/unbound/services/cache/rrset.c +++ b/external/unbound/services/cache/rrset.c @@ -91,7 +91,7 @@ struct rrset_cache* rrset_cache_adjust(struct rrset_cache *r, void rrset_cache_touch(struct rrset_cache* r, struct ub_packed_rrset_key* key, - hashvalue_t hash, rrset_id_t id) + hashvalue_type hash, rrset_id_type id) { struct lruhash* table = slabhash_gettable(&r->table, hash); /* @@ -186,7 +186,7 @@ rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref, { struct lruhash_entry* e; struct ub_packed_rrset_key* k = ref->key; - hashvalue_t h = k->entry.hash; + hashvalue_type h = k->entry.hash; uint16_t rrset_type = ntohs(k->rk.type); int equal = 0; log_assert(ref->id != 0 && k->id != 0); @@ -303,10 +303,10 @@ void rrset_array_unlock_touch(struct rrset_cache* r, struct regional* scratch, struct rrset_ref* ref, size_t count) { - hashvalue_t* h; + hashvalue_type* h; size_t i; - if(count > RR_COUNT_MAX || !(h = (hashvalue_t*)regional_alloc(scratch, - sizeof(hashvalue_t)*count))) { + if(count > RR_COUNT_MAX || !(h = (hashvalue_type*)regional_alloc( + scratch, sizeof(hashvalue_type)*count))) { log_warn("rrset LRU: memory allocation failed"); h = NULL; } else /* store hash values */ diff --git a/external/unbound/services/cache/rrset.h b/external/unbound/services/cache/rrset.h index 98e44a4e5..d5439ef08 100644 --- a/external/unbound/services/cache/rrset.h +++ b/external/unbound/services/cache/rrset.h @@ -102,7 +102,7 @@ struct rrset_cache* rrset_cache_adjust(struct rrset_cache* r, * @param id: used to check that the item is unchanged and not deleted. */ void rrset_cache_touch(struct rrset_cache* r, struct ub_packed_rrset_key* key, - hashvalue_t hash, rrset_id_t id); + hashvalue_type hash, rrset_id_type id); /** * Update an rrset in the rrset cache. Stores the information for later use. diff --git a/external/unbound/services/listen_dnsport.c b/external/unbound/services/listen_dnsport.c index 276c0fb32..37ee9a6b9 100644 --- a/external/unbound/services/listen_dnsport.c +++ b/external/unbound/services/listen_dnsport.c @@ -43,6 +43,9 @@ # include <sys/types.h> #endif #include <sys/time.h> +#ifdef USE_TCP_FASTOPEN +#include <netinet/tcp.h> +#endif #include "services/listen_dnsport.h" #include "services/outside_network.h" #include "util/netevent.h" @@ -60,6 +63,10 @@ #include <sys/un.h> #endif +#ifdef HAVE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif + /** number of queued TCP connections for listen() */ #define TCP_BACKLOG 256 @@ -93,13 +100,74 @@ verbose_print_addr(struct addrinfo *addr) } } +#ifdef HAVE_SYSTEMD +static int +systemd_get_activated(int family, int socktype, int listen, + struct sockaddr *addr, socklen_t addrlen, + const char *path) +{ + int i = 0; + int r = 0; + int s = -1; + const char* listen_pid, *listen_fds; + + /* We should use "listen" option only for stream protocols. For UDP it should be -1 */ + + if((r = sd_booted()) < 1) { + if(r == 0) + log_warn("systemd is not running"); + else + log_err("systemd sd_booted(): %s", strerror(-r)); + return -1; + } + + listen_pid = getenv("LISTEN_PID"); + listen_fds = getenv("LISTEN_FDS"); + + if (!listen_pid) { + log_warn("Systemd mandatory ENV variable is not defined: LISTEN_PID"); + return -1; + } + + if (!listen_fds) { + log_warn("Systemd mandatory ENV variable is not defined: LISTEN_FDS"); + return -1; + } + + if((r = sd_listen_fds(0)) < 1) { + if(r == 0) + log_warn("systemd: did not return socket, check unit configuration"); + else + log_err("systemd sd_listen_fds(): %s", strerror(-r)); + return -1; + } + + for(i = 0; i < r; i++) { + if(sd_is_socket(SD_LISTEN_FDS_START + i, family, socktype, listen)) { + s = SD_LISTEN_FDS_START + i; + break; + } + } + if (s == -1) { + if (addr) + log_err_addr("systemd sd_listen_fds()", + "no such socket", + (struct sockaddr_storage *)addr, addrlen); + else + log_err("systemd sd_listen_fds(): %s", path); + } + return s; +} +#endif + int create_udp_sock(int family, int socktype, struct sockaddr* addr, socklen_t addrlen, int v6only, int* inuse, int* noproto, - int rcv, int snd, int listen, int* reuseport, int transparent) + int rcv, int snd, int listen, int* reuseport, int transparent, + int freebind, int use_systemd) { int s; -#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_USE_MIN_MTU) || defined(IP_TRANSPARENT) +#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_USE_MIN_MTU) || defined(IP_TRANSPARENT) || defined(IP_BINDANY) || defined(IP_FREEBIND) int on=1; #endif #ifdef IPV6_MTU @@ -114,9 +182,22 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, #ifndef IPV6_V6ONLY (void)v6only; #endif -#ifndef IP_TRANSPARENT +#if !defined(IP_TRANSPARENT) && !defined(IP_BINDANY) (void)transparent; #endif +#if !defined(IP_FREEBIND) + (void)freebind; +#endif +#ifdef HAVE_SYSTEMD + int got_fd_from_systemd = 0; + + if (!use_systemd + || (use_systemd + && (s = systemd_get_activated(family, socktype, -1, addr, + addrlen, NULL)) == -1)) { +#else + (void)use_systemd; +#endif if((s = socket(family, socktype, 0)) == -1) { *inuse = 0; #ifndef USE_WINSOCK @@ -137,6 +218,11 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, *noproto = 0; return -1; } +#ifdef HAVE_SYSTEMD + } else { + got_fd_from_systemd = 1; + } +#endif if(listen) { #ifdef SO_REUSEADDR if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, @@ -187,8 +273,24 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, log_warn("setsockopt(.. IP_TRANSPARENT ..) failed: %s", strerror(errno)); } -#endif /* IP_TRANSPARENT */ +#elif defined(IP_BINDANY) + if (transparent && + setsockopt(s, (family==AF_INET6? IPPROTO_IPV6:IPPROTO_IP), + (family == AF_INET6? IPV6_BINDANY:IP_BINDANY), + (void*)&on, (socklen_t)sizeof(on)) < 0) { + log_warn("setsockopt(.. IP%s_BINDANY ..) failed: %s", + (family==AF_INET6?"V6":""), strerror(errno)); + } +#endif /* IP_TRANSPARENT || IP_BINDANY */ + } +#ifdef IP_FREEBIND + if(freebind && + setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, + (socklen_t)sizeof(on)) < 0) { + log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", + strerror(errno)); } +#endif /* IP_FREEBIND */ if(rcv) { #ifdef SO_RCVBUF int got; @@ -442,7 +544,11 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, } # endif /* IPv4 MTU */ } - if(bind(s, (struct sockaddr*)addr, addrlen) != 0) { + if( +#ifdef HAVE_SYSTEMD + !got_fd_from_systemd && +#endif + bind(s, (struct sockaddr*)addr, addrlen) != 0) { *noproto = 0; *inuse = 0; #ifndef USE_WINSOCK @@ -465,7 +571,7 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, (struct sockaddr_storage*)addr, addrlen); } closesocket(s); -#endif +#endif /* USE_WINSOCK */ return -1; } if(!fd_set_nonblock(s)) { @@ -483,17 +589,35 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, int create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, - int* reuseport, int transparent) + int* reuseport, int transparent, int mss, int freebind, int use_systemd) { int s; -#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) +#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) || defined(IP_BINDANY) || defined(IP_FREEBIND) int on = 1; #endif -#ifndef IP_TRANSPARENT +#ifdef HAVE_SYSTEMD + int got_fd_from_systemd = 0; +#endif +#ifdef USE_TCP_FASTOPEN + int qlen; +#endif +#if !defined(IP_TRANSPARENT) && !defined(IP_BINDANY) (void)transparent; #endif +#if !defined(IP_FREEBIND) + (void)freebind; +#endif verbose_print_addr(addr); *noproto = 0; +#ifdef HAVE_SYSTEMD + if (!use_systemd || + (use_systemd + && (s = systemd_get_activated(addr->ai_family, addr->ai_socktype, 1, + addr->ai_addr, addr->ai_addrlen, + NULL)) == -1)) { +#else + (void)use_systemd; +#endif if((s = socket(addr->ai_family, addr->ai_socktype, 0)) == -1) { #ifndef USE_WINSOCK if(errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { @@ -512,6 +636,30 @@ create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, #endif return -1; } + if (mss > 0) { +#if defined(IPPROTO_TCP) && defined(TCP_MAXSEG) + if(setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, (void*)&mss, + (socklen_t)sizeof(mss)) < 0) { + #ifndef USE_WINSOCK + log_err(" setsockopt(.. TCP_MAXSEG ..) failed: %s", + strerror(errno)); + #else + log_err(" setsockopt(.. TCP_MAXSEG ..) failed: %s", + wsa_strerror(WSAGetLastError())); + #endif + } else { + verbose(VERB_ALGO, + " tcp socket mss set to %d", mss); + } +#else + log_warn(" setsockopt(TCP_MAXSEG) unsupported"); +#endif /* defined(IPPROTO_TCP) && defined(TCP_MAXSEG) */ + } +#ifdef HAVE_SYSTEMD + } else { + got_fd_from_systemd = 1; + } +#endif #ifdef SO_REUSEADDR if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, (socklen_t)sizeof(on)) < 0) { @@ -527,6 +675,13 @@ create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, return -1; } #endif /* SO_REUSEADDR */ +#ifdef IP_FREEBIND + if (freebind && setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, + (socklen_t)sizeof(on)) < 0) { + log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", + strerror(errno)); + } +#endif /* IP_FREEBIND */ #ifdef SO_REUSEPORT /* try to set SO_REUSEPORT so that incoming * connections are distributed evenly among the receiving threads. @@ -573,8 +728,20 @@ create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, log_warn("setsockopt(.. IP_TRANSPARENT ..) failed: %s", strerror(errno)); } -#endif /* IP_TRANSPARENT */ - if(bind(s, addr->ai_addr, addr->ai_addrlen) != 0) { +#elif defined(IP_BINDANY) + if (transparent && + setsockopt(s, (addr->ai_family==AF_INET6? IPPROTO_IPV6:IPPROTO_IP), + (addr->ai_family == AF_INET6? IPV6_BINDANY:IP_BINDANY), + (void*)&on, (socklen_t)sizeof(on)) < 0) { + log_warn("setsockopt(.. IP%s_BINDANY ..) failed: %s", + (addr->ai_family==AF_INET6?"V6":""), strerror(errno)); + } +#endif /* IP_TRANSPARENT || IP_BINDANY */ + if( +#ifdef HAVE_SYSTEMD + !got_fd_from_systemd && +#endif + bind(s, addr->ai_addr, addr->ai_addrlen) != 0) { #ifndef USE_WINSOCK /* detect freebsd jail with no ipv6 permission */ if(addr->ai_family==AF_INET6 && errno==EINVAL) @@ -612,20 +779,46 @@ create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, #endif return -1; } +#ifdef USE_TCP_FASTOPEN + /* qlen specifies how many outstanding TFO requests to allow. Limit is a defense + against IP spoofing attacks as suggested in RFC7413 */ +#ifdef __APPLE__ + /* OS X implementation only supports qlen of 1 via this call. Actual + value is configured by the net.inet.tcp.fastopen_backlog kernel parm. */ + qlen = 1; +#else + /* 5 is recommended on linux */ + qlen = 5; +#endif + if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen, + sizeof(qlen))) == -1 ) { + log_err("Setting TCP Fast Open as server failed: %s", strerror(errno)); + } +#endif return s; } int -create_local_accept_sock(const char *path, int* noproto) +create_local_accept_sock(const char *path, int* noproto, int use_systemd) { +#ifdef HAVE_SYSTEMD + int ret; + + if (use_systemd && (ret = systemd_get_activated(AF_LOCAL, SOCK_STREAM, 1, NULL, 0, path)) != -1) + return ret; + else { +#endif #ifdef HAVE_SYS_UN_H int s; struct sockaddr_un usock; +#ifndef HAVE_SYSTEMD + (void)use_systemd; +#endif verbose(VERB_ALGO, "creating unix socket %s", path); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN /* this member exists on BSDs, not Linux */ - usock.sun_len = (socklen_t)sizeof(usock); + usock.sun_len = (unsigned)sizeof(usock); #endif usock.sun_family = AF_LOCAL; /* length is 92-108, 104 on FreeBSD */ @@ -641,29 +834,42 @@ create_local_accept_sock(const char *path, int* noproto) /* The socket already exists and cannot be removed */ log_err("Cannot remove old local socket %s (%s)", path, strerror(errno)); - return -1; + goto err; } if (bind(s, (struct sockaddr *)&usock, (socklen_t)sizeof(struct sockaddr_un)) == -1) { log_err("Cannot bind local socket %s (%s)", path, strerror(errno)); - return -1; + goto err; } if (!fd_set_nonblock(s)) { log_err("Cannot set non-blocking mode"); - return -1; + goto err; } if (listen(s, TCP_BACKLOG) == -1) { log_err("can't listen: %s", strerror(errno)); - return -1; + goto err; } (void)noproto; /*unused*/ return s; + +err: +#ifndef USE_WINSOCK + close(s); +#else + closesocket(s); +#endif + return -1; + +#ifdef HAVE_SYSTEMD + } +#endif #else + (void)use_systemd; (void)path; log_err("Local sockets are not supported"); *noproto = 1; @@ -678,7 +884,7 @@ create_local_accept_sock(const char *path, int* noproto) static int make_sock(int stype, const char* ifname, const char* port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, - int* reuseport, int transparent) + int* reuseport, int transparent, int tcp_mss, int freebind, int use_systemd) { struct addrinfo *res = NULL; int r, s, inuse, noproto; @@ -706,7 +912,7 @@ make_sock(int stype, const char* ifname, const char* port, s = create_udp_sock(res->ai_family, res->ai_socktype, (struct sockaddr*)res->ai_addr, res->ai_addrlen, v6only, &inuse, &noproto, (int)rcv, (int)snd, 1, - reuseport, transparent); + reuseport, transparent, freebind, use_systemd); if(s == -1 && inuse) { log_err("bind: address already in use"); } else if(s == -1 && noproto && hints->ai_family == AF_INET6){ @@ -714,7 +920,7 @@ make_sock(int stype, const char* ifname, const char* port, } } else { s = create_tcp_accept_sock(res, v6only, &noproto, reuseport, - transparent); + transparent, tcp_mss, freebind, use_systemd); if(s == -1 && noproto && hints->ai_family == AF_INET6){ *noip6 = 1; } @@ -727,7 +933,7 @@ make_sock(int stype, const char* ifname, const char* port, static int make_sock_port(int stype, const char* ifname, const char* port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, - int* reuseport, int transparent) + int* reuseport, int transparent, int tcp_mss, int freebind, int use_systemd) { char* s = strchr(ifname, '@'); if(s) { @@ -749,10 +955,10 @@ make_sock_port(int stype, const char* ifname, const char* port, (void)strlcpy(p, s+1, sizeof(p)); p[strlen(s+1)]=0; return make_sock(stype, newif, p, hints, v6only, noip6, - rcv, snd, reuseport, transparent); + rcv, snd, reuseport, transparent, tcp_mss, freebind, use_systemd); } return make_sock(stype, ifname, port, hints, v6only, noip6, rcv, snd, - reuseport, transparent); + reuseport, transparent, tcp_mss, freebind, use_systemd); } /** @@ -802,7 +1008,7 @@ set_recvpktinfo(int s, int family) } # else log_err("no IPV6_RECVPKTINFO and no IPV6_PKTINFO option, please " - "disable interface-automatic in config"); + "disable interface-automatic or do-ip6 in config"); return 0; # endif /* defined IPV6_RECVPKTINFO */ @@ -823,7 +1029,7 @@ set_recvpktinfo(int s, int family) } # else log_err("no IP_SENDSRCADDR or IP_PKTINFO option, please disable " - "interface-automatic in config"); + "interface-automatic or do-ip4 in config"); return 0; # endif /* IP_PKTINFO */ @@ -847,19 +1053,34 @@ set_recvpktinfo(int s, int family) * @param reuseport: try to set SO_REUSEPORT if nonNULL and true. * set to false on exit if reuseport failed due to no kernel support. * @param transparent: set IP_TRANSPARENT socket option. + * @param tcp_mss: maximum segment size of tcp socket. default if zero. + * @param freebind: set IP_FREEBIND socket option. + * @param use_systemd: if true, fetch sockets from systemd. + * @param dnscrypt_port: dnscrypt service port number * @return: returns false on error. */ static int ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, struct addrinfo *hints, const char* port, struct listen_port** list, - size_t rcv, size_t snd, int ssl_port, int* reuseport, int transparent) + size_t rcv, size_t snd, int ssl_port, int* reuseport, int transparent, + int tcp_mss, int freebind, int use_systemd, int dnscrypt_port) { int s, noip6=0; +#ifdef USE_DNSCRYPT + int is_dnscrypt = ((strchr(ifname, '@') && + atoi(strchr(ifname, '@')+1) == dnscrypt_port) || + (!strchr(ifname, '@') && atoi(port) == dnscrypt_port)); +#else + int is_dnscrypt = 0; + (void)dnscrypt_port; +#endif + if(!do_udp && !do_tcp) return 0; if(do_auto) { if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1, - &noip6, rcv, snd, reuseport, transparent)) == -1) { + &noip6, rcv, snd, reuseport, transparent, + tcp_mss, freebind, use_systemd)) == -1) { if(noip6) { log_warn("IPv6 protocol not available"); return 1; @@ -875,7 +1096,8 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, #endif return 0; } - if(!port_insert(list, s, listen_type_udpancil)) { + if(!port_insert(list, s, + is_dnscrypt?listen_type_udpancil_dnscrypt:listen_type_udpancil)) { #ifndef USE_WINSOCK close(s); #else @@ -886,14 +1108,16 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, } else if(do_udp) { /* regular udp socket */ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1, - &noip6, rcv, snd, reuseport, transparent)) == -1) { + &noip6, rcv, snd, reuseport, transparent, + tcp_mss, freebind, use_systemd)) == -1) { if(noip6) { log_warn("IPv6 protocol not available"); return 1; } return 0; } - if(!port_insert(list, s, listen_type_udp)) { + if(!port_insert(list, s, + is_dnscrypt?listen_type_udp_dnscrypt:listen_type_udp)) { #ifndef USE_WINSOCK close(s); #else @@ -907,7 +1131,8 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, atoi(strchr(ifname, '@')+1) == ssl_port) || (!strchr(ifname, '@') && atoi(port) == ssl_port)); if((s = make_sock_port(SOCK_STREAM, ifname, port, hints, 1, - &noip6, 0, 0, reuseport, transparent)) == -1) { + &noip6, 0, 0, reuseport, transparent, tcp_mss, + freebind, use_systemd)) == -1) { if(noip6) { /*log_warn("IPv6 protocol not available");*/ return 1; @@ -917,7 +1142,7 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, if(is_ssl) verbose(VERB_ALGO, "setup TCP for SSL service"); if(!port_insert(list, s, is_ssl?listen_type_ssl: - listen_type_tcp)) { + (is_dnscrypt?listen_type_tcp_dnscrypt:listen_type_tcp))) { #ifndef USE_WINSOCK close(s); #else @@ -951,7 +1176,7 @@ listen_cp_insert(struct comm_point* c, struct listen_dnsport* front) struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, void* sslctx, - struct dt_env* dtenv, comm_point_callback_t* cb, void *cb_arg) + struct dt_env* dtenv, comm_point_callback_type* cb, void *cb_arg) { struct listen_dnsport* front = (struct listen_dnsport*) malloc(sizeof(struct listen_dnsport)); @@ -959,6 +1184,9 @@ listen_create(struct comm_base* base, struct listen_port* ports, return NULL; front->cps = NULL; front->udp_buff = sldns_buffer_new(bufsize); +#ifdef USE_DNSCRYPT + front->dnscrypt_udp_buff = NULL; +#endif if(!front->udp_buff) { free(front); return NULL; @@ -967,17 +1195,20 @@ listen_create(struct comm_base* base, struct listen_port* ports, /* create comm points as needed */ while(ports) { struct comm_point* cp = NULL; - if(ports->ftype == listen_type_udp) + if(ports->ftype == listen_type_udp || + ports->ftype == listen_type_udp_dnscrypt) cp = comm_point_create_udp(base, ports->fd, front->udp_buff, cb, cb_arg); - else if(ports->ftype == listen_type_tcp) + else if(ports->ftype == listen_type_tcp || + ports->ftype == listen_type_tcp_dnscrypt) cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, bufsize, cb, cb_arg); else if(ports->ftype == listen_type_ssl) { cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, bufsize, cb, cb_arg); cp->ssl = sslctx; - } else if(ports->ftype == listen_type_udpancil) + } else if(ports->ftype == listen_type_udpancil || + ports->ftype == listen_type_udpancil_dnscrypt) cp = comm_point_create_udp_ancil(base, ports->fd, front->udp_buff, cb, cb_arg); if(!cp) { @@ -987,6 +1218,21 @@ listen_create(struct comm_base* base, struct listen_port* ports, } cp->dtenv = dtenv; cp->do_not_close = 1; +#ifdef USE_DNSCRYPT + if (ports->ftype == listen_type_udp_dnscrypt || + ports->ftype == listen_type_tcp_dnscrypt || + ports->ftype == listen_type_udpancil_dnscrypt) { + cp->dnscrypt = 1; + cp->dnscrypt_buffer = sldns_buffer_new(bufsize); + if(!cp->dnscrypt_buffer) { + log_err("can't alloc dnscrypt_buffer"); + comm_point_delete(cp); + listen_delete(front); + return NULL; + } + front->dnscrypt_udp_buff = cp->dnscrypt_buffer; + } +#endif if(!listen_cp_insert(cp, front)) { log_err("malloc failed"); comm_point_delete(cp); @@ -1022,6 +1268,12 @@ listen_delete(struct listen_dnsport* front) if(!front) return; listen_list_delete(front->cps); +#ifdef USE_DNSCRYPT + if(front->dnscrypt_udp_buff && + front->udp_buff != front->dnscrypt_udp_buff) { + sldns_buffer_free(front->dnscrypt_udp_buff); + } +#endif sldns_buffer_free(front->udp_buff); free(front); } @@ -1064,7 +1316,9 @@ listening_ports_open(struct config_file* cfg, int* reuseport) &hints, portbuf, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, reuseport, - cfg->ip_transparent)) { + cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, cfg->use_systemd, + cfg->dnscrypt_port)) { listening_ports_free(list); return NULL; } @@ -1076,7 +1330,9 @@ listening_ports_open(struct config_file* cfg, int* reuseport) &hints, portbuf, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, reuseport, - cfg->ip_transparent)) { + cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, cfg->use_systemd, + cfg->dnscrypt_port)) { listening_ports_free(list); return NULL; } @@ -1090,7 +1346,9 @@ listening_ports_open(struct config_file* cfg, int* reuseport) do_tcp, &hints, portbuf, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, reuseport, - cfg->ip_transparent)) { + cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, cfg->use_systemd, + cfg->dnscrypt_port)) { listening_ports_free(list); return NULL; } @@ -1102,7 +1360,9 @@ listening_ports_open(struct config_file* cfg, int* reuseport) do_tcp, &hints, portbuf, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, reuseport, - cfg->ip_transparent)) { + cfg->ip_transparent, + cfg->tcp_mss, cfg->ip_freebind, cfg->use_systemd, + cfg->dnscrypt_port)) { listening_ports_free(list); return NULL; } @@ -1130,10 +1390,16 @@ void listening_ports_free(struct listen_port* list) size_t listen_get_mem(struct listen_dnsport* listen) { + struct listen_list* p; size_t s = sizeof(*listen) + sizeof(*listen->base) + sizeof(*listen->udp_buff) + sldns_buffer_capacity(listen->udp_buff); - struct listen_list* p; +#ifdef USE_DNSCRYPT + s += sizeof(*listen->dnscrypt_udp_buff); + if(listen->udp_buff != listen->dnscrypt_udp_buff){ + s += sldns_buffer_capacity(listen->dnscrypt_udp_buff); + } +#endif for(p = listen->cps; p; p = p->next) { s += sizeof(*p); s += comm_point_get_mem(p->com); diff --git a/external/unbound/services/listen_dnsport.h b/external/unbound/services/listen_dnsport.h index 676f0c638..fac0f7970 100644 --- a/external/unbound/services/listen_dnsport.h +++ b/external/unbound/services/listen_dnsport.h @@ -59,7 +59,9 @@ struct listen_dnsport { /** buffer shared by UDP connections, since there is only one datagram at any time. */ struct sldns_buffer* udp_buff; - +#ifdef USE_DNSCRYPT + struct sldns_buffer* dnscrypt_udp_buff; +#endif /** list of comm points used to get incoming events */ struct listen_list* cps; }; @@ -85,7 +87,14 @@ enum listen_type { /** udp ipv6 (v4mapped) for use with ancillary data */ listen_type_udpancil, /** ssl over tcp type */ - listen_type_ssl + listen_type_ssl, + /** udp type + dnscrypt*/ + listen_type_udp_dnscrypt, + /** tcp type + dnscrypt */ + listen_type_tcp_dnscrypt, + /** udp ipv6 (v4mapped) for use with ancillary data + dnscrypt*/ + listen_type_udpancil_dnscrypt + }; /** @@ -137,7 +146,7 @@ void listening_ports_free(struct listen_port* list); */ struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, - void* sslctx, struct dt_env *dtenv, comm_point_callback_t* cb, + void* sslctx, struct dt_env *dtenv, comm_point_callback_type* cb, void* cb_arg); /** @@ -190,11 +199,13 @@ void listen_start_accept(struct listen_dnsport* listen); * @param reuseport: if nonNULL and true, try to set SO_REUSEPORT on * listening UDP port. Set to false on return if it failed to do so. * @param transparent: set IP_TRANSPARENT socket option. + * @param freebind: set IP_FREEBIND socket option. + * @param use_systemd: if true, fetch sockets from systemd. * @return: the socket. -1 on error. */ int create_udp_sock(int family, int socktype, struct sockaddr* addr, socklen_t addrlen, int v6only, int* inuse, int* noproto, int rcv, - int snd, int listen, int* reuseport, int transparent); + int snd, int listen, int* reuseport, int transparent, int freebind, int use_systemd); /** * Create and bind TCP listening socket @@ -204,18 +215,22 @@ int create_udp_sock(int family, int socktype, struct sockaddr* addr, * @param reuseport: if nonNULL and true, try to set SO_REUSEPORT on * listening UDP port. Set to false on return if it failed to do so. * @param transparent: set IP_TRANSPARENT socket option. + * @param mss: maximum segment size of the socket. if zero, leaves the default. + * @param freebind: set IP_FREEBIND socket option. + * @param use_systemd: if true, fetch sockets from systemd. * @return: the socket. -1 on error. */ int create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, - int* reuseport, int transparent); + int* reuseport, int transparent, int mss, int freebind, int use_systemd); /** * Create and bind local listening socket * @param path: path to the socket. * @param noproto: on error, this is set true if cause is that local sockets * are not supported. + * @param use_systemd: if true, fetch sockets from systemd. * @return: the socket. -1 on error. */ -int create_local_accept_sock(const char* path, int* noproto); +int create_local_accept_sock(const char* path, int* noproto, int use_systemd); #endif /* LISTEN_DNSPORT_H */ diff --git a/external/unbound/services/localzone.c b/external/unbound/services/localzone.c index c50ad0f15..dcce46e86 100644 --- a/external/unbound/services/localzone.c +++ b/external/unbound/services/localzone.c @@ -51,6 +51,12 @@ #include "util/netevent.h" #include "util/data/msgreply.h" #include "util/data/msgparse.h" +#include "util/as112.h" +#include "util/config_file.h" + +/* maximum RRs in an RRset, to cap possible 'endless' list RRs. + * with 16 bytes for an A record, a 64K packet has about 4000 max */ +#define LOCALZONE_RRSET_COUNT_MAX 4096 struct local_zones* local_zones_create(void) @@ -68,7 +74,7 @@ local_zones_create(void) /** helper traverse to delete zones */ static void -lzdel(rbnode_t* n, void* ATTR_UNUSED(arg)) +lzdel(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct local_zone* z = (struct local_zone*)n->key; local_zone_delete(z); @@ -93,6 +99,7 @@ local_zone_delete(struct local_zone* z) lock_rw_destroy(&z->lock); regional_destroy(z->region); free(z->name); + free(z->taglist); free(z); } @@ -152,13 +159,13 @@ local_zone_create(uint8_t* nm, size_t len, int labs, z->namelen = len; z->namelabs = labs; lock_rw_init(&z->lock); - z->region = regional_create(); + z->region = regional_create_custom(sizeof(struct regional)); if(!z->region) { free(z); return NULL; } rbtree_init(&z->data, &local_data_cmp); - lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_t)); + lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_type)); /* also the zones->lock protects node, parent, name*, class */ return z; } @@ -170,6 +177,7 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, { struct local_zone* z = local_zone_create(nm, len, labs, t, c); if(!z) { + free(nm); log_err("out of memory"); return NULL; } @@ -178,11 +186,18 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, lock_rw_wrlock(&zones->lock); lock_rw_wrlock(&z->lock); if(!rbtree_insert(&zones->ztree, &z->node)) { + struct local_zone* oldz; log_warn("duplicate local-zone"); lock_rw_unlock(&z->lock); - local_zone_delete(z); + /* save zone name locally before deallocation, + * otherwise, nm is gone if we zone_delete now. */ + oldz = z; + /* find the correct zone, so not an error for duplicate */ + z = local_zones_find(zones, nm, len, labs, c); + lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); - return NULL; + local_zone_delete(oldz); + return z; } lock_rw_unlock(&zones->lock); return z; @@ -214,9 +229,8 @@ lz_enter_zone(struct local_zones* zones, const char* name, const char* type, return z; } -/** return name and class and rdata of rr; parses string */ -static int -get_rr_content(const char* str, uint8_t** nm, uint16_t* type, +int +rrstr_get_rr_content(const char* str, uint8_t** nm, uint16_t* type, uint16_t* dclass, time_t* ttl, uint8_t* rr, size_t len, uint8_t** rdata, size_t* rdata_len) { @@ -269,16 +283,20 @@ get_rr_nameclass(const char* str, uint8_t** nm, uint16_t* dclass) * Find an rrset in local data structure. * @param data: local data domain name structure. * @param type: type to look for (host order). + * @param alias_ok: 1 if matching a non-exact, alias type such as CNAME is + * allowed. otherwise 0. * @return rrset pointer or NULL if not found. */ static struct local_rrset* -local_data_find_type(struct local_data* data, uint16_t type) +local_data_find_type(struct local_data* data, uint16_t type, int alias_ok) { struct local_rrset* p; type = htons(type); for(p = data->rrsets; p; p = p->next) { if(p->rrset->rk.type == type) return p; + if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) + return p; } return NULL; } @@ -334,15 +352,20 @@ new_local_rrset(struct regional* region, struct local_data* node, } /** insert RR into RRset data structure; Wastes a couple of bytes */ -static int -insert_rr(struct regional* region, struct packed_rrset_data* pd, - uint8_t* rdata, size_t rdata_len, time_t ttl) +int +rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, + uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr) { size_t* oldlen = pd->rr_len; time_t* oldttl = pd->rr_ttl; uint8_t** olddata = pd->rr_data; /* add RR to rrset */ + if(pd->count > LOCALZONE_RRSET_COUNT_MAX) { + log_warn("RRset '%s' has more than %d records, record ignored", + rrstr, LOCALZONE_RRSET_COUNT_MAX); + return 1; + } pd->count++; pd->rr_len = regional_alloc(region, sizeof(*pd->rr_len)*pd->count); pd->rr_ttl = regional_alloc(region, sizeof(*pd->rr_ttl)*pd->count); @@ -432,8 +455,8 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) uint8_t rr[LDNS_RR_BUF_SIZE]; uint8_t* rdata; size_t rdata_len; - if(!get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, rr, sizeof(rr), - &rdata, &rdata_len)) { + if(!rrstr_get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, rr, + sizeof(rr), &rdata, &rdata_len)) { log_err("bad local-data: %s", rrstr); return 0; } @@ -453,7 +476,23 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) log_assert(node); free(nm); - rrset = local_data_find_type(node, rrtype); + /* Reject it if we would end up having CNAME and other data (including + * another CNAME) for a redirect zone. */ + if(z->type == local_zone_redirect && node->rrsets) { + const char* othertype = NULL; + if (rrtype == LDNS_RR_TYPE_CNAME) + othertype = "other"; + else if (node->rrsets->rrset->rk.type == + htons(LDNS_RR_TYPE_CNAME)) { + othertype = "CNAME"; + } + if(othertype) { + log_err("local-data '%s' in redirect zone must not " + "coexist with %s local-data", rrstr, othertype); + return 0; + } + } + rrset = local_data_find_type(node, rrtype, 0); if(!rrset) { rrset = new_local_rrset(z->region, node, rrtype, rrclass); if(!rrset) @@ -473,7 +512,7 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) verbose(VERB_ALGO, "ignoring duplicate RR: %s", rrstr); return 1; } - return insert_rr(z->region, pd, rdata, rdata_len, ttl); + return rrset_insert_rr(z->region, pd, rdata, rdata_len, ttl, rrstr); } /** enter a data RR into auth data; a zone for it must exist */ @@ -505,6 +544,123 @@ lz_enter_rr_str(struct local_zones* zones, const char* rr) return r; } +/** enter tagstring into zone */ +static int +lz_enter_zone_tag(struct local_zones* zones, char* zname, uint8_t* list, + size_t len, uint16_t rr_class) +{ + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; + size_t dname_len = sizeof(dname); + int dname_labs, r = 0; + struct local_zone* z; + + if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { + log_err("cannot parse zone name in local-zone-tag: %s", zname); + return 0; + } + dname_labs = dname_count_labels(dname); + + lock_rw_rdlock(&zones->lock); + z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); + if(!z) { + lock_rw_unlock(&zones->lock); + log_err("no local-zone for tag %s", zname); + return 0; + } + lock_rw_wrlock(&z->lock); + lock_rw_unlock(&zones->lock); + free(z->taglist); + z->taglist = memdup(list, len); + z->taglen = len; + if(z->taglist) + r = 1; + lock_rw_unlock(&z->lock); + return r; +} + +/** enter override into zone */ +static int +lz_enter_override(struct local_zones* zones, char* zname, char* netblock, + char* type, uint16_t rr_class) +{ + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; + size_t dname_len = sizeof(dname); + int dname_labs; + struct sockaddr_storage addr; + int net; + socklen_t addrlen; + struct local_zone* z; + enum localzone_type t; + + /* parse zone name */ + if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { + log_err("cannot parse zone name in local-zone-override: %s %s", + zname, netblock); + return 0; + } + dname_labs = dname_count_labels(dname); + + /* parse netblock */ + if(!netblockstrtoaddr(netblock, UNBOUND_DNS_PORT, &addr, &addrlen, + &net)) { + log_err("cannot parse netblock in local-zone-override: %s %s", + zname, netblock); + return 0; + } + + /* parse zone type */ + if(!local_zone_str2type(type, &t)) { + log_err("cannot parse type in local-zone-override: %s %s %s", + zname, netblock, type); + return 0; + } + + /* find localzone entry */ + lock_rw_rdlock(&zones->lock); + z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); + if(!z) { + lock_rw_unlock(&zones->lock); + log_err("no local-zone for local-zone-override %s", zname); + return 0; + } + lock_rw_wrlock(&z->lock); + lock_rw_unlock(&zones->lock); + + /* create netblock addr_tree if not present yet */ + if(!z->override_tree) { + z->override_tree = (struct rbtree_type*)regional_alloc_zero( + z->region, sizeof(*z->override_tree)); + if(!z->override_tree) { + lock_rw_unlock(&z->lock); + log_err("out of memory"); + return 0; + } + addr_tree_init(z->override_tree); + } + /* add new elem to tree */ + if(z->override_tree) { + struct local_zone_override* n; + n = (struct local_zone_override*)regional_alloc_zero( + z->region, sizeof(*n)); + if(!n) { + lock_rw_unlock(&z->lock); + log_err("out of memory"); + return 0; + } + n->type = t; + if(!addr_tree_insert(z->override_tree, + (struct addr_tree_node*)n, &addr, addrlen, net)) { + lock_rw_unlock(&z->lock); + log_err("duplicate local-zone-override %s %s", + zname, netblock); + return 1; + } + } + + lock_rw_unlock(&z->lock); + return 1; +} + /** parse local-zone: statements */ static int lz_enter_zones(struct local_zones* zones, struct config_file* cfg) @@ -592,10 +748,11 @@ static int lz_enter_defaults(struct local_zones* zones, struct config_file* cfg) { struct local_zone* z; + const char** zstr; - /* this list of zones is from RFC 6303 */ + /* this list of zones is from RFC 6303 and RFC 7686 */ - /* block localhost level zones, first, later the LAN zones */ + /* block localhost level zones first, then onion and later the LAN zones */ /* localhost. zone */ if(!lz_exists(zones, "localhost.") && @@ -653,111 +810,44 @@ lz_enter_defaults(struct local_zones* zones, struct config_file* cfg) } lock_rw_unlock(&z->lock); } + /* onion. zone (RFC 7686) */ + if(!lz_exists(zones, "onion.") && + !lz_nodefault(cfg, "onion.")) { + if(!(z=lz_enter_zone(zones, "onion.", "static", + LDNS_RR_CLASS_IN)) || + !lz_enter_rr_into_zone(z, + "onion. 10800 IN NS localhost.") || + !lz_enter_rr_into_zone(z, + "onion. 10800 IN SOA localhost. nobody.invalid. " + "1 3600 1200 604800 10800")) { + log_err("out of memory adding default zone"); + if(z) { lock_rw_unlock(&z->lock); } + return 0; + } + lock_rw_unlock(&z->lock); + } - /* if unblock lan-zones, then do not add the zones below. - * we do add the zones above, about 127.0.0.1, because localhost is - * not on the lan. */ - if(cfg->unblock_lan_zones) - return 1; + /* block AS112 zones, unless asked not to */ + if(!cfg->unblock_lan_zones) { + for(zstr = as112_zones; *zstr; zstr++) { + if(!add_as112_default(zones, cfg, *zstr)) { + log_err("out of memory adding default zone"); + return 0; + } + } + } + return 1; +} - /* block LAN level zones */ - if ( !add_as112_default(zones, cfg, "10.in-addr.arpa.") || - !add_as112_default(zones, cfg, "16.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "17.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "18.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "19.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "20.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "21.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "22.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "23.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "24.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "25.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "26.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "27.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "28.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "29.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "30.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "31.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "168.192.in-addr.arpa.") || - !add_as112_default(zones, cfg, "0.in-addr.arpa.") || - !add_as112_default(zones, cfg, "64.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "65.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "66.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "67.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "68.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "69.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "70.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "71.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "72.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "73.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "74.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "75.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "76.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "77.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "78.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "79.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "80.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "81.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "82.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "83.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "84.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "85.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "86.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "87.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "88.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "89.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "90.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "91.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "92.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "93.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "94.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "95.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "96.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "97.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "98.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "99.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "100.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "101.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "102.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "103.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "104.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "105.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "106.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "107.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "108.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "109.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "110.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "111.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "112.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "113.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "114.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "115.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "116.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "117.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "118.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "119.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "120.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "121.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "122.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "123.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "124.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "125.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "126.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "127.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "254.169.in-addr.arpa.") || - !add_as112_default(zones, cfg, "2.0.192.in-addr.arpa.") || - !add_as112_default(zones, cfg, "100.51.198.in-addr.arpa.") || - !add_as112_default(zones, cfg, "113.0.203.in-addr.arpa.") || - !add_as112_default(zones, cfg, "255.255.255.255.in-addr.arpa.") || - !add_as112_default(zones, cfg, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.") || - !add_as112_default(zones, cfg, "d.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "8.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "9.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "a.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "b.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "8.b.d.0.1.0.0.2.ip6.arpa.")) { - log_err("out of memory adding default zone"); - return 0; +/** parse local-zone-override: statements */ +static int +lz_enter_overrides(struct local_zones* zones, struct config_file* cfg) +{ + struct config_str3list* p; + for(p = cfg->local_zone_overrides; p; p = p->next) { + if(!lz_enter_override(zones, p->str, p->str2, p->str3, + LDNS_RR_CLASS_IN)) + return 0; } return 1; } @@ -791,6 +881,9 @@ init_parents(struct local_zones* zones) break; } prev = node; + + if(node->override_tree) + addr_tree_init_parents(node->override_tree); lock_rw_unlock(&node->lock); } lock_rw_unlock(&zones->lock); @@ -878,6 +971,22 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) return 1; } +/** enter local-zone-tag info */ +static int +lz_enter_zone_tags(struct local_zones* zones, struct config_file* cfg) +{ + struct config_strbytelist* p; + int c = 0; + for(p = cfg->local_zone_tags; p; p = p->next) { + if(!lz_enter_zone_tag(zones, p->str, p->str2, p->str2len, + LDNS_RR_CLASS_IN)) + return 0; + c++; + } + if(c) verbose(VERB_ALGO, "applied tags to %d local zones", c); + return 1; +} + /** enter auth data */ static int lz_enter_data(struct local_zones* zones, struct config_file* cfg) @@ -913,6 +1022,10 @@ local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg) if(!lz_enter_defaults(zones, cfg)) { return 0; } + /* enter local zone overrides */ + if(!lz_enter_overrides(zones, cfg)) { + return 0; + } /* create implicit transparent zone from data. */ if(!lz_setup_implicit(zones, cfg)) { return 0; @@ -920,6 +1033,10 @@ local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg) /* setup parent ptrs for lookup during data entry */ init_parents(zones); + /* insert local zone tags */ + if(!lz_enter_zone_tags(zones, cfg)) { + return 0; + } /* insert local data */ if(!lz_enter_data(zones, cfg)) { return 0; @@ -933,33 +1050,41 @@ struct local_zone* local_zones_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass) { - rbnode_t* res = NULL; + return local_zones_tags_lookup(zones, name, len, labs, + dclass, NULL, 0, 1); +} + +struct local_zone* +local_zones_tags_lookup(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + uint8_t* taglist, size_t taglen, int ignoretags) +{ + rbnode_type* res = NULL; struct local_zone *result; struct local_zone key; + int m; key.node.key = &key; key.dclass = dclass; key.name = name; key.namelen = len; key.namelabs = labs; - if(rbtree_find_less_equal(&zones->ztree, &key, &res)) { - /* exact */ - return (struct local_zone*)res; - } else { - /* smaller element (or no element) */ - int m; - result = (struct local_zone*)res; - if(!result || result->dclass != dclass) - return NULL; - /* count number of labels matched */ - (void)dname_lab_cmp(result->name, result->namelabs, key.name, - key.namelabs, &m); - while(result) { /* go up until qname is subdomain of zone */ - if(result->namelabs <= m) - break; - result = result->parent; - } - return result; + rbtree_find_less_equal(&zones->ztree, &key, &res); + result = (struct local_zone*)res; + /* exact or smaller element (or no element) */ + if(!result || result->dclass != dclass) + return NULL; + /* count number of labels matched */ + (void)dname_lab_cmp(result->name, result->namelabs, key.name, + key.namelabs, &m); + while(result) { /* go up until qname is zone or subdomain of zone */ + if(result->namelabs <= m) + if(ignoretags || !result->taglist || + taglist_intersect(result->taglist, + result->taglen, taglist, taglen)) + break; + result = result->parent; } + return result; } struct local_zone* @@ -1031,6 +1156,18 @@ void local_zones_print(struct local_zones* zones) log_nametypeclass(0, "inform_deny zone", z->name, 0, z->dclass); break; + case local_zone_always_transparent: + log_nametypeclass(0, "always_transparent zone", + z->name, 0, z->dclass); + break; + case local_zone_always_refuse: + log_nametypeclass(0, "always_refuse zone", + z->name, 0, z->dclass); + break; + case local_zone_always_nxdomain: + log_nametypeclass(0, "always_nxdomain zone", + z->name, 0, z->dclass); + break; default: log_nametypeclass(0, "badtyped zone", z->name, 0, z->dclass); @@ -1044,8 +1181,8 @@ void local_zones_print(struct local_zones* zones) /** encode answer consisting of 1 rrset */ static int -local_encode(struct query_info* qinfo, struct edns_data* edns, - sldns_buffer* buf, struct regional* temp, +local_encode(struct query_info* qinfo, struct module_env* env, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, int rcode) { struct reply_info rep; @@ -1064,22 +1201,155 @@ local_encode(struct query_info* qinfo, struct edns_data* edns, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!reply_info_answer_encode(qinfo, &rep, + if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, temp) + || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + sldns_buffer_read_u16_at(buf, 2), edns); return 1; } +/** encode local error answer */ +static void +local_error_encode(struct query_info* qinfo, struct module_env* env, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, + int rcode, int r) +{ + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + + if(!inplace_cb_reply_local_call(env, qinfo, NULL, NULL, + rcode, edns, temp)) + edns->opt_list = NULL; + error_encode(buf, r, qinfo, *(uint16_t*)sldns_buffer_begin(buf), + sldns_buffer_read_u16_at(buf, 2), edns); +} + +/** find local data tag string match for the given type in the list */ +int +local_data_find_tag_datas(const struct query_info* qinfo, + struct config_strlist* list, struct ub_packed_rrset_key* r, + struct regional* temp) +{ + struct config_strlist* p; + char buf[65536]; + uint8_t rr[LDNS_RR_BUF_SIZE]; + size_t len; + int res; + struct packed_rrset_data* d; + for(p=list; p; p=p->next) { + uint16_t rdr_type; + + len = sizeof(rr); + /* does this element match the type? */ + snprintf(buf, sizeof(buf), ". %s", p->str); + res = sldns_str2wire_rr_buf(buf, rr, &len, NULL, 3600, + NULL, 0, NULL, 0); + if(res != 0) + /* parse errors are already checked before, in + * acllist check_data, skip this for robustness */ + continue; + if(len < 1 /* . */ + 8 /* typeclassttl*/ + 2 /*rdatalen*/) + continue; + rdr_type = sldns_wirerr_get_type(rr, len, 1); + if(rdr_type != qinfo->qtype && rdr_type != LDNS_RR_TYPE_CNAME) + continue; + + /* do we have entries already? if not setup key */ + if(r->rk.dname == NULL) { + r->entry.key = r; + r->rk.dname = qinfo->qname; + r->rk.dname_len = qinfo->qname_len; + r->rk.type = htons(rdr_type); + r->rk.rrset_class = htons(qinfo->qclass); + r->rk.flags = 0; + d = (struct packed_rrset_data*)regional_alloc_zero( + temp, sizeof(struct packed_rrset_data) + + sizeof(size_t) + sizeof(uint8_t*) + + sizeof(time_t)); + if(!d) return 0; /* out of memory */ + r->entry.data = d; + d->ttl = sldns_wirerr_get_ttl(rr, len, 1); + d->rr_len = (size_t*)((uint8_t*)d + + sizeof(struct packed_rrset_data)); + d->rr_data = (uint8_t**)&(d->rr_len[1]); + d->rr_ttl = (time_t*)&(d->rr_data[1]); + } + d = (struct packed_rrset_data*)r->entry.data; + /* add entry to the data */ + if(d->count != 0) { + size_t* oldlen = d->rr_len; + uint8_t** olddata = d->rr_data; + time_t* oldttl = d->rr_ttl; + /* increase arrays for lookup */ + /* this is of course slow for very many records, + * but most redirects are expected with few records */ + d->rr_len = (size_t*)regional_alloc_zero(temp, + (d->count+1)*sizeof(size_t)); + d->rr_data = (uint8_t**)regional_alloc_zero(temp, + (d->count+1)*sizeof(uint8_t*)); + d->rr_ttl = (time_t*)regional_alloc_zero(temp, + (d->count+1)*sizeof(time_t)); + if(!d->rr_len || !d->rr_data || !d->rr_ttl) + return 0; /* out of memory */ + /* first one was allocated after struct d, but new + * ones get their own array increment alloc, so + * copy old content */ + memmove(d->rr_len, oldlen, d->count*sizeof(size_t)); + memmove(d->rr_data, olddata, d->count*sizeof(uint8_t*)); + memmove(d->rr_ttl, oldttl, d->count*sizeof(time_t)); + } + + d->rr_len[d->count] = sldns_wirerr_get_rdatalen(rr, len, 1)+2; + d->rr_ttl[d->count] = sldns_wirerr_get_ttl(rr, len, 1); + d->rr_data[d->count] = regional_alloc_init(temp, + sldns_wirerr_get_rdatawl(rr, len, 1), + d->rr_len[d->count]); + if(!d->rr_data[d->count]) + return 0; /* out of memory */ + d->count++; + } + if(r->rk.dname) + return 1; + return 0; +} + +static int +find_tag_datas(struct query_info* qinfo, struct config_strlist* list, + struct ub_packed_rrset_key* r, struct regional* temp) +{ + int result = local_data_find_tag_datas(qinfo, list, r, temp); + + /* If we've found a non-exact alias type of local data, make a shallow + * copy of the RRset and remember it in qinfo to complete the alias + * chain later. */ + if(result && qinfo->qtype != LDNS_RR_TYPE_CNAME && + r->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + qinfo->local_alias = + regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(!qinfo->local_alias) + return 0; /* out of memory */ + qinfo->local_alias->rrset = + regional_alloc_init(temp, r, sizeof(*r)); + if(!qinfo->local_alias->rrset) + return 0; /* out of memory */ + } + return result; +} + /** answer local data match */ static int -local_data_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - int labs, struct local_data** ldp) +local_data_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, int labs, struct local_data** ldp, + enum localzone_type lz_type, int tag, struct config_strlist** tag_datas, + size_t tag_datas_size, char** tagname, int num_tags) { struct local_data key; struct local_data* ld; @@ -1088,58 +1358,95 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo, key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = labs; - if(z->type == local_zone_redirect) { + if(lz_type == local_zone_redirect) { key.name = z->name; key.namelen = z->namelen; key.namelabs = z->namelabs; + if(tag != -1 && (size_t)tag<tag_datas_size && tag_datas[tag]) { + struct ub_packed_rrset_key r; + memset(&r, 0, sizeof(r)); + if(find_tag_datas(qinfo, tag_datas[tag], &r, temp)) { + verbose(VERB_ALGO, "redirect with tag data [%d] %s", + tag, (tag<num_tags?tagname[tag]:"null")); + + /* If we found a matching alias, we should + * use it as part of the answer, but we can't + * encode it until we complete the alias + * chain. */ + if(qinfo->local_alias) + return 1; + return local_encode(qinfo, env, edns, buf, temp, + &r, 1, LDNS_RCODE_NOERROR); + } + } } ld = (struct local_data*)rbtree_search(&z->data, &key.node); *ldp = ld; if(!ld) { return 0; } - lr = local_data_find_type(ld, qinfo->qtype); + lr = local_data_find_type(ld, qinfo->qtype, 1); if(!lr) return 0; - if(z->type == local_zone_redirect) { + + /* Special case for alias matching. See local_data_answer(). */ + if(lz_type == local_zone_redirect && + qinfo->qtype != LDNS_RR_TYPE_CNAME && + lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + qinfo->local_alias = + regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(!qinfo->local_alias) + return 0; /* out of memory */ + qinfo->local_alias->rrset = + regional_alloc_init(temp, lr->rrset, sizeof(*lr->rrset)); + if(!qinfo->local_alias->rrset) + return 0; /* out of memory */ + qinfo->local_alias->rrset->rk.dname = qinfo->qname; + qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; + return 1; + } + if(lz_type == local_zone_redirect) { /* convert rrset name to query name; like a wildcard */ struct ub_packed_rrset_key r = *lr->rrset; r.rk.dname = qinfo->qname; r.rk.dname_len = qinfo->qname_len; - return local_encode(qinfo, edns, buf, temp, &r, 1, + return local_encode(qinfo, env, edns, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } - return local_encode(qinfo, edns, buf, temp, lr->rrset, 1, + return local_encode(qinfo, env, edns, buf, temp, lr->rrset, 1, LDNS_RCODE_NOERROR); } /** * answer in case where no exact match is found * @param z: zone for query + * @param env: module environment * @param qinfo: query * @param edns: edns from query * @param buf: buffer for answer. * @param temp: temp region for encoding * @param ld: local data, if NULL, no such name exists in localdata. + * @param lz_type: type of the local zone * @return 1 if a reply is to be sent, 0 if not. */ static int -lz_zone_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct local_data* ld) +lz_zone_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct local_data* ld, enum localzone_type lz_type) { - if(z->type == local_zone_deny || z->type == local_zone_inform_deny) { + if(lz_type == local_zone_deny || lz_type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ sldns_buffer_clear(buf); sldns_buffer_flip(buf); return 1; - } else if(z->type == local_zone_refuse) { - error_encode(buf, (LDNS_RCODE_REFUSED|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + } else if(lz_type == local_zone_refuse + || lz_type == local_zone_always_refuse) { + local_error_encode(qinfo, env, edns, buf, temp, + LDNS_RCODE_REFUSED, (LDNS_RCODE_REFUSED|BIT_AA)); return 1; - } else if(z->type == local_zone_static || - z->type == local_zone_redirect) { + } else if(lz_type == local_zone_static || + lz_type == local_zone_redirect || + lz_type == local_zone_always_nxdomain) { /* for static, reply nodata or nxdomain * for redirect, reply nodata */ /* no additional section processing, @@ -1147,30 +1454,30 @@ lz_zone_answer(struct local_zone* z, struct query_info* qinfo, * or using closest match for NSEC. * or using closest match for returning delegation downwards */ - int rcode = ld?LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; + int rcode = (ld || lz_type == local_zone_redirect)? + LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); - error_encode(buf, (rcode|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + local_error_encode(qinfo, env, edns, buf, temp, rcode, + (rcode|BIT_AA)); return 1; - } else if(z->type == local_zone_typetransparent) { + } else if(lz_type == local_zone_typetransparent + || lz_type == local_zone_always_transparent) { /* no NODATA or NXDOMAINS for this zone type */ return 0; } - /* else z->type == local_zone_transparent */ + /* else lz_type == local_zone_transparent */ /* if the zone is transparent and the name exists, but the type * does not, then we should make this noerror/nodata */ if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); - error_encode(buf, (rcode|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + local_error_encode(qinfo, env, edns, buf, temp, rcode, + (rcode|BIT_AA)); return 1; } @@ -1193,44 +1500,136 @@ lz_inform_print(struct local_zone* z, struct query_info* qinfo, log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } +static enum localzone_type +lz_type(uint8_t *taglist, size_t taglen, uint8_t *taglist2, size_t taglen2, + uint8_t *tagactions, size_t tagactionssize, enum localzone_type lzt, + struct comm_reply* repinfo, struct rbtree_type* override_tree, + int* tag, char** tagname, int num_tags) +{ + struct local_zone_override* lzo; + if(repinfo && override_tree) { + lzo = (struct local_zone_override*)addr_tree_lookup( + override_tree, &repinfo->addr, repinfo->addrlen); + if(lzo && lzo->type) { + verbose(VERB_ALGO, "local zone override to type %s", + local_zone_type2str(lzo->type)); + return lzo->type; + } + } + if(!taglist || !taglist2) + return lzt; + return local_data_find_tag_action(taglist, taglen, taglist2, taglen2, + tagactions, tagactionssize, lzt, tag, tagname, num_tags); +} + +enum localzone_type +local_data_find_tag_action(const uint8_t* taglist, size_t taglen, + const uint8_t* taglist2, size_t taglen2, const uint8_t* tagactions, + size_t tagactionssize, enum localzone_type lzt, int* tag, + char* const* tagname, int num_tags) +{ + size_t i, j; + uint8_t tagmatch; + + for(i=0; i<taglen && i<taglen2; i++) { + tagmatch = (taglist[i] & taglist2[i]); + for(j=0; j<8 && tagmatch>0; j++) { + if((tagmatch & 0x1)) { + *tag = (int)(i*8+j); + verbose(VERB_ALGO, "matched tag [%d] %s", + *tag, (*tag<num_tags?tagname[*tag]:"null")); + /* does this tag have a tag action? */ + if(i*8+j < tagactionssize && tagactions + && tagactions[i*8+j] != 0) { + verbose(VERB_ALGO, "tag action [%d] %s to type %s", + *tag, (*tag<num_tags?tagname[*tag]:"null"), + local_zone_type2str( + (enum localzone_type) + tagactions[i*8+j])); + return (enum localzone_type)tagactions[i*8+j]; + } + return lzt; + } + tagmatch >>= 1; + } + } + return lzt; +} + int -local_zones_answer(struct local_zones* zones, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct comm_reply* repinfo) +local_zones_answer(struct local_zones* zones, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, uint8_t* tagactions, size_t tagactionssize, + struct config_strlist** tag_datas, size_t tag_datas_size, + char** tagname, int num_tags, struct view* view) { /* see if query is covered by a zone, * if so: - try to match (exact) local data * - look at zone type for negative response. */ int labs = dname_count_labels(qinfo->qname); - struct local_data* ld; - struct local_zone* z; - int r; - lock_rw_rdlock(&zones->lock); - z = local_zones_lookup(zones, qinfo->qname, - qinfo->qname_len, labs, qinfo->qclass); + struct local_data* ld = NULL; + struct local_zone* z = NULL; + enum localzone_type lzt = local_zone_transparent; + int r, tag = -1; + + if(view) { + lock_rw_rdlock(&view->lock); + if(view->local_zones && + (z = local_zones_lookup(view->local_zones, + qinfo->qname, qinfo->qname_len, labs, + qinfo->qclass))) { + verbose(VERB_ALGO, + "using localzone from view: %s", + view->name); + lock_rw_rdlock(&z->lock); + lzt = z->type; + } + if(!z && !view->isfirst){ + lock_rw_unlock(&view->lock); + return 0; + } + lock_rw_unlock(&view->lock); + } if(!z) { + /* try global local_zones tree */ + lock_rw_rdlock(&zones->lock); + if(!(z = local_zones_tags_lookup(zones, qinfo->qname, + qinfo->qname_len, labs, qinfo->qclass, taglist, + taglen, 0))) { + lock_rw_unlock(&zones->lock); + return 0; + } + lock_rw_rdlock(&z->lock); + + lzt = lz_type(taglist, taglen, z->taglist, z->taglen, + tagactions, tagactionssize, z->type, repinfo, + z->override_tree, &tag, tagname, num_tags); lock_rw_unlock(&zones->lock); - return 0; } - lock_rw_rdlock(&z->lock); - lock_rw_unlock(&zones->lock); - - if((z->type == local_zone_inform || z->type == local_zone_inform_deny) + if((lzt == local_zone_inform || lzt == local_zone_inform_deny) && repinfo) lz_inform_print(z, qinfo, repinfo); - if(local_data_answer(z, qinfo, edns, buf, temp, labs, &ld)) { + if(lzt != local_zone_always_refuse + && lzt != local_zone_always_transparent + && lzt != local_zone_always_nxdomain + && local_data_answer(z, env, qinfo, edns, buf, temp, labs, &ld, lzt, + tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); - return 1; + /* We should tell the caller that encode is deferred if we found + * a local alias. */ + return !qinfo->local_alias; } - r = lz_zone_answer(z, qinfo, edns, buf, temp, ld); + r = lz_zone_answer(z, env, qinfo, edns, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); - return r; + return r && !qinfo->local_alias; /* see above */ } const char* local_zone_type2str(enum localzone_type t) { switch(t) { + case local_zone_unset: return "unset"; case local_zone_deny: return "deny"; case local_zone_refuse: return "refuse"; case local_zone_redirect: return "redirect"; @@ -1240,6 +1639,9 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_nodefault: return "nodefault"; case local_zone_inform: return "inform"; case local_zone_inform_deny: return "inform_deny"; + case local_zone_always_transparent: return "always_transparent"; + case local_zone_always_refuse: return "always_refuse"; + case local_zone_always_nxdomain: return "always_nxdomain"; } return "badtyped"; } @@ -1262,6 +1664,12 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_inform; else if(strcmp(type, "inform_deny") == 0) *t = local_zone_inform_deny; + else if(strcmp(type, "always_transparent") == 0) + *t = local_zone_always_transparent; + else if(strcmp(type, "always_refuse") == 0) + *t = local_zone_always_refuse; + else if(strcmp(type, "always_nxdomain") == 0) + *t = local_zone_always_nxdomain; else return 0; return 1; } @@ -1298,7 +1706,10 @@ struct local_zone* local_zones_add_zone(struct local_zones* zones, { /* create */ struct local_zone* z = local_zone_create(name, len, labs, tp, dclass); - if(!z) return NULL; + if(!z) { + free(name); + return NULL; + } lock_rw_wrlock(&z->lock); /* find the closest parent */ diff --git a/external/unbound/services/localzone.h b/external/unbound/services/localzone.h index 3d62a69d1..658f28024 100644 --- a/external/unbound/services/localzone.h +++ b/external/unbound/services/localzone.h @@ -43,6 +43,10 @@ #define SERVICES_LOCALZONE_H #include "util/rbtree.h" #include "util/locks.h" +#include "util/storage/dnstree.h" +#include "util/module.h" +#include "services/view.h" +struct packed_rrset_data; struct ub_packed_rrset_key; struct regional; struct config_file; @@ -50,6 +54,7 @@ struct edns_data; struct query_info; struct sldns_buffer; struct comm_reply; +struct config_strlist; /** * Local zone type @@ -57,8 +62,10 @@ struct comm_reply; * local-data directly. */ enum localzone_type { + /** unset type, used for unset tag_action elements */ + local_zone_unset = 0, /** drop query */ - local_zone_deny = 0, + local_zone_deny, /** answer with error */ local_zone_refuse, /** answer nxdomain or nodata */ @@ -75,7 +82,13 @@ enum localzone_type { /** log client address, but no block (transparent) */ local_zone_inform, /** log client address, and block (drop) */ - local_zone_inform_deny + local_zone_inform_deny, + /** resolve normally, even when there is local data */ + local_zone_always_transparent, + /** answer with error, even when there is local data */ + local_zone_always_refuse, + /** answer with nxdomain, even when there is local data */ + local_zone_always_nxdomain }; /** @@ -83,9 +96,9 @@ enum localzone_type { */ struct local_zones { /** lock on the localzone tree */ - lock_rw_t lock; + lock_rw_type lock; /** rbtree of struct local_zone */ - rbtree_t ztree; + rbtree_type ztree; }; /** @@ -93,7 +106,7 @@ struct local_zones { */ struct local_zone { /** rbtree node, key is name and class */ - rbnode_t node; + rbnode_type node; /** parent zone, if any. */ struct local_zone* parent; @@ -111,17 +124,24 @@ struct local_zone { * For the node, parent, name, namelen, namelabs, dclass, you * need to also hold the zones_tree lock to change them (or to * delete this zone) */ - lock_rw_t lock; + lock_rw_type lock; /** how to process zone */ enum localzone_type type; + /** tag bitlist */ + uint8_t* taglist; + /** length of the taglist (in bytes) */ + size_t taglen; + /** netblock addr_tree with struct local_zone_override information + * or NULL if there are no override elements */ + struct rbtree_type* override_tree; /** in this region the zone's data is allocated. * the struct local_zone itself is malloced. */ struct regional* region; /** local data for this zone * rbtree of struct local_data */ - rbtree_t data; + rbtree_type data; /** if data contains zone apex SOA data, this is a ptr to it. */ struct ub_packed_rrset_key* soa; }; @@ -131,7 +151,7 @@ struct local_zone { */ struct local_data { /** rbtree node, key is name only */ - rbnode_t node; + rbnode_type node; /** domain name */ uint8_t* name; /** length of name */ @@ -154,6 +174,16 @@ struct local_rrset { }; /** + * Local zone override information + */ +struct local_zone_override { + /** node in addrtree */ + struct addr_tree_node node; + /** override for local zone type */ + enum localzone_type type; +}; + +/** * Create local zones storage * @return new struct or NULL on error. */ @@ -198,6 +228,24 @@ int local_data_cmp(const void* d1, const void* d2); void local_zone_delete(struct local_zone* z); /** + * Lookup zone that contains the given name, class and taglist. + * User must lock the tree or result zone. + * @param zones: the zones tree + * @param name: dname to lookup + * @param len: length of name. + * @param labs: labelcount of name. + * @param dclass: class to lookup. + * @param taglist: taglist to lookup. + * @param taglen: lenth of taglist. + * @param ignoretags: lookup zone by name and class, regardless the + * local-zone's tags. + * @return closest local_zone or NULL if no covering zone is found. + */ +struct local_zone* local_zones_tags_lookup(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + uint8_t* taglist, size_t taglen, int ignoretags); + +/** * Lookup zone that contains the given name, class. * User must lock the tree or result zone. * @param zones: the zones tree @@ -221,18 +269,39 @@ void local_zones_print(struct local_zones* zones); * Answer authoritatively for local zones. * Takes care of locking. * @param zones: the stored zones (shared, read only). + * @param env: the module environment. * @param qinfo: query info (parsed). * @param edns: edns info (parsed). * @param buf: buffer with query ID and flags, also for reply. * @param temp: temporary storage region. * @param repinfo: source address for checks. may be NULL. + * @param taglist: taglist for checks. May be NULL. + * @param taglen: length of the taglist. + * @param tagactions: local zone actions for tags. May be NULL. + * @param tagactionssize: length of the tagactions. + * @param tag_datas: array per tag of strlist with rdata strings. or NULL. + * @param tag_datas_size: size of tag_datas array. + * @param tagname: array of tag name strings (for debug output). + * @param num_tags: number of items in tagname array. + * @param view: answer using this view. May be NULL. * @return true if answer is in buffer. false if query is not answered * by authority data. If the reply should be dropped altogether, the return * value is true, but the buffer is cleared (empty). + * It can also return true if a non-exact alias answer is found. In this + * case qinfo->local_alias points to the corresponding alias RRset but the + * answer is NOT encoded in buffer. It's the caller's responsibility to + * complete the alias chain (if needed) and encode the final set of answer. + * Data pointed to by qinfo->local_alias is allocated in 'temp' or refers to + * configuration data. So the caller will need to make a deep copy of it + * if it needs to keep it beyond the lifetime of 'temp' or a dynamic update + * to local zone data. */ -int local_zones_answer(struct local_zones* zones, struct query_info* qinfo, - struct edns_data* edns, struct sldns_buffer* buf, struct regional* temp, - struct comm_reply* repinfo); +int local_zones_answer(struct local_zones* zones, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, struct sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, uint8_t* tagactions, size_t tagactionssize, + struct config_strlist** tag_datas, size_t tag_datas_size, + char** tagname, int num_tags, struct view* view); /** * Parse the string into localzone type. @@ -321,4 +390,111 @@ void local_zones_del_data(struct local_zones* zones, */ int parse_dname(const char* str, uint8_t** res, size_t* len, int* labs); +/** + * Find local data tag string match for the given type (in qinfo) in the list. + * If found, 'r' will be filled with corresponding rrset information. + * @param qinfo: contains name, type, and class for the data + * @param list: stores local tag data to be searched + * @param r: rrset key to be filled for matched data + * @param temp: region to allocate rrset in 'r' + * @return 1 if a match is found and rrset is built; otherwise 0 including + * errors. + */ +int local_data_find_tag_datas(const struct query_info* qinfo, + struct config_strlist* list, struct ub_packed_rrset_key* r, + struct regional* temp); + +/** + * See if two sets of tag lists (in the form of bitmap) have the same tag that + * has an action. If so, '*tag' will be set to the found tag index, and the + * corresponding action will be returned in the form of local zone type. + * Otherwise the passed type (lzt) will be returned as the default action. + * Pointers except tagactions must not be NULL. + * @param taglist: 1st list of tags + * @param taglen: size of taglist in bytes + * @param taglist2: 2nd list of tags + * @param taglen2: size of taglist2 in bytes + * @param tagactions: local data actions for tags. May be NULL. + * @param tagactionssize: length of the tagactions. + * @param lzt: default action (local zone type) if no tag action is found. + * @param tag: see above. + * @param tagname: array of tag name strings (for debug output). + * @param num_tags: number of items in tagname array. + * @return found tag action or the default action. + */ +enum localzone_type local_data_find_tag_action(const uint8_t* taglist, + size_t taglen, const uint8_t* taglist2, size_t taglen2, + const uint8_t* tagactions, size_t tagactionssize, + enum localzone_type lzt, int* tag, char* const* tagname, int num_tags); + +/** + * Parses resource record string into wire format, also returning its field values. + * @param str: input resource record + * @param nm: domain name field + * @param type: record type field + * @param dclass: record class field + * @param ttl: ttl field + * @param rr: buffer for the parsed rr in wire format + * @param len: buffer length + * @param rdata: rdata field + * @param rdata_len: rdata field length + * @return 1 on success; 0 otherwise. + */ +int rrstr_get_rr_content(const char* str, uint8_t** nm, uint16_t* type, + uint16_t* dclass, time_t* ttl, uint8_t* rr, size_t len, + uint8_t** rdata, size_t* rdata_len); + +/** + * Insert specified rdata into the specified resource record. + * @param region: allocator + * @param pd: data portion of the destination resource record + * @param rdata: source rdata + * @param rdata_len: source rdata length + * @param ttl: time to live + * @param rrstr: resource record in text form (for logging) + * @return 1 on success; 0 otherwise. + */ +int rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, + uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr); + +/** + * Valid response ip actions for the IP-response-driven-action feature; + * defined here instead of in the respip module to enable sharing of enum + * values with the localzone_type enum. + * Note that these values except 'none' are the same as localzone types of + * the 'same semantics'. It's intentional as we use these values via + * access-control-tags, which can be shared for both response ip actions and + * local zones. + */ +enum respip_action { + /** no respip action */ + respip_none = local_zone_unset, + /** don't answer */ + respip_deny = local_zone_deny, + /** redirect as per provided data */ + respip_redirect = local_zone_redirect, + /** log query source and answer query */ + respip_inform = local_zone_inform, + /** log query source and don't answer query */ + respip_inform_deny = local_zone_inform_deny, + /** resolve normally, even when there is response-ip data */ + respip_always_transparent = local_zone_always_transparent, + /** answer with 'refused' response */ + respip_always_refuse = local_zone_always_refuse, + /** answer with 'no such domain' response */ + respip_always_nxdomain = local_zone_always_nxdomain, + + /* The rest of the values are only possible as + * access-control-tag-action */ + + /** serves response data (if any), else, drops queries. */ + respip_refuse = local_zone_refuse, + /** serves response data, else, nodata answer. */ + respip_static = local_zone_static, + /** gives response data (if any), else nodata answer. */ + respip_transparent = local_zone_transparent, + /** gives response data (if any), else nodata answer. */ + respip_typetransparent = local_zone_typetransparent, +}; + #endif /* SERVICES_LOCALZONE_H */ diff --git a/external/unbound/services/mesh.c b/external/unbound/services/mesh.c index 8076874ae..0cb134ade 100644 --- a/external/unbound/services/mesh.c +++ b/external/unbound/services/mesh.c @@ -56,6 +56,10 @@ #include "util/alloc.h" #include "util/config_file.h" #include "sldns/sbuffer.h" +#include "sldns/wire2str.h" +#include "services/localzone.h" +#include "util/data/dname.h" +#include "respip/respip.h" /** subtract timers and the values do not overflow or become negative */ static void @@ -121,11 +125,69 @@ timeval_smaller(const struct timeval* x, const struct timeval* y) #endif } +/* + * Compare two response-ip client info entries for the purpose of mesh state + * compare. It returns 0 if ci_a and ci_b are considered equal; otherwise + * 1 or -1 (they mean 'ci_a is larger/smaller than ci_b', respectively, but + * in practice it should be only used to mean they are different). + * We cannot share the mesh state for two queries if different response-ip + * actions can apply in the end, even if those queries are otherwise identical. + * For this purpose we compare tag lists and tag action lists; they should be + * identical to share the same state. + * For tag data, we don't look into the data content, as it can be + * expensive; unless tag data are not defined for both or they point to the + * exact same data in memory (i.e., they come from the same ACL entry), we + * consider these data different. + * Likewise, if the client info is associated with views, we don't look into + * the views. They are considered different unless they are exactly the same + * even if the views only differ in the names. + */ +static int +client_info_compare(const struct respip_client_info* ci_a, + const struct respip_client_info* ci_b) +{ + int cmp; + + if(!ci_a && !ci_b) + return 0; + if(ci_a && !ci_b) + return -1; + if(!ci_a && ci_b) + return 1; + if(ci_a->taglen != ci_b->taglen) + return (ci_a->taglen < ci_b->taglen) ? -1 : 1; + cmp = memcmp(ci_a->taglist, ci_b->taglist, ci_a->taglen); + if(cmp != 0) + return cmp; + if(ci_a->tag_actions_size != ci_b->tag_actions_size) + return (ci_a->tag_actions_size < ci_b->tag_actions_size) ? + -1 : 1; + cmp = memcmp(ci_a->tag_actions, ci_b->tag_actions, + ci_a->tag_actions_size); + if(cmp != 0) + return cmp; + if(ci_a->tag_datas != ci_b->tag_datas) + return ci_a->tag_datas < ci_b->tag_datas ? -1 : 1; + if(ci_a->view != ci_b->view) + return ci_a->view < ci_b->view ? -1 : 1; + /* For the unbound daemon these should be non-NULL and identical, + * but we check that just in case. */ + if(ci_a->respip_set != ci_b->respip_set) + return ci_a->respip_set < ci_b->respip_set ? -1 : 1; + return 0; +} + int mesh_state_compare(const void* ap, const void* bp) { struct mesh_state* a = (struct mesh_state*)ap; struct mesh_state* b = (struct mesh_state*)bp; + int cmp; + + if(a->unique < b->unique) + return -1; + if(a->unique > b->unique) + return 1; if(a->s.is_priming && !b->s.is_priming) return -1; @@ -147,7 +209,10 @@ mesh_state_compare(const void* ap, const void* bp) if(!(a->s.query_flags&BIT_CD) && (b->s.query_flags&BIT_CD)) return 1; - return query_info_compare(&a->s.qinfo, &b->s.qinfo); + cmp = query_info_compare(&a->s.qinfo, &b->s.qinfo); + if(cmp != 0) + return cmp; + return client_info_compare(a->s.client_info, b->s.client_info); } int @@ -195,7 +260,7 @@ mesh_create(struct module_stack* stack, struct module_env* env) /** help mesh delete delete mesh states */ static void -mesh_delete_helper(rbnode_t* n) +mesh_delete_helper(rbnode_type* n) { struct mesh_state* mstate = (struct mesh_state*)n->key; /* perform a full delete, not only 'cleanup' routine, @@ -279,13 +344,16 @@ int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf) } void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, - uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, - uint16_t qid) + struct respip_client_info* cinfo, uint16_t qflags, + struct edns_data* edns, struct comm_reply* rep, uint16_t qid) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = NULL; + int unique = unique_mesh_state(edns->opt_list, mesh->env); int was_detached = 0; int was_noreply = 0; int added = 0; + if(!unique) + s = mesh_area_find(mesh, cinfo, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); /* does this create a new reply state? */ if(!s || s->list_select == mesh_no_list) { if(!mesh_make_new_space(mesh, rep->c->buffer)) { @@ -310,16 +378,38 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, /* see if it already exists, if not, create one */ if(!s) { #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif - s = mesh_state_create(mesh->env, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + s = mesh_state_create(mesh->env, qinfo, cinfo, + qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) { log_err("mesh_state_create: out of memory; SERVFAIL"); + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); return; } + if(unique) + mesh_state_make_unique(s); + /* copy the edns options we got from the front */ + if(edns->opt_list) { + s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!s->s.edns_opts_front_in) { + log_err("mesh_state_create: out of memory; SERVFAIL"); + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, + NULL, LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; + error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, + qinfo, qid, qflags, edns); + comm_point_send_reply(rep); + return; + } + } + #ifdef UNBOUND_DEBUG n = #else @@ -336,8 +426,11 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, if(!s->reply_list && !s->cb_list) was_noreply = 1; /* add reply to s */ - if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo->qname)) { + if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) { log_err("mesh_new_client: out of memory; SERVFAIL"); + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, &s->s, + NULL, LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); @@ -374,23 +467,37 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, sldns_buffer* buf, - uint16_t qid, mesh_cb_func_t cb, void* cb_arg) + uint16_t qid, mesh_cb_func_type cb, void* cb_arg) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = NULL; + int unique = unique_mesh_state(edns->opt_list, mesh->env); int was_detached = 0; int was_noreply = 0; int added = 0; + if(!unique) + s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + /* there are no limits on the number of callbacks */ /* see if it already exists, if not, create one */ if(!s) { #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif - s = mesh_state_create(mesh->env, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + s = mesh_state_create(mesh->env, qinfo, NULL, + qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) { return 0; } + if(unique) + mesh_state_make_unique(s); + if(edns->opt_list) { + s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!s->s.edns_opts_front_in) { + return 0; + } + } #ifdef UNBOUND_DEBUG n = #else @@ -429,9 +536,10 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = mesh_area_find(mesh, NULL, qinfo, + qflags&(BIT_RD|BIT_CD), 0, 0); #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif /* already exists, and for a different purpose perhaps. * if mesh_no_list, keep it that way. */ @@ -448,7 +556,9 @@ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, mesh->stats_dropped ++; return; } - s = mesh_state_create(mesh->env, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + + s = mesh_state_create(mesh->env, qinfo, NULL, + qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) { log_err("prefetch mesh_state_create: out of memory"); return; @@ -497,7 +607,8 @@ void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, struct mesh_state* mesh_state_create(struct module_env* env, struct query_info* qinfo, - uint16_t qflags, int prime, int valrec) + struct respip_client_info* cinfo, uint16_t qflags, int prime, + int valrec) { struct regional* region = alloc_reg_obtain(env->alloc); struct mesh_state* mstate; @@ -521,9 +632,11 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, rbtree_init(&mstate->super_set, &mesh_state_ref_compare); rbtree_init(&mstate->sub_set, &mesh_state_ref_compare); mstate->num_activated = 0; + mstate->unique = NULL; /* init module qstate */ mstate->s.qinfo.qtype = qinfo->qtype; mstate->s.qinfo.qclass = qinfo->qclass; + mstate->s.qinfo.local_alias = NULL; mstate->s.qinfo.qname_len = qinfo->qname_len; mstate->s.qinfo.qname = regional_alloc_init(region, qinfo->qname, qinfo->qname_len); @@ -531,6 +644,14 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, alloc_reg_release(env->alloc, region); return NULL; } + if(cinfo) { + mstate->s.client_info = regional_alloc_init(region, cinfo, + sizeof(*cinfo)); + if(!mstate->s.client_info) { + alloc_reg_release(env->alloc, region); + return NULL; + } + } /* remove all weird bits from qflags */ mstate->s.query_flags = (qflags & (BIT_RD|BIT_CD)); mstate->s.is_priming = prime; @@ -543,14 +664,34 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, mstate->s.env = env; mstate->s.mesh_info = mstate; mstate->s.prefetch_leeway = 0; + mstate->s.no_cache_lookup = 0; + mstate->s.no_cache_store = 0; /* init modules */ for(i=0; i<env->mesh->mods.num; i++) { mstate->s.minfo[i] = NULL; mstate->s.ext_state[i] = module_state_initial; } + /* init edns option lists */ + mstate->s.edns_opts_front_in = NULL; + mstate->s.edns_opts_back_out = NULL; + mstate->s.edns_opts_back_in = NULL; + mstate->s.edns_opts_front_out = NULL; + return mstate; } +int +mesh_state_is_unique(struct mesh_state* mstate) +{ + return mstate->unique != NULL; +} + +void +mesh_state_make_unique(struct mesh_state* mstate) +{ + mstate->unique = mstate; +} + void mesh_state_cleanup(struct mesh_state* mstate) { @@ -658,7 +799,7 @@ void mesh_detach_subs(struct module_qstate* qstate) struct mesh_area* mesh = qstate->env->mesh; struct mesh_state_ref* ref, lookup; #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif lookup.node.key = &lookup; lookup.s = qstate->mesh_info; @@ -685,8 +826,8 @@ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, { /* find it, if not, create it */ struct mesh_area* mesh = qstate->env->mesh; - struct mesh_state* sub = mesh_area_find(mesh, qinfo, qflags, prime, - valrec); + struct mesh_state* sub = mesh_area_find(mesh, NULL, qinfo, qflags, + prime, valrec); int was_detached; if(mesh_detect_cycle_found(qstate, sub)) { verbose(VERB_ALGO, "attach failed, cycle detected"); @@ -694,10 +835,10 @@ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, } if(!sub) { #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif /* create a new one */ - sub = mesh_state_create(qstate->env, qinfo, qflags, prime, + sub = mesh_state_create(qstate->env, qinfo, NULL, qflags, prime, valrec); if(!sub) { log_err("mesh_attach_sub: out of memory"); @@ -740,7 +881,7 @@ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub) { #ifdef UNBOUND_DEBUG - struct rbnode_t* n; + struct rbnode_type* n; #endif struct mesh_state_ref* subref; /* points to sub, inserted in super */ struct mesh_state_ref* superref; /* points to super, inserted in sub */ @@ -800,6 +941,15 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, } /* send the reply */ if(rcode) { + if(rcode == LDNS_RCODE_SERVFAIL) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, rcode, &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } else { + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, + &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, rcode, r->buf, sec_status_unchecked, NULL); } else { @@ -809,7 +959,10 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, r->edns.udp_size = EDNS_ADVERTISED_SIZE; r->edns.ext_rcode = 0; r->edns.bits &= EDNS_DO; - if(!reply_info_answer_encode(&m->s.qinfo, rep, r->qid, + + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, + LDNS_RCODE_NOERROR, &r->edns, m->s.region) || + !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->buf, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) @@ -842,6 +995,9 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, struct timeval end_time; struct timeval duration; int secure; + /* Copy the client's EDNS for later restore, to make sure the edns + * compare is with the correct edns options. */ + struct edns_data edns_bak = r->edns; /* examine security status */ if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) || m->s.env->cfg->ignore_cd) && rep && @@ -856,10 +1012,18 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, if(!rep && rcode == LDNS_RCODE_NOERROR) rcode = LDNS_RCODE_SERVFAIL; /* send the reply */ + /* We don't reuse the encoded answer if either the previous or current + * response has a local alias. We could compare the alias records + * and still reuse the previous answer if they are the same, but that + * would be complicated and error prone for the relatively minor case. + * So we err on the side of safety. */ if(prev && prev->qflags == r->qflags && + !prev->local_alias && !r->local_alias && prev->edns.edns_present == r->edns.edns_present && prev->edns.bits == r->edns.bits && - prev->edns.udp_size == r->edns.udp_size) { + prev->edns.udp_size == r->edns.udp_size && + edns_opt_list_compare(prev->edns.opt_list, r->edns.opt_list) + == 0) { /* if the previous reply is identical to this one, fix ID */ if(prev->query_reply.c->buffer != r->query_reply.c->buffer) sldns_buffer_copy(r->query_reply.c->buffer, @@ -871,6 +1035,16 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, comm_point_send_reply(&r->query_reply); } else if(rcode) { m->s.qinfo.qname = r->qname; + m->s.qinfo.local_alias = r->local_alias; + if(rcode == LDNS_RCODE_SERVFAIL) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, rcode, &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } else { + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, + &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } error_encode(r->query_reply.c->buffer, rcode, &m->s.qinfo, r->qid, r->qflags, &r->edns); comm_point_send_reply(&r->query_reply); @@ -881,15 +1055,22 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, r->edns.ext_rcode = 0; r->edns.bits &= EDNS_DO; m->s.qinfo.qname = r->qname; - if(!reply_info_answer_encode(&m->s.qinfo, rep, r->qid, + m->s.qinfo.local_alias = r->local_alias; + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, + LDNS_RCODE_NOERROR, &r->edns, m->s.region) || + !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->query_reply.c->buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, LDNS_RCODE_SERVFAIL, &r->edns, m->s.region)) + r->edns.opt_list = NULL; error_encode(r->query_reply.c->buffer, LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, r->qflags, &r->edns); } + r->edns = edns_bak; comm_point_send_reply(&r->query_reply); } /* account */ @@ -910,6 +1091,12 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, query_reply.c->buffer)) == 0) m->s.env->mesh->ans_nodata++; } + /* Log reply sent */ + if(m->s.env->cfg->log_replies) { + log_reply_info(0, &m->s.qinfo, &r->query_reply.addr, + r->query_reply.addrlen, duration, 0, + r->query_reply.c->buffer); + } } void mesh_query_done(struct mesh_state* mstate) @@ -920,8 +1107,25 @@ void mesh_query_done(struct mesh_state* mstate) struct reply_info* rep = (mstate->s.return_msg? mstate->s.return_msg->rep:NULL); for(r = mstate->reply_list; r; r = r->next) { - mesh_send_reply(mstate, mstate->s.return_rcode, rep, r, prev); - prev = r; + /* if a response-ip address block has been stored the + * information should be logged for each client. */ + if(mstate->s.respip_action_info && + mstate->s.respip_action_info->addrinfo) { + respip_inform_print(mstate->s.respip_action_info->addrinfo, + r->qname, mstate->s.qinfo.qtype, + mstate->s.qinfo.qclass, r->local_alias, + &r->query_reply); + } + + /* if this query is determined to be dropped during the + * mesh processing, this is the point to take that action. */ + if(mstate->s.is_drop) + comm_point_drop_reply(&r->query_reply); + else { + mesh_send_reply(mstate, mstate->s.return_rcode, rep, + r, prev); + prev = r; + } } mstate->replies_sent = 1; for(c = mstate->cb_list; c; c = c->next) { @@ -945,7 +1149,8 @@ void mesh_walk_supers(struct mesh_area* mesh, struct mesh_state* mstate) } struct mesh_state* mesh_area_find(struct mesh_area* mesh, - struct query_info* qinfo, uint16_t qflags, int prime, int valrec) + struct respip_client_info* cinfo, struct query_info* qinfo, + uint16_t qflags, int prime, int valrec) { struct mesh_state key; struct mesh_state* result; @@ -955,13 +1160,18 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh, key.s.is_valrec = valrec; key.s.qinfo = *qinfo; key.s.query_flags = qflags; + /* We are searching for a similar mesh state when we DO want to + * aggregate the state. Thus unique is set to NULL. (default when we + * desire aggregation).*/ + key.unique = NULL; + key.s.client_info = cinfo; result = (struct mesh_state*)rbtree_search(&mesh->all, &key); return result; } int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, - sldns_buffer* buf, mesh_cb_func_t cb, void* cb_arg, + sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, uint16_t qid, uint16_t qflags) { struct mesh_cb* r = regional_alloc(s->s.region, @@ -973,6 +1183,12 @@ int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, r->cb = cb; r->cb_arg = cb_arg; r->edns = *edns; + if(edns->opt_list) { + r->edns.opt_list = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!r->edns.opt_list) + return 0; + } r->qid = qid; r->qflags = qflags; r->next = s->cb_list; @@ -982,7 +1198,8 @@ int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, } int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, - struct comm_reply* rep, uint16_t qid, uint16_t qflags, uint8_t* qname) + struct comm_reply* rep, uint16_t qid, uint16_t qflags, + const struct query_info* qinfo) { struct mesh_reply* r = regional_alloc(s->s.region, sizeof(struct mesh_reply)); @@ -990,17 +1207,74 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, return 0; r->query_reply = *rep; r->edns = *edns; + if(edns->opt_list) { + r->edns.opt_list = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!r->edns.opt_list) + return 0; + } r->qid = qid; r->qflags = qflags; r->start_time = *s->s.env->now_tv; r->next = s->reply_list; - r->qname = regional_alloc_init(s->s.region, qname, + r->qname = regional_alloc_init(s->s.region, qinfo->qname, s->s.qinfo.qname_len); if(!r->qname) return 0; + + /* Data related to local alias stored in 'qinfo' (if any) is ephemeral + * and can be different for different original queries (even if the + * replaced query name is the same). So we need to make a deep copy + * and store the copy for each reply info. */ + if(qinfo->local_alias) { + struct packed_rrset_data* d; + struct packed_rrset_data* dsrc; + r->local_alias = regional_alloc_zero(s->s.region, + sizeof(*qinfo->local_alias)); + if(!r->local_alias) + return 0; + r->local_alias->rrset = regional_alloc_init(s->s.region, + qinfo->local_alias->rrset, + sizeof(*qinfo->local_alias->rrset)); + if(!r->local_alias->rrset) + return 0; + dsrc = qinfo->local_alias->rrset->entry.data; + + /* In the current implementation, a local alias must be + * a single CNAME RR (see worker_handle_request()). */ + log_assert(!qinfo->local_alias->next && dsrc->count == 1 && + qinfo->local_alias->rrset->rk.type == + htons(LDNS_RR_TYPE_CNAME)); + /* Technically, we should make a local copy for the owner + * name of the RRset, but in the case of the first (and + * currently only) local alias RRset, the owner name should + * point to the qname of the corresponding query, which should + * be valid throughout the lifetime of this mesh_reply. So + * we can skip copying. */ + log_assert(qinfo->local_alias->rrset->rk.dname == + sldns_buffer_at(rep->c->buffer, LDNS_HEADER_SIZE)); + + d = regional_alloc_init(s->s.region, dsrc, + sizeof(struct packed_rrset_data) + + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)); + if(!d) + return 0; + r->local_alias->rrset->entry.data = d; + d->rr_len = (size_t*)((uint8_t*)d + + sizeof(struct packed_rrset_data)); + d->rr_data = (uint8_t**)&(d->rr_len[1]); + d->rr_ttl = (time_t*)&(d->rr_data[1]); + d->rr_len[0] = dsrc->rr_len[0]; + d->rr_ttl[0] = dsrc->rr_ttl[0]; + d->rr_data[0] = regional_alloc_init(s->s.region, + dsrc->rr_data[0], d->rr_len[0]); + if(!d->rr_data[0]) + return 0; + } else + r->local_alias = NULL; + s->reply_list = r; return 1; - } /** @@ -1041,15 +1315,26 @@ mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate, return mesh_continue(mesh, mstate, module_error, ev); } if(s == module_restart_next) { - fptr_ok(fptr_whitelist_mod_clear( - mesh->mods.mod[mstate->s.curmod]->clear)); - (*mesh->mods.mod[mstate->s.curmod]->clear) - (&mstate->s, mstate->s.curmod); - mstate->s.minfo[mstate->s.curmod] = NULL; + int curmod = mstate->s.curmod; + for(; mstate->s.curmod < mesh->mods.num; + mstate->s.curmod++) { + fptr_ok(fptr_whitelist_mod_clear( + mesh->mods.mod[mstate->s.curmod]->clear)); + (*mesh->mods.mod[mstate->s.curmod]->clear) + (&mstate->s, mstate->s.curmod); + mstate->s.minfo[mstate->s.curmod] = NULL; + } + mstate->s.curmod = curmod; } *ev = module_event_pass; return 1; } + if(s == module_wait_subquery && mstate->sub_set.count == 0) { + log_err("module cannot wait for subquery, subquery list empty"); + log_query_info(VERB_QUERY, "pass error for qstate", + &mstate->s.qinfo); + s = module_error; + } if(s == module_error && mstate->s.return_rcode == LDNS_RCODE_NOERROR) { /* error is bad, handle pass back up below */ mstate->s.return_rcode = LDNS_RCODE_SERVFAIL; @@ -1187,8 +1472,9 @@ mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo, uint16_t flags, int prime, int valrec) { struct mesh_area* mesh = qstate->env->mesh; - struct mesh_state* dep_m = mesh_area_find(mesh, qinfo, flags, prime, - valrec); + struct mesh_state* dep_m = NULL; + if(!mesh_state_is_unique(qstate->mesh_info)) + dep_m = mesh_area_find(mesh, NULL, qinfo, flags, prime, valrec); return mesh_detect_cycle_found(qstate, dep_m); } diff --git a/external/unbound/services/mesh.h b/external/unbound/services/mesh.h index 086e39094..1c7794532 100644 --- a/external/unbound/services/mesh.h +++ b/external/unbound/services/mesh.h @@ -59,6 +59,7 @@ struct query_info; struct reply_info; struct outbound_entry; struct timehist; +struct respip_client_info; /** * Maximum number of mesh state activations. Any more is likely an @@ -83,9 +84,9 @@ struct mesh_area { struct module_env* env; /** set of runnable queries (mesh_state.run_node) */ - rbtree_t run; + rbtree_type run; /** rbtree of all current queries (mesh_state.node)*/ - rbtree_t all; + rbtree_type all; /** count of the total number of mesh_reply entries */ size_t num_reply_addrs; @@ -154,9 +155,9 @@ struct mesh_area { */ struct mesh_state { /** node in mesh_area all tree, key is this struct. Must be first. */ - rbnode_t node; + rbnode_type node; /** node in mesh_area runnable tree, key is this struct */ - rbnode_t run_node; + rbnode_type run_node; /** the query state. Note that the qinfo and query_flags * may not change. */ struct module_qstate s; @@ -166,10 +167,10 @@ struct mesh_state { struct mesh_cb* cb_list; /** set of superstates (that want this state's result) * contains struct mesh_state_ref* */ - rbtree_t super_set; + rbtree_type super_set; /** set of substates (that this state needs to continue) * contains struct mesh_state_ref* */ - rbtree_t sub_set; + rbtree_type sub_set; /** number of activations for the mesh state */ size_t num_activated; @@ -180,6 +181,8 @@ struct mesh_state { /** if this state is in the forever list, jostle list, or neither */ enum mesh_list_select { mesh_no_list, mesh_forever_list, mesh_jostle_list } list_select; + /** pointer to this state for uniqueness or NULL */ + struct mesh_state* unique; /** true if replies have been sent out (at end for alignment) */ uint8_t replies_sent; @@ -191,7 +194,7 @@ struct mesh_state { */ struct mesh_state_ref { /** node in rbtree for set, key is this structure */ - rbnode_t node; + rbnode_type node; /** the mesh state */ struct mesh_state* s; }; @@ -214,13 +217,15 @@ struct mesh_reply { uint16_t qflags; /** qname from this query. len same as mesh qinfo. */ uint8_t* qname; + /** same as that in query_info. */ + struct local_rrset* local_alias; }; /** * Mesh result callback func. * called as func(cb_arg, rcode, buffer_with_reply, security, why_bogus); */ -typedef void (*mesh_cb_func_t)(void*, int, struct sldns_buffer*, enum sec_status, +typedef void (*mesh_cb_func_type)(void*, int, struct sldns_buffer*, enum sec_status, char*); /** @@ -241,7 +246,7 @@ struct mesh_cb { /** callback routine for results. if rcode != 0 buf has message. * called as cb(cb_arg, rcode, buf, sec_state); */ - mesh_cb_func_t cb; + mesh_cb_func_type cb; /** user arg for callback */ void* cb_arg; }; @@ -270,14 +275,18 @@ void mesh_delete(struct mesh_area* mesh); * * @param mesh: the mesh. * @param qinfo: query from client. + * @param cinfo: additional information associated with the query client. + * 'cinfo' itself is ephemeral but data pointed to by its members + * can be assumed to be valid and unchanged until the query processing is + * completed. * @param qflags: flags from client query. * @param edns: edns data from client query. * @param rep: where to reply to. * @param qid: query id to reply with. */ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, - uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, - uint16_t qid); + struct respip_client_info* cinfo, uint16_t qflags, + struct edns_data* edns, struct comm_reply* rep, uint16_t qid); /** * New query with callback. Create new query state if needed, and @@ -296,7 +305,7 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, */ int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, struct sldns_buffer* buf, - uint16_t qid, mesh_cb_func_t cb, void* cb_arg); + uint16_t qid, mesh_cb_func_type cb, void* cb_arg); /** * New prefetch message. Create new query state if needed. @@ -405,14 +414,31 @@ void mesh_state_delete(struct module_qstate* qstate); * Does not put the mesh state into rbtrees and so on. * @param env: module environment to set. * @param qinfo: query info that the mesh is for. + * @param cinfo: control info for the query client (can be NULL). * @param qflags: flags for query (RD / CD flag). * @param prime: if true, it is a priming query, set is_priming on mesh state. * @param valrec: if true, it is a validation recursion query, and sets * is_valrec on the mesh state. * @return: new mesh state or NULL on allocation error. */ -struct mesh_state* mesh_state_create(struct module_env* env, - struct query_info* qinfo, uint16_t qflags, int prime, int valrec); +struct mesh_state* mesh_state_create(struct module_env* env, + struct query_info* qinfo, struct respip_client_info* cinfo, + uint16_t qflags, int prime, int valrec); + +/** + * Check if the mesh state is unique. + * A unique mesh state uses it's unique member to point to itself, else NULL. + * @param mstate: mesh state to check. + * @return true if the mesh state is unique, false otherwise. + */ +int mesh_state_is_unique(struct mesh_state* mstate); + +/** + * Make a mesh state unique. + * A unique mesh state uses it's unique member to point to itself. + * @param mstate: mesh state to check. + */ +void mesh_state_make_unique(struct mesh_state* mstate); /** * Cleanup a mesh state and its query state. Does not do rbtree or @@ -432,14 +458,17 @@ void mesh_delete_all(struct mesh_area* mesh); * Find a mesh state in the mesh area. Pass relevant flags. * * @param mesh: the mesh area to look in. + * @param cinfo: if non-NULL client specific info that may affect IP-based + * actions that apply to the query result. * @param qinfo: what query * @param qflags: if RD / CD bit is set or not. * @param prime: if it is a priming query. * @param valrec: if it is a validation-recursion query. * @return: mesh state or NULL if not found. */ -struct mesh_state* mesh_area_find(struct mesh_area* mesh, - struct query_info* qinfo, uint16_t qflags, int prime, int valrec); +struct mesh_state* mesh_area_find(struct mesh_area* mesh, + struct respip_client_info* cinfo, struct query_info* qinfo, + uint16_t qflags, int prime, int valrec); /** * Setup attachment super/sub relation between super and sub mesh state. @@ -459,11 +488,12 @@ int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub); * @param rep: comm point reply info. * @param qid: ID of reply. * @param qflags: original query flags. - * @param qname: original query name. + * @param qinfo: original query info. * @return: 0 on alloc error. */ -int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, - struct comm_reply* rep, uint16_t qid, uint16_t qflags, uint8_t* qname); +int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, + struct comm_reply* rep, uint16_t qid, uint16_t qflags, + const struct query_info* qinfo); /** * Create new callback structure and attach it to a mesh state. @@ -478,8 +508,8 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, * @return: 0 on alloc error. */ int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, - struct sldns_buffer* buf, mesh_cb_func_t cb, void* cb_arg, uint16_t qid, - uint16_t qflags); + struct sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, + uint16_t qid, uint16_t qflags); /** * Run the mesh. Run all runnable mesh states. Which can create new diff --git a/external/unbound/services/modstack.c b/external/unbound/services/modstack.c index 49bb2fd15..9bebd3a56 100644 --- a/external/unbound/services/modstack.c +++ b/external/unbound/services/modstack.c @@ -46,10 +46,17 @@ #include "dns64/dns64.h" #include "iterator/iterator.h" #include "validator/validator.h" +#include "respip/respip.h" #ifdef WITH_PYTHONMODULE #include "pythonmod/pythonmod.h" #endif +#ifdef USE_CACHEDB +#include "cachedb/cachedb.h" +#endif +#ifdef CLIENT_SUBNET +#include "edns-subnet/subnetmod.h" +#endif /** count number of modules (words) in the string */ static int @@ -121,6 +128,13 @@ module_list_avail(void) #ifdef WITH_PYTHONMODULE "python", #endif +#ifdef USE_CACHEDB + "cachedb", +#endif +#ifdef CLIENT_SUBNET + "subnetcache", +#endif + "respip", "validator", "iterator", NULL}; @@ -139,6 +153,13 @@ module_funcs_avail(void) #ifdef WITH_PYTHONMODULE &pythonmod_get_funcblock, #endif +#ifdef USE_CACHEDB + &cachedb_get_funcblock, +#endif +#ifdef CLIENT_SUBNET + &subnetmod_get_funcblock, +#endif + &respip_get_funcblock, &val_get_funcblock, &iter_get_funcblock, NULL}; @@ -207,7 +228,7 @@ int modstack_find(struct module_stack* stack, const char* name) { int i; - for(i=0; i<stack->num; i++) { + for(i=0; i<stack->num; i++) { if(strcmp(stack->mod[i]->name, name) == 0) return i; } diff --git a/external/unbound/services/outside_network.c b/external/unbound/services/outside_network.c index f105bc0d4..426e87b3e 100644 --- a/external/unbound/services/outside_network.c +++ b/external/unbound/services/outside_network.c @@ -122,6 +122,8 @@ serviced_cmp(const void* key1, const void* key2) } if((r = query_dname_compare(q1->qbuf+10, q2->qbuf+10)) != 0) return r; + if((r = edns_opt_list_compare(q1->opt_list, q2->opt_list)) != 0) + return r; return sockaddr_cmp(&q1->addr, q1->addrlen, &q2->addr, q2->addrlen); } @@ -222,11 +224,52 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len) #endif return 0; } + + if (w->outnet->tcp_mss > 0) { +#if defined(IPPROTO_TCP) && defined(TCP_MAXSEG) + if(setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, + (void*)&w->outnet->tcp_mss, + (socklen_t)sizeof(w->outnet->tcp_mss)) < 0) { + verbose(VERB_ALGO, "outgoing tcp:" + " setsockopt(.. SO_REUSEADDR ..) failed"); + } +#else + verbose(VERB_ALGO, "outgoing tcp:" + " setsockopt(TCP_MAXSEG) unsupported"); +#endif /* defined(IPPROTO_TCP) && defined(TCP_MAXSEG) */ + } + if(!pick_outgoing_tcp(w, s)) return 0; fd_set_nonblock(s); +#ifdef USE_OSX_MSG_FASTOPEN + /* API for fast open is different here. We use a connectx() function and + then writes can happen as normal even using SSL.*/ + /* connectx requires that the len be set in the sockaddr struct*/ + struct sockaddr_in *addr_in = (struct sockaddr_in *)&w->addr; + addr_in->sin_len = w->addrlen; + sa_endpoints_t endpoints; + endpoints.sae_srcif = 0; + endpoints.sae_srcaddr = NULL; + endpoints.sae_srcaddrlen = 0; + endpoints.sae_dstaddr = (struct sockaddr *)&w->addr; + endpoints.sae_dstaddrlen = w->addrlen; + if (connectx(s, &endpoints, SAE_ASSOCID_ANY, + CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE, + NULL, 0, NULL, NULL) == -1) { +#else /* USE_OSX_MSG_FASTOPEN*/ +#ifdef USE_MSG_FASTOPEN + pend->c->tcp_do_fastopen = 1; + /* Only do TFO for TCP in which case no connect() is required here. + Don't combine client TFO with SSL, since OpenSSL can't + currently support doing a handshake on fd that already isn't connected*/ + if (w->outnet->sslctx && w->ssl_upstream) { + if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) { +#else /* USE_MSG_FASTOPEN*/ if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) { +#endif /* USE_MSG_FASTOPEN*/ +#endif /* USE_OSX_MSG_FASTOPEN*/ #ifndef USE_WINSOCK #ifdef EINPROGRESS if(errno != EINPROGRESS) { @@ -246,6 +289,9 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len) return 0; } } +#ifdef USE_MSG_FASTOPEN + } +#endif /* USE_MSG_FASTOPEN */ if(w->outnet->sslctx && w->ssl_upstream) { pend->c->ssl = outgoing_ssl_fd(w->outnet->sslctx, s); if(!pend->c->ssl) { @@ -288,7 +334,7 @@ use_free_buffer(struct outside_network* outnet) if(outnet->tcp_wait_last == w) outnet->tcp_wait_last = NULL; if(!outnet_tcp_take_into_use(w, w->pkt, w->pkt_len)) { - comm_point_callback_t* cb = w->cb; + comm_point_callback_type* cb = w->cb; void* cb_arg = w->cb_arg; waiting_tcp_delete(w); fptr_ok(fptr_whitelist_pending_tcp(cb)); @@ -574,7 +620,9 @@ static int setup_if(struct port_if* pif, const char* addrstr, pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int)); if(!pif->avail_ports) return 0; - if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen)) + if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen) && + !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT, + &pif->addr, &pif->addrlen, &pif->pfxlen)) return 0; pif->maxout = (int)numfd; pif->inuse = 0; @@ -590,7 +638,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, struct infra_cache* infra, struct ub_randstate* rnd, int use_caps_for_id, int* availports, - int numavailports, size_t unwanted_threshold, + int numavailports, size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, struct dt_env* dtenv) { @@ -620,6 +668,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, outnet->unwanted_param = unwanted_param; outnet->use_caps_for_id = use_caps_for_id; outnet->do_udp = do_udp; + outnet->tcp_mss = tcp_mss; #ifndef S_SPLINT_S if(delayclose) { outnet->delayclose = 1; @@ -726,7 +775,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, /** helper pending delete */ static void -pending_node_del(rbnode_t* node, void* arg) +pending_node_del(rbnode_type* node, void* arg) { struct pending* pend = (struct pending*)node; struct outside_network* outnet = (struct outside_network*)arg; @@ -735,12 +784,13 @@ pending_node_del(rbnode_t* node, void* arg) /** helper serviced delete */ static void -serviced_node_del(rbnode_t* node, void* ATTR_UNUSED(arg)) +serviced_node_del(rbnode_type* node, void* ATTR_UNUSED(arg)) { struct serviced_query* sq = (struct serviced_query*)node; struct service_callback* p = sq->cblist, *np; free(sq->qbuf); free(sq->zone); + edns_opt_list_free(sq->opt_list); while(p) { np = p->next; free(p); @@ -874,32 +924,55 @@ pending_delete(struct outside_network* outnet, struct pending* p) free(p); } +static void +sai6_putrandom(struct sockaddr_in6 *sa, int pfxlen, struct ub_randstate *rnd) +{ + int i, last; + if(!(pfxlen > 0 && pfxlen < 128)) + return; + for(i = 0; i < (128 - pfxlen) / 8; i++) { + sa->sin6_addr.s6_addr[15-i] = (uint8_t)ub_random_max(rnd, 256); + } + last = pfxlen & 7; + if(last != 0) { + sa->sin6_addr.s6_addr[15-i] |= + ((0xFF >> last) & ub_random_max(rnd, 256)); + } +} + /** * Try to open a UDP socket for outgoing communication. * Sets sockets options as needed. * @param addr: socket address. * @param addrlen: length of address. + * @param pfxlen: length of network prefix (for address randomisation). * @param port: port override for addr. * @param inuse: if -1 is returned, this bool means the port was in use. + * @param rnd: random state (for address randomisation). * @return fd or -1 */ static int -udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int port, - int* inuse) +udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen, + int port, int* inuse, struct ub_randstate* rnd) { int fd, noproto; if(addr_is_ip6(addr, addrlen)) { - struct sockaddr_in6* sa = (struct sockaddr_in6*)addr; - sa->sin6_port = (in_port_t)htons((uint16_t)port); + int freebind = 0; + struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr; + sa.sin6_port = (in_port_t)htons((uint16_t)port); + if(pfxlen != 0) { + freebind = 1; + sai6_putrandom(&sa, pfxlen, rnd); + } fd = create_udp_sock(AF_INET6, SOCK_DGRAM, - (struct sockaddr*)addr, addrlen, 1, inuse, &noproto, - 0, 0, 0, NULL, 0); + (struct sockaddr*)&sa, addrlen, 1, inuse, &noproto, + 0, 0, 0, NULL, 0, freebind, 0); } else { struct sockaddr_in* sa = (struct sockaddr_in*)addr; sa->sin_port = (in_port_t)htons((uint16_t)port); fd = create_udp_sock(AF_INET, SOCK_DGRAM, (struct sockaddr*)addr, addrlen, 1, inuse, &noproto, - 0, 0, 0, NULL, 0); + 0, 0, 0, NULL, 0, 0, 0); } return fd; } @@ -959,7 +1032,8 @@ select_ifport(struct outside_network* outnet, struct pending* pend, /* try to open new port, if fails, loop to try again */ log_assert(pif->inuse < pif->maxout); portno = pif->avail_ports[my_port - pif->inuse]; - fd = udp_sockport(&pif->addr, pif->addrlen, portno, &inuse); + fd = udp_sockport(&pif->addr, pif->addrlen, pif->pfxlen, + portno, &inuse, outnet->rnd); if(fd == -1 && !inuse) { /* nonrecoverable error making socket */ return 0; @@ -1050,7 +1124,7 @@ randomize_and_send_udp(struct pending* pend, sldns_buffer* packet, int timeout) struct pending* pending_udp_query(struct serviced_query* sq, struct sldns_buffer* packet, - int timeout, comm_point_callback_t* cb, void* cb_arg) + int timeout, comm_point_callback_type* cb, void* cb_arg) { struct pending* pend = (struct pending*)calloc(1, sizeof(*pend)); if(!pend) return NULL; @@ -1100,7 +1174,7 @@ outnet_tcptimer(void* arg) { struct waiting_tcp* w = (struct waiting_tcp*)arg; struct outside_network* outnet = w->outnet; - comm_point_callback_t* cb; + comm_point_callback_type* cb; void* cb_arg; if(w->pkt) { /* it is on the waiting list */ @@ -1123,7 +1197,7 @@ outnet_tcptimer(void* arg) struct waiting_tcp* pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, - int timeout, comm_point_callback_t* callback, void* callback_arg) + int timeout, comm_point_callback_type* callback, void* callback_arg) { struct pending_tcp* pend = sq->outnet->tcp_free; struct waiting_tcp* w; @@ -1203,7 +1277,8 @@ serviced_gen_query(sldns_buffer* buff, uint8_t* qname, size_t qnamelen, /** lookup serviced query in serviced query rbtree */ static struct serviced_query* lookup_serviced(struct outside_network* outnet, sldns_buffer* buff, int dnssec, - struct sockaddr_storage* addr, socklen_t addrlen) + struct sockaddr_storage* addr, socklen_t addrlen, + struct edns_option* opt_list) { struct serviced_query key; key.node.key = &key; @@ -1213,6 +1288,7 @@ lookup_serviced(struct outside_network* outnet, sldns_buffer* buff, int dnssec, memcpy(&key.addr, addr, addrlen); key.addrlen = addrlen; key.outnet = outnet; + key.opt_list = opt_list; return (struct serviced_query*)rbtree_search(outnet->serviced, &key); } @@ -1221,11 +1297,11 @@ static struct serviced_query* serviced_create(struct outside_network* outnet, sldns_buffer* buff, int dnssec, int want_dnssec, int nocaps, int tcp_upstream, int ssl_upstream, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, int qtype) + size_t zonelen, int qtype, struct edns_option* opt_list) { struct serviced_query* sq = (struct serviced_query*)malloc(sizeof(*sq)); #ifdef UNBOUND_DEBUG - rbnode_t* ins; + rbnode_type* ins; #endif if(!sq) return NULL; @@ -1251,6 +1327,16 @@ serviced_create(struct outside_network* outnet, sldns_buffer* buff, int dnssec, sq->ssl_upstream = ssl_upstream; memcpy(&sq->addr, addr, addrlen); sq->addrlen = addrlen; + sq->opt_list = NULL; + if(opt_list) { + sq->opt_list = edns_opt_copy_alloc(opt_list); + if(!sq->opt_list) { + free(sq->zone); + free(sq->qbuf); + free(sq); + return NULL; + } + } sq->outnet = outnet; sq->cblist = NULL; sq->pending = NULL; @@ -1330,6 +1416,7 @@ serviced_perturb_qname(struct ub_randstate* rnd, uint8_t* qbuf, size_t len) long int random = 0; int bits = 0; log_assert(len >= 10 + 5 /* offset qname, root, qtype, qclass */); + (void)len; lablen = *d++; while(lablen) { while(lablen--) { @@ -1378,6 +1465,7 @@ serviced_encode(struct serviced_query* sq, sldns_buffer* buff, int with_edns) edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = EDNS_ADVERTISED_VERSION; + edns.opt_list = sq->opt_list; if(sq->status == serviced_query_UDP_EDNS_FRAG) { if(addr_is_ip6(&sq->addr, sq->addrlen)) { if(EDNS_FRAG_SIZE_IP6 < EDNS_ADVERTISED_SIZE) @@ -1461,7 +1549,7 @@ serviced_check_qname(sldns_buffer* pkt, uint8_t* qbuf, size_t qbuflen) return 0; while(len1 != 0 || len2 != 0) { if(LABEL_IS_PTR(len1)) { - d1 = sldns_buffer_at(pkt, PTR_OFFSET(len1, *d1)); + d1 = sldns_buffer_begin(pkt)+PTR_OFFSET(len1, *d1); if(d1 >= sldns_buffer_at(pkt, sldns_buffer_limit(pkt))) return 0; len1 = *d1++; @@ -1499,7 +1587,7 @@ serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c, uint8_t *backup_p = NULL; size_t backlen = 0; #ifdef UNBOUND_DEBUG - rbnode_t* rem = + rbnode_type* rem = #else (void) #endif @@ -1511,7 +1599,10 @@ serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c, sq->to_be_deleted = 1; verbose(VERB_ALGO, "svcd callbacks start"); if(sq->outnet->use_caps_for_id && error == NETEVENT_NOERROR && c && - !sq->nocaps) { + !sq->nocaps && sq->qtype != LDNS_RR_TYPE_PTR) { + /* for type PTR do not check perturbed name in answer, + * compatibility with cisco dns guard boxes that mess up + * reverse queries 0x20 contents */ /* noerror and nxdomain must have a qname in reply */ if(sldns_buffer_read_u16_at(c->buffer, 4) == 0 && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) @@ -1692,6 +1783,44 @@ serviced_tcp_send(struct serviced_query* sq, sldns_buffer* buff) return sq->pending != NULL; } +/* see if packet is edns malformed; got zeroes at start. + * This is from servers that return malformed packets to EDNS0 queries, + * but they return good packets for nonEDNS0 queries. + * We try to detect their output; without resorting to a full parse or + * check for too many bytes after the end of the packet. */ +static int +packet_edns_malformed(struct sldns_buffer* buf, int qtype) +{ + size_t len; + if(sldns_buffer_limit(buf) < LDNS_HEADER_SIZE) + return 1; /* malformed */ + /* they have NOERROR rcode, 1 answer. */ + if(LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) != LDNS_RCODE_NOERROR) + return 0; + /* one query (to skip) and answer records */ + if(LDNS_QDCOUNT(sldns_buffer_begin(buf)) != 1 || + LDNS_ANCOUNT(sldns_buffer_begin(buf)) == 0) + return 0; + /* skip qname */ + len = dname_valid(sldns_buffer_at(buf, LDNS_HEADER_SIZE), + sldns_buffer_limit(buf)-LDNS_HEADER_SIZE); + if(len == 0) + return 0; + if(len == 1 && qtype == 0) + return 0; /* we asked for '.' and type 0 */ + /* and then 4 bytes (type and class of query) */ + if(sldns_buffer_limit(buf) < LDNS_HEADER_SIZE + len + 4 + 3) + return 0; + + /* and start with 11 zeroes as the answer RR */ + /* so check the qtype of the answer record, qname=0, type=0 */ + if(sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[0] == 0 && + sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[1] == 0 && + sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[2] == 0) + return 1; + return 0; +} + int serviced_udp_callback(struct comm_point* c, void* arg, int error, struct comm_reply* rep) @@ -1750,7 +1879,7 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, return 0; } #ifdef USE_DNSTAP - if(outnet->dtenv && + if(error == NETEVENT_NOERROR && outnet->dtenv && (outnet->dtenv->log_resolver_response_messages || outnet->dtenv->log_forwarder_response_messages)) dt_msg_send_outside_response(outnet->dtenv, &sq->addr, c->type, @@ -1762,7 +1891,9 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, ||sq->status == serviced_query_UDP_EDNS_FRAG) && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_FORMERR || LDNS_RCODE_WIRE( - sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOTIMPL)) { + sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOTIMPL + || packet_edns_malformed(c->buffer, sq->qtype) + )) { /* try to get an answer by falling back without EDNS */ verbose(VERB_ALGO, "serviced query: attempt without EDNS"); sq->status = serviced_query_UDP_EDNS_fallback; @@ -1855,17 +1986,22 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, struct serviced_query* outnet_serviced_query(struct outside_network* outnet, - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - int tcp_upstream, int ssl_upstream, struct sockaddr_storage* addr, - socklen_t addrlen, uint8_t* zone, size_t zonelen, - comm_point_callback_t* callback, void* callback_arg, - sldns_buffer* buff) + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, int tcp_upstream, int ssl_upstream, + struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, + size_t zonelen, struct module_qstate* qstate, + comm_point_callback_type* callback, void* callback_arg, sldns_buffer* buff, + struct module_env* env) { struct serviced_query* sq; struct service_callback* cb; - serviced_gen_query(buff, qname, qnamelen, qtype, qclass, flags); - sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen); + if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen, zone, zonelen, + qstate, qstate->region)) + return NULL; + serviced_gen_query(buff, qinfo->qname, qinfo->qname_len, qinfo->qtype, + qinfo->qclass, flags); + sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen, + qstate->edns_opts_back_out); /* duplicate entries are included in the callback list, because * there is a counterpart registration by our caller that needs to * be doubly-removed (with callbacks perhaps). */ @@ -1875,7 +2011,7 @@ outnet_serviced_query(struct outside_network* outnet, /* make new serviced query entry */ sq = serviced_create(outnet, buff, dnssec, want_dnssec, nocaps, tcp_upstream, ssl_upstream, addr, addrlen, zone, - zonelen, (int)qtype); + zonelen, (int)qinfo->qtype, qstate->edns_opts_back_out); if(!sq) { free(cb); return NULL; @@ -1932,13 +2068,7 @@ void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg) callback_list_remove(sq, cb_arg); /* if callbacks() routine scheduled deletion, let it do that */ if(!sq->cblist && !sq->to_be_deleted) { -#ifdef UNBOUND_DEBUG - rbnode_t* rem = -#else - (void) -#endif - rbtree_delete(sq->outnet->serviced, sq); - log_assert(rem); /* should be present */ + (void)rbtree_delete(sq->outnet->serviced, sq); serviced_delete(sq); } } diff --git a/external/unbound/services/outside_network.h b/external/unbound/services/outside_network.h index 9959676d3..befd512f0 100644 --- a/external/unbound/services/outside_network.h +++ b/external/unbound/services/outside_network.h @@ -58,6 +58,10 @@ struct port_if; struct sldns_buffer; struct serviced_query; struct dt_env; +struct edns_option; +struct module_env; +struct module_qstate; +struct query_info; /** * Send queries to outside servers and wait for answers from servers. @@ -119,9 +123,9 @@ struct outside_network { struct pending* udp_wait_last; /** pending udp answers. sorted by id, addr */ - rbtree_t* pending; + rbtree_type* pending; /** serviced queries, sorted by qbuf, addr, dnssec */ - rbtree_t* serviced; + rbtree_type* serviced; /** host cache, pointer but not owned by outnet. */ struct infra_cache* infra; /** where to get random numbers */ @@ -132,6 +136,8 @@ struct outside_network { /** dnstap environment */ struct dt_env* dtenv; #endif + /** maximum segment size of tcp socket */ + int tcp_mss; /** * Array of tcp pending used for outgoing TCP connections. @@ -162,6 +168,10 @@ struct port_if { /** length of addr field */ socklen_t addrlen; + /** prefix length of network address (in bits), for randomisation. + * if 0, no randomisation. */ + int pfxlen; + /** the available ports array. These are unused. * Only the first total-inuse part is filled. */ int* avail_ports; @@ -200,7 +210,7 @@ struct port_comm { */ struct pending { /** redblacktree entry, key is the pending struct(id, addr). */ - rbnode_t node; + rbnode_type node; /** the ID for the query. int so that a value out of range can * be used to signify a pending that is for certain not present in * the rbtree. (and for which deletion is safe). */ @@ -214,7 +224,7 @@ struct pending { /** timeout event */ struct comm_timer* timer; /** callback for the timeout, error or reply to the message */ - comm_point_callback_t* cb; + comm_point_callback_type* cb; /** callback user argument */ void* cb_arg; /** the outside network it is part of */ @@ -275,7 +285,7 @@ struct waiting_tcp { /** length of query packet. */ size_t pkt_len; /** callback for the timeout, error or reply to the message */ - comm_point_callback_t* cb; + comm_point_callback_type* cb; /** callback user argument */ void* cb_arg; /** if it uses ssl upstream */ @@ -289,7 +299,7 @@ struct service_callback { /** next in callback list */ struct service_callback* next; /** callback function */ - comm_point_callback_t* cb; + comm_point_callback_type* cb; /** user argument for callback function */ void* cb_arg; }; @@ -307,7 +317,7 @@ struct service_callback { */ struct serviced_query { /** The rbtree node, key is this record */ - rbnode_t node; + rbnode_type node; /** The query that needs to be answered. Starts with flags u16, * then qdcount, ..., including qname, qtype, qclass. Does not include * EDNS record. */ @@ -365,6 +375,8 @@ struct serviced_query { int last_rtt; /** do we know edns probe status already, for UDP_EDNS queries */ int edns_lame_known; + /** edns options to use for sending upstream packet */ + struct edns_option* opt_list; /** outside network this is part of */ struct outside_network* outnet; /** list of interested parties that need callback on results. */ @@ -392,6 +404,7 @@ struct serviced_query { * @param unwanted_threshold: when to take defensive action. * @param unwanted_action: the action to take. * @param unwanted_param: user parameter to action. + * @param tcp_mss: maximum segment size of tcp socket. * @param do_udp: if udp is done. * @param sslctx: context to create outgoing connections with (if enabled). * @param delayclose: if not 0, udp sockets are delayed before timeout closure. @@ -403,7 +416,7 @@ struct outside_network* outside_network_create(struct comm_base* base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, struct infra_cache* infra, struct ub_randstate* rnd, int use_caps_for_id, int* availports, - int numavailports, size_t unwanted_threshold, + int numavailports, size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, struct dt_env *dtenv); @@ -430,7 +443,7 @@ void outside_network_quit_prepare(struct outside_network* outnet); * @return: NULL on error for malloc or socket. Else the pending query object. */ struct pending* pending_udp_query(struct serviced_query* sq, - struct sldns_buffer* packet, int timeout, comm_point_callback_t* callback, + struct sldns_buffer* packet, int timeout, comm_point_callback_type* callback, void* callback_arg); /** @@ -446,7 +459,7 @@ struct pending* pending_udp_query(struct serviced_query* sq, * @return: false on error for malloc or socket. Else the pending TCP object. */ struct waiting_tcp* pending_tcp_query(struct serviced_query* sq, - struct sldns_buffer* packet, int timeout, comm_point_callback_t* callback, + struct sldns_buffer* packet, int timeout, comm_point_callback_type* callback, void* callback_arg); /** @@ -461,10 +474,7 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * Perform a serviced query to the authoritative servers. * Duplicate efforts are detected, and EDNS, TCP and UDP retry is performed. * @param outnet: outside network, with rbtree of serviced queries. - * @param qname: what qname to query. - * @param qnamelen: length of qname in octets including 0 root label. - * @param qtype: rrset type to query (host format) - * @param qclass: query class. (host format) + * @param qinfo: query info. * @param flags: flags u16 (host format), includes opcode, CD bit. * @param dnssec: if set, DO bit is set in EDNS queries. * If the value includes BIT_CD, CD bit is set when in EDNS queries. @@ -474,25 +484,28 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * @param nocaps: ignore use_caps_for_id and use unperturbed qname. * @param tcp_upstream: use TCP for upstream queries. * @param ssl_upstream: use SSL for upstream queries. - * @param callback: callback function. - * @param callback_arg: user argument to callback function. * @param addr: to which server to send the query. * @param addrlen: length of addr. * @param zone: name of the zone of the delegation point. wireformat dname. This is the delegation point name for which the server is deemed authoritative. * @param zonelen: length of zone. + * @param qstate: module qstate. Mainly for inspecting the available + * edns_opts_lists. + * @param callback: callback function. + * @param callback_arg: user argument to callback function. * @param buff: scratch buffer to create query contents in. Empty on exit. + * @param env: the module environment. * @return 0 on error, or pointer to serviced query that is used to answer * this serviced query may be shared with other callbacks as well. */ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - int tcp_upstream, int ssl_upstream, struct sockaddr_storage* addr, - socklen_t addrlen, uint8_t* zone, size_t zonelen, - comm_point_callback_t* callback, void* callback_arg, - struct sldns_buffer* buff); + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, int tcp_upstream, int ssl_upstream, + struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, + size_t zonelen, struct module_qstate* qstate, + comm_point_callback_type* callback, void* callback_arg, + struct sldns_buffer* buff, struct module_env* env); /** * Remove service query callback. diff --git a/external/unbound/services/view.c b/external/unbound/services/view.c new file mode 100644 index 000000000..33f4f4986 --- /dev/null +++ b/external/unbound/services/view.c @@ -0,0 +1,212 @@ +/* + * services/view.c - named views containing local zones authority service. + * + * Copyright (c) 2016, 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 enable named views that can hold local zone + * authority service. + */ +#include "config.h" +#include "services/view.h" +#include "services/localzone.h" +#include "util/config_file.h" + +int +view_cmp(const void* v1, const void* v2) +{ + struct view* a = (struct view*)v1; + struct view* b = (struct view*)v2; + + return strcmp(a->name, b->name); +} + +struct views* +views_create(void) +{ + struct views* v = (struct views*)calloc(1, + sizeof(*v)); + if(!v) + return NULL; + rbtree_init(&v->vtree, &view_cmp); + lock_rw_init(&v->lock); + lock_protect(&v->lock, &v->vtree, sizeof(v->vtree)); + return v; +} + +/** This prototype is defined in in respip.h, but we want to avoid + * unnecessary dependencies */ +void respip_set_delete(struct respip_set *set); + +void +view_delete(struct view* v) +{ + if(!v) + return; + lock_rw_destroy(&v->lock); + local_zones_delete(v->local_zones); + respip_set_delete(v->respip_set); + free(v->name); + free(v); +} + +static void +delviewnode(rbnode_type* n, void* ATTR_UNUSED(arg)) +{ + struct view* v = (struct view*)n; + view_delete(v); +} + +void +views_delete(struct views* v) +{ + if(!v) + return; + lock_rw_destroy(&v->lock); + traverse_postorder(&v->vtree, delviewnode, NULL); + free(v); +} + +/** create a new view */ +static struct view* +view_create(char* name) +{ + struct view* v = (struct view*)calloc(1, sizeof(*v)); + if(!v) + return NULL; + v->node.key = v; + if(!(v->name = strdup(name))) { + free(v); + return NULL; + } + lock_rw_init(&v->lock); + lock_protect(&v->lock, &v->name, sizeof(*v)-sizeof(rbnode_type)); + return v; +} + +/** enter a new view returns with WRlock */ +static struct view* +views_enter_view_name(struct views* vs, char* name) +{ + struct view* v = view_create(name); + if(!v) { + log_err("out of memory"); + return NULL; + } + + /* add to rbtree */ + lock_rw_wrlock(&vs->lock); + lock_rw_wrlock(&v->lock); + if(!rbtree_insert(&vs->vtree, &v->node)) { + log_warn("duplicate view: %s", name); + lock_rw_unlock(&v->lock); + view_delete(v); + lock_rw_unlock(&vs->lock); + return NULL; + } + lock_rw_unlock(&vs->lock); + return v; +} + +int +views_apply_cfg(struct views* vs, struct config_file* cfg) +{ + struct config_view* cv; + struct view* v; + struct config_file lz_cfg; + /* Check existence of name in first view (last in config). Rest of + * views are already checked when parsing config. */ + if(cfg->views && !cfg->views->name) { + log_err("view without a name"); + return 0; + } + for(cv = cfg->views; cv; cv = cv->next) { + /* create and enter view */ + if(!(v = views_enter_view_name(vs, cv->name))) + return 0; + v->isfirst = cv->isfirst; + if(cv->local_zones || cv->local_data) { + if(!(v->local_zones = local_zones_create())){ + lock_rw_unlock(&v->lock); + return 0; + } + memset(&lz_cfg, 0, sizeof(lz_cfg)); + lz_cfg.local_zones = cv->local_zones; + lz_cfg.local_data = cv->local_data; + lz_cfg.local_zones_nodefault = + cv->local_zones_nodefault; + if(!local_zones_apply_cfg(v->local_zones, &lz_cfg)){ + lock_rw_unlock(&v->lock); + return 0; + } + /* local_zones, local_zones_nodefault and local_data + * are free'd from config_view by local_zones_apply_cfg. + * Set pointers to NULL. */ + cv->local_zones = NULL; + cv->local_data = NULL; + cv->local_zones_nodefault = NULL; + } + lock_rw_unlock(&v->lock); + } + return 1; +} + +/** find a view by name */ +struct view* +views_find_view(struct views* vs, const char* name, int write) +{ + struct view* v; + struct view key; + key.node.key = &v; + key.name = (char *)name; + lock_rw_rdlock(&vs->lock); + if(!(v = (struct view*)rbtree_search(&vs->vtree, &key.node))) { + lock_rw_unlock(&vs->lock); + return 0; + } + if(write) { + lock_rw_wrlock(&v->lock); + } else { + lock_rw_rdlock(&v->lock); + } + lock_rw_unlock(&vs->lock); + return v; +} + +void views_print(struct views* v) +{ + /* TODO implement print */ + (void)v; +} diff --git a/external/unbound/services/view.h b/external/unbound/services/view.h new file mode 100644 index 000000000..e0b346419 --- /dev/null +++ b/external/unbound/services/view.h @@ -0,0 +1,137 @@ +/* + * services/view.h - named views containing local zones authority service. + * + * Copyright (c) 2016, 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 enable named views that can hold local zone + * authority service. + */ + +#ifndef SERVICES_VIEW_H +#define SERVICES_VIEW_H +#include "util/rbtree.h" +#include "util/locks.h" +struct regional; +struct config_file; +struct config_view; +struct respip_set; + + +/** + * Views storage, shared. + */ +struct views { + /** lock on the view tree */ + lock_rw_type lock; + /** rbtree of struct view */ + rbtree_type vtree; +}; + +/** + * View. Named structure holding local authority zones. + */ +struct view { + /** rbtree node, key is name */ + rbnode_type node; + /** view name. + * Has to be right after rbnode_t due to pointer arithmatic in + * view_create's lock protect */ + char* name; + /** view specific local authority zones */ + struct local_zones* local_zones; + /** response-ip configuration data for this view */ + struct respip_set* respip_set; + /** Fallback to global local_zones when there is no match in the view + * specific tree. 1 for yes, 0 for no */ + int isfirst; + /** lock on the data in the structure + * For the node and name you need to also hold the views_tree lock to + * change them. */ + lock_rw_type lock; +}; + + +/** + * Create views storage + * @return new struct or NULL on error. + */ +struct views* views_create(void); + +/** + * Delete views storage + * @param v: views to delete. + */ +void views_delete(struct views* v); + +/** + * Apply config settings; + * Takes care of locking. + * @param v: view is set up. + * @param cfg: config data. + * @return false on error. + */ +int views_apply_cfg(struct views* v, struct config_file* cfg); + +/** + * Compare two view entries in rbtree. Sort canonical. + * @param v1: view 1 + * @param v2: view 2 + * @return: negative, positive or 0 comparison value. + */ +int view_cmp(const void* v1, const void* v2); + +/** + * Delete one view + * @param v: view to delete. + */ +void view_delete(struct view* v); + +/** + * Debug helper. Print all views + * Takes care of locking. + * @param v: the views tree + */ +void views_print(struct views* v); + +/* Find a view by name. + * @param vs: views + * @param name: name of the view we are looking for + * @param write: 1 for obtaining write lock on found view, 0 for read lock + * @return: locked view or NULL. + */ +struct view* views_find_view(struct views* vs, const char* name, int write); + +#endif /* SERVICES_VIEW_H */ |