work on nsec3 negative cache for qtype DS .

git-svn-id: file:///svn/unbound/trunk@1290 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2008-10-08 14:42:46 +00:00
parent 45afaf3e08
commit c73c662fce
5 changed files with 445 additions and 47 deletions

View File

@ -760,7 +760,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
* NOERROR/NODATA or NXDOMAIN answers that need validation */
msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase,
qstate->region, qstate->env->rrset_cache,
*qstate->env->now);
qstate->env->scratch_buffer, *qstate->env->now);
}
if(msg) {
/* handle positive cache response */

View File

@ -44,6 +44,7 @@
#include "config.h"
#include "validator/val_neg.h"
#include "validator/val_nsec.h"
#include "validator/val_nsec3.h"
#include "validator/val_utils.h"
#include "util/data/dname.h"
#include "util/data/msgreply.h"
@ -205,6 +206,7 @@ static void neg_delete_zone(struct val_neg_cache* neg, struct val_neg_zone* z)
np = p->parent;
(void)rbtree_delete(&neg->tree, &p->node);
neg->use -= p->len + sizeof(*p);
free(p->nsec3_salt);
free(p->name);
free(p);
p = np;
@ -295,6 +297,29 @@ static struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg,
return result;
}
/**
* Find the given data
* @param zone: negative zone
* @param nm: what to look for.
* @param len: length of nm
* @param labs: labels in nm
* @return data or NULL if not found.
*/
static struct val_neg_data* neg_find_data(struct val_neg_zone* zone,
uint8_t* nm, size_t len, int labs)
{
struct val_neg_data lookfor;
struct val_neg_data* result;
lookfor.node.key = &lookfor;
lookfor.name = nm;
lookfor.len = len;
lookfor.labs = labs;
result = (struct val_neg_data*)
rbtree_search(&zone->tree, lookfor.node.key);
return result;
}
/**
* Calculate space needed for the data and all its parents
* @param rep: NSEC entries.
@ -660,12 +685,21 @@ static void wipeout(struct val_neg_cache* neg, struct val_neg_zone* zone,
int end_labs, m;
rbnode_t* walk, *next;
struct val_neg_data* cur;
uint8_t buf[257];
/* get endpoint */
if(!d || d->count == 0 || d->rr_len[0] < 2+1)
return;
end = d->rr_data[0]+2;
end_len = dname_valid(end, d->rr_len[0]-2);
end_labs = dname_count_labels(end);
if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC) {
end = d->rr_data[0]+2;
end_len = dname_valid(end, d->rr_len[0]-2);
end_labs = dname_count_labels(end);
} else {
/* NSEC3 */
if(!nsec3_get_nextowner_b32(nsec, 0, buf, sizeof(buf)))
return;
end = buf;
end_labs = dname_count_size_labels(end, &end_len);
}
/* sanity check, both owner and end must be below the zone apex */
if(!dname_subdomain_c(el->name, zone->name) ||
@ -734,10 +768,12 @@ static void neg_insert_data(struct val_neg_cache* neg,
int labs = dname_count_labels(nsec->rk.dname);
d = (struct packed_rrset_data*)nsec->entry.data;
if(d->security != sec_status_secure)
if( !(d->security == sec_status_secure ||
(d->security == sec_status_unchecked && d->rrsig_count > 0)))
return;
log_nametypeclass(VERB_ALGO, "negcache rr",
nsec->rk.dname, LDNS_RR_TYPE_NSEC, ntohs(nsec->rk.rrset_class));
nsec->rk.dname, ntohs(nsec->rk.type),
ntohs(nsec->rk.rrset_class));
/* find closest enclosing parent data that (still) exists */
parent = neg_closest_data_parent(zone, nm, nm_len, labs);
@ -790,6 +826,26 @@ static void neg_insert_data(struct val_neg_cache* neg,
neg_lru_touch(neg, el);
}
/* if nsec3 store last used parameters */
if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC3) {
int h;
uint8_t* s;
size_t slen, it;
if(nsec3_get_params(nsec, 0, &h, &it, &s, &slen) &&
(h != zone->nsec3_hash || it != zone->nsec3_iter ||
slen != zone->nsec3_saltlen ||
memcmp(zone->nsec3_salt, s, slen) != 0)) {
uint8_t* sa = memdup(s, slen);
if(sa) {
free(zone->nsec3_salt);
zone->nsec3_salt = sa;
zone->nsec3_saltlen = slen;
zone->nsec3_hash = h;
zone->nsec3_iter = it;
}
}
}
/* wipe out the cache items between NSEC start and end */
wipeout(neg, zone, el, nsec);
}
@ -898,6 +954,12 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len,
log_nametypeclass(VERB_ALGO, "negcache zone", zone->name, 0,
zone->dclass);
/* DLV is defined to use NSEC only */
if(zone->nsec3_hash) {
lock_basic_unlock(&neg->lock);
return 0;
}
/* lookup closest data record */
(void)neg_closest_data(zone, qname, len, labs, &data);
while(data && !data->in_use)
@ -972,7 +1034,8 @@ static uint8_t* reply_nsec_signer(struct reply_info* rep, size_t* signer_len,
struct packed_rrset_data* d;
uint8_t* s;
for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) {
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC ||
ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC3) {
d = (struct packed_rrset_data*)rep->rrsets[i]->
entry.data;
/* return first signer name of first NSEC */
@ -1090,9 +1153,177 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len,
return r;
}
/** find nsec3 closest encloser in neg cache */
static struct val_neg_data*
neg_find_nsec3_ce(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len,
int qlabs, ldns_buffer* buf, uint8_t* hashnc, size_t* nclen)
{
struct val_neg_data* data;
uint8_t hashce[SHA_DIGEST_LENGTH];
uint8_t b32[257];
size_t celen, b32len;
*nclen = 0;
while(qlabs > 0) {
/* hash */
if(!(celen=nsec3_get_hashed(buf, qname, qname_len,
zone->nsec3_hash, zone->nsec3_iter, zone->nsec3_salt,
zone->nsec3_saltlen, hashce, sizeof(hashce))))
return NULL;
if(!(b32len=nsec3_hash_to_b32(hashce, celen, zone->name,
zone->len, b32, sizeof(b32))))
return NULL;
/* lookup (exact match only) */
data = neg_find_data(zone, b32, b32len, zone->labs+1);
if(data && data->in_use) {
/* found ce match! */
return data;
}
*nclen = celen;
memmove(hashnc, hashce, celen);
dname_remove_label(&qname, &qname_len);
qlabs --;
}
return NULL;
}
/** check nsec3 parameters on nsec3 rrset with current zone values */
static int
neg_params_ok(struct val_neg_zone* zone, struct ub_packed_rrset_key* rrset)
{
int h;
uint8_t* s;
size_t slen, it;
if(!nsec3_get_params(rrset, 0, &h, &it, &s, &slen))
return 0;
return (h == zone->nsec3_hash && it == zone->nsec3_iter &&
slen == zone->nsec3_saltlen &&
memcmp(zone->nsec3_salt, s, slen) == 0);
}
/** get next closer for nsec3 proof */
static struct ub_packed_rrset_key*
neg_nsec3_getnc(struct val_neg_zone* zone, uint8_t* hashnc, size_t nclen,
struct rrset_cache* rrset_cache, struct regional* region,
uint32_t now, uint8_t* b32, size_t maxb32)
{
struct ub_packed_rrset_key* nc_rrset;
struct val_neg_data* data;
size_t b32len;
if(!(b32len=nsec3_hash_to_b32(hashnc, nclen, zone->name,
zone->len, b32, maxb32)))
return NULL;
(void)neg_closest_data(zone, b32, b32len, zone->labs+1, &data);
if(!data && zone->tree.count != 0) {
/* could be before the first entry ; return the last
* entry (possibly the rollover nsec3 at end) */
data = (struct val_neg_data*)rbtree_last(&zone->tree);
}
while(data && !data->in_use)
data = data->parent;
if(!data)
return NULL;
/* got a data element in tree, grab it */
nc_rrset = grab_nsec(rrset_cache, data->name, data->len,
LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 0, 0, now);
if(!nc_rrset)
return NULL;
if(!neg_params_ok(zone, nc_rrset))
return NULL;
return nc_rrset;
}
/** neg cache nsec3 proof procedure*/
static struct dns_msg*
neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len,
int qlabs, ldns_buffer* buf, struct rrset_cache* rrset_cache,
struct regional* region, uint32_t now)
{
struct dns_msg* msg;
struct val_neg_data* data;
uint8_t hashnc[SHA_DIGEST_LENGTH];
size_t nclen;
struct ub_packed_rrset_key* ce_rrset, *nc_rrset;
struct nsec3_cached_hash c;
uint8_t nc_b32[257];
/* for NSEC3 ; determine the closest encloser for which we
* can find an exact match. Remember the hashed lower name,
* since that is the one we need a closest match for.
* If we find a match straight away, then it becomes NODATA.
* Otherwise, NXDOMAIN or if OPTOUT, an insecure delegation.
* Also check that parameters are the same on closest encloser
* and on closest match.
*/
if(!zone->nsec3_hash)
return NULL; /* not nsec3 zone */
if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf,
hashnc, &nclen))) {
return NULL;
}
/* grab the ce rrset */
ce_rrset = grab_nsec(rrset_cache, data->name, data->len,
LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 1,
LDNS_RR_TYPE_DS, now);
if(!ce_rrset)
return NULL;
if(!neg_params_ok(zone, ce_rrset))
return NULL;
if(nclen == 0) {
/* exact match, just check the type bits */
/* need: -SOA, -DS, +NS */
if(nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_SOA) ||
nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_DS) ||
!nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_NS))
return NULL;
if(!(msg = dns_msg_create(qname, qname_len,
LDNS_RR_TYPE_DS, zone->dclass, region, 1)))
return NULL;
if(!dns_msg_authadd(msg, region, ce_rrset, now))
return NULL;
return msg;
}
/* if there is no exact match, it must be in an optout span
* (an existing DS implies an NSEC3 must exist) */
nc_rrset = neg_nsec3_getnc(zone, hashnc, nclen, rrset_cache,
region, now, nc_b32, sizeof(nc_b32));
if(!nc_rrset)
return NULL;
if(!neg_params_ok(zone, nc_rrset))
return NULL;
if(!nsec3_has_optout(nc_rrset, 0))
return NULL;
c.hash = hashnc;
c.hash_len = nclen;
c.b32 = nc_b32+1;
c.b32_len = (size_t)nc_b32[0];
if(nsec3_covers(zone->name, &c, nc_rrset, 0, buf)) {
/* nc_rrset covers the next closer name.
* ce_rrset equals a closer encloser.
* nc_rrset is optout.
* No need to check wildcard for type DS */
if(!(msg = dns_msg_create(qname, qname_len,
LDNS_RR_TYPE_DS, zone->dclass, region, 2)))
return NULL;
if(!dns_msg_authadd(msg, region, ce_rrset, now))
return NULL;
if(!dns_msg_authadd(msg, region, nc_rrset, now))
return NULL;
return msg;
}
return NULL;
}
struct dns_msg*
val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
struct regional* region, struct rrset_cache* rrset_cache, uint32_t now)
struct regional* region, struct rrset_cache* rrset_cache,
ldns_buffer* buf, uint32_t now)
{
struct dns_msg* msg;
struct ub_packed_rrset_key* rrset;
@ -1100,7 +1331,7 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
size_t zname_len;
int zname_labs;
struct val_neg_zone* zone;
struct val_neg_data* data;
/* only for DS queries */
if(qinfo->qtype != LDNS_RR_TYPE_DS)
return NULL;
@ -1141,17 +1372,8 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
return NULL;
}
/* lookup closest data record TODO hash NSEC3 */
(void)neg_closest_data(zone, qinfo->qname, qinfo->qname_len,
zname_labs+1, &data);
while(data && !data->in_use)
data = data->parent;
if(!data) {
lock_basic_unlock(&neg->lock);
return 0;
}
/* get RR and check */
return 0;
msg = neg_nsec3_proof_ds(zone, qinfo->qname, qinfo->qname_len,
zname_labs+1, buf, rrset_cache, region, now);
lock_basic_unlock(&neg->lock);
return msg;
}

