From: Wouter Wijngaards Date: Tue, 28 Aug 2007 09:39:43 +0000 (+0000) Subject: Referral validation. X-Git-Tag: release-0.5~88 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e90c03e9599cda49e6203343cb51178d4a004b0;p=thirdparty%2Funbound.git Referral validation. git-svn-id: file:///svn/unbound/trunk@553 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 4e497d38d..b093782e7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -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. diff --git a/validator/val_utils.c b/validator/val_utils.c index 8314394a3..33b9c1328 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -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; ian_numrrsets; i++) { + for(i=skip; ian_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; ian_numrrsets; i++) { + for(i=skip; ian_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; ian_numrrsets+orig->ns_numrrsets; + for(i = (skip > orig->an_numrrsets)?skip:orig->an_numrrsets; + ian_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; irrset_count; - i++) { + for(i= (skip>orig->an_numrrsets+orig->ns_numrrsets)? + skip:orig->an_numrrsets+orig->ns_numrrsets; + irrset_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; irrset_count; i++) { + d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; + if(d->security == sec_status_unchecked) { + return i; + } + } + return rep->rrset_count; +} diff --git a/validator/val_utils.h b/validator/val_utils.h index b9d859237..d19bf5f19 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -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 */ diff --git a/validator/validator.c b/validator/validator.c index 8c6ddad48..6277b23f6 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -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; irrset_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; diff --git a/validator/validator.h b/validator/validator.h index cbe588ea7..2861621b4 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -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;