]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Referral validation.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 28 Aug 2007 09:39:43 +0000 (09:39 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 28 Aug 2007 09:39:43 +0000 (09:39 +0000)
git-svn-id: file:///svn/unbound/trunk@553 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
validator/val_utils.c
validator/val_utils.h
validator/validator.c
validator/validator.h

index 4e497d38d9795c79108b3ef5f21d9bc2f6e543d9..b093782e7b2f012d281935f7024fee8263962047 100644 (file)
@@ -1,6 +1,10 @@
 28 August 2007: Wouter
        - removed double use for udp buffers, that could fail,
          instead performs a malloc to do the backup.
+       - validator validates referral messages, by validating all the rrsets
+         and stores the rrsets in the cache. Further referral (nonRD queries)
+         replies are made from the rrset cache directly. Unless unchecked
+         rrsets are encountered, there are then validated.
 
 27 August 2007: Wouter
        - do not garble the edns if a cache answer fails.
index 8314394a3bda1c3926f6b5976d678d7d1a64433b..33b9c13282d1ff8a91e493462d0d385dbd51c50d 100644 (file)
@@ -52,8 +52,8 @@
 #include "util/module.h"
 
 enum val_classification 
-val_classify_response(struct query_info* qinf, struct reply_info* rep
-       size_t skip)
+val_classify_response(uint16_t query_flags, struct query_info* qinf
+       struct reply_info* rep, size_t skip)
 {
        int rcode = (int)FLAGS_GET_RCODE(rep->flags);
        size_t i;
@@ -63,6 +63,10 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep,
        if(rcode == LDNS_RCODE_NXDOMAIN && rep->an_numrrsets == 0)
                return VAL_CLASS_NAMEERROR;
 
+       /* check for referral: nonRD query */
+       if(!(query_flags&BIT_RD))
+               return VAL_CLASS_REFERRAL;
+       
        log_assert(rcode == LDNS_RCODE_NOERROR);
        /* next check if the skip into the answer section shows no answer */
        if(skip>0 && rep->an_numrrsets <= skip)
@@ -185,7 +189,7 @@ val_find_best_signer(struct ub_packed_rrset_key* rrset,
 
 void 
 val_find_signer(enum val_classification subtype, struct query_info* qinf, 
-       struct reply_info* rep, size_t cname_skip, uint8_t** signer_name, 
+       struct reply_info* rep, size_t skip, uint8_t** signer_name, 
        size_t* signer_len)
 {
        size_t i;
@@ -193,7 +197,7 @@ val_find_signer(enum val_classification subtype, struct query_info* qinf,
        if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_CNAME 
                || subtype == VAL_CLASS_ANY) {
                /* check for the answer rrset */
-               for(i=cname_skip; i<rep->an_numrrsets; i++) {
+               for(i=skip; i<rep->an_numrrsets; i++) {
                        if(query_dname_compare(qinf->qname, 
                                rep->rrsets[i]->rk.dname) == 0) {
                                val_find_rrset_signer(rep->rrsets[i], 
@@ -231,6 +235,15 @@ val_find_signer(enum val_classification subtype, struct query_info* qinf,
                                        signer_name, signer_len, &matchcount);
                        }
                }
+       } else if(subtype == VAL_CLASS_REFERRAL) {
+               /* find keys for the item at skip */
+               if(skip < rep->rrset_count) {
+                       val_find_rrset_signer(rep->rrsets[skip], 
+                               signer_name, signer_len);
+                       return;
+               }
+               *signer_name = NULL;
+               *signer_len = 0;
        } else {
                verbose(VERB_ALGO, "find_signer: could not find signer name"
                        " for unknown type response");
@@ -521,7 +534,7 @@ rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len)
 
 void 
 val_fill_reply(struct reply_info* chase, struct reply_info* orig, 
-       size_t cname_skip, uint8_t* name, size_t len)
+       size_t skip, uint8_t* name, size_t len)
 {
        /* unsigned RRsets are never copied, but should not happen in 
         * secure answers anyway. Except for the synthesized CNAME after 
@@ -533,7 +546,7 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig,
        chase->ns_numrrsets = 0;
        chase->ar_numrrsets = 0;
        /* ANSWER section */
-       for(i=cname_skip; i<orig->an_numrrsets; i++) {
+       for(i=skip; i<orig->an_numrrsets; i++) {
                if(seen_dname && ntohs(orig->rrsets[i]->rk.type) == 
                        LDNS_RR_TYPE_CNAME) {
                        chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i];
@@ -547,7 +560,8 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig,
                }
        }       
        /* AUTHORITY section */
-       for(i=orig->an_numrrsets; i<orig->an_numrrsets+orig->ns_numrrsets; 
+       for(i = (skip > orig->an_numrrsets)?skip:orig->an_numrrsets;
+               i<orig->an_numrrsets+orig->ns_numrrsets; 
                i++) {
                if(rrset_has_signer(orig->rrsets[i], name, len)) {
                        chase->rrsets[chase->an_numrrsets+
@@ -555,8 +569,9 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig,
                }
        }
        /* ADDITIONAL section */
-       for(i=orig->an_numrrsets+orig->ns_numrrsets; i<orig->rrset_count; 
-               i++) {
+       for(i= (skip>orig->an_numrrsets+orig->ns_numrrsets)?
+               skip:orig->an_numrrsets+orig->ns_numrrsets; 
+               i<orig->rrset_count; i++) {
                if(rrset_has_signer(orig->rrsets[i], name, len)) {
                        chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+
                                chase->ar_numrrsets++] = orig->rrsets[i];
@@ -648,3 +663,17 @@ val_mark_insecure(struct reply_info* rep, struct key_entry_key* kkey,
                }
        }
 }
+
+size_t 
+val_next_unchecked(struct reply_info* rep, size_t skip)
+{
+       size_t i;
+       struct packed_rrset_data* d;
+       for(i=skip+1; i<rep->rrset_count; i++) {
+               d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data;
+               if(d->security == sec_status_unchecked) {
+                       return i;
+               }
+       }
+       return rep->rrset_count;
+}
index b9d859237601e1f8411eeee3599adad677315142..d19bf5f19a1f7d8aca9d745284b6baac329fde35 100644 (file)
@@ -71,12 +71,15 @@ enum val_classification {
        /** A CNAME/DNAME chain, and the offset is at the end of it,
         * but there is no answer here, it can be NAMERROR or NODATA. */
        VAL_CLASS_CNAMENOANSWER,
+       /** A referral, from cache with a nonRD query. */
+       VAL_CLASS_REFERRAL,
        /** A response to a qtype=ANY query. */
        VAL_CLASS_ANY
 };
 
 /**
  * Given a response, classify ANSWER responses into a subtype.
+ * @param query_flags: query flags for the original query.
  * @param qinf: query info. The chased query name.
  * @param rep: response. The original response.
  * @param skip: offset into the original response answer section.
@@ -84,8 +87,8 @@ enum val_classification {
  *     Once CNAME type is returned you can increase skip.
  *     Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible.
  */
-enum val_classification val_classify_response(struct query_info* qinf,
-       struct reply_info* rep, size_t skip);
+enum val_classification val_classify_response(uint16_t query_flags,
+       struct query_info* qinf, struct reply_info* rep, size_t skip);
 
 /**
  * Given a response, determine the name of the "signer". This is primarily
@@ -238,4 +241,13 @@ void val_mark_indeterminate(struct reply_info* rep,
 void val_mark_insecure(struct reply_info* rep, struct key_entry_key* kkey,
        struct rrset_cache* r);
 
+/**
+ * Find next unchecked rrset position, return it for skip.
+ * @param rep: the original reply to look into.
+ * @param skip: the skip now.
+ * @return new skip, which may be at the rep->rrset_count position to signal
+ *     there are no unchecked items.
+ */
+size_t val_next_unchecked(struct reply_info* rep, size_t skip);
+
 #endif /* VALIDATOR_VAL_UTILS_H */
index 8c6ddad488a442447120334eb8f89c2448921c20..6277b23f695e04ac8070bb4e6ebedb0f9c0788ec 100644 (file)
@@ -154,7 +154,7 @@ val_new(struct module_qstate* qstate, int id)
                        * vq->orig_msg->rep->rrset_count);
        if(!vq->chase_reply->rrsets)
                return NULL;
-       vq->cname_skip = 0;
+       vq->rrset_skip = 0;
        return vq;
 }
 
@@ -601,6 +601,33 @@ validate_nameerror_response(struct query_info* qchase,
        chase_reply->security = sec_status_secure;
 }
 
+/** 
+ * Given a referral response, validate rrsets and take least trusted rrset
+ * as the current validation status.
+ * 
+ * Note that by the time this method is called, the process of finding the
+ * trusted DNSKEY rrset that signs this response must already have been
+ * completed.
+ * 
+ * @param chase_reply: answer to validate.
+ */
+static void
+validate_referral_response(struct reply_info* chase_reply)
+{
+       size_t i;
+       enum sec_status s;
+       /* message security equals lowest rrset security */
+       chase_reply->security = sec_status_secure;
+       for(i=0; i<chase_reply->rrset_count; i++) {
+               s = ((struct packed_rrset_data*)chase_reply->rrsets[i]
+                       ->entry.data)->security;
+               if(s < chase_reply->security)
+                       chase_reply->security = s;
+       }
+       verbose(VERB_ALGO, "validated part of referral response as %s",
+               sec_status_to_string(chase_reply->security));
+}
+
 /** 
  * Given an "ANY" response -- a response that contains an answer to a
  * qtype==ANY question, with answers. This does no checking that all 
@@ -854,8 +881,31 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
 {
        uint8_t* lookup_name;
        size_t lookup_len;
-       enum val_classification subtype = val_classify_response(&vq->qchase, 
-               vq->orig_msg->rep, vq->cname_skip);
+       enum val_classification subtype = val_classify_response(
+               qstate->query_flags, &vq->qchase, vq->orig_msg->rep, 
+               vq->rrset_skip);
+       if(subtype == VAL_CLASS_REFERRAL && 
+               vq->rrset_skip < vq->orig_msg->rep->rrset_count) {
+               /* referral uses the rrset name as qchase, to find keys for
+                * that rrset */
+               vq->qchase.qname = vq->orig_msg->rep->
+                       rrsets[vq->rrset_skip]->rk.dname;
+               vq->qchase.qname_len = vq->orig_msg->rep->
+                       rrsets[vq->rrset_skip]->rk.dname_len;
+               vq->qchase.qtype = ntohs(vq->orig_msg->rep->
+                       rrsets[vq->rrset_skip]->rk.type);
+               vq->qchase.qclass = ntohs(vq->orig_msg->rep->
+                       rrsets[vq->rrset_skip]->rk.rrset_class);
+               /* for type DS look at the parent side for keys/trustanchor */
+               /* also for NSEC not at apex */
+               if(vq->qchase.qtype == LDNS_RR_TYPE_DS ||
+                       (vq->qchase.qtype == LDNS_RR_TYPE_NSEC && 
+                        !(vq->orig_msg->rep->rrsets[vq->rrset_skip]->
+                        rk.flags&PACKED_RRSET_NSEC_AT_APEX))) {
+                       dname_remove_label(&vq->qchase.qname, 
+                               &vq->qchase.qname_len);
+               }
+       }
 
        val_mark_indeterminate(vq->chase_reply, ve->anchors, 
                qstate->env->rrset_cache);
@@ -871,7 +921,7 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
 
        /* Determine the signer/lookup name */
        val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep, 
-               vq->cname_skip, &vq->signer_name, &vq->signer_len);
+               vq->rrset_skip, &vq->signer_name, &vq->signer_len);
        if(vq->signer_name == NULL) {
                lookup_name = vq->qchase.qname;
                lookup_len = vq->qchase.qname_len;
@@ -880,11 +930,12 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
                lookup_len = vq->signer_len;
        }
 
-       if(vq->cname_skip > 0 || subtype == VAL_CLASS_CNAME) {
+       if(vq->rrset_skip > 0 || subtype == VAL_CLASS_CNAME ||
+               subtype == VAL_CLASS_REFERRAL) {
                /* extract this part of orig_msg into chase_reply for
                 * the eventual VALIDATE stage */
                val_fill_reply(vq->chase_reply, vq->orig_msg->rep, 
-                       vq->cname_skip, lookup_name, lookup_len);
+                       vq->rrset_skip, lookup_name, lookup_len);
                log_dns_msg("chased extract", &vq->qchase, vq->chase_reply);
        }
 
@@ -1080,8 +1131,8 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
                return 1;
        }
 
-       subtype = val_classify_response(&vq->qchase, vq->orig_msg->rep
-               vq->cname_skip);
+       subtype = val_classify_response(qstate->query_flags, &vq->qchase
+               vq->orig_msg->rep, vq->rrset_skip);
        switch(subtype) {
                case VAL_CLASS_POSITIVE:
                        verbose(VERB_ALGO, "Validating a positive response");
@@ -1112,6 +1163,11 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
                                vq->chase_reply);
                        break;
 
+               case VAL_CLASS_REFERRAL:
+                       verbose(VERB_ALGO, "Validating a referral response");
+                       validate_referral_response(vq->chase_reply);
+                       break;
+
                case VAL_CLASS_ANY:
                        verbose(VERB_ALGO, "Validating a positive ANY "
                                "response");
@@ -1140,11 +1196,12 @@ static int
 processFinished(struct module_qstate* qstate, struct val_qstate* vq, 
        struct val_env* ve, int id)
 {
-       enum val_classification subtype = val_classify_response(&vq->qchase, 
-               vq->orig_msg->rep, vq->cname_skip);
+       enum val_classification subtype = val_classify_response(
+               qstate->query_flags, &vq->qchase, vq->orig_msg->rep, 
+               vq->rrset_skip);
 
        /* store overall validation result in orig_msg */
-       if(vq->cname_skip == 0)
+       if(vq->rrset_skip == 0)
                vq->orig_msg->rep->security = vq->chase_reply->security;
        else {
                /* use the lowest security status as end result. */
@@ -1153,15 +1210,28 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
                                vq->chase_reply->security;
        }
 
+       if(subtype == VAL_CLASS_REFERRAL) {
+               /* for a referral, move to next unchecked rrset and check it*/
+               vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, 
+                       vq->rrset_skip);
+               if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) {
+                       /* and restart for this rrset */
+                       verbose(VERB_ALGO, "validator: go to next rrset");
+                       vq->chase_reply->security = sec_status_unchecked;
+                       vq->state = VAL_INIT_STATE;
+                       return 1;
+               }
+               /* referral chase is done */
+       }
        if(vq->chase_reply->security != sec_status_bogus &&
                subtype == VAL_CLASS_CNAME) {
                /* chase the CNAME; process next part of the message */
                if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep, 
-                       &vq->cname_skip)) {
+                       &vq->rrset_skip)) {
                        verbose(VERB_ALGO, "validator: failed to chase CNAME");
                        vq->orig_msg->rep->security = sec_status_bogus;
                } else {
-                       /* restart process for new qchase at cname_skip */
+                       /* restart process for new qchase at rrset_skip */
                        log_query_info(VERB_DETAIL, "validator: chased to",
                                &vq->qchase);
                        vq->chase_reply->security = sec_status_unchecked;
@@ -1191,6 +1261,12 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
                        vq->orig_msg->rep, 0)) {
                        log_err("out of memory caching validator results");
                }