View File

@ -99,7 +99,14 @@ struct val_neg_zone {
* No elements have a count of zero, those are removed. */
int count;
/* type of zone ; NSEC */
/** if 0: NSEC zone, else NSEC3 hash algorithm in use */
int nsec3_hash;
/** nsec3 iteration count in use */
size_t nsec3_iter;
/** nsec3 salt in use */
uint8_t* nsec3_salt;
/** length of salt in bytes */
size_t nsec3_saltlen;
/** tree of NSEC data for this zone, sorted canonical
* by NSEC owner name */
@ -227,6 +234,7 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len,
* @param qinfo: query
* @param region: where to allocate reply.
* @param rrset_cache: rrset cache.
* @param buf: temporary buffer.
* @param now: to check TTLs against.
* @return a reply message if something was found.
* This reply may still need validation.
@ -234,6 +242,6 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len,
*/
struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg,
struct query_info* qinfo, struct regional* region,
struct rrset_cache* rrset_cache, uint32_t now);
struct rrset_cache* rrset_cache, ldns_buffer* buf, uint32_t now);
#endif /* VALIDATOR_VAL_NEG_H */

View File

@ -125,8 +125,7 @@ nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r)
return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS);
}
/** return if nsec3 RR has the optout flag */
static int
int
nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r)
{
struct packed_rrset_data* d = (struct packed_rrset_data*)
@ -203,8 +202,19 @@ nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r,
return 1;
}
/** return nsec3 RR next hashed owner name */
static int
int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r,
int* algo, size_t* iter, uint8_t** salt, size_t* saltlen)
{
if(!nsec3_known_algo(rrset, r) || nsec3_unknown_flags(rrset, r))
return 0;
if(!nsec3_get_salt(rrset, r, salt, saltlen))
return 0;
*algo = nsec3_get_algo(rrset, r);
*iter = nsec3_get_iter(rrset, r);
return 1;
}
int
nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r,
uint8_t** next, size_t* nextlen)
{
@ -233,8 +243,39 @@ nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r,
return 1;
}
/** see if NSEC3 RR contains given type */
static int
size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone,
size_t zonelen, uint8_t* buf, size_t max)
{
/* write b32 of name, leave one for length */
int ret;
if(max < hashlen*2+1) /* quick approx of b32, as if hexb16 */
return 0;
ret = b32_ntop_extended_hex(hash, hashlen, (char*)buf+1, max-1);
if(ret < 1)
return 0;
buf[0] = (uint8_t)ret; /* length of b32 label */
ret++;
if(max - ret < zonelen)
return 0;
memmove(buf+ret, zone, zonelen);
return zonelen+(size_t)ret;
}
size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r,
uint8_t* buf, size_t max)
{
uint8_t* nm, *zone;
size_t nmlen, zonelen;
if(!nsec3_get_nextowner(rrset, r, &nm, &nmlen))
return 0;
/* append zone name; the owner name must be <b32>.zone */
zone = rrset->rk.dname;
zonelen = rrset->rk.dname_len;
dname_remove_label(&zone, &zonelen);
return nsec3_hash_to_b32(nm, nmlen, zone, zonelen, buf, max);
}
int
nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type)
{
uint8_t* bitmap;
@ -482,6 +523,45 @@ nsec3_hash_cmp(const void* c1, const void* c2)
return memcmp(s1, s2, s1len);
}
size_t
nsec3_get_hashed(ldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo,
size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max)
{
size_t i, hash_len;
/* prepare buffer for first iteration */
ldns_buffer_clear(buf);
ldns_buffer_write(buf, nm, nmlen);
query_dname_tolower(ldns_buffer_begin(buf));
ldns_buffer_write(buf, salt, saltlen);
ldns_buffer_flip(buf);
switch(algo) {
#ifdef SHA_DIGEST_LENGTH
case NSEC3_HASH_SHA1:
hash_len = SHA_DIGEST_LENGTH;
if(hash_len > max)
return 0;
(void)SHA1((unsigned char*)ldns_buffer_begin(buf),
(unsigned long)ldns_buffer_limit(buf),
(unsigned char*)res);
for(i=0; i<iter; i++) {
ldns_buffer_clear(buf);
ldns_buffer_write(buf, res, hash_len);
ldns_buffer_write(buf, salt, saltlen);
ldns_buffer_flip(buf);
(void)SHA1(
(unsigned char*)ldns_buffer_begin(buf),
(unsigned long)ldns_buffer_limit(buf),
(unsigned char*)res);
}
break;
#endif /* SHA_DIGEST_LENGTH */
default:
log_err("nsec3 hash of unknown algo %d", algo);
return 0;
}
return hash_len;
}
/** perform hash of name */
static int
nsec3_calc_hash(struct regional* region, ldns_buffer* buf,
@ -682,21 +762,8 @@ find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt,
return 0;
}
/**
* nsec3Covers
* Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
* covers the hash. Covers specifically means that the hash is in between
* the owner and next hashes and does not equal either.
*
* @param flt: the NSEC3 RR filter, contains zone name.
* @param hash: the hash of the name
* @param rrset: the rrset of the NSEC3.
* @param rr: which rr in the rrset.
* @param buf: temporary buffer.
* @return true if covers, false if not.
*/
static int
nsec3_covers(struct nsec3_filter* flt, struct nsec3_cached_hash* hash,
int
nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash,
struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf)
{
uint8_t* next, *owner;
@ -711,7 +778,7 @@ nsec3_covers(struct nsec3_filter* flt, struct nsec3_cached_hash* hash,
if(nextlen != hash->hash_len || hash->hash_len==0||hash->b32_len==0||
(size_t)*rrset->rk.dname != hash->b32_len ||
query_dname_compare(rrset->rk.dname+1+
(size_t)*rrset->rk.dname, flt->zone) != 0)
(size_t)*rrset->rk.dname, zone) != 0)
return 0; /* bad lengths or owner name */
/* This is the "normal case: owner < next and owner < hash < next */
@ -777,7 +844,7 @@ find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt,
break; /* alloc failure */
} else if(r < 0)
continue; /* malformed NSEC3 */
else if(nsec3_covers(flt, hash, s, i_rr,
else if(nsec3_covers(flt->zone, hash, s, i_rr,
env->scratch_buffer)) {
*rrset = s; /* rrset with this name */
*rr = i_rr; /* covers hash with these parameters */

View File

@ -273,4 +273,105 @@ int nsec3_hash_name(rbtree_t* table, struct regional* region, ldns_buffer* buf,
struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname,
size_t dname_len, struct nsec3_cached_hash** hash);
/**
* Get next owner name, converted to base32 encoding and with the
* zone name (taken from the nsec3 owner name) appended.
* @param rrset: the NSEC3 rrset.
* @param r: the rr num of the nsec3 in the rrset.
* @param buf: buffer to store name in
* @param max: size of buffer.
* @return length of name on success. 0 on failure (buffer too short or
* bad format nsec3 record).
*/
size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r,
uint8_t* buf, size_t max);
/**
* Convert hash into base32 encoding and with the
* zone name appended.
* @param hash: hashed buffer
* @param hashlen: length of hash
* @param zone: name of zone
* @param zonelen: length of zonename.
* @param buf: buffer to store name in
* @param max: size of buffer.
* @return length of name on success. 0 on failure (buffer too short or
* bad format nsec3 record).
*/
size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone,
size_t zonelen, uint8_t* buf, size_t max);
/**
* Get NSEC3 parameters out of rr.
* @param rrset: the NSEC3 rrset.
* @param r: the rr num of the nsec3 in the rrset.
* @param algo: nsec3 hash algo.
* @param iter: iteration count.
* @param salt: ptr to salt inside rdata.
* @param saltlen: length of salt.
* @return 0 if bad formatted, unknown nsec3 hash algo, or unknown flags set.
*/
int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r,
int* algo, size_t* iter, uint8_t** salt, size_t* saltlen);
/**
* Get NSEC3 hashed in a buffer
* @param buf: buffer for temp use.
* @param nm: name to hash
* @param nmlen: length of nm.
* @param algo: algo to use, must be known.
* @param iter: iterations
* @param salt: salt for nsec3
* @param saltlen: length of salt.
* @param res: result of hash stored here.
* @param max: maximum space for result.
* @return 0 on failure, otherwise bytelength stored.
*/
size_t nsec3_get_hashed(ldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo,
size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max);
/**
* see if NSEC3 RR contains given type
* @param rrset: NSEC3 rrset
* @param r: RR in rrset
* @param type: in host order to check bit for.
* @return true if bit set, false if not or error.
*/
int nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type);
/**
* return if nsec3 RR has the optout flag
* @param rrset: NSEC3 rrset
* @param r: RR in rrset
* @return true if optout, false on error or not optout
*/
int nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r);
/**
* Return nsec3 RR next hashed owner name
* @param rrset: NSEC3 rrset
* @param r: RR in rrset
* @param next: ptr into rdata to next owner hash
* @param nextlen: length of hash.
* @return false on malformed
*/
int nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r,
uint8_t** next, size_t* nextlen);
/**
* nsec3Covers
* Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
* covers the hash. Covers specifically means that the hash is in between
* the owner and next hashes and does not equal either.
*
* @param zone: the zone name.
* @param hash: the hash of the name
* @param rrset: the rrset of the NSEC3.
* @param rr: which rr in the rrset.
* @param buf: temporary buffer.
* @return true if covers, false if not.
*/
int nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash,
struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf);
#endif /* VALIDATOR_VAL_NSEC3_H */