diff options
Diffstat (limited to 'external/unbound/services')
-rw-r--r-- | external/unbound/services/cache/dns.c | 69 | ||||
-rw-r--r-- | external/unbound/services/cache/infra.c | 313 | ||||
-rw-r--r-- | external/unbound/services/cache/infra.h | 107 | ||||
-rw-r--r-- | external/unbound/services/localzone.c | 12 | ||||
-rw-r--r-- | external/unbound/services/localzone.h | 4 | ||||
-rw-r--r-- | external/unbound/services/outside_network.c | 5 |
6 files changed, 504 insertions, 6 deletions
diff --git a/external/unbound/services/cache/dns.c b/external/unbound/services/cache/dns.c index cec2629e1..53127ce59 100644 --- a/external/unbound/services/cache/dns.c +++ b/external/unbound/services/cache/dns.c @@ -389,6 +389,18 @@ dns_msg_authadd(struct dns_msg* msg, struct regional* region, return 1; } +/** add rrset to answer section */ +static int +dns_msg_ansadd(struct dns_msg* msg, struct regional* region, + struct ub_packed_rrset_key* rrset, time_t now) +{ + if(!(msg->rep->rrsets[msg->rep->rrset_count++] = + packed_rrset_copy_region(rrset, region, now))) + return 0; + msg->rep->an_numrrsets++; + return 1; +} + struct delegpt* dns_cache_find_delegation(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, @@ -635,6 +647,58 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region, return msg; } +/** Fill TYPE_ANY response with some data from cache */ +static struct dns_msg* +fill_any(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, + struct regional* region) +{ + time_t now = *env->now; + struct dns_msg* msg = NULL; + uint16_t lookup[] = {LDNS_RR_TYPE_A, LDNS_RR_TYPE_AAAA, + LDNS_RR_TYPE_MX, LDNS_RR_TYPE_SOA, LDNS_RR_TYPE_NS, 0}; + int i, num=5; /* number of RR types to look up */ + log_assert(lookup[num] == 0); + + for(i=0; i<num; i++) { + /* look up this RR for inclusion in type ANY response */ + struct ub_packed_rrset_key* rrset = rrset_cache_lookup( + env->rrset_cache, qname, qnamelen, lookup[i], + qclass, 0, now, 0); + struct packed_rrset_data *d; + if(!rrset) + continue; + + /* only if rrset from answer section */ + d = (struct packed_rrset_data*)rrset->entry.data; + if(d->trust == rrset_trust_add_noAA || + d->trust == rrset_trust_auth_noAA || + d->trust == rrset_trust_add_AA || + d->trust == rrset_trust_auth_AA) { + lock_rw_unlock(&rrset->entry.lock); + continue; + } + + /* create msg if none */ + if(!msg) { + msg = dns_msg_create(qname, qnamelen, qtype, qclass, + region, (size_t)(num-i)); + if(!msg) { + lock_rw_unlock(&rrset->entry.lock); + return NULL; + } + } + + /* add RRset to response */ + if(!dns_msg_ansadd(msg, region, rrset, now)) { + lock_rw_unlock(&rrset->entry.lock); + return NULL; + } + lock_rw_unlock(&rrset->entry.lock); + } + return msg; +} + struct dns_msg* dns_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, @@ -747,6 +811,11 @@ dns_cache_lookup(struct module_env* env, } } + /* fill common RR types for ANY response to avoid requery */ + if(qtype == LDNS_RR_TYPE_ANY) { + return fill_any(env, qname, qnamelen, qtype, qclass, region); + } + return NULL; } diff --git a/external/unbound/services/cache/infra.c b/external/unbound/services/cache/infra.c index 61bab3fe5..c0049d8b6 100644 --- a/external/unbound/services/cache/infra.c +++ b/external/unbound/services/cache/infra.c @@ -40,6 +40,7 @@ */ #include "config.h" #include "sldns/rrdef.h" +#include "sldns/str2wire.h" #include "services/cache/infra.h" #include "util/storage/slabhash.h" #include "util/storage/lookup3.h" @@ -57,6 +58,9 @@ * can do this number of packets (until those all timeout too) */ #define TIMEOUT_COUNT_MAX 3 +/** ratelimit value for delegation point */ +int infra_dp_ratelimit = 0; + size_t infra_sizefunc(void* k, void* ATTR_UNUSED(d)) { @@ -99,6 +103,114 @@ infra_deldatafunc(void* d, void* ATTR_UNUSED(arg)) free(data); } +size_t +rate_sizefunc(void* k, void* ATTR_UNUSED(d)) +{ + struct rate_key* key = (struct rate_key*)k; + return sizeof(*key) + sizeof(struct rate_data) + key->namelen + + lock_get_mem(&key->entry.lock); +} + +int +rate_compfunc(void* key1, void* key2) +{ + struct rate_key* k1 = (struct rate_key*)key1; + struct rate_key* k2 = (struct rate_key*)key2; + if(k1->namelen != k2->namelen) { + if(k1->namelen < k2->namelen) + return -1; + return 1; + } + return query_dname_compare(k1->name, k2->name); +} + +void +rate_delkeyfunc(void* k, void* ATTR_UNUSED(arg)) +{ + struct rate_key* key = (struct rate_key*)k; + if(!key) + return; + lock_rw_destroy(&key->entry.lock); + free(key->name); + free(key); +} + +void +rate_deldatafunc(void* d, void* ATTR_UNUSED(arg)) +{ + struct rate_data* data = (struct rate_data*)d; + free(data); +} + +/** find or create element in domainlimit tree */ +static struct domain_limit_data* domain_limit_findcreate( + struct infra_cache* infra, char* name) +{ + uint8_t* nm; + int labs; + size_t nmlen; + struct domain_limit_data* d; + + /* parse name */ + nm = sldns_str2wire_dname(name, &nmlen); + if(!nm) { + log_err("could not parse %s", name); + return NULL; + } + labs = dname_count_labels(nm); + + /* can we find it? */ + d = (struct domain_limit_data*)name_tree_find(&infra->domain_limits, + nm, nmlen, labs, LDNS_RR_CLASS_IN); + if(d) { + free(nm); + return d; + } + + /* create it */ + d = (struct domain_limit_data*)calloc(1, sizeof(*d)); + if(!d) { + free(nm); + return NULL; + } + d->node.node.key = &d->node; + d->node.name = nm; + d->node.len = nmlen; + d->node.labs = labs; + d->node.dclass = LDNS_RR_CLASS_IN; + d->lim = -1; + d->below = -1; + if(!name_tree_insert(&infra->domain_limits, &d->node, nm, nmlen, + labs, LDNS_RR_CLASS_IN)) { + log_err("duplicate element in domainlimit tree"); + free(nm); + free(d); + return NULL; + } + return d; +} + +/** insert rate limit configuration into lookup tree */ +static int infra_ratelimit_cfg_insert(struct infra_cache* infra, + struct config_file* cfg) +{ + struct config_str2list* p; + struct domain_limit_data* d; + for(p = cfg->ratelimit_for_domain; p; p = p->next) { + d = domain_limit_findcreate(infra, p->str); + if(!d) + return 0; + d->lim = atoi(p->str2); + } + for(p = cfg->ratelimit_below_domain; p; p = p->next) { + d = domain_limit_findcreate(infra, p->str); + if(!d) + return 0; + d->below = atoi(p->str2); + } + return 1; +} + struct infra_cache* infra_create(struct config_file* cfg) { @@ -114,15 +226,44 @@ infra_create(struct config_file* cfg) return NULL; } infra->host_ttl = cfg->host_ttl; + name_tree_init(&infra->domain_limits); + infra_dp_ratelimit = cfg->ratelimit; + if(cfg->ratelimit != 0) { + infra->domain_rates = slabhash_create(cfg->ratelimit_slabs, + INFRA_HOST_STARTSIZE, cfg->ratelimit_size, + &rate_sizefunc, &rate_compfunc, &rate_delkeyfunc, + &rate_deldatafunc, NULL); + if(!infra->domain_rates) { + infra_delete(infra); + return NULL; + } + /* insert config data into ratelimits */ + if(!infra_ratelimit_cfg_insert(infra, cfg)) { + infra_delete(infra); + return NULL; + } + name_tree_init_parents(&infra->domain_limits); + } return infra; } +/** delete domain_limit entries */ +static void domain_limit_free(rbnode_t* n, void* ATTR_UNUSED(arg)) +{ + if(n) { + free(((struct domain_limit_data*)n)->node.name); + free(n); + } +} + void infra_delete(struct infra_cache* infra) { if(!infra) return; slabhash_delete(infra->hosts); + slabhash_delete(infra->domain_rates); + traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); free(infra); } @@ -562,8 +703,178 @@ infra_get_lame_rtt(struct infra_cache* infra, return 1; } +int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, + size_t namelen) +{ + int labs = dname_count_labels(name); + struct domain_limit_data* d = (struct domain_limit_data*) + name_tree_lookup(&infra->domain_limits, name, namelen, labs, + LDNS_RR_CLASS_IN); + if(!d) return infra_dp_ratelimit; + + if(d->node.labs == labs && d->lim != -1) + return d->lim; /* exact match */ + + /* find 'below match' */ + if(d->node.labs == labs) + d = (struct domain_limit_data*)d->node.parent; + while(d) { + if(d->below != -1) + return d->below; + d = (struct domain_limit_data*)d->node.parent; + } + return infra_dp_ratelimit; +} + +/** 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); + memset(&key, 0, sizeof(key)); + key.name = name; + key.namelen = namelen; + key.entry.hash = h; + return slabhash_lookup(infra->domain_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); + 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) { + free(k); + free(d); + return; /* alloc failure */ + } + k->namelen = namelen; + k->name = memdup(name, namelen); + if(!k->name) { + free(k); + free(d); + return; /* alloc failure */ + } + 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->domain_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) +{ + struct rate_data* d = (struct rate_data*)data; + int i, oldest; + for(i=0; i<RATE_WINDOW; i++) { + if(d->timestamp[i] == t) + return &(d->qps[i]); + } + /* remove oldest timestamp, and insert it at t with 0 qps */ + oldest = 0; + for(i=0; i<RATE_WINDOW; i++) { + if(d->timestamp[i] < d->timestamp[oldest]) + oldest = i; + } + d->timestamp[oldest] = t; + d->qps[oldest] = 0; + return &(d->qps[oldest]); +} + +int infra_rate_max(void* data, time_t now) +{ + struct rate_data* d = (struct rate_data*)data; + int i, max = 0; + for(i=0; i<RATE_WINDOW; i++) { + if(now-d->timestamp[i] <= RATE_WINDOW) { + if(d->qps[i] > max) + max = d->qps[i]; + } + } + return max; +} + +int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow) +{ + int lim, max; + struct lruhash_entry* entry; + + if(!infra_dp_ratelimit) + return 1; /* not enabled */ + + /* find ratelimit */ + lim = infra_find_ratelimit(infra, name, namelen); + + /* find or insert ratedata */ + entry = infra_find_ratedata(infra, name, namelen, 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 < lim && max >= lim) { + char buf[257]; + dname_str(name, buf); + verbose(VERB_OPS, "ratelimit exceeded %s %d", buf, lim); + } + return (max < lim); + } + + /* create */ + infra_create_ratedata(infra, name, namelen, timenow); + return (1 < lim); +} + +void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow) +{ + struct lruhash_entry* entry; + int* cur; + if(!infra_dp_ratelimit) + return; /* not enabled */ + entry = infra_find_ratedata(infra, name, namelen, 1); + if(!entry) return; /* not cached */ + cur = infra_rate_find_second(entry->data, timenow); + if((*cur) > 0) + (*cur)--; + lock_rw_unlock(&entry->lock); +} + +int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow) +{ + struct lruhash_entry* entry; + int lim, max; + if(!infra_dp_ratelimit) + return 0; /* not enabled */ + + /* find ratelimit */ + lim = infra_find_ratelimit(infra, name, namelen); + + /* find current rate */ + entry = infra_find_ratedata(infra, name, namelen, 0); + if(!entry) + return 0; /* not cached */ + max = infra_rate_max(entry->data, timenow); + lock_rw_unlock(&entry->lock); + + return (max >= lim); +} + size_t infra_get_mem(struct infra_cache* infra) { - return sizeof(*infra) + slabhash_get_mem(infra->hosts); + size_t s = sizeof(*infra) + slabhash_get_mem(infra->hosts); + if(infra->domain_rates) s += slabhash_get_mem(infra->domain_rates); + /* ignore domain_limits because walk through tree is big */ + return s; } diff --git a/external/unbound/services/cache/infra.h b/external/unbound/services/cache/infra.h index fc54f7f0d..fc7abb7c4 100644 --- a/external/unbound/services/cache/infra.h +++ b/external/unbound/services/cache/infra.h @@ -42,6 +42,7 @@ #ifndef SERVICES_CACHE_INFRA_H #define SERVICES_CACHE_INFRA_H #include "util/storage/lruhash.h" +#include "util/storage/dnstree.h" #include "util/rtt.h" struct slabhash; struct config_file; @@ -108,6 +109,55 @@ struct infra_cache { struct slabhash* hosts; /** TTL value for host information, in seconds */ int host_ttl; + /** 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; +}; + +/** ratelimit, unless overridden by domain_limits, 0 is off */ +extern int infra_dp_ratelimit; + +/** + * ratelimit settings for domains + */ +struct domain_limit_data { + /** key for rbtree, must be first in struct, name of domain */ + struct name_tree_node node; + /** ratelimit for exact match with this name, -1 if not set */ + int lim; + /** ratelimit for names below this name, -1 if not set */ + int below; +}; + +/** + * key for ratelimit lookups, a domain name + */ +struct rate_key { + /** lruhash key entry */ + struct lruhash_entry entry; + /** domain name in uncompressed wireformat */ + uint8_t* name; + /** length of name */ + size_t namelen; +}; + +/** number of seconds to track qps rate */ +#define RATE_WINDOW 2 + +/** + * Data for ratelimits per domain name + * It is incremented when a non-cache-lookup happens for that domain name. + * The name is the delegation point we have for the name. + * If a new delegation point is found (a referral reply), the previous + * delegation point is decremented, and the new one is charged with the query. + */ +struct rate_data { + /** queries counted, for that second. 0 if not in use. */ + int qps[RATE_WINDOW]; + /** what the timestamp is of the qps array members, counter is + * valid for that timestamp. Usually now and now-1. */ + time_t timestamp[RATE_WINDOW]; }; /** infra host cache default hash lookup size */ @@ -287,6 +337,51 @@ long long infra_get_host_rto(struct infra_cache* infra, int* tA, int* tAAAA, int* tother); /** + * Increment the query rate counter for a delegation point. + * @param infra: infra cache. + * @param name: zone name + * @param namelen: zone name length + * @param timenow: what time it is now. + * @return 1 if it could be incremented. 0 if the increment overshot the + * ratelimit or if in the previous second the ratelimit was exceeded. + * Failures like alloc failures are not returned (probably as 1). + */ +int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow); + +/** + * Decrement the query rate counter for a delegation point. + * Because the reply received for the delegation point was pleasant, + * we do not charge this delegation point with it (i.e. it was a referral). + * Should call it with same second as when inc() was called. + * @param infra: infra cache. + * @param name: zone name + * @param namelen: zone name length + * @param timenow: what time it is now. + */ +void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow); + +/** + * See if the query rate counter for a delegation point is exceeded. + * So, no queries are going to be allowed. + * @param infra: infra cache. + * @param name: zone name + * @param namelen: zone name length + * @param timenow: what time it is now. + * @return true if exceeded. + */ +int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, + size_t namelen, time_t timenow); + +/** find the maximum rate stored, not too old. 0 if no information. */ +int infra_rate_max(void* data, time_t now); + +/** find the ratelimit in qps for a domain */ +int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, + size_t namelen); + +/** * Get memory used by the infra cache. * @param infra: infrastructure cache. * @return memory in use in bytes. @@ -306,4 +401,16 @@ void infra_delkeyfunc(void* k, void* arg); /** delete data and destroy the lameness hashtable */ void infra_deldatafunc(void* d, void* arg); +/** calculate size for the hashtable */ +size_t rate_sizefunc(void* k, void* d); + +/** compare two names, returns -1, 0, or +1 */ +int rate_compfunc(void* key1, void* key2); + +/** delete key, and destroy the lock */ +void rate_delkeyfunc(void* k, void* arg); + +/** delete data */ +void rate_deldatafunc(void* d, void* arg); + #endif /* SERVICES_CACHE_INFRA_H */ diff --git a/external/unbound/services/localzone.c b/external/unbound/services/localzone.c index 51491656f..c50ad0f15 100644 --- a/external/unbound/services/localzone.c +++ b/external/unbound/services/localzone.c @@ -1027,6 +1027,10 @@ void local_zones_print(struct local_zones* zones) log_nametypeclass(0, "inform zone", z->name, 0, z->dclass); break; + case local_zone_inform_deny: + log_nametypeclass(0, "inform_deny zone", + z->name, 0, z->dclass); + break; default: log_nametypeclass(0, "badtyped zone", z->name, 0, z->dclass); @@ -1124,7 +1128,7 @@ 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) { - if(z->type == local_zone_deny) { + if(z->type == local_zone_deny || z->type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ sldns_buffer_clear(buf); sldns_buffer_flip(buf); @@ -1211,7 +1215,8 @@ local_zones_answer(struct local_zones* zones, struct query_info* qinfo, lock_rw_rdlock(&z->lock); lock_rw_unlock(&zones->lock); - if(z->type == local_zone_inform && repinfo) + if((z->type == local_zone_inform || z->type == local_zone_inform_deny) + && repinfo) lz_inform_print(z, qinfo, repinfo); if(local_data_answer(z, qinfo, edns, buf, temp, labs, &ld)) { @@ -1234,6 +1239,7 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_static: return "static"; case local_zone_nodefault: return "nodefault"; case local_zone_inform: return "inform"; + case local_zone_inform_deny: return "inform_deny"; } return "badtyped"; } @@ -1254,6 +1260,8 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_redirect; else if(strcmp(type, "inform") == 0) *t = local_zone_inform; + else if(strcmp(type, "inform_deny") == 0) + *t = local_zone_inform_deny; else return 0; return 1; } diff --git a/external/unbound/services/localzone.h b/external/unbound/services/localzone.h index 29ba8663f..3d62a69d1 100644 --- a/external/unbound/services/localzone.h +++ b/external/unbound/services/localzone.h @@ -73,7 +73,9 @@ enum localzone_type { * nodefault is used in config not during service. */ local_zone_nodefault, /** log client address, but no block (transparent) */ - local_zone_inform + local_zone_inform, + /** log client address, and block (drop) */ + local_zone_inform_deny }; /** diff --git a/external/unbound/services/outside_network.c b/external/unbound/services/outside_network.c index dc3d2f404..f105bc0d4 100644 --- a/external/unbound/services/outside_network.c +++ b/external/unbound/services/outside_network.c @@ -1510,7 +1510,8 @@ serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c, log_assert(rem); /* should have been present */ sq->to_be_deleted = 1; verbose(VERB_ALGO, "svcd callbacks start"); - if(sq->outnet->use_caps_for_id && error == NETEVENT_NOERROR && c) { + if(sq->outnet->use_caps_for_id && error == NETEVENT_NOERROR && c && + !sq->nocaps) { /* 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)) @@ -1590,7 +1591,7 @@ serviced_tcp_callback(struct comm_point* c, void* arg, int error, infra_update_tcp_works(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen); #ifdef USE_DNSTAP - if(sq->outnet->dtenv && + if(error==NETEVENT_NOERROR && sq->outnet->dtenv && (sq->outnet->dtenv->log_resolver_response_messages || sq->outnet->dtenv->log_forwarder_response_messages)) dt_msg_send_outside_response(sq->outnet->dtenv, &sq->addr, |