diff --git a/doc/Changelog b/doc/Changelog index f8e6b862b..608e41a64 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,11 @@ 9 December 2009: Wouter - Fix Bug#287(reopened): update of ldns tarball with fix for parse errors generated for domain names like '.example.com'. + - Fix SOA excluded from negative DS responses. Reported by Hauke + Lampe. The negative cache did not include proper SOA records for + negative qtype DS responses which makes BIND barf on it, such + responses are now only used internally. + - Fix negative cache lookup of closestencloser check of DS type bit. 8 December 2009: Wouter - Fix for lookup of parent-child disagreement domains, where the diff --git a/iterator/iterator.c b/iterator/iterator.c index a2491ae29..576038ef0 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -872,7 +872,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, * NOERROR/NODATA or NXDOMAIN answers that need validation */ msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase, qstate->region, qstate->env->rrset_cache, - qstate->env->scratch_buffer, *qstate->env->now); + qstate->env->scratch_buffer, + *qstate->env->now, 1/*add SOA*/); } } if(msg) { diff --git a/validator/val_neg.c b/validator/val_neg.c index 03b48a3ea..808708398 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -1093,6 +1093,25 @@ void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, lock_basic_unlock(&neg->lock); } +/** + * Check that an NSEC3 rrset does not have a type set. + * None of the nsec3s in a hash-collision are allowed to have the type. + * (since we do not know which one is the nsec3 looked at, flags, ..., we + * ignore the cached item and let it bypass negative caching). + * @param k: the nsec3 rrset to check. + * @param t: type to check + * @return true if no RRs have the type. + */ +static int nsec3_no_type(struct ub_packed_rrset_key* k, uint16_t t) +{ + int count = (int)((struct packed_rrset_data*)k->entry.data)->count; + int i; + for(i=0; ientry.lock); return NULL; } @@ -1300,8 +1321,9 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, * ce_rrset equals a closer encloser. * nc_rrset is optout. * No need to check wildcard for type DS */ + /* capacity=3: ce + nc + soa(if needed) */ if(!(msg = dns_msg_create(qname, qname_len, - LDNS_RR_TYPE_DS, zone->dclass, region, 2))) + LDNS_RR_TYPE_DS, zone->dclass, region, 3))) return NULL; /* now=0 because TTL was reduced in grab_nsec */ if(!dns_msg_authadd(msg, region, ce_rrset, 0)) @@ -1313,10 +1335,48 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, return NULL; } +/** + * Add SOA record for external responses. + * @param rrset_cache: to look into. + * @param now: current time. + * @param region: where to perform the allocation + * @param msg: current msg with NSEC. + * @param zone: val_neg_zone if we have one. + * @return false on lookup or alloc failure. + */ +static int add_soa(struct rrset_cache* rrset_cache, uint32_t now, + struct regional* region, struct dns_msg* msg, struct val_neg_zone* zone) +{ + struct ub_packed_rrset_key* soa; + uint8_t* nm; + size_t nmlen; + uint16_t dclass; + if(zone) { + nm = zone->name; + nmlen = zone->len; + dclass = zone->dclass; + } else { + /* Assumes the signer is the zone SOA to add */ + nm = reply_nsec_signer(msg->rep, &nmlen, &dclass); + if(!nm) + return 0; + } + soa = rrset_cache_lookup(rrset_cache, nm, nmlen, LDNS_RR_TYPE_SOA, + dclass, 0, now, 0); + if(!soa) + return 0; + if(!dns_msg_authadd(msg, region, soa, now)) { + lock_rw_unlock(&soa->entry.lock); + return 0; + } + lock_rw_unlock(&soa->entry.lock); + return 1; +} + struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, struct regional* region, struct rrset_cache* rrset_cache, - ldns_buffer* buf, uint32_t now) + ldns_buffer* buf, uint32_t now, int addsoa) { struct dns_msg* msg; struct ub_packed_rrset_key* rrset; @@ -1340,11 +1400,13 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, if(rrset) { /* return msg with that rrset */ if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len, - qinfo->qtype, qinfo->qclass, region, 1))) + qinfo->qtype, qinfo->qclass, region, 2))) return NULL; /* TTL already subtracted in grab_nsec */ if(!dns_msg_authadd(msg, region, rrset, 0)) return NULL; + if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL)) + return NULL; return msg; } @@ -1368,6 +1430,10 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, msg = neg_nsec3_proof_ds(zone, qinfo->qname, qinfo->qname_len, zname_labs+1, buf, rrset_cache, region, now); + if(addsoa && !add_soa(rrset_cache, now, region, msg, zone)) { + lock_basic_unlock(&neg->lock); + return NULL; + } lock_basic_unlock(&neg->lock); return msg; } diff --git a/validator/val_neg.h b/validator/val_neg.h index 834fa5c45..8b17b32db 100644 --- a/validator/val_neg.h +++ b/validator/val_neg.h @@ -240,13 +240,16 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, * @param rrset_cache: rrset cache. * @param buf: temporary buffer. * @param now: to check TTLs against. + * @param addsoa: if true, produce result for external consumption. + * if false, do not add SOA - for unbound-internal consumption. * @return a reply message if something was found. * This reply may still need validation. * NULL if nothing found (or out of memory). */ struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, struct regional* region, - struct rrset_cache* rrset_cache, ldns_buffer* buf, uint32_t now); + struct rrset_cache* rrset_cache, ldns_buffer* buf, uint32_t now, + int addsoa); /**** functions exposed for unit test ****/ diff --git a/validator/val_utils.c b/validator/val_utils.c index 6534da156..2eb88cde7 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -44,7 +44,10 @@ #include "validator/val_kentry.h" #include "validator/val_sigcrypt.h" #include "validator/val_anchor.h" +#include "validator/val_nsec.h" +#include "validator/val_neg.h" #include "services/cache/rrset.h" +#include "services/cache/dns.h" #include "util/data/msgreply.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" @@ -881,3 +884,38 @@ int val_has_signed_nsecs(struct reply_info* rep, char** reason) else *reason = "no signatures over NSEC3s"; return 0; } + +struct dns_msg* +val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, + struct regional* region) +{ + struct dns_msg* msg; + struct query_info qinfo; + struct ub_packed_rrset_key *rrset = rrset_cache_lookup( + env->rrset_cache, nm, nmlen, LDNS_RR_TYPE_DS, c, 0, + *env->now, 0); + if(rrset) { + /* DS rrset exists. Return it to the validator immediately*/ + struct ub_packed_rrset_key* copy = packed_rrset_copy_region( + rrset, region, *env->now); + lock_rw_unlock(&rrset->entry.lock); + if(!copy) + return NULL; + msg = dns_msg_create(nm, nmlen, LDNS_RR_TYPE_DS, c, region, 1); + if(!msg) + return NULL; + msg->rep->rrsets[0] = copy; + msg->rep->rrset_count++; + msg->rep->an_numrrsets++; + return msg; + } + /* lookup in rrset and negative cache for NSEC/NSEC3 */ + qinfo.qname = nm; + qinfo.qname_len = nmlen; + qinfo.qtype = LDNS_RR_TYPE_DS; + qinfo.qclass = c; + /* do not add SOA to reply message, it is going to be used internal */ + msg = val_neg_getmsg(env->neg_cache, &qinfo, region, env->rrset_cache, + env->scratch_buffer, *env->now, 0); + return msg; +} diff --git a/validator/val_utils.h b/validator/val_utils.h index 0e7704d9d..7340c4bd1 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -321,4 +321,21 @@ int val_has_signed_nsecs(struct reply_info* rep, char** reason); */ int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset); +/** + * Find DS denial message in cache. Saves new qstate allocation and allows + * the validator to use partial content which is not enough to construct a + * message for network (or user) consumption. Without SOA for example, + * which is a common occurence in the unbound code since the referrals contain + * NSEC/NSEC3 rrs without the SOA element, thus do not allow synthesis of a + * full negative reply, but do allow synthesis of sufficient proof. + * @param env: query env with caches and time. + * @param nm: name of DS record sought. + * @param nmlen: length of name. + * @param c: class of DS RR. + * @param region: where to allocate result. + * @return a dns_msg on success. NULL on failure. + */ +struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, + uint16_t c, struct regional* region); + #endif /* VALIDATOR_VAL_UTILS_H */ diff --git a/validator/validator.c b/validator/validator.c index 123fa2959..0e0582a5d 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -58,6 +58,11 @@ #include "util/config_file.h" #include "util/fptr_wlist.h" +/* forward decl for cache response and normal super inform calls of a DS */ +static void process_ds_response(struct module_qstate* qstate, + struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, + struct query_info* qinfo, struct sock_list* origin); + /** fill up nsec3 key iterations config entry */ static int fill_nsec3_iter(struct val_env* ve, char* s, int c) @@ -1457,6 +1462,23 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id) if(!vq->ds_rrset || query_dname_compare(vq->ds_rrset->rk.dname, target_key_name) != 0) { + /* check if there is a cache entry : pick up an NSEC if + * there is no DS, check if that NSEC has DS-bit unset, and + * thus can disprove the secure delagation we seek. + * We can then use that NSEC even in the absence of a SOA + * record that would be required by the iterator to supply + * a completely protocol-correct response. + * Uses negative cache for NSEC3 lookup of DS responses. */ + /* only if cache not blacklisted, of course */ + struct dns_msg* msg; + if(!qstate->blacklist && !vq->chain_blacklist && + (msg=val_find_DS(qstate->env, target_key_name, + target_key_len, vq->qchase.qclass, qstate->region)) ) { + verbose(VERB_ALGO, "Process cached DS response"); + process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, + msg, &msg->qinfo, NULL); + return 1; /* continue processing ds-response results */ + } if(!generate_request(qstate, id, target_key_name, target_key_len, LDNS_RR_TYPE_DS, vq->qchase.qclass, BIT_CD)) {