/** * Find best signer name in this set of rrsigs. * @param rrset: which rrsigs to look through. * @param qinf: the query name that needs validation. * @param signer_name: the best signer_name. Updated if a better one is found. * @param signer_len: length of signer name. * @param matchcount: count of current best name (starts at 0 for no match). * Updated if match is improved. */ static void val_find_best_signer(struct ub_packed_rrset_key* rrset, struct query_info* qinf, uint8_t** signer_name, size_t* signer_len, int* matchcount) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; uint8_t* sign; size_t i; int m; for(i=d->count; i<d->count+d->rrsig_count; i++) { sign = d->rr_data[i]+2+18; /* look at signatures that are valid (long enough), * and have a signer name that is a superdomain of qname, * and then check the number of labels in the shared topdomain * improve the match if possible */ if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/ dname_subdomain_c(qinf->qname, sign)) { (void)dname_lab_cmp(qinf->qname, dname_count_labels(qinf->qname), sign, dname_count_labels(sign), &m); if(m > *matchcount) { *matchcount = m; *signer_name = sign; (void)dname_count_size_labels(*signer_name, signer_len); } } } }
int dname_canonical_compare(uint8_t* d1, uint8_t* d2) { int labs1, labs2, m; labs1 = dname_count_labels(d1); labs2 = dname_count_labels(d2); return dname_canon_lab_cmp(d1, labs1, d2, labs2, &m); }
/** * Given a qname and its proven closest encloser, calculate the "next * closest" name. Basically, this is the name that is one label longer than * the closest encloser that is still a subdomain of qname. * * @param qname: query name. * @param qnamelen: length of qname. * @param ce: closest encloser * @param nm: result name. * @param nmlen: length of nm. */ static void next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, uint8_t** nm, size_t* nmlen) { int strip = dname_count_labels(qname) - dname_count_labels(ce) -1; *nm = qname; *nmlen = qnamelen; if(strip>0) dname_remove_labels(nm, nmlen, strip); }
uint8_t* dname_get_shared_topdomain(uint8_t* d1, uint8_t* d2) { int labs1, labs2, m; size_t len = LDNS_MAX_DOMAINLEN; labs1 = dname_count_labels(d1); labs2 = dname_count_labels(d2); (void)dname_lab_cmp(d1, labs1, d2, labs2, &m); dname_remove_labels(&d1, &len, labs1-m); return d1; }
/** test dname_count_labels */ static void dname_test_count_labels(void) { unit_show_func("util/data/dname.c", "dname_count_labels"); unit_assert(dname_count_labels((uint8_t*)"") == 1); unit_assert(dname_count_labels((uint8_t*)"\003com") == 2); unit_assert(dname_count_labels((uint8_t*)"\003org") == 2); unit_assert(dname_count_labels((uint8_t*)"\007example\003com") == 3); unit_assert(dname_count_labels((uint8_t*)"\003bla\007example\003com") == 4); }
/** enter tagstring into zone */ static int lz_enter_zone_tag(struct local_zones* zones, char* zname, uint8_t* list, size_t len, uint16_t rr_class) { uint8_t dname[LDNS_MAX_DOMAINLEN+1]; size_t dname_len = sizeof(dname); int dname_labs, r = 0; struct local_zone* z; if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { log_err("cannot parse zone name in local-zone-tag: %s", zname); return 0; } dname_labs = dname_count_labels(dname); lock_rw_rdlock(&zones->lock); z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); if(!z) { lock_rw_unlock(&zones->lock); log_err("no local-zone for tag %s", zname); return 0; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); free(z->taglist); z->taglist = memdup(list, len); z->taglen = len; if(z->taglist) r = 1; lock_rw_unlock(&z->lock); return r; }
/** apply config caps whitelist items to name tree */ static int caps_white_apply_cfg(rbtree_t* ntree, struct config_file* cfg) { struct config_strlist* p; for(p=cfg->caps_whitelist; p; p=p->next) { struct name_tree_node* n; size_t len; uint8_t* nm = sldns_str2wire_dname(p->str, &len); if(!nm) { log_err("could not parse %s", p->str); return 0; } n = (struct name_tree_node*)calloc(1, sizeof(*n)); if(!n) { log_err("out of memory"); free(nm); return 0; } n->node.key = n; n->name = nm; n->len = len; n->labs = dname_count_labels(nm); n->dclass = LDNS_RR_CLASS_IN; if(!name_tree_insert(ntree, n, nm, len, n->labs, n->dclass)) { /* duplicate element ignored, idempotent */ free(n->name); free(n); } } name_tree_init_parents(ntree); return 1; }
struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, uint8_t* nm, size_t nm_len, uint16_t dclass) { struct val_neg_zone* zone; struct val_neg_zone* parent; struct val_neg_zone* p, *np; int labs = dname_count_labels(nm); /* find closest enclosing parent zone that (still) exists */ parent = neg_closest_zone_parent(neg, nm, nm_len, labs, dclass); if(parent && query_dname_compare(parent->name, nm) == 0) return parent; /* already exists, weird */ /* if parent exists, it is in use */ log_assert(!parent || parent->count > 0); zone = neg_zone_chain(nm, nm_len, labs, dclass, parent); if(!zone) { return NULL; } /* insert the list of zones into the tree */ p = zone; while(p) { np = p->parent; /* mem use */ neg->use += sizeof(struct val_neg_zone) + p->len; /* insert in tree */ (void)rbtree_insert(&neg->tree, &p->node); /* last one needs proper parent pointer */ if(np == NULL) p->parent = parent; p = np; } return zone; }
int dname_subdomain_c(uint8_t* d1, uint8_t* d2) { int m; /* check subdomain: d1: www.example.com. and d2: example.com. */ /* or d1: example.com. and d2: example.com. */ int labs1 = dname_count_labels(d1); int labs2 = dname_count_labels(d2); if(labs2 > labs1) return 0; if(dname_lab_cmp(d1, labs1, d2, labs2, &m) < 0) { /* must have been example.com , www.example.com - wrong */ /* or otherwise different dnames */ return 0; } return (m == labs2); }
int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp) { /* no delegation point, do not see how we can go down, * robust check, it should really exist */ if(!dp) return 0; /* see if dp equals the qname, then we cannot go down further */ if(query_dname_compare(qinfo->qname, dp->name) == 0) return 0; /* if dp is one label above the name we also cannot go down further */ if(dname_count_labels(qinfo->qname) == dp->namelabs+1) return 0; return 1; }
struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, uint8_t* nm, size_t len, uint16_t dclass) { struct val_neg_zone lookfor; struct val_neg_zone* result; lookfor.node.key = &lookfor; lookfor.name = nm; lookfor.len = len; lookfor.labs = dname_count_labels(lookfor.name); lookfor.dclass = dclass; result = (struct val_neg_zone*) rbtree_search(&neg->tree, lookfor.node.key); return result; }
int iter_msg_from_zone(struct dns_msg* msg, struct delegpt* dp, enum response_type type, uint16_t dclass) { if(!msg || !dp || !msg->rep || !dp->name) return 0; /* SOA RRset - always from reply zone */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_SOA, dclass) || reply_find_rrset_section_ns(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_SOA, dclass)) return 1; if(type == RESPONSE_TYPE_REFERRAL) { size_t i; /* if it adds a single label, i.e. we expect .com, * and referral to example.com. NS ... , then origin zone * is .com. For a referral to sub.example.com. NS ... then * we do not know, since example.com. may be in between. */ for(i=0; i<msg->rep->an_numrrsets+msg->rep->ns_numrrsets; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS && ntohs(s->rk.rrset_class) == dclass) { int l = dname_count_labels(s->rk.dname); if(l == dp->namelabs + 1 && dname_strict_subdomain(s->rk.dname, l, dp->name, dp->namelabs)) return 1; } } return 0; } log_assert(type==RESPONSE_TYPE_ANSWER || type==RESPONSE_TYPE_CNAME); /* not a referral, and not lame delegation (upwards), so, * any NS rrset must be from the zone itself */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_NS, dclass) || reply_find_rrset_section_ns(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_NS, dclass)) return 1; /* a DNSKEY set is expected at the zone apex as well */ /* this is for 'minimal responses' for DNSKEYs */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_DNSKEY, dclass)) return 1; return 0; }
/** find or create element in domainlimit tree */ static struct domain_limit_data* domain_limit_findcreate( struct infra_cache* infra, char* name) { uint8_t* nm; int labs; size_t nmlen; struct domain_limit_data* d; /* parse name */ nm = sldns_str2wire_dname(name, &nmlen); if(!nm) { log_err("could not parse %s", name); return NULL; } labs = dname_count_labels(nm); /* can we find it? */ d = (struct domain_limit_data*)name_tree_find(&infra->domain_limits, nm, nmlen, labs, LDNS_RR_CLASS_IN); if(d) { free(nm); return d; } /* create it */ d = (struct domain_limit_data*)calloc(1, sizeof(*d)); if(!d) { free(nm); return NULL; } d->node.node.key = &d->node; d->node.name = nm; d->node.len = nmlen; d->node.labs = labs; d->node.dclass = LDNS_RR_CLASS_IN; d->lim = -1; d->below = -1; if(!name_tree_insert(&infra->domain_limits, &d->node, nm, nmlen, labs, LDNS_RR_CLASS_IN)) { log_err("duplicate element in domainlimit tree"); free(nm); free(d); return NULL; } return d; }
int local_zones_answer(struct local_zones* zones, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, uint8_t* tagactions, size_t tagactionssize, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags) { /* see if query is covered by a zone, * if so: - try to match (exact) local data * - look at zone type for negative response. */ int labs = dname_count_labels(qinfo->qname); struct local_data* ld = NULL; struct local_zone* z; enum localzone_type lzt; int r, tag = -1; lock_rw_rdlock(&zones->lock); z = local_zones_tags_lookup(zones, qinfo->qname, qinfo->qname_len, labs, qinfo->qclass, taglist, taglen, 0); if(!z) { lock_rw_unlock(&zones->lock); return 0; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&zones->lock); lzt = lz_type(taglist, taglen, z->taglist, z->taglen, tagactions, tagactionssize, z->type, repinfo, z->override_tree, &tag, tagname, num_tags); if((lzt == local_zone_inform || lzt == local_zone_inform_deny) && repinfo) lz_inform_print(z, qinfo, repinfo); if(lzt != local_zone_always_refuse && lzt != local_zone_always_transparent && lzt != local_zone_always_nxdomain && local_data_answer(z, qinfo, edns, buf, temp, labs, &ld, lzt, tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); return 1; } r = lz_zone_answer(z, qinfo, edns, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); return r; }
/** store query section in wireformat buffer, return RETVAL */ static int insert_query(struct query_info* qinfo, struct compress_tree_node** tree, ldns_buffer* buffer, struct regional* region) { if(ldns_buffer_remaining(buffer) < qinfo->qname_len+sizeof(uint16_t)*2) return RETVAL_TRUNC; /* buffer too small */ /* the query is the first name inserted into the tree */ if(!compress_tree_store(qinfo->qname, dname_count_labels(qinfo->qname), ldns_buffer_position(buffer), region, NULL, tree)) return RETVAL_OUTMEM; if(ldns_buffer_current(buffer) == qinfo->qname) ldns_buffer_skip(buffer, (ssize_t)qinfo->qname_len); else ldns_buffer_write(buffer, qinfo->qname, qinfo->qname_len); ldns_buffer_write_u16(buffer, qinfo->qtype); ldns_buffer_write_u16(buffer, qinfo->qclass); return RETVAL_OK; }
struct trust_anchor* anchors_lookup(struct val_anchors* anchors, uint8_t* qname, size_t qname_len, uint16_t qclass) { struct trust_anchor key; struct trust_anchor* result; rbnode_t* res = NULL; key.node.key = &key; key.name = qname; key.namelabs = dname_count_labels(qname); key.namelen = qname_len; key.dclass = qclass; lock_basic_lock(&anchors->lock); if(rbtree_find_less_equal(anchors->tree, &key, &res)) { /* exact */ result = (struct trust_anchor*)res; } else { /* smaller element (or no element) */ int m; result = (struct trust_anchor*)res; if(!result || result->dclass != qclass) { lock_basic_unlock(&anchors->lock); return NULL; } /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->namelabs, key.name, key.namelabs, &m); while(result) { /* go up until qname is subdomain of stub */ if(result->namelabs <= m) break; result = result->parent; } } if(result) { lock_basic_lock(&result->lock); } lock_basic_unlock(&anchors->lock); return result; }
int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, size_t namelen) { int labs = dname_count_labels(name); struct domain_limit_data* d = (struct domain_limit_data*) name_tree_lookup(&infra->domain_limits, name, namelen, labs, LDNS_RR_CLASS_IN); if(!d) return infra_dp_ratelimit; if(d->node.labs == labs && d->lim != -1) return d->lim; /* exact match */ /* find 'below match' */ if(d->node.labs == labs) d = (struct domain_limit_data*)d->node.parent; while(d) { if(d->below != -1) return d->below; d = (struct domain_limit_data*)d->node.parent; } return infra_dp_ratelimit; }
int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc) { struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> entry.data; uint8_t labcount; int labdiff; uint8_t* wn; size_t i, wl; if(d->rrsig_count == 0) { return 1; } labcount = rrsig_get_labcount(d, d->count + 0); /* check rest of signatures identical */ for(i=1; i<d->rrsig_count; i++) { if(labcount != rrsig_get_labcount(d, d->count + i)) { return 0; } } /* OK the rrsigs check out */ /* if the RRSIG label count is shorter than the number of actual * labels, then this rrset was synthesized from a wildcard. * Note that the RRSIG label count doesn't count the root label. */ wn = rrset->rk.dname; wl = rrset->rk.dname_len; /* skip a leading wildcard label in the dname (RFC4035 2.2) */ if(dname_is_wild(wn)) { wn += 2; wl -= 2; } labdiff = (dname_count_labels(wn) - 1) - (int)labcount; if(labdiff > 0) { *wc = wn; dname_remove_labels(wc, &wl, labdiff); return 1; } return 1; }
/** enter override into zone */ static int lz_enter_override(struct local_zones* zones, char* zname, char* netblock, char* type, uint16_t rr_class) { uint8_t dname[LDNS_MAX_DOMAINLEN+1]; size_t dname_len = sizeof(dname); int dname_labs; struct sockaddr_storage addr; int net; socklen_t addrlen; struct local_zone* z; enum localzone_type t; /* parse zone name */ if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { log_err("cannot parse zone name in local-zone-override: %s %s", zname, netblock); return 0; } dname_labs = dname_count_labels(dname); /* parse netblock */ if(!netblockstrtoaddr(netblock, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) { log_err("cannot parse netblock in local-zone-override: %s %s", zname, netblock); return 0; } /* parse zone type */ if(!local_zone_str2type(type, &t)) { log_err("cannot parse type in local-zone-override: %s %s %s", zname, netblock, type); return 0; } /* find localzone entry */ lock_rw_rdlock(&zones->lock); z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); if(!z) { lock_rw_unlock(&zones->lock); log_err("no local-zone for local-zone-override %s", zname); return 0; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); /* create netblock addr_tree if not present yet */ if(!z->override_tree) { z->override_tree = (struct rbtree_t*)regional_alloc_zero( z->region, sizeof(*z->override_tree)); if(!z->override_tree) { lock_rw_unlock(&z->lock); log_err("out of memory"); return 0; } addr_tree_init(z->override_tree); } /* add new elem to tree */ if(z->override_tree) { struct local_zone_override* n; n = (struct local_zone_override*)regional_alloc_zero( z->region, sizeof(*n)); if(!n) { lock_rw_unlock(&z->lock); log_err("out of memory"); return 0; } n->type = t; if(!addr_tree_insert(z->override_tree, (struct addr_tree_node*)n, &addr, addrlen, net)) { lock_rw_unlock(&z->lock); log_err("duplicate local-zone-override %s %s", zname, netblock); return 1; } } lock_rw_unlock(&z->lock); return 1; }
/** store rrset in buffer in wireformat, return RETVAL_* */ static int packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt, uint16_t* num_rrs, time_t timenow, struct regional* region, int do_data, int do_sig, struct compress_tree_node** tree, sldns_pkt_section s, uint16_t qtype, int dnssec, size_t rr_offset) { size_t i, j, owner_pos; int r, owner_labs; uint16_t owner_ptr = 0; struct packed_rrset_data* data = (struct packed_rrset_data*) key->entry.data; /* does this RR type belong in the answer? */ if(!rrset_belongs_in_reply(s, ntohs(key->rk.type), qtype, dnssec)) return RETVAL_OK; owner_labs = dname_count_labels(key->rk.dname); owner_pos = sldns_buffer_position(pkt); if(do_data) { const sldns_rr_descriptor* c = type_rdata_compressable(key); for(i=0; i<data->count; i++) { /* rrset roundrobin */ j = (i + rr_offset) % data->count; if((r=compress_owner(key, pkt, region, tree, owner_pos, &owner_ptr, owner_labs)) != RETVAL_OK) return r; sldns_buffer_write(pkt, &key->rk.type, 2); sldns_buffer_write(pkt, &key->rk.rrset_class, 2); if(data->rr_ttl[j] < timenow) sldns_buffer_write_u32(pkt, 0); else sldns_buffer_write_u32(pkt, data->rr_ttl[j]-timenow); if(c) { if((r=compress_rdata(pkt, data->rr_data[j], data->rr_len[j], region, tree, c)) != RETVAL_OK) return r; } else { if(sldns_buffer_remaining(pkt) < data->rr_len[j]) return RETVAL_TRUNC; sldns_buffer_write(pkt, data->rr_data[j], data->rr_len[j]); } } } /* insert rrsigs */ if(do_sig && dnssec) { size_t total = data->count+data->rrsig_count; for(i=data->count; i<total; i++) { if(owner_ptr && owner_labs != 1) { if(sldns_buffer_remaining(pkt) < 2+4+4+data->rr_len[i]) return RETVAL_TRUNC; sldns_buffer_write(pkt, &owner_ptr, 2); } else { if((r=compress_any_dname(key->rk.dname, pkt, owner_labs, region, tree)) != RETVAL_OK) return r; if(sldns_buffer_remaining(pkt) < 4+4+data->rr_len[i]) return RETVAL_TRUNC; } sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_RRSIG); sldns_buffer_write(pkt, &key->rk.rrset_class, 2); if(data->rr_ttl[i] < timenow) sldns_buffer_write_u32(pkt, 0); else sldns_buffer_write_u32(pkt, data->rr_ttl[i]-timenow); /* rrsig rdata cannot be compressed, perform 100+ byte * memcopy. */ sldns_buffer_write(pkt, data->rr_data[i], data->rr_len[i]); } } /* change rrnum only after we are sure it fits */ if(do_data) *num_rrs += data->count; if(do_sig && dnssec) *num_rrs += data->rrsig_count; return RETVAL_OK; }
int dname_strict_subdomain_c(uint8_t* d1, uint8_t* d2) { return dname_strict_subdomain(d1, dname_count_labels(d1), d2, dname_count_labels(d2)); }
struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, struct regional* region, struct rrset_cache* rrset_cache, ldns_buffer* buf, uint32_t now, int addsoa, uint8_t* topname) { struct dns_msg* msg; struct ub_packed_rrset_key* rrset; 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) 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 */ 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)) return NULL; if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL)) return NULL; return msg; } /* check NSEC3 neg cache for type DS */ /* need to look one zone higher for DS type */ zname = qinfo->qname; zname_len = qinfo->qname_len; dname_remove_label(&zname, &zname_len); zname_labs = dname_count_labels(zname); /* lookup closest zone */ lock_basic_lock(&neg->lock); zone = neg_closest_zone_parent(neg, zname, zname_len, zname_labs, qinfo->qclass); while(zone && !zone->in_use) zone = zone->parent; /* check that the zone is not too high up so that we do not pick data * out of a zone that is above the last-seen key (or trust-anchor). */ if(zone && topname) { if(!dname_subdomain_c(zone->name, topname)) zone = NULL; } if(!zone) { lock_basic_unlock(&neg->lock); return NULL; } msg = neg_nsec3_proof_ds(zone, qinfo->qname, qinfo->qname_len, zname_labs+1, buf, rrset_cache, region, now, topname); if(msg && addsoa && !add_soa(rrset_cache, now, region, msg, zone)) { lock_basic_unlock(&neg->lock); return NULL; } lock_basic_unlock(&neg->lock); return msg; }
/** * Remove NSEC records between start and end points. * By walking the tree, the tree is sorted canonically. * @param neg: negative cache. * @param zone: the zone * @param el: element to start walking at. * @param nsec: the nsec record with the end point */ static void wipeout(struct val_neg_cache* neg, struct val_neg_zone* zone, struct val_neg_data* el, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> entry.data; uint8_t* end; size_t end_len; 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; 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) || !dname_subdomain_c(end, zone->name)) return; /* detect end of zone NSEC ; wipe until the end of zone */ if(query_dname_compare(end, zone->name) == 0) { end = NULL; } walk = rbtree_next(&el->node); while(walk && walk != RBTREE_NULL) { cur = (struct val_neg_data*)walk; /* sanity check: must be larger than start */ if(dname_canon_lab_cmp(cur->name, cur->labs, el->name, el->labs, &m) <= 0) { /* r == 0 skip original record. */ /* r < 0 too small! */ walk = rbtree_next(walk); continue; } /* stop at endpoint, also data at empty nonterminals must be * removed (no NSECs there) so everything between * start and end */ if(end && dname_canon_lab_cmp(cur->name, cur->labs, end, end_labs, &m) >= 0) { break; } /* this element has to be deleted, but we cannot do it * now, because we are walking the tree still ... */ /* get the next element: */ next = rbtree_next(walk); /* now delete the original element, this may trigger * rbtree rebalances, but really, the next element is * the one we need. * But it may trigger delete of other data and the * entire zone. However, if that happens, this is done * by deleting the *parents* of the element for deletion, * and maybe also the entire zone if it is empty. * But parents are smaller in canonical compare, thus, * if a larger element exists, then it is not a parent, * it cannot get deleted, the zone cannot get empty. * If the next==NULL, then zone can be empty. */ if(cur->in_use) neg_delete_data(neg, cur); walk = next; } }
int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, uint16_t qclass, struct rrset_cache* rrset_cache, uint32_t now) { /* lookup closest zone */ struct val_neg_zone* zone; struct val_neg_data* data; int labs; struct ub_packed_rrset_key* nsec; struct packed_rrset_data* d; uint32_t flags; uint8_t* wc; struct query_info qinfo; if(!neg) return 0; log_nametypeclass(VERB_ALGO, "negcache dlvlookup", qname, LDNS_RR_TYPE_DLV, qclass); labs = dname_count_labels(qname); lock_basic_lock(&neg->lock); zone = neg_closest_zone_parent(neg, qname, len, labs, qclass); while(zone && !zone->in_use) zone = zone->parent; if(!zone) { lock_basic_unlock(&neg->lock); return 0; } 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) data = data->parent; if(!data) { lock_basic_unlock(&neg->lock); return 0; } log_nametypeclass(VERB_ALGO, "negcache rr", data->name, LDNS_RR_TYPE_NSEC, zone->dclass); /* lookup rrset in rrset cache */ flags = 0; if(query_dname_compare(data->name, zone->name) == 0) flags = PACKED_RRSET_NSEC_AT_APEX; nsec = rrset_cache_lookup(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC, zone->dclass, flags, now, 0); /* check if secure and TTL ok */ if(!nsec) { lock_basic_unlock(&neg->lock); return 0; } d = (struct packed_rrset_data*)nsec->entry.data; if(!d || now > d->ttl) { lock_rw_unlock(&nsec->entry.lock); /* delete data record if expired */ neg_delete_data(neg, data); lock_basic_unlock(&neg->lock); return 0; } if(d->security != sec_status_secure) { lock_rw_unlock(&nsec->entry.lock); neg_delete_data(neg, data); lock_basic_unlock(&neg->lock); return 0; } verbose(VERB_ALGO, "negcache got secure rrset"); /* check NSEC security */ /* check if NSEC proves no DLV type exists */ /* check if NSEC proves NXDOMAIN for qname */ qinfo.qname = qname; qinfo.qtype = LDNS_RR_TYPE_DLV; qinfo.qclass = qclass; if(!nsec_proves_nodata(nsec, &qinfo, &wc) && !val_nsec_proves_name_error(nsec, qname)) { /* the NSEC is not a denial for the DLV */ lock_rw_unlock(&nsec->entry.lock); lock_basic_unlock(&neg->lock); verbose(VERB_ALGO, "negcache not proven"); return 0; } /* so the NSEC was a NODATA proof, or NXDOMAIN proof. */ /* no need to check for wildcard NSEC; no wildcards in DLV repos */ /* no need to lookup SOA record for client; no response message */ lock_rw_unlock(&nsec->entry.lock); /* if OK touch the LRU for neg_data element */ neg_lru_touch(neg, data); lock_basic_unlock(&neg->lock); verbose(VERB_ALGO, "negcache DLV denial proven"); return 1; }
void neg_insert_data(struct val_neg_cache* neg, struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d; struct val_neg_data* parent; struct val_neg_data* el; uint8_t* nm = nsec->rk.dname; size_t nm_len = nsec->rk.dname_len; int labs = dname_count_labels(nsec->rk.dname); d = (struct packed_rrset_data*)nsec->entry.data; 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, 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); if(parent && query_dname_compare(parent->name, nm) == 0) { /* perfect match already exists */ log_assert(parent->count > 0); el = parent; } else { struct val_neg_data* p, *np; /* create subtree for perfect match */ /* if parent exists, it is in use */ log_assert(!parent || parent->count > 0); el = neg_data_chain(nm, nm_len, labs, parent); if(!el) { log_err("out of memory inserting NSEC negative cache"); return; } el->in_use = 0; /* set on below */ /* insert the list of zones into the tree */ p = el; while(p) { np = p->parent; /* mem use */ neg->use += sizeof(struct val_neg_data) + p->len; /* insert in tree */ p->zone = zone; (void)rbtree_insert(&zone->tree, &p->node); /* last one needs proper parent pointer */ if(np == NULL) p->parent = parent; p = np; } } if(!el->in_use) { struct val_neg_data* p; el->in_use = 1; /* increase usage count of all parents */ for(p=el; p; p = p->parent) { p->count++; } neg_lru_front(neg, el); } else { /* in use, bring to front, lru */ 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) && it <= neg->nsec3_max_iter && (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); }