aboutsummaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2020-05-21 05:01:52 +0000
committerCy Schubert <cy@FreeBSD.org>2020-05-21 05:01:52 +0000
commit6a53c00e64c4cf911eb00846733d9e6a47b2e7f4 (patch)
tree60a7720d2d4edfe62b094e2665743e8879ebb911 /services
parente2fe726866d062155f6b1aae749375475ef19191 (diff)
downloadsrc-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.c97
-rw-r--r--services/authzone.h11
-rw-r--r--services/cache/dns.c128
-rw-r--r--services/cache/dns.h7
-rw-r--r--services/localzone.c220
-rw-r--r--services/localzone.h115
-rw-r--r--services/mesh.c426
-rw-r--r--services/mesh.h31
-rw-r--r--services/outside_network.c8
-rw-r--r--services/rpz.c1015
-rw-r--r--services/rpz.h201
-rw-r--r--services/view.c2
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;
}