]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
negative cache work for DLV
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 19 Aug 2008 15:27:31 +0000 (15:27 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 19 Aug 2008 15:27:31 +0000 (15:27 +0000)
git-svn-id: file:///svn/unbound/trunk@1198 be551aaa-1e26-0410-a405-d3ace91eadb9

validator/val_neg.c
validator/val_neg.h

index b7e263cad50ac01d7c7088d54490f47057e27be7..8ca7b28c7ea26d8f734a48f0d98c3cc9213f55a3 100644 (file)
@@ -98,22 +98,118 @@ size_t val_neg_get_mem(struct val_neg_cache* neg)
        return result;
 }
 
+/** clear datas on cache deletion */
+static void
+neg_clear_datas(rbnode_t* n, void* ATTR_UNUSED(arg))
+{
+       struct val_neg_data* d = (struct val_neg_data*)n;
+       free(d->name);
+       free(d);
+}
+
+/** clear zones on cache deletion */
+static void
+neg_clear_zones(rbnode_t* n, void* ATTR_UNUSED(arg))
+{
+       struct val_neg_zone* z = (struct val_neg_zone*)n;
+       /* delete all the rrset entries in the tree */
+       traverse_postorder(&z->tree, &neg_clear_datas, NULL);
+       free(z->name);
+       free(z);
+}
+
 void neg_cache_delete(struct val_neg_cache* neg)
 {
-       struct val_neg_data* p, *np;
        if(!neg) return;
        lock_basic_destroy(&neg->lock);
-       /* delete all the zonedata elements */
-       p = neg->first;
+       /* delete all the zones in the tree */
+       traverse_postorder(&neg->tree, &neg_clear_zones, NULL);
+       free(neg);
+}
+
+/**
+ * Delete a zone element from the negative cache.
+ * May delete other zone elements to keep tree coherent, or
+ * only mark the element as 'not in use'.
+ * @param neg: negative cache.
+ * @param z: zone element to delete.
+ */
+static void neg_delete_zone(struct val_neg_cache* neg, struct val_neg_zone* z)
+{
+       struct val_neg_zone* p, *np;
+       if(!z) return;
+       log_assert(z->in_use);
+       log_assert(z->count > 0);
+       z->in_use = 0;
+
+       /* go up the tree and reduce counts */
+       p = z;
        while(p) {
-               np = p->next;
+               log_assert(p->count > 0);
+               p->count --;
+               p = p->parent;
+       }
+
+       /* remove zones with zero count */
+       p = z;
+       while(p && p->count == 0) {
+               np = p->parent;
+               (void)rbtree_delete(&neg->tree, &p->node);
+               neg->use -= p->len + sizeof(*p);
                free(p->name);
                free(p);
                p = np;
        }
-       /* delete all the zones in the tree */
-       /* TODO */
-       free(neg);
+}
+       
+/**
+ * Delete a data element from the negative cache.
+ * May delete other data elements to keep tree coherent, or
+ * only mark the element as 'not in use'.
+ * @param neg: negative cache.
+ * @param el: data element to delete.
+ */
+static void neg_delete_data(struct val_neg_cache* neg, struct val_neg_data* el)
+{
+       struct val_neg_zone* z;
+       struct val_neg_data* p, *np;
+       if(!el) return;
+       z = el->zone;
+       log_assert(el->in_use);
+       log_assert(el->count > 0);
+       el->in_use = 0;
+
+       /* remove it from the lru list */
+       if(el->prev)
+               el->prev->next = el->next;
+       else    neg->first = el->next;
+       if(el->next)
+               el->next->prev = el->prev;
+       else    neg->last = el->prev;
+       
+       /* go up the tree and reduce counts */
+       p = el;
+       while(p) {
+               log_assert(p->count > 0);
+               p->count --;
+               p = p->parent;
+       }
+
+       /* delete 0 count items from tree */
+       p = el;
+       while(p && p->count == 0) {
+               np = p->parent;
+               (void)rbtree_delete(&z->tree, &p->node);
+               neg->use -= p->len + sizeof(*p);
+               free(p->name);
+               free(p);
+               p = np;
+       }
+
+       /* check if the zone is now unused */
+       if(z->tree.count == 0) {
+               neg_delete_zone(neg, z);
+       }
 }
 
 /**
@@ -126,9 +222,8 @@ void neg_cache_delete(struct val_neg_cache* neg)
 static void neg_make_space(struct val_neg_cache* neg, size_t need)
 {
        /* delete elements until enough space or its empty */
