mirror of
https://github.com/NLnetLabs/unbound.git
synced 2025-12-20 23:00:56 -05:00
Referral validation.
git-svn-id: file:///svn/unbound/trunk@553 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
f293924be8
commit
0e90c03e95
5 changed files with 150 additions and 26 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue