git-svn-id: file:///svn/unbound/trunk@1190 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2008-08-14 15:16:50 +00:00
parent 44f5800da9
commit 7549bddfda
6 changed files with 443 additions and 12 deletions

View file

@ -1,3 +1,6 @@
14 August 2008: Wouter
- synthesize DLV messages from the rrset cache, like done for DS.
13 August 2008: Wouter
- bug #203: nicer do-auto log message when user sets incompatible
options.

View file

@ -595,8 +595,9 @@ dns_cache_lookup(struct module_env* env,
lock_rw_unlock(&rrset->entry.lock);
}
/* construct DS, DNSKEY messages from rrset cache. */
if((qtype == LDNS_RR_TYPE_DS || qtype == LDNS_RR_TYPE_DNSKEY) &&
/* construct DS, DNSKEY, DLV messages from rrset cache. */
if((qtype == LDNS_RR_TYPE_DS || qtype == LDNS_RR_TYPE_DNSKEY ||
qtype == LDNS_RR_TYPE_DLV) &&
(rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen,
qtype, qclass, 0, now, 0))) {
/* if the rrset is from the additional section, and the

View file

@ -45,6 +45,7 @@
#include "validator/val_utils.h"
#include "util/data/msgreply.h"
#include "util/data/dname.h"
#include "util/net_help.h"
/** get ttl of rrset */
static uint32_t
@ -479,3 +480,79 @@ val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname,
}
return 0;
}
/**
* Closest NONEMPTY encloser.
* Thus, no empty nonterminals are returned.
* @param qname: query name
* @param nsec: nsec record.
* @return the name (part of qname).
*/
static uint8_t*
nsec_closest_nonempty(uint8_t* qname, struct ub_packed_rrset_key* nsec)
{
uint8_t* next;
size_t nlen;
uint8_t* common1, *common2;
if(!nsec_get_next(nsec, &next, &nlen))
return NULL;
/* shortest common with owner or next name */
common1 = dname_get_shared_topdomain(qname, nsec->rk.dname);
common2 = dname_get_shared_topdomain(qname, next);
if(dname_count_labels(common1) < dname_count_labels(common2))
return common1;
return common2;
}
int val_nsec_check_dlv(struct query_info* qinfo,
struct reply_info* rep, uint8_t** nm, size_t* nm_len)
{
uint8_t* next;
size_t i, nlen;
int c;
/* we should now have a NOERROR/NODATA or NXDOMAIN message */
if(rep->an_numrrsets != 0) {
return 0;
}
/* is this NOERROR ? */
if(FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) {
/* it can be a plain NSEC match - go up one more level. */
/* or its an empty nonterminal - go up to nonempty level */
for(i=0; i<rep->ns_numrrsets; i++) {
if(!nsec_get_next(rep->rrsets[i], &next, &nlen))
continue;
c = dname_canonical_compare(
rep->rrsets[i]->rk.dname, qinfo->qname);
if(c == 0) {
/* plain match */
dname_remove_label(nm, nm_len);
return 1;
} else if(c < 0 &&
dname_strict_subdomain_c(next, qinfo->qname)) {
/* ENT */
*nm = nsec_closest_nonempty(
*nm, rep->rrsets[i]);
if(!*nm) return 0;
return 1;
}
}
return 0;
}
/* is this NXDOMAIN ? */
if(FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN) {
/* find the qname denial NSEC record. It can tell us
* a closest encloser name; or that we not need bother */
for(i=0; i<rep->ns_numrrsets; i++) {
if(val_nsec_proves_name_error(rep->rrsets[i],
qinfo->qname)) {
*nm = nsec_closest_nonempty(
*nm, rep->rrsets[i]);
if(!*nm) return 0;
return 1;
}
}
return 0;
}
return 0;
}

View file

@ -145,4 +145,17 @@ uint8_t* nsec_closest_encloser(uint8_t* qname,
int val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname,
size_t qnamelen);
/**
* Determine the DLV result, what to do with NSEC DLV reply.
* @param qinfo: what was queried for.
* @param rep: the nonpositive reply.
* @param nm: dlv lookup name, to adjust for new lookup name (if needed).
* @param nm_len: length of lookup name.
* @return 0 on error, 1 if a higher point is found.
* If the higher point is above the dlv repo anchor, the qname does
* not exist.
*/
int val_nsec_check_dlv(struct query_info* qinfo,
struct reply_info* rep, uint8_t** nm, size_t* nm_len);
#endif /* VALIDATOR_VAL_NSEC_H */

View file

@ -287,11 +287,12 @@ needs_validation(struct module_qstate* qstate, int ret_rc,
* @param namelen: length of name.
* @param qtype: query type.
* @param qclass: query class.
* @param flags: additional flags, such as the CD bit (BIT_CD), or 0.
* @return false on alloc failure.
*/
static int
generate_request(struct module_qstate* qstate, int id, uint8_t* name,
size_t namelen, uint16_t qtype, uint16_t qclass)
size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags)
{
struct module_qstate* newq;
struct query_info ask;
@ -302,7 +303,7 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name,
log_query_info(VERB_ALGO, "generate request", &ask);
fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
if(!(*qstate->env->attach_sub)(qstate, &ask,
(uint16_t)(BIT_RD|BIT_CD), 0, &newq)){
(uint16_t)(BIT_RD|flags), 0, &newq)){
log_err("Could not generate request: out of memory");
return 0;
}
@ -328,7 +329,7 @@ prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq,
int id, struct trust_anchor* toprime)
{
int ret = generate_request(qstate, id, toprime->name, toprime->namelen,
LDNS_RR_TYPE_DNSKEY, toprime->dclass);
LDNS_RR_TYPE_DNSKEY, toprime->dclass, BIT_CD);
if(!ret) {
log_err("Could not prime trust anchor: out of memory");
return 0;
@ -1303,7 +1304,7 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id)
vq->key_entry->name) != 0) {
if(!generate_request(qstate, id, vq->ds_rrset->rk.dname,
vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY,
vq->qchase.qclass)) {
vq->qchase.qclass, BIT_CD)) {
log_err("mem error generating DNSKEY request");
return val_error(qstate, id);
}
@ -1313,7 +1314,8 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id)
if(!vq->ds_rrset || query_dname_compare(vq->ds_rrset->rk.dname,
target_key_name) != 0) {
if(!generate_request(qstate, id, target_key_name,
target_key_len, LDNS_RR_TYPE_DS, vq->qchase.qclass)) {
target_key_len, LDNS_RR_TYPE_DS, vq->qchase.qclass,
BIT_CD)) {
log_err("mem error generating DS request");
return val_error(qstate, id);
}
@ -1323,7 +1325,7 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id)
/* Otherwise, it is time to query for the DNSKEY */
if(!generate_request(qstate, id, vq->ds_rrset->rk.dname,
vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY,
vq->qchase.qclass)) {
vq->qchase.qclass, BIT_CD)) {
log_err("mem error generating DNSKEY request");
return val_error(qstate, id);
}
@ -1472,6 +1474,130 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
return 1;
}
/**
* Init DLV check.
* Called when a query is determined by other trust anchors to be insecure
* (or indeterminate). Then we look if there is a key in the DLV.
* Performs aggressive negative cache check to see if there is no key.
* Otherwise, spawns a DLV query, and changes to the DLV wait state.
*
* @param qstate: query state.
* @param vq: validator query state.
* @param ve: validator shared global environment.
* @param id: module id.
* @return true if there is no DLV.
* false: processing is finished for the validator operate().
* This function may exit in three ways:
* o no DLV (agressive cache), so insecure. (true)
* o error - stop processing (false)
* o DLV lookup was started, stop processing (false)
*/
static int
val_dlv_init(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id)
{
uint8_t* nm;
size_t nm_len;
/* there must be a DLV configured */
log_assert(qstate->env->anchors->dlv_anchor);
/* this bool is true to avoid looping in the DLV checks */
log_assert(vq->dlv_checked);
/* init the DLV lookup variables */
vq->dlv_lookup_name = NULL;
vq->dlv_lookup_name_len = 0;
vq->dlv_insecure_at = NULL;
vq->dlv_insecure_at_len = 0;
/* Determine the name for which we want to lookup DLV.
* This name is for the current message, or
* for the current RRset for CNAME, referral subtypes.
* If there is a signer, use that, otherwise the domain name */
if(vq->signer_name) {
nm = vq->signer_name;
nm_len = vq->signer_len;
} else {
/* use qchase */
nm = vq->qchase.qname;
nm_len = vq->qchase.qname_len;
}
log_nametypeclass(VERB_ALGO, "DLV init look", nm, LDNS_RR_TYPE_DS,
vq->qchase.qclass);
log_assert(nm && nm_len)
/* sanity check: no DLV lookups below the DLV anchor itself.
* Like, an securely insecure delegation there makes no sense. */
if(dname_subdomain_c(nm, qstate->env->anchors->dlv_anchor->name)) {
verbose(VERB_ALGO, "DLV lookup within DLV repository denied");
return 1;
}
/* concat name (minus root label) + dlv name */
vq->dlv_lookup_name_len = nm_len - 1 +
qstate->env->anchors->dlv_anchor->namelen;
vq->dlv_lookup_name = regional_alloc(qstate->region,
vq->dlv_lookup_name_len);
if(!vq->dlv_lookup_name) {
log_err("Out of memory preparing DLV lookup");
return val_error(qstate, id);
}
memmove(vq->dlv_lookup_name, nm, nm_len-1);
memmove(vq->dlv_lookup_name+nm_len-1,
qstate->env->anchors->dlv_anchor->name,
qstate->env->anchors->dlv_anchor->namelen);
log_nametypeclass(VERB_ALGO, "DLV name", vq->dlv_lookup_name,
LDNS_RR_TYPE_DLV, vq->qchase.qclass);
/* determine where the insecure point was determined, the DLV must
* be equal or below that to continue building the trust chain
* down. May be NULL if no trust chain was built yet */
nm = NULL;
if(vq->key_entry && key_entry_isnull(vq->key_entry)) {
nm = vq->key_entry->name;
nm_len = vq->key_entry->namelen;
}
if(nm) {
vq->dlv_insecure_at_len = nm_len - 1 +
qstate->env->anchors->dlv_anchor->namelen;
vq->dlv_insecure_at = regional_alloc(qstate->region,
vq->dlv_insecure_at_len);
if(!vq->dlv_insecure_at) {
log_err("Out of memory preparing DLV lookup");
return val_error(qstate, id);
}
memmove(vq->dlv_insecure_at, nm, nm_len-1);
memmove(vq->dlv_insecure_at+nm_len-1,
qstate->env->anchors->dlv_anchor->name,
qstate->env->anchors->dlv_anchor->namelen);
log_nametypeclass(VERB_ALGO, "insecure_at",
vq->dlv_insecure_at, 0, vq->qchase.qclass);
}
/* If we can find the name in the aggressive negative cache,
* give up; insecure is the answer */
/* lookup, several places in the tree may qualify
* between insecure_at and the lookup_name
* Check proof thoroughly
* TODO
* return 1;
*/
/* perform a lookup for the DLV; with validation */
vq->state = VAL_DLVLOOKUP_STATE;
if(!generate_request(qstate, id, vq->dlv_lookup_name,
vq->dlv_lookup_name_len, LDNS_RR_TYPE_DLV,
vq->qchase.qclass, 0)) {
return val_error(qstate, id);
}
/* Find the closest encloser DLV from the repository.
* then that is used to build another chain of trust
* This may first require a query 'too low' that has NSECs in
* the answer, from which we determine the closest encloser DLV.
* When determine the closest encloser, skip empty nonterminals,
* since we want a nonempty node in the DLV repository. */
return 0;
}
/**
* The Finished state. The validation status (good or bad) has been determined.
*
@ -1490,6 +1616,18 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
qstate->query_flags, &qstate->qinfo, &vq->qchase,
vq->orig_msg->rep, vq->rrset_skip);
/* if the result is insecure or indeterminate and we have not
* checked the DLV yet, check the DLV */
verbose(VERB_ALGO, "check for DLV");
verbose(VERB_ALGO, "sec %s", sec_status_to_string(vq->chase_reply->security));
if((vq->chase_reply->security == sec_status_insecure ||
vq->chase_reply->security == sec_status_indeterminate) &&
qstate->env->anchors->dlv_anchor && !vq->dlv_checked) {
vq->dlv_checked = 1;
if(!val_dlv_init(qstate, vq, ve, id))
return 0;
}
/* store overall validation result in orig_msg */
if(vq->rrset_skip == 0)
vq->orig_msg->rep->security = vq->chase_reply->security;
@ -1511,6 +1649,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
/* and restart for this rrset */
verbose(VERB_ALGO, "validator: go to next rrset");
vq->chase_reply->security = sec_status_unchecked;
vq->dlv_checked = 0; /* can do DLV for this RR */
vq->state = VAL_INIT_STATE;
return 1;
}
@ -1528,6 +1667,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
log_query_info(VERB_ALGO, "validator: chased to",
&vq->qchase);
vq->chase_reply->security = sec_status_unchecked;
vq->dlv_checked = 0; /* can do DLV for this RR */
vq->state = VAL_INIT_STATE;
return 1;
}
@ -1540,9 +1680,11 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
* that are not secure (if clean-additional option is set) */
/* this may cause the msg to be marked bogus */
val_check_nonsecure(ve, vq->orig_msg->rep);
if(vq->orig_msg->rep->security == sec_status_secure) {
log_query_info(VERB_DETAIL, "validation success",
&qstate->qinfo);
}
}
/* if the result is bogus - set message ttl to bogus ttl to avoid
* endless bogus revalidation */
@ -1572,6 +1714,80 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
return 0;
}
/**
* The DLVLookup state. Process DLV lookups.
*
* @param qstate: query state.
* @param vq: validator query state.
* @param ve: validator shared global environment.
* @param id: module id.
* @return true if the event should be processed further on return, false if
* not.
*/
static int
processDLVLookup(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id)
{
/* see if this we are ready to continue normal resolution */
/* we may need more DLV lookups */
if(vq->dlv_status==dlv_error)
verbose(VERB_ALGO, "DLV woke up with status dlv_error");
else if(vq->dlv_status==dlv_success)
verbose(VERB_ALGO, "DLV woke up with status dlv_success");
else if(vq->dlv_status==dlv_ask_higher)
verbose(VERB_ALGO, "DLV woke up with status dlv_ask_higher");
else if(vq->dlv_status==dlv_there_is_no_dlv)
verbose(VERB_ALGO, "DLV woke up with status dlv_there_is_no_dlv");
else verbose(VERB_ALGO, "DLV woke up with status unknown");
log_nametypeclass(VERB_ALGO, "next look", vq->dlv_lookup_name,
LDNS_RR_TYPE_DLV, vq->qchase.qclass);
if(vq->dlv_status == dlv_error) {
verbose(VERB_QUERY, "failed DLV lookup");
return val_error(qstate, id);
} else if(vq->dlv_status == dlv_success) {
/* chain continues with DNSKEY, continue in FINDKEY */
vq->state = VAL_FINDKEY_STATE;
if(!generate_request(qstate, id, vq->ds_rrset->rk.dname,
vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY,
vq->qchase.qclass, BIT_CD)) {
log_err("mem error generating DNSKEY request");
return val_error(qstate, id);
}
return 0;
} else if(vq->dlv_status == dlv_there_is_no_dlv) {
/* continue with the insecure result we got */
vq->state = VAL_FINISHED_STATE;
return 1;
}
log_assert(vq->dlv_status == dlv_ask_higher);
/* ask higher, make sure we stay in DLV repo, below dlv_at */
if(!dname_subdomain_c(vq->dlv_lookup_name,
qstate->env->anchors->dlv_anchor->name)) {
/* just like, there is no DLV */
verbose(VERB_ALGO, "ask above dlv repo");
vq->state = VAL_FINISHED_STATE;
return 1;
}
if(vq->dlv_insecure_at && !dname_subdomain_c(vq->dlv_lookup_name,
vq->dlv_insecure_at)) {
/* already checked a chain lower than dlv_lookup_name */
verbose(VERB_ALGO, "ask above insecure endpoint");
log_nametypeclass(0, "enpt", vq->dlv_insecure_at, 0, 0);
vq->state = VAL_FINISHED_STATE;
return 1;
}
if(!generate_request(qstate, id, vq->dlv_lookup_name,
vq->dlv_lookup_name_len, LDNS_RR_TYPE_DLV,
vq->qchase.qclass, 0)) {
return val_error(qstate, id);
}
return 0;
}
/**
* Handle validator state.
* If a method returns true, the next state is started. If false, then
@ -1602,6 +1818,9 @@ val_handle(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_FINISHED_STATE:
cont = processFinished(qstate, vq, ve, id);
break;
case VAL_DLVLOOKUP_STATE:
cont = processDLVLookup(qstate, vq, ve, id);
break;
default:
log_warn("validator: invalid state %d",
vq->state);
@ -2075,6 +2294,96 @@ process_prime_response(struct module_qstate* qstate, struct val_qstate* vq,
/* the qstate will be reactivated after inform_super is done */
}
/**
* Process DLV response. Called from inform_supers.
* Because it is in inform_supers, the mesh itself is busy doing callbacks
* for a state that is to be deleted soon; don't touch the mesh; instead
* set a state in the super, as the super will be reactivated soon.
* Perform processing to determine what state to set in the super.
*
* @param qstate: query state that is validating and asked for a DLV.
* @param vq: validator query state
* @param id: module id.
* @param rcode: rcode result value.
* @param msg: result message (if rcode is OK).
* @param qinfo: from the sub query state, query info.
*/
static void
process_dlv_response(struct module_qstate* qstate, struct val_qstate* vq,
int id, int rcode, struct dns_msg* msg, struct query_info* qinfo)
{
struct val_env* ve = (struct val_env*)qstate->env->modinfo[id];
verbose(VERB_ALGO, "process dlv response to super");
if(rcode != LDNS_RCODE_NOERROR) {
/* lookup failed, set in vq to give up */
vq->dlv_status = dlv_error;
verbose(VERB_ALGO, "response is error");
return;
}
if(msg->rep->security != sec_status_secure) {
vq->dlv_status = dlv_error;
verbose(VERB_ALGO, "response is not secure");
return;
}
/* was the lookup a success? validated DLV? */
if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NOERROR &&
msg->rep->an_numrrsets == 1 &&
msg->rep->security == sec_status_secure &&
ntohs(msg->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DLV &&
ntohs(msg->rep->rrsets[0]->rk.rrset_class) == qinfo->qclass &&
query_dname_compare(msg->rep->rrsets[0]->rk.dname,
vq->dlv_lookup_name) == 0) {
/* yay! it is just like a DS */
vq->ds_rrset = (struct ub_packed_rrset_key*)
regional_alloc_init(qstate->region,
msg->rep->rrsets[0], sizeof(*vq->ds_rrset));
if(!vq->ds_rrset) {
log_err("out of memory in process_dlv");
return;
}
vq->ds_rrset->entry.key = vq->ds_rrset;
vq->ds_rrset->rk.dname = (uint8_t*)regional_alloc_init(
qstate->region, vq->ds_rrset->rk.dname,
vq->ds_rrset->rk.dname_len);
if(!vq->ds_rrset->rk.dname) {
log_err("out of memory in process_dlv");
vq->dlv_status = dlv_error;
return;
}
vq->ds_rrset->entry.data = regional_alloc_init(qstate->region,
vq->ds_rrset->entry.data,
packed_rrset_sizeof(vq->ds_rrset->entry.data));
if(!vq->ds_rrset->entry.data) {
log_err("out of memory in process_dlv");
vq->dlv_status = dlv_error;
return;
}
packed_rrset_ptr_fixup(vq->ds_rrset->entry.data);
/* make vq do a DNSKEY query next up */
vq->dlv_status = dlv_success;
return;
}
/* was the lookup a failure?
* if we have to go up into the DLV for a higher DLV anchor
* then set this in the vq, so it can make queries when activated.
* See if the NSECs indicate that we should look for higher DLV
* or, that there is no DLV securely */
if(!val_nsec_check_dlv(qinfo, msg->rep, &vq->dlv_lookup_name,
&vq->dlv_lookup_name_len)) {
vq->dlv_status = dlv_error;
verbose(VERB_ALGO, "nsec error");
return;
}
if(!dname_subdomain_c(vq->dlv_lookup_name,
qstate->env->anchors->dlv_anchor->name)) {
vq->dlv_status = dlv_there_is_no_dlv;
return;
}
vq->dlv_status = dlv_ask_higher;
}
/*
* inform validator super.
*
@ -2108,6 +2417,10 @@ val_inform_super(struct module_qstate* qstate, int id,
process_dnskey_response(super, vq, id, qstate->return_rcode,
qstate->return_msg, &qstate->qinfo);
return;
} else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DLV) {
process_dlv_response(super, vq, id, qstate->return_rcode,
qstate->return_msg, &qstate->qinfo);
return;
}
log_err("internal error in validator: no inform_supers possible");
}
@ -2155,6 +2468,7 @@ val_state_to_string(enum val_state state)
case VAL_FINDKEY_STATE: return "VAL_FINDKEY_STATE";
case VAL_VALIDATE_STATE: return "VAL_VALIDATE_STATE";
case VAL_FINISHED_STATE: return "VAL_FINISHED_STATE";
case VAL_DLVLOOKUP_STATE: return "VAL_DLVLOOKUP_STATE";
}
return "UNKNOWN VALIDATOR STATE";
}

View file

@ -116,7 +116,9 @@ enum val_state {
/** validate the answer, using found key entry */
VAL_VALIDATE_STATE,
/** finish up */
VAL_FINISHED_STATE
VAL_FINISHED_STATE,
/** DLV lookup state, processing DLV queries */
VAL_DLVLOOKUP_STATE
};
/**
@ -184,6 +186,27 @@ struct val_qstate {
/** true if this state is waiting to prime a trust anchor */
int wait_prime_ta;
/** have we already checked the DLV? */
int dlv_checked;
/** The name for which the DLV is looked up. For the current message
* or for the current RRset (for CNAME, REFERRAL types).
* If there is signer name, that may be it, else a domain name */
uint8_t* dlv_lookup_name;
/** length of dlv lookup name */
size_t dlv_lookup_name_len;
/** Name at which chain of trust stopped with insecure, starting DLV
* DLV must result in chain going further down */
uint8_t* dlv_insecure_at;
/** length of dlv insecure point name */
size_t dlv_insecure_at_len;
/** status of DLV lookup. Indication to VAL_DLV_STATE what to do */
enum dlv_status {
dlv_error, /* server failure */
dlv_success, /* got a DLV */
dlv_ask_higher, /* ask again */
dlv_there_is_no_dlv /* got no DLV, sure of it */
} dlv_status;
};
/**