From 50fcf71f0486da0e03548151504ed4b3fd157342 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 11 Nov 2024 15:43:10 +0100 Subject: [PATCH 1/4] - ttl-zero-cacherep, Responses in the last second of their cache TTL, get an extra second. That makes the TTL not 0, since they are from cache and can be cached by the client. --- cachedb/cachedb.c | 2 +- daemon/worker.c | 4 ++-- services/authzone.c | 2 +- services/localzone.c | 2 +- services/mesh.c | 4 ++-- services/rpz.c | 2 +- testcode/unitmain.c | 2 +- .../serve_expired_client_timeout_no_prefetch.rpl | 6 +++--- util/data/msgencode.c | 13 ++++++++++++- util/data/msgencode.h | 6 +++++- 10 files changed, 29 insertions(+), 14 deletions(-) diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index eca3b7cb7..24f458e8f 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -419,7 +419,7 @@ prep_data(struct module_qstate* qstate, struct sldns_buffer* buf) qstate->return_msg->rep); if(!reply_info_answer_encode(&qstate->return_msg->qinfo, qstate->return_msg->rep, 0, qstate->query_flags, - buf, 0, 1, qstate->env->scratch, 65535, &edns, 1, 0)) + buf, 0, 1, qstate->env->scratch, 65535, &edns, 1, 0, 1)) return 0; /* TTLs in the return_msg are relative to time(0) so we have to diff --git a/daemon/worker.c b/daemon/worker.c index 713de3163..7bce4d5a1 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -564,7 +564,7 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, } if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags, repinfo->c->buffer, 0, 1, worker->scratchpad, - udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + udpsize, edns, (int)(edns->bits & EDNS_DO), secure, 1)) { if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad, worker->env.now_tv)) @@ -802,7 +802,7 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, if(!reply_info_answer_encode(qinfo, encode_rep, id, flags, repinfo->c->buffer, timenow, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), - *is_secure_answer)) { + *is_secure_answer, 1)) { if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad, worker->env.now_tv)) diff --git a/services/authzone.c b/services/authzone.c index 6f6c55d43..90a1e4cb2 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -3542,7 +3542,7 @@ auth_answer_encode(struct query_info* qinfo, struct module_env* env, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, - (int)(edns->bits&EDNS_DO), 0)) { + (int)(edns->bits&EDNS_DO), 0, 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/services/localzone.c b/services/localzone.c index d21e0c48a..5314319aa 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1322,7 +1322,7 @@ local_encode(struct query_info* qinfo, struct module_env* env, if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0, 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/services/mesh.c b/services/mesh.c index 156cde791..f864437b2 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -1263,7 +1263,7 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->buf, 0, 1, m->s.env->scratch, udp_size, &r->edns, - (int)(r->edns.bits & EDNS_DO), secure)) + (int)(r->edns.bits & EDNS_DO), secure, 0)) { fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, LDNS_RCODE_SERVFAIL, r->buf, @@ -1451,7 +1451,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r_buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), - secure)) + secure, 0)) { if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time)) diff --git a/services/rpz.c b/services/rpz.c index 3b92ee538..162881c2f 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -1807,7 +1807,7 @@ rpz_local_encode(struct module_env* env, struct query_info* qinfo, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0, 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 653d3efbe..763c9385f 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1110,7 +1110,7 @@ static void edns_ede_encode_encodedecode(struct query_info* qinfo, /* encode */ unit_assert( reply_info_answer_encode(qinfo, rep, 1, rep->flags, pkt, - 0, 0, region, 65535, edns, 0, 0)); + 0, 0, region, 65535, edns, 0, 0, 0)); /* buffer ready for reading; skip after the question section */ sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); (void)query_dname_len(pkt); diff --git a/testdata/serve_expired_client_timeout_no_prefetch.rpl b/testdata/serve_expired_client_timeout_no_prefetch.rpl index aed397d9e..0177dd14a 100644 --- a/testdata/serve_expired_client_timeout_no_prefetch.rpl +++ b/testdata/serve_expired_client_timeout_no_prefetch.rpl @@ -98,11 +98,11 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 0 IN A 5.6.7.8 + example.com. 1 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 3590 IN NS ns.example.com. + example.com. 3591 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 3590 IN A 1.2.3.4 + ns.example.com. 3591 IN A 1.2.3.4 ENTRY_END ; If a prefetch triggers the test will fail with 'messages pending'. diff --git a/util/data/msgencode.c b/util/data/msgencode.c index 6d116fb52..3129018db 100644 --- a/util/data/msgencode.c +++ b/util/data/msgencode.c @@ -997,7 +997,7 @@ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, sldns_buffer* pkt, time_t timenow, int cached, struct regional* region, uint16_t udpsize, - struct edns_data* edns, int dnssec, int secure) + struct edns_data* edns, int dnssec, int secure, int cached_ttl) { uint16_t flags; unsigned int attach_edns = 0; @@ -1022,6 +1022,17 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, flags &= ~BIT_AD; } log_assert(flags & BIT_QR); /* QR bit must be on in our replies */ + if(cached_ttl && rep->ttl - timenow == 0) { + /* The last remaining second of the TTL for a cached response + * is replied. This makes a 0 in the protocol message. The + * response is valid for the cache, but the DNS TTL 0 item + * causes the received to drop the contents. Even though the + * contents are cachable, so the time used is decremented + * to change that into 1 second, and it can be cached, and + * used for expired response generation, and does not give + * repeated queries during that last second. */ + timenow --; + } if(udpsize < LDNS_HEADER_SIZE) return 0; /* currently edns does not change during calculations; diff --git a/util/data/msgencode.h b/util/data/msgencode.h index 6aff06099..bff6f7a40 100644 --- a/util/data/msgencode.h +++ b/util/data/msgencode.h @@ -64,12 +64,16 @@ struct edns_data; * or if edns_present = 0, it is not included. * @param dnssec: if 0 DNSSEC records are omitted from the answer. * @param secure: if 1, the AD bit is set in the reply. + * @param cached_ttl: the ttl is from a cache response. So that means it + * was some value minus the current time, and not an authoritative + * response with an autoritative TTL or a direct upstream response, + * that could have upstream TTL 0 items. * @return: 0 on error (server failure). */ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, struct sldns_buffer* dest, time_t timenow, int cached, struct regional* region, uint16_t udpsize, - struct edns_data* edns, int dnssec, int secure); + struct edns_data* edns, int dnssec, int secure, int cached_ttl); /** * Regenerate the wireformat from the stored msg reply. From c88eed83d99e8bb6cf0bb45437a6963955823449 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 11 Nov 2024 17:08:38 +0100 Subject: [PATCH 2/4] - ttl-zero-cacherep, unit test for ttl zero for a cache response. --- testdata/ttl_zero_cacherep.rpl | 290 +++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 testdata/ttl_zero_cacherep.rpl diff --git a/testdata/ttl_zero_cacherep.rpl b/testdata/ttl_zero_cacherep.rpl new file mode 100644 index 000000000..7e9eb5394 --- /dev/null +++ b/testdata/ttl_zero_cacherep.rpl @@ -0,0 +1,290 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + prefetch: no + serve-expired: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test TTL for the last zero second of cached messages. + +; 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 subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +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 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.gtld-servers.net. IN A +SECTION ANSWER +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +K.ROOT-SERVERS.NET. IN A +SECTION ANSWER +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.gtld-servers.net. IN AAAA +SECTION AUTHORITY +. 86400 IN SOA . . 20070304 28800 7200 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +K.ROOT-SERVERS.NET. IN AAAA +SECTION AUTHORITY +. 86400 IN SOA . . 20070304 28800 7200 604800 86400 +ENTRY_END + +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 300 + 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 subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +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 100 + ADDRESS 1.2.3.4 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA 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 AA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 10 IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com +RANGE_BEGIN 100 300 + ADDRESS 1.2.3.4 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +example.com. IN NS +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +ns.example.com. IN A +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +ns.example.com. IN AAAA +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR REFUSED +SECTION QUESTION +www.example.com. IN A +ENTRY_END +RANGE_END + +STEP 1 TIME_PASSES ELAPSE 10 + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 10 IN A 1.2.3.4 +ENTRY_END + +STEP 30 TIME_PASSES ELAPSE 5 + +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 5 IN A 1.2.3.4 +ENTRY_END + +STEP 60 TIME_PASSES ELAPSE 3 + +STEP 70 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 80 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 2 IN A 1.2.3.4 +ENTRY_END + +STEP 90 TIME_PASSES ELAPSE 1 + +STEP 100 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 110 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 1 IN A 1.2.3.4 +ENTRY_END + +STEP 120 TIME_PASSES ELAPSE 1 + +STEP 130 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 140 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +; note that it did not send 0 TTL. The message can be cached by the receiver +; during the last second of the TTL. +www.example.com. 1 IN A 1.2.3.4 +ENTRY_END + +STEP 150 TIME_PASSES ELAPSE 1 + +STEP 160 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 170 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA SERVFAIL +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +ENTRY_END + +SCENARIO_END From 73e408f1d0792267429e9f5f89537cda61297952 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 15 Sep 2025 10:03:35 +0200 Subject: [PATCH 3/4] A few changes for TTL processing: - Cached messages that reach 0 TTL are considered expired. This prevents Unbound itself from issuing replies with TTL 0 and possibly causing a thundering herd at the last second. Upstream replies of TTL 0 still get the usual pass-through but they are not considered for caching from Unbound or any of its caching modules. - 'serve-expired-reply-ttl' is changed and is now capped by the original TTL value of the record to try and make some sense when replying with expired records. - TTL decoding was updated to adhere to RFC8767 section 4 where a set high-order bit means the value is positive instead of 0. --- cachedb/cachedb.c | 95 ++++++++------- daemon/worker.c | 14 +-- doc/example.conf.in | 1 + doc/unbound.conf.rst | 6 + services/authzone.c | 12 +- services/cache/dns.c | 86 +++++++++----- services/cache/dns.h | 2 +- services/cache/rrset.c | 19 +-- services/localzone.c | 2 +- services/mesh.c | 6 +- services/rpz.c | 2 +- testcode/unitmain.c | 2 +- testdata/cachedb_expired.crpl | 60 +++++----- testdata/cachedb_expired_client_timeout.crpl | 57 ++++----- testdata/cachedb_expired_reply_ttl.crpl | 47 ++++---- testdata/cachedb_subnet_change.crpl | 46 ++++---- testdata/cachedb_val_expired.crpl | 60 +++++----- testdata/fwd_0ttlservfail.rpl | 21 +--- testdata/rrset_use_cached.rpl | 47 ++++---- testdata/serve_expired.rpl | 17 +-- testdata/serve_expired_0ttl_nodata.rpl | 23 ++-- testdata/serve_expired_0ttl_nxdomain.rpl | 4 +- testdata/serve_expired_0ttl_servfail.rpl | 2 +- testdata/serve_expired_client_timeout.rpl | 4 +- ...rve_expired_client_timeout_no_prefetch.rpl | 110 ------------------ .../serve_expired_client_timeout_servfail.rpl | 30 ++--- testdata/serve_expired_reply_ttl.rpl | 75 +++++++++++- testdata/serve_expired_ttl_client_timeout.rpl | 2 +- testdata/serve_expired_ttl_reset.rpl | 6 +- testdata/serve_expired_zerottl.rpl | 14 +-- ...subnet_global_prefetch_always_forward.crpl | 19 +-- testdata/subnet_global_prefetch_expired.crpl | 29 ++--- testdata/ttl_zero_cacherep.rpl | 21 ---- util/data/msgencode.c | 47 ++++---- util/data/msgencode.h | 10 +- util/data/msgparse.c | 10 ++ util/data/msgparse.h | 25 ++++ util/data/msgreply.c | 72 ++++++++---- util/data/msgreply.h | 13 ++- util/data/packed_rrset.c | 36 +++--- util/data/packed_rrset.h | 3 + validator/val_neg.c | 6 +- validator/val_utils.c | 2 + 43 files changed, 619 insertions(+), 546 deletions(-) delete mode 100644 testdata/serve_expired_client_timeout_no_prefetch.rpl diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index 8f2e66342..b45c0a3ea 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -401,12 +401,9 @@ prep_data(struct module_qstate* qstate, struct sldns_buffer* buf) FLAGS_GET_RCODE(qstate->return_msg->rep->flags) != LDNS_RCODE_YXDOMAIN) return 0; - /* We don't store the reply if its TTL is 0 unless serve-expired is - * enabled. Such a reply won't be reusable and simply be a waste for - * the backend. It's also compatible with the default behavior of - * dns_cache_store_msg(). */ - if(qstate->return_msg->rep->ttl == 0 && - !qstate->env->cfg->serve_expired) + /* We don't store the reply if its TTL is 0. This is probably coming + * from upstream and it is not meant to be stored. */ + if(qstate->return_msg->rep->ttl == 0) return 0; /* The EDE is added to the out-list so it is encoded in the cached message */ @@ -421,7 +418,7 @@ prep_data(struct module_qstate* qstate, struct sldns_buffer* buf) qstate->return_msg->rep); if(!reply_info_answer_encode(&qstate->return_msg->qinfo, qstate->return_msg->rep, 0, qstate->query_flags, - buf, 0, 1, qstate->env->scratch, 65535, &edns, 1, 0, 1)) + buf, 0, 1, qstate->env->scratch, 65535, &edns, 1, 0)) return 0; /* TTLs in the return_msg are relative to time(0) so we have to @@ -460,7 +457,7 @@ good_expiry_and_qinfo(struct module_qstate* qstate, struct sldns_buffer* buf) * - serve_expired needs to be set * - if SERVE_EXPIRED_TTL is set make sure that the record is not older * than that. */ - if((time_t)expiry < *qstate->env->now && + if(TTL_IS_EXPIRED((time_t)expiry, *qstate->env->now) && (!qstate->env->cfg->serve_expired || (SERVE_EXPIRED_TTL && *qstate->env->now - (time_t)expiry > SERVE_EXPIRED_TTL))) @@ -472,7 +469,8 @@ good_expiry_and_qinfo(struct module_qstate* qstate, struct sldns_buffer* buf) /* Adjust the TTL of the given RRset by 'subtract'. If 'subtract' is * negative, set the TTL to 0. */ static void -packed_rrset_ttl_subtract(struct packed_rrset_data* data, time_t subtract) +packed_rrset_ttl_subtract(struct packed_rrset_data* data, time_t subtract, + time_t timestamp) { size_t i; size_t total = data->count + data->rrsig_count; @@ -484,13 +482,13 @@ packed_rrset_ttl_subtract(struct packed_rrset_data* data, time_t subtract) data->rr_ttl[i] -= subtract; else data->rr_ttl[i] = 0; } - data->ttl_add = (subtract < data->ttl_add) ? (data->ttl_add - subtract) : 0; + data->ttl_add = timestamp; } /* Adjust the TTL of a DNS message and its RRs by 'adjust'. If 'adjust' is * negative, set the TTLs to 0. */ static void -adjust_msg_ttl(struct dns_msg* msg, time_t adjust) +adjust_msg_ttl(struct dns_msg* msg, time_t adjust, time_t timestamp) { size_t i; if(adjust >= 0 && msg->rep->ttl > adjust) @@ -502,13 +500,13 @@ adjust_msg_ttl(struct dns_msg* msg, time_t adjust) for(i=0; irep->rrset_count; i++) { packed_rrset_ttl_subtract((struct packed_rrset_data*)msg-> - rep->rrsets[i]->entry.data, adjust); + rep->rrsets[i]->entry.data, adjust, timestamp); } } /* Set the TTL of the given RRset to fixed value. */ static void -packed_rrset_ttl_set(struct packed_rrset_data* data, time_t ttl) +packed_rrset_ttl_set(struct packed_rrset_data* data, time_t ttl, time_t timestamp) { size_t i; size_t total = data->count + data->rrsig_count; @@ -516,12 +514,12 @@ packed_rrset_ttl_set(struct packed_rrset_data* data, time_t ttl) for(i=0; irr_ttl[i] = ttl; } - data->ttl_add = 0; + data->ttl_add = timestamp; } /* Set the TTL of a DNS message and its RRs by to a fixed value. */ static void -set_msg_ttl(struct dns_msg* msg, time_t ttl) +set_msg_ttl(struct dns_msg* msg, time_t ttl, time_t timestamp) { size_t i; msg->rep->ttl = ttl; @@ -530,14 +528,14 @@ set_msg_ttl(struct dns_msg* msg, time_t ttl) for(i=0; irep->rrset_count; i++) { packed_rrset_ttl_set((struct packed_rrset_data*)msg-> - rep->rrsets[i]->entry.data, ttl); + rep->rrsets[i]->entry.data, ttl, timestamp); } } /** convert dns message in buffer to return_msg */ static int parse_data(struct module_qstate* qstate, struct sldns_buffer* buf, - int* msg_expired) + int* msg_expired, time_t* msg_timestamp, time_t* msg_expiry) { struct msg_parse* prs; struct edns_data edns; @@ -554,6 +552,9 @@ parse_data(struct module_qstate* qstate, struct sldns_buffer* buf, ×tamp, sizeof(timestamp)); expiry = be64toh(expiry); timestamp = be64toh(timestamp); + log_assert(timestamp <= expiry); + *msg_expiry = (time_t)expiry; + *msg_timestamp = (time_t)timestamp; /* parse DNS packet */ regional_free_all(qstate->env->scratch); @@ -605,11 +606,9 @@ parse_data(struct module_qstate* qstate, struct sldns_buffer* buf, return 1; /* message from the future (clock skew?) */ } adjust = *qstate->env->now - (time_t)timestamp; - if(qstate->return_msg->rep->ttl < adjust) { + if(TTL_IS_EXPIRED((time_t)expiry, *qstate->env->now)) { verbose(VERB_ALGO, "cachedb msg expired"); *msg_expired = 1; - /* If serve-expired is enabled, we still use an expired message - * setting the TTL to 0. */ if(!qstate->env->cfg->serve_expired || (FLAGS_GET_RCODE(qstate->return_msg->rep->flags) != LDNS_RCODE_NOERROR && @@ -618,23 +617,21 @@ parse_data(struct module_qstate* qstate, struct sldns_buffer* buf, FLAGS_GET_RCODE(qstate->return_msg->rep->flags) != LDNS_RCODE_YXDOMAIN)) return 0; /* message expired */ - else - adjust = -1; + /* If serve-expired is enabled, we still use an expired message. + * Set the TTL to 0 now and it will be handled specially later + * when we need to store it internally. */ + adjust = -1; } + adjust_msg_ttl(qstate->return_msg, adjust, timestamp); verbose(VERB_ALGO, "cachedb msg adjusted down by %d", (int)adjust); - adjust_msg_ttl(qstate->return_msg, adjust); if(qstate->env->cfg->aggressive_nsec) { limit_nsec_ttl(qstate->return_msg); } /* Similar to the unbound worker, if serve-expired is enabled and * the msg would be considered to be expired, mark the state so a - * refetch will be scheduled. The comparison between 'expiry' and - * 'now' should be redundant given how these values were calculated, - * but we check it just in case as does good_expiry_and_qinfo(). */ - if(qstate->env->cfg->serve_expired && - !qstate->env->cfg->serve_expired_client_timeout && - (adjust == -1 || (time_t)expiry < *qstate->env->now)) { + * refetch will be scheduled. */ + if(*msg_expired && !qstate->env->cfg->serve_expired_client_timeout) { qstate->need_refetch = 1; } @@ -647,7 +644,7 @@ parse_data(struct module_qstate* qstate, struct sldns_buffer* buf, */ static int cachedb_extcache_lookup(struct module_qstate* qstate, struct cachedb_env* ie, - int* msg_expired) + int* msg_expired, time_t* msg_timestamp, time_t* msg_expiry) { char key[(CACHEDB_HASHSIZE/8)*2+1]; calc_hash(&qstate->qinfo, qstate->env, key, sizeof(key)); @@ -664,7 +661,8 @@ cachedb_extcache_lookup(struct module_qstate* qstate, struct cachedb_env* ie, } /* parse dns message into return_msg */ - if( !parse_data(qstate, qstate->env->scratch_buffer, msg_expired) ) { + if( !parse_data(qstate, qstate->env->scratch_buffer, msg_expired, + msg_timestamp, msg_expiry) ) { return 0; } return 1; @@ -736,20 +734,24 @@ cachedb_intcache_lookup(struct module_qstate* qstate, struct cachedb_env* cde) * Store query into the internal cache of unbound. */ static void -cachedb_intcache_store(struct module_qstate* qstate, int msg_expired) +cachedb_intcache_store(struct module_qstate* qstate, int msg_expired, + time_t msg_timestamp, time_t msg_expiry) { uint32_t store_flags = qstate->query_flags; int serve_expired = qstate->env->cfg->serve_expired; - - if(qstate->env->cfg->serve_expired) - store_flags |= DNSCACHE_STORE_ZEROTTL; if(!qstate->return_msg) return; if(serve_expired && msg_expired) { - /* Set TTLs to a value such that value + *env->now is - * going to be now-3 seconds. Making it expired - * in the cache. */ - set_msg_ttl(qstate->return_msg, (time_t)-3); + time_t original_ttl = msg_expiry - msg_timestamp; + store_flags |= DNSCACHE_STORE_EXPIRED_MSG_CACHEDB; + /* Pass the original TTL of the expired message and signal with + * the DNSCACHE_STORE_EXPIRED_MSG_CACHEDB flag that + * dns_cache_store_msg() needs to set absolute expired TTLs + * based on the original message TTL. + * Results as expired message in the cache */ + set_msg_ttl(qstate->return_msg, original_ttl, 0); + verbose(VERB_ALGO, "cachedb expired msg set to be expired now " + "(original ttl: %d)", (int)original_ttl); /* The expired entry does not get checked by the validator * and we need a validation value for it. */ if(qstate->env->cfg->cachedb_check_when_serve_expired) @@ -767,12 +769,14 @@ cachedb_intcache_store(struct module_qstate* qstate, int msg_expired) * of cache. */ return; } - /* set TTLs to zero again */ - adjust_msg_ttl(qstate->return_msg, -1); /* Send serve expired responses based on the cachedb * returned message, that was just stored in the cache. * It can then continue to work on this query. */ mesh_respond_serve_expired(qstate->mesh_info); + /* set TTLs as expired for this return_msg in case it is used + * later on */ + set_msg_ttl(qstate->return_msg, + EXPIRED_REPLY_TTL_CALC(msg_expiry, msg_timestamp), 0); } } @@ -790,6 +794,7 @@ cachedb_handle_query(struct module_qstate* qstate, struct cachedb_env* ie, int id) { int msg_expired = 0; + time_t msg_timestamp, msg_expiry; qstate->is_cachedb_answer = 0; /* check if we are enabled, and skip if so */ if(!ie->enabled) { @@ -824,13 +829,15 @@ cachedb_handle_query(struct module_qstate* qstate, } /* ask backend cache to see if we have data */ - if(cachedb_extcache_lookup(qstate, ie, &msg_expired)) { + if(cachedb_extcache_lookup(qstate, ie, &msg_expired, &msg_timestamp, + &msg_expiry)) { if(verbosity >= VERB_ALGO) log_dns_msg(ie->backend->name, &qstate->return_msg->qinfo, qstate->return_msg->rep); /* store this result in internal cache */ - cachedb_intcache_store(qstate, msg_expired); + cachedb_intcache_store(qstate, + msg_expired, msg_timestamp, msg_expiry); /* 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. @@ -850,6 +857,8 @@ cachedb_handle_query(struct module_qstate* qstate, qstate->ext_state[id] = module_wait_module; return; } + /* No 0TTL answers escaping from external cache. */ + log_assert(qstate->return_msg->rep->ttl > 0); qstate->is_cachedb_answer = 1; /* we are done with the query */ qstate->ext_state[id] = module_finished; diff --git a/daemon/worker.c b/daemon/worker.c index 77417c249..0f0af7457 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -651,7 +651,7 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, } if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags, repinfo->c->buffer, 0, 1, worker->scratchpad, - udpsize, edns, (int)(edns->bits & EDNS_DO), secure, 1)) { + udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad, worker->env.now_tv)) @@ -746,7 +746,7 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, *partial_repp = NULL; /* avoid accidental further pass */ /* Check TTL */ - if(rep->ttl < timenow) { + if(TTL_IS_EXPIRED(rep->ttl, timenow)) { /* Check if we need to serve expired now */ if(worker->env.cfg->serve_expired && /* if serve-expired-client-timeout is set, serve @@ -891,7 +891,7 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, if(!reply_info_answer_encode(qinfo, encode_rep, id, flags, repinfo->c->buffer, timenow, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), - *is_secure_answer, 1)) { + *is_secure_answer)) { if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad, worker->env.now_tv)) @@ -1929,11 +1929,11 @@ lookup_cache: if((worker->env.cfg->prefetch && rep->prefetch_ttl <= *worker->env.now) || (worker->env.cfg->serve_expired && - rep->ttl < *worker->env.now && + TTL_IS_EXPIRED(rep->ttl, *worker->env.now) && !(*worker->env.now < rep->serve_expired_norec_ttl))) { - time_t leeway = rep->ttl - *worker->env.now; - if(rep->ttl < *worker->env.now) - leeway = 0; + time_t leeway = + TTL_IS_EXPIRED(rep->ttl, *worker->env.now) + ? 0 : rep->ttl - *worker->env.now; lock_rw_unlock(&e->lock); reply_and_prefetch(worker, lookup_qinfo, diff --git a/doc/example.conf.in b/doc/example.conf.in index b33e65bfe..02f6ec774 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -752,6 +752,7 @@ server: # serve-expired-ttl-reset: no # # TTL value to use when replying with expired data. + # Capped by the original TTL of the record. # serve-expired-reply-ttl: 30 # # Time in milliseconds before replying to the client with expired data. diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index ad8404e11..72522af70 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -2306,6 +2306,12 @@ These options are part of the **server:** clause. :ref:`serve-expired-client-timeout` is also used then it is RECOMMENDED to use 30 as the value (:rfc:`8767`). + This value is capped by the original TTL of the record. + This means that records with higher original TTL than this value will use + this value for expired replies. + Records with lower original TTL than this value will use their original TTL + for expired replies. + Default: 30 diff --git a/services/authzone.c b/services/authzone.c index 6a9de52f8..e4dd32465 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -171,7 +171,7 @@ get_rrset_ttl(struct ub_packed_rrset_key* k) /** Copy rrset into region from domain-datanode and packet rrset */ static struct ub_packed_rrset_key* auth_packed_rrset_copy_region(struct auth_zone* z, struct auth_data* node, - struct auth_rrset* rrset, struct regional* region, time_t adjust) + struct auth_rrset* rrset, struct regional* region) { struct ub_packed_rrset_key key; memset(&key, 0, sizeof(key)); @@ -182,7 +182,7 @@ auth_packed_rrset_copy_region(struct auth_zone* z, struct auth_data* node, key.rk.type = htons(rrset->type); key.rk.rrset_class = htons(z->dclass); key.entry.hash = rrset_key_hash(&key.rk); - return packed_rrset_copy_region(&key, region, adjust); + return packed_rrset_copy_region(&key, region, 0); } /** fix up msg->rep TTL and prefetch ttl */ @@ -236,7 +236,7 @@ msg_add_rrset_an(struct auth_zone* z, struct regional* region, return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = - auth_packed_rrset_copy_region(z, node, rrset, region, 0))) + auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->an_numrrsets++; @@ -260,7 +260,7 @@ msg_add_rrset_ns(struct auth_zone* z, struct regional* region, return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = - auth_packed_rrset_copy_region(z, node, rrset, region, 0))) + auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->ns_numrrsets++; @@ -283,7 +283,7 @@ msg_add_rrset_ar(struct auth_zone* z, struct regional* region, return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = - auth_packed_rrset_copy_region(z, node, rrset, region, 0))) + auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->ar_numrrsets++; @@ -3530,7 +3530,7 @@ auth_answer_encode(struct query_info* qinfo, struct module_env* env, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, - (int)(edns->bits&EDNS_DO), 0, 0)) { + (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/services/cache/dns.c b/services/cache/dns.c index 351b3568c..325faa0b2 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -60,10 +60,10 @@ * @param rep: contains list of rrsets to store. * @param now: current time. * @param leeway: during prefetch how much leeway to update TTLs. - * This makes rrsets (other than type NS) timeout sooner so they get - * updated with a new full TTL. - * Type NS does not get this, because it must not be refreshed from the - * child domain, but keep counting down properly. + * This makes rrsets expire sooner so they get updated with a new full + * TTL. + * Child side type NS does get this but TTL checks are done using the time + * the query was created rather than the time the answer was received. * @param pside: if from parentside discovered NS, so that its NS is okay * in a prefetch situation to be updated (without becoming sticky). * @param qrep: update rrsets here if cache is better @@ -100,11 +100,20 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, rep->ref[i].id != rep->ref[i].key->id) ck = NULL; else ck = packed_rrset_copy_region( - rep->ref[i].key, region, now); + rep->ref[i].key, region, + ((ntohs(rep->ref[i].key->rk.type)== + LDNS_RR_TYPE_NS && !pside)?qstarttime:now)); lock_rw_unlock(&rep->ref[i].key->entry.lock); if(ck) { /* use cached copy if memory allows */ qrep->rrsets[i] = ck; + ttl = ((struct packed_rrset_data*) + ck->entry.data)->ttl; + if(ttl < qrep->ttl) { + qrep->ttl = ttl; + qrep->prefetch_ttl = PREFETCH_TTL_CALC(qrep->ttl); + qrep->serve_expired_ttl = qrep->ttl + SERVE_EXPIRED_TTL; + } } } /* no break: also copy key item */ @@ -169,10 +178,12 @@ 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); + if((flags & DNSCACHE_STORE_EXPIRED_MSG_CACHEDB)) { + reply_info_absolute_ttls(rep, *env->now, *env->now - ttl); + } else reply_info_set_ttls(rep, *env->now); store_rrsets(env, rep, *env->now, leeway, pside, qrep, region, qstarttime); - if(ttl == 0 && !(flags & DNSCACHE_STORE_ZEROTTL)) { + if(ttl == 0) { /* 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"); @@ -272,8 +283,10 @@ addr_to_additional(struct ub_packed_rrset_key* rrset, struct regional* region, { if((msg->rep->rrsets[msg->rep->rrset_count] = packed_rrset_copy_region(rrset, region, now))) { + struct packed_rrset_data* d = rrset->entry.data; msg->rep->ar_numrrsets++; msg->rep->rrset_count++; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } } @@ -456,8 +469,10 @@ find_add_ds(struct module_env* env, struct regional* region, /* add it to auth section. This is the second rrset. */ if((msg->rep->rrsets[msg->rep->rrset_count] = packed_rrset_copy_region(rrset, region, now))) { + struct packed_rrset_data* d = rrset->entry.data; msg->rep->ns_numrrsets++; msg->rep->rrset_count++; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } lock_rw_unlock(&rrset->entry.lock); } @@ -487,6 +502,8 @@ 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->ttl = MAX_TTL; /* will be updated (brought down) while we add + * rrsets to the message */ msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets = (struct ub_packed_rrset_key**) regional_alloc(region, @@ -497,24 +514,28 @@ dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype, } int -dns_msg_authadd(struct dns_msg* msg, struct regional* region, +dns_msg_authadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now) { - if(!(msg->rep->rrsets[msg->rep->rrset_count++] = + struct packed_rrset_data* d = rrset->entry.data; + if(!(msg->rep->rrsets[msg->rep->rrset_count++] = packed_rrset_copy_region(rrset, region, now))) return 0; msg->rep->ns_numrrsets++; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return 1; } int -dns_msg_ansadd(struct dns_msg* msg, struct regional* region, +dns_msg_ansadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now) { - if(!(msg->rep->rrsets[msg->rep->rrset_count++] = + struct packed_rrset_data* d = rrset->entry.data; + if(!(msg->rep->rrsets[msg->rep->rrset_count++] = packed_rrset_copy_region(rrset, region, now))) return 0; msg->rep->an_numrrsets++; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return 1; } @@ -585,6 +606,7 @@ 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->ttl = MAX_TTL; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->reason_bogus_str = NULL; if(num > RR_COUNT_MAX) @@ -606,13 +628,13 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, size_t i; int is_expired = 0; time_t now_control = now; - if(now > r->ttl) { + if(TTL_IS_EXPIRED(r->ttl, now)) { /* Check if we are allowed to serve expired */ 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. */ - now_control = r->ttl - env->cfg->serve_expired_reply_ttl; + /* Change the current time so we can pass the below TTL checks + * when serving expired data. */ + now_control = 0; is_expired = 1; } @@ -620,15 +642,6 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, if(!msg) return NULL; msg->rep->flags = r->flags; msg->rep->qdcount = r->qdcount; - msg->rep->ttl = is_expired - ?SERVE_EXPIRED_REPLY_TTL - :r->ttl - now; - if(r->prefetch_ttl > now) - msg->rep->prefetch_ttl = r->prefetch_ttl - now; - else - msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); - 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; @@ -656,13 +669,30 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, return NULL; } for(i=0; irep->rrset_count; i++) { + struct packed_rrset_data* d; msg->rep->rrsets[i] = packed_rrset_copy_region(r->rrsets[i], region, now); if(!msg->rep->rrsets[i]) { rrset_array_unlock(r->ref, r->rrset_count); return NULL; } + d = msg->rep->rrsets[i]->entry.data; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } + if(msg->rep->rrset_count < 1) { + msg->rep->ttl = is_expired + ?SERVE_EXPIRED_REPLY_TTL + :r->ttl - now; + if(r->prefetch_ttl > now) + msg->rep->prefetch_ttl = r->prefetch_ttl - now; + else + msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); + } else { + /* msg->rep->ttl has been updated through the RRSets above */ + 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; if(env) rrset_array_unlock_touch(env->rrset_cache, scratch, r->ref, r->rrset_count); @@ -701,7 +731,7 @@ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region, struct dns_msg* msg; struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; - if(now > d->ttl) + if(TTL_IS_EXPIRED(d->ttl, now)) return NULL; msg = gen_dns_msg(region, q, 1); /* only the CNAME (or other) RRset */ if(!msg) @@ -736,7 +766,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region, rrset->entry.data; uint8_t* newname, *dtarg = NULL; size_t newlen, dtarglen; - if(now > d->ttl) + if(TTL_IS_EXPIRED(d->ttl, now)) return NULL; /* only allow validated (with DNSSEC) DNAMEs used from cache * for insecure DNAMEs, query again. */ @@ -844,6 +874,8 @@ fill_any(struct module_env* env, /* set NOTIMPL for RFC 8482 */ msg->rep->flags |= LDNS_RCODE_NOTIMPL; msg->rep->security = sec_status_indeterminate; + msg->rep->ttl = 1; /* empty NOTIMPL response will never be + * updated with rrsets, set TTL to 1 */ return msg; } @@ -1069,7 +1101,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, msgqinf->qclass, flags, 0, 1); if(e) { struct reply_info* cached = e->entry.data; - if(cached->ttl < *env->now + if(TTL_IS_EXPIRED(cached->ttl, *env->now) && reply_info_could_use_expired(cached, *env->now) /* If we are validating make sure only * validating modules can update such messages. diff --git a/services/cache/dns.h b/services/cache/dns.h index 8aa6b44bc..41d42e60d 100644 --- a/services/cache/dns.h +++ b/services/cache/dns.h @@ -53,7 +53,7 @@ struct delegpt; * Must be an unsigned 32-bit value larger than 0xffff */ /** Allow caching a DNS message with a zero TTL. */ -#define DNSCACHE_STORE_ZEROTTL 0x100000 +#define DNSCACHE_STORE_EXPIRED_MSG_CACHEDB 0x100000 /** * Region allocated message reply diff --git a/services/cache/rrset.c b/services/cache/rrset.c index 6d5c24f80..200e3c701 100644 --- a/services/cache/rrset.c +++ b/services/cache/rrset.c @@ -131,7 +131,7 @@ 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, cached data is better */ - if( newd->ttl < timenow && timenow <= cached->ttl) + if( TTL_IS_EXPIRED(newd->ttl, timenow) && !TTL_IS_EXPIRED(cached->ttl, timenow)) return 0; /* o store if rrset has been validated * everything better than bogus data @@ -146,13 +146,13 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) if( newd->trust > cached->trust ) { /* if the cached rrset is bogus, and new is equal, * do not update the TTL - let it expire. */ - if(equal && cached->ttl >= timenow && + if(equal && !TTL_IS_EXPIRED(cached->ttl, timenow) && cached->security == sec_status_bogus) return 0; return 1; } /* o item in cache has expired */ - if( cached->ttl < timenow ) + if( TTL_IS_EXPIRED(cached->ttl, timenow) ) return 1; /* o same trust, but different in data - insert it */ if( newd->trust == cached->trust && !equal ) { @@ -300,7 +300,7 @@ rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen, /* check TTL */ struct packed_rrset_data* data = (struct packed_rrset_data*)e->data; - if(timenow > data->ttl) { + if(TTL_IS_EXPIRED(data->ttl, timenow)) { lock_rw_unlock(&e->lock); return NULL; } @@ -310,17 +310,18 @@ rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen, return NULL; } -int +int rrset_array_lock(struct rrset_ref* ref, size_t count, time_t timenow) { size_t i; + struct packed_rrset_data* d; for(i=0; i0 && ref[i].key == ref[i-1].key) continue; /* only lock items once */ lock_rw_rdlock(&ref[i].key->entry.lock); - if(ref[i].id != ref[i].key->id || timenow > - ((struct packed_rrset_data*)(ref[i].key->entry.data)) - ->ttl) { + d = ref[i].key->entry.data; + if(ref[i].id != ref[i].key->id || + TTL_IS_EXPIRED(d->ttl, timenow)) { /* failure! rollback our readlocks */ rrset_array_unlock(ref, i+1); return 0; @@ -511,7 +512,7 @@ rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname, size_t* *qnamelen, searchtype, qclass, 0, 0, 0))) { struct packed_rrset_data* data = (struct packed_rrset_data*)rrset->entry.data; - if(now > data->ttl) { + if(TTL_IS_EXPIRED(data->ttl, now)) { /* it is expired, this is not wanted */ lock_rw_unlock(&rrset->entry.lock); log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass); diff --git a/services/localzone.c b/services/localzone.c index ce796c349..9ea98c250 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1332,7 +1332,7 @@ local_encode(struct query_info* qinfo, struct module_env* env, if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0, 0)) { + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/services/mesh.c b/services/mesh.c index 30040f752..a0bb9ba2f 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -348,7 +348,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; + if(TTL_IS_EXPIRED(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) @@ -1351,7 +1351,7 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->buf, 0, 1, m->s.env->scratch, udp_size, &r->edns, - (int)(r->edns.bits & EDNS_DO), secure, 0)) + (int)(r->edns.bits & EDNS_DO), secure)) { fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, LDNS_RCODE_SERVFAIL, r->buf, @@ -1539,7 +1539,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r_buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), - secure, 0)) + secure)) { if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time)) diff --git a/services/rpz.c b/services/rpz.c index b77f47c9b..df39e75b0 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -1807,7 +1807,7 @@ rpz_local_encode(struct module_env* env, struct query_info* qinfo, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0, 0)) { + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 14e31927b..07c016d7b 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1037,7 +1037,7 @@ static void edns_ede_encode_encodedecode(struct query_info* qinfo, /* encode */ unit_assert( reply_info_answer_encode(qinfo, rep, 1, rep->flags, pkt, - 0, 0, region, 65535, edns, 0, 0, 0)); + 0, 0, region, 65535, edns, 0, 0)); /* buffer ready for reading; skip after the question section */ sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); (void)query_dname_len(pkt); diff --git a/testdata/cachedb_expired.crpl b/testdata/cachedb_expired.crpl index d3bf06fe1..697e88e26 100644 --- a/testdata/cachedb_expired.crpl +++ b/testdata/cachedb_expired.crpl @@ -5,7 +5,10 @@ server: minimal-responses: no serve-expired: yes serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 module-config: "cachedb iterator" + ede: yes + ede-serve-expired: yes cachedb: backend: "testframe" @@ -82,7 +85,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -91,7 +94,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END RANGE_END @@ -111,7 +115,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Get another query in cache to make it expired. @@ -130,46 +134,46 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; it is now expired -STEP 40 TIME_PASSES ELAPSE 20 +STEP 40 TIME_PASSES ELAPSE 200 ; cache is expired, and cachedb is expired. STEP 50 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www2.example.com. IN A ENTRY_END STEP 60 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 30 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; cache is expired, cachedb has no answer STEP 70 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 80 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 90 TRAFFIC @@ -189,7 +193,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; flush the entry from cache @@ -210,30 +214,30 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; it is now expired -STEP 150 TIME_PASSES ELAPSE 20 +STEP 150 TIME_PASSES ELAPSE 200 ; flush the entry from cache STEP 160 FLUSH_MESSAGE www.example.com. IN A ; cache has no answer, cachedb is expired STEP 170 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 180 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 190 TRAFFIC @@ -254,7 +258,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; expire the entry in cache @@ -275,30 +279,30 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; it is now expired -STEP 250 TIME_PASSES ELAPSE 20 +STEP 250 TIME_PASSES ELAPSE 200 ; expire the entry in cache STEP 260 EXPIRE_MESSAGE www.example.com. IN A ; cache is expired, cachedb is expired STEP 270 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 280 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 290 TRAFFIC @@ -319,7 +323,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END SCENARIO_END diff --git a/testdata/cachedb_expired_client_timeout.crpl b/testdata/cachedb_expired_client_timeout.crpl index 78ddf4d8f..16d8cc30d 100644 --- a/testdata/cachedb_expired_client_timeout.crpl +++ b/testdata/cachedb_expired_client_timeout.crpl @@ -4,12 +4,14 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes - serve-expired-reply-ttl: 30 + serve-expired-reply-ttl: 123 ; at least one second, so we can time skip past the timer in the ; testbound script steps, but also reply within the time. serve-expired-client-timeout: 1200 module-config: "cachedb iterator" discard-timeout: 3000 + ede: yes + ede-serve-expired: yes cachedb: backend: "testframe" @@ -86,7 +88,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -95,7 +97,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END RANGE_END @@ -108,7 +111,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.6 +www.example.com. 200 IN A 1.2.3.6 ENTRY_END ENTRY_BEGIN @@ -117,7 +120,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.7 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.7 ENTRY_END RANGE_END @@ -132,7 +136,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.8 +www.example.com. 200 IN A 1.2.3.8 ENTRY_END ENTRY_BEGIN @@ -141,7 +145,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.9 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.9 ENTRY_END RANGE_END @@ -156,7 +161,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.10 +www.example.com. 200 IN A 1.2.3.10 ENTRY_END ENTRY_BEGIN @@ -165,7 +170,7 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.11 +www2.example.com. 100 IN A 1.2.3.11 ENTRY_END RANGE_END @@ -188,7 +193,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Get another query in cache. @@ -207,7 +212,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; www.example.com and www2.example.com are in cache, www2 in cachedb. @@ -217,7 +222,7 @@ STEP 40 FLUSH_MESSAGE www2.example.com. IN A ; response from cachedb for www2. ; make 2 seconds pass to decrement the TTL on the response, -; the upstream TTL would be 10, cachedb 8. +; the upstream TTL would be 200, cachedb 198. STEP 48 TIME_PASSES ELAPSE 2 STEP 50 QUERY @@ -234,11 +239,11 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 8 IN A 1.2.3.5 +www2.example.com. 98 IN A 1.2.3.5 ENTRY_END ; make both cache and cachedb expired -STEP 70 TIME_PASSES ELAPSE 20 +STEP 70 TIME_PASSES ELAPSE 200 ; www and www2 expired in cache, www2 expired in cachedb. ; the query should now try to resolve and complete within the @@ -258,11 +263,11 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.7 +www2.example.com. 100 IN A 1.2.3.7 ENTRY_END ; expire the data again -STEP 100 TIME_PASSES ELAPSE 20 +STEP 100 TIME_PASSES ELAPSE 200 ; the query should now try to resolve, but the upstream is not ; responsive for several testbound steps. When the timer expires, @@ -271,7 +276,7 @@ STEP 100 TIME_PASSES ELAPSE 20 ; www2 expired in cache and www2 expired in cachedb. STEP 110 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www2.example.com. IN A ENTRY_END @@ -281,26 +286,26 @@ STEP 112 TIME_PASSES ELAPSE 2 STEP 120 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 30 IN A 1.2.3.7 +www2.example.com. 100 IN A 1.2.3.7 ENTRY_END ; make traffic flow to resolve the query, server responds. STEP 130 TRAFFIC ; expire the data again -STEP 140 TIME_PASSES ELAPSE 20 +STEP 140 TIME_PASSES ELAPSE 200 ; The client query tries to resolve, but gets no immediate answer, ; so the expired data is used. But the expired data is in cache and ; the query is not in cachedb. STEP 150 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -310,12 +315,12 @@ STEP 152 TIME_PASSES ELAPSE 2 STEP 160 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END ; make traffic flow to resolve the query, server responds. @@ -337,7 +342,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.11 +www2.example.com. 100 IN A 1.2.3.11 ENTRY_END SCENARIO_END diff --git a/testdata/cachedb_expired_reply_ttl.crpl b/testdata/cachedb_expired_reply_ttl.crpl index 03fd01add..b8adbe738 100644 --- a/testdata/cachedb_expired_reply_ttl.crpl +++ b/testdata/cachedb_expired_reply_ttl.crpl @@ -5,8 +5,10 @@ server: minimal-responses: no serve-expired: yes serve-expired-client-timeout: 0 - serve-expired-reply-ttl: 30 + serve-expired-reply-ttl: 123 module-config: "cachedb iterator" + ede: yes + ede-serve-expired: yes cachedb: backend: "testframe" @@ -83,7 +85,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -92,7 +94,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END RANGE_END @@ -115,7 +118,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Get another query in cache to make it expired. @@ -134,28 +137,28 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; it is now expired -STEP 40 TIME_PASSES ELAPSE 20 +STEP 40 TIME_PASSES ELAPSE 200 ; cache is expired, and cachedb is expired. STEP 50 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www2.example.com. IN A ENTRY_END STEP 60 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 30 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; got an answer from upstream @@ -173,25 +176,25 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; cache is expired, cachedb has no answer STEP 70 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 80 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 90 TRAFFIC @@ -211,29 +214,29 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; make both cache and cachedb expired. -STEP 120 TIME_PASSES ELAPSE 20 +STEP 120 TIME_PASSES ELAPSE 200 STEP 130 FLUSH_MESSAGE www.example.com. IN A ; cache has no entry and cachedb is expired. STEP 140 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 150 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END ; the name is resolved @@ -254,7 +257,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END SCENARIO_END diff --git a/testdata/cachedb_subnet_change.crpl b/testdata/cachedb_subnet_change.crpl index 73584305c..87903132c 100644 --- a/testdata/cachedb_subnet_change.crpl +++ b/testdata/cachedb_subnet_change.crpl @@ -4,7 +4,7 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes - serve-expired-reply-ttl: 30 + serve-expired-reply-ttl: 123 ; disable the serve expired client timeout. serve-expired-client-timeout: 0 @@ -14,6 +14,8 @@ server: ; store for edns subnet content for modules to the right of it. ; this keeps subnet content out of cachedb as global content. module-config: "subnetcache cachedb iterator" + ede: yes + ede-serve-expired: yes cachedb: backend: "testframe" @@ -105,7 +107,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN CNAME www.initial.com. +www.example.com. 200 IN CNAME www.initial.com. ENTRY_END RANGE_END @@ -118,7 +120,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN CNAME example.foo.com. +www.example.com. 200 IN CNAME example.foo.com. ENTRY_END RANGE_END @@ -131,7 +133,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.initial.com. IN A SECTION ANSWER -www.initial.com. 10 IN A 1.2.3.4 +www.initial.com. 200 IN A 1.2.3.4 ENTRY_END RANGE_END @@ -144,7 +146,7 @@ REPLY QR AA NOERROR SECTION QUESTION example.foo.com. IN A SECTION ANSWER -example.foo.com. 10 IN A 1.2.3.5 +example.foo.com. 200 IN A 1.2.3.5 SECTION ADDITIONAL HEX_EDNSDATA_BEGIN ; client is 127.0.0.1 @@ -166,7 +168,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN CNAME example.foo.com. +www.example.com. 200 IN CNAME example.foo.com. ENTRY_END RANGE_END @@ -179,7 +181,7 @@ REPLY QR AA NOERROR SECTION QUESTION example.foo.com. IN A SECTION ANSWER -example.foo.com. 10 IN A 1.2.3.6 +example.foo.com. 200 IN A 1.2.3.6 SECTION ADDITIONAL HEX_EDNSDATA_BEGIN ; client is 127.0.0.1 @@ -211,19 +213,19 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN CNAME www.initial.com. -www.initial.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN CNAME www.initial.com. +www.initial.com. 200 IN A 1.2.3.4 ENTRY_END ; now valid in cache and valid in cachedb, without subnet. -STEP 30 TIME_PASSES ELAPSE 20 +STEP 30 TIME_PASSES ELAPSE 200 ; now the cache and cachedb have an expired entry. ; the upstream is updated to CNAME to a subnet zone A record. STEP 40 QUERY ADDRESS 127.0.0.1 ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -231,13 +233,13 @@ ENTRY_END ; the expired answer, while the ECS answer is looked up. STEP 50 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN CNAME www.initial.com. -www.initial.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN CNAME www.initial.com. +www.initial.com. 123 IN A 1.2.3.4 ENTRY_END ; check that subnet has the query in cache. @@ -256,12 +258,12 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 8 IN CNAME example.foo.com. -example.foo.com. 8 IN A 1.2.3.5 +www.example.com. 198 IN CNAME example.foo.com. +example.foo.com. 198 IN A 1.2.3.5 ENTRY_END ; everything is expired, cache, subnetcache and cachedb. -STEP 80 TIME_PASSES ELAPSE 20 +STEP 80 TIME_PASSES ELAPSE 200 STEP 90 QUERY ADDRESS 127.0.0.1 ENTRY_BEGIN @@ -277,8 +279,8 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN CNAME example.foo.com. -example.foo.com. 10 IN A 1.2.3.6 +www.example.com. 200 IN CNAME example.foo.com. +example.foo.com. 200 IN A 1.2.3.6 ENTRY_END ; see the entry now in cache, from the subnetcache. @@ -297,8 +299,8 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 8 IN CNAME example.foo.com. -example.foo.com. 8 IN A 1.2.3.6 +www.example.com. 198 IN CNAME example.foo.com. +example.foo.com. 198 IN A 1.2.3.6 ENTRY_END SCENARIO_END diff --git a/testdata/cachedb_val_expired.crpl b/testdata/cachedb_val_expired.crpl index 741445ce8..aa98b5fe6 100644 --- a/testdata/cachedb_val_expired.crpl +++ b/testdata/cachedb_val_expired.crpl @@ -5,8 +5,11 @@ server: minimal-responses: yes serve-expired: yes serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 ;module-config: "subnetcache validator cachedb iterator" module-config: "validator cachedb iterator" + ede: yes + ede-serve-expired: yes cachedb: backend: "testframe" @@ -83,7 +86,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -92,7 +95,8 @@ REPLY QR AA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +; TTL lower than serve-expired-reply-ttl on purpose +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END RANGE_END @@ -112,7 +116,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Get another query in cache to make it expired. @@ -131,48 +135,48 @@ REPLY QR RD RA NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 10 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; it is now expired -STEP 40 TIME_PASSES ELAPSE 20 +STEP 40 TIME_PASSES ELAPSE 200 ; cache is expired, and cachedb is expired. ; The expired reply, from cachedb, needs a validation status, ; because the validator module set that validation is needed. STEP 50 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www2.example.com. IN A ENTRY_END STEP 60 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www2.example.com. IN A SECTION ANSWER -www2.example.com. 30 IN A 1.2.3.5 +www2.example.com. 100 IN A 1.2.3.5 ENTRY_END ; cache is expired, cachedb has no answer STEP 70 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 80 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 90 TRAFFIC @@ -192,7 +196,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; flush the entry from cache @@ -213,30 +217,30 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; it is now expired -STEP 150 TIME_PASSES ELAPSE 20 +STEP 150 TIME_PASSES ELAPSE 200 ; flush the entry from cache STEP 160 FLUSH_MESSAGE www.example.com. IN A ; cache has no answer, cachedb is expired STEP 170 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 180 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 190 TRAFFIC @@ -257,7 +261,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; expire the entry in cache @@ -278,30 +282,30 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END ; it is now expired -STEP 250 TIME_PASSES ELAPSE 20 +STEP 250 TIME_PASSES ELAPSE 200 ; expire the entry in cache STEP 260 EXPIRE_MESSAGE www.example.com. IN A ; cache is expired, cachedb is expired STEP 270 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END STEP 280 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 1.2.3.4 +www.example.com. 123 IN A 1.2.3.4 ENTRY_END STEP 290 TRAFFIC @@ -322,7 +326,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 1.2.3.4 +www.example.com. 200 IN A 1.2.3.4 ENTRY_END SCENARIO_END diff --git a/testdata/fwd_0ttlservfail.rpl b/testdata/fwd_0ttlservfail.rpl index d50d386d4..635ee6db6 100644 --- a/testdata/fwd_0ttlservfail.rpl +++ b/testdata/fwd_0ttlservfail.rpl @@ -46,25 +46,10 @@ www.example.com. IN A SECTION ANSWER ENTRY_END -; enough to pass by the TTL of the servfail answer in cache +; enough to expire the servfail answer in cache STEP 50 TIME_PASSES ELAPSE 5 -; this query triggers a prefetch -STEP 210 QUERY -ENTRY_BEGIN -REPLY RD -SECTION QUESTION -www.example.com. IN A -ENTRY_END - -STEP 220 CHECK_ANSWER -ENTRY_BEGIN -MATCH all -REPLY QR RD RA SERVFAIL -SECTION QUESTION -www.example.com. IN A -SECTION ANSWER -ENTRY_END +; Expired SERVFAILS are no longer served from Unbound ; this query gets the 0ttl answer STEP 230 QUERY @@ -76,7 +61,7 @@ ENTRY_END STEP 240 CHECK_ANSWER ENTRY_BEGIN -MATCH all +MATCH all ttl REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A diff --git a/testdata/rrset_use_cached.rpl b/testdata/rrset_use_cached.rpl index 8420ae02a..7af5c85d2 100644 --- a/testdata/rrset_use_cached.rpl +++ b/testdata/rrset_use_cached.rpl @@ -5,8 +5,11 @@ server: # We do not want only serve-expired because fetches from that # apply a generous PREFETCH_LEEWAY. serve-expired-client-timeout: 1000 + serve-expired-reply-ttl: 123 # So that we can only have to give one SERVFAIL answer. outbound-msg-retry: 0 + ede: yes + ede-serve-expired: yes forward-zone: name: "." forward-addr: 216.0.0.1 CONFIG_END @@ -35,11 +38,11 @@ ENTRY_BEGIN SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 5 IN A 10.20.30.40 + www.example.com. 205 IN A 10.20.30.40 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 210 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 10.20.30.50 + ns.example.com. 210 IN A 10.20.30.50 ENTRY_END STEP 4 CHECK_ANSWER ENTRY_BEGIN @@ -48,15 +51,15 @@ ENTRY_BEGIN SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 5 IN A 10.20.30.40 + www.example.com. 205 IN A 10.20.30.40 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 210 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 10.20.30.50 + ns.example.com. 210 IN A 10.20.30.50 ENTRY_END ; Wait for the A RRSET to expire. -STEP 5 TIME_PASSES ELAPSE 6 +STEP 5 TIME_PASSES ELAPSE 205 STEP 6 QUERY ENTRY_BEGIN @@ -80,14 +83,14 @@ ENTRY_BEGIN SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 5 IN A 10.20.30.40 + www.example.com. 205 IN A 10.20.30.40 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 210 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 10.20.30.50 + ns.example.com. 210 IN A 10.20.30.50 ENTRY_END ; The cached NS related RRSETs will not be overwritten by the fresh answer. -; The message should have a TTL of 4 instead of 5 from above. +; The message should have a TTL of 5 instead of 205 from above. STEP 9 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl @@ -95,11 +98,11 @@ ENTRY_BEGIN SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 5 IN A 10.20.30.40 + www.example.com. 205 IN A 10.20.30.40 SECTION AUTHORITY - example.com. 4 IN NS ns.example.com. + example.com. 5 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 4 IN A 10.20.30.50 + ns.example.com. 5 IN A 10.20.30.50 ENTRY_END ; Wait for the NS RRSETs to expire. @@ -107,7 +110,7 @@ STEP 10 TIME_PASSES ELAPSE 5 STEP 11 QUERY ENTRY_BEGIN - REPLY RD + REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -129,23 +132,23 @@ ENTRY_BEGIN ENTRY_END ; The SERVFAIL will trigger the serve-expired-client-timeout logic to try and ; replace the SERVFAIL with a possible cached (expired) answer. -; The A RRSET would be at 0TTL left (not expired) but the message should have -; been updated to use a TTL of 4 so expired by now. +; The A RRSET would be at 200 left but the message should have +; been updated to use a TTL of 5 so expired by now. ; If the message TTL was not updated (bug), this message would be treated as ; non-expired and the now expired NS related RRSETs would fail sanity checks ; for non-expired messages. The result would be SERVFAIL here. STEP 14 CHECK_ANSWER ENTRY_BEGIN - MATCH all ttl - REPLY QR RD RA + MATCH all ttl ede=3 + REPLY QR RD RA DO SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 0 IN A 10.20.30.40 + www.example.com. 200 IN A 10.20.30.40 SECTION AUTHORITY - example.com. 30 IN NS ns.example.com. + example.com. 123 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 30 IN A 10.20.30.50 + ns.example.com. 123 IN A 10.20.30.50 ENTRY_END SCENARIO_END diff --git a/testdata/serve_expired.rpl b/testdata/serve_expired.rpl index 990a562c7..4aa65e167 100644 --- a/testdata/serve_expired.rpl +++ b/testdata/serve_expired.rpl @@ -5,6 +5,7 @@ server: minimal-responses: no serve-expired: yes serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 access-control: 127.0.0.1/32 allow_snoop ede: yes ede-serve-expired: yes @@ -44,7 +45,7 @@ RANGE_BEGIN 0 100 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -68,15 +69,15 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL ns.example.com. IN A 1.2.3.4 ENTRY_END -; Wait for the TTL to expire -STEP 11 TIME_PASSES ELAPSE 3601 +; Wait for the TTL to expire (for all RRSets; default 3600) +STEP 11 TIME_PASSES ELAPSE 3600 ; Query again without RD bit STEP 30 QUERY @@ -94,11 +95,11 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 30 IN A 5.6.7.8 + example.com. 123 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 30 IN NS ns.example.com. + example.com. 123 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 30 IN A 1.2.3.4 + ns.example.com. 123 IN A 1.2.3.4 ENTRY_END ; Query with RD bit (the record should have been prefetched) @@ -116,7 +117,7 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL diff --git a/testdata/serve_expired_0ttl_nodata.rpl b/testdata/serve_expired_0ttl_nodata.rpl index 8ca461be2..a53df437a 100644 --- a/testdata/serve_expired_0ttl_nodata.rpl +++ b/testdata/serve_expired_0ttl_nodata.rpl @@ -5,6 +5,7 @@ server: minimal-responses: no serve-expired: yes serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 ede: yes ede-serve-expired: yes @@ -48,9 +49,9 @@ RANGE_BEGIN 30 100 SECTION QUESTION example.com. IN NS SECTION ANSWER - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -62,9 +63,9 @@ RANGE_BEGIN 30 100 SECTION ANSWER 0ttl.example.com. 0 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END RANGE_END @@ -106,13 +107,13 @@ ENTRY_BEGIN example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10 ENTRY_END -; Wait for the NXDOMAIN to expire -STEP 31 TIME_PASSES ELAPSE 32 +; Wait for the NODATA to expire +STEP 31 TIME_PASSES ELAPSE 10 ; Query again STEP 40 QUERY ENTRY_BEGIN - REPLY RD + REPLY RD DO SECTION QUESTION 0ttl.example.com. IN A ENTRY_END @@ -120,8 +121,8 @@ ENTRY_END ; Check that we get the cached NODATA STEP 50 CHECK_ANSWER ENTRY_BEGIN - MATCH all - REPLY QR RD RA NOERROR + MATCH all ede=3 + REPLY QR RD RA DO NOERROR SECTION QUESTION 0ttl.example.com. IN A SECTION AUTHORITY @@ -146,9 +147,9 @@ ENTRY_BEGIN SECTION ANSWER 0ttl.example.com. 0 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END SCENARIO_END diff --git a/testdata/serve_expired_0ttl_nxdomain.rpl b/testdata/serve_expired_0ttl_nxdomain.rpl index 7cf26aedd..7dd27b8d5 100644 --- a/testdata/serve_expired_0ttl_nxdomain.rpl +++ b/testdata/serve_expired_0ttl_nxdomain.rpl @@ -107,7 +107,7 @@ ENTRY_BEGIN ENTRY_END ; Wait for the NXDOMAIN to expire -STEP 31 TIME_PASSES ELAPSE 32 +STEP 31 TIME_PASSES ELAPSE 10 ; Query again STEP 40 QUERY @@ -117,7 +117,7 @@ ENTRY_BEGIN 0ttl.example.com. IN A ENTRY_END -; Check that we get the cached NXDOMAIN +; Check that we get the newly cached NXDOMAIN STEP 50 CHECK_ANSWER ENTRY_BEGIN MATCH all diff --git a/testdata/serve_expired_0ttl_servfail.rpl b/testdata/serve_expired_0ttl_servfail.rpl index e9d4c4884..9e5a17a09 100644 --- a/testdata/serve_expired_0ttl_servfail.rpl +++ b/testdata/serve_expired_0ttl_servfail.rpl @@ -101,7 +101,7 @@ ENTRY_BEGIN ENTRY_END ; Wait for the SERVFAIL to expire -STEP 31 TIME_PASSES ELAPSE 32 +STEP 31 TIME_PASSES ELAPSE 10 ; Query again STEP 40 QUERY diff --git a/testdata/serve_expired_client_timeout.rpl b/testdata/serve_expired_client_timeout.rpl index 5560aa05a..c44543692 100644 --- a/testdata/serve_expired_client_timeout.rpl +++ b/testdata/serve_expired_client_timeout.rpl @@ -48,7 +48,7 @@ RANGE_BEGIN 0 20 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -72,7 +72,7 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL diff --git a/testdata/serve_expired_client_timeout_no_prefetch.rpl b/testdata/serve_expired_client_timeout_no_prefetch.rpl deleted file mode 100644 index 0177dd14a..000000000 --- a/testdata/serve_expired_client_timeout_no_prefetch.rpl +++ /dev/null @@ -1,110 +0,0 @@ -; config options -server: - module-config: "validator iterator" - qname-minimisation: "no" - minimal-responses: no - serve-expired: yes - serve-expired-client-timeout: 1 - serve-expired-reply-ttl: 123 - ede: yes - ede-serve-expired: yes - -stub-zone: - name: "example.com" - stub-addr: 1.2.3.4 -CONFIG_END - -SCENARIO_BEGIN Test that no prefetch is triggered for 0TTL records with serve-expired and client-timeout enabled -; Scenario overview: -; - query for example.com. IN A -; - check that we get an answer for example.com. IN A with the correct TTL -; - query again right at the 0TTL cached entry -; - check that we get the cached answer with no prefetching triggered - -; ns.example.com. -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. - SECTION ADDITIONAL - ns.example.com. IN A 1.2.3.4 - ENTRY_END -RANGE_END - -; ns.example.com. -RANGE_BEGIN 0 10 - 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. IN NS ns.example.com. - SECTION ADDITIONAL - ns.example.com. IN A 1.2.3.4 - ENTRY_END -RANGE_END - -; Query with RD flag -STEP 0 QUERY -ENTRY_BEGIN - REPLY RD - SECTION QUESTION - example.com. IN A -ENTRY_END - -; Check that we got the correct answer (should be cached) -STEP 1 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. IN NS ns.example.com. - SECTION ADDITIONAL - ns.example.com. IN A 1.2.3.4 -ENTRY_END - -; Wait for the TTL to expire and produce a 0 TTL cached record. -STEP 10 TIME_PASSES ELAPSE 10 - -; Query again -STEP 20 QUERY -ENTRY_BEGIN - REPLY RD DO - SECTION QUESTION - example.com. IN A -ENTRY_END - -; This should come from the cache with no prefetch triggered (earlier bug). -STEP 21 CHECK_ANSWER -ENTRY_BEGIN - MATCH all ttl - REPLY QR RD RA DO NOERROR - SECTION QUESTION - example.com. IN A - SECTION ANSWER - example.com. 1 IN A 5.6.7.8 - SECTION AUTHORITY - example.com. 3591 IN NS ns.example.com. - SECTION ADDITIONAL - ns.example.com. 3591 IN A 1.2.3.4 -ENTRY_END - -; If a prefetch triggers the test will fail with 'messages pending'. - -SCENARIO_END diff --git a/testdata/serve_expired_client_timeout_servfail.rpl b/testdata/serve_expired_client_timeout_servfail.rpl index 3c5b35e17..46fe032fb 100644 --- a/testdata/serve_expired_client_timeout_servfail.rpl +++ b/testdata/serve_expired_client_timeout_servfail.rpl @@ -40,9 +40,9 @@ RANGE_BEGIN 0 20 SECTION QUESTION example.com. IN NS SECTION ANSWER - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END ENTRY_BEGIN @@ -52,11 +52,11 @@ RANGE_BEGIN 0 20 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END RANGE_END @@ -84,11 +84,11 @@ RANGE_BEGIN 50 100 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END RANGE_END @@ -108,15 +108,15 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Wait for the TTL to expire -STEP 11 TIME_PASSES ELAPSE 11 +STEP 11 TIME_PASSES ELAPSE 200 ; Query again STEP 30 QUERY @@ -209,11 +209,11 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END SCENARIO_END diff --git a/testdata/serve_expired_reply_ttl.rpl b/testdata/serve_expired_reply_ttl.rpl index e76976bde..c7859e00e 100644 --- a/testdata/serve_expired_reply_ttl.rpl +++ b/testdata/serve_expired_reply_ttl.rpl @@ -18,8 +18,11 @@ SCENARIO_BEGIN Test serve-expired with reply-ttl ; Scenario overview: ; - query for example.com. IN A ; - check that we get an answer for example.com. IN A with the correct TTL +; - query for shorterttl.example.com. IN A +; - check that we get an answer for shorterttl.example.com. IN A with the correct TTL ; - query again right after the TTL expired -; - check that we get the expired cached answer with the configured TTL +; - check that we get the expired cached answer for example.com. with the configured TTL +; - check that we get the expired cached answer for shorterttl.example.com. with its own original TTL since it is shorter than the configured one ; ns.example.com. RANGE_BEGIN 0 100 @@ -43,7 +46,21 @@ RANGE_BEGIN 0 100 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 + SECTION AUTHORITY + 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 + shorterttl.example.com. IN A + SECTION ANSWER + shorterttl.example.com. 121 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -67,15 +84,37 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL ns.example.com. IN A 1.2.3.4 ENTRY_END -; Wait for the TTL to expire -STEP 11 TIME_PASSES ELAPSE 3601 +STEP 11 QUERY +ENTRY_BEGIN + REPLY RD + SECTION QUESTION + shorterttl.example.com. IN A +ENTRY_END + + +STEP 12 CHECK_ANSWER +ENTRY_BEGIN + MATCH all ttl + REPLY QR RD RA NOERROR + SECTION QUESTION + shorterttl.example.com. IN A + SECTION ANSWER + shorterttl.example.com. 121 IN A 5.6.7.8 + SECTION AUTHORITY + example.com. IN NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. IN A 1.2.3.4 +ENTRY_END + +; Wait for the TTL to expire (for all rrsets; default 3600) +STEP 20 TIME_PASSES ELAPSE 3600 ; Query again STEP 30 QUERY @@ -100,7 +139,31 @@ ENTRY_BEGIN ns.example.com. 123 A 1.2.3.4 ENTRY_END +; Query again for shorter ttl +STEP 50 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + shorterttl.example.com. IN A +ENTRY_END + +; Check that we got a stale answer +; Note: auth, additional rrsets are already updated from previous recursion. +STEP 60 CHECK_ANSWER +ENTRY_BEGIN + MATCH all ttl ede=3 + REPLY QR RD RA DO NOERROR + SECTION QUESTION + shorterttl.example.com. IN A + SECTION ANSWER + shorterttl.example.com. 121 A 5.6.7.8 + SECTION AUTHORITY + example.com. 3600 NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. 3600 A 1.2.3.4 +ENTRY_END + ; Give time for the pending query to get answered -STEP 41 TRAFFIC +STEP 61 TRAFFIC SCENARIO_END diff --git a/testdata/serve_expired_ttl_client_timeout.rpl b/testdata/serve_expired_ttl_client_timeout.rpl index 169d070ea..d1aef32af 100644 --- a/testdata/serve_expired_ttl_client_timeout.rpl +++ b/testdata/serve_expired_ttl_client_timeout.rpl @@ -78,7 +78,7 @@ ENTRY_BEGIN ENTRY_END ; Wait for the TTL to expire + serve-expired-ttl -STEP 11 TIME_PASSES ELAPSE 3611 +STEP 11 TIME_PASSES ELAPSE 3610 ; Query again STEP 30 QUERY diff --git a/testdata/serve_expired_ttl_reset.rpl b/testdata/serve_expired_ttl_reset.rpl index 9f215c96b..f732fddcf 100644 --- a/testdata/serve_expired_ttl_reset.rpl +++ b/testdata/serve_expired_ttl_reset.rpl @@ -36,7 +36,7 @@ REPLY QR AA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 0.0.0.0 +www.example.com. 200 IN A 0.0.0.0 ENTRY_END STEP 3 CHECK_ANSWER @@ -46,11 +46,11 @@ REPLY QR RA RD NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 10 IN A 0.0.0.0 +www.example.com. 200 IN A 0.0.0.0 ENTRY_END ; Expire the record (+ serve-expired-ttl) -STEP 4 TIME_PASSES ELAPSE 12 +STEP 4 TIME_PASSES ELAPSE 201 STEP 5 QUERY ENTRY_BEGIN diff --git a/testdata/serve_expired_zerottl.rpl b/testdata/serve_expired_zerottl.rpl index 1411cb8e7..50a50bb3b 100644 --- a/testdata/serve_expired_zerottl.rpl +++ b/testdata/serve_expired_zerottl.rpl @@ -65,11 +65,11 @@ RANGE_BEGIN 11 100 SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END RANGE_END @@ -118,15 +118,15 @@ ENTRY_BEGIN SECTION QUESTION example.com. IN A SECTION ANSWER - example.com. 10 IN A 5.6.7.8 + example.com. 200 IN A 5.6.7.8 SECTION AUTHORITY - example.com. 10 IN NS ns.example.com. + example.com. 200 IN NS ns.example.com. SECTION ADDITIONAL - ns.example.com. 10 IN A 1.2.3.4 + ns.example.com. 200 IN A 1.2.3.4 ENTRY_END ; Wait for the TTL to expire -STEP 30 TIME_PASSES ELAPSE 11 +STEP 30 TIME_PASSES ELAPSE 200 ; Query with RD flag STEP 40 QUERY diff --git a/testdata/subnet_global_prefetch_always_forward.crpl b/testdata/subnet_global_prefetch_always_forward.crpl index 775474cbc..f2c8bdb60 100644 --- a/testdata/subnet_global_prefetch_always_forward.crpl +++ b/testdata/subnet_global_prefetch_always_forward.crpl @@ -7,12 +7,15 @@ server: target-fetch-policy: "0 0 0 0 0" serve-expired: yes serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 client-subnet-always-forward: yes module-config: "subnetcache iterator" verbosity: 3 access-control: 127.0.0.1 allow_snoop qname-minimisation: no minimal-responses: no + ede: yes + ede-serve-expired: yes stub-zone: name: "." @@ -100,7 +103,7 @@ RANGE_BEGIN 0 100 SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 10 IN A 10.20.30.40 + www.example.com. 200 IN A 10.20.30.40 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -131,11 +134,11 @@ ns.example.com. IN A 1.2.3.4 ENTRY_END ; Wait for the TTL to expire -STEP 3 TIME_PASSES ELAPSE 20 +STEP 3 TIME_PASSES ELAPSE 200 STEP 11 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -143,16 +146,16 @@ ENTRY_END ; This record came from the global cache and a prefetch was triggered STEP 12 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 10.20.30.40 +www.example.com. 123 IN A 10.20.30.40 SECTION AUTHORITY -example.com. 3580 IN NS ns.example.com. +example.com. 3400 IN NS ns.example.com. SECTION ADDITIONAL -ns.example.com. 3580 IN A 1.2.3.4 +ns.example.com. 3400 IN A 1.2.3.4 ENTRY_END STEP 13 CHECK_OUT_QUERY diff --git a/testdata/subnet_global_prefetch_expired.crpl b/testdata/subnet_global_prefetch_expired.crpl index 374bf3e69..a347550ba 100644 --- a/testdata/subnet_global_prefetch_expired.crpl +++ b/testdata/subnet_global_prefetch_expired.crpl @@ -16,7 +16,11 @@ server: serve-expired: yes serve-expired-client-timeout: 0 serve-expired-ttl: 1 - prefetch: yes + serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 123 + #prefetch: yes #not needed, expired answers also trigger refetch + ede: yes + ede-serve-expired: yes stub-zone: name: "." @@ -113,7 +117,7 @@ RANGE_BEGIN 0 10 SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 10 IN A 10.20.30.40 + www.example.com. 200 IN A 10.20.30.40 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -147,7 +151,7 @@ RANGE_BEGIN 11 100 SECTION QUESTION www.example.com. IN A SECTION ANSWER - www.example.com. 10 IN A 10.20.30.40 + www.example.com. 200 IN A 10.20.30.40 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -178,7 +182,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. IN A 10.20.30.40 +www.example.com. 200 IN A 10.20.30.40 SECTION AUTHORITY example.com. IN NS ns.example.com. SECTION ADDITIONAL @@ -186,11 +190,11 @@ ns.example.com. IN A 1.2.3.4 ENTRY_END ; Try to trigger a prefetch with expired data -STEP 3 TIME_PASSES ELAPSE 11 +STEP 3 TIME_PASSES ELAPSE 200 STEP 11 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -198,19 +202,18 @@ ENTRY_END ; This expired record came from the global cache and a prefetch is triggered. STEP 12 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RD RA NOERROR +MATCH all ttl ede=3 +REPLY QR RD RA DO NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 30 IN A 10.20.30.40 +www.example.com. 123 IN A 10.20.30.40 SECTION AUTHORITY -example.com. 3589 IN NS ns.example.com. +example.com. 3400 IN NS ns.example.com. SECTION ADDITIONAL -ns.example.com. 3589 IN A 1.2.3.4 +ns.example.com. 3400 IN A 1.2.3.4 ENTRY_END -;STEP 13 TRAFFIC ; Allow enough time to pass so that the expired record from the global cache ; cannot be used anymore. STEP 14 TIME_PASSES ELAPSE 1 @@ -232,7 +235,7 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN A SECTION ANSWER -www.example.com. 9 IN A 10.20.30.40 +www.example.com. 199 IN A 10.20.30.40 SECTION AUTHORITY example.com. 3599 IN NS ns.example.com. SECTION ADDITIONAL diff --git a/testdata/ttl_zero_cacherep.rpl b/testdata/ttl_zero_cacherep.rpl index 7e9eb5394..fe6f9dcc7 100644 --- a/testdata/ttl_zero_cacherep.rpl +++ b/testdata/ttl_zero_cacherep.rpl @@ -260,27 +260,6 @@ ENTRY_END STEP 140 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl -REPLY QR RD RA NOERROR -SECTION QUESTION -www.example.com. IN A -SECTION ANSWER -; note that it did not send 0 TTL. The message can be cached by the receiver -; during the last second of the TTL. -www.example.com. 1 IN A 1.2.3.4 -ENTRY_END - -STEP 150 TIME_PASSES ELAPSE 1 - -STEP 160 QUERY -ENTRY_BEGIN -REPLY RD -SECTION QUESTION -www.example.com. IN A -ENTRY_END - -STEP 170 CHECK_ANSWER -ENTRY_BEGIN -MATCH all ttl REPLY QR RD RA SERVFAIL SECTION QUESTION www.example.com. IN A diff --git a/util/data/msgencode.c b/util/data/msgencode.c index b389800d0..22c3ba9d7 100644 --- a/util/data/msgencode.c +++ b/util/data/msgencode.c @@ -496,10 +496,18 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt, return r; sldns_buffer_write(pkt, &key->rk.type, 2); sldns_buffer_write(pkt, &key->rk.rrset_class, 2); - if(data->rr_ttl[j] < adjust) + if(key->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) { + sldns_buffer_write_u32(pkt, 0); + } else if(adjust == 0) { + sldns_buffer_write_u32(pkt, data->rr_ttl[i]); + } else if(TTL_IS_EXPIRED(data->rr_ttl[j], adjust)) { sldns_buffer_write_u32(pkt, - SERVE_EXPIRED?SERVE_EXPIRED_REPLY_TTL:0); - else sldns_buffer_write_u32(pkt, data->rr_ttl[j]-adjust); + EXPIRED_REPLY_TTL_CALC( + data->rr_ttl[i], data->ttl_add)); + } else { + sldns_buffer_write_u32(pkt, + data->rr_ttl[i] - adjust); + } if(c) { if((r=compress_rdata(pkt, data->rr_data[j], data->rr_len[j], region, tree, c, @@ -533,10 +541,18 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt, } sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_RRSIG); sldns_buffer_write(pkt, &key->rk.rrset_class, 2); - if(data->rr_ttl[i] < adjust) + if(key->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) { + sldns_buffer_write_u32(pkt, 0); + } else if(adjust == 0) { + sldns_buffer_write_u32(pkt, data->rr_ttl[i]); + } else if(TTL_IS_EXPIRED(data->rr_ttl[i], adjust)) { sldns_buffer_write_u32(pkt, - SERVE_EXPIRED?SERVE_EXPIRED_REPLY_TTL:0); - else sldns_buffer_write_u32(pkt, data->rr_ttl[i]-adjust); + EXPIRED_REPLY_TTL_CALC( + data->rr_ttl[i], data->ttl_add)); + } else { + sldns_buffer_write_u32(pkt, + data->rr_ttl[i] - adjust); + } /* rrsig rdata cannot be compressed, perform 100+ byte * memcopy. */ sldns_buffer_write(pkt, data->rr_data[i], @@ -993,11 +1009,11 @@ attach_edns_record(sldns_buffer* pkt, struct edns_data* edns) attach_edns_record_max_msg_sz(pkt, edns, edns->udp_size); } -int -reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, +int +reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, sldns_buffer* pkt, time_t timenow, - int cached, struct regional* region, uint16_t udpsize, - struct edns_data* edns, int dnssec, int secure, int cached_ttl) + int cached, struct regional* region, uint16_t udpsize, + struct edns_data* edns, int dnssec, int secure) { uint16_t flags; unsigned int attach_edns = 0; @@ -1022,17 +1038,6 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, flags &= ~BIT_AD; } log_assert((flags & BIT_QR)); /* QR bit must be on in our replies */ - if(cached_ttl && rep->ttl - timenow == 0) { - /* The last remaining second of the TTL for a cached response - * is replied. This makes a 0 in the protocol message. The - * response is valid for the cache, but the DNS TTL 0 item - * causes the received to drop the contents. Even though the - * contents are cachable, so the time used is decremented - * to change that into 1 second, and it can be cached, and - * used for expired response generation, and does not give - * repeated queries during that last second. */ - timenow --; - } if(udpsize < LDNS_HEADER_SIZE) return 0; /* currently edns does not change during calculations; diff --git a/util/data/msgencode.h b/util/data/msgencode.h index 231361680..0363a90bf 100644 --- a/util/data/msgencode.h +++ b/util/data/msgencode.h @@ -64,16 +64,12 @@ struct edns_data; * or if edns_present = 0, it is not included. * @param dnssec: if 0 DNSSEC records are omitted from the answer. * @param secure: if 1, the AD bit is set in the reply. - * @param cached_ttl: the ttl is from a cache response. So that means it - * was some value minus the current time, and not an authoritative - * response with an autoritative TTL or a direct upstream response, - * that could have upstream TTL 0 items. * @return: 0 on error (server failure). */ -int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, +int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, struct sldns_buffer* dest, time_t timenow, - int cached, struct regional* region, uint16_t udpsize, - struct edns_data* edns, int dnssec, int secure, int cached_ttl); + int cached, struct regional* region, uint16_t udpsize, + struct edns_data* edns, int dnssec, int secure); /** * Regenerate the wireformat from the stored msg reply. diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 6963d8501..fc7018def 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -1361,3 +1361,13 @@ msgparse_rrset_remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* * the rr->next works fine to continue. */ return rrset->rr_count == 0; } + +#ifdef UNBOUND_DEBUG +time_t debug_expired_reply_ttl_calc(time_t ttl, time_t ttl_add) { + /* Check that we are serving expired when this is called */ + /* ttl (absolute) should be later than ttl_add */ + log_assert(SERVE_EXPIRED && ttl_add <= ttl); + return (SERVE_EXPIRED_REPLY_TTL < (ttl) - (ttl_add) ? + SERVE_EXPIRED_REPLY_TTL : (ttl) - (ttl_add)); +} +#endif diff --git a/util/data/msgparse.h b/util/data/msgparse.h index 7de4e394f..373de677d 100644 --- a/util/data/msgparse.h +++ b/util/data/msgparse.h @@ -98,6 +98,31 @@ extern time_t SERVE_EXPIRED_REPLY_TTL; /** If we serve the original TTL or decrementing TTLs */ extern int SERVE_ORIGINAL_TTL; +/** calculate the prefetch TTL as 90% of original. Calculation + * without numerical overflow (uin32_t) */ +#define PREFETCH_TTL_CALC(ttl) ((ttl) - (ttl)/10) + +/* caclulate the TTL used for expired answers to somewhat make sense wrt the + * original TTL; don't reply with higher TTL than the original */ +#ifdef UNBOUND_DEBUG +time_t debug_expired_reply_ttl_calc(time_t ttl, time_t ttl_add); +#define EXPIRED_REPLY_TTL_CALC(ttl, ttl_add) \ + debug_expired_reply_ttl_calc(ttl, ttl_add) +#else +#define EXPIRED_REPLY_TTL_CALC(ttl, ttl_add) \ + (SERVE_EXPIRED_REPLY_TTL < (ttl) - (ttl_add) ? \ + SERVE_EXPIRED_REPLY_TTL : (ttl) - (ttl_add)) +#endif + +/** Update the reply_info TTL from an RRSet's TTL, essentially keeping the TTL + * sane with all the (progressively added) rrsets to the message */ +#define UPDATE_TTL_FROM_RRSET(ttl, rrsetttl) \ + ((ttl) = ((ttl) < (rrsetttl)) ? (ttl) : (rrsetttl)) + +/** Check if TTL is expired. 0 TTL is considered expired. + * Used mainly to identify parts of the code that do this comparison. */ +#define TTL_IS_EXPIRED(ttl, now) ((ttl) <= (now)) + /** * Data stored in scratch pad memory during parsing. * Stores the data that will enter into the msgreply and packet result. diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 6b65e1eb1..d67c1d0ca 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -178,9 +178,9 @@ reply_info_alloc_rrset_keys(struct reply_info* rep, struct alloc_cache* alloc, int reply_info_can_answer_expired(struct reply_info* rep, time_t timenow) { - log_assert(rep->ttl < timenow); + log_assert(TTL_IS_EXPIRED(rep->ttl, timenow)); /* Really expired */ - if(SERVE_EXPIRED_TTL && rep->serve_expired_ttl < timenow) return 0; + if(SERVE_EXPIRED_TTL && TTL_IS_EXPIRED(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 && @@ -188,12 +188,13 @@ reply_info_can_answer_expired(struct reply_info* rep, time_t timenow) return 1; } -int reply_info_could_use_expired(struct reply_info* rep, time_t timenow) +int +reply_info_could_use_expired(struct reply_info* rep, time_t timenow) { - log_assert(rep->ttl < timenow); + log_assert(TTL_IS_EXPIRED(rep->ttl, timenow)); /* Really expired */ - if(SERVE_EXPIRED_TTL && rep->serve_expired_ttl < timenow && - !SERVE_EXPIRED_TTL_RESET) return 0; + if(SERVE_EXPIRED_TTL && TTL_IS_EXPIRED(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 && @@ -229,7 +230,7 @@ make_new_reply_info(const struct reply_info* rep, struct regional* region, } /** find the minimumttl in the rdata of SOA record */ -static time_t +static uint32_t soa_find_minttl(struct rr_parse* rr) { uint16_t rlen = sldns_read_uint16(rr->ttl_data+4); @@ -237,7 +238,7 @@ soa_find_minttl(struct rr_parse* rr) return 0; /* rdata too small for SOA (dname, dname, 5*32bit) */ /* minimum TTL is the last 32bit value in the rdata of the record */ /* at position ttl_data + 4(ttl) + 2(rdatalen) + rdatalen - 4(timeval)*/ - return (time_t)sldns_read_uint32(rr->ttl_data+6+rlen-4); + return sldns_read_uint32(rr->ttl_data+6+rlen-4); } /** do the rdata copy */ @@ -247,37 +248,40 @@ rdata_copy(sldns_buffer* pkt, struct packed_rrset_data* data, uint8_t* to, sldns_pkt_section section) { uint16_t pkt_len; + uint32_t ttl; const sldns_rr_descriptor* desc; - *rr_ttl = sldns_read_uint32(rr->ttl_data); + ttl = sldns_read_uint32(rr->ttl_data); /* RFC 2181 Section 8. if msb of ttl is set treat as if zero. */ - if((*rr_ttl & 0x80000000U)) - *rr_ttl = 0; + /* RFC 8767 Section 4. values with high-order bit as positive, not 0. ++ * As such, it will be capped by MAX_TTL below. */ if(type == LDNS_RR_TYPE_SOA && section == LDNS_SECTION_AUTHORITY) { /* negative response. see if TTL of SOA record larger than the * minimum-ttl in the rdata of the SOA record */ - if(*rr_ttl > soa_find_minttl(rr)) *rr_ttl = soa_find_minttl(rr); + if(ttl > soa_find_minttl(rr)) ttl = soa_find_minttl(rr); if(!SERVE_ORIGINAL_TTL) { /* If MIN_NEG_TTL is configured skip setting MIN_TTL */ - if(MIN_NEG_TTL <= 0 && *rr_ttl < MIN_TTL) { - *rr_ttl = MIN_TTL; + if(MIN_NEG_TTL <= 0 && ttl < MIN_TTL) { + ttl = MIN_TTL; } - if(*rr_ttl > MAX_TTL) *rr_ttl = MAX_TTL; + if(ttl > MAX_TTL) ttl = MAX_TTL; } /* MAX_NEG_TTL overrides the min and max ttl of everything * else; it is for a more specific record */ - if(*rr_ttl > MAX_NEG_TTL) *rr_ttl = MAX_NEG_TTL; + if(ttl > MAX_NEG_TTL) ttl = MAX_NEG_TTL; /* MIN_NEG_TTL overrides the min and max ttl of everything * else if configured; it is for a more specific record */ - if(MIN_NEG_TTL > 0 && *rr_ttl < MIN_NEG_TTL) { - *rr_ttl = MIN_NEG_TTL; + if(MIN_NEG_TTL > 0 && ttl < MIN_NEG_TTL) { + ttl = MIN_NEG_TTL; } } else if(!SERVE_ORIGINAL_TTL) { - if(*rr_ttl < MIN_TTL) *rr_ttl = MIN_TTL; - if(*rr_ttl > MAX_TTL) *rr_ttl = MAX_TTL; + if(ttl < MIN_TTL) ttl = MIN_TTL; + if(ttl > MAX_TTL) ttl = MAX_TTL; } - if(*rr_ttl < data->ttl) - data->ttl = *rr_ttl; + if(ttl < data->ttl) + data->ttl = ttl; + /* We have concluded the TTL checks */ + *rr_ttl = (time_t)ttl; if(rr->outside_packet) { /* uncompressed already, only needs copy */ @@ -481,6 +485,7 @@ parse_copy_decompress_rrset(sldns_buffer* pkt, struct msg_parse* msg, pk->entry.key = (void*)pk; pk->entry.hash = pset->hash; data->trust = get_rrset_trust(msg, pset); + pk->rk.flags |= (data->ttl == 0) ? PACKED_RRSET_UPSTREAM_0TTL : 0; return 1; } @@ -617,6 +622,29 @@ reply_info_set_ttls(struct reply_info* rep, time_t timenow) } } +void +reply_info_absolute_ttls(struct reply_info* rep, time_t ttl, time_t ttl_add) +{ + size_t i, j; + rep->ttl = ttl; + rep->prefetch_ttl = PREFETCH_TTL_CALC(ttl); + rep->serve_expired_ttl = ttl + SERVE_EXPIRED_TTL; + /* 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; + if(i>0 && rep->ref[i].key == rep->ref[i-1].key) + continue; + data->ttl = ttl; + for(j=0; jcount + data->rrsig_count; j++) { + data->rr_ttl[j] = ttl; + } + data->ttl_add = ttl_add; + } +} + void reply_info_parsedelete(struct reply_info* rep, struct alloc_cache* alloc) { diff --git a/util/data/msgreply.h b/util/data/msgreply.h index b65abd922..a63ac49a3 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -60,10 +60,6 @@ struct local_rrset; struct dns_msg; enum comm_point_type; -/** calculate the prefetch TTL as 90% of original. Calculation - * without numerical overflow (uin32_t) */ -#define PREFETCH_TTL_CALC(ttl) ((ttl) - (ttl)/10) - /** * Structure to store query information that makes answers to queries * different. @@ -340,6 +336,15 @@ void reply_info_sortref(struct reply_info* rep); */ void reply_info_set_ttls(struct reply_info* rep, time_t timenow); +/** + * Set TTLs inside the replyinfo to the given absolute values. + * @param rep: reply info. rrsets must be filled in. + * Also refs must be filled in. + * @param ttl: absolute ttl value to be set. + * @param ttl_add: the current time to be used verbatim for ttl_add in the rrsets. + */ +void reply_info_absolute_ttls(struct reply_info* rep, time_t ttl, time_t ttl_add); + /** * Delete reply_info and packed_rrsets (while they are not yet added to the * hashtables.). Returns rrsets to the alloc cache. diff --git a/util/data/packed_rrset.c b/util/data/packed_rrset.c index d18486cc5..89ece3c03 100644 --- a/util/data/packed_rrset.c +++ b/util/data/packed_rrset.c @@ -336,10 +336,9 @@ packed_rrset_copy_region(struct ub_packed_rrset_key* key, struct ub_packed_rrset_key* ck = regional_alloc(region, sizeof(struct ub_packed_rrset_key)); struct packed_rrset_data* d; - struct packed_rrset_data* data = (struct packed_rrset_data*) - key->entry.data; + struct packed_rrset_data* data = key->entry.data; size_t dsize, i; - time_t adjust = 0; + time_t now_control; if(!ck) return NULL; ck->id = key->id; @@ -352,22 +351,31 @@ packed_rrset_copy_region(struct ub_packed_rrset_key* key, if(!ck->rk.dname) return NULL; dsize = packed_rrset_sizeof(data); - d = (struct packed_rrset_data*)regional_alloc_init(region, data, dsize); + d = regional_alloc_init(region, data, dsize); if(!d) return NULL; ck->entry.data = d; packed_rrset_ptr_fixup(d); - /* make TTLs relative - once per rrset */ - adjust = SERVE_ORIGINAL_TTL ? data->ttl_add : now; - for(i=0; icount + d->rrsig_count; i++) { - if(d->rr_ttl[i] < adjust) - d->rr_ttl[i] = SERVE_EXPIRED?SERVE_EXPIRED_REPLY_TTL:0; - else d->rr_ttl[i] -= adjust; + /* make TTLs relative - once per rr */ + if(now > 0) { + /* NS RRSets may be here with ttl_add higher than now because + * of the novel ghost attack mitigation i.e., using the + * qstarttime for NS RRSets. In that case make sure that the + * returned TTL is not higher than the original one. */ + log_assert(d->ttl_add <= now || + (ntohs(key->rk.type) == LDNS_RR_TYPE_NS)); + now_control = SERVE_ORIGINAL_TTL ? data->ttl_add + : (d->ttl_add > now ? d->ttl_add : now ); + for(i=0; icount + d->rrsig_count; i++) { + if(TTL_IS_EXPIRED(d->rr_ttl[i], now_control)) { + d->rr_ttl[i] = EXPIRED_REPLY_TTL_CALC(d->rr_ttl[i], data->ttl_add); + } else d->rr_ttl[i] -= now_control; + } + if(TTL_IS_EXPIRED(d->ttl, now_control)) { + d->ttl = EXPIRED_REPLY_TTL_CALC(d->ttl, data->ttl_add); + } else d->ttl -= now_control; + d->ttl_add = 0; /* TTLs have been made relative */ } - if(d->ttl < adjust) - d->ttl = SERVE_EXPIRED?SERVE_EXPIRED_REPLY_TTL:0; - else d->ttl -= adjust; - d->ttl_add = 0; /* TTLs have been made relative */ return ck; } diff --git a/util/data/packed_rrset.h b/util/data/packed_rrset.h index 776e8d092..4e0911ccf 100644 --- a/util/data/packed_rrset.h +++ b/util/data/packed_rrset.h @@ -70,6 +70,8 @@ typedef uint64_t rrset_id_type; #define PACKED_RRSET_RPZ 0x8 /** this rrset is A/AAAA and is an unverified glue record */ #define PACKED_RRSET_UNVERIFIED_GLUE 0x10 +/** this rrset has a 0TTL from upstream */ +#define PACKED_RRSET_UPSTREAM_0TTL 0x20 /** number of rrs and rrsets for integer overflow protection. More than * this is not really possible (64K packet has much less RRs and RRsets) in @@ -99,6 +101,7 @@ struct packed_rrset_key { * o PACKED_RRSET_FIXEDTTL (not supposed to be cached) * o PACKED_RRSET_RPZ * o PACKED_RRSET_UNVERIFIED_GLUE + * o PACKED_RRSET_UPSTREAM_0TTL (not supposed to be cached) */ uint32_t flags; /** the rrset type in network format */ diff --git a/validator/val_neg.c b/validator/val_neg.c index bc3a83aeb..7817d56fc 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -1066,11 +1066,7 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len, qname, qname_len, qtype, qclass, flags, now, 0); struct packed_rrset_data* d; if(!k) return NULL; - d = (struct packed_rrset_data*)k->entry.data; - if(d->ttl < now) { - lock_rw_unlock(&k->entry.lock); - return NULL; - } + d = k->entry.data; /* only secure or unchecked records that have signatures. */ if( ! ( d->security == sec_status_secure || (d->security == sec_status_unchecked && diff --git a/validator/val_utils.c b/validator/val_utils.c index 549264d76..1a5f19673 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -1310,6 +1310,7 @@ val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, /* DS rrset exists. Return it to the validator immediately*/ struct ub_packed_rrset_key* copy = packed_rrset_copy_region( rrset, region, *env->now); + struct packed_rrset_data* d = copy->entry.data; lock_rw_unlock(&rrset->entry.lock); if(!copy) return NULL; @@ -1319,6 +1320,7 @@ val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, msg->rep->rrsets[0] = copy; msg->rep->rrset_count++; msg->rep->an_numrrsets++; + UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return msg; } /* lookup in rrset and negative cache for NSEC/NSEC3 */ From bc61034f602fef0ec445d68d9abb488b7c3a946c Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 17 Sep 2025 12:19:20 +0200 Subject: [PATCH 4/4] code review: use proper roundrobin index Co-authored-by: Wouter Wijngaards --- util/data/msgencode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/data/msgencode.c b/util/data/msgencode.c index 22c3ba9d7..019da7253 100644 --- a/util/data/msgencode.c +++ b/util/data/msgencode.c @@ -499,14 +499,14 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt, if(key->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) { sldns_buffer_write_u32(pkt, 0); } else if(adjust == 0) { - sldns_buffer_write_u32(pkt, data->rr_ttl[i]); + sldns_buffer_write_u32(pkt, data->rr_ttl[j]); } else if(TTL_IS_EXPIRED(data->rr_ttl[j], adjust)) { sldns_buffer_write_u32(pkt, EXPIRED_REPLY_TTL_CALC( - data->rr_ttl[i], data->ttl_add)); + data->rr_ttl[j], data->ttl_add)); } else { sldns_buffer_write_u32(pkt, - data->rr_ttl[i] - adjust); + data->rr_ttl[j] - adjust); } if(c) { if((r=compress_rdata(pkt, data->rr_data[j],