/* * 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 "sldns/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[36]; int labels = random() % 3; int i; char* p = zname; int labnum; for(i=0; ilock); 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_type* 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_type*)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); }