-       while(neg->last && (neg->max - neg->use) < need) {
-               /* Delete data, zone */
-               /* update parent ptrs of items beneath it */
+       while(neg->last && neg->max < neg->use + need) {
+               neg_delete_data(neg, neg->last);
        }
 }
 
@@ -155,46 +250,256 @@ static struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg,
 }
 
 /**
- * Create a new zone.
- * @param neg: negative cache
- * @param soa: what to look for.
- * @return zone or NULL if out of memory.
- *     Other data may be deleted to make room for the new zone.
+ * Calculate space needed for the data and all its parents
+ * @param rep: NSEC entries.
+ * @return size.
  */
-static struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg,
-       struct ub_packed_rrset_key* soa)
+static size_t calc_data_need(struct reply_info* rep)
 {
-       struct val_neg_zone* zone;
-       size_t need;
+       uint8_t* d;
+       size_t len;
+       size_t res = 0;
+       size_t i;
 
-       /* make space */
-       need = sizeof(struct val_neg_zone) + soa->rk.dname_len;
-       neg_make_space(neg, need);
+       for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) {
+               if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) {
+                       d = rep->rrsets[i]->rk.dname;
+                       len = rep->rrsets[i]->rk.dname_len;
+                       res = sizeof(struct val_neg_data) + len;
+                       while(!dname_is_root(d)) {
+                               log_assert(len > 1); /* not root label */
+                               dname_remove_label(&d, &len);
+                               res += sizeof(struct val_neg_data) + len;
+                       }
+               }
+       }
+       return res;
+}
+
+/**
+ * Calculate space needed for zone and all its parents
+ * @param soa: with name.
+ * @return size.
+ */
+static size_t calc_zone_need(struct ub_packed_rrset_key* soa)
+{
+       uint8_t* d = soa->rk.dname;
+       size_t len = soa->rk.dname_len;
+       size_t res = sizeof(struct val_neg_zone) + len;
+       while(!dname_is_root(d)) {
+               log_assert(len > 1); /* not root label */
+               dname_remove_label(&d, &len);
+               res += sizeof(struct val_neg_zone) + len;
+       }
+       return res;
+}
+
+/**
+ * Find closest existing parent zone of the given name.
+ * @param neg: negative cache.
+ * @param nm: name to look for
+ * @param nm_len: length of nm
+ * @param labs: labelcount of nm.
+ * @param qclass: class.
+ * @return the zone or NULL if none found.
+ */
+static struct val_neg_zone* neg_closest_zone_parent(
+       struct val_neg_cache* neg, uint8_t* nm, size_t nm_len, int labs,
+       uint16_t qclass)
+{
+       struct val_neg_zone key;
+       struct val_neg_zone* result;
+       rbnode_t* res = NULL;
+       key.node.key = &key;
+       key.name = nm;
+       key.len = nm_len;
+       key.labs = labs;
+       key.dclass = qclass;
+       if(rbtree_find_less_equal(&neg->tree, &key, &res)) {
+               /* exact match */
+               result = (struct val_neg_zone*)res;
+       } else {
+               /* smaller element (or no element) */
+               int m;
+               result = (struct val_neg_zone*)res;
+               if(!result || result->dclass != qclass)
+                       return NULL;
+               /* count number of labels matched */
+               (void)dname_lab_cmp(result->name, result->labs, key.name,
+                       key.labs, &m);
+               while(result) { /* go up until qname is subdomain of stub */
+                       if(result->labs <= m)
+                               break;
+                       result = result->parent;
+               }
+       }
+       return result;
+}
 
