diff options
Diffstat (limited to 'edns-subnet/addrtree.c')
| -rw-r--r-- | edns-subnet/addrtree.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/edns-subnet/addrtree.c b/edns-subnet/addrtree.c new file mode 100644 index 000000000000..69ace60549bf --- /dev/null +++ b/edns-subnet/addrtree.c @@ -0,0 +1,531 @@ +/* + * edns-subnet/addrtree.c -- radix tree for edns subnet cache. + * + * Copyright (c) 2013, 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 + * addrtree -- radix tree for edns subnet cache. + */ + +#include "config.h" +#include "util/log.h" +#include "util/data/msgreply.h" +#include "util/module.h" +#include "addrtree.h" + +/** + * Create a new edge + * @param node: Child node this edge will connect to. + * @param addr: full key to this edge. + * @param addrlen: length of relevant part of key for this node + * @param parent_node: Parent node for node + * @param parent_index: Index of child node at parent node + * @return new addredge or NULL on failure + */ +static struct addredge * +edge_create(struct addrnode *node, const addrkey_t *addr, + addrlen_t addrlen, struct addrnode *parent_node, int parent_index) +{ + size_t n; + struct addredge *edge = (struct addredge *)malloc( sizeof (*edge) ); + if (!edge) + return NULL; + edge->node = node; + edge->len = addrlen; + edge->parent_index = parent_index; + edge->parent_node = parent_node; + /* ceil() */ + n = (size_t)((addrlen / KEYWIDTH) + ((addrlen % KEYWIDTH != 0)?1:0)); + edge->str = (addrkey_t *)calloc(n, sizeof (addrkey_t)); + if (!edge->str) { + free(edge); + return NULL; + } + memcpy(edge->str, addr, n * sizeof (addrkey_t)); + /* Only manipulate other objects after successful alloc */ + node->parent_edge = edge; + log_assert(parent_node->edge[parent_index] == NULL); + parent_node->edge[parent_index] = edge; + return edge; +} + +/** + * Create a new node + * @param tree: Tree the node lives in. + * @param elem: Element to store at this node + * @param scope: Scopemask from server reply + * @param ttl: Element is valid up to this time. Absolute, seconds + * @return new addrnode or NULL on failure + */ +static struct addrnode * +node_create(struct addrtree *tree, void *elem, addrlen_t scope, + time_t ttl) +{ + struct addrnode* node = (struct addrnode *)malloc( sizeof (*node) ); + if (!node) + return NULL; + node->elem = elem; + tree->node_count++; + node->scope = scope; + node->ttl = ttl; + node->edge[0] = NULL; + node->edge[1] = NULL; + node->parent_edge = NULL; + node->next = NULL; + node->prev = NULL; + return node; +} + +/** Size in bytes of node and parent edge + * @param tree: tree the node lives in + * @param n: node which size must be calculated + * @return size in bytes. + **/ +static inline size_t +node_size(const struct addrtree *tree, const struct addrnode *n) +{ + return sizeof *n + sizeof *n->parent_edge + n->parent_edge->len + + (n->elem?tree->sizefunc(n->elem):0); +} + +struct addrtree * +addrtree_create(addrlen_t max_depth, void (*delfunc)(void *, void *), + size_t (*sizefunc)(void *), void *env, unsigned int max_node_count) +{ + struct addrtree *tree; + log_assert(delfunc != NULL); + log_assert(sizefunc != NULL); + tree = (struct addrtree *)calloc(1, sizeof(*tree)); + if (!tree) + return NULL; + tree->root = node_create(tree, NULL, 0, 0); + if (!tree->root) { + free(tree); + return NULL; + } + tree->size_bytes = sizeof *tree + sizeof *tree->root; + tree->first = NULL; + tree->last = NULL; + tree->max_depth = max_depth; + tree->delfunc = delfunc; + tree->sizefunc = sizefunc; + tree->env = env; + tree->node_count = 0; + tree->max_node_count = max_node_count; + return tree; +} + +/** + * Scrub a node clean of elem + * @param tree: tree the node lives in. + * @param node: node to be cleaned. + */ +static void +clean_node(struct addrtree *tree, struct addrnode *node) +{ + if (!node->elem) return; + tree->size_bytes -= tree->sizefunc(node->elem); + tree->delfunc(tree->env, node->elem); + node->elem = NULL; +} + +/** Remove specified node from LRU list */ +static void +lru_pop(struct addrtree *tree, struct addrnode *node) +{ + if (node == tree->first) { + if (!node->next) { /* it is the last as well */ + tree->first = NULL; + tree->last = NULL; + } else { + tree->first = node->next; + tree->first->prev = NULL; + } + } else if (node == tree->last) { /* but not the first */ + tree->last = node->prev; + tree->last->next = NULL; + } else { + node->prev->next = node->next; + node->next->prev = node->prev; + } +} + +/** Add node to LRU list as most recently used. */ +static void +lru_push(struct addrtree *tree, struct addrnode *node) +{ + if (!tree->first) { + tree->first = node; + node->prev = NULL; + } else { + tree->last->next = node; + node->prev = tree->last; + } + tree->last = node; + node->next = NULL; +} + +/** Move node to the end of LRU list */ +static void +lru_update(struct addrtree *tree, struct addrnode *node) +{ + if (tree->root == node) return; + lru_pop(tree, node); + lru_push(tree, node); +} + +/** + * Purge a node from the tree. Node and parentedge are cleaned and + * free'd. + * @param tree: Tree the node lives in. + * @param node: Node to be freed + */ +static void +purge_node(struct addrtree *tree, struct addrnode *node) +{ + struct addredge *parent_edge, *child_edge = NULL; + int index; + int keep = node->edge[0] && node->edge[1]; + + clean_node(tree, node); + parent_edge = node->parent_edge; + if (keep || !parent_edge) return; + tree->node_count--; + index = parent_edge->parent_index; + child_edge = node->edge[!node->edge[0]]; + if (child_edge) { + child_edge->parent_node = parent_edge->parent_node; + child_edge->parent_index = index; + } + parent_edge->parent_node->edge[index] = child_edge; + tree->size_bytes -= node_size(tree, node); + free(parent_edge->str); + free(parent_edge); + lru_pop(tree, node); + free(node); +} + +/** + * If a limit is set remove old nodes while above that limit. + * @param tree: Tree to be cleaned up. + */ +static void +lru_cleanup(struct addrtree *tree) +{ + struct addrnode *n, *p; + int children; + if (tree->max_node_count == 0) return; + while (tree->node_count > tree->max_node_count) { + n = tree->first; + if (!n) break; + children = (n->edge[0] != NULL) + (n->edge[1] != NULL); + /** Don't remove this node, it is either the root or we can't + * do without it because it has 2 children */ + if (children == 2 || !n->parent_edge) { + lru_update(tree, n); + continue; + } + p = n->parent_edge->parent_node; + purge_node(tree, n); + /** Since we removed n, n's parent p is eligible for deletion + * if it is not the root node, caries no data and has only 1 + * child */ + children = (p->edge[0] != NULL) + (p->edge[1] != NULL); + if (!p->elem && children == 1 && p->parent_edge) { + purge_node(tree, p); + } + } +} + +inline size_t +addrtree_size(const struct addrtree *tree) +{ + return tree?tree->size_bytes:0; +} + +void addrtree_delete(struct addrtree *tree) +{ + struct addrnode *n; + if (!tree) return; + clean_node(tree, tree->root); + free(tree->root); + tree->size_bytes -= sizeof(struct addrnode); + while ((n = tree->first)) { + tree->first = n->next; + clean_node(tree, n); + tree->size_bytes -= node_size(tree, n); + free(n->parent_edge->str); + free(n->parent_edge); + free(n); + } + log_assert(sizeof *tree == addrtree_size(tree)); + free(tree); +} + +/** + * Get N'th bit from address + * @param addr: address to inspect + * @param addrlen: length of addr in bits + * @param n: index of bit to test. Must be in range [0, addrlen) + * @return 0 or 1 + */ +static int +getbit(const addrkey_t *addr, addrlen_t addrlen, addrlen_t n) +{ + log_assert(addrlen > n); + return (int)(addr[n/KEYWIDTH]>>((KEYWIDTH-1)-(n%KEYWIDTH))) & 1; +} + +/** + * Test for equality on N'th bit. + * @return 0 for equal, 1 otherwise + */ +static inline int +cmpbit(const addrkey_t *key1, const addrkey_t *key2, addrlen_t n) +{ + addrkey_t c = key1[n/KEYWIDTH] ^ key2[n/KEYWIDTH]; + return (int)(c >> ((KEYWIDTH-1)-(n%KEYWIDTH))) & 1; +} + +/** + * Common number of bits in prefix. + * @param s1: first prefix. + * @param l1: length of s1 in bits. + * @param s2: second prefix. + * @param l2: length of s2 in bits. + * @param skip: nr of bits already checked. + * @return common number of bits. + */ +static addrlen_t +bits_common(const addrkey_t *s1, addrlen_t l1, + const addrkey_t *s2, addrlen_t l2, addrlen_t skip) +{ + addrlen_t len, i; + len = (l1 > l2) ? l2 : l1; + log_assert(skip < len); + for (i = skip; i < len; i++) { + if (cmpbit(s1, s2, i)) return i; + } + return len; +} + +/** + * Tests if s1 is a substring of s2 + * @param s1: first prefix. + * @param l1: length of s1 in bits. + * @param s2: second prefix. + * @param l2: length of s2 in bits. + * @param skip: nr of bits already checked. + * @return 1 for substring, 0 otherwise + */ +static int +issub(const addrkey_t *s1, addrlen_t l1, + const addrkey_t *s2, addrlen_t l2, addrlen_t skip) +{ + return bits_common(s1, l1, s2, l2, skip) == l1; +} + +void +addrtree_insert(struct addrtree *tree, const addrkey_t *addr, + addrlen_t sourcemask, addrlen_t scope, void *elem, time_t ttl, + time_t now) +{ + struct addrnode *newnode, *node; + struct addredge *edge; + int index; + addrlen_t common, depth; + + node = tree->root; + log_assert(node != NULL); + + /* Protect our cache against too much fine-grained data */ + if (tree->max_depth < scope) scope = tree->max_depth; + /* Server answer was less specific than question */ + if (scope < sourcemask) sourcemask = scope; + + depth = 0; + while (1) { + log_assert(depth <= sourcemask); + /* Case 1: update existing node */ + if (depth == sourcemask) { + /* update this node's scope and data */ + clean_node(tree, node); + node->ttl = ttl; + node->elem = elem; + node->scope = scope; + tree->size_bytes += tree->sizefunc(elem); + return; + } + index = getbit(addr, sourcemask, depth); + /* Get an edge to an unexpired node */ + edge = node->edge[index]; + while (edge) { + /* Purge all expired nodes on path */ + if (!edge->node->elem || edge->node->ttl >= now) + break; + purge_node(tree, edge->node); + edge = node->edge[index]; + } + /* Case 2: New leafnode */ + if (!edge) { + newnode = node_create(tree, elem, scope, ttl); + if (!newnode) return; + if (!edge_create(newnode, addr, sourcemask, node, + index)) { + clean_node(tree, newnode); + tree->node_count--; + free(newnode); + return; + } + tree->size_bytes += node_size(tree, newnode); + lru_push(tree, newnode); + lru_cleanup(tree); + return; + } + /* Case 3: Traverse edge */ + common = bits_common(edge->str, edge->len, addr, sourcemask, + depth); + if (common == edge->len) { + /* We update the scope of intermediate nodes. Apparently + * the * authority changed its mind. If we would not do + * this we might not be able to reach our new node. */ + node->scope = scope; + depth = edge->len; + node = edge->node; + continue; + } + /* Case 4: split. */ + if (!(newnode = node_create(tree, NULL, 0, 0))) + return; + node->edge[index] = NULL; + if (!edge_create(newnode, addr, common, node, index)) { + node->edge[index] = edge; + clean_node(tree, newnode); + tree->node_count--; + free(newnode); + return; + } + lru_push(tree, newnode); + /* connect existing child to our new node */ + index = getbit(edge->str, edge->len, common); + newnode->edge[index] = edge; + edge->parent_node = newnode; + edge->parent_index = (int)index; + + if (common == sourcemask) { + /* Data is stored in the node */ + newnode->elem = elem; + newnode->scope = scope; + newnode->ttl = ttl; + } + + tree->size_bytes += node_size(tree, newnode); + + if (common != sourcemask) { + /* Data is stored in other leafnode */ + node = newnode; + newnode = node_create(tree, elem, scope, ttl); + if (!edge_create(newnode, addr, sourcemask, node, + index^1)) { + clean_node(tree, newnode); + tree->node_count--; + free(newnode); + return; + } + tree->size_bytes += node_size(tree, newnode); + lru_push(tree, newnode); + } + lru_cleanup(tree); + return; + } +} + +struct addrnode * +addrtree_find(struct addrtree *tree, const addrkey_t *addr, + addrlen_t sourcemask, time_t now) +{ + struct addrnode *node = tree->root; + struct addredge *edge = NULL; + addrlen_t depth = 0; + + log_assert(node != NULL); + while (1) { + /* Current node more specific then question. */ + log_assert(depth <= sourcemask); + /* does this node have data? if yes, see if we have a match */ + if (node->elem && node->ttl >= now) { + /* saved at wrong depth */; + log_assert(node->scope >= depth) + if (depth == node->scope || + (node->scope > sourcemask && + depth == sourcemask)) { + /* Authority indicates it does not have a more + * precise answer or we cannot ask a more + * specific question. */ + lru_update(tree, node); + return node; + } + } + /* This is our final depth, but we haven't found an answer. */ + if (depth == sourcemask) + return NULL; + /* Find an edge to traverse */ + edge = node->edge[getbit(addr, sourcemask, depth)]; + if (!edge || !edge->node) + return NULL; + if (edge->len > sourcemask ) + return NULL; + if (!issub(edge->str, edge->len, addr, sourcemask, depth)) + return NULL; + log_assert(depth < edge->len); + depth = edge->len; + node = edge->node; + } +} + +/** Wrappers for static functions to unit test */ +int unittest_wrapper_addrtree_cmpbit(const addrkey_t *key1, + const addrkey_t *key2, addrlen_t n) { + return cmpbit(key1, key2, n); +} +addrlen_t unittest_wrapper_addrtree_bits_common(const addrkey_t *s1, + addrlen_t l1, const addrkey_t *s2, addrlen_t l2, addrlen_t skip) { + return bits_common(s1, l1, s2, l2, skip); +} +int unittest_wrapper_addrtree_getbit(const addrkey_t *addr, + addrlen_t addrlen, addrlen_t n) { + return getbit(addr, addrlen, n); +} +int unittest_wrapper_addrtree_issub(const addrkey_t *s1, addrlen_t l1, + const addrkey_t *s2, addrlen_t l2, addrlen_t skip) { + return issub(s1, l1, s2, l2, skip); +} |
