From 263116238285bd099ea97cb833914980b504a7ba Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 23 Sep 2024 11:59:58 +0200 Subject: [PATCH 1/7] - Fix cache update when serve expired is used in order to not evict still usable expired records. Modules are forbidden to update the cache if their answer is DNSSEC unchecked or bogus and a valid (expired) entry already exists. Bogus replies from the validator are also discarded in favor of existing (expired) valid replies. --- cachedb/cachedb.c | 2 - daemon/remote.c | 4 + daemon/worker.c | 18 +- dns64/dns64.c | 1 + iterator/iterator.c | 23 +- services/cache/dns.c | 51 ++- services/cache/rrset.c | 10 +- services/mesh.c | 13 +- services/mesh.h | 3 +- services/rpz.c | 4 + testcode/unitmain.c | 2 +- testdata/serve_expired_cached_servfail.rpl | 2 +- .../serve_expired_cached_servfail_refresh.rpl | 2 +- .../serve_expired_client_timeout_servfail.rpl | 83 ++++- ...serve_expired_client_timeout_val_bogus.rpl | 317 ++++++++++++++++++ ...client_timeout_val_insecure_delegation.rpl | 247 ++++++++++++++ testdata/serve_expired_val_bogus.rpl | 316 +++++++++++++++++ util/data/msgreply.c | 32 +- util/data/msgreply.h | 24 +- util/module.h | 6 +- validator/validator.c | 41 ++- 21 files changed, 1133 insertions(+), 68 deletions(-) create mode 100644 testdata/serve_expired_client_timeout_val_bogus.rpl create mode 100644 testdata/serve_expired_client_timeout_val_insecure_delegation.rpl create mode 100644 testdata/serve_expired_val_bogus.rpl diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index 7a07b9976..cf5143ee3 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -828,8 +828,6 @@ cachedb_handle_query(struct module_qstate* qstate, /* In case we have expired data but there is a client timer for expired * answers, pass execution to next module in order to try updating the * data first. - * TODO: this needs revisit. The expired data stored from cachedb has - * 0 TTL which is picked up by iterator later when looking in the cache. */ if(qstate->env->cfg->serve_expired && msg_expired) { qstate->return_msg = NULL; diff --git a/daemon/remote.c b/daemon/remote.c index 855b1f963..5af03328e 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1953,6 +1953,8 @@ bogus_del_msg(struct lruhash_entry* e, void* arg) struct reply_info* d = (struct reply_info*)e->data; if(d->security == sec_status_bogus) { d->ttl = inf->expired; + d->prefetch_ttl = inf->expired; + d->serve_expired_ttl = inf->expired; inf->num_msgs++; #ifdef USE_CACHEDB if(inf->remcachedb && inf->worker->env.cachedb_enabled) @@ -2035,6 +2037,8 @@ negative_del_msg(struct lruhash_entry* e, void* arg) * or NOERROR rcode with ANCOUNT==0: a NODATA answer */ if(FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) { d->ttl = inf->expired; + d->prefetch_ttl = inf->expired; + d->serve_expired_ttl = inf->expired; inf->num_msgs++; #ifdef USE_CACHEDB if(inf->remcachedb && inf->worker->env.cachedb_enabled) diff --git a/daemon/worker.c b/daemon/worker.c index 5e6b2a656..4fddb180a 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -661,22 +661,18 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, if(rep->ttl < timenow) { /* Check if we need to serve expired now */ if(worker->env.cfg->serve_expired && - !worker->env.cfg->serve_expired_client_timeout + /* if serve-expired-client-timeout is set, serve + * an expired record without attempting recursion + * if the serve_expired_norec_ttl is set for the record + * as we know that recursion is currently failing. */ + (!worker->env.cfg->serve_expired_client_timeout || + timenow < rep->serve_expired_norec_ttl) #ifdef USE_CACHEDB && !(worker->env.cachedb_enabled && worker->env.cfg->cachedb_check_when_serve_expired) #endif ) { - if(worker->env.cfg->serve_expired_ttl && - rep->serve_expired_ttl < timenow) - return 0; - /* Ignore expired failure answers */ - if(FLAGS_GET_RCODE(rep->flags) != - LDNS_RCODE_NOERROR && - FLAGS_GET_RCODE(rep->flags) != - LDNS_RCODE_NXDOMAIN && - FLAGS_GET_RCODE(rep->flags) != - LDNS_RCODE_YXDOMAIN) + if(!reply_info_can_use_expired(rep, timenow)) return 0; if(!rrset_array_lock(rep->ref, rep->rrset_count, 0)) return 0; diff --git a/dns64/dns64.c b/dns64/dns64.c index cfb6ce63e..d72ef4f96 100644 --- a/dns64/dns64.c +++ b/dns64/dns64.c @@ -847,6 +847,7 @@ dns64_adjust_a(int id, struct module_qstate* super, struct module_qstate* qstate */ cp = construct_reply_info_base(super->region, rep->flags, rep->qdcount, rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl, + rep->serve_expired_norec_ttl, rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets, rep->rrset_count, rep->security, LDNS_EDE_NONE); if(!cp) diff --git a/iterator/iterator.c b/iterator/iterator.c index d566a7998..0b66db8a6 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -322,16 +322,21 @@ error_response_cache(struct module_qstate* qstate, int id, int rcode) qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, qstate->qinfo.qclass, qstate->query_flags, 0, - qstate->env->cfg->serve_expired_ttl_reset)) != NULL) { + qstate->env->cfg->serve_expired)) != NULL) { struct reply_info* rep = (struct reply_info*)msg->entry.data; - if(qstate->env->cfg->serve_expired && - qstate->env->cfg->serve_expired_ttl_reset && rep && - *qstate->env->now + qstate->env->cfg->serve_expired_ttl - > rep->serve_expired_ttl) { - verbose(VERB_ALGO, "reset serve-expired-ttl for " + if(qstate->env->cfg->serve_expired && rep) { + if(qstate->env->cfg->serve_expired_ttl_reset && + *qstate->env->now + qstate->env->cfg->serve_expired_ttl + > rep->serve_expired_ttl) { + verbose(VERB_ALGO, "reset serve-expired-ttl for " + "response in cache"); + rep->serve_expired_ttl = *qstate->env->now + + qstate->env->cfg->serve_expired_ttl; + } + verbose(VERB_ALGO, "set serve-expired-norec-ttl for " "response in cache"); - rep->serve_expired_ttl = *qstate->env->now + - qstate->env->cfg->serve_expired_ttl; + rep->serve_expired_norec_ttl = NORR_TTL + + *qstate->env->now; } if(rep && (FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR || @@ -4046,6 +4051,8 @@ processClassResponse(struct module_qstate* qstate, int id, to->rep->prefetch_ttl = from->rep->prefetch_ttl; if(from->rep->serve_expired_ttl < to->rep->serve_expired_ttl) to->rep->serve_expired_ttl = from->rep->serve_expired_ttl; + if(from->rep->serve_expired_norec_ttl < to->rep->serve_expired_norec_ttl) + to->rep->serve_expired_norec_ttl = from->rep->serve_expired_norec_ttl; } /* are we done? */ foriq->num_current_queries --; diff --git a/services/cache/dns.c b/services/cache/dns.c index e79002b79..e266dde17 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -162,7 +162,7 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, size_t i; /* store RRsets */ - for(i=0; irrset_count; i++) { + for(i=0; irrset_count; i++) { rep->ref[i].key = rep->rrsets[i]; rep->ref[i].id = rep->rrsets[i]->id; } @@ -197,6 +197,7 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, reply_info_sortref(rep); if(!(e = query_info_entrysetup(qinfo, rep, hash))) { log_err("store_msg: malloc failed"); + reply_info_delete(rep, NULL); return; } slabhash_insert(env->msg_cache, hash, &e->entry, rep, env->alloc); @@ -607,22 +608,8 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, 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; - } - /* 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 { + if(!allow_expired || !reply_info_can_use_expired(r, now)) 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; @@ -641,6 +628,7 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, else msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; + msg->rep->serve_expired_norec_ttl = 0; msg->rep->security = r->security; msg->rep->an_numrrsets = r->an_numrrsets; msg->rep->ns_numrrsets = r->ns_numrrsets; @@ -724,6 +712,7 @@ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region, msg->rep->ttl = d->ttl - now; msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; + msg->rep->serve_expired_norec_ttl = 0; msg->rep->security = sec_status_unchecked; msg->rep->an_numrrsets = 1; msg->rep->ns_numrrsets = 0; @@ -763,6 +752,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region, msg->rep->ttl = d->ttl - now; msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; + msg->rep->serve_expired_norec_ttl = 0; msg->rep->security = sec_status_unchecked; msg->rep->an_numrrsets = 1; msg->rep->ns_numrrsets = 0; @@ -1070,6 +1060,35 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, struct regional* region, uint32_t flags, time_t qstarttime) { struct reply_info* rep = NULL; + if(SERVE_EXPIRED) { + /* We are serving expired records. Before caching, check if a + * useful expired record exists. */ + struct msgreply_entry* e = msg_cache_lookup(env, + msgqinf->qname, msgqinf->qname_len, msgqinf->qtype, + msgqinf->qclass, flags, 0, 0); + if(e) { + struct reply_info* cached = e->entry.data; + if(cached->ttl < *env->now + && reply_info_can_use_expired(cached, *env->now) + /* If we are validating make sure only + * validating modules can update such messages. + * In that case don't cache it and let a + * subsequent module handle the caching. For + * example, the iterator should not replace an + * expired secure answer with a fresh unchecked + * one and let the validator manage caching. */ + && cached->security != sec_status_bogus + && (env->need_to_validate && + msgrep->security == sec_status_unchecked)) { + verbose(VERB_ALGO, "a validated expired entry " + "could be overwritten, skip caching " + "the new message at this stage"); + lock_rw_unlock(&e->entry.lock); + return 1; + } + lock_rw_unlock(&e->entry.lock); + } + } /* alloc, malloc properly (not in region, like msg is) */ rep = reply_info_copy(msgrep, env->alloc, NULL); if(!rep) diff --git a/services/cache/rrset.c b/services/cache/rrset.c index 2c03214c8..a05ae5a56 100644 --- a/services/cache/rrset.c +++ b/services/cache/rrset.c @@ -128,8 +128,8 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) { struct packed_rrset_data* newd = (struct packed_rrset_data*)nd; struct packed_rrset_data* cached = (struct packed_rrset_data*)cd; - /* o if new data is expired, current data is better */ - if( newd->ttl < timenow && cached->ttl >= timenow) + /* o if new data is expired, cached data is better */ + if( newd->ttl < timenow && timenow <= cached->ttl) return 0; /* o store if rrset has been validated * everything better than bogus data @@ -140,9 +140,9 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) if( cached->security == sec_status_bogus && newd->security != sec_status_bogus && !equal) return 1; - /* o if current RRset is more trustworthy - insert it */ + /* o if new RRset is more trustworthy - insert it */ if( newd->trust > cached->trust ) { - /* if the cached rrset is bogus, and this one equal, + /* if the cached rrset is bogus, and new is equal, * do not update the TTL - let it expire. */ if(equal && cached->ttl >= timenow && cached->security == sec_status_bogus) @@ -155,7 +155,7 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) /* o same trust, but different in data - insert it */ if( newd->trust == cached->trust && !equal ) { /* if this is type NS, do not 'stick' to owner that changes - * the NS RRset, but use the old TTL for the new data, and + * the NS RRset, but use the cached TTL for the new data, and * update to fetch the latest data. ttl is not expired, because * that check was before this one. */ if(ns) { diff --git a/services/mesh.c b/services/mesh.c index 522118844..d512ab3d3 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -311,7 +311,7 @@ int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf) struct dns_msg* mesh_serve_expired_lookup(struct module_qstate* qstate, - struct query_info* lookup_qinfo) + struct query_info* lookup_qinfo, int* is_expired) { hashvalue_type h; struct lruhash_entry* e; @@ -321,6 +321,7 @@ mesh_serve_expired_lookup(struct module_qstate* qstate, time_t timenow = *qstate->env->now; int must_validate = (!(qstate->query_flags&BIT_CD) || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; + *is_expired = 0; /* Lookup cache */ h = query_info_hash(lookup_qinfo, qstate->query_flags); e = slabhash_lookup(qstate->env->msg_cache, h, lookup_qinfo, 0); @@ -328,6 +329,7 @@ mesh_serve_expired_lookup(struct module_qstate* qstate, key = (struct msgreply_entry*)e->key; data = (struct reply_info*)e->data; + if(data->ttl < timenow) *is_expired = 1; msg = tomsg(qstate->env, &key->key, data, qstate->region, timenow, qstate->env->cfg->serve_expired, qstate->env->scratch); if(!msg) @@ -2176,6 +2178,7 @@ mesh_serve_expired_callback(void* arg) int must_validate = (!(qstate->query_flags&BIT_CD) || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; int i = 0; + int is_expired; 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); @@ -2193,7 +2196,7 @@ mesh_serve_expired_callback(void* arg) 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); + lookup_qinfo, &is_expired); if(!msg) return; /* Reset these in case we pass a second time from here. */ @@ -2285,8 +2288,10 @@ mesh_serve_expired_callback(void* arg) /* Add EDE Stale Answer (RCF8914). Ignore global ede as this is * warning instead of an error */ - if (r->edns.edns_present && qstate->env->cfg->ede_serve_expired && - qstate->env->cfg->ede) { + if(r->edns.edns_present && + qstate->env->cfg->ede_serve_expired && + qstate->env->cfg->ede && + is_expired) { edns_opt_list_append_ede(&r->edns.opt_list_out, mstate->s.region, LDNS_EDE_STALE_ANSWER, NULL); } diff --git a/services/mesh.h b/services/mesh.h index 5bd53e065..26ececbe6 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -673,11 +673,12 @@ void mesh_serve_expired_callback(void* arg); * 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. + * @param is_expired: set if the cached answer is expired. * @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); + struct query_info* lookup_qinfo, int* is_expired); /** * See if the mesh has space for more queries. You can allocate queries diff --git a/services/rpz.c b/services/rpz.c index 1ee143f84..3b92ee538 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -1969,6 +1969,7 @@ rpz_synthesize_nodata(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ + 0, /* norecttl */ 0, /* an */ 0, /* ns */ 0, /* ar */ @@ -1999,6 +2000,7 @@ rpz_synthesize_nxdomain(struct rpz* r, struct module_qstate* ms, 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ + 0, /* norecttl */ 0, /* an */ 0, /* ns */ 0, /* ar */ @@ -2031,6 +2033,7 @@ rpz_synthesize_localdata_from_rrset(struct rpz* ATTR_UNUSED(r), struct module_qs 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ + 0, /* norecttl */ 1, /* an */ 0, /* ns */ 0, /* ar */ @@ -2176,6 +2179,7 @@ rpz_synthesize_cname_override_msg(struct rpz* r, struct module_qstate* ms, 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ + 0, /* norecttl */ 1, /* an */ 0, /* ns */ 0, /* ar */ diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 084c12b93..9129d722b 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1232,7 +1232,7 @@ static void edns_ede_answer_encode_test(void) unit_assert(region); rep = construct_reply_info_base(region, LDNS_RCODE_NOERROR | BIT_QR, 1, - 3600, 3600, 3600, + 3600, 3600, 3600, 0, 0, 0, 0, 0, sec_status_unchecked, LDNS_EDE_NONE); unit_assert(rep); diff --git a/testdata/serve_expired_cached_servfail.rpl b/testdata/serve_expired_cached_servfail.rpl index f5f4c7030..edec74479 100644 --- a/testdata/serve_expired_cached_servfail.rpl +++ b/testdata/serve_expired_cached_servfail.rpl @@ -38,7 +38,7 @@ RANGE_BEGIN 0 20 RANGE_END ; ns.example.com. -RANGE_BEGIN 30 100 +RANGE_BEGIN 40 100 ADDRESS 1.2.3.4 ENTRY_BEGIN MATCH opcode qtype qname diff --git a/testdata/serve_expired_cached_servfail_refresh.rpl b/testdata/serve_expired_cached_servfail_refresh.rpl index 9b7c1fda1..4d14dd948 100644 --- a/testdata/serve_expired_cached_servfail_refresh.rpl +++ b/testdata/serve_expired_cached_servfail_refresh.rpl @@ -15,7 +15,7 @@ stub-zone: stub-addr: 1.2.3.4 CONFIG_END -SCENARIO_BEGIN Test serve-expired with client-timeout and a SERVFAIL upstream reply +SCENARIO_BEGIN Test serve-expired without client-timeout and a SERVFAIL upstream reply ; Scenario overview: ; - query for example.com. IN A ; - answer from upstream is SERVFAIL; will be cached for NORR_TTL(5) diff --git a/testdata/serve_expired_client_timeout_servfail.rpl b/testdata/serve_expired_client_timeout_servfail.rpl index 51aa04370..cea216d4c 100644 --- a/testdata/serve_expired_client_timeout_servfail.rpl +++ b/testdata/serve_expired_client_timeout_servfail.rpl @@ -22,7 +22,13 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and a SERVFAIL upstream re ; - check that we get an answer for example.com. IN A with the correct TTL ; - query again right after the TTL expired ; - answer from upstream is servfail -; - check that we get the expired cached answer instead +; - (expired cached answer will not be replaced, instead marked as unresolvable for NORR_TTL(5)) +; - check that we get the expired cached answer +; - query again (the answer is available on the upstream server now) +; - check that we get the immediate expired answer back instead +; - (the upstream query does happen after the expired reply and updates the cache) +; - query again (the upstream has no answer) +; - check that we get the freshly cached answer ; ns.example.com. RANGE_BEGIN 0 20 @@ -55,7 +61,7 @@ RANGE_BEGIN 0 20 RANGE_END ; ns.example.com. -RANGE_BEGIN 30 70 +RANGE_BEGIN 30 40 ADDRESS 1.2.3.4 ; response to A query ENTRY_BEGIN @@ -67,6 +73,25 @@ RANGE_BEGIN 30 70 ENTRY_END RANGE_END +; ns.example.com. +RANGE_BEGIN 50 60 + ADDRESS 1.2.3.4 + ; response to A query + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + example.com. IN A + SECTION ANSWER + example.com. 10 IN A 5.6.7.8 + SECTION AUTHORITY + example.com. 10 IN NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. 10 IN A 1.2.3.4 + ENTRY_END +RANGE_END + ; Query with RD flag STEP 1 QUERY ENTRY_BEGIN @@ -101,7 +126,7 @@ ENTRY_BEGIN example.com. IN A ENTRY_END -; Check that we got a stale answer +; Check that we got a stale answer because of the upstream SERVFAIL STEP 40 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl ede=3 @@ -116,4 +141,56 @@ ENTRY_BEGIN ns.example.com. 123 IN A 1.2.3.4 ENTRY_END +; Query again +STEP 50 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + example.com. IN A +ENTRY_END + +; Allow for upstream query to resolve. +STEP 51 TRAFFIC + +; Check that we got an immediate stale answer because of the previous failure, +; regardless if upstream has the answer already in this range. The query will +; be resolved after the immediate cached answer and will cache the result. +STEP 60 CHECK_ANSWER +ENTRY_BEGIN + MATCH all ttl ede=3 + REPLY QR RD RA DO NOERROR + SECTION QUESTION + example.com. IN A + SECTION ANSWER + example.com. 123 IN A 5.6.7.8 + SECTION AUTHORITY + example.com. 123 IN NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. 123 IN A 1.2.3.4 +ENTRY_END + +; Query again +STEP 70 QUERY +ENTRY_BEGIN + REPLY RD + SECTION QUESTION + example.com. IN A +ENTRY_END + +; Check that we got the cached updated answer from the previous step since +; there is no upstream in this range. +STEP 80 CHECK_ANSWER +ENTRY_BEGIN + MATCH all ttl + REPLY QR RD RA NOERROR + SECTION QUESTION + example.com. IN A + SECTION ANSWER + example.com. 10 IN A 5.6.7.8 + SECTION AUTHORITY + example.com. 10 IN NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. 10 IN A 1.2.3.4 +ENTRY_END + SCENARIO_END diff --git a/testdata/serve_expired_client_timeout_val_bogus.rpl b/testdata/serve_expired_client_timeout_val_bogus.rpl new file mode 100644 index 000000000..f4937a16c --- /dev/null +++ b/testdata/serve_expired_client_timeout_val_bogus.rpl @@ -0,0 +1,317 @@ +; config options +; The island of trust is at example.com +server: + trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" + val-override-date: "20070916134226" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + + serve-expired: yes + serve-expired-client-timeout: 1 + serve-expired-reply-ttl: 123 + ede: yes + ede-serve-expired: yes + + # No need for AAAA nameserver queries + do-ip6: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test serve-expired with client-timeout and bogus answer +; Scenario overview: +; - query for www.example.com. IN A +; - check the answer +; - wait for the record to expire +; - (upstream now has a bogus response) +; - query again for www.example.com. IN A +; - check that we get the expired valid response instead +; - query once more +; - (upstream has the valid response again) +; - check that we get the immediate expired valid response +; - (the prefetch query updates the cache with the valid response) +; - query one last time +; - check that we get the immediate valid cache response; upstream does not have an answer at this moment + +;; +;; 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 + ADJUST copy_id copy_query + 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 + ADJUST copy_id copy_query + 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. with generic valid data +;; +RANGE_BEGIN 0 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. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} + ENTRY_END + + ; response to DNSKEY priming query + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + example.com. IN DNSKEY + SECTION ANSWER + example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b} + example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} + ENTRY_END +RANGE_END + +;; +;; ns.example.com with valid data +;; +RANGE_BEGIN 0 10 + ADDRESS 1.2.3.4 + ; response to query of interest + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ENTRY_END +RANGE_END + +;; +;; ns.example.com. with bogus data +;; +RANGE_BEGIN 20 30 + ADDRESS 1.2.3.4 + ; response to query of interest (bogus answer) + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ;; (valid signature) + ;; www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ;; (bogus signature) + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. + ENTRY_END +RANGE_END + +;; +;; ns.example.com. with valid data again +;; +RANGE_BEGIN 40 60 + ADDRESS 1.2.3.4 + ; response to query of interest + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +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 AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 11 TIME_PASSES ELAPSE 3601 + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; expired answer because upstream is bogus +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. 123 IN NS ns.example.com. +example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. 123 IN A 1.2.3.4 +ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; immediate cached answer because upstream is valid again +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. 123 IN NS ns.example.com. +example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. 123 IN A 1.2.3.4 +ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +; upstream query is resolved before this query comes in +STEP 60 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; prefetch query updated the cache, since there is no upstream response in this range +STEP 70 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +SCENARIO_END diff --git a/testdata/serve_expired_client_timeout_val_insecure_delegation.rpl b/testdata/serve_expired_client_timeout_val_insecure_delegation.rpl new file mode 100644 index 000000000..6654a2c68 --- /dev/null +++ b/testdata/serve_expired_client_timeout_val_insecure_delegation.rpl @@ -0,0 +1,247 @@ +; config options +server: + trust-anchor: "example. DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJ ( j7IommWSpJABVfW8Q0rOvXdM6kzt+TAu92L9 AbsUdblMFin8CVF3n4s= )" + val-override-date: "20120420235959" + val-max-restart: 0 + outbound-msg-retry: 0 + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + rrset-roundrobin: no + + serve-expired: yes + serve-expired-client-timeout: 1 + serve-expired-reply-ttl: 123 + ede: yes + ede-serve-expired: yes + + # No need for AAAA nameserver queries + do-ip6: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test serve-expired with client-timeout and failed DNSSEC parent of insecure zone +; Scenario overview: +; - query for mc.c.example. IN MX +; - check the answer +; - wait for all the records to expire +; - (example. now has a bogus DNSKEY response) +; - query again for mc.c.example. IN MX +; - (validator fails priming the trust anchor because of the bogus DNSKEY) +; - check that we get the expired insecure response instead + +;; +;; 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 +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +. IN A +SECTION AUTHORITY +example. IN NS ns1.example. +SECTION ADDITIONAL +ns1.example. IN A 192.0.2.1 +ENTRY_END +RANGE_END + +;; +;; ns1.example. generic data +;; +RANGE_BEGIN 0 100 + ADDRESS 192.0.2.1 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id copy_query +REPLY QR REFUSED +SECTION QUESTION +example. IN NS +SECTION ANSWER +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns1.example. IN A +SECTION ANSWER +ns1.example. IN A 192.0.2.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO NOERROR +SECTION QUESTION +mc.c.example. IN MX +SECTION AUTHORITY +;; NSEC3 RR that covers the "next closer" name (c.example) +;; H(c.example) = 4g6p9u5gvfshp30pqecj98b3maqbn1ck +35mthgpgcu1qg68fab165klnsnk3dpvl.example. NSEC3 1 1 12 aabbccdd ( b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG ) +35mthgpgcu1qg68fab165klnsnk3dpvl.example. RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 ( 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQ Aynzo8EUWH+z6hEIBlUTPGj15eZll6VhQqgZ XtAIR3chwgW+SA== ) +;; NSEC3 RR that matches the closest encloser (example) +;; H(example) = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. NSEC3 1 1 12 aabbccdd ( 2t7b4g4vsa5smi47k61mv5bv1a22bojr MX DNSKEY NS SOA NSEC3PARAM RRSIG ) +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 ( 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKL IBHYH6blRxK9rC0bMJPwQ4mLIuw85H2EY762 BOCXJZMnpuwhpA== ) +c.example. NS ns1.c.example. +SECTION ADDITIONAL +ns1.c.example. A 192.0.2.7 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO NOERROR +SECTION QUESTION +c.example. IN DS +SECTION AUTHORITY +;; NSEC3 RR that covers the "next closer" name (c.example) +;; H(c.example) = 4g6p9u5gvfshp30pqecj98b3maqbn1ck +35mthgpgcu1qg68fab165klnsnk3dpvl.example. NSEC3 1 1 12 aabbccdd ( b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG ) +35mthgpgcu1qg68fab165klnsnk3dpvl.example. RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 ( 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQ Aynzo8EUWH+z6hEIBlUTPGj15eZll6VhQqgZ XtAIR3chwgW+SA== ) +;; NSEC3 RR that matches the closest encloser (example) +;; H(example) = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. NSEC3 1 1 12 aabbccdd ( 2t7b4g4vsa5smi47k61mv5bv1a22bojr MX DNSKEY NS SOA NSEC3PARAM RRSIG ) +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 ( 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKL IBHYH6blRxK9rC0bMJPwQ4mLIuw85H2EY762 BOCXJZMnpuwhpA== ) +ENTRY_END +RANGE_END + +;; +;; ns1.example. with valid DNSKEY data +;; +RANGE_BEGIN 0 20 + ADDRESS 192.0.2.1 +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example. IN DNSKEY +SECTION ANSWER +example. DNSKEY 256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LU ( sAD0QPWU+wzt89epO6tHzkMBVDkC7qphQO2h TY4hHn9npWFRw5BYubE= ) +example. DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJ ( j7IommWSpJABVfW8Q0rOvXdM6kzt+TAu92L9 AbsUdblMFin8CVF3n4s= ) +example. RRSIG DNSKEY 7 1 3600 20150420235959 ( 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31 uzMZ/U/FpsUb8aC6QZS+sTsJXnLnz7flGOsm MGQZf3bH+QsCtg== ) +ENTRY_END +RANGE_END + +;; +;; ns1.example. with bogus DNSKEY data +;; +RANGE_BEGIN 30 40 + ADDRESS 192.0.2.1 +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example. IN DNSKEY +SECTION ANSWER +example. DNSKEY 256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LU ( sAD0QPWU+wzt89epO6tHzkMBVDkC7qphQO2h TY4hHn9npWFRw5BYubE= ) +example. DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJ ( j7IommWSpJABVfW8Q0rOvXdM6kzt+TAu92L9 AbsUdblMFin8CVF3n4s= ) +;; (bogus signature) +example. RRSIG DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. +ENTRY_END +RANGE_END + +;; +;; ns1.c.example. +;; +RANGE_BEGIN 0 100 + ADDRESS 192.0.2.7 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +c.example. IN NS +SECTION ANSWER +c.example. NS ns1.c.example. +SECTION ADDITIONAL +ns1.c.example. A 192.0.2.7 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +mc.c.example. IN MX +SECTION ANSWER +mc.c.example. IN MX 50 mx.c.example. +SECTION AUTHORITY +c.example. NS ns1.c.example. +SECTION ADDITIONAL +ns1.c.example. A 192.0.2.7 +ENTRY_END +RANGE_END + + +STEP 0 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +mc.c.example. IN MX +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +mc.c.example. IN MX +SECTION ANSWER +mc.c.example. IN MX 50 mx.c.example. +SECTION AUTHORITY +c.example. NS ns1.c.example. +SECTION ADDITIONAL +ns1.c.example. A 192.0.2.7 +ENTRY_END + +STEP 20 TIME_PASSES ELAPSE 3601 + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +mc.c.example. IN MX +ENTRY_END + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR +SECTION QUESTION +mc.c.example. IN MX +SECTION ANSWER +mc.c.example. 123 IN MX 50 mx.c.example. +SECTION AUTHORITY +c.example. 123 NS ns1.c.example. +SECTION ADDITIONAL +ns1.c.example. 123 A 192.0.2.7 +ENTRY_END + +SCENARIO_END diff --git a/testdata/serve_expired_val_bogus.rpl b/testdata/serve_expired_val_bogus.rpl new file mode 100644 index 000000000..35365beef --- /dev/null +++ b/testdata/serve_expired_val_bogus.rpl @@ -0,0 +1,316 @@ +; config options +; The island of trust is at example.com +server: + trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" + val-override-date: "20070916134226" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + + serve-expired: yes + serve-expired-reply-ttl: 123 + ede: yes + ede-serve-expired: yes + + # No need for AAAA nameserver queries + do-ip6: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test serve-expired with client-timeout and bogus answer +; Scenario overview: +; - query for www.example.com. IN A +; - check the answer +; - wait for the record to expire +; - (upstream now has a bogus response) +; - query again for www.example.com. IN A +; - check that we get the immediate expired valid response +; - (prefetch response is bogus and is not cached) +; - query once more +; - check that we still get the immediate expired valid response and not the fresh bogus one +; - (upstream has a valid response again; prefetch will update the cache) +; - query one last time +; - check that we get an immediate valid cache response + +;; +;; 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 + ADJUST copy_id copy_query + 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 + ADJUST copy_id copy_query + 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. with generic data +;; +RANGE_BEGIN 0 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. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} + ENTRY_END + + ; response to DNSKEY priming query + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + example.com. IN DNSKEY + SECTION ANSWER + example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b} + example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} + ENTRY_END +RANGE_END + +;; +;; ns.example.com. with valid data +;; +RANGE_BEGIN 0 10 + ADDRESS 1.2.3.4 + ; response to query of interest + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ENTRY_END +RANGE_END + +;; +;; ns.example.com. with bogus data +;; +RANGE_BEGIN 20 40 + ADDRESS 1.2.3.4 + ; response to query of interest (bogus answer) + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + ;; (valid signature) + ;; www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ;; (bogus signature) + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. + ENTRY_END +RANGE_END + +;; +;; ns.example.com. with valid data again +;; +RANGE_BEGIN 50 100 + ADDRESS 1.2.3.4 + ; response to query of interest + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + www.example.com. IN A + SECTION ANSWER + www.example.com. IN A 10.20.30.40 + ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} + SECTION AUTHORITY + example.com. IN NS ns.example.com. + example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 + www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} + ENTRY_END +RANGE_END + + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is the valid answer +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 11 TIME_PASSES ELAPSE 3601 + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is the immediate expired cache response +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. 123 IN NS ns.example.com. +example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. 123 IN A 1.2.3.4 +ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is still the immediate cache response because the previous upstream response was bogus +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. 123 IN NS ns.example.com. +example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. 123 IN A 1.2.3.4 +ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 60 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is the immediate cache response because the previous upstream response was valid +STEP 70 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +SCENARIO_END diff --git a/util/data/msgreply.c b/util/data/msgreply.c index c9d7bbf3a..bd8608ff7 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -95,8 +95,9 @@ parse_create_qinfo(sldns_buffer* pkt, struct msg_parse* msg, /** constructor for replyinfo */ struct reply_info* construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd, - time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns, - size_t ar, size_t total, enum sec_status sec, sldns_ede_code reason_bogus) + time_t ttl, time_t prettl, time_t expttl, time_t norecttl, size_t an, + size_t ns, size_t ar, size_t total, enum sec_status sec, + sldns_ede_code reason_bogus) { struct reply_info* rep; /* rrset_count-1 because the first ref is part of the struct. */ @@ -114,6 +115,7 @@ construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd, rep->ttl = ttl; rep->prefetch_ttl = prettl; rep->serve_expired_ttl = expttl; + rep->serve_expired_norec_ttl = norecttl; rep->an_numrrsets = an; rep->ns_numrrsets = ns; rep->ar_numrrsets = ar; @@ -139,8 +141,8 @@ static int parse_create_repinfo(struct msg_parse* msg, struct reply_info** rep, struct regional* region) { - *rep = construct_reply_info_base(region, msg->flags, msg->qdcount, 0, - 0, 0, msg->an_rrsets, msg->ns_rrsets, msg->ar_rrsets, + *rep = construct_reply_info_base(region, msg->flags, msg->qdcount, 0, + 0, 0, 0, msg->an_rrsets, msg->ns_rrsets, msg->ar_rrsets, msg->rrset_count, sec_status_unchecked, LDNS_EDE_NONE); if(!*rep) return 0; @@ -171,6 +173,19 @@ reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, return 1; } +int +reply_info_can_use_expired(struct reply_info* rep, time_t timenow) +{ + log_assert(rep->ttl < timenow); + /* Really expired */ + if(SERVE_EXPIRED_TTL && rep->serve_expired_ttl < timenow) return 0; + /* Ignore expired failure answers */ + if(FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_NOERROR && + FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_NXDOMAIN && + FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_YXDOMAIN) return 0; + return 1; +} + struct reply_info * make_new_reply_info(const struct reply_info* rep, struct regional* region, size_t an_numrrsets, size_t copy_rrsets) @@ -185,7 +200,8 @@ make_new_reply_info(const struct reply_info* rep, struct regional* region, * so the total number of RRsets is an_numrrsets. */ new_rep = construct_reply_info_base(region, rep->flags, rep->qdcount, rep->ttl, rep->prefetch_ttl, - rep->serve_expired_ttl, an_numrrsets, 0, 0, an_numrrsets, + rep->serve_expired_ttl, rep->serve_expired_norec_ttl, + an_numrrsets, 0, 0, an_numrrsets, sec_status_insecure, LDNS_EDE_NONE); if(!new_rep) return NULL; @@ -486,6 +502,8 @@ parse_copy_decompress(sldns_buffer* pkt, struct msg_parse* msg, } rep->prefetch_ttl = PREFETCH_TTL_CALC(rep->ttl); rep->serve_expired_ttl = rep->ttl + SERVE_EXPIRED_TTL; + /* rep->serve_expired_norec_ttl should stay at 0 */ + log_assert(rep->serve_expired_norec_ttl == 0); return 1; } @@ -568,6 +586,9 @@ reply_info_set_ttls(struct reply_info* rep, time_t timenow) rep->ttl += timenow; rep->prefetch_ttl += timenow; rep->serve_expired_ttl += timenow; + /* Don't set rep->serve_expired_norec_ttl; this should only be set + * on cached records when encountering an error */ + log_assert(rep->serve_expired_norec_ttl == 0); for(i=0; irrset_count; i++) { struct packed_rrset_data* data = (struct packed_rrset_data*) rep->ref[i].key->entry.data; @@ -763,6 +784,7 @@ reply_info_copy(struct reply_info* rep, struct alloc_cache* alloc, struct reply_info* cp; cp = construct_reply_info_base(region, rep->flags, rep->qdcount, rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl, + rep->serve_expired_norec_ttl, rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets, rep->rrset_count, rep->security, rep->reason_bogus); if(!cp) diff --git a/util/data/msgreply.h b/util/data/msgreply.h index 4b93b12c8..e10236c9f 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -145,7 +145,7 @@ struct reply_info { /** 32 bit padding to pad struct member alignment to 64 bits. */ uint32_t padding; - /** + /** * TTL of the entire reply (for negative caching). * only for use when there are 0 RRsets in this message. * if there are RRsets, check those instead. @@ -158,12 +158,18 @@ struct reply_info { */ time_t prefetch_ttl; - /** + /** * Reply TTL extended with serve expired TTL, to limit time to serve * expired message. */ time_t serve_expired_ttl; + /** + * TTL for an expired entry to be used without attempting recursion + * since a previous recursion attempt failed to update the message. + */ + time_t serve_expired_norec_ttl; + /** * The security status from DNSSEC validation of this message. */ @@ -244,6 +250,7 @@ struct msgreply_entry { * @param ttl: TTL of replyinfo * @param prettl: prefetch ttl * @param expttl: serve expired ttl + * @param norecttl: serve expired no recursion ttl * @param an: an count * @param ns: ns count * @param ar: ar count @@ -255,8 +262,8 @@ struct msgreply_entry { */ struct reply_info* construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd, - time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns, - size_t ar, size_t total, enum sec_status sec, + time_t ttl, time_t prettl, time_t expttl, time_t norecttl, size_t an, + size_t ns, size_t ar, size_t total, enum sec_status sec, sldns_ede_code reason_bogus); /** @@ -399,6 +406,15 @@ struct reply_info* reply_info_copy(struct reply_info* rep, int reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, struct regional* region); +/** + * Check if an *expired* reply info (checked by the caller already) can be used + * as an expired answer. + * @param rep: expired reply info to check. + * @param timenow: the current time. + * @return 1 if it can be used, 0 otherwise. + */ +int reply_info_can_use_expired(struct reply_info* rep, time_t timenow); + /* * Create a new reply_info based on 'rep'. The new info is based on * the passed 'rep', but ignores any rrsets except for the first 'an_numrrsets' diff --git a/util/module.h b/util/module.h index 03f3eab0b..abad3c8dd 100644 --- a/util/module.h +++ b/util/module.h @@ -319,13 +319,15 @@ typedef int inplace_cb_query_response_func_type(struct module_qstate* qstate, /** * Function called when looking for (expired) cached answers during the serve * expired logic. - * Called as func(qstate, lookup_qinfo) + * Called as func(qstate, lookup_qinfo, &is_expired) * Where: * qstate: the query state. * lookup_qinfo: the qinfo to lookup for. + * is_expired: set if the cached answer is expired. */ typedef struct dns_msg* serve_expired_lookup_func_type( - struct module_qstate* qstate, struct query_info* lookup_qinfo); + struct module_qstate* qstate, struct query_info* lookup_qinfo, + int* is_expired); /** * Module environment. diff --git a/validator/validator.c b/validator/validator.c index da9215883..1e33eae61 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -2435,6 +2435,8 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, /* if the result is bogus - set message ttl to bogus ttl to avoid * endless bogus revalidation */ if(vq->orig_msg->rep->security == sec_status_bogus) { + struct msgreply_entry* e; + /* see if we can try again to fetch data */ if(vq->restart_count < ve->max_restart) { verbose(VERB_ALGO, "validation failed, " @@ -2449,10 +2451,40 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, return 0; } + if(SERVE_EXPIRED && (e=msg_cache_lookup(qstate->env, + qstate->qinfo.qname, + qstate->qinfo.qname_len, qstate->qinfo.qtype, + qstate->qinfo.qclass, qstate->query_flags, + 0 /*now; allow expired*/, + 1 /*wr; we may update the data*/))) { + struct reply_info* rep = (struct reply_info*)e->entry.data; + if(rep && rep->security > sec_status_bogus) { + verbose(VERB_ALGO, "validation failed but " + "previously cached valid response " + "exists; set serve-expired-norec-ttl " + "for response in cache"); + rep->serve_expired_norec_ttl = NORR_TTL + + *qstate->env->now; + /* Return an error response. + * If serve-expired-client-timeout is enabled, + * the client-timeout logic will try to find an + * (expired) answer in the cache as last + * resort. If it is not enabled, expired + * answers are already used before the mesh + * activation. */ + qstate->return_rcode = LDNS_RCODE_SERVFAIL; + qstate->return_msg = NULL; + qstate->ext_state[id] = module_finished; + lock_rw_unlock(&e->entry.lock); + return 0; + } + lock_rw_unlock(&e->entry.lock); + } + vq->orig_msg->rep->ttl = ve->bogus_ttl; vq->orig_msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(vq->orig_msg->rep->ttl); - vq->orig_msg->rep->serve_expired_ttl = + vq->orig_msg->rep->serve_expired_ttl = vq->orig_msg->rep->ttl + qstate->env->cfg->serve_expired_ttl; if((qstate->env->cfg->val_log_level >= 1 || qstate->env->cfg->log_servfail) && @@ -2518,8 +2550,9 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, * to check if from parentNS */ if(!qstate->no_cache_store) { if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, - vq->orig_msg->rep, 0, qstate->prefetch_leeway, 0, NULL, - qstate->query_flags, qstate->qstarttime)) { + vq->orig_msg->rep, 0, qstate->prefetch_leeway, + 0, qstate->region, qstate->query_flags, + qstate->qstarttime)) { log_err("out of memory caching validator results"); } } @@ -2527,7 +2560,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, /* 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, 0, 0, NULL, + vq->orig_msg->rep, 1, 0, 0, qstate->region, qstate->query_flags, qstate->qstarttime)) { log_err("out of memory caching validator results"); } From dc0e6e84b6c54b0bd5dffb35fdd2142ddf3d2ddf Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 23 Sep 2024 15:26:25 +0200 Subject: [PATCH 2/7] - Also apply serve-expired-ttl-reset on usable expired valid responses --- validator/validator.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/validator/validator.c b/validator/validator.c index 1e33eae61..730d2fa8c 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -2451,20 +2451,30 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, return 0; } - if(SERVE_EXPIRED && (e=msg_cache_lookup(qstate->env, - qstate->qinfo.qname, + if(qstate->env->cfg->serve_expired && + (e=msg_cache_lookup(qstate->env, qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, qstate->qinfo.qclass, qstate->query_flags, 0 /*now; allow expired*/, 1 /*wr; we may update the data*/))) { struct reply_info* rep = (struct reply_info*)e->entry.data; - if(rep && rep->security > sec_status_bogus) { + if(rep && rep->security > sec_status_bogus && + (!qstate->env->cfg->serve_expired_ttl || + *qstate->env->now <= rep->serve_expired_ttl)) { verbose(VERB_ALGO, "validation failed but " "previously cached valid response " "exists; set serve-expired-norec-ttl " "for response in cache"); rep->serve_expired_norec_ttl = NORR_TTL + *qstate->env->now; + if(qstate->env->cfg->serve_expired_ttl_reset && + *qstate->env->now + qstate->env->cfg->serve_expired_ttl + > rep->serve_expired_ttl) { + verbose(VERB_ALGO, "reset serve-expired-ttl for " + "valid response in cache"); + rep->serve_expired_ttl = *qstate->env->now + + qstate->env->cfg->serve_expired_ttl; + } /* Return an error response. * If serve-expired-client-timeout is enabled, * the client-timeout logic will try to find an From 57316fb89045e8fda7914862f5a05df46b203165 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 23 Sep 2024 16:01:47 +0200 Subject: [PATCH 3/7] - server-expired-ttl-reset should work for expired records regardless of their expired TTL value. --- validator/validator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/validator/validator.c b/validator/validator.c index 730d2fa8c..857510b65 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -2460,6 +2460,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, struct reply_info* rep = (struct reply_info*)e->entry.data; if(rep && rep->security > sec_status_bogus && (!qstate->env->cfg->serve_expired_ttl || + qstate->env->cfg->serve_expired_ttl_reset || *qstate->env->now <= rep->serve_expired_ttl)) { verbose(VERB_ALGO, "validation failed but " "previously cached valid response " From fa11d1328567f905f60a95e302d844efa9caf0f9 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 23 Sep 2024 18:13:32 +0200 Subject: [PATCH 4/7] - Fix write access for lock. --- services/cache/dns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/cache/dns.c b/services/cache/dns.c index e266dde17..9f1624266 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -1065,7 +1065,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, * useful expired record exists. */ struct msgreply_entry* e = msg_cache_lookup(env, msgqinf->qname, msgqinf->qname_len, msgqinf->qtype, - msgqinf->qclass, flags, 0, 0); + msgqinf->qclass, flags, 0, 1); if(e) { struct reply_info* cached = e->entry.data; if(cached->ttl < *env->now From 52d424f188947754c99ea8a02816ca5280baca0a Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 23 Sep 2024 18:19:44 +0200 Subject: [PATCH 5/7] Revert "- Fix write access for lock." This reverts commit fa11d1328567f905f60a95e302d844efa9caf0f9. --- services/cache/dns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/cache/dns.c b/services/cache/dns.c index 9f1624266..e266dde17 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -1065,7 +1065,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, * useful expired record exists. */ struct msgreply_entry* e = msg_cache_lookup(env, msgqinf->qname, msgqinf->qname_len, msgqinf->qtype, - msgqinf->qclass, flags, 0, 1); + msgqinf->qclass, flags, 0, 0); if(e) { struct reply_info* cached = e->entry.data; if(cached->ttl < *env->now From aaecdf39ec50e21db8322ace9a4802b18a942acd Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 24 Sep 2024 10:35:19 +0200 Subject: [PATCH 6/7] - serve-expired-ttl-reset should try to keep expired records in the cache in case they are reset --- daemon/worker.c | 2 +- services/cache/dns.c | 4 +- testdata/serve_expired_ttl_reset.rpl | 102 +++++++++++++++++++++++++++ util/config_file.c | 5 +- util/data/msgparse.h | 2 + util/data/msgreply.c | 17 ++++- util/data/msgreply.h | 15 +++- 7 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 testdata/serve_expired_ttl_reset.rpl diff --git a/daemon/worker.c b/daemon/worker.c index 4fddb180a..fe105eb7b 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -672,7 +672,7 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, worker->env.cfg->cachedb_check_when_serve_expired) #endif ) { - if(!reply_info_can_use_expired(rep, timenow)) + if(!reply_info_can_answer_expired(rep, timenow)) return 0; if(!rrset_array_lock(rep->ref, rep->rrset_count, 0)) return 0; diff --git a/services/cache/dns.c b/services/cache/dns.c index e266dde17..af4280035 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -608,7 +608,7 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, time_t now_control = now; if(now > r->ttl) { /* Check if we are allowed to serve expired */ - if(!allow_expired || !reply_info_can_use_expired(r, now)) + if(!allow_expired || !reply_info_can_answer_expired(r, now)) return NULL; /* Change the current time so we can pass the below TTL checks when * serving expired data. */ @@ -1069,7 +1069,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, if(e) { struct reply_info* cached = e->entry.data; if(cached->ttl < *env->now - && reply_info_can_use_expired(cached, *env->now) + && reply_info_could_use_expired(cached, *env->now) /* If we are validating make sure only * validating modules can update such messages. * In that case don't cache it and let a diff --git a/testdata/serve_expired_ttl_reset.rpl b/testdata/serve_expired_ttl_reset.rpl new file mode 100644 index 000000000..521d5a0f0 --- /dev/null +++ b/testdata/serve_expired_ttl_reset.rpl @@ -0,0 +1,102 @@ +; config options go here. +server: + serve-expired: yes + serve-expired-ttl: 1 + serve-expired-ttl-reset: yes + serve-expired-reply-ttl: 123 + ede: yes + ede-serve-expired: yes +forward-zone: name: "." forward-addr: 216.0.0.1 +CONFIG_END +SCENARIO_BEGIN Serve expired ttl with reset on forwarder with a timeout on upstream query +; Scenario overview: +; - Send query +; - Get reply +; - Wait for it to expire (+ serve-expired-ttl) +; - Send query again +; - Upstream timeouts +; - Error response from iterator SERVFAIL, resets expired-ttl on cache +; - Check we are getting the SERVFAIL response +; - Query again +; - Check we are getting the expired answer +; - Upstream still timeouts + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; Upstream reply +STEP 2 REPLY +ENTRY_BEGIN +REPLY QR AA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 10 IN A 0.0.0.0 +ENTRY_END + +STEP 3 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RA RD NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 10 IN A 0.0.0.0 +ENTRY_END + +; Expire the record (+ serve-expired-ttl) +STEP 4 TIME_PASSES ELAPSE 12 + +STEP 5 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; But the pending query times out! +; outbound-msg-retry times timeout. +STEP 6 TIMEOUT +STEP 7 TIMEOUT +STEP 8 TIMEOUT +STEP 9 TIMEOUT +STEP 10 TIMEOUT + +; Returns servfail +; but error response from iterator resets the expired ttl +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RA RD SERVFAIL +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; Query again +STEP 12 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; Check that we get the expired answer +STEP 13 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RA RD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 0.0.0.0 +ENTRY_END + +; But the pending query times out! +; Only one because RTT reached the limit. +STEP 16 TIMEOUT + +SCENARIO_END diff --git a/util/config_file.c b/util/config_file.c index d82e4374e..742a420b6 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -717,7 +717,9 @@ int config_set_option(struct config_file* cfg, const char* opt, SERVE_EXPIRED = cfg->serve_expired; } else if(strcmp(opt, "serve-expired-ttl:") == 0) { IS_NUMBER_OR_ZERO; cfg->serve_expired_ttl = atoi(val); SERVE_EXPIRED_TTL=(time_t)cfg->serve_expired_ttl;} - else S_YNO("serve-expired-ttl-reset:", serve_expired_ttl_reset) + else if(strcmp(opt, "serve-expired-ttl-reset:") == 0) + { IS_YES_OR_NO; cfg->serve_expired_ttl_reset = (strcmp(val, "yes") == 0); + SERVE_EXPIRED_TTL_RESET = cfg->serve_expired_ttl_reset; } else if(strcmp(opt, "serve-expired-reply-ttl:") == 0) { IS_NUMBER_OR_ZERO; cfg->serve_expired_reply_ttl = atoi(val); SERVE_EXPIRED_REPLY_TTL=(time_t)cfg->serve_expired_reply_ttl;} else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout) @@ -2391,6 +2393,7 @@ config_apply(struct config_file* config) MIN_TTL = (time_t)config->min_ttl; SERVE_EXPIRED = config->serve_expired; SERVE_EXPIRED_TTL = (time_t)config->serve_expired_ttl; + SERVE_EXPIRED_TTL_RESET = config->serve_expired_ttl_reset; SERVE_EXPIRED_REPLY_TTL = (time_t)config->serve_expired_reply_ttl; SERVE_ORIGINAL_TTL = config->serve_original_ttl; MAX_NEG_TTL = (time_t)config->max_negative_ttl; diff --git a/util/data/msgparse.h b/util/data/msgparse.h index aebd48efa..62f0d5aac 100644 --- a/util/data/msgparse.h +++ b/util/data/msgparse.h @@ -89,6 +89,8 @@ extern time_t MIN_NEG_TTL; extern int SERVE_EXPIRED; /** Time to serve records after expiration */ extern time_t SERVE_EXPIRED_TTL; +/** Reset serve expired TTL after failed update attempt */ +extern time_t SERVE_EXPIRED_TTL_RESET; /** TTL to use for expired records */ extern time_t SERVE_EXPIRED_REPLY_TTL; /** Negative cache time (for entries without any RRs.) */ diff --git a/util/data/msgreply.c b/util/data/msgreply.c index bd8608ff7..78e4fb1c3 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -67,6 +67,8 @@ time_t MIN_NEG_TTL = 0; int SERVE_EXPIRED = 0; /** Time to serve records after expiration */ time_t SERVE_EXPIRED_TTL = 0; +/** Reset serve expired TTL after failed update attempt */ +time_t SERVE_EXPIRED_TTL_RESET = 0; /** TTL to use for expired records */ time_t SERVE_EXPIRED_REPLY_TTL = 30; /** If we serve the original TTL or decrementing TTLs */ @@ -174,7 +176,7 @@ reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, } int -reply_info_can_use_expired(struct reply_info* rep, time_t timenow) +reply_info_can_answer_expired(struct reply_info* rep, time_t timenow) { log_assert(rep->ttl < timenow); /* Really expired */ @@ -186,6 +188,19 @@ reply_info_can_use_expired(struct reply_info* rep, time_t timenow) return 1; } +int reply_info_could_use_expired(struct reply_info* rep, time_t timenow) +{ + log_assert(rep->ttl < timenow); + /* Really expired */ + if(SERVE_EXPIRED_TTL && rep->serve_expired_ttl < timenow && + !SERVE_EXPIRED_TTL_RESET) return 0; + /* Ignore expired failure answers */ + if(FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_NOERROR && + FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_NXDOMAIN && + FLAGS_GET_RCODE(rep->flags) != LDNS_RCODE_YXDOMAIN) return 0; + return 1; +} + struct reply_info * make_new_reply_info(const struct reply_info* rep, struct regional* region, size_t an_numrrsets, size_t copy_rrsets) diff --git a/util/data/msgreply.h b/util/data/msgreply.h index e10236c9f..7ac6769ff 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -407,13 +407,22 @@ int reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, struct regional* region); /** - * Check if an *expired* reply info (checked by the caller already) can be used + * Check if an *expired* (checked by the caller already) reply info can be used * as an expired answer. * @param rep: expired reply info to check. * @param timenow: the current time. - * @return 1 if it can be used, 0 otherwise. + * @return 1 if it can be used as an answer, 0 otherwise. */ -int reply_info_can_use_expired(struct reply_info* rep, time_t timenow); +int reply_info_can_answer_expired(struct reply_info* rep, time_t timenow); + +/** + * Check if an *expired* (checked by the caller already) reply info could be + * useful data to stay in the cache. + * @param rep: expired reply info to check. + * @param timenow: the current time. + * @return 1 if it is useful, 0 otherwise. + */ +int reply_info_could_use_expired(struct reply_info* rep, time_t timenow); /* * Create a new reply_info based on 'rep'. The new info is based on From e90f68db694beb884f1ad858ba6d08e66eef2192 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 24 Sep 2024 15:44:01 +0200 Subject: [PATCH 7/7] - More clear description of serve_expired_norec_ttl. --- util/data/msgreply.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/data/msgreply.h b/util/data/msgreply.h index 7ac6769ff..d1c2bfa4b 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -167,6 +167,12 @@ struct reply_info { /** * TTL for an expired entry to be used without attempting recursion * since a previous recursion attempt failed to update the message. + * This is just an efficiency timer when serve-expired-client-timeout + * is configured. It will make Unbound immediately reply with the + * expired entry instead of trying resolution first. + * It is set on cached entries by modules that identified problems + * while resolving, e.g., failed upstreams from Iterator, or failed + * validation from Validator. */ time_t serve_expired_norec_ttl;