-       /* create new item */
-       zone = (struct val_neg_zone*)calloc(1, sizeof(*zone));
+/**
+ * Find closest existing parent data for the given name.
+ * @param zone: to look in.
+ * @param nm: name to look for
+ * @param nm_len: length of nm
+ * @param labs: labelcount of nm.
+ * @return the data or NULL if none found.
+ */
+static struct val_neg_data* neg_closest_data_parent(
+       struct val_neg_zone* zone, uint8_t* nm, size_t nm_len, int labs)
+{
+       struct val_neg_data key;
+       struct val_neg_data* result;
+       rbnode_t* res = NULL;
+       key.node.key = &key;
+       key.name = nm;
+       key.len = nm_len;
+       key.labs = labs;
+       if(rbtree_find_less_equal(&zone->tree, &key, &res)) {
+               /* exact match */
+               result = (struct val_neg_data*)res;
+       } else {
+               /* smaller element (or no element) */
+               int m;
+               result = (struct val_neg_data*)res;
+               if(!result)
+                       return NULL;
+               /* count number of labels matched */
+               (void)dname_lab_cmp(result->name, result->labs, key.name,
+                       key.labs, &m);
+               while(result) { /* go up until qname is subdomain of stub */
+                       if(result->labs <= m)
+                               break;
+                       result = result->parent;
+               }
+       }
+       return result;
+}
+
+/**
+ * Create a single zone node
+ * @param nm: name for zone (copied)
+ * @param nm_len: length of name
+ * @param labs: labels in name.
+ * @param dclass: class of zone.
+ * @return new zone or NULL on failure
+ */
+static struct val_neg_zone* neg_setup_zone_node(
+       uint8_t* nm, size_t nm_len, int labs, uint16_t dclass)
+{
+       struct val_neg_zone* zone = 
+               (struct val_neg_zone*)calloc(1, sizeof(*zone));
        if(!zone) {
                return NULL;
        }
        zone->node.key = zone;
-       zone->name = memdup(soa->rk.dname, soa->rk.dname_len);
+       zone->name = memdup(nm, nm_len);
        if(!zone->name) {
+               free(zone);
                return NULL;
        }
-       zone->len = soa->rk.dname_len;
-       zone->labs = dname_count_labels(zone->name);
-       zone->dclass = ntohs(soa->rk.rrset_class);
+       zone->len = nm_len;
+       zone->labs = labs;
+       zone->dclass = dclass;
 
-       zone->soa_hash = soa->entry.hash;
        rbtree_init(&zone->tree, &val_neg_data_compare);
+       return zone;
+}
 
-       /* insert in tree */
-       (void)rbtree_insert(&neg->tree, &zone->node);
+/**
+ * Create a linked list of parent zones, starting at longname ending on
+ * the parent (can be NULL, creates to the root).
+ * @param nm: name for lowest in chain
+ * @param nm_len: length of name
+ * @param labs: labels in name.
+ * @param dclass: class of zone.
+ * @param parent: NULL for to root, else so it fits under here.
+ * @return zone; a chain of zones and their parents up to the parent.
+ *     or NULL on malloc failure
+ */
+static struct val_neg_zone* neg_zone_chain(
+       uint8_t* nm, size_t nm_len, int labs, uint16_t dclass,
+       struct val_neg_zone* parent)
+{
+       int i;
+       int tolabs = parent?parent->labs:-1;
+       struct val_neg_zone* zone, *prev = NULL, *first = NULL;
 
-       /* find zone->parent */
-       
+       /* create the new subtree, i is labelcount of current creation */
+       /* this creates a 'first' to z->parent=NULL list of zones */
+       for(i=labs; i!=tolabs; i--) {
+               /* create new item */
+               zone = neg_setup_zone_node(nm, nm_len, i, dclass);
+               if(!zone) {
+                       /* need to delete other allocations in this routine!*/
+                       struct val_neg_zone* p=first, *np;
+                       while(p) {
+                               np = p->parent;
+                               free(p);
+                               free(p->name);
+                               p = np;
+                       }
+                       return NULL;
+               }
+               if(i == labs) {
+                       first = zone;
+               } else {
+                       prev->parent = zone;
+               }
+               /* prepare for next name */
+               prev = zone;
+               dname_remove_label(&nm, &nm_len);
+       }
+       return first;
+}
 
