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