if(!go_on)
return 1; /* skip this one, not all references satisfied */
- if(!dns_cache_store(&worker->env, &qinf, &rep, 0)) {
+ if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0)) {
log_warn("error out of memory");
return 0;
}
/** Size of an UDP datagram */
#define NORMAL_UDP_SIZE 512 /* bytes */
+/**
+ * seconds to add to prefetch leeway. This is a TTL that expires old rrsets
+ * earlier than they should in order to put the new update into the cache.
+ * This additional value is to make sure that if not all TTLs are equal in
+ * the message to be updated(and replaced), that rrsets with up to this much
+ * extra TTL are also replaced. This means that the resulting new message
+ * will have (most likely) this TTL at least, avoiding very small 'split
+ * second' TTLs due to operators choosing relative primes for TTLs (or so).
+ * Also has to be at least one to break ties (and overwrite cached entry).
+ */
+#define PREFETCH_EXPIRY_ADD 60
+
#ifdef UNBOUND_ALLOC_STATS
/** measure memory leakage */
static void
/** Reply to client and perform prefetch to keep cache up to date */
static void
reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
- uint16_t flags, struct comm_reply* repinfo)
+ uint16_t flags, struct comm_reply* repinfo, uint32_t leeway)
{
/* first send answer to client to keep its latency
* as small as a cachereply */
* client addrs waiting, which has the cache blacklisted (to bypass
* the cache and go to the network for the data). */
/* this (potentially) runs the mesh for the new query */
- mesh_new_prefetch(worker->env.mesh, qinfo, flags);
+ mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+ PREFETCH_EXPIRY_ADD);
}
/**
/* prefetch it if the prefetch TTL expired */
if(worker->env.cfg->prefetch && *worker->env.now >=
((struct reply_info*)e->data)->prefetch_ttl) {
+ uint32_t leeway = ((struct reply_info*)e->
+ data)->ttl - *worker->env.now;
lock_rw_unlock(&e->lock);
reply_and_prefetch(worker, &qinfo,
ldns_buffer_read_u16_at(c->buffer, 2),
- repinfo);
+ repinfo, leeway);
return 0;
}
lock_rw_unlock(&e->lock);
is fixed to no longer block lookup of child side information and
the iterator is fixed to no longer attempt to get ipv6 when it is
not enabled and then give up in failure.
+ - test and fixes to make prefetch actually store the answer in the
+ cache. Considers some rrsets 'already expired' but does not allow
+ overwriting of rrsets considered more secure.
7 January 2010: Wouter
- Fixup python documentation (thanks Leo Vandewoestijne).
int
iter_dns_store(struct module_env* env, struct query_info* msgqinf,
- struct reply_info* msgrep, int is_referral)
+ struct reply_info* msgrep, int is_referral, uint32_t leeway)
{
- return dns_cache_store(env, msgqinf, msgrep, is_referral);
+ return dns_cache_store(env, msgqinf, msgrep, is_referral, leeway);
}
int
* @param rep: reply in dns_msg from dns_alloc_msg for example.
* @param is_referral: If true, then the given message to be stored is a
* referral. The cache implementation may use this as a hint.
+ * @param leeway: prefetch TTL leeway to expire old rrsets quicker.
* @return 0 on alloc error (out of memory).
*/
int iter_dns_store(struct module_env* env, struct query_info* qinf,
- struct reply_info* rep, int is_referral);
+ struct reply_info* rep, int is_referral, uint32_t leeway);
/**
* Select randomly with n/m probability.
/* do not waste time trying to validate this servfail */
err.security = sec_status_indeterminate;
verbose(VERB_ALGO, "store error response in message cache");
- if(!iter_dns_store(qstate->env, &qstate->qinfo, &err, 0)) {
+ if(!iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0)) {
log_err("error_response_cache: could not store error (nomem)");
}
return error_response(qstate, id, rcode);
* so they sent on their */
verbose(VERB_DETAIL, "query response was ANSWER");
if(!iter_dns_store(qstate->env, &iq->response->qinfo,
- iq->response->rep, 0))
+ iq->response->rep, 0, qstate->prefetch_leeway))
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
/* close down outstanding requests to be discarded */
outbound_list_clear(&iq->outlist);
)
)) {
/* Store the referral under the current query */
+ /* no prefetch-leeway, since its not the answer */
if(!iter_dns_store(qstate->env, &iq->response->qinfo,
- iq->response->rep, 1))
+ iq->response->rep, 1, 0))
return error_response(qstate, id,
LDNS_RCODE_SERVFAIL);
if(qstate->env->neg_cache)
/* cache the CNAME response under the current query */
/* NOTE : set referral=1, so that rrsets get stored but not
* the partial query answer (CNAME only). */
+ /* prefetchleeway applied because this updates answer parts */
if(!iter_dns_store(qstate->env, &iq->response->qinfo,
- iq->response->rep, 1))
+ iq->response->rep, 1, qstate->prefetch_leeway))
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
/* set the current request's qname to the new value. */
iq->qchase.qname = sname;
* from cache does not need to be stored in the msg cache. */
if(qstate->query_flags&BIT_RD) {
if(!iter_dns_store(qstate->env, &qstate->qinfo,
- iq->response->rep, 0))
+ iq->response->rep, 0, qstate->prefetch_leeway))
return error_response(qstate, id,
LDNS_RCODE_SERVFAIL);
}
return 0;
}
- return dns_cache_store(qstate->env, qinfo, msgrep, is_referral);
+ return dns_cache_store(qstate->env, qinfo, msgrep, is_referral,
+ qstate->prefetch_leeway);
}
/* Invalidate the message associated with query_info stored in message cache */
void
dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
- hashvalue_t hash, struct reply_info* rep)
+ hashvalue_t hash, struct reply_info* rep, uint32_t leeway)
{
struct msgreply_entry* e;
uint32_t ttl = rep->ttl;
}
reply_info_sortref(rep);
reply_info_set_ttls(rep, *env->now);
- store_rrsets(env, rep, *env->now);
+ store_rrsets(env, rep, *env->now+leeway);
if(ttl == 0) {
/* we do not store the message, but we did store the RRs,
* which could be useful for delegation information */
int
dns_cache_store(struct module_env* env, struct query_info* msgqinf,
- struct reply_info* msgrep, int is_referral)
+ struct reply_info* msgrep, int is_referral, uint32_t leeway)
{
struct reply_info* rep = NULL;
/* alloc, malloc properly (not in region, like msg is) */
return 0;
/* 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 */
ref.id = rep->rrsets[i]->id;
/*ignore ret: it was in the cache, ref updated */
(void)rrset_cache_update(env->rrset_cache, &ref,
- env->alloc, *env->now);
+ env->alloc, *env->now + leeway);
}
free(rep);
return 1;
rep->flags |= (BIT_RA | BIT_QR);
rep->flags &= ~(BIT_AA | BIT_CD);
h = query_info_hash(&qinf);
- dns_cache_store_msg(env, &qinf, h, rep);
+ dns_cache_store_msg(env, &qinf, h, rep, leeway);
/* 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);
* @param is_referral: If true, then the given message to be stored is a
* referral. The cache implementation may use this as a hint.
* It will store only the RRsets, not the message.
+ * @param leeway: TTL value, if not 0, other rrsets are considered expired
+ * that many seconds before actual TTL expiry.
* @return 0 on alloc error (out of memory).
*/
int dns_cache_store(struct module_env* env, struct query_info* qinf,
- struct reply_info* rep, int is_referral);
+ struct reply_info* rep, int is_referral, uint32_t leeway);
/**
* Store message in the cache. Stores in message cache and rrset cache.
* @param hash: hash over qinfo.
* @param rep: reply info, together with qinfo makes up the message.
* Adjusts the reply info TTLs to absolute time.
+ * @param leeway: TTL value, if not 0, other rrsets are considered expired
+ * that many seconds before actual TTL expiry.
*/
void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
- hashvalue_t hash, struct reply_info* rep);
+ hashvalue_t hash, struct reply_info* rep, uint32_t leeway);
/**
* Find a delegation from the cache.
}
void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
- uint16_t qflags)
+ uint16_t qflags, uint32_t leeway)
{
struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags, 0);
struct rbnode_t* n;
/* make it ignore the cache from now on */
if(!s->s.blacklist)
sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region);
+ if(s->s.prefetch_leeway < leeway)
+ s->s.prefetch_leeway = leeway;
return;
}
if(!mesh_make_new_space(mesh)) {
mesh->num_detached_states++;
/* make it ignore the cache */
sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region);
+ s->s.prefetch_leeway = leeway;
if(s->list_select == mesh_no_list) {
/* move to either the forever or the jostle_list */
mstate->s.return_rcode = LDNS_RCODE_NOERROR;
mstate->s.env = env;
mstate->s.mesh_info = mstate;
+ mstate->s.prefetch_leeway = 0;
/* init modules */
for(i=0; i<env->mesh->mods.num; i++) {
mstate->s.minfo[i] = NULL;
* @param mesh: the mesh.
* @param qinfo: query from client.
* @param qflags: flags from client query.
+ * @param leeway: TTL leeway what to expire earlier for this update.
*/
void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
- uint16_t qflags);
+ uint16_t qflags, uint32_t leeway);
/**
* Handle new event from the wire. A serviced query has returned.
--- /dev/null
+; config options
+server:
+ target-fetch-policy: "0 0 0 0 0"
+ prefetch: "yes"
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test resolver prefetch of almost expired data
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+ ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 40
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 3600 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 3600 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 3600 IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 50 100
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 3600 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 3600 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 3600 IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 3600 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 3600 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 3600 IN A 1.2.3.4
+ENTRY_END
+
+; after 1800 secs still the cached answer
+STEP 20 TIME_PASSES ELAPSE 1800
+
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+; recursion happens here.
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 1800 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 1800 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 1800 IN A 1.2.3.4
+ENTRY_END
+
+; after 1440 we are 360 seconds before the expiry
+; (the authority changes behind the scenes to detect new lookup)
+STEP 50 TIME_PASSES ELAPSE 1440
+
+STEP 60 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+; recursion happens here.
+STEP 70 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 360 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 360 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 360 IN A 1.2.3.4
+ENTRY_END
+STEP 80 TRAFFIC
+; let traffic flow for prefetch to happen
+
+; above a cache reply with 10% of the original TTL
+; but the actual cache is changed, try to get that
+STEP 120 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+; recursion happens here.
+STEP 130 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 3600 IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. 3600 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 3600 IN A 1.2.3.4
+ENTRY_END
+
+SCENARIO_END
struct regional* region;
/** failure reason information if val-log-level is high */
struct config_strlist* errinf;
+ /** how many seconds before expiry is this prefetched (0 if not) */
+ uint32_t prefetch_leeway;
/** which module is executing */
int curmod;
/* store results in cache */
if(qstate->query_flags&BIT_RD) {
if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
- vq->orig_msg->rep, 0)) {
+ vq->orig_msg->rep, 0, qstate->prefetch_leeway)) {
log_err("out of memory caching validator results");
}
} else {
/* for a referral, store the verified RRsets */
+ /* and this does not get prefetched, so no leeway */
if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
- vq->orig_msg->rep, 1)) {
+ vq->orig_msg->rep, 1, 0)) {
log_err("out of memory caching validator results");
}
}