aboutsummaryrefslogtreecommitdiff
path: root/validator/val_neg.c
diff options
context:
space:
mode:
Diffstat (limited to 'validator/val_neg.c')
-rw-r--r--validator/val_neg.c272
1 files changed, 220 insertions, 52 deletions
diff --git a/validator/val_neg.c b/validator/val_neg.c
index fe57ac2c442e..541238148307 100644
--- a/validator/val_neg.c
+++ b/validator/val_neg.c
@@ -847,34 +847,71 @@ void neg_insert_data(struct val_neg_cache* neg,
wipeout(neg, zone, el, nsec);
}
+/** see if the reply has signed NSEC records and return the signer */
+static uint8_t* reply_nsec_signer(struct reply_info* rep, size_t* signer_len,
+ uint16_t* dclass)
+{
+ size_t i;
+ 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 ||
+ 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 */
+ if(d->rrsig_count != 0) {
+ val_find_rrset_signer(rep->rrsets[i],
+ &s, signer_len);
+ if(s && *signer_len) {
+ *dclass = ntohs(rep->rrsets[i]->
+ rk.rrset_class);
+ return s;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
{
size_t i, need;
struct ub_packed_rrset_key* soa;
+ uint8_t* dname = NULL;
+ size_t dname_len;
+ uint16_t rrset_class;
struct val_neg_zone* zone;
/* see if secure nsecs inside */
if(!reply_has_nsec(rep))
return;
/* find the zone name in message */
- soa = reply_find_soa(rep);
- if(!soa)
- return;
+ if((soa = reply_find_soa(rep))) {
+ dname = soa->rk.dname;
+ dname_len = soa->rk.dname_len;
+ rrset_class = ntohs(soa->rk.rrset_class);
+ }
+ else {
+ /* No SOA in positive (wildcard) answer. Use signer from the
+ * validated answer RRsets' signature. */
+ if(!(dname = reply_nsec_signer(rep, &dname_len, &rrset_class)))
+ return;
+ }
log_nametypeclass(VERB_ALGO, "negcache insert for zone",
- soa->rk.dname, LDNS_RR_TYPE_SOA, ntohs(soa->rk.rrset_class));
+ dname, LDNS_RR_TYPE_SOA, rrset_class);
/* ask for enough space to store all of it */
need = calc_data_need(rep) +
- calc_zone_need(soa->rk.dname, soa->rk.dname_len);
+ calc_zone_need(dname, dname_len);
lock_basic_lock(&neg->lock);
neg_make_space(neg, need);
/* find or create the zone entry */
- zone = neg_find_zone(neg, soa->rk.dname, soa->rk.dname_len,
- ntohs(soa->rk.rrset_class));
+ zone = neg_find_zone(neg, dname, dname_len, rrset_class);
if(!zone) {
- if(!(zone = neg_create_zone(neg, soa->rk.dname,
- soa->rk.dname_len, ntohs(soa->rk.rrset_class)))) {
+ if(!(zone = neg_create_zone(neg, dname, dname_len,
+ rrset_class))) {
lock_basic_unlock(&neg->lock);
log_err("out of memory adding negative zone");
return;
@@ -1029,33 +1066,6 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len,
return 1;
}
-/** see if the reply has signed NSEC records and return the signer */
-static uint8_t* reply_nsec_signer(struct reply_info* rep, size_t* signer_len,
- uint16_t* dclass)
-{
- size_t i;
- 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 ||
- 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 */
- if(d->rrsig_count != 0) {
- val_find_rrset_signer(rep->rrsets[i],
- &s, signer_len);
- if(s && *signer_len) {
- *dclass = ntohs(rep->rrsets[i]->
- rk.rrset_class);
- return s;
- }
- }
- }
- }
- return 0;
-}
-
void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep,
uint8_t* zone_name)
{
@@ -1183,6 +1193,73 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len,
return r;
}
+/**
+ * Get best NSEC record for qname. Might be matching, covering or totally
+ * useless.
+ * @param neg_cache: neg cache
+ * @param qname: to lookup rrset name
+ * @param qname_len: length of qname.
+ * @param qclass: class of rrset to lookup, host order
+ * @param rrset_cache: rrset cache
+ * @param now: to check ttl against
+ * @param region: where to alloc result
+ * @return rrset or NULL
+ */
+static struct ub_packed_rrset_key*
+neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len,
+ uint16_t qclass, struct rrset_cache* rrset_cache, time_t now,
+ struct regional* region)
+{
+ int labs;
+ uint32_t flags;
+ struct val_neg_zone* zone;
+ struct val_neg_data* data;
+ struct ub_packed_rrset_key* nsec;
+
+ labs = dname_count_labels(qname);
+ lock_basic_lock(&neg_cache->lock);
+ zone = neg_closest_zone_parent(neg_cache, qname, qname_len, labs,
+ qclass);
+ while(zone && !zone->in_use)
+ zone = zone->parent;
+ if(!zone) {
+ lock_basic_unlock(&neg_cache->lock);
+ return NULL;
+ }
+
+ /* NSEC only for now */
+ if(zone->nsec3_hash) {
+ lock_basic_unlock(&neg_cache->lock);
+ return NULL;
+ }
+
+ /* ignore return value, don't care if it is an exact or smaller match */
+ (void)neg_closest_data(zone, qname, qname_len, labs, &data);
+ if(!data) {
+ lock_basic_unlock(&neg_cache->lock);
+ return NULL;
+ }
+
+ /* ENT nodes are not in use, try the previous node. If the previous node
+ * is not in use, we don't have an useful NSEC and give up. */
+ if(!data->in_use) {
+ data = (struct val_neg_data*)rbtree_previous((rbnode_type*)data);
+ if((rbnode_type*)data == RBTREE_NULL || !data->in_use) {
+ lock_basic_unlock(&neg_cache->lock);
+ return NULL;
+ }
+ }
+
+ flags = 0;
+ if(query_dname_compare(data->name, zone->name) == 0)
+ flags = PACKED_RRSET_NSEC_AT_APEX;
+
+ nsec = grab_nsec(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC,
+ zone->dclass, flags, region, 0, 0, now);
+ lock_basic_unlock(&neg_cache->lock);
+ return nsec;
+}
+
/** 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,
@@ -1400,41 +1477,132 @@ static int add_soa(struct rrset_cache* rrset_cache, time_t now,
struct dns_msg*
val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
struct regional* region, struct rrset_cache* rrset_cache,
- sldns_buffer* buf, time_t now, int addsoa, uint8_t* topname)
+ sldns_buffer* buf, time_t now, int addsoa, uint8_t* topname,
+ struct config_file* cfg)
{
struct dns_msg* msg;
- struct ub_packed_rrset_key* rrset;
+ struct ub_packed_rrset_key* nsec; /* qname matching/covering nsec */
+ struct ub_packed_rrset_key* wcrr; /* wildcard record or nsec */
+ uint8_t* nodata_wc = NULL;
+ uint8_t* ce = NULL;
+ size_t ce_len;
+ uint8_t wc_ce[LDNS_MAX_DOMAINLEN+3];
+ struct query_info wc_qinfo;
+ struct ub_packed_rrset_key* cache_wc;
+ struct packed_rrset_data* wcrr_data;
+ int rcode = LDNS_RCODE_NOERROR;
uint8_t* zname;
size_t zname_len;
int zname_labs;
struct val_neg_zone* zone;
- /* only for DS queries */
- if(qinfo->qtype != LDNS_RR_TYPE_DS)
+ /* only for DS queries when aggressive use of NSEC is disabled */
+ if(qinfo->qtype != LDNS_RR_TYPE_DS && !cfg->aggressive_nsec)
return NULL;
log_assert(!topname || dname_subdomain_c(qinfo->qname, topname));
- /* see if info from neg cache is available
- * For NSECs, because there is no optout; a DS next to a delegation
- * always has exactly an NSEC for it itself; check its DS bit.
- * flags=0 (not the zone apex).
- */
- rrset = grab_nsec(rrset_cache, qinfo->qname, qinfo->qname_len,
- LDNS_RR_TYPE_NSEC, qinfo->qclass, 0, region, 1,
- qinfo->qtype, now);
- if(rrset) {
- /* return msg with that rrset */
+ /* Get best available NSEC for qname */
+ nsec = neg_find_nsec(neg, qinfo->qname, qinfo->qname_len, qinfo->qclass,
+ rrset_cache, now, region);
+
+ /* Matching NSEC, use to generate No Data answer. Not creating answers
+ * yet for No Data proven using wildcard. */
+ if(nsec && nsec_proves_nodata(nsec, qinfo, &nodata_wc) && !nodata_wc) {
if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
qinfo->qtype, qinfo->qclass, region, 2)))
return NULL;
- /* TTL already subtracted in grab_nsec */
- if(!dns_msg_authadd(msg, region, rrset, 0))
+ if(!dns_msg_authadd(msg, region, nsec, 0))
return NULL;
if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL))
return NULL;
return msg;
+ } else if(nsec && val_nsec_proves_name_error(nsec, qinfo->qname)) {
+ if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
+ qinfo->qtype, qinfo->qclass, region, 3)))
+ return NULL;
+ if(!(ce = nsec_closest_encloser(qinfo->qname, nsec)))
+ return NULL;
+ dname_count_size_labels(ce, &ce_len);
+
+ /* No extra extra NSEC required if both nameerror qname and
+ * nodata *.ce. are proven already. */
+ if(!nodata_wc || query_dname_compare(nodata_wc, ce) != 0) {
+ /* Qname proven non existing, get wildcard record for
+ * QTYPE or NSEC covering or matching wildcard. */
+
+ /* Num labels in ce is always smaller than in qname,
+ * therefore adding the wildcard label cannot overflow
+ * buffer. */
+ wc_ce[0] = 1;
+ wc_ce[1] = (uint8_t)'*';
+ memmove(wc_ce+2, ce, ce_len);
+ wc_qinfo.qname = wc_ce;
+ wc_qinfo.qname_len = ce_len + 2;
+ wc_qinfo.qtype = qinfo->qtype;
+
+
+ if((cache_wc = rrset_cache_lookup(rrset_cache, wc_qinfo.qname,
+ wc_qinfo.qname_len, wc_qinfo.qtype,
+ qinfo->qclass, 0/*flags*/, now, 0/*read only*/))) {
+ /* Synthesize wildcard answer */
+ wcrr_data = (struct packed_rrset_data*)cache_wc->entry.data;
+ if(!(wcrr_data->security == sec_status_secure ||
+ (wcrr_data->security == sec_status_unchecked &&
+ wcrr_data->rrsig_count > 0))) {
+ lock_rw_unlock(&cache_wc->entry.lock);
+ return NULL;
+ }
+ if(!(wcrr = packed_rrset_copy_region(cache_wc,
+ region, now))) {
+ lock_rw_unlock(&cache_wc->entry.lock);
+ return NULL;
+ };
+ lock_rw_unlock(&cache_wc->entry.lock);
+ wcrr->rk.dname = qinfo->qname;
+ wcrr->rk.dname_len = qinfo->qname_len;
+ if(!dns_msg_ansadd(msg, region, wcrr, 0))
+ return NULL;
+ /* No SOA needed for wildcard synthesised
+ * answer. */
+ addsoa = 0;
+ } else {
+ /* Get wildcard NSEC for possible non existence
+ * proof */
+ if(!(wcrr = neg_find_nsec(neg, wc_qinfo.qname,
+ wc_qinfo.qname_len, qinfo->qclass,
+ rrset_cache, now, region)))
+ return NULL;
+
+ nodata_wc = NULL;
+ if(val_nsec_proves_name_error(wcrr, wc_ce))
+ rcode = LDNS_RCODE_NXDOMAIN;
+ else if(!nsec_proves_nodata(wcrr, &wc_qinfo,
+ &nodata_wc) || nodata_wc)
+ /* &nodata_wc shoudn't be set, wc_qinfo
+ * already contains wildcard domain. */
+ /* NSEC doesn't prove anything for
+ * wildcard. */
+ return NULL;
+ if(query_dname_compare(wcrr->rk.dname,
+ nsec->rk.dname) != 0)
+ if(!dns_msg_authadd(msg, region, wcrr, 0))
+ return NULL;
+ }
+ }
+
+ if(!dns_msg_authadd(msg, region, nsec, 0))
+ return NULL;
+ if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL))
+ return NULL;
+
+ FLAGS_SET_RCODE(msg->rep->flags, rcode);
+ return msg;
}
+ /* No aggressive use of NSEC3 for now, only proceed for DS types. */
+ if(qinfo->qtype != LDNS_RR_TYPE_DS){
+ return NULL;
+ }
/* check NSEC3 neg cache for type DS */
/* need to look one zone higher for DS type */
zname = qinfo->qname;