aboutsummaryrefslogtreecommitdiff
path: root/contrib/unbound/services/cache/dns.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/unbound/services/cache/dns.c')
-rw-r--r--contrib/unbound/services/cache/dns.c176
1 files changed, 127 insertions, 49 deletions
diff --git a/contrib/unbound/services/cache/dns.c b/contrib/unbound/services/cache/dns.c
index f3149b614b54..632ed79ace49 100644
--- a/contrib/unbound/services/cache/dns.c
+++ b/contrib/unbound/services/cache/dns.c
@@ -68,21 +68,27 @@
* in a prefetch situation to be updated (without becoming sticky).
* @param qrep: update rrsets here if cache is better
* @param region: for qrep allocs.
+ * @param qstarttime: time when delegations were looked up, this is perhaps
+ * earlier than the time in now. The time is used to determine if RRsets
+ * of type NS have expired, so that they can only be updated using
+ * lookups of delegation points that did not use them, since they had
+ * expired then.
*/
static void
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)
+ struct regional* region, time_t qstarttime)
{
size_t i;
+ time_t ttl, min_ttl = rep->ttl;
/* 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, ((ntohs(rep->ref[i].key->rk.type)==
+ LDNS_RR_TYPE_NS && !pside)?qstarttime:now + leeway))) {
case 0: /* ref unchanged, item inserted */
break;
case 2: /* ref updated, cache is superior */
@@ -107,6 +113,15 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now,
case 1: /* ref updated, item inserted */
rep->rrsets[i] = rep->ref[i].key;
}
+ /* if ref was updated make sure the message ttl is updated to
+ * the minimum of the current rrsets. */
+ ttl = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data)->ttl;
+ if(ttl < min_ttl) min_ttl = ttl;
+ }
+ if(min_ttl < rep->ttl) {
+ rep->ttl = min_ttl;
+ rep->prefetch_ttl = PREFETCH_TTL_CALC(rep->ttl);
+ rep->serve_expired_ttl = rep->ttl + SERVE_EXPIRED_TTL;
}
}
@@ -127,35 +142,11 @@ msg_cache_remove(struct module_env* env, uint8_t* qname, size_t qnamelen,
slabhash_remove(env->msg_cache, h, &k);
}
-/** remove servfail msg cache entry */
-static void
-msg_del_servfail(struct module_env* env, struct query_info* qinfo,
- uint32_t flags)
-{
- struct msgreply_entry* e;
- /* see if the entry is servfail, and then remove it, so that
- * lookups move from the cacheresponse stage to the recursionresponse
- * stage */
- e = msg_cache_lookup(env, qinfo->qname, qinfo->qname_len,
- qinfo->qtype, qinfo->qclass, flags, 0, 0);
- if(!e) return;
- /* we don't check for the ttl here, also expired servfail entries
- * are removed. If the user uses serve-expired, they would still be
- * used to answer from cache */
- if(FLAGS_GET_RCODE(((struct reply_info*)e->entry.data)->flags)
- != LDNS_RCODE_SERVFAIL) {
- lock_rw_unlock(&e->entry.lock);
- return;
- }
- lock_rw_unlock(&e->entry.lock);
- msg_cache_remove(env, qinfo->qname, qinfo->qname_len, qinfo->qtype,
- qinfo->qclass, flags);
-}
-
void
dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside,
- struct reply_info* qrep, uint32_t flags, struct regional* region)
+ struct reply_info* qrep, uint32_t flags, struct regional* region,
+ time_t qstarttime)
{
struct msgreply_entry* e;
time_t ttl = rep->ttl;
@@ -170,18 +161,26 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
/* there was a reply_info_sortref(rep) here but it seems to be
* unnecessary, because the cache gets locked per rrset. */
reply_info_set_ttls(rep, *env->now);
- store_rrsets(env, rep, *env->now, leeway, pside, qrep, region);
+ store_rrsets(env, rep, *env->now, leeway, pside, qrep, region,
+ qstarttime);
if(ttl == 0 && !(flags & DNSCACHE_STORE_ZEROTTL)) {
/* we do not store the message, but we did store the RRs,
* which could be useful for delegation information */
verbose(VERB_ALGO, "TTL 0: dropped msg from cache");
- free(rep);
- /* if the message is SERVFAIL in cache, remove that SERVFAIL,
+ reply_info_delete(rep, NULL);
+ /* if the message is in the cache, remove that msg,
* so that the TTL 0 response can be returned for future
- * responses (i.e. don't get answered by the servfail from
+ * responses (i.e. don't get answered from
* cache, but instead go to recursion to get this TTL0
- * response). */
- msg_del_servfail(env, qinfo, flags);
+ * response).
+ * Possible messages that could be in the cache:
+ * - SERVFAIL
+ * - NXDOMAIN
+ * - NODATA
+ * - an older record that is expired
+ * - an older record that did not yet expire */
+ msg_cache_remove(env, qinfo->qname, qinfo->qname_len,
+ qinfo->qtype, qinfo->qclass, flags);
return;
}
@@ -197,7 +196,8 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
/** find closest NS or DNAME and returns the rrset (locked) */
static struct ub_packed_rrset_key*
find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen,
- uint16_t qclass, time_t now, uint16_t searchtype, int stripfront)
+ uint16_t qclass, time_t now, uint16_t searchtype, int stripfront,
+ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen)
{
struct ub_packed_rrset_key *rrset;
uint8_t lablen;
@@ -212,8 +212,40 @@ find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen,
/* snip off front part of qname until the type is found */
while(qnamelen > 0) {
if((rrset = rrset_cache_lookup(env->rrset_cache, qname,
- qnamelen, searchtype, qclass, 0, now, 0)))
- return rrset;
+ qnamelen, searchtype, qclass, 0, now, 0))) {
+ uint8_t* origqname = qname;
+ size_t origqnamelen = qnamelen;
+ if(!noexpiredabove)
+ return rrset;
+ /* if expiretop set, do not look above it, but
+ * qname is equal, so the just found result is also
+ * the nonexpired above part. */
+ if(expiretop && qnamelen == expiretoplen &&
+ query_dname_compare(qname, expiretop)==0)
+ return rrset;
+ /* check for expiry, but we have to let go of the rrset
+ * for the lock ordering */
+ lock_rw_unlock(&rrset->entry.lock);
+ /* the rrset_cache_expired_above function always takes
+ * off one label (if qnamelen>0) and returns the final
+ * qname where it searched, so we can continue from
+ * there turning the O N*N search into O N. */
+ if(!rrset_cache_expired_above(env->rrset_cache, &qname,
+ &qnamelen, searchtype, qclass, now, expiretop,
+ expiretoplen)) {
+ /* we want to return rrset, but it may be
+ * gone from cache, if so, just loop like
+ * it was not in the cache in the first place.
+ */
+ if((rrset = rrset_cache_lookup(env->
+ rrset_cache, origqname, origqnamelen,
+ searchtype, qclass, 0, now, 0))) {
+ return rrset;
+ }
+ }
+ log_nametypeclass(VERB_ALGO, "ignoring rrset because expired rrsets exist above it", origqname, searchtype, qclass);
+ continue;
+ }
/* snip off front label */
lablen = *qname;
@@ -324,6 +356,9 @@ cache_fill_missing(struct module_env* env, uint16_t qclass,
struct ub_packed_rrset_key* akey;
time_t now = *env->now;
for(ns = dp->nslist; ns; ns = ns->next) {
+ if(ns->cache_lookup_count > ITERATOR_NAME_CACHELOOKUP_MAX)
+ continue;
+ ns->cache_lookup_count++;
akey = rrset_cache_lookup(env->rrset_cache, ns->name,
ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0);
if(akey) {
@@ -428,6 +463,7 @@ dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype,
return NULL; /* integer overflow protection */
msg->rep->flags = BIT_QR; /* with QR, no AA */
msg->rep->qdcount = 1;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
msg->rep->rrsets = (struct ub_packed_rrset_key**)
regional_alloc(region,
capacity*sizeof(struct ub_packed_rrset_key*));
@@ -461,7 +497,8 @@ dns_msg_ansadd(struct dns_msg* msg, struct regional* region,
struct delegpt*
dns_cache_find_delegation(struct module_env* env, uint8_t* qname,
size_t qnamelen, uint16_t qtype, uint16_t qclass,
- struct regional* region, struct dns_msg** msg, time_t now)
+ struct regional* region, struct dns_msg** msg, time_t now,
+ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen)
{
/* try to find closest NS rrset */
struct ub_packed_rrset_key* nskey;
@@ -469,7 +506,7 @@ dns_cache_find_delegation(struct module_env* env, uint8_t* qname,
struct delegpt* dp;
nskey = find_closest_of_type(env, qname, qnamelen, qclass, now,
- LDNS_RR_TYPE_NS, 0);
+ LDNS_RR_TYPE_NS, 0, noexpiredabove, expiretop, expiretoplen);
if(!nskey) /* hope the caller has hints to prime or something */
return NULL;
nsdata = (struct packed_rrset_data*)nskey->entry.data;
@@ -524,6 +561,8 @@ gen_dns_msg(struct regional* region, struct query_info* q, size_t num)
sizeof(struct reply_info) - sizeof(struct rrset_ref));
if(!msg->rep)
return NULL;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
+ msg->rep->reason_bogus_str = NULL;
if(num > RR_COUNT_MAX)
return NULL; /* integer overflow protection */
msg->rep->rrsets = (struct ub_packed_rrset_key**)
@@ -550,6 +589,14 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r,
r->serve_expired_ttl < now) {
return NULL;
}
+ /* Ignore expired failure answers */
+ if(FLAGS_GET_RCODE(r->flags) !=
+ LDNS_RCODE_NOERROR &&
+ FLAGS_GET_RCODE(r->flags) !=
+ LDNS_RCODE_NXDOMAIN &&
+ FLAGS_GET_RCODE(r->flags) !=
+ LDNS_RCODE_YXDOMAIN)
+ return 0;
} else {
return NULL;
}
@@ -577,6 +624,11 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r,
msg->rep->ar_numrrsets = r->ar_numrrsets;
msg->rep->rrset_count = r->rrset_count;
msg->rep->authoritative = r->authoritative;
+ msg->rep->reason_bogus = r->reason_bogus;
+ if(r->reason_bogus_str) {
+ msg->rep->reason_bogus_str = regional_strdup(region, r->reason_bogus_str);
+ }
+
if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) {
return NULL;
}
@@ -608,6 +660,28 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r,
return msg;
}
+struct dns_msg*
+dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
+{
+ size_t i;
+ struct dns_msg* res = NULL;
+ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
+ if(!res) return NULL;
+ *res->rep = *origin->rep;
+ if(origin->rep->reason_bogus_str) {
+ res->rep->reason_bogus_str = regional_strdup(region,
+ origin->rep->reason_bogus_str);
+ }
+ for(i=0; i<res->rep->rrset_count; i++) {
+ res->rep->rrsets[i] = packed_rrset_copy_region(
+ origin->rep->rrsets[i], region, 0);
+ if(!res->rep->rrsets[i]) {
+ return NULL;
+ }
+ }
+ return res;
+}
+
/** synthesize RRset-only response from cached RRset item */
static struct dns_msg*
rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
@@ -632,6 +706,7 @@ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
msg->rep->ns_numrrsets = 0;
msg->rep->ar_numrrsets = 0;
msg->rep->rrset_count = 1;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now);
if(!msg->rep->rrsets[0]) /* copy CNAME */
return NULL;
@@ -670,6 +745,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
msg->rep->ns_numrrsets = 0;
msg->rep->ar_numrrsets = 0;
msg->rep->rrset_count = 1;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now);
if(!msg->rep->rrsets[0]) /* copy DNAME */
return NULL;
@@ -712,7 +788,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
if(!newd)
return NULL;
ck->entry.data = newd;
- newd->ttl = 0; /* 0 for synthesized CNAME TTL */
+ newd->ttl = d->ttl - now; /* RFC6672: synth CNAME TTL == DNAME TTL */
newd->count = 1;
newd->rrsig_count = 0;
newd->trust = rrset_trust_ans_noAA;
@@ -801,7 +877,7 @@ struct dns_msg*
dns_cache_lookup(struct module_env* env,
uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass,
uint16_t flags, struct regional* region, struct regional* scratch,
- int no_partial)
+ int no_partial, uint8_t* dpname, size_t dpnamelen)
{
struct lruhash_entry* e;
struct query_info k;
@@ -835,7 +911,7 @@ dns_cache_lookup(struct module_env* env,
* consistent with the DNAME */
if(!no_partial &&
(rrset=find_closest_of_type(env, qname, qnamelen, qclass, now,
- LDNS_RR_TYPE_DNAME, 1))) {
+ LDNS_RR_TYPE_DNAME, 1, 0, NULL, 0))) {
/* synthesize a DNAME+CNAME message based on this */
enum sec_status sec_status = sec_status_unchecked;
struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k,
@@ -923,6 +999,9 @@ dns_cache_lookup(struct module_env* env,
* the same. We search upwards for NXDOMAINs. */
if(env->cfg->harden_below_nxdomain) {
while(!dname_is_root(k.qname)) {
+ if(dpname && dpnamelen
+ && !dname_subdomain_c(k.qname, dpname))
+ break; /* no synth nxdomain above the stub */
dname_remove_label(&k.qname, &k.qname_len);
h = query_info_hash(&k, flags);
e = slabhash_lookup(env->msg_cache, h, &k, 0);
@@ -965,7 +1044,7 @@ dns_cache_lookup(struct module_env* env,
int
dns_cache_store(struct module_env* env, struct query_info* msgqinf,
struct reply_info* msgrep, int is_referral, time_t leeway, int pside,
- struct regional* region, uint32_t flags)
+ struct regional* region, uint32_t flags, time_t qstarttime)
{
struct reply_info* rep = NULL;
/* alloc, malloc properly (not in region, like msg is) */
@@ -975,7 +1054,6 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
/* ttl must be relative ;i.e. 0..86400 not time(0)+86400.
* the env->now is added to message and RRsets in this routine. */
/* the leeway is used to invalidate other rrsets earlier */
-
if(is_referral) {
/* store rrsets */
struct rrset_ref ref;
@@ -988,11 +1066,11 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
/*ignore ret: it was in the cache, ref updated */
/* no leeway for typeNS */
(void)rrset_cache_update(env->rrset_cache, &ref,
- env->alloc, *env->now +
+ env->alloc,
((ntohs(ref.key->rk.type)==LDNS_RR_TYPE_NS
- && !pside) ? 0:leeway));
+ && !pside) ? qstarttime:*env->now + leeway));
}
- free(rep);
+ reply_info_delete(rep, NULL);
return 1;
} else {
/* store msg, and rrsets */
@@ -1012,7 +1090,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
rep->flags &= ~(BIT_AA | BIT_CD);
h = query_info_hash(&qinf, (uint16_t)flags);
dns_cache_store_msg(env, &qinf, h, rep, leeway, pside, msgrep,
- flags, region);
+ flags, region, qstarttime);
/* qname is used inside query_info_entrysetup, and set to
* NULL. If it has not been used, free it. free(0) is safe. */
free(qinf.qname);