From: Victor Julien Date: Wed, 18 May 2022 12:32:35 +0000 (+0200) Subject: radix: implement more compact trees X-Git-Tag: suricata-8.0.0-beta1~697 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8f03a2eb9b0862cbb072cac59dfbfb487e0ba7ef;p=thirdparty%2Fsuricata.git radix: implement more compact trees Implement a more compact set of trees specifically for IPv4 and IPv6 addresses. This allows for more compact data structures and fewer memory allocations. Based on the existing radix tree implementation. --- diff --git a/src/Makefile.am b/src/Makefile.am index b0f841cfd0..665da6e0a5 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -542,6 +542,9 @@ noinst_HEADERS = \ util-profiling-locks.h \ util-proto-name.h \ util-radix-tree.h \ + util-radix-tree-common.h \ + util-radix4-tree.h \ + util-radix6-tree.h \ util-random.h \ util-reference-config.h \ util-rohash.h \ @@ -1089,6 +1092,8 @@ libsuricata_c_a_SOURCES = \ util-profiling-rules.c \ util-proto-name.c \ util-radix-tree.c \ + util-radix4-tree.c \ + util-radix6-tree.c \ util-random.c \ util-reference-config.c \ util-rohash.c \ diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 35780ab101..104f7ea781 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -65,6 +65,8 @@ #include "util-action.h" #include "util-radix-tree.h" +#include "util-radix4-tree.h" +#include "util-radix6-tree.h" #include "util-host-os-info.h" #include "util-cidr.h" #include "util-unittest-helper.h" @@ -172,6 +174,8 @@ static void RegisterUnittests(void) IPPairRegisterUnittests(); SCSigRegisterSignatureOrderingTests(); SCRadixRegisterTests(); + SCRadix4RegisterTests(); + SCRadix6RegisterTests(); DefragRegisterTests(); SigGroupHeadRegisterTests(); SCHInfoRegisterTests(); diff --git a/src/util-radix-tree-common.h b/src/util-radix-tree-common.h new file mode 100644 index 0000000000..8251b42aa5 --- /dev/null +++ b/src/util-radix-tree-common.h @@ -0,0 +1,966 @@ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien + * \author Anoop Saldanha + * + * Implementation of radix trees + */ + +#include "util-validate.h" + +#ifndef ADDRESS_BYTES +#error "define ADDRESS_BYTES" +#endif +#ifndef NETMASK_MAX +#error "define NETMASK_MAX" +#endif + +#define RADIX_BITTEST(x, y) ((x) & (y)) + +/** + * \brief Structure that hold the user data and the netmask associated with it. + */ +typedef struct RadixUserData { + /* holds a pointer to the user data associated with the particular netmask */ + void *user; + /* pointer to the next user data in the list */ + struct RadixUserData *next; + /* holds the netmask value that corresponds to this user data pointer */ + uint8_t netmask; +} RadixUserData; + +/** + * \brief Allocates and returns a new instance of RadixUserData. + * + * \param netmask The netmask entry (cidr) that has to be made in the new + * RadixUserData instance + * \param user The user data that has to be set for the above + * netmask in the newly created RadixUserData instance. + * + * \retval user_data Pointer to a new instance of RadixUserData. + */ +static RadixUserData *AllocUserData(uint8_t netmask, void *user) +{ + RadixUserData *user_data = SCCalloc(1, sizeof(RadixUserData)); + if (unlikely(user_data == NULL)) { + sc_errno = SC_ENOMEM; + return NULL; + } + user_data->netmask = netmask; + user_data->user = user; + return user_data; +} + +/** + * \brief Deallocates an instance of RadixUserData. + * + * \param user_data Pointer to the instance of RadixUserData that has to be + * freed. + */ +static void FreeUserData(RadixUserData *user_data) +{ + SCFree(user_data); +} + +/** + * \brief Appends a user_data instance(RadixUserData) to a + * user_data(RadixUserData) list. We add the new entry in descending + * order with respect to the netmask contained in the RadixUserData. + * + * \param new Pointer to the RadixUserData to be added to the list. + * \param list Pointer to the RadixUserData list head, to which "new" has to + * be appended. + */ +static void AppendToUserDataList(RadixUserData *add, RadixUserData **list) +{ + RadixUserData *temp = NULL; + + BUG_ON(add == NULL || list == NULL); + + /* add to the list in descending order. The reason we do this is for + * optimizing key retrieval for a ip key under a netblock */ + RadixUserData *prev = temp = *list; + while (temp != NULL) { + if (add->netmask > temp->netmask) + break; + prev = temp; + temp = temp->next; + } + + if (temp == *list) { + add->next = *list; + *list = add; + } else { + add->next = prev->next; + prev->next = add; + } +} + +/** + * \brief Adds a netmask and its user_data for a particular prefix stream. + * + * \param prefix The prefix stream to which the netmask and its corresponding + * user data has to be added. + * \param netmask The netmask value (cidr) that has to be added to the prefix. + * \param user The pointer to the user data corresponding to the above + * netmask. + */ +static void AddNetmaskUserDataToNode(RADIX_NODE_TYPE *node, uint8_t netmask, void *user) +{ + BUG_ON(!node); + AppendToUserDataList(AllocUserData(netmask, user), &node->user_data); +} + +/** + * \brief Removes a particular user_data corresponding to a particular netmask + * entry, from a prefix. + * + * \param prefix Pointer to the prefix from which the user_data/netmask entry + * has to be removed. + * \param netmask The netmask value (cidr) whose user_data has to be deleted. + */ +static void RemoveNetmaskUserDataFromNode(RADIX_NODE_TYPE *node, uint8_t netmask) +{ + BUG_ON(!node); + + RadixUserData *temp = NULL, *prev = NULL; + prev = temp = node->user_data; + while (temp != NULL) { + if (temp->netmask == netmask) { + if (temp == node->user_data) + node->user_data = temp->next; + else + prev->next = temp->next; + + FreeUserData(temp); + break; + } + prev = temp; + temp = temp->next; + } +} + +/** + * \brief Indicates if prefix contains an entry for an ip with a specific netmask. + * + * \param prefix Pointer to the ip prefix that is being checked. + * \param netmask The netmask value (cidr) that has to be checked for + * presence in the prefix. + * + * \retval 1 On match. + * \retval 0 On no match. + */ +static int ContainNetmask(RADIX_NODE_TYPE *node, uint8_t netmask) +{ + BUG_ON(!node); + RadixUserData *user_data = node->user_data; + while (user_data != NULL) { + if (user_data->netmask == netmask) + return 1; + user_data = user_data->next; + } + return 0; +} + +/** + * \brief Returns the total netmask count for this prefix. + * + * \param prefix Pointer to the prefix + * + * \retval count The total netmask count for this prefix. + */ +static int NetmaskCount(RADIX_NODE_TYPE *node) +{ + BUG_ON(!node); + uint32_t count = 0; + RadixUserData *user_data = node->user_data; + while (user_data != NULL) { + count++; + user_data = user_data->next; + } + return count; +} + +/** + * \brief Indicates if prefix contains an entry for an ip with a specific netmask + * and if it does, it sets `user_data_result` to the netmask user_data entry. + * + * \param prefix Pointer to the ip prefix that is being checked. + * \param netmask The netmask value for which we will have to return the user_data + * \param exact_match Bool flag which indicates if we should check if the prefix + * holds proper netblock or not. + * \param[out] user_data_result user data pointer + * + * \retval 1 On match. + * \retval 0 On no match. + */ +static int ContainNetmaskAndSetUserData( + RADIX_NODE_TYPE *node, uint8_t netmask, bool exact_match, void **user_data_result) +{ + BUG_ON(!node); + + RadixUserData *user_data = node->user_data; + /* Check if we have a match for an exact ip. An exact ip as in not a proper + * netblock, i.e. an ip with a netmask of 32. */ + if (exact_match) { + if (user_data->netmask == netmask) { + if (user_data_result) + *user_data_result = user_data->user; + return 1; + } else { + goto no_match; + } + } + + /* Check for the user_data entry for this netmask_value */ + while (user_data != NULL) { + if (user_data->netmask == netmask) { + if (user_data_result) + *user_data_result = user_data->user; + return 1; + } + user_data = user_data->next; + } + +no_match: + if (user_data_result != NULL) + *user_data_result = NULL; + return 0; +} + +/** + * \brief Creates a new node for the Radix tree + * + * \retval node The newly created node for the radix tree + */ +static inline RADIX_NODE_TYPE *RadixCreateNode(void) +{ + RADIX_NODE_TYPE *node = NULL; + + if ((node = SCCalloc(1, sizeof(RADIX_NODE_TYPE))) == NULL) { + sc_errno = SC_ENOMEM; + return NULL; + } + node->bit = NETMASK_MAX; + return node; +} + +/** + * \brief Frees a Radix tree node + * + * \param node Pointer to a Radix tree node + * \param tree Pointer to the Radix tree to which this node belongs + */ +static void ReleaseNode( + RADIX_NODE_TYPE *node, RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config) +{ + DEBUG_VALIDATE_BUG_ON(config == NULL); + if (node != NULL) { + RadixUserData *ud = node->user_data; + while (ud != NULL) { + RadixUserData *next = ud->next; + if (config->Free != NULL && ud->user) { + config->Free(ud->user); + } + FreeUserData(ud); + ud = next; + } + SCFree(node); + } +} + +/** + * \brief Internal helper function used by TreeRelease to free a subtree + * + * \param node Pointer to the root of the subtree that has to be freed + * \param tree Pointer to the Radix tree to which this subtree belongs + */ +static void ReleaseSubtree( + RADIX_NODE_TYPE *node, RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config) +{ + DEBUG_VALIDATE_BUG_ON(config == NULL); + if (node != NULL) { + ReleaseSubtree(node->left, tree, config); + ReleaseSubtree(node->right, tree, config); + ReleaseNode(node, tree, config); + } +} + +/** + * \brief frees a Radix tree and all its nodes + * + * \param tree Pointer to the Radix tree that has to be freed + */ +static void TreeRelease(RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config) +{ + DEBUG_VALIDATE_BUG_ON(config == NULL); + if (tree == NULL) + return; + + ReleaseSubtree(tree->head, tree, config); + tree->head = NULL; + return; +} + +/** + * \brief Adds a key to the Radix tree. Used internally by the API. + * + * \param tree Pointer to the Radix tree + * \param key_stream Data that has to added to the Radix tree + * \param netmask The netmask (cidr) + * \param user Pointer to the user data that has to be associated with + * this key + * \param exclusive True if the node should be added iff it doesn't exist. + * + * \retval node Pointer to the newly created node + */ +static RADIX_NODE_TYPE *AddKey(RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config, + const uint8_t *key_stream, uint8_t netmask, void *user, const bool exclusive) +{ + DEBUG_VALIDATE_BUG_ON(config == NULL); + RADIX_NODE_TYPE *node = NULL; + RADIX_NODE_TYPE *parent = NULL; + RADIX_NODE_TYPE *bottom_node = NULL; + + uint8_t tmp_stream[ADDRESS_BYTES]; + memcpy(tmp_stream, key_stream, sizeof(tmp_stream)); + + if (tree == NULL) { + SCLogError("Argument \"tree\" NULL"); + sc_errno = SC_EINVAL; + return NULL; + } + + /* chop the ip address against a netmask */ + MaskIPNetblock(tmp_stream, netmask, NETMASK_MAX); + + /* the very first element in the radix tree */ + if (tree->head == NULL) { + node = RadixCreateNode(); + if (node == NULL) + return NULL; + memcpy(node->prefix_stream, tmp_stream, sizeof(tmp_stream)); + node->has_prefix = true; + node->user_data = AllocUserData(netmask, user); + if (node->user_data == NULL) { + ReleaseNode(node, tree, config); + return NULL; + } + tree->head = node; + if (netmask == NETMASK_MAX) + return node; + + AddNetmaskToMasks(node, netmask); + return node; + } + node = tree->head; + + /* we walk down the tree only when we satisfy 2 conditions. The first one + * being the incoming prefix is shorter than the differ bit of the current + * node. In case we fail in this aspect, we walk down to the tree, till we + * arrive at a node that ends in a prefix */ + while (node->bit < NETMASK_MAX || node->has_prefix == false) { + /* if the bitlen isn't long enough to handle the bit test, we just walk + * down along one of the paths, since either paths should end up with a + * node that has a common prefix whose differ bit is greater than the + * bitlen of the incoming prefix */ + if (NETMASK_MAX <= node->bit) { + if (node->right == NULL) + break; + node = node->right; + } else { + if (RADIX_BITTEST(tmp_stream[node->bit >> 3], (0x80 >> (node->bit % 8)))) { + if (node->right == NULL) + break; + node = node->right; + } else { + if (node->left == NULL) + break; + node = node->left; + } + } + } + + /* we need to keep a reference to the bottom-most node, that actually holds + * the prefix */ + bottom_node = node; + + /* get the first bit position where the ips differ */ + uint8_t check_bit = MIN(node->bit, NETMASK_MAX); + uint8_t differ_bit = 0; + uint8_t j = 0; + for (uint8_t i = 0; (i * 8) < check_bit; i++) { + int temp = 0; + if ((temp = (tmp_stream[i] ^ bottom_node->prefix_stream[i])) == 0) { + differ_bit = (i + 1) * 8; + continue; + } + + /* find out the position where the first bit differs. This method is + * faster, but at the cost of being larger. But with larger caches + * these days we don't have to worry about cache misses */ + temp = temp * 2; + if (temp >= 256) + j = 0; + else if (temp >= 128) + j = 1; + else if (temp >= 64) + j = 2; + else if (temp >= 32) + j = 3; + else if (temp >= 16) + j = 4; + else if (temp >= 8) + j = 5; + else if (temp >= 4) + j = 6; + else if (temp >= 2) + j = 7; + + differ_bit = i * 8 + j; + break; + } + if (check_bit < differ_bit) + differ_bit = check_bit; + + /* walk up the tree till we find the position, to fit our new node in */ + parent = node->parent; + while (parent && differ_bit <= parent->bit) { + node = parent; + parent = node->parent; + } + BUG_ON(differ_bit == NETMASK_MAX && node->bit != NETMASK_MAX); + + /* We already have the node in the tree with the same differing bit position */ + if (differ_bit == NETMASK_MAX && node->bit == NETMASK_MAX) { + if (node->has_prefix) { + /* Check if we already have this netmask entry covered by this prefix */ + if (ContainNetmask(node, netmask)) { + /* Basically we already have this stream prefix, as well as the + * netblock entry for this. A perfect duplicate. */ + if (exclusive) { + SCLogDebug("not inserting since it already exists"); + sc_errno = SC_EEXIST; + return NULL; + } + SCLogDebug("Duplicate entry for this ip address/netblock"); + } else { + /* Basically we already have this stream prefix, but we don't + * have an entry for this particular netmask value for this + * prefix. For example, we have an entry for 192.168.0.0 and + * 192.168.0.0/16 and now we are trying to enter 192.168.0.0/20 */ + AddNetmaskUserDataToNode(node, netmask, user); + + /* if we are adding a netmask of 32 it indicates we are adding + * an exact host ip into the radix tree, in which case we don't + * need to add the netmask value into the tree */ + if (netmask == NETMASK_MAX) + return node; + + /* looks like we have a netmask which is != 32, in which + * case we walk up the tree to insert this netmask value in the + * correct node */ + parent = node->parent; + while (parent != NULL && netmask < (parent->bit + 1)) { + node = parent; + parent = parent->parent; + } + + AddNetmaskToMasks(node, netmask); + if (NetmaskEqualsMask(node, netmask)) { + return node; + } + } + } + return node; + } + + /* create the leaf node for the new key */ + RADIX_NODE_TYPE *new_node = RadixCreateNode(); + if (new_node == NULL) + return NULL; + memcpy(new_node->prefix_stream, tmp_stream, sizeof(tmp_stream)); + new_node->has_prefix = true; + new_node->user_data = AllocUserData(netmask, user); + if (new_node->user_data == NULL) { + ReleaseNode(new_node, tree, config); + return NULL; + } + + /* stick our new_node into the tree. Create a node that holds the + * differing bit position and break the branch. Also handle the + * tranfer of netmasks between node and inter_node(explained in more + * detail below) */ + RADIX_NODE_TYPE *inter_node = RadixCreateNode(); + if (inter_node == NULL) { + ReleaseNode(new_node, tree, config); + return NULL; + } + inter_node->has_prefix = false; + inter_node->bit = differ_bit; + inter_node->parent = node->parent; + SCLogDebug("inter_node: differ_bit %u", differ_bit); + + /* update netmasks for node and set them for inter_node */ + ProcessInternode(node, inter_node); + + if (RADIX_BITTEST(tmp_stream[differ_bit >> 3], (0x80 >> (differ_bit % 8)))) { + inter_node->left = node; + inter_node->right = new_node; + } else { + inter_node->left = new_node; + inter_node->right = node; + } + new_node->parent = inter_node; + + if (node->parent == NULL) + tree->head = inter_node; + else if (node->parent->right == node) + node->parent->right = inter_node; + else + node->parent->left = inter_node; + + node->parent = inter_node; + + /* insert the netmask into the tree */ + if (netmask != NETMASK_MAX) { + node = new_node; + parent = new_node->parent; + while (parent != NULL && netmask < (parent->bit + 1)) { + node = parent; + parent = parent->parent; + } + AddNetmaskToMasks(node, netmask); + } + return new_node; +} + +/** + * \brief Removes a netblock entry from an ip node. The function first + * deletes the netblock/user_data entry for the prefix and then + * removes the netmask entry that has been made in the tree, by + * walking up the tree and deleting the entry from the specific node. + * + * \param node The node from which the netblock entry has to be removed. + * \param netmask The netmask entry (cidr) that has to be removed. + */ +static void RemoveNetblockEntry(RADIX_NODE_TYPE *node, uint8_t netmask) +{ + BUG_ON(!node); + + RemoveNetmaskUserDataFromNode(node, netmask); + + if (netmask == NETMASK_MAX) { + SCLogDebug("%d == %d", netmask, NETMASK_MAX); + return; + } + + RemoveNetmaskFromMasks(node, netmask); + if (node->parent != NULL) + RemoveNetmaskFromMasks(node->parent, netmask); + return; +} + +/** + * \brief Removes a key from the Radix tree + * + * \param key_stream Data that has to be removed from the Radix tree + * \param tree Pointer to the Radix tree from which the key has to be + * removed + */ +static void RemoveKey(RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config, + const uint8_t *key_stream, const uint8_t netmask) +{ + RADIX_NODE_TYPE *node = tree->head; + RADIX_NODE_TYPE *parent = NULL; + RADIX_NODE_TYPE *temp_dest = NULL; + + if (node == NULL) { + SCLogDebug("tree is empty"); + return; + } + + uint8_t tmp_stream[ADDRESS_BYTES]; + memcpy(tmp_stream, key_stream, sizeof(tmp_stream)); + + while (node->bit < NETMASK_MAX) { + if (RADIX_BITTEST(tmp_stream[node->bit >> 3], (0x80 >> (node->bit % 8)))) { + node = node->right; + } else { + node = node->left; + } + + if (node == NULL) { + SCLogDebug("no matching node found"); + return; + } + } + + if (node->bit != NETMASK_MAX || node->has_prefix == false) { + SCLogDebug("node %p bit %d != %d, or not has_prefix %s", node, node->bit, NETMASK_MAX, + node->has_prefix ? "true" : "false"); + return; + } + + if (SCMemcmp(node->prefix_stream, tmp_stream, sizeof(tmp_stream)) == 0) { + if (!ContainNetmask(node, netmask)) { + SCLogDebug("key exists in the tree, but this (%d) " + "netblock entry doesn't exist", + netmask); + return; + } + } else { + SCLogDebug("You are trying to remove a key that doesn't exist in the " + "Radix Tree"); + return; + } + + /* The ip node does exist, and the netblock entry does exist in this node, if + * we have reached this point. If we have more than one netblock entry, it + * indicates we have multiple entries for this key. So we delete that + * particular netblock entry, and make our way out of this function */ + if (NetmaskCount(node) > 1) { // || !NoneNegated(node)) { + RemoveNetblockEntry(node, netmask); + SCLogDebug("NetmaskCount"); + return; + } + SCLogDebug("not netmask cnt"); + + /* we are deleting the root of the tree. This would be the only node left + * in the tree */ + if (tree->head == node) { + ReleaseNode(node, tree, config); + tree->head = NULL; + SCLogDebug("tree->head == node"); + return; + } + + parent = node->parent; + /* parent->parent is not the root of the tree */ + if (parent->parent != NULL) { + if (parent->parent->left == parent) { + if (node->parent->left == node) { + temp_dest = parent->right; + parent->parent->left = parent->right; + parent->right->parent = parent->parent; + } else { + temp_dest = parent->left; + parent->parent->left = parent->left; + parent->left->parent = parent->parent; + } + } else { + if (node->parent->left == node) { + temp_dest = parent->right; + parent->parent->right = parent->right; + parent->right->parent = parent->parent; + } else { + temp_dest = parent->left; + parent->parent->right = parent->left; + parent->left->parent = parent->parent; + } + } + /* parent is the root of the tree */ + } else { + if (parent->left == node) { + temp_dest = tree->head->right; + tree->head->right->parent = NULL; + tree->head = tree->head->right; + } else { + temp_dest = tree->head->left; + tree->head->left->parent = NULL; + tree->head = tree->head->left; + } + } + /* We need to shift the netmask entries from the node that would be + * deleted to its immediate descendant */ + AddNetmasksFromNode(temp_dest, parent); + RemoveNetmaskFromMasks(temp_dest, netmask); + /* release the nodes */ + ReleaseNode(parent, tree, config); + ReleaseNode(node, tree, config); + + SCLogDebug("end (netmask %d)", netmask); + return; +} + +/** + * \brief Checks if an IP prefix falls under a netblock, in the path to the root + * of the tree, from the node. Used internally by FindKey() + * + * \param prefix Pointer to the prefix that contains the ip address + * \param node Pointer to the node from where we have to climb the tree + */ +static inline RADIX_NODE_TYPE *FindKeyIPNetblock(const uint8_t *key_stream, RADIX_NODE_TYPE *node, + void **user_data_result, uint8_t *out_netmask) +{ + while (node != NULL && NetmasksEmpty(node)) + node = node->parent; + if (node == NULL) + return NULL; + + uint8_t tmp_stream[ADDRESS_BYTES]; + memcpy(tmp_stream, key_stream, sizeof(tmp_stream)); + + /* hold the node found containing a netmask. We will need it when we call + * this function recursively */ + RADIX_NODE_TYPE *netmask_node = node; + + for (uint8_t j = 0; j <= NETMASK_MAX; j++) { + uint8_t m = NETMASK_MAX - j; + + if (!(NetmaskIssetInMasks(netmask_node, m))) + continue; + + for (uint8_t i = 0; i < ADDRESS_BYTES; i++) { + uint32_t mask = UINT_MAX; + if (((i + 1) * 8) > m) { + if (((i + 1) * 8 - m) < 8) + mask = UINT_MAX << ((i + 1) * 8 - m); + else + mask = 0; + } + tmp_stream[i] &= mask; + } + + while (node->bit < NETMASK_MAX) { + if (RADIX_BITTEST(tmp_stream[node->bit >> 3], (0x80 >> (node->bit % 8)))) { + node = node->right; + } else { + node = node->left; + } + + if (node == NULL) + return NULL; + } + + if (node->bit != NETMASK_MAX || node->has_prefix == false) + return NULL; + + if (SCMemcmp(node->prefix_stream, tmp_stream, sizeof(tmp_stream)) == 0) { + if (ContainNetmaskAndSetUserData(node, m, false, user_data_result)) { + *out_netmask = m; + return node; + } + } + } + + return FindKeyIPNetblock(tmp_stream, netmask_node->parent, user_data_result, out_netmask); +} + +/** + * \brief Checks if an IP address key is present in the tree. The function + * apart from handling any normal data, also handles ipv4/ipv6 netblocks + * + * \param key_stream Data that has to be found in the Radix tree + * \param tree Pointer to the Radix tree + * \param exact_match The key to be searched is an ip address + */ +static RADIX_NODE_TYPE *FindKey(const RADIX_TREE_TYPE *tree, const uint8_t *key_stream, + const uint8_t netmask, bool exact_match, void **user_data_result, uint8_t *out_netmask) +{ + if (tree == NULL || tree->head == NULL) + return NULL; + + RADIX_NODE_TYPE *node = tree->head; + uint8_t tmp_stream[ADDRESS_BYTES]; + memcpy(tmp_stream, key_stream, sizeof(tmp_stream)); + + while (node->bit < NETMASK_MAX) { + if (RADIX_BITTEST(tmp_stream[node->bit >> 3], (0x80 >> (node->bit % 8)))) { + node = node->right; + } else { + node = node->left; + } + + if (node == NULL) { + return NULL; + } + } + + if (node->bit != NETMASK_MAX || node->has_prefix == false) { + return NULL; + } + + if (SCMemcmp(node->prefix_stream, tmp_stream, sizeof(tmp_stream)) == 0) { + SCLogDebug("stream match"); + if (ContainNetmaskAndSetUserData(node, netmask, true, user_data_result)) { + SCLogDebug("contains netmask etc"); + *out_netmask = netmask; + return node; + } + } + + /* if you are not an ip key, get out of here */ + if (exact_match) { + SCLogDebug("no node found and need exact match, so failed"); + return NULL; + } + + RADIX_NODE_TYPE *ret = FindKeyIPNetblock(tmp_stream, node, user_data_result, out_netmask); + return ret; +} + +/** + * \brief Checks if an IPV4 address is present in the tree + * + * \param key_stream Data that has to be found in the Radix tree. In this case + * an IPV4 address + * \param tree Pointer to the Radix tree instance + */ +static RADIX_NODE_TYPE *FindExactMatch( + const RADIX_TREE_TYPE *tree, const uint8_t *key_stream, void **user_data_result) +{ + uint8_t unused = 0; + return FindKey(tree, key_stream, NETMASK_MAX, true, user_data_result, &unused); +} + +/** + * \brief Checks if an IPV4 address is present in the tree under a netblock + * + * \param key_stream Data that has to be found in the Radix tree. In this case + * an IPV4 address + * \param tree Pointer to the Radix tree instance + */ +static RADIX_NODE_TYPE *FindBestMatch( + const RADIX_TREE_TYPE *tree, const uint8_t *key_stream, void **user_data_result) +{ + uint8_t unused = 0; + return FindKey(tree, key_stream, NETMASK_MAX, false, user_data_result, &unused); +} + +static RADIX_NODE_TYPE *FindBestMatch2(const RADIX_TREE_TYPE *tree, const uint8_t *key_stream, + void **user_data_result, uint8_t *out_netmask) +{ + return FindKey(tree, key_stream, NETMASK_MAX, false, user_data_result, out_netmask); +} + +/** + * \brief Checks if an IPV4 Netblock address is present in the tree + * + * \param key_stream Data that has to be found in the Radix tree. In this case + * an IPV4 netblock address + * \param tree Pointer to the Radix tree instance + */ +static RADIX_NODE_TYPE *FindNetblock(const RADIX_TREE_TYPE *tree, const uint8_t *key_stream, + const uint8_t netmask, void **user_data_result) +{ + uint8_t unused = 0; + RADIX_NODE_TYPE *node = FindKey(tree, key_stream, netmask, true, user_data_result, &unused); + return node; +} + +/** + * \brief Helper function used by PrintTree. Prints the subtree with + * node as the root of the subtree + * + * \param node Pointer to the node that is the root of the subtree to be printed + * \param level Used for indentation purposes + */ +static void PrintSubtree(RADIX_NODE_TYPE *node, int level, void (*PrintData)(void *)) +{ + if (node != NULL) { + PrintNodeInfo(node, level, PrintData); + PrintSubtree(node->left, level + 1, PrintData); + PrintSubtree(node->right, level + 1, PrintData); + } + + return; +} + +/** + * \brief Prints the Radix Tree. While printing the radix tree we use the + * following format + * + * Parent_0 + * Left_Child_1 + * Left_Child_2 + * Right_Child_2 + * Right_Child_1 + * Left_Child_2 + * Right_Child_2 and so on + * + * Each node printed out holds details on the next bit that differs + * amongst its children, and if the node holds a prefix, the perfix is + * printed as well. + * + * \param tree Pointer to the Radix tree that has to be printed + */ +static void PrintTree(RADIX_TREE_TYPE *tree, const RADIX_CONFIG_TYPE *config) +{ + printf("Printing the Radix Tree: \n"); + PrintSubtree(tree->head, 0, config->PrintData); +} + +static bool CompareTreesSub( + RADIX_NODE_TYPE *n1, RADIX_NODE_TYPE *n2, RADIX_TREE_COMPARE_CALLBACK Callback) +{ + // compare nodes + bool n1_has_left = n1->left != NULL; + bool n2_has_left = n2->left != NULL; + if (n1_has_left != n2_has_left) + return false; + + bool n1_has_right = n1->right != NULL; + bool n2_has_right = n2->right != NULL; + if (n1_has_right != n2_has_right) + return false; + + if (SCMemcmp(n1->prefix_stream, n2->prefix_stream, ADDRESS_BYTES) != 0) + return false; + + RadixUserData *u1 = n1->user_data; + RadixUserData *u2 = n2->user_data; + while (1) { + if (u1 == NULL && u2 == NULL) + break; + if ((u1 != NULL && u2 == NULL) || (u1 == NULL && u2 != NULL)) + return false; + if (u1->netmask != u2->netmask) + return false; + + if (Callback != NULL) { + if (Callback(u1->user, u2->user) == false) + return false; + } + + u1 = u1->next; + u2 = u2->next; + } + + if (n1->left && n2->left) + if (CompareTreesSub(n1->left, n2->left, Callback) == false) + return false; + if (n1->right && n2->right) + if (CompareTreesSub(n1->right, n2->right, Callback) == false) + return false; + + return true; +} + +static bool CompareTrees( + const RADIX_TREE_TYPE *t1, const RADIX_TREE_TYPE *t2, RADIX_TREE_COMPARE_CALLBACK Callback) +{ + if (t1->head == NULL && t2->head == NULL) + return true; + if ((t1->head == NULL && t2->head != NULL) || (t1->head != NULL && t2->head == NULL)) + return false; + return CompareTreesSub(t1->head, t2->head, Callback); +} diff --git a/src/util-radix4-tree.c b/src/util-radix4-tree.c new file mode 100644 index 0000000000..30031dc183 --- /dev/null +++ b/src/util-radix4-tree.c @@ -0,0 +1,940 @@ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien + * \author Anoop Saldanha + * + * Implementation of radix tree for IPv4 + */ + +#include "suricata-common.h" +#include "util-debug.h" +#include "util-error.h" +#include "util-ip.h" +#include "util-unittest.h" +#include "util-memcmp.h" +#include "util-print.h" +#include "util-byte.h" +#include "util-radix4-tree.h" + +#define ADDRESS_BYTES (uint8_t)4 +#define NETMASK_MAX (uint8_t)32 + +#define RADIX_TREE_TYPE SCRadix4Tree +#define RADIX_NODE_TYPE SCRadix4Node +#define RADIX_CONFIG_TYPE SCRadix4Config +#define RADIX_TREE_COMPARE_CALLBACK SCRadix4TreeCompareFunc + +static void PrintUserdata(SCRadix4Node *node, int level, void (*PrintData)(void *)); + +static inline void AddNetmaskToMasks(SCRadix4Node *node, int netmask) +{ + SCLogDebug("masks %" PRIX64 ", adding %d/%" PRIX64, (uint64_t)node->masks, netmask, + (uint64_t)BIT_U64(netmask)); + node->masks |= BIT_U64(netmask); + SCLogDebug("masks %" PRIX64, (uint64_t)node->masks); +} + +static inline void RemoveNetmaskFromMasks(SCRadix4Node *node, int netmask) +{ + SCLogDebug("masks %" PRIX64 ", removing %d/%" PRIX64, (uint64_t)node->masks, netmask, + (uint64_t)BIT_U64(netmask)); + node->masks &= ~BIT_U64(netmask); + SCLogDebug("masks %" PRIX64, (uint64_t)node->masks); +} + +static inline void AddNetmasksFromNode(SCRadix4Node *dst, SCRadix4Node *src) +{ + dst->masks |= src->masks; +} + +static inline bool NetmasksEmpty(const SCRadix4Node *node) +{ + return (node->masks == 0); +} + +static inline bool NetmaskEqualsMask(const SCRadix4Node *node, int netmask) +{ + return (node->masks == BIT_U64(netmask)); +} + +static inline bool NetmaskIssetInMasks(const SCRadix4Node *node, int netmask) +{ + return ((node->masks & BIT_U64(netmask)) != 0); +} + +static inline void ProcessInternode(SCRadix4Node *node, SCRadix4Node *inter_node) +{ + const int differ_bit = inter_node->bit; + uint64_t rem = 0; + for (int x = 0; x <= NETMASK_MAX; x++) { + int m = NETMASK_MAX - x; + if (m == differ_bit) + break; + else { + rem |= (node->masks & BIT_U64(m)); + } + } + + inter_node->masks |= node->masks; + inter_node->masks &= ~rem; + node->masks = rem; +} + +/** + * \brief Prints the node information from a Radix4 tree + * + * \param node Pointer to the Radix4 node whose information has to be printed + * \param level Used for indentation purposes + */ +static void PrintNodeInfo(SCRadix4Node *node, int level, void (*PrintData)(void *)) +{ + if (node == NULL) + return; + + for (int i = 0; i < level; i++) + printf(" "); + + printf("%d [", node->bit); + + if (node->masks == 0) { + printf(" - "); + } else { + for (int i = 0, x = 0; i <= 32; i++) { + if (node->masks & BIT_U64(i)) { + printf("%s%d", (x && x < 32) ? ", " : "", i); + x++; + } + } + } + printf("] ("); + + if (node->has_prefix) { + char addr[16] = ""; + PrintInet(AF_INET, &node->prefix_stream, addr, sizeof(addr)); + printf("%s - user_data %p)\n", addr, node->user_data); + PrintUserdata(node, level + 1, PrintData); + } else { + printf("no prefix)\n"); + } + return; +} + +#include "util-radix-tree-common.h" + +SCRadix4Node *SCRadix4TreeFindExactMatch( + const SCRadix4Tree *tree, const uint8_t *key, void **user_data) +{ + return FindExactMatch(tree, key, user_data); +} + +SCRadix4Node *SCRadix4TreeFindNetblock( + const SCRadix4Tree *tree, const uint8_t *key, const uint8_t netmask, void **user_data) +{ + return FindNetblock(tree, key, netmask, user_data); +} + +SCRadix4Node *SCRadix4TreeFindBestMatch( + const SCRadix4Tree *tree, const uint8_t *key, void **user_data) +{ + return FindBestMatch(tree, key, user_data); +} + +SCRadix4Node *SCRadix4TreeFindBestMatch2( + const SCRadix4Tree *tree, const uint8_t *key, void **user_data, uint8_t *out_netmask) +{ + return FindBestMatch2(tree, key, user_data, out_netmask); +} + +SCRadix4Tree SCRadix4TreeInitialize(void) +{ + SCRadix4Tree t = SC_RADIX4_TREE_INITIALIZER; + return t; +} + +void SCRadix4TreeRelease(SCRadix4Tree *tree, const SCRadix4Config *config) +{ + TreeRelease(tree, config); +} + +/** + * \brief Adds a new IPV4 address to the Radix4 tree + * + * \param key_stream Data that has to be added to the Radix4 tree. In this case + * a pointer to an IPV4 address + * \param tree Pointer to the Radix4 tree + * \param user Pointer to the user data that has to be associated with the + * key + * + * \retval node Pointer to the newly created node + */ +SCRadix4Node *SCRadix4AddKeyIPV4( + SCRadix4Tree *tree, const SCRadix4Config *config, const uint8_t *key_stream, void *user) +{ + return AddKey(tree, config, key_stream, 32, user, false); +} + +/** + * \brief Adds a new IPV4 netblock to the Radix4 tree + * + * \param key_stream Data that has to be added to the Radix4 tree. In this case + * a pointer to an IPV4 netblock + * \param tree Pointer to the Radix4 tree + * \param user Pointer to the user data that has to be associated with the + * key + * \param netmask The netmask (cidr) if we are adding a netblock + * + * \retval node Pointer to the newly created node + */ +SCRadix4Node *SCRadix4AddKeyIPV4Netblock(SCRadix4Tree *tree, const SCRadix4Config *config, + const uint8_t *key_stream, uint8_t netmask, void *user) +{ + return AddKey(tree, config, key_stream, netmask, user, false); +} + +/** + * \brief Adds a new IPV4/netblock to the Radix4 tree from a string + * + * \param str IPV4 string with optional /cidr netmask + * \param tree Pointer to the Radix4 tree + * \param user Pointer to the user data that has to be associated with + * the key + * + * \retval bool true if node was added, false otherwise + * + * If the function returns false, `sc_errno` is set: + * - SC_EEXIST: Node already exists + * - SC_EINVAL: Parameter value error + * - SC_ENOMEM: Memory allocation failed + */ +bool SCRadix4AddKeyIPV4String( + SCRadix4Tree *tree, const SCRadix4Config *config, const char *str, void *user) +{ + uint32_t ip; + uint8_t netmask = 32; + char ip_str[32]; /* Max length for full ipv4/mask string with NUL */ + char *mask_str = NULL; + struct in_addr addr; + + /* Make a copy of the string so it can be modified */ + strlcpy(ip_str, str, sizeof(ip_str) - 2); + *(ip_str + (sizeof(ip_str) - 1)) = '\0'; + + /* Does it have a mask? */ + if (NULL != (mask_str = strchr(ip_str, '/'))) { + *(mask_str++) = '\0'; + + /* Dotted type netmask not supported */ + if (strchr(mask_str, '.') != NULL) { + sc_errno = SC_EINVAL; + return false; + } + + uint8_t cidr; + if (StringParseU8RangeCheck(&cidr, 10, 0, (const char *)mask_str, 0, 32) < 0) { + sc_errno = SC_EINVAL; + return false; + } + netmask = (uint8_t)cidr; + } + + /* Validate the IP */ + if (inet_pton(AF_INET, ip_str, &addr) <= 0) { + sc_errno = SC_EINVAL; + return false; + } + ip = addr.s_addr; + + if (AddKey(tree, config, (uint8_t *)&ip, netmask, user, true) == NULL) { + DEBUG_VALIDATE_BUG_ON(sc_errno == SC_OK); + return false; + } + return true; +} + +/** + * \brief Removes an IPV4 address key(not a netblock) from the Radix4 tree. + * Instead of using this function, we can also used + * SCRadix4RemoveKeyIPV4Netblock(), by supplying a netmask value of 32. + * + * \param key_stream Data that has to be removed from the Radix4 tree. In this + * case an IPV4 address + * \param tree Pointer to the Radix4 tree from which the key has to be + * removed + */ +void SCRadix4RemoveKeyIPV4( + SCRadix4Tree *tree, const SCRadix4Config *config, const uint8_t *key_stream) +{ + RemoveKey(tree, config, key_stream, 32); +} + +/** + * \brief Removes an IPV4 address netblock key from the Radix4 tree. + * + * \param key_stream Data that has to be removed from the Radix4 tree. In this + * case an IPV4 address + * \param tree Pointer to the Radix4 tree from which the key has to be + * removed + */ +void SCRadix4RemoveKeyIPV4Netblock(SCRadix4Tree *tree, const SCRadix4Config *config, + const uint8_t *key_stream, uint8_t netmask) +{ + SCLogNotice("removing with netmask %u", netmask); + RemoveKey(tree, config, key_stream, netmask); +} + +void SCRadix4PrintTree(SCRadix4Tree *tree, const SCRadix4Config *config) +{ + PrintTree(tree, config); +} + +static void PrintUserdata(SCRadix4Node *node, int level, void (*PrintData)(void *)) +{ + if (PrintData != NULL) { + RadixUserData *ud = node->user_data; + while (ud != NULL) { + for (int i = 0; i < level; i++) + printf(" "); + printf("[%d], ", ud->netmask); + PrintData(ud->user); + ud = ud->next; + } + } else { + RadixUserData *ud = node->user_data; + while (ud != NULL) { + for (int i = 0; i < level; i++) + printf(" "); + printf(" [%d], ", ud->netmask); + ud = ud->next; + } + } +} + +static int SCRadix4ForEachNodeSub( + const SCRadix4Node *node, SCRadix4ForEachNodeFunc Callback, void *data) +{ + BUG_ON(!node); + + /* invoke callback for each stored user data */ + for (RadixUserData *ud = node->user_data; ud != NULL; ud = ud->next) { + if (Callback(node, ud->user, ud->netmask, data) < 0) + return -1; + } + + if (node->left) { + if (SCRadix4ForEachNodeSub(node->left, Callback, data) < 0) + return -1; + } + if (node->right) { + if (SCRadix4ForEachNodeSub(node->right, Callback, data) < 0) + return -1; + } + return 0; +} + +int SCRadix4ForEachNode(const SCRadix4Tree *tree, SCRadix4ForEachNodeFunc Callback, void *data) +{ + if (tree->head == NULL) + return 0; + return SCRadix4ForEachNodeSub(tree->head, Callback, data); +} + +bool SCRadix4CompareTrees( + const SCRadix4Tree *t1, const SCRadix4Tree *t2, SCRadix4TreeCompareFunc Callback) +{ + return CompareTrees(t1, t2, Callback); +} + +/*------------------------------------Unit_Tests------------------------------*/ + +#ifdef UNITTESTS + +static const SCRadix4Config ut_ip_radix4_config = { NULL, NULL }; + +#define GET_IPV4(str) \ + SCLogDebug("setting up %s", (str)); \ + memset(&(sa), 0, sizeof((sa))); \ + FAIL_IF(inet_pton(AF_INET, (str), &(sa).sin_addr) <= 0); + +#define ADD_IPV4(str) \ + GET_IPV4((str)); \ + SCRadix4AddKeyIPV4(&tree, &ut_ip_radix4_config, (uint8_t *)&(sa).sin_addr, NULL); + +#define REM_IPV4(str) \ + GET_IPV4((str)); \ + SCRadix4RemoveKeyIPV4(&tree, &ut_ip_radix4_config, (uint8_t *)&(sa).sin_addr); + +#define ADD_IPV4_MASK(str, cidr) \ + GET_IPV4((str)); \ + SCRadix4AddKeyIPV4Netblock( \ + &tree, &ut_ip_radix4_config, (uint8_t *)&(sa).sin_addr, (cidr), NULL); + +#define REM_IPV4_MASK(str, cidr) \ + GET_IPV4((str)); \ + SCRadix4RemoveKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&(sa).sin_addr, (cidr)); + +static int SCRadix4TestIPV4Insertion03(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + ADD_IPV4("192.168.1.1"); + ADD_IPV4("192.168.1.2"); + ADD_IPV4("192.167.1.3"); + ADD_IPV4("192.167.1.4"); + ADD_IPV4("192.167.1.4"); + + /* test for the existance of a key */ + GET_IPV4("192.168.1.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + /* test for the existance of a key */ + GET_IPV4("192.167.1.4"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + /* continue adding keys */ + ADD_IPV4("220.168.1.2"); + ADD_IPV4("192.168.1.5"); + ADD_IPV4("192.168.1.18"); + + /* test the existence of keys */ + GET_IPV4("192.168.1.3"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("127.234.2.62"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("192.168.1.1"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.168.1.5"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.168.1.2"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + GET_IPV4("192.167.1.3"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.167.1.4"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("220.168.1.2"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.168.1.18"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + + PASS; +} + +static int SCRadix4TestIPV4Removal04(void) +{ + struct sockaddr_in sa; + + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + /* add the keys */ + ADD_IPV4("192.168.1.1"); + ADD_IPV4("192.168.1.2"); + ADD_IPV4("192.167.1.3"); + ADD_IPV4("192.167.1.4"); + ADD_IPV4("220.168.1.2"); + ADD_IPV4("192.168.1.5"); + ADD_IPV4("192.168.1.18"); + + /* remove the keys from the tree */ + REM_IPV4("192.168.1.1"); + REM_IPV4("192.167.1.3"); + REM_IPV4("192.167.1.4"); + REM_IPV4("192.168.1.18"); + + GET_IPV4("192.167.1.1"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.168.1.2"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + REM_IPV4("192.167.1.3"); + REM_IPV4("220.168.1.2"); + + GET_IPV4("192.168.1.5"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.168.1.2"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + REM_IPV4("192.168.1.2"); + REM_IPV4("192.168.1.5"); + + FAIL_IF_NOT_NULL(tree.head); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +static int SCRadix4TestIPV4NetblockInsertion09(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + /* add the keys */ + ADD_IPV4("192.168.1.1"); + ADD_IPV4("192.168.1.2"); + ADD_IPV4("192.167.1.3"); + ADD_IPV4("192.167.1.4"); + ADD_IPV4("220.168.1.2"); + ADD_IPV4("192.168.1.5"); + ADD_IPV4("192.168.1.18"); + + ADD_IPV4_MASK("192.168.0.0", 16); + ADD_IPV4_MASK("192.171.128.0", 24); + ADD_IPV4_MASK("192.171.192.0", 18); + ADD_IPV4_MASK("192.175.0.0", 16); + + /* test for the existance of a key */ + GET_IPV4("192.168.1.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.170.1.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.171.128.145"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.171.64.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.171.191.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.171.224.6"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + GET_IPV4("192.171.224.6"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.175.224.6"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +static int SCRadix4TestIPV4NetblockInsertion10(void) +{ + SCRadix4Node *node[2]; + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + /* add the keys */ + ADD_IPV4_MASK("253.192.0.0", 16); + ADD_IPV4_MASK("253.192.235.0", 24); + ADD_IPV4_MASK("192.167.0.0", 16); + ADD_IPV4("192.167.1.4"); + ADD_IPV4_MASK("220.168.0.0", 16); + ADD_IPV4("253.224.1.5"); + ADD_IPV4_MASK("192.168.0.0", 16); + + GET_IPV4("192.171.128.0"); + node[0] = SCRadix4AddKeyIPV4Netblock( + &tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 24, NULL); + + GET_IPV4("192.171.128.45"); + node[1] = SCRadix4AddKeyIPV4(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, NULL); + + ADD_IPV4_MASK("192.171.0.0", 18); + ADD_IPV4_MASK("192.175.0.0", 16); + + /* test for the existance of a key */ + GET_IPV4("192.171.128.53"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[0]); + + GET_IPV4("192.171.128.45"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[1]); + + GET_IPV4("192.171.128.45"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[1]); + + GET_IPV4("192.171.128.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[0]); + + REM_IPV4_MASK("192.171.128.0", 24); + + GET_IPV4("192.171.128.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.171.127.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + + PASS; +} + +static int SCRadix4TestIPV4NetblockInsertion11(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + /* add the keys */ + ADD_IPV4_MASK("253.192.0.0", 16); + ADD_IPV4_MASK("253.192.235.0", 24); + ADD_IPV4_MASK("192.167.0.0", 16); + ADD_IPV4("192.167.1.4"); + ADD_IPV4_MASK("220.168.0.0", 16); + ADD_IPV4("253.224.1.5"); + ADD_IPV4_MASK("192.168.0.0", 16); + ADD_IPV4_MASK("192.171.128.0", 24); + ADD_IPV4("192.171.128.45"); + ADD_IPV4_MASK("192.171.0.0", 18); + ADD_IPV4_MASK("192.175.0.0", 16); + + GET_IPV4("0.0.0.0"); + SCRadix4Node *node = SCRadix4AddKeyIPV4Netblock( + &tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 0, NULL); + FAIL_IF_NULL(node); + + /* test for the existance of a key */ + GET_IPV4("192.171.128.53"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + GET_IPV4("192.171.128.45"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + GET_IPV4("192.171.128.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + GET_IPV4("192.171.127.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("1.1.1.1"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("192.255.254.25"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("169.255.254.25"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("0.0.0.0"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("253.224.1.5"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != node); + + GET_IPV4("245.63.62.121"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + GET_IPV4("253.224.1.6"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node); + + /* remove node 0.0.0.0 */ + REM_IPV4_MASK("0.0.0.0", 0); + + GET_IPV4("253.224.1.6"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("192.171.127.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("1.1.1.1"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("192.255.254.25"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + GET_IPV4("169.255.254.25"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("0.0.0.0"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +static int SCRadix4TestIPV4NetblockInsertion12(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + SCRadix4Node *node[2]; + + /* add the keys */ + ADD_IPV4_MASK("253.192.0.0", 16); + ADD_IPV4_MASK("253.192.235.0", 24); + ADD_IPV4_MASK("192.167.0.0", 16); + ADD_IPV4("192.167.1.4"); + ADD_IPV4_MASK("220.168.0.0", 16); + ADD_IPV4("253.224.1.5"); + ADD_IPV4_MASK("192.168.0.0", 16); + + GET_IPV4("192.171.128.0"); + node[0] = SCRadix4AddKeyIPV4Netblock( + &tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 24, NULL); + FAIL_IF_NULL(node[0]); + + GET_IPV4("192.171.128.45"); + node[1] = SCRadix4AddKeyIPV4(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, NULL); + FAIL_IF_NULL(node[1]); + + ADD_IPV4_MASK("192.171.0.0", 18); + ADD_IPV4_MASK("225.175.21.228", 32); + + /* test for the existance of a key */ + GET_IPV4("192.171.128.53"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[0]); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("192.171.128.45"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[1]); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[1]); + + GET_IPV4("192.171.128.78"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == node[0]); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("225.175.21.228"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + + GET_IPV4("225.175.21.224"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("225.175.21.229"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + GET_IPV4("225.175.21.230"); + FAIL_IF_NOT(SCRadix4TreeFindExactMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) == NULL); + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + + PASS; +} + +/** + * \test Check that the best match search works for all the + * possible netblocks of a fixed address + */ +static int SCRadix4TestIPV4NetBlocksAndBestSearch16(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + GET_IPV4("192.168.1.1"); + + for (uint32_t i = 0; i <= 32; i++) { + uint32_t *user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = i; + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, i, user); + void *user_data = NULL; + SCRadix4Node *node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != i); + } + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +/** + * \test Check special combinations of netblocks and addresses + * on best search checking the returned userdata + */ +static int SCRadix4TestIPV4NetBlocksAndBestSearch19(void) +{ + struct sockaddr_in sa; + void *user_data = NULL; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + + GET_IPV4("0.0.0.0"); + uint32_t *user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 100; + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 0, user); + + GET_IPV4("192.168.1.15"); + SCRadix4Node *node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + GET_IPV4("177.0.0.0"); + user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 200; + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 8, user); + + GET_IPV4("177.168.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 200); + user_data = NULL; + + GET_IPV4("178.168.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + GET_IPV4("177.168.0.0"); + user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 300; + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 12, user); + + GET_IPV4("177.168.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 300); + user_data = NULL; + + GET_IPV4("177.167.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 300); + user_data = NULL; + + GET_IPV4("177.178.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 200); + user_data = NULL; + + GET_IPV4("197.178.1.15"); + node = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +/** + * \test SCRadix4TestIPV4NetblockInsertion15 insert a node searching on it. + * Should always return true but the purposse of the test is to monitor + * the memory usage to detect memleaks (there was one on searching) + */ +static int SCRadix4TestIPV4NetblockInsertion25(void) +{ + struct sockaddr_in sa; + SCRadix4Tree tree = SCRadix4TreeInitialize(); + ADD_IPV4_MASK("192.168.0.0", 16); + GET_IPV4("192.168.128.53"); + FAIL_IF_NOT(SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, NULL) != NULL); + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +/** + * \test SCRadix4TestIPV4NetblockInsertion26 insert a node searching on it. + * Should always return true but the purposse of the test is to monitor + * the memory usage to detect memleaks (there was one on searching) + */ +static int SCRadix4TestIPV4NetblockInsertion26(void) +{ + SCRadix4Node *tmp = NULL; + struct sockaddr_in sa; + char *str = SCStrdup("Hello1"); + FAIL_IF_NULL(str); + SCRadix4Tree tree = SCRadix4TreeInitialize(); + GET_IPV4("0.0.0.0"); + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 0, str); + str = SCStrdup("Hello2"); + FAIL_IF_NULL(str); + GET_IPV4("176.0.0.1"); + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 5, str); + str = SCStrdup("Hello3"); + FAIL_IF_NULL(str); + GET_IPV4("0.0.0.0"); + SCRadix4AddKeyIPV4Netblock(&tree, &ut_ip_radix4_config, (uint8_t *)&sa.sin_addr, 7, str); + /* test for the existance of a key */ + void *retptr = NULL; + tmp = SCRadix4TreeFindBestMatch(&tree, (uint8_t *)&sa.sin_addr, &retptr); + FAIL_IF_NULL(tmp); + FAIL_IF_NULL(retptr); + FAIL_IF_NOT(strcmp((char *)retptr, "Hello3") == 0); + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + PASS; +} + +static int SCRadix4TestIPV4InsertRemove01(void) +{ + struct sockaddr_in sa; + + SCRadix4Tree tree = SCRadix4TreeInitialize(); + ADD_IPV4_MASK("1.0.0.0", 8); + ADD_IPV4_MASK("1.1.1.0", 24); + ADD_IPV4("1.1.1.1"); + FAIL_IF(tree.head == NULL); + FAIL_IF_NOT(tree.head->bit == 15); + FAIL_IF_NULL(tree.head->left); + FAIL_IF_NOT(tree.head->left->masks == 0); + FAIL_IF_NOT(tree.head->left->bit == 32); + FAIL_IF_NULL(tree.head->right); + FAIL_IF_NOT(tree.head->right->masks == BIT_U64(24)); + FAIL_IF_NOT(tree.head->right->bit == 31); + SCRadix4PrintTree(&tree, &ut_ip_radix4_config); + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + + /* tree after adds/removals */ + tree = SCRadix4TreeInitialize(); + ADD_IPV4_MASK("1.0.0.0", 8); + ADD_IPV4_MASK("1.0.0.0", 10); + ADD_IPV4_MASK("1.0.0.0", 12); + ADD_IPV4_MASK("1.1.0.0", 16); + ADD_IPV4_MASK("1.1.0.0", 18); + ADD_IPV4_MASK("1.1.0.0", 20); + ADD_IPV4_MASK("1.1.1.0", 24); + ADD_IPV4("1.1.1.1"); + REM_IPV4_MASK("1.1.0.0", 20); + REM_IPV4_MASK("1.1.0.0", 18); + REM_IPV4_MASK("1.1.0.0", 16); + REM_IPV4_MASK("1.0.0.0", 12); + REM_IPV4_MASK("1.0.0.0", 10); + FAIL_IF(tree.head == NULL); + FAIL_IF_NOT(tree.head->bit == 15); + FAIL_IF_NULL(tree.head->left); + FAIL_IF_NOT(tree.head->left->masks == 0); + FAIL_IF_NOT(tree.head->left->bit == 32); + FAIL_IF_NULL(tree.head->right); + FAIL_IF_NOT(tree.head->right->masks == BIT_U64(24)); + FAIL_IF_NOT(tree.head->right->bit == 31); + SCRadix4PrintTree(&tree, &ut_ip_radix4_config); + SCRadix4TreeRelease(&tree, &ut_ip_radix4_config); + + PASS; +} +#endif + +void SCRadix4RegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("SCRadix4TestIPV4Insertion03", SCRadix4TestIPV4Insertion03); + UtRegisterTest("SCRadix4TestIPV4Removal04", SCRadix4TestIPV4Removal04); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion09", SCRadix4TestIPV4NetblockInsertion09); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion10", SCRadix4TestIPV4NetblockInsertion10); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion11", SCRadix4TestIPV4NetblockInsertion11); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion12", SCRadix4TestIPV4NetblockInsertion12); + UtRegisterTest( + "SCRadix4TestIPV4NetBlocksAndBestSearch16", SCRadix4TestIPV4NetBlocksAndBestSearch16); + UtRegisterTest( + "SCRadix4TestIPV4NetBlocksAndBestSearch19", SCRadix4TestIPV4NetBlocksAndBestSearch19); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion25", SCRadix4TestIPV4NetblockInsertion25); + UtRegisterTest("SCRadix4TestIPV4NetblockInsertion26", SCRadix4TestIPV4NetblockInsertion26); + UtRegisterTest("SCRadix4TestIPV4InsertRemove01", SCRadix4TestIPV4InsertRemove01); +#endif + return; +} diff --git a/src/util-radix4-tree.h b/src/util-radix4-tree.h new file mode 100644 index 0000000000..966657557a --- /dev/null +++ b/src/util-radix4-tree.h @@ -0,0 +1,119 @@ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien + * Based on util-radix-tree.[ch] by: + * \author Anoop Saldanha + */ + +#ifndef SURICATA_UTIL_RADIX4_TREE_H +#define SURICATA_UTIL_RADIX4_TREE_H + +#include "suricata-common.h" + +struct RadixUserData; + +/** + * \brief Structure for the node in the radix tree + */ +typedef struct SCRadix4Node_ { + /** holds bitmap of netmasks that come under this node in the tree */ + uint64_t masks : 33; + uint64_t pad1 : 31; + + /** the bit position where the bits differ in the nodes children. Used + * to determine the path to be taken during a lookup */ + uint8_t bit; + + /** bool to see if prefix_stream is filled */ + bool has_prefix; + + /** the key that has been stored in the tree */ + uint8_t prefix_stream[4]; + + /** User data that is associated with this key. We need a user data field + * for each netblock value possible since one ip can be associated + * with any of the 32 netblocks. */ + struct RadixUserData *user_data; + + /** the left and the right children of a node */ + struct SCRadix4Node_ *left, *right; + + /** the parent node for this tree */ + struct SCRadix4Node_ *parent; +} SCRadix4Node; + +/** + * \brief Structure for the radix tree + */ +typedef struct SCRadix4Tree_ { + /** the root node in the radix tree */ + SCRadix4Node *head; +} SCRadix4Tree; + +typedef struct SCRadix4Config_ { + void (*Free)(void *); + /** function pointer that is supplied by the user to free the user data + * held by the user field of SCRadix4Node */ + void (*PrintData)(void *); // debug only? +} SCRadix4Config; + +#define SC_RADIX4_TREE_INITIALIZER \ + { \ + .head = NULL \ + } + +SCRadix4Tree SCRadix4TreeInitialize(void); +void SCRadix4TreeRelease(SCRadix4Tree *, const SCRadix4Config *); + +SCRadix4Node *SCRadix4AddKeyIPV4(SCRadix4Tree *, const SCRadix4Config *, const uint8_t *, void *); +SCRadix4Node *SCRadix4AddKeyIPV4Netblock( + SCRadix4Tree *, const SCRadix4Config *, const uint8_t *, uint8_t, void *); +bool SCRadix4AddKeyIPV4String(SCRadix4Tree *, const SCRadix4Config *, const char *, void *); + +void SCRadix4RemoveKeyIPV4Netblock( + SCRadix4Tree *, const SCRadix4Config *, const uint8_t *, uint8_t); +void SCRadix4RemoveKeyIPV4(SCRadix4Tree *, const SCRadix4Config *, const uint8_t *); + +SCRadix4Node *SCRadix4TreeFindExactMatch(const SCRadix4Tree *, const uint8_t *, void **); +SCRadix4Node *SCRadix4TreeFindNetblock( + const SCRadix4Tree *, const uint8_t *, const uint8_t, void **); +SCRadix4Node *SCRadix4TreeFindBestMatch(const SCRadix4Tree *, const uint8_t *, void **); +SCRadix4Node *SCRadix4TreeFindBestMatch2(const SCRadix4Tree *, const uint8_t *, void **, uint8_t *); + +void SCRadix4PrintTree(SCRadix4Tree *, const SCRadix4Config *config); +void SCRadix4PrintNodeInfo(SCRadix4Node *, int, void (*PrintData)(void *)); + +void SCRadix4RegisterTests(void); + +typedef int (*SCRadix4ForEachNodeFunc)( + const SCRadix4Node *node, void *user_data, const uint8_t netmask, void *data); + +int SCRadix4ForEachNode(const SCRadix4Tree *tree, SCRadix4ForEachNodeFunc Callback, void *data); + +/** \brief compare content of 2 user data entries + * \retval true equal + * \retval false not equal + */ +typedef bool (*SCRadix4TreeCompareFunc)(const void *ud1, const void *ud2); +bool SCRadix4CompareTrees( + const SCRadix4Tree *t1, const SCRadix4Tree *t2, SCRadix4TreeCompareFunc Callback); + +#endif /* SURICATA_UTIL_RADIX4_TREE_H */ diff --git a/src/util-radix6-tree.c b/src/util-radix6-tree.c new file mode 100644 index 0000000000..725ffbae14 --- /dev/null +++ b/src/util-radix6-tree.c @@ -0,0 +1,963 @@ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien + * \author Anoop Saldanha + * + * Implementation of radix trees + */ + +#include "suricata-common.h" +#include "util-debug.h" +#include "util-error.h" +#include "util-ip.h" +#include "util-cidr.h" +#include "util-unittest.h" +#include "util-memcmp.h" +#include "util-print.h" +#include "util-byte.h" +#include "util-radix6-tree.h" + +#define ADDRESS_BYTES (uint8_t)16 +#define NETMASK_MAX (uint8_t)128 + +#define RADIX_TREE_TYPE SCRadix6Tree +#define RADIX_NODE_TYPE SCRadix6Node +#define RADIX_TREE_COMPARE_CALLBACK SCRadix6TreeCompareFunc +#define RADIX_CONFIG_TYPE SCRadix6Config + +static void PrintUserdata(SCRadix6Node *node, void (*PrintData)(void *)); + +static inline void AddNetmaskToMasks(SCRadix6Node *node, int netmask) +{ + uint8_t *masks = node->masks; + masks[netmask / 8] |= 1 << (netmask % 8); +} + +static inline void RemoveNetmaskFromMasks(SCRadix6Node *node, int netmask) +{ + uint8_t *masks = node->masks; + masks[netmask / 8] &= ~(1 << (netmask % 8)); +} + +static inline void AddNetmasksFromNode(SCRadix6Node *dst, SCRadix6Node *src) +{ + for (size_t i = 0; i < sizeof(src->masks); i++) { + dst->masks[i] |= src->masks[i]; + } +} + +static inline bool NetmasksEmpty(const SCRadix6Node *node) +{ + for (size_t i = 0; i < sizeof(node->masks); i++) { + if (node->masks[i] != 0) { + return false; + } + } + return true; +} + +static inline bool NetmaskEqualsMask(const SCRadix6Node *node, int netmask) +{ + size_t b = netmask / 8; + + for (size_t i = 0; i < sizeof(node->masks); i++) { + if (i != b && node->masks[i] != 0) + return false; + else if (node->masks[i] != (1 << (netmask % 8))) + return false; + } + return true; +} + +static inline bool NetmaskIssetInMasks(const SCRadix6Node *node, int netmask) +{ + return ((node->masks[netmask / 8] & 1 << (netmask % 8)) != 0); +} + +static inline void ProcessInternode(SCRadix6Node *node, SCRadix6Node *inter_node) +{ + const int differ_bit = inter_node->bit; + uint8_t rem[sizeof(node->masks)]; + memset(rem, 0, sizeof(rem)); + + for (int x = 0; x <= NETMASK_MAX; x++) { + int m = NETMASK_MAX - x; + if (m == differ_bit) + break; + else { + if (NetmaskIssetInMasks(node, m)) + rem[m / 8] |= 1 << (m % 8); + } + } + + AddNetmasksFromNode(inter_node, node); + + for (size_t i = 0; i < sizeof(inter_node->masks); i++) { + inter_node->masks[i] &= ~rem[i]; + } + + memcpy(node->masks, rem, sizeof(node->masks)); +} + +/** + * \brief Prints the node information from a Radix6 tree + * + * \param node Pointer to the Radix6 node whose information has to be printed + * \param level Used for indentation purposes + */ +static void PrintNodeInfo(SCRadix6Node *node, int level, void (*PrintData)(void *)) +{ + if (node == NULL) + return; + for (int i = 0; i < level; i++) + printf(" "); + + printf("%d [", node->bit); + + if (NetmasksEmpty(node)) { + printf(" - "); + } else { + for (int i = 0, x = 0; i <= NETMASK_MAX; i++) { + if (NetmaskIssetInMasks(node, i)) { + printf("%s%d", x ? ", " : "", i); + x++; + } + } + } + printf("] ("); + + if (node->has_prefix) { + char addr[46] = ""; + PrintInet(AF_INET6, &node->prefix_stream, addr, sizeof(addr)); + printf("%s)\t%p", addr, node); + PrintUserdata(node, PrintData); + printf("\n"); + } else { + printf("no prefix) %p\n", node); + } + return; +} + +#include "util-radix-tree-common.h" + +SCRadix6Node *SCRadix6TreeFindExactMatch( + const SCRadix6Tree *tree, const uint8_t *key, void **user_data) +{ + return FindExactMatch(tree, key, user_data); +} + +SCRadix6Node *SCRadix6TreeFindNetblock( + const SCRadix6Tree *tree, const uint8_t *key, const uint8_t netmask, void **user_data) +{ + return FindNetblock(tree, key, netmask, user_data); +} + +SCRadix6Node *SCRadix6TreeFindBestMatch( + const SCRadix6Tree *tree, const uint8_t *key, void **user_data) +{ + return FindBestMatch(tree, key, user_data); +} + +SCRadix6Node *SCRadix6TreeFindBestMatch2( + const SCRadix6Tree *tree, const uint8_t *key, void **user_data, uint8_t *out_netmask) +{ + return FindBestMatch2(tree, key, user_data, out_netmask); +} + +/** + * \brief Adds a new IPV6 address to the Radix6 tree + * + * \param key_stream Data that has to be added to the Radix6 tree. In this case + * a pointer to an IPV6 address + * \param tree Pointer to the Radix6 tree + * \param user Pointer to the user data that has to be associated with the + * key + * + * \retval node Pointer to the newly created node + */ +SCRadix6Node *SCRadix6AddKeyIPV6( + SCRadix6Tree *tree, const SCRadix6Config *config, const uint8_t *key_stream, void *user) +{ + return AddKey(tree, config, key_stream, 128, user, false); +} + +/** + * \brief Adds a new IPV6 netblock to the Radix6 tree + * + * \param key_stream Data that has to be added to the Radix6 tree. In this case + * a pointer to an IPV6 netblock + * \param tree Pointer to the Radix6 tree + * \param user Pointer to the user data that has to be associated with the + * key + * \param netmask The netmask (cidr) if we are adding a netblock + * + * \retval node Pointer to the newly created node + */ +SCRadix6Node *SCRadix6AddKeyIPV6Netblock(SCRadix6Tree *tree, const SCRadix6Config *config, + const uint8_t *key_stream, uint8_t netmask, void *user) +{ + return AddKey(tree, config, key_stream, netmask, user, false); +} + +#if defined(DEBUG_VALIDATION) || defined(UNITTESTS) +static void SCRadix6ValidateIPv6Key(uint8_t *key, const uint8_t netmask) +{ + uint32_t address[4]; + memcpy(&address, key, sizeof(address)); + + uint32_t mask[4]; + memset(&mask, 0, sizeof(mask)); + struct in6_addr mask6; + CIDRGetIPv6(netmask, &mask6); + memcpy(&mask, &mask6.s6_addr, sizeof(mask)); + + uint32_t masked[4]; + masked[0] = address[0] & mask[0]; + masked[1] = address[1] & mask[1]; + masked[2] = address[2] & mask[2]; + masked[3] = address[3] & mask[3]; + + if (memcmp(masked, address, sizeof(masked)) != 0) { + char ostr[64], nstr[64]; + PrintInet(AF_INET6, (void *)&address, ostr, sizeof(ostr)); + PrintInet(AF_INET6, (void *)&masked, nstr, sizeof(nstr)); + SCLogNotice("input %s/%u != expected %s/%u", ostr, netmask, nstr, netmask); + abort(); + } +} +#endif +/** + * \brief Adds a new IPV6/netblock to the Radix6 tree from a string + * + * \param str IPV6 string with optional /cidr netmask + * \param tree Pointer to the Radix6 tree + * \param user Pointer to the user data that has to be associated with + * the key + * + * \retval bool true if node was added, false otherwise + * + * If the function returns false, `sc_errno` is set: + * - SC_EEXIST: Node already exists + * - SC_EINVAL: Parameter value error + * - SC_ENOMEM: Memory allocation failed + */ +bool SCRadix6AddKeyIPV6String( + SCRadix6Tree *tree, const SCRadix6Config *config, const char *str, void *user) +{ + uint8_t netmask = 128; + char ip_str[80] = ""; /* Max length for full ipv6/cidr string with NUL */ + char *mask_str = NULL; + struct in6_addr addr; + + /* Make a copy of the string so it can be modified */ + strlcpy(ip_str, str, sizeof(ip_str)); + + /* Does it have a mask? */ + if (NULL != (mask_str = strchr(ip_str, '/'))) { + *(mask_str++) = '\0'; + + /* Dotted type netmask not valid for ipv6 */ + if (strchr(mask_str, '.') != NULL) { + sc_errno = SC_EINVAL; + return false; + } + + uint8_t cidr; + if (StringParseU8RangeCheck(&cidr, 10, 0, (const char *)mask_str, 0, 128) < 0) { + sc_errno = SC_EINVAL; + return false; + } + netmask = (uint8_t)cidr; + } + + /* Validate the IP */ + if (inet_pton(AF_INET6, ip_str, &addr) <= 0) { + sc_errno = SC_EINVAL; + return false; + } + + if (netmask != 128) { + struct in6_addr maddr; + struct in6_addr mask6, check; + CIDRGetIPv6(netmask, &mask6); + memcpy(&check, &addr, sizeof(check)); + bool diff = false; + for (int i = 0; i < 16; i++) { + maddr.s6_addr[i] = addr.s6_addr[i] & mask6.s6_addr[i]; + diff |= (maddr.s6_addr[i] != check.s6_addr[i]); + } + if (diff) { + char nstr[64]; + PrintInet(AF_INET6, (void *)&maddr.s6_addr, nstr, sizeof(nstr)); + SCLogWarning("adding '%s' as '%s/%u'", str, nstr, netmask); + memcpy(addr.s6_addr, maddr.s6_addr, 16); +#if defined(DEBUG_VALIDATION) || defined(UNITTESTS) + SCRadix6ValidateIPv6Key((uint8_t *)&addr.s6_addr, netmask); +#endif + } + } + + if (AddKey(tree, config, (uint8_t *)&addr.s6_addr, netmask, user, true) == NULL) { + DEBUG_VALIDATE_BUG_ON(sc_errno == SC_OK); + return false; + } + return true; +} + +/** + * \brief Removes an IPV6 address key(not a netblock) from the Radix6 tree. + * Instead of using this function, we can also used + * SCRadix6RemoveKeyIPV6Netblock(), by supplying a netmask value of 32. + * + * \param key_stream Data that has to be removed from the Radix6 tree. In this + * case an IPV6 address + * \param tree Pointer to the Radix6 tree from which the key has to be + * removed + */ +void SCRadix6RemoveKeyIPV6( + SCRadix6Tree *tree, const SCRadix6Config *config, const uint8_t *key_stream) +{ + RemoveKey(tree, config, key_stream, 128); +} + +/** + * \brief Removes an IPV6 address netblock key from the tree. + * + * \param key_stream Data that has to be removed from the tree. In this + * case an IPV6 address with netmask. + * \param tree Pointer to the tree from which the key has to be + * removed + */ +void SCRadix6RemoveKeyIPV6Netblock(SCRadix6Tree *tree, const SCRadix6Config *config, + const uint8_t *key_stream, uint8_t netmask) +{ + RemoveKey(tree, config, key_stream, netmask); +} + +void SCRadix6PrintTree(SCRadix6Tree *tree, const SCRadix6Config *config) +{ + PrintTree(tree, config); +} + +SCRadix6Tree SCRadix6TreeInitialize(void) +{ + SCRadix6Tree t = SC_RADIX6_TREE_INITIALIZER; + return t; +} + +void SCRadix6TreeRelease(SCRadix6Tree *tree, const SCRadix6Config *config) +{ + TreeRelease(tree, config); +} + +static void PrintUserdata(SCRadix6Node *node, void (*PrintData)(void *)) +{ + if (PrintData != NULL) { + RadixUserData *ud = node->user_data; + while (ud != NULL) { + printf("[%d], ", ud->netmask); + PrintData(ud->user); + ud = ud->next; + } + } else { + RadixUserData *ud = node->user_data; + while (ud != NULL) { + printf(" [%d], ", ud->netmask); + ud = ud->next; + } + } +} + +static int SCRadix6ForEachNodeSub( + const SCRadix6Node *node, SCRadix6ForEachNodeFunc Callback, void *data) +{ + BUG_ON(!node); + + /* invoke callback for each stored user data */ + for (RadixUserData *ud = node->user_data; ud != NULL; ud = ud->next) { + if (Callback(node, ud->user, ud->netmask, data) < 0) + return -1; + } + + if (node->left) { + if (SCRadix6ForEachNodeSub(node->left, Callback, data) < 0) + return -1; + } + if (node->right) { + if (SCRadix6ForEachNodeSub(node->right, Callback, data) < 0) + return -1; + } + return 0; +} + +int SCRadix6ForEachNode(const SCRadix6Tree *tree, SCRadix6ForEachNodeFunc Callback, void *data) +{ + if (tree->head == NULL) + return 0; + return SCRadix6ForEachNodeSub(tree->head, Callback, data); +} + +bool SCRadix6CompareTrees( + const SCRadix6Tree *t1, const SCRadix6Tree *t2, SCRadix6TreeCompareFunc Callback) +{ + return CompareTrees(t1, t2, Callback); +} + +/*------------------------------------Unit_Tests------------------------------*/ + +#ifdef UNITTESTS + +static const SCRadix6Config ut_ip_radix6_config = { NULL, NULL }; + +#define GET_IPV6(str) \ + SCLogDebug("setting up %s", (str)); \ + memset(&(sa), 0, sizeof((sa))); \ + FAIL_IF(inet_pton(AF_INET6, (str), &(sa).sin6_addr) <= 0); + +#define ADD_IPV6(str) \ + GET_IPV6((str)); \ + SCRadix6AddKeyIPV6(&tree, &ut_ip_radix6_config, (uint8_t *)&(sa).sin6_addr, NULL); + +#define REM_IPV6(str) \ + GET_IPV6((str)); \ + SCRadix6RemoveKeyIPV6(&tree, &ut_ip_radix6_config, (uint8_t *)&(sa).sin6_addr); + +#define ADD_IPV6_MASK(str, cidr) \ + GET_IPV6((str)); \ + SCRadix6AddKeyIPV6Netblock( \ + &tree, &ut_ip_radix6_config, (uint8_t *)&(sa).sin6_addr, (cidr), NULL); + +#define REM_IPV6_MASK(str, cidr) \ + GET_IPV6((str)); \ + SCRadix6RemoveKeyIPV6Netblock(&tree, &ut_ip_radix6_config, (uint8_t *)&(sa).sin6_addr, (cidr)); + +static int SCRadix6TestIPV6Insertion03(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + ADD_IPV6("2000:1::1"); + ADD_IPV6("2000:1::2"); + ADD_IPV6("2000:0::3"); + ADD_IPV6("2000:0::4"); + ADD_IPV6("2000:0::4"); + + /* test for the existance of a key */ + GET_IPV6("2000:1::6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + /* test for the existance of a key */ + GET_IPV6("2000:0::4"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + /* continue adding keys */ + ADD_IPV6("2000:0::2"); + ADD_IPV6("2000:1::5"); + ADD_IPV6("2000:1::18"); + + /* test the existence of keys */ + GET_IPV6("2000:1::3"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2001:1:2:3::62"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000:1::1"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:1::5"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:1::2"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + GET_IPV6("2000:0::3"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:0::4"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:0::2"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:1::18"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + + PASS; +} + +static int SCRadix6TestIPV6Removal04(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + /* add the keys */ + ADD_IPV6("2000:1::1"); + ADD_IPV6("2000:1::2"); + ADD_IPV6("2000:0::3"); + ADD_IPV6("2000:0::4"); + ADD_IPV6("1000:1::2"); + ADD_IPV6("2000:1::5"); + ADD_IPV6("2000:1::18"); + + /* remove the keys from the tree */ + REM_IPV6("2000:1::1"); + REM_IPV6("2000:0::3"); + REM_IPV6("2000:0::4"); + REM_IPV6("2000:1::18"); + + GET_IPV6("2000:0::1"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000:1::2"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + REM_IPV6("2000:0::3"); + REM_IPV6("1000:1::2"); + + GET_IPV6("2000:1::5"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000:1::2"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + REM_IPV6("2000:1::2"); + REM_IPV6("2000:1::5"); + + FAIL_IF_NOT_NULL(tree.head); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + + PASS; +} + +static int SCRadix6TestIPV6NetblockInsertion09(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + /* add the keys */ + ADD_IPV6("2000::1:1"); + ADD_IPV6("2000::1:2"); + ADD_IPV6("2000::0:3"); + ADD_IPV6("2000::0:4"); + ADD_IPV6("1000::1:2"); + ADD_IPV6("2000::1:5"); + ADD_IPV6("2000::1:18"); + + ADD_IPV6_MASK("2000::", 16); + ADD_IPV6_MASK("2000::192:171:128:0", 128 - 8); + ADD_IPV6_MASK("2000::192:171:192:0", 128 - 14); + ADD_IPV6_MASK("2000::192:175:0:0", 128 - 16); + + /* test for the existance of a key */ + GET_IPV6("2000:1::6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:170:1:6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:171:128:145"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000::192:171:64:6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:171:191:6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:171:224:6"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + GET_IPV6("2000::192:171:224:6"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:175:224:6"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + + PASS; +} + +static int SCRadix6TestIPV6NetblockInsertion10(void) +{ + SCRadix6Node *node[2]; + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + /* add the keys */ + ADD_IPV6_MASK("2000::253:192:0:0", 112); + ADD_IPV6_MASK("2000::253:192:235:0", 112); + ADD_IPV6_MASK("2000::192:167:0:0", 112); + ADD_IPV6("2000:0::4"); + ADD_IPV6_MASK("2000::220:168:0:0", 112); + ADD_IPV6("2000::253:224:1:5"); + ADD_IPV6_MASK("2000::192:168:0:0", 112); + + GET_IPV6("2000::192:171:128:0"); + node[0] = SCRadix6AddKeyIPV6Netblock( + &tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 112, NULL); + + GET_IPV6("2000::192:171:128:45"); + node[1] = SCRadix6AddKeyIPV6(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, NULL); + + ADD_IPV6_MASK("2000::192:171:0:0", 110); + ADD_IPV6_MASK("2000::192:175:0:0", 112); + + /* test for the existance of a key */ + GET_IPV6("2000::192:171:128:53"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[0]); + + GET_IPV6("2000::192:171:128:45"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[1]); + + GET_IPV6("2000::192:171:128:45"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[1]); + + GET_IPV6("2000::192:171:128:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[0]); + + REM_IPV6_MASK("2000::192:171:128:0", 112); + + GET_IPV6("2000::192:171:128:78"); + SCRadix6Node *n = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL); + SCLogNotice("n %p", n); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:171:127:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + + PASS; +} + +static int SCRadix6TestIPV6NetblockInsertion11(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + /* add the keys */ + ADD_IPV6_MASK("2000::253:192:0:0", 96); + ADD_IPV6_MASK("2000::253:192:235:0", 112); + ADD_IPV6_MASK("2000::192:167:0:0", 96); + ADD_IPV6("2000:0::4"); + ADD_IPV6_MASK("2000::220:168:0:0", 96); + ADD_IPV6("2000::253:224:1:5"); + ADD_IPV6_MASK("2000::192:168:0:0", 96); + ADD_IPV6_MASK("2000::192:171:128:0", 112); + ADD_IPV6("2000::192:171:128:45"); + ADD_IPV6_MASK("2000::192:171:0:0", 112); + ADD_IPV6_MASK("2000::192:175:0:0", 96); + + GET_IPV6("::"); + SCRadix6Node *node = SCRadix6AddKeyIPV6Netblock( + &tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 0, NULL); + FAIL_IF_NULL(node); + + /* test for the existance of a key */ + GET_IPV6("2000::192:171:128:53"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + GET_IPV6("2000::192:171:128:45"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + GET_IPV6("2000::192:171:128:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + GET_IPV6("2000::192:171:127:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("2000::1:1:1:1"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("2000::192:255:254:25"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("2000::169:255:254:25"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("::"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("2000::253:224:1:5"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != node); + + GET_IPV6("2000::245:63:62:121"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + GET_IPV6("2000::253:224:1:6"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node); + + /* remove node 0.0.0.0 */ + REM_IPV6_MASK("::", 0); + + GET_IPV6("2000::253:224:1:6"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::192:171:127:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::1:1:1:1"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000::192:255:254:25"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + GET_IPV6("2000::169:255:254:25"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("::"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + + PASS; +} + +static int SCRadix6TestIPV6NetblockInsertion12(void) +{ + struct sockaddr_in6 sa; + SCRadix6Node *node[2]; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + /* add the keys */ + ADD_IPV6_MASK("2000::253:192:0:0", 96); + ADD_IPV6_MASK("2000::253:192:235:0", 112); + ADD_IPV6_MASK("2000::192:167:0:0", 96); + ADD_IPV6("2000:0::4"); + ADD_IPV6_MASK("2000::220:168:0:0", 96); + ADD_IPV6("2000::253:224:1:5"); + ADD_IPV6_MASK("2000::192:168:0:0", 96); + + GET_IPV6("2000::192:171:128:0"); + node[0] = SCRadix6AddKeyIPV6Netblock( + &tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 96, NULL); + FAIL_IF_NULL(node[0]); + + GET_IPV6("2000::192:171:128:45"); + node[1] = SCRadix6AddKeyIPV6(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, NULL); + FAIL_IF_NULL(node[1]); + + ADD_IPV6_MASK("2000::192:171:0:0", 96); + ADD_IPV6_MASK("2000::225:175:21:228", 128); + + /* test for the existance of a key */ + GET_IPV6("2000::192:171:128:53"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[0]); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000::192:171:128:45"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[1]); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[1]); + + GET_IPV6("2000::192:171:128:78"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == node[0]); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000::225:175:21:228"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + + GET_IPV6("2000::225:175:21:224"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000::225:175:21:229"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + GET_IPV6("2000::225:175:21:230"); + FAIL_IF_NOT(SCRadix6TreeFindExactMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) == NULL); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + PASS; +} + +/** + * \test Check that the best match search works for all the + * possible netblocks of a fixed address + */ +static int SCRadix6TestIPV6NetBlocksAndBestSearch16(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + GET_IPV6("2000:1::1"); + for (uint32_t i = 0; i <= 128; i++) { + uint32_t *user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = i; + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, i, user); + void *user_data = NULL; + SCRadix6Node *node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != i); + } + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + PASS; +} + +/** + * \test Check special combinations of netblocks and addresses + * on best search checking the returned userdata + */ +static int SCRadix6TestIPV6NetBlocksAndBestSearch19(void) +{ + struct sockaddr_in6 sa; + void *user_data = NULL; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + GET_IPV6("::"); + uint32_t *user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 100; + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 0, user); + + GET_IPV6("2000:1::15"); + SCRadix6Node *node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + GET_IPV6("2000:177::0:0:0"); + user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 200; + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 64, user); + + GET_IPV6("2000:177::168:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 200); + user_data = NULL; + + GET_IPV6("2000:178::168:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + GET_IPV6("2000:177::168:0:0"); + user = SCMalloc(sizeof(uint32_t)); + FAIL_IF_NULL(user); + *user = 300; + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config, (uint8_t *)&sa.sin6_addr, 92, user); + + GET_IPV6("2000:177::168:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 300); + user_data = NULL; + + GET_IPV6("2000:177::167:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 300); + user_data = NULL; + + GET_IPV6("2000:177::178:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 200); + user_data = NULL; + + GET_IPV6("2000:197::178:1:15"); + node = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &user_data); + FAIL_IF_NULL(node); + FAIL_IF_NULL(user_data); + FAIL_IF(*((uint32_t *)user_data) != 100); + user_data = NULL; + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + PASS; +} + +/** + * \test SCRadix6TestIPV6NetblockInsertion15 insert a node searching on it. + * Should always return true but the purposse of the test is to monitor + * the memory usage to detect memleaks (there was one on searching) + */ +static int SCRadix6TestIPV6NetblockInsertion25(void) +{ + struct sockaddr_in6 sa; + SCRadix6Tree tree = SCRadix6TreeInitialize(); + ADD_IPV6_MASK("2000::192:168:0:0", 16); + GET_IPV6("2000::192:168:128:53"); + FAIL_IF_NOT(SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, NULL) != NULL); + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config); + PASS; +} + +/** + * \test SCRadix6TestIPV6NetblockInsertion26 insert a node searching on it. + * Should always return true but the purposse of the test is to monitor + * the memory usage to detect memleaks (there was one on searching) + */ +static int SCRadix6TestIPV6NetblockInsertion26(void) +{ + SCRadix6Node *tmp = NULL; + struct sockaddr_in6 sa; + const SCRadix6Config ut_ip_radix6_config_26 = { free, NULL }; + + char *str = SCStrdup("Hello1"); + FAIL_IF_NULL(str); + + SCRadix6Tree tree = SCRadix6TreeInitialize(); + + GET_IPV6("::"); + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config_26, (uint8_t *)&sa.sin6_addr, 0, str); + + str = SCStrdup("Hello2"); + FAIL_IF_NULL(str); + + GET_IPV6("2000::176:0:0:1"); + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config_26, (uint8_t *)&sa.sin6_addr, 5, str); + + str = SCStrdup("Hello3"); + FAIL_IF_NULL(str); + + GET_IPV6("::"); + SCRadix6AddKeyIPV6Netblock(&tree, &ut_ip_radix6_config_26, (uint8_t *)&sa.sin6_addr, 7, str); + + /* test for the existance of a key */ + void *retptr = NULL; + tmp = SCRadix6TreeFindBestMatch(&tree, (uint8_t *)&sa.sin6_addr, &retptr); + FAIL_IF_NULL(tmp); + FAIL_IF_NULL(retptr); + FAIL_IF_NOT(strcmp((char *)retptr, "Hello3") == 0); + + SCRadix6TreeRelease(&tree, &ut_ip_radix6_config_26); + + PASS; +} +#endif + +void SCRadix6RegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("SCRadix6TestIPV6Insertion03", SCRadix6TestIPV6Insertion03); + UtRegisterTest("SCRadix6TestIPV6Removal04", SCRadix6TestIPV6Removal04); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion09", SCRadix6TestIPV6NetblockInsertion09); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion10", SCRadix6TestIPV6NetblockInsertion10); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion11", SCRadix6TestIPV6NetblockInsertion11); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion12", SCRadix6TestIPV6NetblockInsertion12); + UtRegisterTest( + "SCRadix6TestIPV6NetBlocksAndBestSearch16", SCRadix6TestIPV6NetBlocksAndBestSearch16); + UtRegisterTest( + "SCRadix6TestIPV6NetBlocksAndBestSearch19", SCRadix6TestIPV6NetBlocksAndBestSearch19); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion25", SCRadix6TestIPV6NetblockInsertion25); + UtRegisterTest("SCRadix6TestIPV6NetblockInsertion26", SCRadix6TestIPV6NetblockInsertion26); +#endif + return; +} diff --git a/src/util-radix6-tree.h b/src/util-radix6-tree.h new file mode 100644 index 0000000000..8df51e2e7b --- /dev/null +++ b/src/util-radix6-tree.h @@ -0,0 +1,117 @@ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien + * Based on util-radix-tree.[ch] by: + * \author Anoop Saldanha + */ + +#ifndef SURICATA_UTIL_RADIX6_TREE_H +#define SURICATA_UTIL_RADIX6_TREE_H + +#include "suricata-common.h" + +struct RadixUserData; + +/** + * \brief Structure for the node in the radix tree + */ +typedef struct SCRadix6Node_ { + /** the key that has been stored in the tree */ + uint8_t prefix_stream[16]; + + /** holds bitmap of netmasks that come under this node in the tree */ + uint8_t masks[17]; + + /** the bit position where the bits differ in the nodes children. Used + * to determine the path to be taken during a lookup */ + uint8_t bit; + /** bool to see if prefix_stream is filled */ + bool has_prefix; + + /** User data that has is associated with this key. We need a user + * data field for each netblock value possible since one ip can be associated + * with any of the 128 netblocks. */ + struct RadixUserData *user_data; + + /** the left and the right children of a node */ + struct SCRadix6Node_ *left, *right; + + /** the parent node for this tree */ + struct SCRadix6Node_ *parent; +} SCRadix6Node; + +/** + * \brief Structure for the radix tree + */ +typedef struct SCRadix6Tree_ { + /** the root node in the radix tree */ + SCRadix6Node *head; +} SCRadix6Tree; + +typedef struct SCRadix6Config_ { + void (*Free)(void *); + /** function pointer that is supplied by the user to free the user data + * held by the user field of SCRadix6Node */ + void (*PrintData)(void *); +} SCRadix6Config; + +#define SC_RADIX6_TREE_INITIALIZER \ + { \ + .head = NULL \ + } + +SCRadix6Tree SCRadix6TreeInitialize(void); +void SCRadix6TreeRelease(SCRadix6Tree *, const SCRadix6Config *); + +SCRadix6Node *SCRadix6AddKeyIPV6(SCRadix6Tree *, const SCRadix6Config *, const uint8_t *, void *); +SCRadix6Node *SCRadix6AddKeyIPV6Netblock( + SCRadix6Tree *, const SCRadix6Config *, const uint8_t *, uint8_t, void *); +bool SCRadix6AddKeyIPV6String(SCRadix6Tree *, const SCRadix6Config *, const char *, void *); + +void SCRadix6RemoveKeyIPV6Netblock( + SCRadix6Tree *, const SCRadix6Config *, const uint8_t *, uint8_t); +void SCRadix6RemoveKeyIPV6(SCRadix6Tree *, const SCRadix6Config *, const uint8_t *); + +SCRadix6Node *SCRadix6TreeFindExactMatch(const SCRadix6Tree *, const uint8_t *, void **); +SCRadix6Node *SCRadix6TreeFindNetblock( + const SCRadix6Tree *, const uint8_t *, const uint8_t, void **); +SCRadix6Node *SCRadix6TreeFindBestMatch(const SCRadix6Tree *, const uint8_t *, void **); +SCRadix6Node *SCRadix6TreeFindBestMatch2(const SCRadix6Tree *, const uint8_t *, void **, uint8_t *); + +void SCRadix6PrintTree(SCRadix6Tree *, const SCRadix6Config *); +void SCRadix6PrintNodeInfo(SCRadix6Node *, int, void (*PrintData)(void *)); + +void SCRadix6RegisterTests(void); + +typedef int (*SCRadix6ForEachNodeFunc)( + const SCRadix6Node *node, void *user_data, const uint8_t netmask, void *data); + +int SCRadix6ForEachNode(const SCRadix6Tree *tree, SCRadix6ForEachNodeFunc Callback, void *data); + +/** \brief compare content of 2 user data entries + * \retval true equal + * \retval false not equal + */ +typedef bool (*SCRadix6TreeCompareFunc)(const void *ud1, const void *ud2); +bool SCRadix6CompareTrees( + const SCRadix6Tree *t1, const SCRadix6Tree *t2, SCRadix6TreeCompareFunc Callback); + +#endif /* SURICATA_UTIL_RADIX4_TREE_H */