0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2025-01-07 01:21:54 +00:00
bird/proto/rpki/rpki.c
Pavel Tvrdík 4661035431 RPKI protocol with one cache server per protocol
The RPKI protocol (RFC 6810) using the RTRLib
(http://rpki.realmv6.org/) that is integrated inside
the BIRD's code.

Implemeted transports are:
 - unprotected transport over TCP
 - secure transport over SSHv2

Example configuration of bird.conf:
  ...
  roa4 table r4;
  roa6 table r6;

  protocol rpki {
    debug all;

    # Import both IPv4 and IPv6 ROAs
    roa4 { table r4; };
    roa6 { table r6; };

    # Set cache server (validator) address,
    # overwrite default port 323
    remote "rpki-validator.realmv6.org" port 8282;

    # Overwrite default time intervals
    retry   10;         # Default 600 seconds
    refresh 60;         # Default 3600 seconds
    expire 600;         # Default 7200 seconds
  }

  protocol rpki {
    debug all;

    # Import only IPv4 routes
    roa4 { table r4; };

    # Set cache server address to localhost,
    # use default ports tcp => 323 or ssh => 22
    remote 127.0.0.1;

    # Use SSH transport instead of unprotected transport over TCP
    ssh encryption {
      bird private key "/home/birdgeek/.ssh/id_rsa";
      remote public key "/home/birdgeek/.ssh/known_hosts";
      user "birdgeek";
    };
  }
  ...
2016-05-26 13:57:19 +02:00

738 lines
19 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 (RFC 6810)
* is protocol for communication between router (BIRD) and RPKI cache server
* (RPKI validator). Validator sends implementation
* is based on the RTRlib (http://rpki.realmv6.org/). The BIRD takes over
* |packets.c|, |rtr.c| (inside |rpki.c|), |transport.c|, |tcp_transport.c| and |ssh_transport.c| files
* from RTRlib.
*
* A SSH transport requires LibSSH library. LibSSH is loading dynamically using dlopen
* function.
*/
/*
* TODO list
* - Receive Router Key PDU with End-Entity certificate
* https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-5.10
* It's implemented in RTRlib.
* - Saving EE Certificate
*/
#include <stdlib.h>
#include <netdb.h>
#undef LOCAL_DEBUG
#include "rpki.h"
#include "lib/string.h"
#include "nest/cli.h"
static const char *str_cache_states[] = {
[RPKI_CS_CONNECTING] = "CONNECTING",
[RPKI_CS_ESTABLISHED] = "ESTABLISHED",
[RPKI_CS_RESET] = "RESET",
[RPKI_CS_SYNC] = "SYNC",
[RPKI_CS_FAST_RECONNECT] = "FAST_RECONNECT",
[RPKI_CS_ERROR_NO_DATA_AVAIL] = "ERROR_NO_DATA_AVAIL",
[RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL] = "ERROR_NO_INCR_UPDATE_AVAIL",
[RPKI_CS_ERROR_FATAL] = "ERROR_FATAL",
[RPKI_CS_ERROR_TRANSPORT] = "ERROR_TRANSPORT",
[RPKI_CS_SHUTDOWN] = "SHUTDOWN"
};
const char *
rpki_cache_state_to_str(enum rpki_cache_state state)
{
return str_cache_states[state];
}
/* Return 0 if non-valid transition,
* return 1 if valid transition */
static int
rpki_is_allowed_transition_cache_state(const enum rpki_cache_state old, const enum rpki_cache_state new)
{
switch (new)
{
case RPKI_CS_CONNECTING: return old == RPKI_CS_SHUTDOWN || old == RPKI_CS_ERROR_TRANSPORT || old == RPKI_CS_FAST_RECONNECT;
case RPKI_CS_ESTABLISHED: return old == RPKI_CS_SYNC;
case RPKI_CS_RESET: return old == RPKI_CS_ERROR_NO_DATA_AVAIL || old == RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL;
case RPKI_CS_SYNC: return old == RPKI_CS_RESET || old == RPKI_CS_CONNECTING || old == RPKI_CS_ESTABLISHED;
case RPKI_CS_FAST_RECONNECT: return old == RPKI_CS_ESTABLISHED || old == RPKI_CS_SYNC;
case RPKI_CS_ERROR_NO_DATA_AVAIL: return old == RPKI_CS_SYNC;
case RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL: return old == RPKI_CS_SYNC;
case RPKI_CS_ERROR_FATAL: return 1;
case RPKI_CS_ERROR_TRANSPORT: return 1;
case RPKI_CS_SHUTDOWN: return 1;
}
return 0;
}
static struct proto *
rpki_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
return P;
}
const char *
rpki_get_cache_ident(struct rpki_cache *cache)
{
return rpki_tr_ident(cache->tr_sock);
}
/*
* Timers
*/
void
rpki_schedule_next_refresh(struct rpki_cache *cache)
{
if (cache->state == RPKI_CS_SHUTDOWN)
{
CACHE_DBG(cache, "Stop refreshing");
return;
}
unsigned time_to_wait = cache->refresh_interval;
CACHE_DBG(cache, "Scheduling next refresh after %u seconds", time_to_wait);
tm_start(cache->refresh_timer, time_to_wait);
}
void
rpki_schedule_next_retry(struct rpki_cache *cache)
{
uint time_to_wait = cache->retry_interval;
switch (cache->state)
{
case RPKI_CS_ESTABLISHED:
case RPKI_CS_SYNC:
case RPKI_CS_RESET:
CACHE_DBG(cache, "Stop retrying connection");
break;
default:
CACHE_DBG(cache, "Scheduling next retry after %u seconds", time_to_wait);
tm_start(cache->retry_timer, time_to_wait);
}
}
void
rpki_schedule_next_expire_check(struct rpki_cache *cache)
{
/* minimum time to wait is 1 second */
unsigned time_to_wait = MAX(((int)cache->expire_interval - (int)(now - cache->last_update)), 1);
CACHE_DBG(cache, "Scheduling next expiration check after %u seconds", time_to_wait);
tm_start(cache->expire_timer, time_to_wait);
}
static void
rpki_refresh_hook(struct timer *tm)
{
struct rpki_cache *cache = tm->data;
CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
switch (cache->state)
{
case RPKI_CS_ESTABLISHED:
rpki_cache_change_state(cache, RPKI_CS_SYNC);
break;
case RPKI_CS_SYNC:
rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
break;
default:
break;
}
rpki_schedule_next_refresh(cache);
}
static void
rpki_retry_hook(struct timer *tm)
{
struct rpki_cache *cache = tm->data;
CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
switch (cache->state)
{
case RPKI_CS_ESTABLISHED:
case RPKI_CS_CONNECTING:
case RPKI_CS_SYNC:
case RPKI_CS_SHUTDOWN:
break;
default:
rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
break;
}
rpki_schedule_next_retry(cache);
}
static void
rpki_purge_records_if_outdated(struct rpki_cache *cache)
{
if (cache->last_update == 0)
return;
if ((cache->last_update + cache->expire_interval) < now)
{
CACHE_TRACE(D_EVENTS, cache, "All routes expired");
rpki_table_remove_all(cache);
cache->request_session_id = 1;
cache->serial_number = 0;
cache->last_update = 0;
}
else
{
CACHE_DBG(cache, "No outdated records, remains %d seconds to become obsolete", (int)cache->expire_interval - (int)(now - cache->last_update));
}
}
static void
rpki_expire_hook(struct timer *tm)
{
struct rpki_cache *cache = tm->data;
if (cache->last_update == 0)
return;
CACHE_DBG(cache, ""); /* Show name of function */
rpki_purge_records_if_outdated(cache);
rpki_schedule_next_expire_check(cache);
}
static int
rpki_open_connection(struct rpki_cache *cache)
{
CACHE_TRACE(D_EVENTS, cache, "Opening a connection");
if (rpki_tr_open(cache->tr_sock) == TR_ERROR)
{
rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
return TR_ERROR;
}
return TR_SUCCESS;
}
static void
rpki_close_connection(struct rpki_cache *cache)
{
CACHE_TRACE(D_EVENTS, cache, "Closing a connection");
rpki_tr_close(cache->tr_sock);
}
/**
* rpki_cache_change_state - check and change cache state
* @cache: RPKI cache instance
* @new_state: suggested new state
*
* Validates and makes transition. Does appropriate actions after change
*/
void
rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state)
{
const enum rpki_cache_state old_state = cache->state;
if (old_state == new_state)
return;
if (!rpki_is_allowed_transition_cache_state(old_state, new_state))
{
CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s is not allowed", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state));
ASSERT(0);
return;
}
CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state));
cache->state = new_state;
switch (new_state)
{
case RPKI_CS_CONNECTING:
{
sock *sk = cache->tr_sock->sk;
if (sk == NULL || sk->fd < 0)
rpki_open_connection(cache);
else
rpki_cache_change_state(cache, RPKI_CS_SYNC);
break;
}
case RPKI_CS_ESTABLISHED:
break;
case RPKI_CS_RESET:
/* Resetting RTR connection. */
cache->request_session_id = 1;
cache->serial_number = 0;
rpki_cache_change_state(cache, RPKI_CS_SYNC);
break;
case RPKI_CS_SYNC:
/* Requesting for receive validation records from the RTR server. */
if (cache->request_session_id)
{
/* Change to state RESET, if socket dont has a session_id */
if (rpki_send_reset_query(cache) != RPKI_SUCCESS)
rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
}
else
{
/* if we already have a session_id, send a serial query and start to sync */
if (rpki_send_serial_query(cache) != RPKI_SUCCESS)
rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
}
break;
case RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL:
/* Server was unable to answer the last serial or reset query. */
rpki_purge_records_if_outdated(cache);
rpki_cache_change_state(cache, RPKI_CS_RESET);
break;
case RPKI_CS_ERROR_NO_DATA_AVAIL:
/* No validation records are available on the RTR server. */
rpki_cache_change_state(cache, RPKI_CS_RESET);
break;
case RPKI_CS_ERROR_FATAL:
/* Fatal protocol error occurred. */
cache->request_session_id = 1;
cache->serial_number = 0;
cache->last_update = 0;
rpki_table_remove_all(cache);
/* Fall through */
case RPKI_CS_ERROR_TRANSPORT:
/* Error on the transport socket occurred. */
rpki_close_connection(cache);
rpki_schedule_next_retry(cache);
break;
case RPKI_CS_FAST_RECONNECT:
/* Reconnect without any waiting period */
rpki_close_connection(cache);
rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
break;
case RPKI_CS_SHUTDOWN:
/* RTR Socket is stopped. */
rpki_close_connection(cache);
cache->request_session_id = 1;
cache->serial_number = 0;
cache->last_update = 0;
rpki_table_remove_all(cache);
break;
};
}
/**
* rpki_check_refresh_interval - check validity of refresh interval value
* @seconds: suggested value
*
* Validate value and return NULL if check passed or error message if check failed.
*/
const char *
rpki_check_refresh_interval(uint seconds)
{
if (seconds < 1)
return "Minimum allowed refresh interval is 1 second";
if (seconds > 86400)
return "Maximum allowed refresh interval is 86400 seconds";
return NULL;
}
/**
* rpki_check_retry_interval - check validity of retry interval value
* @seconds: suggested value
*
* Validate value and return NULL if check passed or error message if check failed.
*/
const char *
rpki_check_retry_interval(uint seconds)
{
if (seconds < 1)
return "Minimum allowed retry interval is 1 second";
if (seconds > 7200)
return "Maximum allowed retry interval is 7200 seconds";
return NULL;
}
/**
* rpki_check_expire_interval - check validity of expire interval value
* @seconds: suggested value
*
* Validate value and return NULL if check passed or error message if check failed.
*/
const char *
rpki_check_expire_interval(uint seconds)
{
if (seconds < 600)
return "Minimum allowed expire interval is 600 seconds";
if (seconds > 172800)
return "Maximum allowed expire interval is 172800 seconds";
return NULL;
}
static struct rpki_cache *
rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf)
{
pool *pool = rp_new(p->p.pool, cf->hostname);
struct rpki_cache *cache = mb_allocz(pool, sizeof(struct rpki_cache));
cache->pool = pool;
cache->p = p;
proto_configure_channel(&p->p, &cache->roa4_channel, proto_cf_find_channel(p->p.cf, NET_ROA4));
proto_configure_channel(&p->p, &cache->roa6_channel, proto_cf_find_channel(p->p.cf, NET_ROA6));
cache->state = RPKI_CS_SHUTDOWN;
cache->request_session_id = 1;
cache->serial_number = 0;
cache->last_update = 0;
cache->version = RPKI_MAX_VERSION;
cache->refresh_interval = cf->refresh_interval;
cache->retry_interval = cf->retry_interval;
cache->expire_interval = cf->expire_interval;
cache->retry_timer = tm_new_set(pool, &rpki_retry_hook, cache, 0, 0);
cache->refresh_timer = tm_new_set(pool, &rpki_refresh_hook, cache, 0, 0);
cache->expire_timer = tm_new_set(pool, &rpki_expire_hook, cache, 0, 0);
cache->tr_sock = mb_allocz(pool, sizeof(struct rpki_tr_sock));
cache->tr_sock->cache = cache;
if (cf->ssh)
rpki_tr_ssh_init(cache->tr_sock);
else
rpki_tr_tcp_init(cache->tr_sock);
CACHE_TRACE(D_EVENTS, cache, "Created");
return cache;
}
static void
rpki_free_cache(struct rpki_cache *cache)
{
struct rpki_proto *p = cache->p;
rpki_table_remove_all(cache);
CACHE_TRACE(D_EVENTS, p->cache, "Destroyed");
rfree(cache->pool);
p->cache = NULL;
}
static int
rpki_shutdown(struct proto *P)
{
struct rpki_proto *p = (void *) P;
p->cache = NULL;
/* protocol memory pool will be automatically freed */
return PS_DOWN;
}
static void
rpki_start_cache(struct rpki_cache *cache)
{
rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
}
static void
rpki_replace_cache(struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
{
struct rpki_proto *p = cache->p;
rpki_free_cache(cache);
p->cache = rpki_init_cache(p, new);
rpki_start_cache(p->cache);
}
static void
rpki_fast_reconnect_cache(struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
{
if (cache->state == RPKI_CS_ESTABLISHED)
rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
else
rpki_replace_cache(cache, old, new);
}
/*
* Return 0 if need to restart
* Return 1 if reconfiguration finished successful
*/
static int
rpki_reconfigure_cache(struct rpki_proto *p, struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
{
u8 try_fast_reconnect = 0;
if (!proto_configure_channel(&p->p, &cache->roa4_channel, proto_cf_find_channel(p->p.cf, NET_ROA4)) ||
!proto_configure_channel(&p->p, &cache->roa6_channel, proto_cf_find_channel(p->p.cf, NET_ROA6)))
{
CACHE_TRACE(D_EVENTS, cache, "Channels changed");
return 0;
}
if (strcmp(old->hostname, new->hostname) != 0)
{
CACHE_TRACE(D_EVENTS, cache, "Remote cache server address changed to %s", new->hostname);
goto hard_cache_replace;
}
if (old->port != new->port)
{
CACHE_TRACE(D_EVENTS, cache, "Remote cache server port changed to %u", new->port);
goto hard_cache_replace;
}
if (!!old->ssh != !!new->ssh)
{
CACHE_TRACE(D_EVENTS, cache, "SSH encryption toggled");
goto hard_cache_replace;
}
else if (old->ssh && new->ssh)
{
if ((strcmp(old->ssh->bird_private_key, new->ssh->bird_private_key) != 0) ||
(strcmp(old->ssh->cache_public_key, new->ssh->cache_public_key) != 0) ||
(strcmp(old->ssh->user, new->ssh->user) != 0))
{
CACHE_TRACE(D_EVENTS, cache, "Settings of SSH transport encryption changed");
try_fast_reconnect = 1;
}
}
if (cache->expire_interval != new->expire_interval)
{
cache->expire_interval = new->expire_interval;
CACHE_TRACE(D_EVENTS, cache, "Expire interval changed to %u seconds", cache->expire_interval);
try_fast_reconnect = 1;
}
if (cache->refresh_interval != new->refresh_interval)
{
cache->refresh_interval = new->refresh_interval;
CACHE_TRACE(D_EVENTS, cache, "Refresh interval changed to %u seconds", cache->refresh_interval);
try_fast_reconnect = 1;
}
if (cache->retry_interval != new->retry_interval)
{
cache->retry_interval = new->retry_interval;
CACHE_TRACE(D_EVENTS, cache, "Retry interval changed to %u seconds", cache->retry_interval);
try_fast_reconnect = 1;
}
if (try_fast_reconnect)
rpki_fast_reconnect_cache(cache, new, old);
return 1;
hard_cache_replace:
rpki_replace_cache(cache, new, old);
return 1;
}
/*
* Return 0 if need to restart
* Return 1 if reconfiguration finished successful
*/
static int
rpki_reconfigure_proto(struct rpki_proto *p, struct rpki_config *new_cf, struct rpki_config *old_cf)
{
u8 new = new_cf && new_cf->hostname;
u8 old = old_cf && old_cf->hostname;
struct rpki_cache *cache = p->cache;
if (new && !old)
{
p->cache = rpki_init_cache(p, new_cf);
rpki_start_cache(p->cache);
}
else if (!new && old && cache)
rpki_free_cache(cache);
else if (new && old && cache)
return rpki_reconfigure_cache(p, cache, new_cf, old_cf);
return 1;
}
/*
* Return 0 if need to restart
* Return 1 if reconfiguration finished successful
*/
static int
rpki_reconfigure(struct proto *P, struct proto_config *CF)
{
struct rpki_proto *p = (void *) P;
struct rpki_config *new = (void *) CF;
struct rpki_config *old = (void *) p->p.cf;
P->cf = CF;
if (rpki_reconfigure_proto(p, new, old))
return 1;
P->cf = (void *) old;
return 0;
}
static void
rpki_get_status(struct proto *P, byte *buf)
{
struct rpki_proto *p = (struct rpki_proto *) P;
if (P->proto_state == PS_DOWN)
{
*buf = 0;
return;
}
if (p->cache)
bsprintf(buf, "%s", rpki_cache_state_to_str(p->cache->state));
else
bsprintf(buf, "No cache server configured");
}
static void
rpki_show_proto_info_timer(const char *name, uint num, timer *t)
{
if (t->expires)
cli_msg(-1006, " %-17s %us (remains %us)", name, num, tm_remains(t));
else
cli_msg(-1006, " %-17s %us", name, num);
}
static void
rpki_show_proto_info(struct proto *P)
{
struct rpki_proto *p = (struct rpki_proto *) P;
struct rpki_config *cf = (void *) p->p.cf;
struct rpki_cache *cache = p->cache;
if (cache)
{
cli_msg(-1006, " Remote server: %s", rpki_get_cache_ident(cache));
cli_msg(-1006, " Status: %s", rpki_cache_state_to_str(cache->state));
cli_msg(-1006, " Transport: %s", cf->ssh ? "SSHv2" : "Unprotected over TCP");
cli_msg(-1006, " Protocol version: %u", cache->version);
if (cache->last_update)
cli_msg(-1006, " Last update: before %us", now - cache->last_update);
else
cli_msg(-1006, " Last update: ---");
rpki_show_proto_info_timer("Retry interval:", cache->retry_interval, cache->retry_timer);
rpki_show_proto_info_timer("Refresh interval:", cache->refresh_interval, cache->refresh_timer);
rpki_show_proto_info_timer("Expire interval:", cache->expire_interval, cache->expire_timer);
if (cache->roa4_channel)
channel_show_info(cache->roa4_channel);
else
cli_msg(-1006, " No roa4 channel");
if (cache->roa6_channel)
channel_show_info(cache->roa6_channel);
else
cli_msg(-1006, " No roa6 channel");
}
}
static int
rpki_start(struct proto *P)
{
struct rpki_proto *p = (void *) P;
struct rpki_config *cf = (void *) P->cf;
rpki_reconfigure_proto(p, cf, NULL);
return PS_UP;
}
static void
rpki_postconfig(struct proto_config *CF)
{
/* Define default channel */
if (EMPTY_LIST(CF->channels))
channel_config_new(NULL, CF->net_type, CF);
}
static void
rpki_copy_config(struct proto_config *dest, struct proto_config *src)
{
struct rpki_config *d = (void *) dest;
struct rpki_config *s = (void *) src;
/*
* Make a deep copy.
* The SSH configuration block can be reopened and extended.
*/
if (s->ssh)
{
d->ssh = cfg_alloc(sizeof(struct rpki_config_ssh));
memcpy(d->ssh, s->ssh, sizeof(struct rpki_config_ssh));
}
}
void
rpki_check_config(struct rpki_config *cf)
{
/* Do not check templates at all */
if (cf->c.class == SYM_TEMPLATE)
return;
if (cf->hostname == NULL)
cf_error("Address or hostname of remote cache server must be set");
if (cf->port == 0)
{
if (cf->ssh != NULL)
cf->port = RPKI_SSH_PORT;
else
cf->port = RPKI_PORT;
}
}
struct protocol proto_rpki = {
.name = "RPKI",
.template = "rpki%d",
.preference = DEF_PREF_RPKI,
.proto_size = sizeof(struct rpki_proto),
.config_size = sizeof(struct rpki_config),
.init = rpki_init,
.start = rpki_start,
.postconfig = rpki_postconfig,
.channel_mask = (NB_ROA4 | NB_ROA6),
.show_proto_info = rpki_show_proto_info,
.shutdown = rpki_shutdown,
.copy_config = rpki_copy_config,
.reconfigure = rpki_reconfigure,
.get_status = rpki_get_status,
};