diff options
Diffstat (limited to '')
-rw-r--r-- | rtrlib/pfx/pfx.h | 160 | ||||
-rw-r--r-- | rtrlib/pfx/pfx_private.h | 60 | ||||
-rw-r--r-- | rtrlib/pfx/trie/trie-pfx.c | 645 | ||||
-rw-r--r-- | rtrlib/pfx/trie/trie-pfx.h | 73 | ||||
-rw-r--r-- | rtrlib/pfx/trie/trie.c | 235 | ||||
-rw-r--r-- | rtrlib/pfx/trie/trie_private.h | 98 |
6 files changed, 1271 insertions, 0 deletions
diff --git a/rtrlib/pfx/pfx.h b/rtrlib/pfx/pfx.h new file mode 100644 index 0000000..712f416 --- /dev/null +++ b/rtrlib/pfx/pfx.h @@ -0,0 +1,160 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_pfx_h Prefix validation table + * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data + * received from an RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_PFX_H +#define RTR_PFX_H + +#include "rtrlib/lib/ip.h" +#include "rtrlib/pfx/trie/trie-pfx.h" + +#include <inttypes.h> + +/** + * @brief Possible return values for pfx_ functions. + */ +enum pfx_rtvals { + /** Operation was successful. */ + PFX_SUCCESS = 0, + + /** Error occurred. */ + PFX_ERROR = -1, + + /** The supplied pfx_record already exists in the pfx_table. */ + PFX_DUPLICATE_RECORD = -2, + + /** pfx_record wasn't found in the pfx_table. */ + PFX_RECORD_NOT_FOUND = -3 +}; + +/** + * @brief Validation states returned from pfx_validate_origin. + */ +enum pfxv_state { + /** A valid certificate for the pfx_record exists. */ + BGP_PFXV_STATE_VALID, + + /** @brief No certificate for the route exists. */ + BGP_PFXV_STATE_NOT_FOUND, + + /** @brief One or more records that match the input prefix exists in the pfx_table + * but the prefix max_len or ASN doesn't match. + */ + BGP_PFXV_STATE_INVALID +}; + +/** + * @brief A function pointer that is called for each record in the pfx_table. + * @param pfx_record + * @param data forwarded data which the user has passed to pfx_table_for_each_ipv4_record() or + * pfx_table_for_each_ipv6_record() + */ +typedef void (*pfx_for_each_fp)(const struct pfx_record *pfx_record, void *data); + +/** + * @brief Initializes the pfx_table struct. + * @param[in] pfx_table pfx_table that will be initialized. + * @param[in] update_fp A function pointer that will be called if a record was added or removed. + */ +void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp); + +/** + * @brief Frees all memory associated with the pfx_table. + * @param[in] pfx_table pfx_table that will be freed. + */ +void pfx_table_free(struct pfx_table *pfx_table); + +/** + * @brief Adds a pfx_record to a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] pfx_record pfx_record that will be added. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + * @return PFX_DUPLICATE_RECORD If the pfx_record already exists. + */ +int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *pfx_record); + +/** + * @brief Removes a pfx_record from a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] pfx_record Record that will be removed. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + * @return PFX_RECORD_NOT_FOUND If pfx_records couldn't be found. + */ +int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *pfx_record); + +/** + * @brief Removes all entries in the pfx_table that match the passed socket_id value from a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] socket origin socket of the record + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket); + +/** + * @brief Validates the origin of a BGP-Route. + * @param[in] pfx_table pfx_table to use. + * @param[in] asn Autonomous system number of the Origin-AS of the route. + * @param[in] prefix Announced network Prefix. + * @param[in] mask_len Length of the network mask of the announced prefix. + * @param[out] result Result of the validation. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, enum pfxv_state *result); + +/** + * @brief Validates the origin of a BGP-Route and returns a list of pfx_record that decided the result. + * @param[in] pfx_table pfx_table to use. + * @param[out] reason Pointer to a memory area that will be used as array of pfx_records. + * The memory area will be overwritten. Reason must point to NULL or an allocated memory area. + * @param[out] reason_len Size of the array reason. + * @param[in] asn Autonomous system number of the Origin-AS of the route. + * @param[in] prefix Announced network Prefix + * @param[in] mask_len Length of the network mask of the announced prefix + * @param[out] result Result of the validation. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason, unsigned int *reason_len, + const uint32_t asn, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + enum pfxv_state *result); + +/** + * @brief Iterates over all IPv4 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] pfx_table + * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data); + +/** + * @brief Iterates over all IPv6 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] pfx_table + * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data); + +#endif +/** @} */ diff --git a/rtrlib/pfx/pfx_private.h b/rtrlib/pfx/pfx_private.h new file mode 100644 index 0000000..6ec1d8a --- /dev/null +++ b/rtrlib/pfx/pfx_private.h @@ -0,0 +1,60 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_pfx_h Prefix validation table + * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data + * received from an RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_PFX_PRIVATE_H +#define RTR_PFX_PRIVATE_H + +#include "pfx.h" + +#include "rtrlib/lib/ip_private.h" + +#include <stdint.h> + +/** + * @brief Frees all memory associated with the pfx_table without calling the update callback. + * @param[in] pfx_table pfx_table that will be freed. + */ +void pfx_table_free_without_notify(struct pfx_table *pfx_table); + +/** + * @brief Swap root nodes of the argument tables + * @param[in,out] a First table + * @param[in,out] b second table + */ +void pfx_table_swap(struct pfx_table *a, struct pfx_table *b); + +/** + * @brief Copy content of @p src_table into @p dst_table + * @details dst must be empty and initialized + * @param[in] src_table Source table + * @param[out] dst_table Destination table + * @param[in] socket socket which prefixes should not be copied + */ +int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table, + const struct rtr_socket *socket); + +/** + * @brief Notify client about changes between to pfx tables regarding one specific socket + * @details old_table will be modified it should be freed after calling this function + * @param[in] new_table + * @param[in] old_table + * @param[in] socket socket which prefixes should be diffed + */ +void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket); + +#endif +/** @} */ diff --git a/rtrlib/pfx/trie/trie-pfx.c b/rtrlib/pfx/trie/trie-pfx.c new file mode 100644 index 0000000..ffb2416 --- /dev/null +++ b/rtrlib/pfx/trie/trie-pfx.c @@ -0,0 +1,645 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "trie-pfx.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/ip_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/pfx/trie/trie_private.h" +#include "rtrlib/rtrlib_export_private.h" + +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> + +struct data_elem { + uint32_t asn; + uint8_t max_len; + const struct rtr_socket *socket; +}; + +struct node_data { + unsigned int len; + struct data_elem *ary; +}; + +struct copy_cb_args { + struct pfx_table *pfx_table; + const struct rtr_socket *socket; + bool error; +}; + +struct notify_diff_cb_args { + struct pfx_table *old_table; + struct pfx_table *new_table; + const struct rtr_socket *socket; + pfx_update_fp pfx_update_fp; + bool added; +}; + +static struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver); +static int pfx_table_del_elem(struct node_data *data, const unsigned int index); +static int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record); +static int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record); +static struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record, + unsigned int *index); +static bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len); +static void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added); +static int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node, + const struct rtr_socket *socket, const unsigned int level); +static int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record records[], const unsigned int ary_len); +static void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len); + +void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added) +{ + if (pfx_table->update_fp) + pfx_table->update_fp(pfx_table, *record, added); +} + +RTRLIB_EXPORT void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp) +{ + pfx_table->ipv4 = NULL; + pfx_table->ipv6 = NULL; + pfx_table->update_fp = update_fp; + pthread_rwlock_init(&(pfx_table->lock), NULL); +} + +void pfx_table_free_without_notify(struct pfx_table *pfx_table) +{ + pfx_table->update_fp = NULL; + pfx_table_free(pfx_table); +} + +RTRLIB_EXPORT void pfx_table_free(struct pfx_table *pfx_table) +{ + for (int i = 0; i < 2; i++) { + struct trie_node *root = (i == 0 ? pfx_table->ipv4 : pfx_table->ipv6); + + if (root) { + struct trie_node *rm_node; + + pthread_rwlock_wrlock(&(pfx_table->lock)); + do { + struct node_data *data = (struct node_data *)(root->data); + + for (unsigned int j = 0; j < data->len; j++) { + struct pfx_record record = {data->ary[j].asn, (root->prefix), root->len, + data->ary[j].max_len, data->ary[j].socket}; + pfx_table_notify_clients(pfx_table, &record, false); + } + rm_node = (trie_remove(root, &(root->prefix), root->len, 0)); + assert(rm_node); + lrtr_free(((struct node_data *)rm_node->data)->ary); + lrtr_free(rm_node->data); + lrtr_free(rm_node); + } while (rm_node != root); + if (i == 0) + pfx_table->ipv4 = NULL; + else + pfx_table->ipv6 = NULL; + + pthread_rwlock_unlock(&(pfx_table->lock)); + } + } + pthread_rwlock_destroy(&(pfx_table->lock)); +} + +int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record) +{ + struct data_elem *tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * ((data->len) + 1)); + + if (!tmp) + return PFX_ERROR; + data->len++; + data->ary = tmp; + data->ary[data->len - 1].asn = record->asn; + data->ary[data->len - 1].max_len = record->max_len; + data->ary[data->len - 1].socket = record->socket; + return PFX_SUCCESS; +} + +int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record) +{ + int err; + + *node = lrtr_malloc(sizeof(struct trie_node)); + if (!*node) + return PFX_ERROR; + + (*node)->prefix = record->prefix; + (*node)->len = record->min_len; + (*node)->lchild = NULL; + (*node)->rchild = NULL; + (*node)->parent = NULL; + + (*node)->data = lrtr_malloc(sizeof(struct node_data)); + if (!(*node)->data) { + err = PFX_ERROR; + goto free_node; + } + + ((struct node_data *)(*node)->data)->len = 0; + ((struct node_data *)(*node)->data)->ary = NULL; + + err = pfx_table_append_elem(((struct node_data *)(*node)->data), record); + if (err) + goto free_node_data; + + return PFX_SUCCESS; + +free_node_data: + lrtr_free((*node)->data); +free_node: + lrtr_free(*node); + + return err; +} + +struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record, + unsigned int *index) +{ + for (unsigned int i = 0; i < data->len; i++) { + if (data->ary[i].asn == record->asn && data->ary[i].max_len == record->max_len && + data->ary[i].socket == record->socket) { + if (index) + *index = i; + return &(data->ary[i]); + } + } + return NULL; +} + +// returns pfx_table->ipv4 if record version is LRTR_IPV4 else pfx_table->ipv6 +inline struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver) +{ + return ver == LRTR_IPV4 ? pfx_table->ipv4 : pfx_table->ipv6; +} + +int pfx_table_del_elem(struct node_data *data, const unsigned int index) +{ + struct data_elem *tmp; + struct data_elem deleted_elem = data->ary[index]; + + // if index is not the last elem in the list, move all other elems backwards in the array + if (index != data->len - 1) { + for (unsigned int i = index; i < data->len - 1; i++) + data->ary[i] = data->ary[i + 1]; + } + + data->len--; + if (!data->len) { + lrtr_free(data->ary); + data->ary = NULL; + return PFX_SUCCESS; + } + + tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * data->len); + if (!tmp) { + data->ary[data->len] = deleted_elem; + data->len++; + return PFX_ERROR; + } + + data->ary = tmp; + + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *record) +{ + pthread_rwlock_wrlock(&(pfx_table->lock)); + + struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver); + unsigned int lvl = 0; + + if (root) { + bool found; + struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found); + + if (found) { // node with prefix exists + if (pfx_table_find_elem(node->data, record, NULL)) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_DUPLICATE_RECORD; + } + // append record to note_data array + int rtval = pfx_table_append_elem(node->data, record); + + pthread_rwlock_unlock(&pfx_table->lock); + if (rtval == PFX_SUCCESS) + pfx_table_notify_clients(pfx_table, record, true); + return rtval; + } + + // no node with same prefix and prefix_len found + struct trie_node *new_node = NULL; + + if (pfx_table_create_node(&new_node, record) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + trie_insert(node, new_node, lvl); + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_notify_clients(pfx_table, record, true); + return PFX_SUCCESS; + } + + // tree is empty, record will be the root_node + struct trie_node *new_node = NULL; + + if (pfx_table_create_node(&new_node, record) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + if (record->prefix.ver == LRTR_IPV4) + pfx_table->ipv4 = new_node; + else + pfx_table->ipv6 = new_node; + + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_notify_clients(pfx_table, record, true); + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *record) +{ + pthread_rwlock_wrlock(&(pfx_table->lock)); + struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver); + + unsigned int lvl = 0; // tree depth were node was found + bool found; + struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found); + + if (!found) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_RECORD_NOT_FOUND; + } + + unsigned int index; + struct data_elem *elem = pfx_table_find_elem(node->data, record, &index); + + if (!elem) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_RECORD_NOT_FOUND; + } + + struct node_data *ndata = (struct node_data *)node->data; + + if (pfx_table_del_elem(ndata, index) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + + if (ndata->len == 0) { + node = trie_remove(node, &(record->prefix), record->min_len, lvl); + assert(node); + + if (node == root) { + if (record->prefix.ver == LRTR_IPV4) + pfx_table->ipv4 = NULL; + else + pfx_table->ipv6 = NULL; + } + assert(((struct node_data *)node->data)->len == 0); + lrtr_free(node->data); + lrtr_free(node); + } + pthread_rwlock_unlock(&pfx_table->lock); + + pfx_table_notify_clients(pfx_table, record, false); + + return PFX_SUCCESS; +} + +bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len) +{ + for (unsigned int i = 0; i < data->len; i++) { + if (data->ary[i].asn != 0 && data->ary[i].asn == asn && prefix_len <= data->ary[i].max_len) + return true; + } + return false; +} + +int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record *records, const unsigned int ary_len) +{ + struct node_data *data = node->data; + + if (ary_len < data->len) + return PFX_ERROR; + + for (unsigned int i = 0; i < data->len; i++) { + records[i].asn = data->ary[i].asn; + records[i].prefix = node->prefix; + records[i].min_len = node->len; + records[i].max_len = data->ary[i].max_len; + records[i].socket = data->ary[i].socket; + } + return data->len; +} + +inline void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len) +{ + if (reason) { + lrtr_free(*reason); + *reason = NULL; + } + if (reason_len) + *reason_len = 0; +} + +RTRLIB_EXPORT int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason, + unsigned int *reason_len, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t prefix_len, enum pfxv_state *result) +{ + // assert(reason_len == NULL || *reason_len == 0); + // assert(reason == NULL || *reason == NULL); + + pthread_rwlock_rdlock(&(pfx_table->lock)); + struct trie_node *root = pfx_table_get_root(pfx_table, prefix->ver); + + if (!root) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_NOT_FOUND; + pfx_table_free_reason(reason, reason_len); + return PFX_SUCCESS; + } + + unsigned int lvl = 0; + struct trie_node *node = trie_lookup(root, prefix, prefix_len, &lvl); + + if (!node) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_NOT_FOUND; + pfx_table_free_reason(reason, reason_len); + return PFX_SUCCESS; + } + + if (reason_len && reason) { + *reason_len = ((struct node_data *)node->data)->len; + *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record)); + if (!*reason) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + if (pfx_table_node2pfx_record(node, *reason, *reason_len) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + } + + while (!pfx_table_elem_matches(node->data, asn, prefix_len)) { + if (lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits( + prefix, lvl++, + 1))) //post-incr lvl, trie_lookup is performed on child_nodes => parent lvl + 1 + node = trie_lookup(node->lchild, prefix, prefix_len, &lvl); + else + node = trie_lookup(node->rchild, prefix, prefix_len, &lvl); + + if (!node) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_INVALID; + return PFX_SUCCESS; + } + + if (reason_len && reason) { + unsigned int r_len_old = *reason_len; + *reason_len += ((struct node_data *)node->data)->len; + *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record)); + struct pfx_record *start = *reason + r_len_old; + + if (!*reason) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + if (pfx_table_node2pfx_record(node, start, ((struct node_data *)node->data)->len) == + PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + } + } + + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_VALID; + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t prefix_len, enum pfxv_state *result) +{ + return pfx_table_validate_r(pfx_table, NULL, NULL, asn, prefix, prefix_len, result); +} + +RTRLIB_EXPORT int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket) +{ + for (unsigned int i = 0; i < 2; i++) { + struct trie_node **root = (i == 0 ? &(pfx_table->ipv4) : &(pfx_table->ipv6)); + + pthread_rwlock_wrlock(&(pfx_table->lock)); + if (*root) { + int rtval = pfx_table_remove_id(pfx_table, root, *root, socket, 0); + + if (rtval == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + } + pthread_rwlock_unlock(&pfx_table->lock); + } + return PFX_SUCCESS; +} + +int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node, + const struct rtr_socket *socket, const unsigned int level) +{ + assert(node); + assert(root); + assert(*root); + bool check_node = true; + + while (check_node) { + // data from removed node are replaced from data from child nodes (if children exists), + // same node must be checked again if it was replaced with previous child node data + struct node_data *data = node->data; + + for (unsigned int i = 0; i < data->len; i++) { + while (data->len > i && data->ary[i].socket == socket) { + struct pfx_record record = {data->ary[i].asn, node->prefix, node->len, + data->ary[i].max_len, data->ary[i].socket}; + if (pfx_table_del_elem(data, i) == PFX_ERROR) + return PFX_ERROR; + pfx_table_notify_clients(pfx_table, &record, false); + } + } + if (data->len == 0) { + struct trie_node *rm_node = trie_remove(node, &(node->prefix), node->len, level); + + assert(rm_node); + assert(((struct node_data *)rm_node->data)->len == 0); + lrtr_free(((struct node_data *)rm_node->data)); + lrtr_free(rm_node); + + if (rm_node == *root) { + *root = NULL; + return PFX_SUCCESS; + } else if (rm_node == node) { + return PFX_SUCCESS; + } + } else { + check_node = false; + } + } + + if (node->lchild) { + if (pfx_table_remove_id(pfx_table, root, node->lchild, socket, level + 1) == PFX_ERROR) + return PFX_ERROR; + } + if (node->rchild) + return pfx_table_remove_id(pfx_table, root, node->rchild, socket, level + 1); + return PFX_SUCCESS; +} + +static void pfx_table_for_each_rec(struct trie_node *n, pfx_for_each_fp fp, void *data) +{ + struct pfx_record pfxr; + struct node_data *nd; + + assert(n); + assert(fp); + + nd = (struct node_data *)n->data; + assert(nd); + + if (n->lchild) + pfx_table_for_each_rec(n->lchild, fp, data); + + for (unsigned int i = 0; i < nd->len; i++) { + pfxr.asn = nd->ary[i].asn; + pfxr.prefix = n->prefix; + pfxr.min_len = n->len; + pfxr.max_len = nd->ary[i].max_len; + pfxr.socket = nd->ary[i].socket; + fp(&pfxr, data); + } + + if (n->rchild) + pfx_table_for_each_rec(n->rchild, fp, data); +} + +RTRLIB_EXPORT void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data) +{ + assert(pfx_table); + + if (!pfx_table->ipv4) + return; + + pthread_rwlock_rdlock(&(pfx_table->lock)); + pfx_table_for_each_rec(pfx_table->ipv4, fp, data); + pthread_rwlock_unlock(&pfx_table->lock); +} + +RTRLIB_EXPORT void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data) +{ + assert(pfx_table); + + if (!pfx_table->ipv6) + return; + + pthread_rwlock_rdlock(&(pfx_table->lock)); + pfx_table_for_each_rec(pfx_table->ipv6, fp, data); + pthread_rwlock_unlock(&pfx_table->lock); +} + +static void pfx_table_copy_cb(const struct pfx_record *record, void *data) +{ + struct copy_cb_args *args = data; + + if (record->socket != args->socket) { + if (pfx_table_add(args->pfx_table, record) != PFX_SUCCESS) + args->error = true; + } +} + +int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table, + const struct rtr_socket *socket) +{ + struct copy_cb_args args = {dst_table, socket, false}; + + pfx_table_for_each_ipv4_record(src_table, pfx_table_copy_cb, &args); + if (args.error) + return PFX_ERROR; + + pfx_table_for_each_ipv6_record(src_table, pfx_table_copy_cb, &args); + if (args.error) + return PFX_ERROR; + + return PFX_SUCCESS; +} + +void pfx_table_swap(struct pfx_table *a, struct pfx_table *b) +{ + struct trie_node *ipv4_tmp; + struct trie_node *ipv6_tmp; + + pthread_rwlock_wrlock(&(a->lock)); + pthread_rwlock_wrlock(&(b->lock)); + + ipv4_tmp = a->ipv4; + ipv6_tmp = a->ipv6; + + a->ipv4 = b->ipv4; + a->ipv6 = b->ipv6; + + b->ipv4 = ipv4_tmp; + b->ipv6 = ipv6_tmp; + + pthread_rwlock_unlock(&(b->lock)); + pthread_rwlock_unlock(&(a->lock)); +} + +static void pfx_table_notify_diff_cb(const struct pfx_record *record, void *data) +{ + struct notify_diff_cb_args *args = data; + + if (args->socket == record->socket && args->added) { + if (pfx_table_remove(args->old_table, record) != PFX_SUCCESS) + pfx_table_notify_clients(args->new_table, record, args->added); + } else if (args->socket == record->socket && !args->added) { + pfx_table_notify_clients(args->new_table, record, args->added); + } +} + +void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket) +{ + pfx_update_fp old_table_fp; + struct notify_diff_cb_args args = {old_table, new_table, socket, new_table->update_fp, true}; + + // Disable update callback for old_table table + old_table_fp = old_table->update_fp; + old_table->update_fp = NULL; + + // Iterate new_table and try to delete every prefix from the given socket in old_table + // If the prefix could not be removed it was added in new_table and the update cb must be called + pfx_table_for_each_ipv4_record(new_table, pfx_table_notify_diff_cb, &args); + pfx_table_for_each_ipv6_record(new_table, pfx_table_notify_diff_cb, &args); + + // Iterate over old_table table and search and remove remaining prefixes from the socket + // issue a remove notification for every one of them, because they are not present in new_table. + args.added = false; + pfx_table_for_each_ipv4_record(old_table, pfx_table_notify_diff_cb, &args); + pfx_table_for_each_ipv6_record(old_table, pfx_table_notify_diff_cb, &args); + + // Restore original state of old_tables update_fp + old_table->update_fp = old_table_fp; +} diff --git a/rtrlib/pfx/trie/trie-pfx.h b/rtrlib/pfx/trie/trie-pfx.h new file mode 100644 index 0000000..7a8f69a --- /dev/null +++ b/rtrlib/pfx/trie/trie-pfx.h @@ -0,0 +1,73 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_trie_pfx_h Trie + * @ingroup mod_pfx_h + * @brief An implementation of a \ref mod_pfx_h "pfx_table" data structure + * using a shortest prefix first tree (trie) for storing @ref pfx_record "pfx_records". + * @details This implementation uses two separate lpfs-trees, one for IPv4 + * validation records and one for IPv6 records.\n + * See \ref mod_pfx_h "pfx_table" for a list of supported operations of + * this data structure.\n + * + * @{ + */ + +#ifndef RTR_TRIE_PFX +#define RTR_TRIE_PFX + +#include "rtrlib/lib/ip.h" + +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +struct pfx_table; + +/** + * @brief pfx_record. + * @param asn Origin AS number. + * @param prefix IP prefix. + * @param min_len Minimum prefix length. + * @param max_len Maximum prefix length. + * @param socket The rtr_socket that received this record. + */ +struct pfx_record { + uint32_t asn; + struct lrtr_ip_addr prefix; + uint8_t min_len; + uint8_t max_len; + const struct rtr_socket *socket; +}; + +/** + * @brief A function pointer that is called if an record was added to the pfx_table or was removed from the pfx_table. + * @param pfx_table which was updated. + * @param record pfx_record that was modified. + * @param added True if the record was added, false if the record was removed. + */ +typedef void (*pfx_update_fp)(struct pfx_table *pfx_table, const struct pfx_record record, const bool added); + +/** + * @brief pfx_table. + * @param ipv4 + * @param ipv6 + * @param update_fp + * @param lock + */ +struct pfx_table { + struct trie_node *ipv4; + struct trie_node *ipv6; + pfx_update_fp update_fp; + pthread_rwlock_t lock; +}; + +#endif +/** @} */ diff --git a/rtrlib/pfx/trie/trie.c b/rtrlib/pfx/trie/trie.c new file mode 100644 index 0000000..b359892 --- /dev/null +++ b/rtrlib/pfx/trie/trie.c @@ -0,0 +1,235 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "trie_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/ip_private.h" + +#include <assert.h> +#include <stdlib.h> + +static void swap_nodes(struct trie_node *a, struct trie_node *b) +{ + struct trie_node tmp; + + tmp.prefix = a->prefix; + tmp.len = a->len; + tmp.data = a->data; + + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; + + b->prefix = tmp.prefix; + b->len = tmp.len; + b->data = tmp.data; +} + +enum child_node_rel { LEFT, RIGHT }; + +static void add_child_node(struct trie_node *parent, struct trie_node *child, enum child_node_rel rel) +{ + assert(rel == LEFT || rel == RIGHT); + + if (rel == LEFT) + parent->lchild = child; + else + parent->rchild = child; + + child->parent = parent; +} + +static inline bool is_left_child(const struct lrtr_ip_addr *addr, unsigned int lvl) +{ + /* A node must be inserted as left child if bit <lvl> of the IP address + * is 0 otherwise as right child + */ + return lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits(addr, lvl, 1)); +} + +void trie_insert(struct trie_node *root, struct trie_node *new, const unsigned int lvl) +{ + if (new->len < root->len) + swap_nodes(root, new); + + if (is_left_child(&new->prefix, lvl)) { + if (!root->lchild) + return add_child_node(root, new, LEFT); + + return trie_insert(root->lchild, new, lvl + 1); + } + + if (!root->rchild) + return add_child_node(root, new, RIGHT); + + trie_insert(root->rchild, new, lvl + 1); +} + +struct trie_node *trie_lookup(const struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + unsigned int *lvl) +{ + while (root) { + if (root->len <= mask_len && lrtr_ip_addr_equal(lrtr_ip_addr_get_bits(&root->prefix, 0, root->len), + lrtr_ip_addr_get_bits(prefix, 0, root->len))) + return (struct trie_node *)root; + + if (is_left_child(prefix, *lvl)) + root = root->lchild; + else + root = root->rchild; + + (*lvl)++; + } + return NULL; +} + +struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *lvl, bool *found) +{ + *found = false; + + while (root_node) { + if (*lvl > 0 && root_node->len > mask_len) { + (*lvl)--; + return root_node->parent; + } + + if (root_node->len == mask_len && lrtr_ip_addr_equal(root_node->prefix, *prefix)) { + *found = true; + return root_node; + } + + if (is_left_child(prefix, *lvl)) { + if (!root_node->lchild) + return root_node; + root_node = root_node->lchild; + } else { + if (!root_node->rchild) + return root_node; + root_node = root_node->rchild; + } + + (*lvl)++; + } + return NULL; +} + +static void deref_node(struct trie_node *n) +{ + if (!n->parent) + return; + + if (n->parent->lchild == n) { + n->parent->lchild = NULL; + return; + } + + n->parent->rchild = NULL; +} + +static inline bool prefix_is_same(const struct trie_node *n, const struct lrtr_ip_addr *p, uint8_t mask_len) +{ + return n->len == mask_len && lrtr_ip_addr_equal(n->prefix, *p); +} + +static void replace_node_data(struct trie_node *a, struct trie_node *b) +{ + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; +} + +struct trie_node *trie_remove(struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + const unsigned int lvl) +{ + /* If the node has no children we can simply remove it + * If the node has children, we swap the node with the child that + * has the smaller prefix length and drop the child. + */ + if (prefix_is_same(root, prefix, mask_len)) { + void *tmp; + + if (trie_is_leaf(root)) { + deref_node(root); + return root; + } + + /* swap with the left child and drop the child */ + if (root->lchild && (!root->rchild || root->lchild->len < root->rchild->len)) { + tmp = root->data; + replace_node_data(root, root->lchild); + root->lchild->data = tmp; + + return trie_remove(root->lchild, &root->lchild->prefix, root->lchild->len, lvl + 1); + } + + /* swap with the right child and drop the child */ + tmp = root->data; + replace_node_data(root, root->rchild); + root->rchild->data = tmp; + + return trie_remove(root->rchild, &root->rchild->prefix, root->rchild->len, lvl + 1); + } + + if (is_left_child(prefix, lvl)) { + if (!root->lchild) + return NULL; + return trie_remove(root->lchild, prefix, mask_len, lvl + 1); + } + + if (!root->rchild) + return NULL; + return trie_remove(root->rchild, prefix, mask_len, lvl + 1); +} + +static int append_node_to_array(struct trie_node ***ary, unsigned int *len, struct trie_node *n) +{ + struct trie_node **new; + + new = lrtr_realloc(*ary, *len * sizeof(n)); + if (!new) + return -1; + + *ary = new; + (*ary)[*len - 1] = n; + return 0; +} + +int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len) +{ + if (root_node->lchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->lchild)) + goto err; + + if (trie_get_children(root_node->lchild, array, len) == -1) + goto err; + } + + if (root_node->rchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->rchild)) + goto err; + + if (trie_get_children(root_node->rchild, array, len) == -1) + goto err; + } + + return 0; + +err: + lrtr_free(*array); + return -1; +} + +inline bool trie_is_leaf(const struct trie_node *node) +{ + return !node->lchild && !node->rchild; +} diff --git a/rtrlib/pfx/trie/trie_private.h b/rtrlib/pfx/trie/trie_private.h new file mode 100644 index 0000000..8003ced --- /dev/null +++ b/rtrlib/pfx/trie/trie_private.h @@ -0,0 +1,98 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTR_TRIE_PRIVATE +#define RTR_TRIE_PRIVATE + +#include "rtrlib/lib/ip_private.h" + +#include <inttypes.h> + +/** + * @brief trie_node + * @param prefix + * @param rchild + * @param lchild + * @param parent + * @param data + * @param len number of elements in data array + */ +struct trie_node { + struct lrtr_ip_addr prefix; + struct trie_node *rchild; + struct trie_node *lchild; + struct trie_node *parent; + void *data; + uint8_t len; +}; + +/** + * @brief Inserts new_node in the tree. + * @param[in] root Root node of the tree for the inserting process. + * @param[in] new_node Node that will be inserted. + * @param[in] level Level of the the root node in the tree. + */ +void trie_insert(struct trie_node *root, struct trie_node *new_node, const unsigned int level); + +/** + * @brief Searches for a matching node matching the passed ip + * prefix and prefix length. If multiple matching nodes exist, the one + * with the shortest prefix is returned. + * @param[in] root_node Node were the lookup process starts. + * @param[in] lrtr_ip_addr IP-Prefix. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in,out] level of the the node root in the tree. Is set to the level of + * the node that is returned. + * @returns The trie_node with the short prefix in the tree matching the + * passed ip prefix and prefix length. + * @returns NULL if no node that matches the passed prefix and prefix length + * could be found. + */ +struct trie_node *trie_lookup(const struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *level); + +/** + * @brief Search for a node with the same prefix and prefix length. + * @param[in] root_node Node were the lookup process starts. + * @param[in] lrtr_ip_addr IP-Prefix. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in,out] level of the the node root in the tree. Is set to the level of + * the node that is returned. + * @param[in] found Is true if a node which matches could be found else found is + * set to false. + * @return A node which matches the passed parameter (found==true). + * @return The parent of the node where the lookup operation + * stopped (found==false). + * @return NULL if root_node is NULL. + */ +struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *level, bool *found); + +/** + * @brief Removes the node with the passed IP prefix and mask_len from the tree. + * @param[in] root Node were the inserting process starts. + * @param[in] prefix Prefix that will removed from the tree. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in] level Level of the root node in the tree. + * @returns Node that was removed from the tree. The caller has to free it. + * @returns NULL If the Prefix couldn't be found in the tree. + */ +struct trie_node *trie_remove(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + const unsigned int level); + +/** + * @brief Detects if a node is a leaf in the tree. + * @param[in] node + * @returns true if node is a leaf. + * @returns false if node isn't a leaf. + */ +bool trie_is_leaf(const struct trie_node *node); + +int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len); +#endif |