0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-10-18 18:08:45 +00:00
bird/proto/rpki/rpki.c
2015-12-23 15:09:01 +01:00

577 lines
14 KiB
C

/*
* BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
*
* (c) 2015 CZ.NIC
*
* Using RTRlib: http://rpki.realmv6.org/
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
/**
* DOC: RPKI to Router Protocol
*
* The Resource Public Key Infrastructure (RPKI) to router protocol implementation
* is based on the RTRlib (http://rpki.realmv6.org/). The BIRD takes over
* |packets.c|, |rtr.c|, |transport.c|, |tcp_transport.c| and |ssh_transport.c| files
* from RTRlib.
*/
#undef LOCAL_DEBUG
#include <stdlib.h>
#include <assert.h>
#include "rpki.h"
static struct proto *
rpki_init(struct proto_config *C)
{
struct proto *P = proto_new(C, sizeof(struct rpki_proto));
struct rpki_proto *p = (void *) P;
p->cf = (void *) C;
init_list(&p->group_list);
return P;
}
const char *
get_cache_ident(struct rpki_cache *cache)
{
return tr_ident(cache->rtr_socket->tr_socket);
}
static struct rpki_cache_group *
rpki_cache_group_alloc(struct rpki_proto *p, u8 preference)
{
struct rpki_cache_group *new = mb_allocz(p->p.pool, sizeof(struct rpki_cache_group));
init_list(&new->cache_list);
new->preference = preference;
return new;
}
static struct rpki_cache_group *
rpki_new_cache_group_before(struct rpki_proto *p, struct rpki_cache_group *before, list *group_list, u8 preference)
{
struct rpki_cache_group *new = rpki_cache_group_alloc(p, preference);
if (&before->n == group_list->head)
add_head(group_list, &new->n);
else
insert_node(&new->n, before->n.prev);
return new;
}
static void
rpki_insert_cache_into_group(struct rpki_cache *cache)
{
struct rpki_proto *p = cache->p;
struct rpki_cache_group *group_iter;
WALK_LIST(group_iter, p->group_list)
{
if (group_iter->preference == cache->cfg->preference)
{
add_tail(&group_iter->cache_list, &cache->n);
return;
}
if (group_iter->preference > cache->cfg->preference)
{
struct rpki_cache_group *new_group = rpki_new_cache_group_before(p, group_iter, &p->group_list, cache->cfg->preference);
add_tail(&new_group->cache_list, &cache->n);
return;
}
}
struct rpki_cache_group *new_group = rpki_cache_group_alloc(p, cache->cfg->preference);
add_tail(&p->group_list, &new_group->n);
add_tail(&new_group->cache_list, &cache->n);
}
struct rpki_cache_cfg *
rpki_new_cache_cfg(void)
{
struct rpki_cache_cfg *cache = cfg_allocz(sizeof(struct rpki_cache_cfg));
cache->preference = RPKI_DEFAULT_CACHE_PREFERENCE;
cache->ip = IPA_NONE;
cache->retry_interval = RPKI_DEFAULT_RETRY_INTERVAL;
cache->refresh_interval = RPKI_DEFAULT_REFRESH_INTERVAL;
cache->expire_interval = RPKI_DEFAULT_EXPIRE_INTERVAL;
/* The port number will be set afterwards */
return cache;
}
/*
* We need be able to identify routes that origin from the cache server
* for case that the cache server send bad serial number and we have to
* remove from table all ROA learned from this cache server.
*
* This function return first not used ROA_SRC_* number in protocol
*/
static u8
get_unused_roa_src(struct rpki_proto *p)
{
u8 bitmap[256];
/* The first 3 are reserved for:
* ROA_SRC_ANY 0,
* ROA_SRC_CONFIG 1,
* ROA_SRC_DYNAMIC 2,
*/
const u8 number_of_reserved = 3;
memset(bitmap, 1, number_of_reserved);
bzero(bitmap+number_of_reserved, sizeof(bitmap)-number_of_reserved);
struct rpki_cache_group *group;
WALK_LIST(group, p->group_list)
{
struct rpki_cache *cache;
WALK_LIST(cache, group->cache_list)
{
if (bitmap[cache->roa_src] != 0)
RPKI_WARN(p, "ROA_SRC %u is used more than once!", cache->roa_src);
bitmap[cache->roa_src] += 1;
}
}
uint i;
for (i = number_of_reserved; i < sizeof(bitmap); i++)
if (bitmap[i] == 0)
return i;
RPKI_WARN(p, "All ROA_SRC are used?!");
return ROA_SRC_RPKI;
}
struct rpki_cache *
rpki_new_cache(struct rpki_proto *p, struct rpki_cache_cfg *cache_cfg)
{
struct rpki_cache *cache = mb_allocz(p->p.pool, sizeof(struct rpki_cache));
struct rtr_socket *rtr_socket = mb_allocz(p->p.pool, sizeof(struct rtr_socket));
struct tr_socket *tr_socket = mb_allocz(p->p.pool, sizeof(struct tr_socket));
cache->p = p;
cache->cfg = cache_cfg;
cache->roa_src = get_unused_roa_src(p);
cache->retry_timer = tm_new_set(p->p.pool, &rpki_retry_hook, cache, 0, 0);
cache->refresh_timer = tm_new_set(p->p.pool, &rpki_refresh_hook, cache, 0, 0);
cache->expire_timer = tm_new_set(p->p.pool, &rpki_expire_hook, cache, 0, 0);
cache->rtr_socket = rtr_socket;
cache->rtr_socket->tr_socket = tr_socket;
cache->rtr_socket->cache = cache;
if (cache_cfg->ssh)
tr_ssh_init(cache);
else
tr_tcp_init(cache);
rtr_init(rtr_socket, cache_cfg->refresh_interval, cache_cfg->expire_interval, cache_cfg->retry_interval);
return cache;
}
/*
* Close connection without change a status
*/
void
rpki_close_connection(struct rpki_cache *cache)
{
sock *sk = cache->sk;
if (sk)
{
CACHE_TRACE(D_EVENTS, cache, "Close the connection");
tr_close(cache->rtr_socket->tr_socket);
rfree(sk);
cache->sk = NULL;
}
}
int
rpki_open_connection(struct rpki_cache *cache)
{
struct rpki_proto *p = cache->p;
struct tr_socket *tr_socket = cache->rtr_socket->tr_socket;
CACHE_TRACE(D_EVENTS, cache, "Open a connection");
ASSERT(cache->sk == NULL);
cache->sk = sk_new(p->p.pool);
sock *sk = cache->sk;
sk->tx_hook = rpki_connected_hook;
sk->err_hook = rpki_err_hook;
sk->data = cache;
sk->daddr = cache->cfg->ip;
sk->dport = cache->cfg->port;
sk->host = cache->cfg->hostname;
sk->rbsize = RPKI_RX_BUFFER_SIZE;
sk->tbsize = RPKI_TX_BUFFER_SIZE;
sk->tos = IP_PREC_INTERNET_CONTROL;
sk->type = -1; /* must be set in the specific transport layer in tr_open() */
if (tr_open(tr_socket) == TR_ERROR)
{
sk_log_error(sk, p->p.name);
rtr_change_socket_state(cache->rtr_socket, RTR_ERROR_TRANSPORT);
return TR_ERROR;
}
return TR_SUCCESS;
}
static void
rpki_open_group(struct rpki_proto *p, struct rpki_cache_group *group)
{
struct rpki_cache *cache;
WALK_LIST(cache, group->cache_list)
{
if (cache->rtr_socket->state == RTR_SHUTDOWN)
rpki_open_connection(cache);
}
}
static void
rpki_close_group(struct rpki_proto *p, struct rpki_cache_group *group)
{
struct rpki_cache *cache;
WALK_LIST(cache, group->cache_list)
{
if (cache->rtr_socket->state != RTR_SHUTDOWN)
rtr_change_socket_state(cache->rtr_socket, RTR_SHUTDOWN);
}
}
static void
rpki_remove_cache_from_group(struct rpki_cache *cache)
{
rem2_node(&cache->n);
}
static void
rpki_free_cache(struct rpki_cache *cache)
{
rpki_remove_cache_from_group(cache);
rpki_close_connection(cache);
pfx_table_src_remove(cache);
tr_free(cache->rtr_socket->tr_socket);
mb_free(cache->rtr_socket->tr_socket);
mb_free(cache->rtr_socket);
/* timers */
tm_stop(cache->retry_timer);
tm_stop(cache->refresh_timer);
tm_stop(cache->expire_timer);
rfree(cache->retry_timer);
rfree(cache->refresh_timer);
rfree(cache->expire_timer);
mb_free(cache);
}
static void
rpki_stop_and_free_caches(struct rpki_proto *p)
{
struct rpki_cache_group *group;
WALK_LIST_FIRST(group, p->group_list)
{
struct rpki_cache *cache;
WALK_LIST_FIRST(cache, group->cache_list)
{
rem_node(NODE cache);
rpki_free_cache(cache);
}
rem_node(NODE group);
mb_free(group);
}
proto_notify_state(&p->p, PS_DOWN);
}
static int
rpki_shutdown(struct proto *P)
{
struct rpki_proto *p = (struct rpki_proto *) P;
rpki_stop_and_free_caches(p);
return PS_DOWN;
}
static int
are_port_and_host_same(struct rpki_cache_cfg *a, struct rpki_cache_cfg *b)
{
return (
(a->port == b->port) &&
(
(a->hostname && b->hostname && strcmp(a->hostname, b->hostname) == 0) ||
(ipa_nonzero(a->ip) && (ipa_compare(a->ip, b->ip) == 0))
)
);
}
static struct rpki_cache_cfg *
find_cache_cfg_by_host_and_port(list *cache_list, struct rpki_cache_cfg *needle)
{
struct rpki_cache_cfg *cache_cfg;
WALK_LIST(cache_cfg, *cache_list)
{
if (are_port_and_host_same(needle, cache_cfg))
return cache_cfg;
}
return NULL;
}
static struct rpki_cache *
find_cache_in_proto_by_host_and_port(struct rpki_proto *p, struct rpki_cache_cfg *needle)
{
struct rpki_cache_group *group;
WALK_LIST(group, p->group_list)
{
struct rpki_cache *cache;
WALK_LIST(cache, group->cache_list)
{
if (are_port_and_host_same(needle, cache->cfg))
return cache;
}
}
return NULL;
}
/*
* Remove empty cache groups in list
*/
static void
rpki_relax_group_list(struct rpki_proto *p)
{
struct rpki_cache_group *group, *group_nxt;
WALK_LIST_DELSAFE(group, group_nxt, p->group_list)
{
if (EMPTY_LIST(group->cache_list))
rem_node(&group->n);
}
}
/*
* Move cache into `cache->cfg->preference` preference
*/
static void
move_cache_into_group(struct rpki_cache *cache)
{
rpki_remove_cache_from_group(cache);
rpki_insert_cache_into_group(cache);
rpki_relax_group_list(cache->p);
}
/*
* Start connections to caches in the first (the highest priority) group
* and shut down all connections to caches in others groups
*/
static int
rpki_relax_groups(struct rpki_proto *p)
{
if (EMPTY_LIST(p->group_list))
{
RPKI_WARN(p, "No cache in configuration found");
return 0;
}
struct rpki_cache_group *group;
WALK_LIST(group, p->group_list)
{
if (group == (struct rpki_cache_group *) p->group_list.head)
rpki_open_group(p, group);
else
rpki_close_group(p, group);
}
return 1;
}
static int
rpki_reconfigure_proto(struct rpki_proto *p, struct rpki_config *new_cf, struct rpki_config *old_cf)
{
if (old_cf->roa_table_cf && old_cf->roa_table_cf->table != new_cf->roa_table_cf->table)
{
RPKI_TRACE(D_EVENTS, p, "ROA table changed");
return 0; /* Need to restart the protocol */
}
struct rpki_cache_cfg *old;
WALK_LIST(old, old_cf->cache_cfg_list)
{
struct rpki_cache *cache = find_cache_in_proto_by_host_and_port(p, old);
if (!cache)
bug("Weird...");
struct rpki_cache_cfg *new = find_cache_cfg_by_host_and_port(&new_cf->cache_cfg_list, old);
if (!new)
{
/* The cache was in new configuration deleted */
rpki_free_cache(cache);
continue;
}
cache->cfg = new;
if (old->preference != new->preference)
{
/* The preference of cache was changed */
move_cache_into_group(cache);
}
if (!!old->ssh != !!new->ssh)
{
/* toggled SSH enable/disable */
return 0; /* Need to restart the protocol */
}
if (old->ssh && new->ssh)
{
/* TODO: RTR_FAST_RECONNECT will be probably enough */
if (strcmp(old->ssh->bird_private_key, new->ssh->bird_private_key) != 0)
return 0; /* Need to restart the protocol */
if (strcmp(old->ssh->cache_public_key, new->ssh->cache_public_key) != 0)
return 0; /* Need to restart the protocol */
if (strcmp(old->ssh->username, new->ssh->username) != 0)
return 0; /* Need to restart the protocol */
}
}
struct rpki_cache_cfg *new;
WALK_LIST(new, new_cf->cache_cfg_list)
{
struct rpki_cache *cache = find_cache_in_proto_by_host_and_port(p, new);
if (cache)
cache->cfg = new;
struct rpki_cache_cfg *old = find_cache_cfg_by_host_and_port(&old_cf->cache_cfg_list, new);
if (!old)
{
/* Some cache was added to new configuration */
struct rpki_cache *new_cache = rpki_new_cache(p, new);
rpki_insert_cache_into_group(new_cache);
}
}
struct rpki_cache_group *g;
WALK_LIST(g, p->group_list)
{
RPKI_TRACE(D_EVENTS, p, "Group(%u)", g->preference);
struct rpki_cache *c;
WALK_LIST(c, g->cache_list)
{
RPKI_TRACE(D_EVENTS, p, " Cache(%s)", get_cache_ident(c));
}
}
return 1;
}
/*
* Return 0 if need to restart rtrlib manager
* Return 1 if not need to restart rtrlib manager
*/
static int
rpki_reconfigure(struct proto *P, struct proto_config *c)
{
struct rpki_proto *p = (struct rpki_proto *) P;
struct rpki_config *old_cf = p->cf;
struct rpki_config *new_cf = (struct rpki_config *) c;
int continue_without_restart = rpki_reconfigure_proto(p, new_cf, old_cf);
p->cf = new_cf;
if (continue_without_restart)
rpki_relax_groups(p);
return continue_without_restart;
}
static void
rpki_get_status(struct proto *P, byte *buf)
{
struct rpki_proto *p = (struct rpki_proto *) P;
unsigned int i, j;
uint established_connections = 0;
uint cache_servers = 0;
uint connecting = 0;
struct rpki_cache_group *group;
WALK_LIST(group, p->group_list)
{
struct rpki_cache *cache;
WALK_LIST(cache, group->cache_list)
{
cache_servers++;
switch (cache->rtr_socket->state)
{
case RTR_ESTABLISHED:
case RTR_SYNC:
established_connections++;
break;
case RTR_SHUTDOWN:
break;
default:
connecting++;
}
}
}
if (established_connections > 0)
bsprintf(buf, "Keep synchronized with %u cache server%s", established_connections, (established_connections > 1) ? "s" : "");
else if (connecting > 0)
bsprintf(buf, "Connecting to %u cache server%s", connecting, (connecting > 1) ? "s" : "");
else if (cache_servers == 0)
bsprintf(buf, "No cache server is configured");
else if (cache_servers == 1)
bsprintf(buf, "Cannot connect to a cache server");
else
bsprintf(buf, "Cannot connect to any cache servers");
}
static int
rpki_start(struct proto *P)
{
struct rpki_proto *p = (struct rpki_proto *) P;
struct rpki_config *cf = (struct rpki_config *) (P->cf);
struct rpki_config empty_configuration = {
.roa_table_cf = cf->roa_table_cf
};
init_list(&empty_configuration.cache_cfg_list);
rpki_reconfigure_proto(p, cf, &empty_configuration);
rpki_relax_groups(p);
return PS_UP;
}
struct protocol proto_rpki = {
.name = "RPKI",
.template = "rpki%d",
.config_size = sizeof(struct rpki_config),
.init = rpki_init,
.start = rpki_start,
// .show_proto_info = rpki_show_proto_info, // TODO: be nice to be implemented
.shutdown = rpki_shutdown,
.reconfigure = rpki_reconfigure,
.get_status = rpki_get_status,
};