#include "iterator/iter_priv.h"
#include "validator/val_neg.h"
#include "services/cache/dns.h"
+#include "services/cache/rrset.h"
#include "services/cache/infra.h"
#include "services/authzone.h"
#include "util/module.h"
}
return final_state(iq);
} else if(type == RESPONSE_TYPE_REFERRAL) {
+ struct delegpt* old_dp = NULL;
/* REFERRAL type responses get a reset of the
* delegation point, and back to the QUERYTARGETS_STATE. */
verbose(VERB_DETAIL, "query response was REFERRAL");
/* Reset the event state, setting the current delegation
* point to the referral. */
iq->deleg_msg = iq->response;
+ /* Keep current delegation point for label comparison */
+ old_dp = iq->dp;
iq->dp = delegpt_from_message(iq->response, qstate->region);
if (qstate->env->cfg->qname_minimisation)
iq->minimisation_state = INIT_MINIMISE_STATE;
errinf(qstate, "malloc failure, for delegation point");
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
+ if(old_dp->namelabs + 1 < iq->dp->namelabs) {
+ /* We got a grandchild delegation (more than one label
+ * difference) than expected. Check for in-between
+ * delegations in the cache and remove them.
+ * They could prove problematic when they expire
+ * and rrset_expired_above() encounters them during
+ * delegation cache lookups. */
+ uint8_t* qname = iq->dp->name;
+ size_t qnamelen = iq->dp->namelen;
+ rrset_cache_remove_above(qstate->env->rrset_cache,
+ &qname, &qnamelen, LDNS_RR_TYPE_NS,
+ iq->qchase.qclass, *qstate->env->now,
+ old_dp->name, old_dp->namelen);
+ }
if(!cache_fill_missing(qstate->env, iq->qchase.qclass,
qstate->region, iq->dp)) {
errinf(qstate, "malloc failure, copy extra info into delegation point");
slabhash_insert(env->msg_cache, hash, &e->entry, rep, env->alloc);
}
-/** see if an rrset is expired above the qname, return upper qname. */
-static int
-rrset_expired_above(struct module_env* env, uint8_t** qname, size_t* qnamelen,
- uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* expiretop,
- size_t expiretoplen)
-{
- struct ub_packed_rrset_key *rrset;
- uint8_t lablen;
-
- while(*qnamelen > 0) {
- /* look one label higher */
- lablen = **qname;
- *qname += lablen + 1;
- *qnamelen -= lablen + 1;
- if(*qnamelen <= 0)
- break;
-
- /* looks up with a time of 0, to see expired entries */
- if((rrset = rrset_cache_lookup(env->rrset_cache, *qname,
- *qnamelen, searchtype, qclass, 0, 0, 0))) {
- struct packed_rrset_data* data =
- (struct packed_rrset_data*)rrset->entry.data;
- if(now > data->ttl) {
- /* it is expired, this is not wanted */
- lock_rw_unlock(&rrset->entry.lock);
- log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass);
- return 1;
- }
- /* it is not expired, continue looking */
- lock_rw_unlock(&rrset->entry.lock);
- }
-
- /* do not look above the expiretop. */
- if(expiretop && *qnamelen == expiretoplen &&
- query_dname_compare(*qname, expiretop)==0)
- break;
- }
- return 0;
-}
-
/** find closest NS or DNAME and returns the rrset (locked) */
static struct ub_packed_rrset_key*
find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen,
/* check for expiry, but we have to let go of the rrset
* for the lock ordering */
lock_rw_unlock(&rrset->entry.lock);
- /* the expired_above function always takes off one
- * label (if qnamelen>0) and returns the final qname
- * where it searched, so we can continue from there
- * turning the O N*N search into O N. */
- if(!rrset_expired_above(env, &qname, &qnamelen,
- searchtype, qclass, now, expiretop,
+ /* the rrset_cache_expired_above function always takes
+ * off one label (if qnamelen>0) and returns the final
+ * qname where it searched, so we can continue from
+ * there turning the O N*N search into O N. */
+ if(!rrset_cache_expired_above(env->rrset_cache, &qname,
+ &qnamelen, searchtype, qclass, now, expiretop,
expiretoplen)) {
/* we want to return rrset, but it may be
* gone from cache, if so, just loop like
#include "util/data/packed_rrset.h"
#include "util/data/msgreply.h"
#include "util/data/msgparse.h"
+#include "util/data/dname.h"
#include "util/regional.h"
#include "util/alloc.h"
#include "util/net_help.h"
lock_rw_unlock(&e->lock);
}
+void
+rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname, size_t*
+ qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t*
+ qnametop, size_t qnametoplen)
+{
+ struct ub_packed_rrset_key *rrset;
+ uint8_t lablen;
+
+ while(*qnamelen > 0) {
+ /* look one label higher */
+ lablen = **qname;
+ *qname += lablen + 1;
+ *qnamelen -= lablen + 1;
+ if(*qnamelen <= 0)
+ return;
+
+ /* stop at qnametop */
+ if(qnametop && *qnamelen == qnametoplen &&
+ query_dname_compare(*qname, qnametop)==0)
+ return;
+
+ if(verbosity >= VERB_ALGO) {
+ /* looks up with a time of 0, to see expired entries */
+ if((rrset = rrset_cache_lookup(r, *qname,
+ *qnamelen, searchtype, qclass, 0, 0, 0))) {
+ struct packed_rrset_data* data =
+ (struct packed_rrset_data*)rrset->entry.data;
+ int expired = (now > data->ttl);
+ lock_rw_unlock(&rrset->entry.lock);
+ if(expired)
+ log_nametypeclass(verbosity, "this "
+ "(grand)parent rrset will be "
+ "removed (expired)",
+ *qname, searchtype, qclass);
+ else log_nametypeclass(verbosity, "this "
+ "(grand)parent rrset will be "
+ "removed",
+ *qname, searchtype, qclass);
+ }
+ }
+ rrset_cache_remove(r, *qname, *qnamelen, searchtype, qclass, 0);
+ }
+}
+
+int
+rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname, size_t*
+ qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t*
+ qnametop, size_t qnametoplen)
+{
+ struct ub_packed_rrset_key *rrset;
+ uint8_t lablen;
+
+ while(*qnamelen > 0) {
+ /* look one label higher */
+ lablen = **qname;
+ *qname += lablen + 1;
+ *qnamelen -= lablen + 1;
+ if(*qnamelen <= 0)
+ break;
+
+ /* looks up with a time of 0, to see expired entries */
+ if((rrset = rrset_cache_lookup(r, *qname,
+ *qnamelen, searchtype, qclass, 0, 0, 0))) {
+ struct packed_rrset_data* data =
+ (struct packed_rrset_data*)rrset->entry.data;
+ if(now > data->ttl) {
+ /* it is expired, this is not wanted */
+ lock_rw_unlock(&rrset->entry.lock);
+ log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass);
+ return 1;
+ }
+ /* it is not expired, continue looking */
+ lock_rw_unlock(&rrset->entry.lock);
+ }
+
+ /* do not look above the qnametop. */
+ if(qnametop && *qnamelen == qnametoplen &&
+ query_dname_compare(*qname, qnametop)==0)
+ break;
+ }
+ return 0;
+}
+
void rrset_cache_remove(struct rrset_cache* r, uint8_t* nm, size_t nmlen,
uint16_t type, uint16_t dclass, uint32_t flags)
{
void rrset_check_sec_status(struct rrset_cache* r,
struct ub_packed_rrset_key* rrset, time_t now);
+/**
+ * Removes rrsets above the qname, returns upper qname.
+ * @param r: the rrset cache.
+ * @param qname: the start qname, also used as the output.
+ * @param qnamelen: length of qname, updated when it returns.
+ * @param searchtype: qtype to search for.
+ * @param qclass: qclass to search for.
+ * @param now: current time.
+ * @param qnametop: the top qname to stop removal (it is not removed).
+ * @param qnametoplen: length of qnametop.
+ */
+void rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname,
+ size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now,
+ uint8_t* qnametop, size_t qnametoplen);
+
+/**
+ * Sees if an rrset is expired above the qname, returns upper qname.
+ * @param r: the rrset cache.
+ * @param qname: the start qname, also used as the output.
+ * @param qnamelen: length of qname, updated when it returns.
+ * @param searchtype: qtype to search for.
+ * @param qclass: qclass to search for.
+ * @param now: current time.
+ * @param qnametop: the top qname, don't look farther than that.
+ * @param qnametoplen: length of qnametop.
+ * @return true if there is an expired rrset above, false otherwise.
+ */
+int rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname,
+ size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now,
+ uint8_t* qnametop, size_t qnametoplen);
+
/**
* Remove an rrset from the cache, by name and type and flags
* @param r: rrset cache
--- /dev/null
+; config options
+server:
+ target-fetch-policy: "0 0 0 0 0"
+ qname-minimisation: "no"
+ minimal-responses: no
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test that deep delegation from the parent deletes intermediate delegations to avoid triggering the ghost domain countermeasure.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 19
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 86400 IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. 86400 IN A 193.0.14.129
+ENTRY_END
+
+; we will explicitly ask for this
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION AUTHORITY
+com. 10 IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. 86400 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 NS
+SECTION AUTHORITY
+example.com. 86400 IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. 86400 IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+ ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. 10 IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. 86400 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 NS
+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 NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.com. IN A
+SECTION ANSWER
+ns.example.com. IN A 1.2.3.4
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.com. IN AAAA
+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
+a.example.com. IN A
+SECTION ANSWER
+a.example.com. IN A 10.20.30.40
+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
+b.example.com. IN A
+SECTION ANSWER
+b.example.com. IN A 10.20.30.40
+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
+c.example.com. IN A
+SECTION ANSWER
+c.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; get the com. IN NS delegation in cache
+STEP 0 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+com. IN NS
+ENTRY_END
+
+STEP 1 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. 10 IN NS a.gtld-servers.net.
+ENTRY_END
+
+STEP 2 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.example.com. IN A
+ENTRY_END
+
+STEP 3 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.example.com. IN A
+SECTION ANSWER
+a.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; time passes for com. IN NS to expire.
+STEP 9 TIME_PASSES ELAPSE 11
+
+; the following query should go to the root instead of example.com. IN NS
+; because com. IN NS is expired
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+b.example.com. IN A
+ENTRY_END
+
+; root replies with the example.com IN NS delegation
+; the expired com. IN NS delegation should be deleted
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+b.example.com. IN A
+SECTION ANSWER
+b.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; root is offline in this range.
+; the following query should go straight to the example.com. IN NS delegation
+; because the expired com. IN NS should not be in the cache anymore
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+c.example.com. IN A
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+c.example.com. IN A
+SECTION ANSWER
+c.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+SCENARIO_END