diff options
Diffstat (limited to 'external/unbound/testcode/unitneg.c')
-rw-r--r-- | external/unbound/testcode/unitneg.c | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/external/unbound/testcode/unitneg.c b/external/unbound/testcode/unitneg.c new file mode 100644 index 000000000..d39684095 --- /dev/null +++ b/external/unbound/testcode/unitneg.c @@ -0,0 +1,543 @@ +/* + * testcode/unitneg.c - unit test for negative cache routines. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Calls negative cache unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "testcode/unitmain.h" +#include "validator/val_neg.h" +#include "ldns/rrdef.h" + +/** verbose unit test for negative cache */ +static int negverbose = 0; + +/** debug printout of neg cache */ +static void print_neg_cache(struct val_neg_cache* neg) +{ + char buf[1024]; + struct val_neg_zone* z; + struct val_neg_data* d; + printf("neg_cache print\n"); + printf("memuse %d of %d\n", (int)neg->use, (int)neg->max); + printf("maxiter %d\n", (int)neg->nsec3_max_iter); + printf("%d zones\n", (int)neg->tree.count); + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + dname_str(z->name, buf); + printf("%24s", buf); + printf(" len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n", + (int)z->len, z->labs, (int)z->in_use, z->count, + (int)z->tree.count); + } + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + printf("\n"); + dname_print(stdout, NULL, z->name); + printf(" zone details\n"); + printf("len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n", + (int)z->len, z->labs, (int)z->in_use, z->count, + (int)z->tree.count); + if(z->parent) { + printf("parent="); + dname_print(stdout, NULL, z->parent->name); + printf("\n"); + } else { + printf("parent=NULL\n"); + } + + RBTREE_FOR(d, struct val_neg_data*, &z->tree) { + dname_str(d->name, buf); + printf("%24s", buf); + printf(" len=%2.2d labs=%d inuse=%d count=%d\n", + (int)d->len, d->labs, (int)d->in_use, d->count); + } + } +} + +/** get static pointer to random zone name */ +static char* get_random_zone(void) +{ + static char zname[256]; + int labels = random() % 3; + int i; + char* p = zname; + int labnum; + + for(i=0; i<labels; i++) { + labnum = random()%10; + snprintf(p, 256-(p-zname), "\003%3.3d", labnum); + p+=4; + } + snprintf(p, 256-(p-zname), "\007example\003com"); + return zname; +} + +/** get static pointer to random data names from and to */ +static void get_random_data(char** fromp, char** top, char* zname) +{ + static char buf1[256], buf2[256]; + int type; + int lab1, lab2; + int labnum1[10], labnum2[10]; + int i; + char* p; + + *fromp = buf1; + *top = buf2; + type = random()%10; + + if(type == 0) { + /* ENT */ + lab1 = random() %3 + 1; + lab2 = lab1 + random()%3 + 1; + for(i=0; i<lab1; i++) { + labnum1[i] = random()%100; + labnum2[i] = labnum1[i]; + } + for(i=lab1; i<lab2; i++) { + labnum2[i] = random()%100; + } + } else if(type == 1) { + /* end of zone */ + lab2 = 0; + lab1 = random()%3 + 1; + for(i=0; i<lab1; i++) { + labnum1[i] = random()%100; + } + } else if(type == 2) { + /* start of zone */ + lab1 = 0; + lab2 = random()%3 + 1; + for(i=0; i<lab2; i++) { + labnum2[i] = random()%100; + } + } else { + /* normal item */ + int common = random()%3; + lab1 = random() %3 + 1; + lab2 = random() %3 + 1; + for(i=0; i<common; i++) { + labnum1[i] = random()%100; + labnum2[i] = labnum1[i]; + } + labnum1[common] = random()%100; + labnum2[common] = labnum1[common] + random()%20; + for(i=common; i<lab1; i++) + labnum1[i] = random()%100; + for(i=common; i<lab2; i++) + labnum2[i] = random()%100; + } + + /* construct first */ + p = buf1; + for(i=0; i<lab1; i++) { + snprintf(p, 256-(p-buf1), "\003%3.3d", labnum1[i]); + p+=4; + } + snprintf(p, 256-(p-buf1), "%s", zname); + + /* construct 2nd */ + p = buf2+2; + for(i=0; i<lab2; i++) { + snprintf(p, 256-(p-buf2)-3, "\003%3.3d", labnum2[i]); + p+=4; + } + snprintf(p, 256-(p-buf2)-3, "%s", zname); + buf2[0] = (char)(strlen(buf2+2)+1); + buf2[1] = 0; + + if(negverbose) { + log_nametypeclass(0, "add from", (uint8_t*)buf1, 0, 0); + log_nametypeclass(0, "add to ", (uint8_t*)buf2+2, 0, 0); + } +} + +/** add a random item */ +static void add_item(struct val_neg_cache* neg) +{ + struct val_neg_zone* z; + struct packed_rrset_data rd; + struct ub_packed_rrset_key nsec; + size_t rr_len; + time_t rr_ttl; + uint8_t* rr_data; + char* zname = get_random_zone(); + char* from, *to; + + lock_basic_lock(&neg->lock); + if(negverbose) + log_nametypeclass(0, "add to zone", (uint8_t*)zname, 0, 0); + z = neg_find_zone(neg, (uint8_t*)zname, strlen(zname)+1, + LDNS_RR_CLASS_IN); + if(!z) { + z = neg_create_zone(neg, (uint8_t*)zname, strlen(zname)+1, + LDNS_RR_CLASS_IN); + } + unit_assert(z); + val_neg_zone_take_inuse(z); + + /* construct random NSEC item */ + get_random_data(&from, &to, zname); + + /* create nsec and insert it */ + memset(&rd, 0, sizeof(rd)); + memset(&nsec, 0, sizeof(nsec)); + nsec.rk.dname = (uint8_t*)from; + nsec.rk.dname_len = strlen(from)+1; + nsec.rk.type = htons(LDNS_RR_TYPE_NSEC); + nsec.rk.rrset_class = htons(LDNS_RR_CLASS_IN); + nsec.entry.data = &rd; + rd.security = sec_status_secure; + rd.count = 1; + rd.rr_len = &rr_len; + rr_len = 19; + rd.rr_ttl = &rr_ttl; + rr_ttl = 0; + rd.rr_data = &rr_data; + rr_data = (uint8_t*)to; + + neg_insert_data(neg, z, &nsec); + lock_basic_unlock(&neg->lock); +} + +/** remove a random item */ +static void remove_item(struct val_neg_cache* neg) +{ + int n, i; + struct val_neg_data* d; + rbnode_t* walk; + struct val_neg_zone* z; + + lock_basic_lock(&neg->lock); + if(neg->tree.count == 0) { + lock_basic_unlock(&neg->lock); + return; /* nothing to delete */ + } + + /* pick a random zone */ + walk = rbtree_first(&neg->tree); /* first highest parent, big count */ + z = (struct val_neg_zone*)walk; + n = random() % (int)(z->count); + if(negverbose) + printf("neg stress delete zone %d\n", n); + i=0; + walk = rbtree_first(&neg->tree); + z = (struct val_neg_zone*)walk; + while(i!=n+1 && walk && walk != RBTREE_NULL && !z->in_use) { + walk = rbtree_next(walk); + z = (struct val_neg_zone*)walk; + if(z->in_use) + i++; + } + if(!walk || walk == RBTREE_NULL) { + lock_basic_unlock(&neg->lock); + return; + } + if(!z->in_use) { + lock_basic_unlock(&neg->lock); + return; + } + if(negverbose) + log_nametypeclass(0, "delete zone", z->name, 0, 0); + + /* pick a random nsec item. - that is in use */ + walk = rbtree_first(&z->tree); /* first is highest parent */ + d = (struct val_neg_data*)walk; + n = random() % (int)(d->count); + if(negverbose) + printf("neg stress delete item %d\n", n); + i=0; + walk = rbtree_first(&z->tree); + d = (struct val_neg_data*)walk; + while(i!=n+1 && walk && walk != RBTREE_NULL && !d->in_use) { + walk = rbtree_next(walk); + d = (struct val_neg_data*)walk; + if(d->in_use) + i++; + } + if(!walk || walk == RBTREE_NULL) { + lock_basic_unlock(&neg->lock); + return; + } + if(d->in_use) { + if(negverbose) + log_nametypeclass(0, "neg delete item:", d->name, 0, 0); + neg_delete_data(neg, d); + } + lock_basic_unlock(&neg->lock); +} + +/** sum up the zone trees */ +static size_t sumtrees_all(struct val_neg_cache* neg) +{ + size_t res = 0; + struct val_neg_zone* z; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + res += z->tree.count; + } + return res; +} + +/** sum up the zone trees, in_use only */ +static size_t sumtrees_inuse(struct val_neg_cache* neg) +{ + size_t res = 0; + struct val_neg_zone* z; + struct val_neg_data* d; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + /* get count of highest parent for num in use */ + d = (struct val_neg_data*)rbtree_first(&z->tree); + if(d && (rbnode_t*)d!=RBTREE_NULL) + res += d->count; + } + return res; +} + +/** check if lru is still valid */ +static void check_lru(struct val_neg_cache* neg) +{ + struct val_neg_data* p, *np; + size_t num = 0; + size_t inuse; + p = neg->first; + while(p) { + if(!p->prev) { + unit_assert(neg->first == p); + } + np = p->next; + if(np) { + unit_assert(np->prev == p); + } else { + unit_assert(neg->last == p); + } + num++; + p = np; + } + inuse = sumtrees_inuse(neg); + if(negverbose) + printf("num lru %d, inuse %d, all %d\n", + (int)num, (int)sumtrees_inuse(neg), + (int)sumtrees_all(neg)); + unit_assert( num == inuse); + unit_assert( inuse <= sumtrees_all(neg)); +} + +/** sum up number of items inuse in subtree */ +static int sum_subtree_inuse(struct val_neg_zone* zone, + struct val_neg_data* data) +{ + struct val_neg_data* d; + int num = 0; + RBTREE_FOR(d, struct val_neg_data*, &zone->tree) { + if(dname_subdomain_c(d->name, data->name)) { + if(d->in_use) + num++; + } + } + return num; +} + +/** sum up number of items inuse in subtree */ +static int sum_zone_subtree_inuse(struct val_neg_cache* neg, + struct val_neg_zone* zone) +{ + struct val_neg_zone* z; + int num = 0; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + if(dname_subdomain_c(z->name, zone->name)) { + if(z->in_use) + num++; + } + } + return num; +} + +/** check point in data tree */ +static void check_data(struct val_neg_zone* zone, struct val_neg_data* data) +{ + unit_assert(data->count > 0); + if(data->parent) { + unit_assert(data->parent->count >= data->count); + if(data->parent->in_use) { + unit_assert(data->parent->count > data->count); + } + unit_assert(data->parent->labs == data->labs-1); + /* and parent must be one label shorter */ + unit_assert(data->name[0] == (data->len-data->parent->len-1)); + unit_assert(query_dname_compare(data->name + data->name[0]+1, + data->parent->name) == 0); + } else { + /* must be apex */ + unit_assert(dname_is_root(data->name)); + } + /* tree property: */ + unit_assert(data->count == sum_subtree_inuse(zone, data)); +} + +/** check if tree of data in zone is valid */ +static void checkzonetree(struct val_neg_zone* zone) +{ + struct val_neg_data* d; + + /* check all data in tree */ + RBTREE_FOR(d, struct val_neg_data*, &zone->tree) { + check_data(zone, d); + } +} + +/** check if negative cache is still valid */ +static void check_zone_invariants(struct val_neg_cache* neg, + struct val_neg_zone* zone) +{ + unit_assert(zone->nsec3_hash == 0); + unit_assert(zone->tree.cmp == &val_neg_data_compare); + unit_assert(zone->count != 0); + + if(zone->tree.count == 0) + unit_assert(!zone->in_use); + else { + if(!zone->in_use) { + /* details on error */ + log_nametypeclass(0, "zone", zone->name, 0, 0); + log_err("inuse %d count=%d tree.count=%d", + zone->in_use, zone->count, + (int)zone->tree.count); + if(negverbose) + print_neg_cache(neg); + } + unit_assert(zone->in_use); + } + + if(zone->parent) { + unit_assert(zone->parent->count >= zone->count); + if(zone->parent->in_use) { + unit_assert(zone->parent->count > zone->count); + } + unit_assert(zone->parent->labs == zone->labs-1); + /* and parent must be one label shorter */ + unit_assert(zone->name[0] == (zone->len-zone->parent->len-1)); + unit_assert(query_dname_compare(zone->name + zone->name[0]+1, + zone->parent->name) == 0); + } else { + /* must be apex */ + unit_assert(dname_is_root(zone->name)); + } + /* tree property: */ + unit_assert(zone->count == sum_zone_subtree_inuse(neg, zone)); + + /* check structure of zone data tree */ + checkzonetree(zone); +} + +/** check if negative cache is still valid */ +static void check_neg_invariants(struct val_neg_cache* neg) +{ + struct val_neg_zone* z; + /* check structure of LRU list */ + lock_basic_lock(&neg->lock); + check_lru(neg); + unit_assert(neg->max == 1024*1024); + unit_assert(neg->nsec3_max_iter == 1500); + unit_assert(neg->tree.cmp == &val_neg_zone_compare); + + if(neg->tree.count == 0) { + /* empty */ + unit_assert(neg->tree.count == 0); + unit_assert(neg->first == NULL); + unit_assert(neg->last == NULL); + unit_assert(neg->use == 0); + lock_basic_unlock(&neg->lock); + return; + } + + unit_assert(neg->first != NULL); + unit_assert(neg->last != NULL); + + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + check_zone_invariants(neg, z); + } + lock_basic_unlock(&neg->lock); +} + +/** perform stress test on insert and delete in neg cache */ +static void stress_test(struct val_neg_cache* neg) +{ + int i; + if(negverbose) + printf("negcache test\n"); + for(i=0; i<100; i++) { + if(random() % 10 < 8) + add_item(neg); + else remove_item(neg); + check_neg_invariants(neg); + } + /* empty it */ + if(negverbose) + printf("neg stress empty\n"); + while(neg->first) { + remove_item(neg); + check_neg_invariants(neg); + } + if(negverbose) + printf("neg stress emptied\n"); + unit_assert(neg->first == NULL); + /* insert again */ + for(i=0; i<100; i++) { + if(random() % 10 < 8) + add_item(neg); + else remove_item(neg); + check_neg_invariants(neg); + } +} + +void neg_test(void) +{ + struct val_neg_cache* neg; + srandom(48); + unit_show_feature("negative cache"); + + /* create with defaults */ + neg = val_neg_create(NULL, 1500); + unit_assert(neg); + + stress_test(neg); + + neg_cache_delete(neg); +} |