From c73c662fce31a2712a44503a6a1147208693f53a Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Wed, 8 Oct 2008 14:42:46 +0000 Subject: [PATCH] work on nsec3 negative cache for qtype DS . git-svn-id: file:///svn/unbound/trunk@1290 be551aaa-1e26-0410-a405-d3ace91eadb9 --- iterator/iterator.c | 2 +- validator/val_neg.c | 264 ++++++++++++++++++++++++++++++++++++++---- validator/val_neg.h | 12 +- validator/val_nsec3.c | 113 ++++++++++++++---- validator/val_nsec3.h | 101 ++++++++++++++++ 5 files changed, 445 insertions(+), 47 deletions(-) diff --git a/iterator/iterator.c b/iterator/iterator.c index 11226d2e8..94c8c848e 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -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 */ diff --git a/validator/val_neg.c b/validator/val_neg.c index f2bcd61e6..49f96c2d2 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -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; } diff --git a/validator/val_neg.h b/validator/val_neg.h index 8bb60848e..e1c949c0f 100644 --- a/validator/val_neg.h +++ b/validator/val_neg.h @@ -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 */ diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c index 3d73ef9c4..4d5c34a8c 100644 --- a/validator/val_nsec3.c +++ b/validator/val_nsec3.c @@ -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 .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; ihash_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 */ diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h index f0188d2de..fc6792cb8 100644 --- a/validator/val_nsec3.h +++ b/validator/val_nsec3.h @@ -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 */ -- 2.47.3