-       /* set this zone as parent for lower zones */
+/**
+ * Create a new zone.
+ * @param neg: negative cache
+ * @param soa: what to look for.
+ * @return zone or NULL if out of memory.
+ */
+static struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg,
+       struct ub_packed_rrset_key* soa)
+{
+       struct val_neg_zone* zone;
+       struct val_neg_zone* parent;
+       struct val_neg_zone* p, *np;
+       uint8_t* nm = soa->rk.dname;
+       size_t nm_len = soa->rk.dname_len;
+       int labs = dname_count_labels(nm);
+       uint16_t dclass = ntohs(soa->rk.rrset_class);
+
+       /* 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;
+       }
+       zone->in_use = 1;
+       zone->soa_hash = soa->entry.hash;
+
+       /* 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;
+       }
+       /* increase usage count of all parents */
+       for(p=zone; p; p = p->parent) {
+               p->count++;
+       }
+       return zone;
 }
 
 /** find zone name of message, returns the SOA record */
@@ -202,18 +507,223 @@ static struct ub_packed_rrset_key* reply_find_soa(struct reply_info* rep)
 {
        size_t i;
        for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){
-               if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_SOA)
+               if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_SOA)
                        return rep->rrsets[i];
        }
        return NULL;
 }
 
-void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
+/** see if the reply has NSEC records worthy of caching */
+static int reply_has_nsec(struct reply_info* rep)
 {
        size_t i;
+       struct packed_rrset_data* d;
+       if(rep->security != sec_status_secure)
+               return 0;
+       for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){
+               if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) {
+                       d = (struct packed_rrset_data*)rep->rrsets[i]->
+                               entry.data;
+                       if(d->security == sec_status_secure)
+                               return 1;
+               }
+       }
+       return 0;
+}
+
+
+/**
+ * Create single node of data element.
+ * @param nm: name (copied)
+ * @param nm_len: length of name
+ * @param labs: labels in name.
+ * @return element with name nm, or NULL malloc failure.
+ */
+static struct val_neg_data* neg_setup_data_node(
+       uint8_t* nm, size_t nm_len, int labs)
+{
+       struct val_neg_data* el;
+       el = (struct val_neg_data*)calloc(1, sizeof(*el));
+       if(!el) {
+               return NULL;
+       }
+       el->node.key = el;
+       el->name = memdup(nm, nm_len);
+       if(!el->name) {
+               free(el);
+               return NULL;
+       }
+       el->len = nm_len;
+       el->labs = labs;
+       return el;
+}
+
+/**
+ * Create chain of data element and parents
+ * @param nm: name
+ * @param nm_len: length of name
+ * @param labs: labels in name.
+ * @param parent: up to where to make, if NULL up to root label.
+ * @return lowest element with name nm, or NULL malloc failure.
+ */
+static struct val_neg_data* neg_data_chain(
+       uint8_t* nm, size_t nm_len, int labs, struct val_neg_data* parent)
+{
+       int i;
+       int tolabs = parent?parent->labs:-1;
+       struct val_neg_data* el, *first = NULL, *prev = NULL;
+
+       /* create the new subtree, i is labelcount of current creation */
+       /* this creates a 'first' to z->parent=NULL list of zones */
+       for(i=labs; i!=tolabs; i--) {
+               /* create new item */
+               el = neg_setup_data_node(nm, nm_len, i);
+               if(!el) {
+                       /* need to delete other allocations in this routine!*/
+                       struct val_neg_data* p = first, *np;
+                       p=first;
+                       while(p) {
+                               np = p->parent;
+                               free(p);
+                               free(p->name);
+                               p = np;
+                       }
+                       return NULL;
+               }
+               if(i == labs) {
+                       first = el;
+               } else {
+                       prev->parent = el;
+               }
+
+               /* prepare for next name */
+               prev = el;
+               dname_remove_label(&nm, &nm_len);
+       }
+       return first;
+}
+
+/**
+ * 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;
+       /* 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);
+
+       /* 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 */
+
+       /* TODO */
+}
+
+
+/**
+ * Insert data into the data tree of a zone
+ * @param neg: negative cache
+ * @param zone: zone to insert into
+ * @param nsec: record to insert.
+ */
+static 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)
+               return;
+
+       /* 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;
+               el->nsec_hash = nsec->entry.hash;
+       } 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 */
+               el->nsec_hash = nsec->entry.hash;
+
+               /* 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 */
+                       (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++;
+               }
+
+               /** INSERT data in LRU chain */
+               el->next = neg->first;
+               el->prev = NULL;
+               if(neg->first)
+                       neg->first->prev = el;
+               else    neg->last = el;
+               neg->first = el;
+       }
+
+       /* wipe out the cache items between NSEC start and end */
+       wipeout(neg, zone, el, nsec);
+}
+
+void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
+{
+       size_t i, need;
        struct ub_packed_rrset_key* soa;
        struct val_neg_zone* zone;
        /* find the zone name in message */
+       if(!reply_has_nsec(rep))
+               return;
        soa = reply_find_soa(rep);
        if(!soa)
                return;
@@ -221,6 +731,12 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
        /* find or create the zone entry */
        lock_basic_lock(&neg->lock);
        zone = neg_find_zone(neg, soa);
+
+       /* ask for enough space to store all of it */
+       need = calc_data_need(rep);
+       need += calc_zone_need(soa);
+       neg_make_space(neg, need);
+
        if(!zone) {
                if(!(zone = neg_create_zone(neg, soa))) {
                        lock_basic_unlock(&neg->lock);
@@ -233,6 +749,10 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
        for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){
                if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC)
                        continue;
+               /* insert NSEC into this zone's tree */
+               log_assert(dname_subdomain_c(rep->rrsets[i]->rk.dname, 
+                       zone->name));
+               neg_insert_data(neg, zone, rep->rrsets[i]);
        }
        lock_basic_unlock(&neg->lock);
 }
