diff options
Diffstat (limited to 'rtrlib/rtr_mgr.c')
-rw-r--r-- | rtrlib/rtr_mgr.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c new file mode 100644 index 0000000..cfd1de6 --- /dev/null +++ b/rtrlib/rtr_mgr.c @@ -0,0 +1,675 @@ +/* + * 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 "rtr_mgr_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/rtr/rtr_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <arpa/inet.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MGR_DBG(fmt, ...) lrtr_dbg("RTR_MGR: " fmt, ##__VA_ARGS__) +#define MGR_DBG1(a) lrtr_dbg("RTR_MGR: " a) + +static const char *const mgr_str_status[] = { + [RTR_MGR_CLOSED] = "RTR_MGR_CLOSED", + [RTR_MGR_CONNECTING] = "RTR_MGR_CONNECTING", + [RTR_MGR_ESTABLISHED] = "RTR_MGR_ESTABLISHED", + [RTR_MGR_ERROR] = "RTR_MGR_ERROR", +}; + +static int rtr_mgr_config_cmp(const void *a, const void *b); +static int rtr_mgr_config_cmp_tommy(const void *a, const void *b); +static bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group); +static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config, + void *data_group); + +static void set_status(const struct rtr_mgr_config *conf, struct rtr_mgr_group *group, enum rtr_mgr_status mgr_status, + const struct rtr_socket *rtr_sock) +{ + MGR_DBG("Group(%u) status changed to: %s", group->preference, rtr_mgr_status_to_str(mgr_status)); + + group->status = mgr_status; + if (conf->status_fp) + conf->status_fp(group, mgr_status, rtr_sock, conf->status_fp_data); +} + +static int rtr_mgr_start_sockets(struct rtr_mgr_group *group) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + if (rtr_start(group->sockets[i]) != 0) { + MGR_DBG1("rtr_mgr: Error starting rtr_socket pthread"); + return RTR_ERROR; + } + } + group->status = RTR_MGR_CONNECTING; + return RTR_SUCCESS; +} + +static int rtr_mgr_init_sockets(struct rtr_mgr_group *group, struct rtr_mgr_config *config, + const unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval, enum rtr_interval_mode iv_mode) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + enum rtr_rtvals err_code = rtr_init(group->sockets[i], NULL, config->pfx_table, config->spki_table, + refresh_interval, expire_interval, retry_interval, iv_mode, + rtr_mgr_cb, config, group); + if (err_code) + return err_code; + } + return RTR_SUCCESS; +} + +bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + enum rtr_socket_state state = group->sockets[i]->state; + + if ((group->sockets[i]->last_update == 0) || + ((state != RTR_ESTABLISHED) && (state != RTR_RESET) && (state != RTR_SYNC))) + return false; + } + return true; +} + +static void rtr_mgr_close_less_preferable_groups(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group->status != RTR_MGR_CLOSED) && (current_group != group) && + (current_group->preference > group->preference)) { + for (unsigned int j = 0; j < current_group->sockets_len; j++) + rtr_stop(current_group->sockets[j]); + set_status(config, current_group, RTR_MGR_CLOSED, sock); + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); +} + +static struct rtr_mgr_group *get_best_inactive_rtr_mgr_group(struct rtr_mgr_config *config, struct rtr_mgr_group *group) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group != group) && (current_group->status == RTR_MGR_CLOSED)) { + pthread_mutex_unlock(&config->mutex); + return current_group; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return NULL; +} + +static bool is_some_rtr_mgr_group_established(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + if (group_node->group->status == RTR_MGR_ESTABLISHED) { + pthread_mutex_unlock(&config->mutex); + return true; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return false; +} + +static inline void _rtr_mgr_cb_state_shutdown(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + bool all_down = true; + + for (unsigned int i = 0; i < group->sockets_len; i++) { + if (group->sockets[i]->state != RTR_SHUTDOWN) { + all_down = false; + break; + } + } + if (all_down) + set_status(config, group, RTR_MGR_CLOSED, sock); + else + set_status(config, group, group->status, sock); +} + +static inline void _rtr_mgr_cb_state_established(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + /* socket established a connection to the rtr_server */ + if (group->status == RTR_MGR_CONNECTING) { + /* + * if previous state was CONNECTING, check if all + * other sockets in the group also have a established + * connection, if yes change group state to ESTABLISHED + */ + if (rtr_mgr_config_status_is_synced(group)) { + set_status(config, group, RTR_MGR_ESTABLISHED, sock); + rtr_mgr_close_less_preferable_groups(sock, config, group); + } else { + set_status(config, group, RTR_MGR_CONNECTING, sock); + } + } else if (group->status == RTR_MGR_ERROR) { + /* if previous state was ERROR, only change state to + * ESTABLISHED if all other more preferable socket + * groups are also in ERROR or SHUTDOWN state + */ + bool all_error = true; + + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group != group) && (current_group->status != RTR_MGR_ERROR) && + (current_group->status != RTR_MGR_CLOSED) && + (current_group->preference < group->preference)) { + all_error = false; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + + if (all_error && rtr_mgr_config_status_is_synced(group)) { + set_status(config, group, RTR_MGR_ESTABLISHED, sock); + rtr_mgr_close_less_preferable_groups(sock, config, group); + } else { + set_status(config, group, RTR_MGR_ERROR, sock); + } + } +} + +static inline void _rtr_mgr_cb_state_connecting(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + if (group->status == RTR_MGR_ERROR) + set_status(config, group, RTR_MGR_ERROR, sock); + else + set_status(config, group, RTR_MGR_CONNECTING, sock); +} + +static inline void _rtr_mgr_cb_state_error(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + set_status(config, group, RTR_MGR_ERROR, sock); + + if (!is_some_rtr_mgr_group_established(config)) { + struct rtr_mgr_group *next_group = get_best_inactive_rtr_mgr_group(config, group); + + if (next_group) + rtr_mgr_start_sockets(next_group); + else + MGR_DBG1("No other inactive groups found"); + } +} + +static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config, + void *data_group) +{ + if (state == RTR_SHUTDOWN) + MGR_DBG1("Received RTR_SHUTDOWN callback"); + + struct rtr_mgr_config *config = data_config; + struct rtr_mgr_group *group = data_group; + + if (!group) { + MGR_DBG1("ERROR: Socket has no group"); + return; + } + + switch (state) { + case RTR_SHUTDOWN: + _rtr_mgr_cb_state_shutdown(sock, config, group); + break; + case RTR_ESTABLISHED: + _rtr_mgr_cb_state_established(sock, config, group); + break; + case RTR_CONNECTING: + _rtr_mgr_cb_state_connecting(sock, config, group); + break; + case RTR_ERROR_FATAL: + case RTR_ERROR_TRANSPORT: + case RTR_ERROR_NO_DATA_AVAIL: + _rtr_mgr_cb_state_error(sock, config, group); + break; + default: + set_status(config, group, group->status, sock); + } +} + +int rtr_mgr_config_cmp(const void *a, const void *b) +{ + const struct rtr_mgr_group *ar = a; + const struct rtr_mgr_group *br = b; + + if (ar->preference > br->preference) + return 1; + else if (ar->preference < br->preference) + return -1; + return 0; +} + +int rtr_mgr_config_cmp_tommy(const void *a, const void *b) +{ + const struct rtr_mgr_group_node *ar = a; + const struct rtr_mgr_group_node *br = b; + + return rtr_mgr_config_cmp(ar->group, br->group); +} + +RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[], + const unsigned int groups_len, const unsigned int refresh_interval, + const unsigned int expire_interval, const unsigned int retry_interval, + const pfx_update_fp update_fp, const spki_update_fp spki_update_fp, + const rtr_mgr_status_fp status_fp, void *status_fp_data) +{ + enum rtr_rtvals err_code = RTR_ERROR; + enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; + struct pfx_table *pfxt = NULL; + struct spki_table *spki_table = NULL; + struct rtr_mgr_config *config = NULL; + struct rtr_mgr_group *cg = NULL; + struct rtr_mgr_group_node *group_node; + uint8_t last_preference = UINT8_MAX; + + *config_out = NULL; + + if (groups_len == 0) { + MGR_DBG1("Error Empty rtr_mgr_group array"); + return RTR_ERROR; + } + + *config_out = config = lrtr_malloc(sizeof(*config)); + if (!config) + return RTR_ERROR; + + config->len = groups_len; + + if (pthread_mutex_init(&config->mutex, NULL) != 0) { + MGR_DBG1("Mutex initialization failed"); + goto err; + } + /* sort array in asc order, so we can check for dupl. pref */ + qsort(groups, groups_len, sizeof(struct rtr_mgr_group), &rtr_mgr_config_cmp); + + /* Check that the groups have unique pref and at least one socket */ + for (unsigned int i = 0; i < config->len; i++) { + if ((i > 0) && (groups[i].preference == last_preference)) { + MGR_DBG1("Error Same preference for 2 socket groups!"); + goto err; + } + if (groups[i].sockets_len == 0) { + MGR_DBG1("Error Empty sockets array in socket group!"); + goto err; + } + + last_preference = groups[i].preference; + } + + /* Init data structures that we need to pass to the sockets */ + pfxt = lrtr_malloc(sizeof(*pfxt)); + if (!pfxt) + goto err; + pfx_table_init(pfxt, update_fp); + + spki_table = lrtr_malloc(sizeof(*spki_table)); + if (!spki_table) + goto err; + spki_table_init(spki_table, spki_update_fp); + + config->pfx_table = pfxt; + config->spki_table = spki_table; + + /* Copy the groups from the array into linked list config->groups */ + config->len = groups_len; + config->groups = lrtr_malloc(sizeof(*config->groups)); + if (!config->groups) + goto err; + config->groups->list = NULL; + + for (unsigned int i = 0; i < groups_len; i++) { + cg = lrtr_malloc(sizeof(struct rtr_mgr_group)); + if (!cg) + goto err; + + memcpy(cg, &groups[i], sizeof(struct rtr_mgr_group)); + + cg->status = RTR_MGR_CLOSED; + err_code = rtr_mgr_init_sockets(cg, config, refresh_interval, expire_interval, retry_interval, iv_mode); + if (err_code) + goto err; + + group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node)); + if (!group_node) + goto err; + + group_node->group = cg; + tommy_list_insert_tail(&config->groups->list, &group_node->node, group_node); + } + /* Our linked list should be sorted already, since the groups array was + * sorted. However, for safety reasons we sort again. + */ + tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy); + + config->status_fp_data = status_fp_data; + config->status_fp = status_fp; + return RTR_SUCCESS; + +err: + if (spki_table) + spki_table_free(spki_table); + if (pfxt) + pfx_table_free(pfxt); + lrtr_free(pfxt); + lrtr_free(spki_table); + + lrtr_free(cg); + + lrtr_free(config->groups); + lrtr_free(config); + config = NULL; + *config_out = NULL; + + return err_code; +} + +RTRLIB_EXPORT struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config) +{ + tommy_node *head = tommy_list_head(&config->groups->list); + struct rtr_mgr_group_node *group_node = head->data; + + return group_node->group; +} + +RTRLIB_EXPORT int rtr_mgr_start(struct rtr_mgr_config *config) +{ + MGR_DBG("%s()", __func__); + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + return rtr_mgr_start_sockets(best_group); +} + +RTRLIB_EXPORT bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + bool all_sync = true; + struct rtr_mgr_group_node *group_node = node->data; + + for (unsigned int j = 0; all_sync && (j < group_node->group->sockets_len); j++) { + if (group_node->group->sockets[j]->last_update == 0) + all_sync = false; + } + if (all_sync) { + pthread_mutex_unlock(&config->mutex); + return true; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return false; +} + +RTRLIB_EXPORT void rtr_mgr_free(struct rtr_mgr_config *config) +{ + MGR_DBG("%s()", __func__); + pthread_mutex_lock(&config->mutex); + + pfx_table_free(config->pfx_table); + spki_table_free(config->spki_table); + lrtr_free(config->spki_table); + lrtr_free(config->pfx_table); + + /* Free linked list */ + tommy_node *head = tommy_list_head(&config->groups->list); + + while (head) { + tommy_node *tmp = head; + struct rtr_mgr_group_node *group_node = tmp->data; + + head = head->next; + for (unsigned int j = 0; j < group_node->group->sockets_len; j++) + tr_free(group_node->group->sockets[j]->tr_socket); + + lrtr_free(group_node->group); + lrtr_free(group_node); + } + + lrtr_free(config->groups); + + pthread_mutex_unlock(&config->mutex); + pthread_mutex_destroy(&config->mutex); + lrtr_free(config); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline int rtr_mgr_validate(struct rtr_mgr_config *config, const uint32_t asn, + const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + enum pfxv_state *result) +{ + return pfx_table_validate(config->pfx_table, asn, prefix, mask_len, result); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline int rtr_mgr_get_spki(struct rtr_mgr_config *config, const uint32_t asn, uint8_t *ski, + struct spki_record **result, unsigned int *result_count) +{ + return spki_table_get_all(config->spki_table, asn, ski, result, result_count); +} + +RTRLIB_EXPORT void rtr_mgr_stop(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + MGR_DBG("%s()", __func__); + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + for (unsigned int j = 0; j < group_node->group->sockets_len; j++) + rtr_stop(group_node->group->sockets[j]); + node = node->next; + } + pthread_mutex_unlock(&config->mutex); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct rtr_mgr_group *group) +{ + unsigned int refresh_iv = 3600; + unsigned int retry_iv = 600; + unsigned int expire_iv = 7200; + enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; + enum rtr_rtvals err_code = RTR_ERROR; + struct rtr_mgr_group_node *new_group_node = NULL; + struct rtr_mgr_group *new_group = NULL; + struct rtr_mgr_group_node *gnode; + + pthread_mutex_lock(&config->mutex); + + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + gnode = node->data; + if (gnode->group->preference == group->preference) { + MGR_DBG1("Group with preference value already exists!"); + err_code = RTR_INVALID_PARAM; + goto err; + } + + // TODO This is not pretty. It wants to get the same intervals + // that are being used by other groups. Maybe intervals should + // be store globally/per-group/per-socket? + if (gnode->group->sockets[0]->refresh_interval) + refresh_iv = gnode->group->sockets[0]->refresh_interval; + if (gnode->group->sockets[0]->retry_interval) + retry_iv = gnode->group->sockets[0]->retry_interval; + if (gnode->group->sockets[0]->expire_interval) + expire_iv = gnode->group->sockets[0]->expire_interval; + node = node->next; + } + new_group = lrtr_malloc(sizeof(struct rtr_mgr_group)); + + if (!new_group) + goto err; + + memcpy(new_group, group, sizeof(struct rtr_mgr_group)); + new_group->status = RTR_MGR_CLOSED; + + err_code = rtr_mgr_init_sockets(new_group, config, refresh_iv, expire_iv, retry_iv, iv_mode); + if (err_code) + goto err; + + new_group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node)); + if (!new_group_node) + goto err; + + new_group_node->group = new_group; + tommy_list_insert_tail(&config->groups->list, &new_group_node->node, new_group_node); + config->len++; + + MGR_DBG("Group with preference %d successfully added!", new_group->preference); + + tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy); + + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + if (best_group->status == RTR_MGR_CLOSED) + rtr_mgr_start_sockets(best_group); + + pthread_mutex_unlock(&config->mutex); + return RTR_SUCCESS; + +err: + pthread_mutex_unlock(&config->mutex); + + if (new_group) + lrtr_free(new_group); + + return err_code; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *remove_node = NULL; + tommy_node *node = tommy_list_head(&config->groups->list); + struct rtr_mgr_group_node *group_node; + struct rtr_mgr_group *remove_group; + + if (config->len == 1) { + MGR_DBG1("Cannot remove last remaining group!"); + pthread_mutex_unlock(&config->mutex); + return RTR_ERROR; + } + + // Find the node of the group we want to remove + while (node && !remove_node) { + group_node = node->data; + if (group_node->group->preference == preference) + remove_node = node; + node = node->next; + } + + if (!remove_node) { + MGR_DBG1("The group that should be removed does not exist!"); + pthread_mutex_unlock(&config->mutex); + return RTR_ERROR; + } + + group_node = remove_node->data; + remove_group = group_node->group; + tommy_list_remove_existing(&config->groups->list, remove_node); + config->len--; + MGR_DBG("Group with preference %d successfully removed!", preference); + // tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp); + pthread_mutex_unlock(&config->mutex); + + // If group isn't closed, make it so! + if (remove_group->status != RTR_MGR_CLOSED) { + for (unsigned int j = 0; j < remove_group->sockets_len; j++) { + rtr_stop(remove_group->sockets[j]); + tr_free(remove_group->sockets[j]->tr_socket); + } + set_status(config, remove_group, RTR_MGR_CLOSED, NULL); + } + + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + if (best_group->status == RTR_MGR_CLOSED) + rtr_mgr_start_sockets(best_group); + + lrtr_free(group_node->group); + lrtr_free(group_node); + return RTR_SUCCESS; +} + +// TODO: write test for this function. +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_for_each_group(struct rtr_mgr_config *config, + void(fp)(const struct rtr_mgr_group *group, void *data), void *data) +{ + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + fp(group_node->group, data); + node = node->next; + } + + return RTR_SUCCESS; +} + +RTRLIB_EXPORT const char *rtr_mgr_status_to_str(enum rtr_mgr_status status) +{ + return mgr_str_status[status]; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv4_record(struct rtr_mgr_config *config, + void(fp)(const struct pfx_record *, void *data), void *data) +{ + pfx_table_for_each_ipv4_record(config->pfx_table, fp, data); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *config, + void(fp)(const struct pfx_record *, void *data), void *data) +{ + pfx_table_for_each_ipv6_record(config->pfx_table, fp, data); +} |