Referral validation.

git-svn-id: file:///svn/unbound/trunk@553 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2007-08-28 09:39:43 +00:00
parent f293924be8
commit 0e90c03e95
5 changed files with 150 additions and 26 deletions

View file

@ -1,6 +1,10 @@
28 August 2007: Wouter 28 August 2007: Wouter
- removed double use for udp buffers, that could fail, - removed double use for udp buffers, that could fail,
instead performs a malloc to do the backup. 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 27 August 2007: Wouter
- do not garble the edns if a cache answer fails. - do not garble the edns if a cache answer fails.

View file

@ -52,8 +52,8 @@
#include "util/module.h" #include "util/module.h"
enum val_classification enum val_classification
val_classify_response(struct query_info* qinf, struct reply_info* rep, val_classify_response(uint16_t query_flags, struct query_info* qinf,
size_t skip) struct reply_info* rep, size_t skip)
{ {
int rcode = (int)FLAGS_GET_RCODE(rep->flags); int rcode = (int)FLAGS_GET_RCODE(rep->flags);
size_t i; 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) if(rcode == LDNS_RCODE_NXDOMAIN && rep->an_numrrsets == 0)
return VAL_CLASS_NAMEERROR; return VAL_CLASS_NAMEERROR;
/* check for referral: nonRD query */
if(!(query_flags&BIT_RD))
return VAL_CLASS_REFERRAL;
log_assert(rcode == LDNS_RCODE_NOERROR); log_assert(rcode == LDNS_RCODE_NOERROR);
/* next check if the skip into the answer section shows no answer */ /* next check if the skip into the answer section shows no answer */
if(skip>0 && rep->an_numrrsets <= skip) if(skip>0 && rep->an_numrrsets <= skip)
@ -185,7 +189,7 @@ val_find_best_signer(struct ub_packed_rrset_key* rrset,
void void
val_find_signer(enum val_classification subtype, struct query_info* qinf, 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* signer_len)
{ {
size_t i; 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 if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_CNAME
|| subtype == VAL_CLASS_ANY) { || subtype == VAL_CLASS_ANY) {
/* check for the answer rrset */ /* 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, if(query_dname_compare(qinf->qname,
rep->rrsets[i]->rk.dname) == 0) { rep->rrsets[i]->rk.dname) == 0) {
val_find_rrset_signer(rep->rrsets[i], 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); 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 { } else {
verbose(VERB_ALGO, "find_signer: could not find signer name" verbose(VERB_ALGO, "find_signer: could not find signer name"
" for unknown type response"); " for unknown type response");
@ -521,7 +534,7 @@ rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len)
void void
val_fill_reply(struct reply_info* chase, struct reply_info* orig, 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 /* unsigned RRsets are never copied, but should not happen in
* secure answers anyway. Except for the synthesized CNAME after * 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->ns_numrrsets = 0;
chase->ar_numrrsets = 0; chase->ar_numrrsets = 0;
/* ANSWER section */ /* 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) == if(seen_dname && ntohs(orig->rrsets[i]->rk.type) ==
LDNS_RR_TYPE_CNAME) { LDNS_RR_TYPE_CNAME) {
chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; 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 */ /* 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++) { i++) {
if(rrset_has_signer(orig->rrsets[i], name, len)) { if(rrset_has_signer(orig->rrsets[i], name, len)) {
chase->rrsets[chase->an_numrrsets+ chase->rrsets[chase->an_numrrsets+
@ -555,8 +569,9 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig,
} }
} }
/* ADDITIONAL section */ /* ADDITIONAL section */
for(i=orig->an_numrrsets+orig->ns_numrrsets; i<orig->rrset_count; for(i= (skip>orig->an_numrrsets+orig->ns_numrrsets)?
i++) { skip:orig->an_numrrsets+orig->ns_numrrsets;
i<orig->rrset_count; i++) {
if(rrset_has_signer(orig->rrsets[i], name, len)) { if(rrset_has_signer(orig->rrsets[i], name, len)) {
chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+ chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+
chase->ar_numrrsets++] = orig->rrsets[i]; 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;
}

View file

@ -71,12 +71,15 @@ enum val_classification {
/** A CNAME/DNAME chain, and the offset is at the end of it, /** 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. */ * but there is no answer here, it can be NAMERROR or NODATA. */
VAL_CLASS_CNAMENOANSWER, VAL_CLASS_CNAMENOANSWER,
/** A referral, from cache with a nonRD query. */
VAL_CLASS_REFERRAL,
/** A response to a qtype=ANY query. */ /** A response to a qtype=ANY query. */
VAL_CLASS_ANY VAL_CLASS_ANY
}; };
/** /**
* Given a response, classify ANSWER responses into a subtype. * 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 qinf: query info. The chased query name.
* @param rep: response. The original response. * @param rep: response. The original response.
* @param skip: offset into the original response answer section. * @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. * Once CNAME type is returned you can increase skip.
* Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible. * Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible.
*/ */
enum val_classification val_classify_response(struct query_info* qinf, enum val_classification val_classify_response(uint16_t query_flags,
struct reply_info* rep, size_t skip); struct query_info* qinf, struct reply_info* rep, size_t skip);
/** /**
* Given a response, determine the name of the "signer". This is primarily * 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, void val_mark_insecure(struct reply_info* rep, struct key_entry_key* kkey,
struct rrset_cache* r); 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 */ #endif /* VALIDATOR_VAL_UTILS_H */

View file

@ -154,7 +154,7 @@ val_new(struct module_qstate* qstate, int id)
* vq->orig_msg->rep->rrset_count); * vq->orig_msg->rep->rrset_count);
if(!vq->chase_reply->rrsets) if(!vq->chase_reply->rrsets)
return NULL; return NULL;
vq->cname_skip = 0; vq->rrset_skip = 0;
return vq; return vq;
} }
@ -601,6 +601,33 @@ validate_nameerror_response(struct query_info* qchase,
chase_reply->security = sec_status_secure; 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 * Given an "ANY" response -- a response that contains an answer to a
* qtype==ANY question, with answers. This does no checking that all * 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; uint8_t* lookup_name;
size_t lookup_len; size_t lookup_len;
enum val_classification subtype = val_classify_response(&vq->qchase, enum val_classification subtype = val_classify_response(
vq->orig_msg->rep, vq->cname_skip); 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, val_mark_indeterminate(vq->chase_reply, ve->anchors,
qstate->env->rrset_cache); qstate->env->rrset_cache);
@ -871,7 +921,7 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
/* Determine the signer/lookup name */ /* Determine the signer/lookup name */
val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep, 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) { if(vq->signer_name == NULL) {
lookup_name = vq->qchase.qname; lookup_name = vq->qchase.qname;
lookup_len = vq->qchase.qname_len; lookup_len = vq->qchase.qname_len;
@ -880,11 +930,12 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
lookup_len = vq->signer_len; 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 /* extract this part of orig_msg into chase_reply for
* the eventual VALIDATE stage */ * the eventual VALIDATE stage */
val_fill_reply(vq->chase_reply, vq->orig_msg->rep, 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); 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; return 1;
} }
subtype = val_classify_response(&vq->qchase, vq->orig_msg->rep, subtype = val_classify_response(qstate->query_flags, &vq->qchase,
vq->cname_skip); vq->orig_msg->rep, vq->rrset_skip);
switch(subtype) { switch(subtype) {
case VAL_CLASS_POSITIVE: case VAL_CLASS_POSITIVE:
verbose(VERB_ALGO, "Validating a positive response"); verbose(VERB_ALGO, "Validating a positive response");
@ -1112,6 +1163,11 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
vq->chase_reply); vq->chase_reply);
break; break;
case VAL_CLASS_REFERRAL:
verbose(VERB_ALGO, "Validating a referral response");
validate_referral_response(vq->chase_reply);
break;
case VAL_CLASS_ANY: case VAL_CLASS_ANY:
verbose(VERB_ALGO, "Validating a positive ANY " verbose(VERB_ALGO, "Validating a positive ANY "
"response"); "response");
@ -1140,11 +1196,12 @@ static int
processFinished(struct module_qstate* qstate, struct val_qstate* vq, processFinished(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id) struct val_env* ve, int id)
{ {
enum val_classification subtype = val_classify_response(&vq->qchase, enum val_classification subtype = val_classify_response(
vq->orig_msg->rep, vq->cname_skip); qstate->query_flags, &vq->qchase, vq->orig_msg->rep,
vq->rrset_skip);
/* store overall validation result in orig_msg */ /* 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; vq->orig_msg->rep->security = vq->chase_reply->security;
else { else {
/* use the lowest security status as end result. */ /* 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; 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 && if(vq->chase_reply->security != sec_status_bogus &&
subtype == VAL_CLASS_CNAME) { subtype == VAL_CLASS_CNAME) {
/* chase the CNAME; process next part of the message */ /* chase the CNAME; process next part of the message */
if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep, if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep,
&vq->cname_skip)) { &vq->rrset_skip)) {
verbose(VERB_ALGO, "validator: failed to chase CNAME"); verbose(VERB_ALGO, "validator: failed to chase CNAME");
vq->orig_msg->rep->security = sec_status_bogus; vq->orig_msg->rep->security = sec_status_bogus;
} else { } 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", log_query_info(VERB_DETAIL, "validator: chased to",
&vq->qchase); &vq->qchase);
vq->chase_reply->security = sec_status_unchecked; 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)) { vq->orig_msg->rep, 0)) {
log_err("out of memory caching validator results"); 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_rcode = LDNS_RCODE_NOERROR;
qstate->return_msg = vq->orig_msg; 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; 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) { if(subtype == VAL_CLASS_POSITIVE) {
struct ub_packed_rrset_key* ds; struct ub_packed_rrset_key* ds;
enum sec_status sec; enum sec_status sec;

View file

@ -130,8 +130,11 @@ struct val_qstate {
* starts at 0 - for the full original message. * starts at 0 - for the full original message.
* if it is >0 - qchase followed the cname, chase_reply setup to be * if it is >0 - qchase followed the cname, chase_reply setup to be
* that message and relevant authority rrsets. * 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 */ /** the trust anchor rrset */
struct trust_anchor* trust_anchor; struct trust_anchor* trust_anchor;