Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
751 lines (636 sloc) 20.2 KB
/*
* 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 <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.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/rtr_mgr_private.h"
#include "rtrlib/rtrlib_export_private.h"
#include "rtrlib/spki/hashtable/ht-spkitable_private.h"
#include "rtrlib/transport/transport_private.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);
}
You can’t perform that action at this time.