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
- 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.

View 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;
}

View 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 */

View 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;

View 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;