index 857c6aff417e10d017b8d334ba9e99705688fb7f..3b93ac8b3400da7c90fe298d100e67b0efa03330 100644 (file)
@@ -90,6 +90,11 @@ struct val_neg_zone {
        /** pointer to parent zone in the negative cache */
        struct val_neg_zone* parent;
 
+       /** the number of elements, including this one and the ones whose
+        * parents (-parents) include this one, that are in use 
+        * No elements have a count of zero, those are removed. */
+       int count;
+
        /** type of zone ; NSEC */
 
        /** hash of zonename, SOA type, class, for lookup of SOA rrset */
@@ -97,11 +102,11 @@ struct val_neg_zone {
 
        /** tree of NSEC data for this zone, sort by NSEC owner name */
        rbtree_t tree;
-       /** the children that have NULL as a parent ptr */
-       struct val_neg_data* child_first, *child_last;
 
        /** class of node; host order */
        uint16_t dclass;
+       /** if this element is in use */
+       uint8_t in_use;
 };
 
 /**
@@ -119,11 +124,11 @@ struct val_neg_data {
 
        /** pointer to parent node in the negative cache */
        struct val_neg_data* parent;
-       /** linked list of items that have this one as parent, children */
-       struct val_neg_data* child_first, *child_last;
-       /** next and previous siblings in the list of childprent with the
-        * same value for the parent pointer */
-       struct val_neg_data* sibling_next, *sibling_prev;
+
+       /** the number of elements, including this one and the ones whose
+        * parents (-parents) include this one, that are in use 
+        * No elements have a count of zero, those are removed. */
+       int count;
 
        /** the zone that this denial is part of */
        struct val_neg_zone* zone;
@@ -135,6 +140,9 @@ struct val_neg_data {
 
        /** hash of denial rrset: owner name, NSEC, class, for rrset lookup*/
        hashvalue_t nsec_hash;
+
+       /** if this element is in use */
+       uint8_t in_use;
 };
 
 /**