+       } else {
+               /* for a referral, store the verified RRsets */
+               if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, 
+                       vq->orig_msg->rep, 1)) {
+                       log_err("out of memory caching validator results");
+               }
        }
        qstate->return_rcode = LDNS_RCODE_NOERROR;
        qstate->return_msg = vq->orig_msg;
@@ -1412,7 +1488,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
                goto return_bogus;
        }
 
-       subtype = val_classify_response(qinfo, msg->rep, 0);
+       subtype = val_classify_response(BIT_RD, qinfo, msg->rep, 0);
        if(subtype == VAL_CLASS_POSITIVE) {
                struct ub_packed_rrset_key* ds;
                enum sec_status sec;
index cbe588ea79f276c8a2d677776a52e69d3a2cc889..2861621b4253e48f7a83876acddc50a01ac75ef2 100644 (file)
@@ -130,8 +130,11 @@ struct val_qstate {
         * starts at 0 - for the full original message.
         * if it is >0 - qchase followed the cname, chase_reply setup to be
         * that message and relevant authority rrsets.
+        *
+        * The skip is also used for referral messages, where it will
+        * range from 0, over the answer, authority and additional sections.
         */
-       size_t cname_skip;
+       size_t rrset_skip;
 
        /** the trust anchor rrset */
        struct trust_anchor* trust_anchor;