diff options
author | Cy Schubert <cy@FreeBSD.org> | 2020-05-21 05:01:52 +0000 |
---|---|---|
committer | Cy Schubert <cy@FreeBSD.org> | 2020-05-21 05:01:52 +0000 |
commit | 6a53c00e64c4cf911eb00846733d9e6a47b2e7f4 (patch) | |
tree | 60a7720d2d4edfe62b094e2665743e8879ebb911 /services | |
parent | e2fe726866d062155f6b1aae749375475ef19191 (diff) | |
download | src-6a53c00e64c4cf911eb00846733d9e6a47b2e7f4.tar.gz src-6a53c00e64c4cf911eb00846733d9e6a47b2e7f4.zip |
Vendor import of Unbound 1.10.1.vendor/unbound/1.10.1
Security: CVE-2020-12662, CVE-2020-12663
Notes
Notes:
svn path=/vendor/unbound/dist/; revision=361322
svn path=/vendor/unbound/1.10.1/; revision=361323; tag=vendor/unbound/1.10.1
Diffstat (limited to 'services')
-rw-r--r-- | services/authzone.c | 97 | ||||
-rw-r--r-- | services/authzone.h | 11 | ||||
-rw-r--r-- | services/cache/dns.c | 128 | ||||
-rw-r--r-- | services/cache/dns.h | 7 | ||||
-rw-r--r-- | services/localzone.c | 220 | ||||
-rw-r--r-- | services/localzone.h | 115 | ||||
-rw-r--r-- | services/mesh.c | 426 | ||||
-rw-r--r-- | services/mesh.h | 31 | ||||
-rw-r--r-- | services/outside_network.c | 8 | ||||
-rw-r--r-- | services/rpz.c | 1015 | ||||
-rw-r--r-- | services/rpz.h | 201 | ||||
-rw-r--r-- | services/view.c | 2 |
12 files changed, 2067 insertions, 194 deletions
diff --git a/services/authzone.c b/services/authzone.c index 7d806d9d59d0..34170abaf4a2 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -299,6 +299,8 @@ struct auth_zones* auth_zones_create(void) lock_protect(&az->lock, &az->ztree, sizeof(az->ztree)); lock_protect(&az->lock, &az->xtree, sizeof(az->xtree)); /* also lock protects the rbnode's in struct auth_zone, auth_xfer */ + lock_rw_init(&az->rpz_lock); + lock_protect(&az->rpz_lock, &az->rpz_first, sizeof(az->rpz_first)); return az; } @@ -381,11 +383,25 @@ auth_data_del(rbnode_type* n, void* ATTR_UNUSED(arg)) /** delete an auth zone structure (tree remove must be done elsewhere) */ static void -auth_zone_delete(struct auth_zone* z) +auth_zone_delete(struct auth_zone* z, struct auth_zones* az) { if(!z) return; lock_rw_destroy(&z->lock); traverse_postorder(&z->data, auth_data_del, NULL); + + if(az && z->rpz) { + /* keep RPZ linked list intact */ + lock_rw_wrlock(&az->rpz_lock); + if(z->rpz->prev) + z->rpz->prev->next = z->rpz->next; + else + az->rpz_first = z->rpz->next; + if(z->rpz->next) + z->rpz->next->prev = z->rpz->prev; + lock_rw_unlock(&az->rpz_lock); + } + if(z->rpz) + rpz_delete(z->rpz); free(z->name); free(z->zonefile); free(z); @@ -415,7 +431,7 @@ auth_zone_create(struct auth_zones* az, uint8_t* nm, size_t nmlen, /* z lock protects all, except rbtree itself, which is az->lock */ if(!rbtree_insert(&az->ztree, &z->node)) { lock_rw_unlock(&z->lock); - auth_zone_delete(z); + auth_zone_delete(z, NULL); log_warn("duplicate auth zone"); return NULL; } @@ -660,23 +676,6 @@ domain_remove_rrset(struct auth_data* node, uint16_t rr_type) } } -/** find an rr index in the rrset. returns true if found */ -static int -az_rrset_find_rr(struct packed_rrset_data* d, uint8_t* rdata, size_t len, - size_t* index) -{ - size_t i; - for(i=0; i<d->count; i++) { - if(d->rr_len[i] != len) - continue; - if(memcmp(d->rr_data[i], rdata, len) == 0) { - *index = i; - return 1; - } - } - return 0; -} - /** find an rrsig index in the rrset. returns true if found */ static int az_rrset_find_rrsig(struct packed_rrset_data* d, uint8_t* rdata, size_t len, @@ -1178,6 +1177,12 @@ az_insert_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, log_err("cannot add RR to domain"); return 0; } + if(z->rpz) { + if(!(rpz_insert_rr(z->rpz, z->namelen, dname, dname_len, + rr_type, rr_class, rr_ttl, rdata, rdatalen, rr, + rr_len))) + return 0; + } return 1; } @@ -1192,7 +1197,7 @@ az_domain_remove_rr(struct auth_data* node, uint16_t rr_type, /* find the plain RR of the given type */ if((rrset=az_domain_rrset(node, rr_type))!= NULL) { - if(az_rrset_find_rr(rrset->data, rdata, rdatalen, &index)) { + if(packed_rrset_find_rr(rrset->data, rdata, rdatalen, &index)) { if(rrset->data->count == 1 && rrset->data->rrsig_count == 0) { /* last RR, delete the rrset */ @@ -1293,6 +1298,10 @@ az_remove_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, (void)rbtree_delete(&z->data, node); auth_data_delete(node); } + if(z->rpz) { + rpz_remove_rr(z->rpz, z->namelen, dname, dname_len, rr_type, + rr_class, rdata, rdatalen); + } return 1; } @@ -1585,6 +1594,9 @@ auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); + /* clear the RPZ policies */ + if(z->rpz) + rpz_clear(z->rpz); memset(&state, 0, sizeof(state)); /* default TTL to 3600 */ @@ -1604,6 +1616,9 @@ auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) return 0; } fclose(in); + + if(z->rpz) + rpz_finish_config(z->rpz); return 1; } @@ -1877,6 +1892,18 @@ auth_zones_cfg(struct auth_zones* az, struct config_auth* c) z->for_downstream = c->for_downstream; z->for_upstream = c->for_upstream; z->fallback_enabled = c->fallback_enabled; + if(c->isrpz && !z->rpz){ + if(!(z->rpz = rpz_create(c))){ + fatal_exit("Could not setup RPZ zones"); + return 0; + } + lock_rw_wrlock(&az->rpz_lock); + z->rpz->next = az->rpz_first; + if(az->rpz_first) + az->rpz_first->prev = z->rpz; + az->rpz_first = z->rpz; + lock_rw_unlock(&az->rpz_lock); + } /* xfer zone */ if(x) { @@ -1947,14 +1974,14 @@ az_delete_deleted_zones(struct auth_zones* az) auth_xfer_delete(xfr); } (void)rbtree_delete(&az->ztree, &z->node); - auth_zone_delete(z); + auth_zone_delete(z, az); z = next; } lock_rw_unlock(&az->lock); } int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, - int setup) + int setup, int* is_rpz) { struct config_auth* p; az_setall_deleted(az); @@ -1963,6 +1990,7 @@ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, log_warn("auth-zone without a name, skipped"); continue; } + *is_rpz = (*is_rpz || p->isrpz); if(!auth_zones_cfg(az, p)) { log_err("cannot config auth zone %s", p->name); return 0; @@ -2063,7 +2091,7 @@ static void auth_zone_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_zone* z = (struct auth_zone*)n->key; - auth_zone_delete(z); + auth_zone_delete(z, NULL); } /** helper traverse to delete xfer zones */ @@ -2078,6 +2106,7 @@ void auth_zones_delete(struct auth_zones* az) { if(!az) return; lock_rw_destroy(&az->lock); + lock_rw_destroy(&az->rpz_lock); traverse_postorder(&az->ztree, auth_zone_del, NULL); traverse_postorder(&az->xtree, auth_xfer_del, NULL); free(az); @@ -2586,12 +2615,14 @@ az_nsec3_hash(uint8_t* buf, size_t buflen, uint8_t* nm, size_t nmlen, /* hashfunc(name, salt) */ memmove(p, nm, nmlen); query_dname_tolower(p); - memmove(p+nmlen, salt, saltlen); + if(salt && saltlen > 0) + memmove(p+nmlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, nmlen+saltlen, (unsigned char*)buf); for(i=0; i<iter; i++) { /* hashfunc(hash, salt) */ memmove(p, buf, hlen); - memmove(p+hlen, salt, saltlen); + if(salt && saltlen > 0) + memmove(p+hlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, hlen+saltlen, (unsigned char*)buf); } @@ -4688,6 +4719,10 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); + /* clear the RPZ policies */ + if(z->rpz) + rpz_clear(z->rpz); + xfr->have_zone = 0; xfr->serial = 0; @@ -4784,6 +4819,10 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); + /* clear the RPZ policies */ + if(z->rpz) + rpz_clear(z->rpz); + xfr->have_zone = 0; xfr->serial = 0; @@ -4969,6 +5008,9 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, if(xfr->have_zone) xfr->lease_time = *env->now; + if(z->rpz) + rpz_finish_config(z->rpz); + /* unlock */ lock_rw_unlock(&z->lock); @@ -5530,9 +5572,12 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, xfr->task_transfer->rr_scan_num == 0 && LDNS_ANCOUNT(wire)==1) { verbose(VERB_ALGO, "xfr to %s ended, " - "IXFR reply that zone has serial %u", + "IXFR reply that zone has serial %u," + " fallback from IXFR to AXFR", xfr->task_transfer->master->host, (unsigned)serial); + xfr->task_transfer->ixfr_fail = 1; + *gonextonfail = 0; return 0; } diff --git a/services/authzone.h b/services/authzone.h index a695bd029b56..9bb131ad8b39 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -46,6 +46,7 @@ #include "util/rbtree.h" #include "util/locks.h" #include "services/mesh.h" +#include "services/rpz.h" struct ub_packed_rrset_key; struct regional; struct config_file; @@ -81,6 +82,11 @@ struct auth_zones { size_t num_query_up; /** number of queries downstream */ size_t num_query_down; + /** first rpz item in linked list */ + struct rpz* rpz_first; + /** rw lock for rpz linked list, needed when iterating or editing linked + * list. */ + lock_rw_type rpz_lock; }; /** @@ -126,6 +132,8 @@ struct auth_zone { /** for upstream: this zone answers queries that unbound intends to * send upstream. */ int for_upstream; + /** RPZ zones */ + struct rpz* rpz; /** zone has been deleted */ int zone_deleted; /** deletelist pointer, unused normally except during delete */ @@ -460,10 +468,11 @@ struct auth_zones* auth_zones_create(void); * @param az: auth zones structure * @param cfg: config to apply. * @param setup: if true, also sets up values in the auth zones structure + * @param is_rpz: set to 1 if at least one RPZ zone is configured. * @return false on failure. */ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, - int setup); + int setup, int* is_rpz); /** initial pick up of worker timeouts, ties events to worker event loop * @param az: auth zones structure diff --git a/services/cache/dns.c b/services/cache/dns.c index aa4efec73f41..7b6e142c9915 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -45,6 +45,7 @@ #include "validator/val_utils.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" +#include "util/data/msgparse.h" #include "util/data/msgreply.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" @@ -73,15 +74,15 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, time_t leeway, int pside, struct reply_info* qrep, struct regional* region) { - size_t i; - /* see if rrset already exists in cache, if not insert it. */ - for(i=0; i<rep->rrset_count; i++) { - rep->ref[i].key = rep->rrsets[i]; - rep->ref[i].id = rep->rrsets[i]->id; - /* update ref if it was in the cache */ + size_t i; + /* see if rrset already exists in cache, if not insert it. */ + for(i=0; i<rep->rrset_count; i++) { + rep->ref[i].key = rep->rrsets[i]; + rep->ref[i].id = rep->rrsets[i]->id; + /* update ref if it was in the cache */ switch(rrset_cache_update(env->rrset_cache, &rep->ref[i], - env->alloc, now + ((ntohs(rep->ref[i].key->rk.type)== - LDNS_RR_TYPE_NS && !pside)?0:leeway))) { + env->alloc, now + ((ntohs(rep->ref[i].key->rk.type)== + LDNS_RR_TYPE_NS && !pside)?0:leeway))) { case 0: /* ref unchanged, item inserted */ break; case 2: /* ref updated, cache is superior */ @@ -104,9 +105,9 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, * the fallthrough warning */ /* fallthrough */ case 1: /* ref updated, item inserted */ - rep->rrsets[i] = rep->ref[i].key; + rep->rrsets[i] = rep->ref[i].key; } - } + } } /** delete message from message cache */ @@ -272,7 +273,7 @@ find_add_addrs(struct module_env* env, uint16_t qclass, akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(akey) { - if(!delegpt_add_rrset_A(dp, region, akey, 0)) { + if(!delegpt_add_rrset_A(dp, region, akey, 0, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } @@ -292,7 +293,7 @@ find_add_addrs(struct module_env* env, uint16_t qclass, akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); if(akey) { - if(!delegpt_add_rrset_AAAA(dp, region, akey, 0)) { + if(!delegpt_add_rrset_AAAA(dp, region, akey, 0, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } @@ -326,7 +327,8 @@ cache_fill_missing(struct module_env* env, uint16_t qclass, akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(akey) { - if(!delegpt_add_rrset_A(dp, region, akey, ns->lame)) { + if(!delegpt_add_rrset_A(dp, region, akey, ns->lame, + NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } @@ -346,7 +348,8 @@ cache_fill_missing(struct module_env* env, uint16_t qclass, akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); if(akey) { - if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame)) { + if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame, + NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } @@ -532,31 +535,51 @@ gen_dns_msg(struct regional* region, struct query_info* q, size_t num) } 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) +tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, + struct regional* region, time_t now, int allow_expired, + struct regional* scratch) { struct dns_msg* msg; size_t i; - if(now > r->ttl) - return NULL; + int is_expired = 0; + time_t now_control = now; + if(now > r->ttl) { + /* Check if we are allowed to serve expired */ + if(allow_expired) { + if(env->cfg->serve_expired_ttl && + r->serve_expired_ttl < now) { + return NULL; + } + } else { + return NULL; + } + /* Change the current time so we can pass the below TTL checks when + * serving expired data. */ + now_control = r->ttl - env->cfg->serve_expired_reply_ttl; + is_expired = 1; + } + msg = gen_dns_msg(region, q, r->rrset_count); - if(!msg) - return NULL; + if(!msg) return NULL; msg->rep->flags = r->flags; msg->rep->qdcount = r->qdcount; - msg->rep->ttl = r->ttl - now; + msg->rep->ttl = is_expired + ?SERVE_EXPIRED_REPLY_TTL + :r->ttl - now; if(r->prefetch_ttl > now) msg->rep->prefetch_ttl = r->prefetch_ttl - now; - else msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); + else + msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; msg->rep->security = r->security; msg->rep->an_numrrsets = r->an_numrrsets; msg->rep->ns_numrrsets = r->ns_numrrsets; msg->rep->ar_numrrsets = r->ar_numrrsets; msg->rep->rrset_count = r->rrset_count; - msg->rep->authoritative = r->authoritative; - if(!rrset_array_lock(r->ref, r->rrset_count, now)) + msg->rep->authoritative = r->authoritative; + if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) { return NULL; + } if(r->an_numrrsets > 0 && (r->rrsets[0]->rk.type == htons( LDNS_RR_TYPE_CNAME) || r->rrsets[0]->rk.type == htons( LDNS_RR_TYPE_DNAME)) && !reply_check_cname_chain(q, r)) { @@ -570,7 +593,7 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, return NULL; } for(i=0; i<msg->rep->rrset_count; i++) { - msg->rep->rrsets[i] = packed_rrset_copy_region(r->rrsets[i], + msg->rep->rrsets[i] = packed_rrset_copy_region(r->rrsets[i], region, now); if(!msg->rep->rrsets[i]) { rrset_array_unlock(r->ref, r->rrset_count); @@ -797,7 +820,7 @@ dns_cache_lookup(struct module_env* env, if(e) { struct msgreply_entry* key = (struct msgreply_entry*)e->key; struct reply_info* data = (struct reply_info*)e->data; - struct dns_msg* msg = tomsg(env, &key->key, data, region, now, + struct dns_msg* msg = tomsg(env, &key->key, data, region, now, 0, scratch); if(msg) { lock_rw_unlock(&e->lock); @@ -899,37 +922,38 @@ dns_cache_lookup(struct module_env* env, * Empty nonterminals are NOERROR, so an NXDOMAIN for foo * means bla.foo also does not exist. The DNSSEC proofs are * the same. We search upwards for NXDOMAINs. */ - if(env->cfg->harden_below_nxdomain) - while(!dname_is_root(k.qname)) { - 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; + if(env->cfg->harden_below_nxdomain) { + while(!dname_is_root(k.qname)) { + 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) { - struct reply_info* data = (struct reply_info*)e->data; - struct dns_msg* msg; - if(FLAGS_GET_RCODE(data->flags) == LDNS_RCODE_NXDOMAIN - && data->security == sec_status_secure - && (data->an_numrrsets == 0 || - ntohs(data->rrsets[0]->rk.type) != LDNS_RR_TYPE_CNAME) - && (msg=tomsg(env, &k, data, region, now, scratch))){ + 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; + if(FLAGS_GET_RCODE(data->flags) == LDNS_RCODE_NXDOMAIN + && data->security == sec_status_secure + && (data->an_numrrsets == 0 || + ntohs(data->rrsets[0]->rk.type) != LDNS_RR_TYPE_CNAME) + && (msg=tomsg(env, &k, data, region, now, 0, scratch))) { + lock_rw_unlock(&e->lock); + msg->qinfo.qname=qname; + msg->qinfo.qname_len=qnamelen; + /* check that DNSSEC really works out */ + msg->rep->security = sec_status_unchecked; + iter_scrub_nxdomain(msg); + return msg; + } lock_rw_unlock(&e->lock); - msg->qinfo.qname=qname; - msg->qinfo.qname_len=qnamelen; - /* check that DNSSEC really works out */ - msg->rep->security = sec_status_unchecked; - iter_scrub_nxdomain(msg); - return msg; } - lock_rw_unlock(&e->lock); + k.qtype = qtype; } - k.qtype = qtype; - } + } /* fill common RR types for ANY response to avoid requery */ if(qtype == LDNS_RR_TYPE_ANY) { diff --git a/services/cache/dns.h b/services/cache/dns.h index 19d0d9f992d1..f1b77fb36c00 100644 --- a/services/cache/dns.h +++ b/services/cache/dns.h @@ -143,11 +143,14 @@ struct delegpt* dns_cache_find_delegation(struct module_env* env, * @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 allow_expired: if true and serve-expired is enabled, it will allow + * for expired dns_msg to be generated based on the configured serve-expired + * logic. * @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); + int allow_expired, struct regional* scratch); /** * Find cached message @@ -160,7 +163,7 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q, * @param region: where to allocate result. * @param scratch: where to allocate temporary data. * @param no_partial: if true, only complete messages and not a partial - * one (with only the start of the CNAME chain and not the rest). + * one (with only the start of the CNAME chain and not the rest). * @return new response message (alloced in region, rrsets do not have IDs). * or NULL on error or if not found in cache. * TTLs are made relative to the current time. diff --git a/services/localzone.c b/services/localzone.c index 492bb83042c1..18407832ff41 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -41,7 +41,6 @@ #include "config.h" #include "services/localzone.h" #include "sldns/str2wire.h" -#include "sldns/sbuffer.h" #include "util/regional.h" #include "util/config_file.h" #include "util/data/dname.h" @@ -395,9 +394,30 @@ rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, return 1; } -/** find a data node by exact name */ -static struct local_data* -lz_find_node(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs) +/** Delete RR from local-zone RRset, wastes memory as the deleted RRs cannot be + * free'd (regionally alloc'd) */ +int +local_rrset_remove_rr(struct packed_rrset_data* pd, size_t index) +{ + log_assert(pd->count > 0); + if(index >= pd->count) { + log_warn("Trying to remove RR with out of bound index"); + return 0; + } + if(index + 1 < pd->count) { + /* not removing last element */ + size_t nexti = index + 1; + size_t num = pd->count - nexti; + memmove(pd->rr_len+index, pd->rr_len+nexti, sizeof(*pd->rr_len)*num); + memmove(pd->rr_ttl+index, pd->rr_ttl+nexti, sizeof(*pd->rr_ttl)*num); + memmove(pd->rr_data+index, pd->rr_data+nexti, sizeof(*pd->rr_data)*num); + } + pd->count--; + return 1; +} + +struct local_data* +local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs) { struct local_data key; key.node.key = &key; @@ -412,7 +432,7 @@ static int lz_find_create_node(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs, struct local_data** res) { - struct local_data* ld = lz_find_node(z, nm, nmlen, nmlabs); + struct local_data* ld = local_zone_find_data(z, nm, nmlen, nmlabs); if(!ld) { /* create a domain name to store rr. */ ld = (struct local_data*)regional_alloc_zero(z->region, @@ -443,42 +463,19 @@ lz_find_create_node(struct local_zone* z, uint8_t* nm, size_t nmlen, return 1; } -/** enter data RR into auth zone */ -static int -lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) +int +local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen, + int nmlabs, uint16_t rrtype, uint16_t rrclass, time_t ttl, + uint8_t* rdata, size_t rdata_len, const char* rrstr) { - uint8_t* nm; - size_t nmlen; - int nmlabs; struct local_data* node; struct local_rrset* rrset; struct packed_rrset_data* pd; - uint16_t rrtype = 0, rrclass = 0; - time_t ttl = 0; - uint8_t rr[LDNS_RR_BUF_SIZE]; - uint8_t* rdata; - size_t 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; - } - log_assert(z->dclass == rrclass); - if((z->type == local_zone_redirect || - z->type == local_zone_inform_redirect) && - query_dname_compare(z->name, nm) != 0) { - log_err("local-data in redirect zone must reside at top of zone" - ", not at %s", rrstr); - free(nm); - return 0; - } - nmlabs = dname_count_size_labels(nm, &nmlen); + if(!lz_find_create_node(z, nm, nmlen, nmlabs, &node)) { - free(nm); return 0; } log_assert(node); - free(nm); /* Reject it if we would end up having CNAME and other data (including * another CNAME) for a redirect zone. */ @@ -520,6 +517,39 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) return rrset_insert_rr(z->region, pd, rdata, rdata_len, ttl, rrstr); } +/** enter data RR into auth zone */ +int +lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) +{ + uint8_t* nm; + size_t nmlen; + int nmlabs, ret; + uint16_t rrtype = 0, rrclass = 0; + time_t ttl = 0; + uint8_t rr[LDNS_RR_BUF_SIZE]; + uint8_t* rdata; + size_t 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; + } + log_assert(z->dclass == rrclass); + if((z->type == local_zone_redirect || + z->type == local_zone_inform_redirect) && + query_dname_compare(z->name, nm) != 0) { + log_err("local-data in redirect zone must reside at top of zone" + ", not at %s", rrstr); + free(nm); + return 0; + } + nmlabs = dname_count_size_labels(nm, &nmlen); + ret = local_zone_enter_rr(z, nm, nmlen, nmlabs, rrtype, rrclass, ttl, + rdata, rdata_len, rrstr); + free(nm); + return ret; +} + /** enter a data RR into auth data; a zone for it must exist */ static int lz_enter_rr_str(struct local_zones* zones, const char* rr) @@ -823,12 +853,12 @@ int local_zone_enter_defaults(struct local_zones* zones, struct config_file* cfg log_err("out of memory adding default zone"); return 0; } - /* test. zone (RFC 7686) */ + /* test. zone (RFC 6761) */ if(!add_empty_default(zones, cfg, "test.")) { log_err("out of memory adding default zone"); return 0; } - /* invalid. zone (RFC 7686) */ + /* invalid. zone (RFC 6761) */ if(!add_empty_default(zones, cfg, "invalid.")) { log_err("out of memory adding default zone"); return 0; @@ -1113,6 +1143,22 @@ local_zones_find(struct local_zones* zones, return (struct local_zone*)rbtree_search(&zones->ztree, &key); } +struct local_zone* +local_zones_find_le(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + int* exact) +{ + struct local_zone key; + rbnode_type *node; + key.node.key = &key; + key.dclass = dclass; + key.name = name; + key.namelen = len; + key.namelabs = labs; + *exact = rbtree_find_less_equal(&zones->ztree, &key, &node); + return (struct local_zone*)node; +} + /** print all RRsets in local zone */ static void local_zone_out(struct local_zone* z) @@ -1309,8 +1355,7 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list, return result; } -/** answer local data match */ -static int +int local_data_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, @@ -1362,16 +1407,69 @@ local_data_answer(struct local_zone* z, struct module_env* env, lz_type == local_zone_inform_redirect) && qinfo->qtype != LDNS_RR_TYPE_CNAME && lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + uint8_t* ctarget; + size_t ctargetlen = 0; + 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)); + 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; + get_cname_target(lr->rrset, &ctarget, &ctargetlen); + if(!ctargetlen) + return 0; /* invalid cname */ + if(dname_is_wild(ctarget)) { + /* synthesize cname target */ + struct packed_rrset_data* d; + /* -3 for wildcard label and root label from qname */ + size_t newtargetlen = qinfo->qname_len + ctargetlen - 3; + + log_assert(ctargetlen >= 3); + log_assert(qinfo->qname_len >= 1); + + if(newtargetlen > LDNS_MAX_DOMAINLEN) { + qinfo->local_alias = NULL; + local_error_encode(qinfo, env, edns, repinfo, + buf, temp, LDNS_RCODE_YXDOMAIN, + (LDNS_RCODE_YXDOMAIN|BIT_AA)); + return 1; + } + memset(&qinfo->local_alias->rrset->entry, 0, + sizeof(qinfo->local_alias->rrset->entry)); + qinfo->local_alias->rrset->entry.key = + qinfo->local_alias->rrset; + qinfo->local_alias->rrset->entry.hash = + rrset_key_hash(&qinfo->local_alias->rrset->rk); + d = (struct packed_rrset_data*)regional_alloc_zero(temp, + sizeof(struct packed_rrset_data) + sizeof(size_t) + + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t) + + newtargetlen); + if(!d) + return 0; /* out of memory */ + qinfo->local_alias->rrset->entry.data = d; + d->ttl = 0; /* 0 for synthesized CNAME TTL */ + d->count = 1; + d->rrsig_count = 0; + d->trust = rrset_trust_ans_noAA; + d->rr_len = (size_t*)((uint8_t*)d + + sizeof(struct packed_rrset_data)); + d->rr_len[0] = newtargetlen + sizeof(uint16_t); + packed_rrset_ptr_fixup(d); + d->rr_ttl[0] = d->ttl; + sldns_write_uint16(d->rr_data[0], newtargetlen); + /* write qname */ + memmove(d->rr_data[0] + sizeof(uint16_t), qinfo->qname, + qinfo->qname_len - 1); + /* write cname target wilcard wildcard label */ + memmove(d->rr_data[0] + sizeof(uint16_t) + + qinfo->qname_len - 1, ctarget + 2, + ctargetlen - 2); + } return 1; } if(lz_type == local_zone_redirect || @@ -1416,26 +1514,15 @@ local_zone_does_not_cover(struct local_zone* z, struct query_info* qinfo, return (lr == NULL); } -/** - * 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 repinfo: source address for checks. may be NULL. - * @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 module_env* env, +int +local_zones_zone_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct local_data* ld, enum localzone_type lz_type) { - if(lz_type == local_zone_deny || lz_type == local_zone_inform_deny) { + if(lz_type == local_zone_deny || + lz_type == local_zone_always_deny || + lz_type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ sldns_buffer_clear(buf); sldns_buffer_flip(buf); @@ -1448,7 +1535,8 @@ lz_zone_answer(struct local_zone* z, struct module_env* env, } else if(lz_type == local_zone_static || lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect || - lz_type == local_zone_always_nxdomain) { + lz_type == local_zone_always_nxdomain || + lz_type == local_zone_always_nodata) { /* for static, reply nodata or nxdomain * for redirect, reply nodata */ /* no additional section processing, @@ -1457,7 +1545,8 @@ lz_zone_answer(struct local_zone* z, struct module_env* env, * or using closest match for returning delegation downwards */ int rcode = (ld || lz_type == local_zone_redirect || - lz_type == local_zone_inform_redirect)? + lz_type == local_zone_inform_redirect || + lz_type == local_zone_always_nodata)? LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; if(z->soa) return local_encode(qinfo, env, edns, repinfo, buf, temp, @@ -1640,6 +1729,8 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, if(lzt != local_zone_always_refuse && lzt != local_zone_always_transparent && lzt != local_zone_always_nxdomain + && lzt != local_zone_always_nodata + && lzt != local_zone_always_deny && local_data_answer(z, env, qinfo, edns, repinfo, buf, temp, labs, &ld, lzt, tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); @@ -1647,7 +1738,7 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, * a local alias. */ return !qinfo->local_alias; } - r = lz_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, ld, lzt); + r = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); return r && !qinfo->local_alias; /* see above */ } @@ -1669,7 +1760,10 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_always_transparent: return "always_transparent"; case local_zone_always_refuse: return "always_refuse"; case local_zone_always_nxdomain: return "always_nxdomain"; + case local_zone_always_nodata: return "always_nodata"; + case local_zone_always_deny: return "always_deny"; case local_zone_noview: return "noview"; + case local_zone_invalid: return "invalid"; } return "badtyped"; } @@ -1700,6 +1794,10 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_always_refuse; else if(strcmp(type, "always_nxdomain") == 0) *t = local_zone_always_nxdomain; + else if(strcmp(type, "always_nodata") == 0) + *t = local_zone_always_nodata; + else if(strcmp(type, "always_deny") == 0) + *t = local_zone_always_deny; else if(strcmp(type, "noview") == 0) *t = local_zone_noview; else if(strcmp(type, "nodefault") == 0) @@ -1843,7 +1941,7 @@ del_empty_term(struct local_zone* z, struct local_data* d, return; dname_remove_label(&name, &len); labs--; - d = lz_find_node(z, name, len, labs); + d = local_zone_find_data(z, name, len, labs); } } @@ -1876,7 +1974,7 @@ void local_zones_del_data(struct local_zones* zones, z = local_zones_lookup(zones, name, len, labs, dclass, LDNS_RR_TYPE_DS); if(z) { lock_rw_wrlock(&z->lock); - d = lz_find_node(z, name, len, labs); + d = local_zone_find_data(z, name, len, labs); if(d) { del_local_rrset(d, LDNS_RR_TYPE_DS); del_empty_term(z, d, name, len, labs); @@ -1897,7 +1995,7 @@ void local_zones_del_data(struct local_zones* zones, lock_rw_unlock(&zones->lock); /* find the domain */ - d = lz_find_node(z, name, len, labs); + d = local_zone_find_data(z, name, len, labs); if(d) { /* no memory recycling for zone deletions ... */ d->rrsets = NULL; diff --git a/services/localzone.h b/services/localzone.h index 1d6caeff2c74..bb35939366a7 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -46,6 +46,7 @@ #include "util/storage/dnstree.h" #include "util/module.h" #include "services/view.h" +#include "sldns/sbuffer.h" struct packed_rrset_data; struct ub_packed_rrset_key; struct regional; @@ -91,8 +92,14 @@ enum localzone_type { local_zone_always_refuse, /** answer with nxdomain, even when there is local data */ local_zone_always_nxdomain, + /** answer with noerror/nodata, even when there is local data */ + local_zone_always_nodata, + /** drop query, even when there is local data */ + local_zone_always_deny, /** answer not from the view, but global or no-answer */ - local_zone_noview + local_zone_noview, + /** Invalid type, cannot be used to generate answer */ + local_zone_invalid }; /** @@ -310,6 +317,25 @@ int local_zones_answer(struct local_zones* zones, struct module_env* env, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags, struct view* view); +/** + * Answer using the local zone only (not local data used). + * @param z: zone for query. + * @param env: module environment. + * @param qinfo: query. + * @param edns: edns from query. + * @param repinfo: source address for checks. may be NULL. + * @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. + */ +int +local_zones_zone_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, + struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, + struct local_data* ld, enum localzone_type lz_type); + /** * Parse the string into localzone type. * @@ -341,6 +367,22 @@ struct local_zone* local_zones_find(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass); /** + * Find zone that with exactly or smaller name/class + * 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 exact: 1 on return is this is an exact match. + * @return the exact or smaller local_zone or NULL. + */ +struct local_zone* +local_zones_find_le(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + int* exact); + +/** * Add a new zone. Caller must hold the zones lock. * Adjusts the other zones as well (parent pointers) after insertion. * The zone must NOT exist (returns NULL and logs error). @@ -474,6 +516,15 @@ 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); /** + * Remove RR from rrset that is created using localzone's rrset_insert_rr. + * @param pd: the RRset containing the RR to remove + * @param index: index of RR to remove + * @return: 1 on success; 0 otherwise. + */ +int +local_rrset_remove_rr(struct packed_rrset_data* pd, size_t index); + +/** * 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. @@ -501,6 +552,10 @@ enum respip_action { respip_always_refuse = local_zone_always_refuse, /** answer with 'no such domain' response */ respip_always_nxdomain = local_zone_always_nxdomain, + /** answer with nodata response */ + respip_always_nodata = local_zone_always_nodata, + /** answer with nodata response */ + respip_always_deny = local_zone_always_deny, /* The rest of the values are only possible as * access-control-tag-action */ @@ -513,6 +568,64 @@ enum respip_action { respip_transparent = local_zone_transparent, /** gives response data (if any), else nodata answer. */ respip_typetransparent = local_zone_typetransparent, + /** type invalid */ + respip_invalid = local_zone_invalid, }; +/** + * Get local data from local zone and encode answer. + * @param z: local zone to use + * @param env: module env + * @param qinfo: qinfo + * @param edns: edns data, for message encoding + * @param repinfo: reply info, for message encoding + * @param buf: commpoint buffer + * @param temp: scratchpad region + * @param labs: number of labels in qname + * @param ldp: where to store local data + * @param lz_type: type of local zone + * @param tag: matching tag index + * @param tag_datas: alc specific tag data list + * @param tag_datas_size: size of tag_datas + * @param tagname: list of names of tags, for logging purpose + * @param num_tags: number of tags + * @return 1 on success + */ +int +local_data_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, + struct comm_reply* repinfo, 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); + +/** + * Add RR to local zone. + * @param z: local zone to add RR to + * @param nm: dname of RR + * @param nmlen: length of nm + * @param nmlabs: number of labels of nm + * @param rrtype: RR type + * @param rrclass: RR class + * @param ttl: TTL of RR to add + * @param rdata: RDATA of RR to add + * @param rdata_len: length of rdata + * @param rrstr: RR in string format, for logging + * @return: 1 on success + */ +int +local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen, + int nmlabs, uint16_t rrtype, uint16_t rrclass, time_t ttl, + uint8_t* rdata, size_t rdata_len, const char* rrstr); + +/** + * Find a data node by exact name for a local zone + * @param z: local_zone containing data tree + * @param nm: name of local-data element to find + * @param nmlen: length of nm + * @param nmlabs: labs of nm + * @return local_data on exact match, NULL otherwise. + */ +struct local_data* +local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs); #endif /* SERVICES_LOCALZONE_H */ diff --git a/services/mesh.c b/services/mesh.c index d4f814d5a925..9114ef4c4e2d 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -46,6 +46,7 @@ #include "services/mesh.h" #include "services/outbound_list.h" #include "services/cache/dns.h" +#include "services/cache/rrset.h" #include "util/log.h" #include "util/net_help.h" #include "util/module.h" @@ -127,7 +128,7 @@ 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 @@ -250,6 +251,7 @@ mesh_create(struct module_stack* stack, struct module_env* env) mesh->num_forever_states = 0; mesh->stats_jostled = 0; mesh->stats_dropped = 0; + mesh->ans_expired = 0; mesh->max_reply_states = env->cfg->num_queries_per_thread; mesh->max_forever_states = (mesh->max_reply_states+1)/2; #ifndef S_SPLINT_S @@ -345,6 +347,97 @@ int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf) return 0; } +struct dns_msg* +mesh_serve_expired_lookup(struct module_qstate* qstate, + struct query_info* lookup_qinfo) +{ + hashvalue_type h; + struct lruhash_entry* e; + struct dns_msg* msg; + struct reply_info* data; + struct msgreply_entry* key; + time_t timenow = *qstate->env->now; + int must_validate = (!(qstate->query_flags&BIT_CD) + || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; + /* Lookup cache */ + h = query_info_hash(lookup_qinfo, qstate->query_flags); + e = slabhash_lookup(qstate->env->msg_cache, h, lookup_qinfo, 0); + if(!e) return NULL; + + key = (struct msgreply_entry*)e->key; + data = (struct reply_info*)e->data; + msg = tomsg(qstate->env, &key->key, data, qstate->region, timenow, + qstate->env->cfg->serve_expired, qstate->env->scratch); + if(!msg) + goto bail_out; + + /* Check CNAME chain (if any) + * This is part of tomsg above; no need to check now. */ + + /* Check security status of the cached answer. + * tomsg above has a subset of these checks, so we are leaving + * these as is. + * In case of bogus or revalidation we don't care to reply here. */ + if(must_validate && (msg->rep->security == sec_status_bogus || + msg->rep->security == sec_status_secure_sentinel_fail)) { + verbose(VERB_ALGO, "Serve expired: bogus answer found in cache"); + goto bail_out; + } else if(msg->rep->security == sec_status_unchecked && must_validate) { + verbose(VERB_ALGO, "Serve expired: unchecked entry needs " + "validation"); + goto bail_out; /* need to validate cache entry first */ + } else if(msg->rep->security == sec_status_secure && + !reply_all_rrsets_secure(msg->rep) && must_validate) { + verbose(VERB_ALGO, "Serve expired: secure entry" + " changed status"); + goto bail_out; /* rrset changed, re-verify */ + } + + lock_rw_unlock(&e->lock); + return msg; + +bail_out: + lock_rw_unlock(&e->lock); + return NULL; +} + + +/** Init the serve expired data structure */ +static int +mesh_serve_expired_init(struct mesh_state* mstate, int timeout) +{ + struct timeval t; + + /* Create serve_expired_data if not there yet */ + if(!mstate->s.serve_expired_data) { + mstate->s.serve_expired_data = (struct serve_expired_data*) + regional_alloc_zero( + mstate->s.region, sizeof(struct serve_expired_data)); + if(!mstate->s.serve_expired_data) + return 0; + } + + /* Don't overwrite the function if already set */ + mstate->s.serve_expired_data->get_cached_answer = + mstate->s.serve_expired_data->get_cached_answer? + mstate->s.serve_expired_data->get_cached_answer: + mesh_serve_expired_lookup; + + /* In case this timer already popped, start it again */ + if(!mstate->s.serve_expired_data->timer) { + mstate->s.serve_expired_data->timer = comm_timer_create( + mstate->s.env->worker_base, mesh_serve_expired_callback, mstate); + if(!mstate->s.serve_expired_data->timer) + return 0; +#ifndef S_SPLINT_S + t.tv_sec = timeout/1000; + t.tv_usec = (timeout%1000)*1000; +#endif + comm_timer_set(mstate->s.serve_expired_data->timer, &t); + } + return 1; +} + void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, uint16_t qid) @@ -354,6 +447,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, int was_detached = 0; int was_noreply = 0; int added = 0; + int timeout = mesh->env->cfg->serve_expired? + mesh->env->cfg->serve_expired_client_timeout:0; struct sldns_buffer* r_buffer = rep->c->buffer; if(rep->c->tcp_req_info) { r_buffer = rep->c->tcp_req_info->spool_buffer; @@ -366,7 +461,7 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, verbose(VERB_ALGO, "Too many queries. dropping " "incoming query."); comm_point_drop_reply(rep); - mesh->stats_dropped ++; + mesh->stats_dropped++; return; } /* for this new reply state, the reply address is free, @@ -376,8 +471,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, if(mesh->num_reply_addrs > mesh->max_reply_states*16) { verbose(VERB_ALGO, "Too many requests queued. " "dropping incoming query."); - mesh->stats_dropped++; comm_point_drop_reply(rep); + mesh->stats_dropped++; return; } } @@ -427,23 +522,16 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, mesh->num_detached_states++; added = 1; } - if(!s->reply_list && !s->cb_list && s->super_set.count == 0) - was_detached = 1; - if(!s->reply_list && !s->cb_list) + if(!s->reply_list && !s->cb_list) { was_noreply = 1; + if(s->super_set.count == 0) { + was_detached = 1; + } + } /* add reply to s */ if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) { - log_err("mesh_new_client: out of memory; SERVFAIL"); - servfail_mem: - if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, &s->s, - NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch)) - edns->opt_list = NULL; - error_encode(r_buffer, LDNS_RCODE_SERVFAIL, - qinfo, qid, qflags, edns); - comm_point_send_reply(rep); - if(added) - mesh_state_delete(&s->s); - return; + log_err("mesh_new_client: out of memory; SERVFAIL"); + goto servfail_mem; } if(rep->c->tcp_req_info) { if(!tcp_req_info_add_meshstate(rep->c->tcp_req_info, mesh, s)) { @@ -451,6 +539,11 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, goto servfail_mem; } } + /* add serve expired timer if required and not already there */ + if(timeout && !mesh_serve_expired_init(s, timeout)) { + log_err("mesh_new_client: out of memory initializing serve expired"); + goto servfail_mem; + } /* update statistics */ if(was_detached) { log_assert(mesh->num_detached_states > 0); @@ -475,6 +568,18 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, } if(added) mesh_run(mesh, s, module_event_new, NULL); + return; + +servfail_mem: + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, &s->s, + NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch)) + edns->opt_list = NULL; + error_encode(r_buffer, LDNS_RCODE_SERVFAIL, + qinfo, qid, qflags, edns); + comm_point_send_reply(rep); + if(added) + mesh_state_delete(&s->s); + return; } int @@ -484,6 +589,8 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, { struct mesh_state* s = NULL; int unique = unique_mesh_state(edns->opt_list, mesh->env); + int timeout = mesh->env->cfg->serve_expired? + mesh->env->cfg->serve_expired_client_timeout:0; int was_detached = 0; int was_noreply = 0; int added = 0; @@ -522,15 +629,21 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, mesh->num_detached_states++; added = 1; } - if(!s->reply_list && !s->cb_list && s->super_set.count == 0) - was_detached = 1; - if(!s->reply_list && !s->cb_list) + if(!s->reply_list && !s->cb_list) { was_noreply = 1; + if(s->super_set.count == 0) { + was_detached = 1; + } + } /* add reply to s */ if(!mesh_state_add_cb(s, edns, buf, cb, cb_arg, qid, qflags)) { - if(added) - mesh_state_delete(&s->s); - return 0; + if(added) + mesh_state_delete(&s->s); + return 0; + } + /* add serve expired timer if not already there */ + if(timeout && !mesh_serve_expired_init(s, timeout)) { + return 0; } /* update statistics */ if(was_detached) { @@ -546,15 +659,6 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, return 1; } -static void mesh_schedule_prefetch(struct mesh_area* mesh, - struct query_info* qinfo, uint16_t qflags, time_t leeway, int run); - -void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, - uint16_t qflags, time_t leeway) -{ - mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1); -} - /* Internal backend routine of mesh_new_prefetch(). It takes one additional * parameter, 'run', which controls whether to run the prefetch state * immediately. When this function is called internally 'run' could be @@ -631,6 +735,12 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh, mesh_run(mesh, s, module_event_new, NULL); } +void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, + uint16_t qflags, time_t leeway) +{ + mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1); +} + void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, struct comm_reply* reply, int what) { @@ -703,6 +813,7 @@ 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.serve_expired_data = NULL; mstate->s.no_cache_lookup = 0; mstate->s.no_cache_store = 0; mstate->s.need_refetch = 0; @@ -742,6 +853,11 @@ mesh_state_cleanup(struct mesh_state* mstate) if(!mstate) return; mesh = mstate->s.env->mesh; + /* Stop and delete the serve expired timer */ + if(mstate->s.serve_expired_data && mstate->s.serve_expired_data->timer) { + comm_timer_delete(mstate->s.serve_expired_data->timer); + mstate->s.serve_expired_data->timer = NULL; + } /* drop unsent replies */ if(!mstate->replies_sent) { struct mesh_reply* rep = mstate->reply_list; @@ -752,6 +868,7 @@ mesh_state_cleanup(struct mesh_state* mstate) mstate->reply_list = NULL; for(; rep; rep=rep->next) { comm_point_drop_reply(&rep->query_reply); + log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; } while((cb = mstate->cb_list)!=NULL) { @@ -759,6 +876,7 @@ mesh_state_cleanup(struct mesh_state* mstate) fptr_ok(fptr_whitelist_mesh_cb(cb->cb)); (*cb->cb)(cb->cb_arg, LDNS_RCODE_SERVFAIL, NULL, sec_status_unchecked, NULL, 0); + log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; } } @@ -826,7 +944,7 @@ find_in_subsub(struct mesh_state* m, struct mesh_state* tofind, size_t *c) } /** find cycle for already looked up mesh_state */ -static int +static int mesh_detect_cycle_found(struct module_qstate* qstate, struct mesh_state* dep_m) { struct mesh_state* cyc_m = qstate->mesh_info; @@ -1038,6 +1156,7 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, } } free(reason); + log_assert(m->s.env->mesh->num_reply_addrs > 0); m->s.env->mesh->num_reply_addrs--; } @@ -1139,6 +1258,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, comm_point_send_reply(&r->query_reply); } /* account */ + log_assert(m->s.env->mesh->num_reply_addrs > 0); m->s.env->mesh->num_reply_addrs--; end_time = *m->s.env->now_tv; timeval_subtract(&duration, &end_time, &r->start_time); @@ -1164,37 +1284,76 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, void mesh_query_done(struct mesh_state* mstate) { - struct mesh_reply* r; + struct mesh_reply* r, *reply_list = NULL; struct mesh_reply* prev = NULL; struct sldns_buffer* prev_buffer = NULL; struct mesh_cb* c; struct reply_info* rep = (mstate->s.return_msg? mstate->s.return_msg->rep:NULL); - if((mstate->s.return_rcode == LDNS_RCODE_SERVFAIL || - (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) + /* No need for the serve expired timer anymore; we are going to reply. */ + if(mstate->s.serve_expired_data) { + comm_timer_delete(mstate->s.serve_expired_data->timer); + mstate->s.serve_expired_data->timer = NULL; + } + if(mstate->s.return_rcode == LDNS_RCODE_SERVFAIL || + (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) { + /* we are SERVFAILing; check for expired asnwer here */ + mesh_serve_expired_callback(mstate); + if((mstate->reply_list || mstate->cb_list) && mstate->s.env->cfg->log_servfail && !mstate->s.env->cfg->val_log_squelch) { - char* err = errinf_to_str_servfail(&mstate->s); - if(err) - log_err("%s", err); - free(err); + char* err = errinf_to_str_servfail(&mstate->s); + if(err) + log_err("%s", err); + free(err); + } } - for(r = mstate->reply_list; r; r = r->next) { + if(mstate->reply_list) { + /* set the reply_list to NULL during the mesh_query_done + * processing, so that calls back into the mesh from + * tcp_req_info (deciding to drop the reply and thus + * unregister the mesh_reply from the mstate) are stopped + * because the list is empty. + * The mstate is then likely not a reply_state, and maybe + * also a detached_state. + */ + reply_list = mstate->reply_list; + mstate->reply_list = NULL; + if(!mstate->reply_list && !mstate->cb_list) { + /* was a reply state, not anymore */ + log_assert(mstate->s.env->mesh->num_reply_states > 0); + mstate->s.env->mesh->num_reply_states--; + } + if(!mstate->reply_list && !mstate->cb_list && + mstate->super_set.count == 0) + mstate->s.env->mesh->num_detached_states++; + } + for(r = reply_list; r; r = r->next) { /* 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, + respip_inform_print(mstate->s.respip_action_info, r->qname, mstate->s.qinfo.qtype, mstate->s.qinfo.qclass, r->local_alias, &r->query_reply); + if(mstate->s.env->cfg->stat_extended && + mstate->s.respip_action_info->rpz_used) { + if(mstate->s.respip_action_info->rpz_disabled) + mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION]++; + if(mstate->s.respip_action_info->rpz_cname_override) + mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; + else + mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action( + mstate->s.respip_action_info->action)]++; + } } /* 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) + if(mstate->s.is_drop) { comm_point_drop_reply(&r->query_reply); - else { + } else { struct sldns_buffer* r_buffer = r->query_reply.c->buffer; if(r->query_reply.c->tcp_req_info) { r_buffer = r->query_reply.c->tcp_req_info->spool_buffer; @@ -1216,6 +1375,7 @@ void mesh_query_done(struct mesh_state* mstate) * changed, eg. by adds from the callback routine */ if(!mstate->reply_list && mstate->cb_list && !c->next) { /* was a reply state, not anymore */ + log_assert(mstate->s.env->mesh->num_reply_states > 0); mstate->s.env->mesh->num_reply_states--; } mstate->cb_list = c->next; @@ -1581,7 +1741,9 @@ mesh_stats_clear(struct mesh_area* mesh) timehist_clear(mesh->histogram); mesh->ans_secure = 0; mesh->ans_bogus = 0; - memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*16); + mesh->ans_expired = 0; + memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*UB_STATS_RCODE_NUM); + memset(&mesh->rpz_action[0], 0, sizeof(size_t)*UB_STATS_RPZ_ACTION_NUM); mesh->ans_nodata = 0; } @@ -1647,6 +1809,7 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, if(prev) prev->next = n->next; else m->reply_list = n->next; /* delete it, but allocated in m region */ + log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; /* prev = prev; */ @@ -1667,3 +1830,176 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, mesh->num_reply_states--; } } + + +static int +apply_respip_action(struct module_qstate* qstate, + const struct query_info* qinfo, struct respip_client_info* cinfo, + struct respip_action_info* actinfo, struct reply_info* rep, + struct ub_packed_rrset_key** alias_rrset, + struct reply_info** encode_repp, struct auth_zones* az) +{ + if(qinfo->qtype != LDNS_RR_TYPE_A && + qinfo->qtype != LDNS_RR_TYPE_AAAA && + qinfo->qtype != LDNS_RR_TYPE_ANY) + return 1; + + if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo, + alias_rrset, 0, qstate->region, az)) + return 0; + + /* xxx_deny actions mean dropping the reply, unless the original reply + * was redirected to response-ip data. */ + if((actinfo->action == respip_deny || + actinfo->action == respip_inform_deny) && + *encode_repp == rep) + *encode_repp = NULL; + + return 1; +} + +void +mesh_serve_expired_callback(void* arg) +{ + struct mesh_state* mstate = (struct mesh_state*) arg; + struct module_qstate* qstate = &mstate->s; + struct mesh_reply* r; + struct mesh_area* mesh = qstate->env->mesh; + struct dns_msg* msg; + struct mesh_cb* c; + struct mesh_reply* prev = NULL; + struct sldns_buffer* prev_buffer = NULL; + struct sldns_buffer* r_buffer = NULL; + struct reply_info* partial_rep = NULL; + struct ub_packed_rrset_key* alias_rrset = NULL; + struct reply_info* encode_rep = NULL; + struct respip_action_info actinfo; + struct query_info* lookup_qinfo = &qstate->qinfo; + struct query_info qinfo_tmp; + int must_validate = (!(qstate->query_flags&BIT_CD) + || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; + if(!qstate->serve_expired_data) return; + verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data"); + comm_timer_delete(qstate->serve_expired_data->timer); + qstate->serve_expired_data->timer = NULL; + if(qstate->blacklist || qstate->no_cache_lookup || qstate->is_drop) { + verbose(VERB_ALGO, + "Serve expired: Not allowed to look into cache for stale"); + return; + } + /* The following while is used instead of the `goto lookup_cache` + * like in the worker. */ + while(1) { + fptr_ok(fptr_whitelist_serve_expired_lookup( + qstate->serve_expired_data->get_cached_answer)); + msg = qstate->serve_expired_data->get_cached_answer(qstate, + lookup_qinfo); + if(!msg) + return; + /* Reset these in case we pass a second time from here. */ + encode_rep = msg->rep; + memset(&actinfo, 0, sizeof(actinfo)); + actinfo.action = respip_none; + alias_rrset = NULL; + if((mesh->use_response_ip || mesh->use_rpz) && + !partial_rep && !apply_respip_action(qstate, &qstate->qinfo, + qstate->client_info, &actinfo, msg->rep, &alias_rrset, &encode_rep, + qstate->env->auth_zones)) { + return; + } else if(partial_rep && + !respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep, + qstate->client_info, must_validate, &encode_rep, qstate->region, + qstate->env->auth_zones)) { + return; + } + if(!encode_rep || alias_rrset) { + if(!encode_rep) { + /* Needs drop */ + return; + } else { + /* A partial CNAME chain is found. */ + partial_rep = encode_rep; + } + } + /* We've found a partial reply ending with an + * alias. Replace the lookup qinfo for the + * alias target and lookup the cache again to + * (possibly) complete the reply. As we're + * passing the "base" reply, there will be no + * more alias chasing. */ + if(partial_rep) { + memset(&qinfo_tmp, 0, sizeof(qinfo_tmp)); + get_cname_target(alias_rrset, &qinfo_tmp.qname, + &qinfo_tmp.qname_len); + if(!qinfo_tmp.qname) { + log_err("Serve expired: unexpected: invalid answer alias"); + return; + } + qinfo_tmp.qtype = qstate->qinfo.qtype; + qinfo_tmp.qclass = qstate->qinfo.qclass; + lookup_qinfo = &qinfo_tmp; + continue; + } + break; + } + + if(verbosity >= VERB_ALGO) + log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep); + + r = mstate->reply_list; + mstate->reply_list = NULL; + if(!mstate->reply_list && !mstate->cb_list) { + log_assert(mesh->num_reply_states > 0); + mesh->num_reply_states--; + if(mstate->super_set.count == 0) { + mesh->num_detached_states++; + } + } + for(; r; r = r->next) { + /* If address info is returned, it means the action should be an + * 'inform' variant and the information should be logged. */ + if(actinfo.addrinfo) { + respip_inform_print(&actinfo, r->qname, + qstate->qinfo.qtype, qstate->qinfo.qclass, + r->local_alias, &r->query_reply); + + if(qstate->env->cfg->stat_extended && actinfo.rpz_used) { + if(actinfo.rpz_disabled) + qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION]++; + if(actinfo.rpz_cname_override) + qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; + else + qstate->env->mesh->rpz_action[ + respip_action_to_rpz_action(actinfo.action)]++; + } + } + + r_buffer = r->query_reply.c->buffer; + if(r->query_reply.c->tcp_req_info) + r_buffer = r->query_reply.c->tcp_req_info->spool_buffer; + mesh_send_reply(mstate, LDNS_RCODE_NOERROR, msg->rep, + r, r_buffer, prev, prev_buffer); + if(r->query_reply.c->tcp_req_info) + tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate); + prev = r; + prev_buffer = r_buffer; + + /* Account for each reply sent. */ + mesh->ans_expired++; + + } + while((c = mstate->cb_list) != NULL) { + /* take this cb off the list; so that the list can be + * changed, eg. by adds from the callback routine */ + if(!mstate->reply_list && mstate->cb_list && !c->next) { + /* was a reply state, not anymore */ + log_assert(qstate->env->mesh->num_reply_states > 0); + qstate->env->mesh->num_reply_states--; + } + mstate->cb_list = c->next; + if(!mstate->reply_list && !mstate->cb_list && + mstate->super_set.count == 0) + qstate->env->mesh->num_detached_states++; + mesh_do_callback(mstate, LDNS_RCODE_NOERROR, msg->rep, c); + } +} diff --git a/services/mesh.h b/services/mesh.h index a2622844bbf0..df2972ac3306 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -51,6 +51,8 @@ #include "util/data/msgparse.h" #include "util/module.h" #include "services/modstack.h" +#include "services/rpz.h" +#include "libunbound/unbound.h" struct sldns_buffer; struct mesh_state; struct mesh_reply; @@ -110,6 +112,8 @@ struct mesh_area { size_t stats_jostled; /** stats, cumulative number of incoming client msgs dropped */ size_t stats_dropped; + /** stats, number of expired replies sent */ + size_t ans_expired; /** number of replies sent */ size_t replies_sent; /** sum of waiting times for the replies */ @@ -121,9 +125,11 @@ struct mesh_area { /** (extended stats) bogus replies */ size_t ans_bogus; /** (extended stats) rcodes in replies */ - size_t ans_rcode[16]; + size_t ans_rcode[UB_STATS_RCODE_NUM]; /** (extended stats) rcode nodata in replies */ size_t ans_nodata; + /** (extended stats) type of applied RPZ action */ + size_t rpz_action[UB_STATS_RPZ_ACTION_NUM]; /** backup of query if other operations recurse and need the * network buffers */ @@ -142,6 +148,11 @@ struct mesh_area { struct mesh_state* jostle_last; /** timeout for jostling. if age is lower, it does not get jostled. */ struct timeval jostle_max; + + /** If we need to use response ip (value passed from daemon)*/ + int use_response_ip; + /** If we need to use RPZ (value passed from daemon) */ + int use_rpz; }; /** @@ -643,4 +654,22 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, struct comm_point* cp); +/** Callback for when the serve expired client timer has run out. Tries to + * find an expired answer in the cache and reply that to the client. + * @param arg: the argument passed to the callback. + */ +void mesh_serve_expired_callback(void* arg); + +/** + * Try to get a (expired) cached answer. + * This needs to behave like the worker's answer_from_cache() in order to have + * the same behavior as when replying from cache. + * @param qstate: the module qstate. + * @param lookup_qinfo: the query info to look for in the cache. + * @return dns_msg if a cached answer was found, otherwise NULL. + */ +struct dns_msg* +mesh_serve_expired_lookup(struct module_qstate* qstate, + struct query_info* lookup_qinfo); + #endif /* SERVICES_MESH_H */ diff --git a/services/outside_network.c b/services/outside_network.c index f865f13c1390..80b1f12454d6 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -293,6 +293,9 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len) /* open socket */ s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss); + if(s == -1) + return 0; + if(!pick_outgoing_tcp(w, s)) return 0; @@ -1971,7 +1974,6 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, sq->pending = NULL; /* removed after callback */ if(error == NETEVENT_TIMEOUT) { - int rto = 0; if(sq->status == serviced_query_UDP_EDNS && sq->last_rtt < 5000) { /* fallback to 1480/1280 */ sq->status = serviced_query_UDP_EDNS_FRAG; @@ -1987,9 +1989,9 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, sq->status = serviced_query_UDP_EDNS; } sq->retry++; - if(!(rto=infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, + if(!infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, sq->qtype, -1, sq->last_rtt, - (time_t)now.tv_sec))) + (time_t)now.tv_sec)) log_err("out of memory in UDP exponential backoff"); if(sq->retry < OUTBOUND_UDP_RETRY) { log_name_addr(VERB_ALGO, "retry query", sq->qbuf+10, diff --git a/services/rpz.c b/services/rpz.c new file mode 100644 index 000000000000..643b20c91d20 --- /dev/null +++ b/services/rpz.c @@ -0,0 +1,1015 @@ +/* + * services/rpz.c - rpz service + * + * Copyright (c) 2019, 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 RPZ service. + */ + +#include "config.h" +#include "services/rpz.h" +#include "util/config_file.h" +#include "sldns/wire2str.h" +#include "sldns/str2wire.h" +#include "util/data/dname.h" +#include "util/net_help.h" +#include "util/log.h" +#include "util/data/dname.h" +#include "util/locks.h" +#include "util/regional.h" + +/** string for RPZ action enum */ +const char* +rpz_action_to_string(enum rpz_action a) +{ + switch(a) { + case RPZ_NXDOMAIN_ACTION: return "nxdomain"; + case RPZ_NODATA_ACTION: return "nodata"; + case RPZ_PASSTHRU_ACTION: return "passthru"; + case RPZ_DROP_ACTION: return "drop"; + case RPZ_TCP_ONLY_ACTION: return "tcp_only"; + case RPZ_INVALID_ACTION: return "invalid"; + case RPZ_LOCAL_DATA_ACTION: return "local_data"; + case RPZ_DISABLED_ACTION: return "disabled"; + case RPZ_CNAME_OVERRIDE_ACTION: return "cname_override"; + case RPZ_NO_OVERRIDE_ACTION: return "no_override"; + } + return "unknown"; +} + +/** RPZ action enum for config string */ +static enum rpz_action +rpz_config_to_action(char* a) +{ + if(strcmp(a, "nxdomain") == 0) + return RPZ_NXDOMAIN_ACTION; + else if(strcmp(a, "nodata") == 0) + return RPZ_NODATA_ACTION; + else if(strcmp(a, "passthru") == 0) + return RPZ_PASSTHRU_ACTION; + else if(strcmp(a, "drop") == 0) + return RPZ_DROP_ACTION; + else if(strcmp(a, "tcp_only") == 0) + return RPZ_TCP_ONLY_ACTION; + else if(strcmp(a, "cname") == 0) + return RPZ_CNAME_OVERRIDE_ACTION; + else if(strcmp(a, "disabled") == 0) + return RPZ_DISABLED_ACTION; + return RPZ_INVALID_ACTION; +} + +/** string for RPZ trigger enum */ +static const char* +rpz_trigger_to_string(enum rpz_trigger r) +{ + switch(r) { + case RPZ_QNAME_TRIGGER: return "qname"; + case RPZ_CLIENT_IP_TRIGGER: return "client_ip"; + case RPZ_RESPONSE_IP_TRIGGER: return "response_ip"; + case RPZ_NSDNAME_TRIGGER: return "nsdname"; + case RPZ_NSIP_TRIGGER: return "nsip"; + case RPZ_INVALID_TRIGGER: return "invalid"; + } + return "unknown"; +} + +/** + * Get the label that is just before the root label. + * @param dname: dname to work on + * @param maxdnamelen: maximum length of the dname + * @return: pointer to TLD label, NULL if not found or invalid dname + */ +static uint8_t* +get_tld_label(uint8_t* dname, size_t maxdnamelen) +{ + uint8_t* prevlab = dname; + size_t dnamelen = 0; + + /* one byte needed for label length */ + if(dnamelen+1 > maxdnamelen) + return NULL; + + /* only root label */ + if(*dname == 0) + return NULL; + + while(*dname) { + dnamelen += ((size_t)*dname)+1; + if(dnamelen+1 > maxdnamelen) + return NULL; + dname = dname+((size_t)*dname)+1; + if(*dname != 0) + prevlab = dname; + } + return prevlab; +} + +/** + * Classify RPZ action for RR type/rdata + * @param rr_type: the RR type + * @param rdatawl: RDATA with 2 bytes length + * @param rdatalen: the length of rdatawl (including its 2 bytes length) + * @return: the RPZ action + */ +static enum rpz_action +rpz_rr_to_action(uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) +{ + char* endptr; + uint8_t* rdata; + int rdatalabs; + uint8_t* tldlab = NULL; + + switch(rr_type) { + case LDNS_RR_TYPE_SOA: + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_DNAME: + /* all DNSSEC-related RRs must be ignored */ + case LDNS_RR_TYPE_DNSKEY: + case LDNS_RR_TYPE_DS: + case LDNS_RR_TYPE_RRSIG: + case LDNS_RR_TYPE_NSEC: + case LDNS_RR_TYPE_NSEC3: + return RPZ_INVALID_ACTION; + case LDNS_RR_TYPE_CNAME: + break; + default: + return RPZ_LOCAL_DATA_ACTION; + } + + /* use CNAME target to determine RPZ action */ + log_assert(rr_type == LDNS_RR_TYPE_CNAME); + if(rdatalen < 3) + return RPZ_INVALID_ACTION; + + rdata = rdatawl + 2; /* 2 bytes of rdata length */ + if(dname_valid(rdata, rdatalen-2) != rdatalen-2) + return RPZ_INVALID_ACTION; + + rdatalabs = dname_count_labels(rdata); + if(rdatalabs == 1) + return RPZ_NXDOMAIN_ACTION; + else if(rdatalabs == 2) { + if(dname_subdomain_c(rdata, (uint8_t*)&"\001*\000")) + return RPZ_NODATA_ACTION; + else if(dname_subdomain_c(rdata, + (uint8_t*)&"\014rpz-passthru\000")) + return RPZ_PASSTHRU_ACTION; + else if(dname_subdomain_c(rdata, (uint8_t*)&"\010rpz-drop\000")) + return RPZ_DROP_ACTION; + else if(dname_subdomain_c(rdata, + (uint8_t*)&"\014rpz-tcp-only\000")) + return RPZ_TCP_ONLY_ACTION; + } + + /* all other TLDs starting with "rpz-" are invalid */ + tldlab = get_tld_label(rdata, rdatalen-2); + if(tldlab && dname_lab_startswith(tldlab, "rpz-", &endptr)) + return RPZ_INVALID_ACTION; + + /* no special label found */ + return RPZ_LOCAL_DATA_ACTION; +} + +static enum localzone_type +rpz_action_to_localzone_type(enum rpz_action a) +{ + switch(a) { + case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; + case RPZ_NODATA_ACTION: return local_zone_always_nodata; + case RPZ_DROP_ACTION: return local_zone_always_deny; + case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; + case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ + case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect; + case RPZ_INVALID_ACTION: /* fallthrough */ + case RPZ_TCP_ONLY_ACTION: /* fallthrough */ + default: return local_zone_invalid; + } +} + +enum respip_action +rpz_action_to_respip_action(enum rpz_action a) +{ + switch(a) { + case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; + case RPZ_NODATA_ACTION: return respip_always_nodata; + case RPZ_DROP_ACTION: return respip_always_deny; + case RPZ_PASSTHRU_ACTION: return respip_always_transparent; + case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ + case RPZ_CNAME_OVERRIDE_ACTION: return respip_redirect; + case RPZ_INVALID_ACTION: /* fallthrough */ + case RPZ_TCP_ONLY_ACTION: /* fallthrough */ + default: return respip_invalid; + } +} + +static enum rpz_action +localzone_type_to_rpz_action(enum localzone_type lzt) +{ + switch(lzt) { + case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case local_zone_always_nodata: return RPZ_NODATA_ACTION; + case local_zone_always_deny: return RPZ_DROP_ACTION; + case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; + case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; + case local_zone_invalid: + default: + return RPZ_INVALID_ACTION; + } +} + +enum rpz_action +respip_action_to_rpz_action(enum respip_action a) +{ + switch(a) { + case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case respip_always_nodata: return RPZ_NODATA_ACTION; + case respip_always_deny: return RPZ_DROP_ACTION; + case respip_always_transparent: return RPZ_PASSTHRU_ACTION; + case respip_redirect: return RPZ_LOCAL_DATA_ACTION; + case respip_invalid: + default: + return RPZ_INVALID_ACTION; + } +} + +/** + * Get RPZ trigger for dname + * @param dname: dname containing RPZ trigger + * @param dname_len: length of the dname + * @return: RPZ trigger enum + */ +static enum rpz_trigger +rpz_dname_to_trigger(uint8_t* dname, size_t dname_len) +{ + uint8_t* tldlab; + char* endptr; + + if(dname_valid(dname, dname_len) != dname_len) + return RPZ_INVALID_TRIGGER; + + tldlab = get_tld_label(dname, dname_len); + if(!tldlab || !dname_lab_startswith(tldlab, "rpz-", &endptr)) + return RPZ_QNAME_TRIGGER; + + if(dname_subdomain_c(tldlab, + (uint8_t*)&"\015rpz-client-ip\000")) + return RPZ_CLIENT_IP_TRIGGER; + else if(dname_subdomain_c(tldlab, (uint8_t*)&"\006rpz-ip\000")) + return RPZ_RESPONSE_IP_TRIGGER; + else if(dname_subdomain_c(tldlab, (uint8_t*)&"\013rpz-nsdname\000")) + return RPZ_NSDNAME_TRIGGER; + else if(dname_subdomain_c(tldlab, (uint8_t*)&"\010rpz-nsip\000")) + return RPZ_NSIP_TRIGGER; + + return RPZ_QNAME_TRIGGER; +} + +void rpz_delete(struct rpz* r) +{ + if(!r) + return; + local_zones_delete(r->local_zones); + respip_set_delete(r->respip_set); + regional_destroy(r->region); + free(r->taglist); + free(r->log_name); + free(r); +} + +int +rpz_clear(struct rpz* r) +{ + /* must hold write lock on auth_zone */ + local_zones_delete(r->local_zones); + respip_set_delete(r->respip_set); + if(!(r->local_zones = local_zones_create())){ + return 0; + } + if(!(r->respip_set = respip_set_create())) { + return 0; + } + return 1; +} + +void +rpz_finish_config(struct rpz* r) +{ + lock_rw_wrlock(&r->respip_set->lock); + addr_tree_init_parents(&r->respip_set->ip_tree); + lock_rw_unlock(&r->respip_set->lock); +} + +/** new rrset containing CNAME override, does not yet contain a dname */ +static struct ub_packed_rrset_key* +new_cname_override(struct regional* region, uint8_t* ct, size_t ctlen) +{ + struct ub_packed_rrset_key* rrset; + struct packed_rrset_data* pd; + uint16_t rdlength = htons(ctlen); + rrset = (struct ub_packed_rrset_key*)regional_alloc_zero(region, + sizeof(*rrset)); + if(!rrset) { + log_err("out of memory"); + return NULL; + } + rrset->entry.key = rrset; + pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); + if(!pd) { + log_err("out of memory"); + return NULL; + } + pd->trust = rrset_trust_prim_noglue; + pd->security = sec_status_insecure; + + pd->count = 1; + pd->rr_len = regional_alloc_zero(region, sizeof(*pd->rr_len)); + pd->rr_ttl = regional_alloc_zero(region, sizeof(*pd->rr_ttl)); + pd->rr_data = regional_alloc_zero(region, sizeof(*pd->rr_data)); + if(!pd->rr_len || !pd->rr_ttl || !pd->rr_data) { + log_err("out of memory"); + return NULL; + } + pd->rr_len[0] = ctlen+2; + pd->rr_ttl[0] = 3600; + pd->rr_data[0] = regional_alloc_zero(region, 2 /* rdlength */ + ctlen); + if(!pd->rr_data[0]) { + log_err("out of memory"); + return NULL; + } + memmove(pd->rr_data[0], &rdlength, 2); + memmove(pd->rr_data[0]+2, ct, ctlen); + + rrset->entry.data = pd; + rrset->rk.type = htons(LDNS_RR_TYPE_CNAME); + rrset->rk.rrset_class = htons(LDNS_RR_CLASS_IN); + return rrset; +} + +struct rpz* +rpz_create(struct config_auth* p) +{ + struct rpz* r = calloc(1, sizeof(*r)); + if(!r) + goto err; + + r->region = regional_create_custom(sizeof(struct regional)); + if(!r->region) { + goto err; + } + + if(!(r->local_zones = local_zones_create())){ + goto err; + } + if(!(r->respip_set = respip_set_create())) { + goto err; + } + r->taglistlen = p->rpz_taglistlen; + r->taglist = memdup(p->rpz_taglist, r->taglistlen); + if(p->rpz_action_override) { + r->action_override = rpz_config_to_action(p->rpz_action_override); + } + else + r->action_override = RPZ_NO_OVERRIDE_ACTION; + + if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { + uint8_t nm[LDNS_MAX_DOMAINLEN+1]; + size_t nmlen = sizeof(nm); + + if(!p->rpz_cname) { + log_err("RPZ override with cname action found, but no " + "rpz-cname-override configured"); + goto err; + } + + if(sldns_str2wire_dname_buf(p->rpz_cname, nm, &nmlen) != 0) { + log_err("cannot parse RPZ cname override: %s", + p->rpz_cname); + goto err; + } + r->cname_override = new_cname_override(r->region, nm, nmlen); + if(!r->cname_override) { + goto err; + } + } + r->log = p->rpz_log; + if(p->rpz_log_name) { + if(!(r->log_name = strdup(p->rpz_log_name))) { + log_err("malloc failure on RPZ log_name strdup"); + goto err; + } + } + return r; +err: + if(r) { + if(r->local_zones) + local_zones_delete(r->local_zones); + if(r->respip_set) + respip_set_delete(r->respip_set); + if(r->taglist) + free(r->taglist); + free(r); + } + return NULL; +} + +/** + * Remove RPZ zone name from dname + * Copy dname to newdname, without the originlen number of trailing bytes + */ +static size_t +strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen, + uint8_t* newdname, size_t maxnewdnamelen) +{ + size_t newdnamelen; + if(dnamelen < originlen) + return 0; + newdnamelen = dnamelen - originlen; + if(newdnamelen+1 > maxnewdnamelen) + return 0; + memmove(newdname, dname, newdnamelen); + newdname[newdnamelen] = 0; + return newdnamelen + 1; /* + 1 for root label */ +} + +/** Insert RR into RPZ's local-zone */ +static void +rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct local_zone* z; + enum localzone_type tp = local_zone_always_transparent; + int dnamelabs = dname_count_labels(dname); + char* rrstr; + int newzone = 0; + + if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION) { + verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", + rpz_action_to_string(a)); + free(dname); + return; + } + + lock_rw_wrlock(&r->local_zones->lock); + /* exact match */ + z = local_zones_find(r->local_zones, dname, dnamelen, dnamelabs, + LDNS_RR_CLASS_IN); + if(z && a != RPZ_LOCAL_DATA_ACTION) { + rrstr = sldns_wire2str_rr(rr, rr_len); + if(!rrstr) { + log_err("malloc error while inserting RPZ qname " + "trigger"); + free(dname); + lock_rw_unlock(&r->local_zones->lock); + return; + } + verbose(VERB_ALGO, "RPZ: skipping duplicate record: '%s'", + rrstr); + free(rrstr); + free(dname); + lock_rw_unlock(&r->local_zones->lock); + return; + } + if(!z) { + tp = rpz_action_to_localzone_type(a); + if(!(z = local_zones_add_zone(r->local_zones, dname, dnamelen, + dnamelabs, rrclass, tp))) { + log_warn("RPZ create failed"); + lock_rw_unlock(&r->local_zones->lock); + /* dname will be free'd in failed local_zone_create() */ + return; + } + newzone = 1; + } + if(a == RPZ_LOCAL_DATA_ACTION) { + rrstr = sldns_wire2str_rr(rr, rr_len); + if(!rrstr) { + log_err("malloc error while inserting RPZ qname " + "trigger"); + free(dname); + lock_rw_unlock(&r->local_zones->lock); + return; + } + lock_rw_wrlock(&z->lock); + local_zone_enter_rr(z, dname, dnamelen, dnamelabs, + rrtype, rrclass, ttl, rdata, rdata_len, rrstr); + lock_rw_unlock(&z->lock); + free(rrstr); + } + if(!newzone) + free(dname); + lock_rw_unlock(&r->local_zones->lock); + return; +} + +/** Insert RR into RPZ's respip_set */ +static int +rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct resp_addr* node; + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + char* rrstr; + enum respip_action respa = rpz_action_to_respip_action(a); + + if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION || + respa == respip_invalid) { + verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", + rpz_action_to_string(a)); + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) + return 0; + + lock_rw_wrlock(&r->respip_set->lock); + rrstr = sldns_wire2str_rr(rr, rr_len); + if(!rrstr) { + log_err("malloc error while inserting RPZ respip trigger"); + lock_rw_unlock(&r->respip_set->lock); + return 0; + } + if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen, + net, 1, rrstr))) { + lock_rw_unlock(&r->respip_set->lock); + free(rrstr); + return 0; + } + + lock_rw_wrlock(&node->lock); + lock_rw_unlock(&r->respip_set->lock); + node->action = respa; + + if(a == RPZ_LOCAL_DATA_ACTION) { + respip_enter_rr(r->respip_set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr, ""); + } + lock_rw_unlock(&node->lock); + free(rrstr); + return 1; +} + +int +rpz_insert_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, + size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, + uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len) +{ + size_t policydnamelen; + /* name is free'd in local_zone delete */ + enum rpz_trigger t; + enum rpz_action a; + uint8_t* policydname; + + log_assert(dnamelen >= aznamelen); + if(!(policydname = calloc(1, (dnamelen-aznamelen)+1))) + return 0; + + a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); + if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, + policydname, (dnamelen-aznamelen)+1))) { + free(policydname); + return 0; + } + t = rpz_dname_to_trigger(policydname, policydnamelen); + if(t == RPZ_INVALID_TRIGGER) { + free(policydname); + verbose(VERB_ALGO, "RPZ: skipping invalid trigger"); + return 1; + } + if(t == RPZ_QNAME_TRIGGER) { + rpz_insert_qname_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + } + else if(t == RPZ_RESPONSE_IP_TRIGGER) { + rpz_insert_response_ip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } + else { + free(policydname); + verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s", + rpz_trigger_to_string(t)); + } + return 1; +} + +/** + * Find RPZ local-zone by qname. + * @param r: rpz containing local-zone tree + * @param qname: qname + * @param qname_len: length of qname + * @param qclass: qclass + * @param only_exact: if 1 only excact (non wildcard) matches are returned + * @param wr: get write lock for local-zone if 1, read lock if 0 + * @param zones_keep_lock: if set do not release the r->local_zones lock, this + * makes the caller of this function responsible for releasing the lock. + * @return: NULL or local-zone holding rd or wr lock + */ +static struct local_zone* +rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, + int only_exact, int wr, int zones_keep_lock) +{ + uint8_t* ce; + size_t ce_len, ce_labs; + uint8_t wc[LDNS_MAX_DOMAINLEN+1]; + int exact; + struct local_zone* z = NULL; + if(wr) { + lock_rw_wrlock(&r->local_zones->lock); + } else { + lock_rw_rdlock(&r->local_zones->lock); + } + z = local_zones_find_le(r->local_zones, qname, qname_len, + dname_count_labels(qname), + LDNS_RR_CLASS_IN, &exact); + if(!z || (only_exact && !exact)) { + lock_rw_unlock(&r->local_zones->lock); + return NULL; + } + if(wr) { + lock_rw_wrlock(&z->lock); + } else { + lock_rw_rdlock(&z->lock); + } + if(!zones_keep_lock) { + lock_rw_unlock(&r->local_zones->lock); + } + + if(exact) + return z; + + /* No exact match found, lookup wildcard. closest encloser must + * be the shared parent between the qname and the best local + * zone match, append '*' to that and do another lookup. */ + + ce = dname_get_shared_topdomain(z->name, qname); + if(!ce /* should not happen */ || !*ce /* root */) { + lock_rw_unlock(&z->lock); + if(zones_keep_lock) { + lock_rw_unlock(&r->local_zones->lock); + } + return NULL; + } + ce_labs = dname_count_size_labels(ce, &ce_len); + if(ce_len+2 > sizeof(wc)) { + lock_rw_unlock(&z->lock); + if(zones_keep_lock) { + lock_rw_unlock(&r->local_zones->lock); + } + return NULL; + } + wc[0] = 1; /* length of wildcard label */ + wc[1] = (uint8_t)'*'; /* wildcard label */ + memmove(wc+2, ce, ce_len); + lock_rw_unlock(&z->lock); + + if(!zones_keep_lock) { + if(wr) { + lock_rw_wrlock(&r->local_zones->lock); + } else { + lock_rw_rdlock(&r->local_zones->lock); + } + } + z = local_zones_find_le(r->local_zones, wc, + ce_len+2, ce_labs+1, qclass, &exact); + if(!z || !exact) { + lock_rw_unlock(&r->local_zones->lock); + return NULL; + } + if(wr) { + lock_rw_wrlock(&z->lock); + } else { + lock_rw_rdlock(&z->lock); + } + if(!zones_keep_lock) { + lock_rw_unlock(&r->local_zones->lock); + } + return z; +} + +/** + * Remove RR from RPZ's local-data + * @param z: local-zone for RPZ, holding write lock + * @param policydname: dname of RR to remove + * @param policydnamelen: lenth of policydname + * @param rr_type: RR type of RR to remove + * @param rdata: rdata of RR to remove + * @param rdatalen: length of rdata + * @return: 1 if zone must be removed after RR deletion + */ +static int +rpz_data_delete_rr(struct local_zone* z, uint8_t* policydname, + size_t policydnamelen, uint16_t rr_type, uint8_t* rdata, + size_t rdatalen) +{ + struct local_data* ld; + struct packed_rrset_data* d; + size_t index; + ld = local_zone_find_data(z, policydname, policydnamelen, + dname_count_labels(policydname)); + if(ld) { + struct local_rrset* prev=NULL, *p=ld->rrsets; + while(p && ntohs(p->rrset->rk.type) != rr_type) { + prev = p; + p = p->next; + } + if(!p) + return 0; + d = (struct packed_rrset_data*)p->rrset->entry.data; + if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { + if(d->count == 1) { + /* no memory recycling for zone deletions ... */ + if(prev) prev->next = p->next; + else ld->rrsets = p->next; + } + if(d->count > 1) { + if(!local_rrset_remove_rr(d, index)) + return 0; + } + } + } + if(ld && ld->rrsets) + return 0; + return 1; +} + +/** + * Remove RR from RPZ's respip set + * @param raddr: respip node + * @param rr_type: RR type of RR to remove + * @param rdata: rdata of RR to remove + * @param rdatalen: length of rdata + * @return: 1 if zone must be removed after RR deletion + */ +static int +rpz_rrset_delete_rr(struct resp_addr* raddr, uint16_t rr_type, uint8_t* rdata, + size_t rdatalen) +{ + size_t index; + struct packed_rrset_data* d; + if(!raddr->data) + return 1; + d = raddr->data->entry.data; + if(ntohs(raddr->data->rk.type) != rr_type) { + return 0; + } + if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { + if(d->count == 1) { + /* regional alloc'd */ + raddr->data->entry.data = NULL; + raddr->data = NULL; + return 1; + } + if(d->count > 1) { + if(!local_rrset_remove_rr(d, index)) + return 0; + } + } + return 0; + +} + +/** Remove RR from RPZ's local-zone */ +static void +rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rr_type, uint16_t rr_class, + uint8_t* rdatawl, size_t rdatalen) +{ + struct local_zone* z; + int delete_zone = 1; + z = rpz_find_zone(r, dname, dnamelen, rr_class, + 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/); + if(!z) { + verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + "RPZ domain not found"); + return; + } + if(a == RPZ_LOCAL_DATA_ACTION) + delete_zone = rpz_data_delete_rr(z, dname, + dnamelen, rr_type, rdatawl, rdatalen); + else if(a != localzone_type_to_rpz_action(z->type)) { + return; + } + lock_rw_unlock(&z->lock); + if(delete_zone) { + local_zones_del_zone(r->local_zones, z); + } + lock_rw_unlock(&r->local_zones->lock); + return; +} + +static void +rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) +{ + struct resp_addr* node; + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + int delete_respip = 1; + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) + return; + + lock_rw_wrlock(&r->respip_set->lock); + if(!(node = (struct resp_addr*)addr_tree_find( + &r->respip_set->ip_tree, &addr, addrlen, net))) { + verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + "RPZ domain not found"); + lock_rw_unlock(&r->respip_set->lock); + return; + } + + lock_rw_wrlock(&node->lock); + if(a == RPZ_LOCAL_DATA_ACTION) { + /* remove RR, signal whether RR can be removed */ + delete_respip = rpz_rrset_delete_rr(node, rr_type, rdatawl, + rdatalen); + } + lock_rw_unlock(&node->lock); + if(delete_respip) + respip_sockaddr_delete(r->respip_set, node); + lock_rw_unlock(&r->respip_set->lock); +} + +void +rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen, + uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) +{ + size_t policydnamelen; + enum rpz_trigger t; + enum rpz_action a; + uint8_t* policydname; + + if(!(policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1))) + return; + + a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); + if(a == RPZ_INVALID_ACTION) { + free(policydname); + return; + } + if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, + policydname, LDNS_MAX_DOMAINLEN + 1))) { + free(policydname); + return; + } + t = rpz_dname_to_trigger(policydname, policydnamelen); + if(t == RPZ_QNAME_TRIGGER) { + rpz_remove_qname_trigger(r, policydname, policydnamelen, a, + rr_type, rr_class, rdatawl, rdatalen); + } else if(t == RPZ_RESPONSE_IP_TRIGGER) { + rpz_remove_response_ip_trigger(r, policydname, policydnamelen, + a, rr_type, rdatawl, rdatalen); + } + free(policydname); +} + +/** print log information for an applied RPZ policy. Based on local-zone's + * lz_inform_print(). + */ +static void +log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo, + struct comm_reply* repinfo, char* log_name) +{ + char ip[128], txt[512]; + char dnamestr[LDNS_MAX_DOMAINLEN+1]; + uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); + dname_str(dname, dnamestr); + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); + if(log_name) + snprintf(txt, sizeof(txt), "RPZ applied [%s] %s %s %s@%u", + log_name, dnamestr, rpz_action_to_string(a), ip, + (unsigned)port); + else + snprintf(txt, sizeof(txt), "RPZ applied %s %s %s@%u", + dnamestr, rpz_action_to_string(a), ip, (unsigned)port); + log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); +} + +int +rpz_apply_qname_trigger(struct auth_zones* az, 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, struct ub_server_stats* stats) +{ + struct rpz* r; + int ret; + enum localzone_type lzt; + struct local_zone* z = NULL; + struct local_data* ld = NULL; + lock_rw_rdlock(&az->rpz_lock); + for(r = az->rpz_first; r; r = r->next) { + if(!r->taglist || taglist_intersect(r->taglist, + r->taglistlen, taglist, taglen)) { + z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len, + qinfo->qclass, 0, 0, 0); + if(z && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply(z->name, + r->action_override, + qinfo, repinfo, r->log_name); + /* TODO only register stats when stats_extended? + * */ + stats->rpz_action[r->action_override]++; + lock_rw_unlock(&z->lock); + z = NULL; + } + if(z) + break; + } + } + lock_rw_unlock(&az->rpz_lock); + if(!z) + return 0; + + + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) + lzt = z->type; + else + lzt = rpz_action_to_localzone_type(r->action_override); + + if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { + qinfo->local_alias = + regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(!qinfo->local_alias) { + lock_rw_unlock(&z->lock); + return 0; /* out of memory */ + } + qinfo->local_alias->rrset = + regional_alloc_init(temp, r->cname_override, + sizeof(*r->cname_override)); + if(!qinfo->local_alias->rrset) { + lock_rw_unlock(&z->lock); + return 0; /* out of memory */ + } + qinfo->local_alias->rrset->rk.dname = qinfo->qname; + qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; + if(r->log) + log_rpz_apply(z->name, RPZ_CNAME_OVERRIDE_ACTION, + qinfo, repinfo, r->log_name); + stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; + lock_rw_unlock(&z->lock); + return 0; + } + + if(lzt == local_zone_redirect && local_data_answer(z, env, qinfo, + edns, repinfo, buf, temp, dname_count_labels(qinfo->qname), + &ld, lzt, -1, NULL, 0, NULL, 0)) { + if(r->log) + log_rpz_apply(z->name, + localzone_type_to_rpz_action(lzt), qinfo, + repinfo, r->log_name); + stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; + lock_rw_unlock(&z->lock); + return !qinfo->local_alias; + } + + ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, + 0 /* no local data used */, lzt); + if(r->log) + log_rpz_apply(z->name, localzone_type_to_rpz_action(lzt), + qinfo, repinfo, r->log_name); + stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; + lock_rw_unlock(&z->lock); + + return ret; +} diff --git a/services/rpz.h b/services/rpz.h new file mode 100644 index 000000000000..676a4f2a8406 --- /dev/null +++ b/services/rpz.h @@ -0,0 +1,201 @@ +/* + * services/rpz.h - rpz service + * + * Copyright (c) 2019, 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 RPZ service. + */ + +#ifndef SERVICES_RPZ_H +#define SERVICES_RPZ_H + +#include "services/localzone.h" +#include "util/locks.h" +#include "util/log.h" +#include "util/config_file.h" +#include "services/authzone.h" +#include "sldns/sbuffer.h" +#include "daemon/stats.h" +#include "respip/respip.h" + +/** + * RPZ triggers, only the QNAME trigger is currently supported in Unbound. + */ +enum rpz_trigger { + RPZ_QNAME_TRIGGER = 0, + /* unsupported triggers */ + RPZ_CLIENT_IP_TRIGGER, /* rpz-client-ip */ + RPZ_RESPONSE_IP_TRIGGER, /* rpz-ip */ + RPZ_NSDNAME_TRIGGER, /* rpz-nsdname */ + RPZ_NSIP_TRIGGER, /* rpz-nsip */ + RPZ_INVALID_TRIGGER, /* dname does not contain valid trigger */ +}; + +/** + * RPZ actions. + */ +enum rpz_action { + RPZ_NXDOMAIN_ACTION = 0,/* CNAME . */ + RPZ_NODATA_ACTION, /* CNAME *. */ + RPZ_PASSTHRU_ACTION, /* CNAME rpz-passthru. */ + RPZ_DROP_ACTION, /* CNAME rpz-drop. */ + RPZ_TCP_ONLY_ACTION, /* CNAME rpz-tcp-only. */ + RPZ_INVALID_ACTION, /* CNAME with (child of) TLD starting with + "rpz-" in target, SOA, NS, DNAME and + DNSSEC-related records. */ + RPZ_LOCAL_DATA_ACTION, /* anything else */ + /* RPZ override actions */ + RPZ_DISABLED_ACTION, /* RPZ action disabled using override */ + RPZ_NO_OVERRIDE_ACTION, /* RPZ action no override*/ + RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/ +}; + +/** + * RPZ containing policies. Pointed to from corresponding auth-zone. Part of a + * linked list to keep configuration order. Iterating or changing the linked + * list requires the rpz_lock from struct auth_zones. + */ +struct rpz { + struct local_zones* local_zones; + struct respip_set* respip_set; + uint8_t* taglist; + size_t taglistlen; + enum rpz_action action_override; + struct ub_packed_rrset_key* cname_override; + int log; + char* log_name; + struct rpz* next; + struct rpz* prev; + struct regional* region; +}; + +/** + * Create policy from RR and add to this RPZ. + * @param r: the rpz to add the policy to. + * @param aznamelen: the length of the auth-zone name + * @param dname: dname of the RR + * @param dnamelen: length of the dname + * @param rr_type: RR type of the RR + * @param rr_class: RR class of the RR + * @param rr_ttl: TTL of the RR + * @param rdatawl: rdata of the RR, prepended with the rdata size + * @param rdatalen: length if the RR, including the prepended rdata size + * @param rr: the complete RR, for logging purposes + * @param rr_len: the length of the complete RR + * @return: 0 on error + */ +int rpz_insert_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, + size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, + uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len); + +/** + * Delete policy matching RR, used for IXFR. + * @param r: the rpz to add the policy to. + * @param aznamelen: the length of the auth-zone name + * @param dname: dname of the RR + * @param dnamelen: length of the dname + * @param rr_type: RR type of the RR + * @param rr_class: RR class of the RR + * @param rdatawl: rdata of the RR, prepended with the rdata size + * @param rdatalen: length if the RR, including the prepended rdata size + */ +void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, + size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, + size_t rdatalen); + +/** + * Walk over the RPZ zones to find and apply a QNAME trigger policy. + * @param az: auth_zones struct, containing first RPZ item and RPZ lock + * @param env: module env + * @param qinfo: qinfo containing qname and qtype + * @param edns: edns data + * @param buf: buffer to write answer to + * @param temp: scratchpad + * @param repinfo: reply info + * @param taglist: taglist to lookup. + * @param taglen: lenth of taglist. + * @param stats: worker stats struct + * @return: 1 if client answer is ready, 0 to continue resolving + */ +int rpz_apply_qname_trigger(struct auth_zones* az, 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, struct ub_server_stats* stats); + +/** + * Delete RPZ + * @param r: RPZ struct to delete + */ +void rpz_delete(struct rpz* r); + +/** + * Clear local-zones and respip data in RPZ, used after reloading file or + * AXFR/HTTP transfer. + * @param r: RPZ to use + */ +int rpz_clear(struct rpz* r); + +/** + * Create RPZ. RPZ must be added to linked list after creation. + * @return: the newly created RPZ + */ +struct rpz* rpz_create(struct config_auth* p); + +/** + * String for RPZ action enum + * @param a: RPZ action to get string for + * @return: string for RPZ action + */ +const char* rpz_action_to_string(enum rpz_action a); + +enum rpz_action +respip_action_to_rpz_action(enum respip_action a); + +/** + * Prepare RPZ after procesing feed content. + * @param r: RPZ to use + */ +void rpz_finish_config(struct rpz* r); + +/** + * Classify respip action for RPZ action + * @param a: RPZ action + * @return: the respip action + */ +enum respip_action +rpz_action_to_respip_action(enum rpz_action a); + +#endif /* SERVICES_RPZ_H */ diff --git a/services/view.c b/services/view.c index c6709e58fd6a..db48ae9545f8 100644 --- a/services/view.c +++ b/services/view.c @@ -198,8 +198,6 @@ views_apply_cfg(struct views* vs, struct config_file* cfg) log_err("failed to insert " "default zones into " "local-zone list"); - free(nd_str); - free(nd_type); lock_rw_unlock(&v->lock); return 0; } |