#define PARSER 1
+#include <stdio.h>
+
#include "nest/bird.h"
#include "conf/conf.h"
#include "lib/resource.h"
CF_DEFINES
+static void
+check_u8(unsigned val)
+{
+ if (val > 0xFF)
+ cf_error("Value %d out of range (0-255)", val);
+}
+
static void
check_u16(unsigned val)
{
cf_error("Value %d out of range (0-65535)", val);
}
+static void
+check_file_readability(const char *file_path)
+{
+ FILE *file = fopen(file_path, "r");
+ if (file)
+ fclose(file);
+ else
+ cf_error("File '%s' cannot be open for read: %m", file_path);
+}
+
CF_DECLS
%union {
struct lsadb_show_data *ld;
struct iface *iface;
struct roa_table *rot;
+ struct roa_table_config *rotcf;
void *g;
bird_clock_t time;
struct prefix px;
AC_SUBST(iproutedir)
-all_protocols="$proto_bfd bgp ospf pipe $proto_radv rip static"
+all_protocols="$proto_bfd bgp ospf pipe $proto_radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
if test "$with_protocols" = all ; then
fi
fi
+BIRD_LIBS=
+AC_CHECK_LIB(dl, dlopen, BIRD_LIBS="-ldl")
+AC_SUBST(BIRD_LIBS)
+
CLIENT=
CLIENT_LIBS=
if test "$enable_client" = yes ; then
#define _BIRD_SOCKET_H_
#include <errno.h>
+#include <libssh/libssh.h>
// #include <sys/socket.h>
#include "lib/resource.h"
+struct ssh_sock {
+ char *username; /* (Required) SSH user name */
+ char *server_hostkey_path; /* (Optional) Filepath to the SSH public key of remote side, can be knownhost file */
+ char *client_privkey_path; /* (Optional) Filepath to the SSH private key of BIRD */
+ char *subsystem; /* (Optional) Name of SSH subsytem */
+ ssh_session session; /* Internal */
+ ssh_channel channel; /* Internal */
+ int state; /* Internal */
+#define BIRD_SSH_CONNECT 0 /* Start state */
+#define BIRD_SSH_IS_SERVER_KNOWN 1
+#define BIRD_SSH_USERAUTH_PUBLICKEY_AUTO 2
+#define BIRD_SSH_CHANNEL_NEW 3
+#define BIRD_SSH_CHANNEL_OPEN_SESSION 4
+#define BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM 5
+#define BIRD_SSH_CONNECTION_ESTABLISHED 6 /* Final state */
+};
+
typedef struct birdsock {
resource r;
pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
int type; /* Socket type */
void *data; /* User data */
ip_addr saddr, daddr; /* IPA_NONE = unspecified */
+ char *host; /* Alternative to daddr, NULL = unspecified */
uint sport, dport; /* 0 = unspecified (for IP: protocol type) */
int tos; /* TOS / traffic class, -1 = default */
int priority; /* Local socket priority, -1 = default */
node n;
void *rbuf_alloc, *tbuf_alloc;
char *password; /* Password for MD5 authentication */
- char *err; /* Error message */
+ const char *err; /* Error message */
+ struct ssh_sock *ssh; /* Used in SK_SSH */
} sock;
sock *sock_new(pool *); /* Allocate new socket */
#define SK_MAGIC 7 /* Internal use by sysdep code */
#define SK_UNIX_PASSIVE 8
#define SK_UNIX 9
+#define SK_SSH_ACTIVE 10 /* - - * * - ? - DA = host */
+#define SK_SSH 11
/*
* For SK_UDP or SK_IP sockets setting DA/DP allows to use sk_send(),
%type <ra> r_args
%type <ro> roa_args
%type <rot> roa_table_arg
+%type <rotcf> roa_table_cf
%type <sd> sym_args
%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode roa_mode limit_action tab_sorted tos
%type <ps> proto_patt proto_patt2
}
;
+roa_table_cf:
+ SYM {
+ if ($1->class != SYM_ROA) cf_error("ROA table name expected");
+ $$ = $1->def;
+ }
+ ;
+
+
CF_ADDTO(conf, debug_default)
debug_default:
proto_build(&proto_bfd);
bfd_init_all();
#endif
+#ifdef CONFIG_RPKI
+ proto_build(&proto_rpki);
+#endif
proto_pool = rp_new(&root_pool, "Protocols");
proto_flush_event = ev_new(proto_pool);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static,
- proto_ospf, proto_pipe, proto_bgp, proto_bfd;
+ proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_rpki;
/*
* Routing Protocol Instance
#define ROA_SRC_ANY 0
#define ROA_SRC_CONFIG 1
#define ROA_SRC_DYNAMIC 2
+#define ROA_SRC_RPKI 3
#define ROA_SHOW_ALL 0
#define ROA_SHOW_PX 1
C bgp
C ospf
C pipe
-C rip
C radv
+C rip
+C rpki
C static
S ../nest/rt-dev.c
--- /dev/null
+source=rpki.c packets.c rtr.c tcp_transport.c ssh_transport.c transport.c
+root-rel=../../
+dir-name=proto/rpki
+
+include ../../Rules
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/rpki/rpki.h"
+
+CF_DEFINES
+
+#define RPKI_CFG ((struct rpki_config *) this_proto)
+
+static struct rpki_cache_cfg *this_rpki_cache_cfg;
+
+CF_DECLS
+
+CF_KEYWORDS(RPKI, CACHE, LIST, PREFERENCE, BIRD, PRIVATE, PUBLIC, KEY, SSH, ENCRYPTION, USER)
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, rpki_proto)
+
+rpki_proto:
+rpki_proto_start proto_name '{' rpki_proto_opts '}' rpki_proto_finish
+;
+
+rpki_proto_start:
+proto_start RPKI {
+ this_proto = proto_config_new(&proto_rpki, $1);
+ init_list(&RPKI_CFG->cache_cfg_list);
+}
+;
+
+rpki_proto_finish:
+{
+ if (RPKI_CFG->roa_table_cf == NULL)
+ cf_error("For the RPKI protocol must be specified a roa table");
+};
+
+rpki_proto_opts:
+/* empty */
+| rpki_proto_opts rpki_proto_item ';'
+;
+
+rpki_proto_item:
+proto_item
+| CACHE rpki_cache
+| ROA TABLE roa_table_cf { RPKI_CFG->roa_table_cf = $3; }
+;
+
+rpki_cache:
+rpki_cache_init rpki_cache_addr rpki_optional_cache_opts rpki_cache_finish {
+ add_tail(&RPKI_CFG->cache_cfg_list, &this_rpki_cache_cfg->n);
+}
+;
+
+rpki_cache_finish:
+{
+ if (this_rpki_cache_cfg->port == 0) /* empty? */
+ {
+ if (this_rpki_cache_cfg->ssh != NULL)
+ this_rpki_cache_cfg->port = RPKI_DEFAULT_SSH_PORT;
+ else
+ this_rpki_cache_cfg->port = RPKI_DEFAULT_PORT;
+ }
+}
+;
+
+rpki_cache_init:
+{
+ this_rpki_cache_cfg = rpki_new_cache_cfg();
+}
+;
+
+rpki_cache_addr:
+text {
+ this_rpki_cache_cfg->hostname = $1;
+}
+| ipa {
+ this_rpki_cache_cfg->ip = $1;
+ this_rpki_cache_cfg->hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN+1));
+ bsnprintf(this_rpki_cache_cfg->hostname, INET6_ADDRSTRLEN+1, "%I", this_rpki_cache_cfg->ip);
+}
+;
+
+rpki_optional_cache_opts:
+/* empty */
+| '{' rpki_cache_opts '}'
+;
+
+rpki_cache_opts:
+/* empty */
+| rpki_cache_opts rpki_cache_opts_item ';'
+;
+
+rpki_cache_opts_item:
+PORT expr {
+ check_u16($2);
+ this_rpki_cache_cfg->port = $2;
+}
+| PREFERENCE expr {
+ if ($2 < 1 || $2 > 0xFF)
+ cf_error("Value %d is out of range (1-255)", $2);
+ this_rpki_cache_cfg->preference = $2;
+}
+| SSH ENCRYPTION rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_finish
+;
+
+rpki_transport_ssh_init:
+{
+ this_rpki_cache_cfg->ssh = cfg_allocz(sizeof(struct rpki_cache_ssh_cfg));
+}
+;
+
+rpki_transport_ssh_opts:
+/* empty */
+| rpki_transport_ssh_opts rpki_transport_ssh_item ';'
+;
+
+rpki_transport_ssh_item:
+BIRD PRIVATE KEY text {
+ check_file_readability($4);
+ this_rpki_cache_cfg->ssh->bird_private_key = $4;
+}
+| CACHE PUBLIC KEY text {
+ check_file_readability($4);
+ this_rpki_cache_cfg->ssh->cache_public_key = $4;
+}
+| USER text {
+ this_rpki_cache_cfg->ssh->username = $2;
+}
+;
+
+rpki_transport_ssh_finish:
+{
+#define RPKI_PARSE_CACHE_MISS_SSH_OPT(what) "Miss '" what ";' option in the %s protocol at cache server %s inside the ssh encryption block"
+
+ if (!this_rpki_cache_cfg->ssh->username)
+ cf_error(RPKI_PARSE_CACHE_MISS_SSH_OPT("user \"ssh_username\""), RPKI_CFG->c.name, this_rpki_cache_cfg->hostname);
+}
+
+CF_CODE
+
+CF_END
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "rpki.h"
+
+#include "transport.h"
+#include "packets.h"
+#include "utils.h"
+#include "rtr.h"
+
+enum rpki_transmit_type {
+ RPKI_RECV = 0,
+ RPKI_SEND = 1,
+};
+
+enum pdu_error_type {
+ CORRUPT_DATA = 0,
+ INTERNAL_ERROR = 1,
+ NO_DATA_AVAIL = 2,
+ INVALID_REQUEST = 3,
+ UNSUPPORTED_PROTOCOL_VER = 4,
+ UNSUPPORTED_PDU_TYPE = 5,
+ WITHDRAWAL_OF_UNKNOWN_RECORD = 6,
+ DUPLICATE_ANNOUNCEMENT = 7,
+ PDU_TOO_BIG = 32
+};
+
+enum pdu_type {
+ SERIAL_NOTIFY = 0,
+ SERIAL_QUERY = 1,
+ RESET_QUERY = 2,
+ CACHE_RESPONSE = 3,
+ IPV4_PREFIX = 4,
+ IPV6_PREFIX = 6,
+ END_OF_DATA = 7,
+ CACHE_RESET = 8,
+ ROUTER_KEY = 9,
+ ERROR = 10
+};
+
+static const char *str_pdu_type[] = {
+ [SERIAL_NOTIFY] = "Serial Notify",
+ [SERIAL_QUERY] = "Serial Query",
+ [RESET_QUERY] = "Reset Query",
+ [CACHE_RESPONSE] = "Cache Response",
+ [IPV4_PREFIX] = "IPv4 Prefix",
+ [IPV6_PREFIX] = "IPv6 Prefix",
+ [END_OF_DATA] = "End of Data",
+ [CACHE_RESET] = "Cache Reset",
+ [ROUTER_KEY] = "Router Key",
+ [ERROR] = "Error"
+};
+
+struct pdu_header {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+};
+
+struct pdu_cache_response {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+};
+
+struct pdu_serial_notify {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_serial_query {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_ipv4 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+ uint8_t flags;
+ uint8_t prefix_len;
+ uint8_t max_prefix_len;
+ uint8_t zero;
+ uint32_t prefix;
+ uint32_t asn;
+};
+
+struct pdu_ipv6 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+ uint8_t flags;
+ uint8_t prefix_len;
+ uint8_t max_prefix_len;
+ uint8_t zero;
+ uint32_t prefix[4];
+ uint32_t asn;
+};
+
+struct pdu_error {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t error_code;
+ uint32_t len;
+ uint32_t len_enc_pdu;
+ uint8_t rest[];
+};
+
+/*
+ 0 8 16 24 31
+ .-------------------------------------------.
+ | Protocol | PDU | |
+ | Version | Type | reserved = zero |
+ | 0 | 2 | |
+ +-------------------------------------------+
+ | |
+ | Length=8 |
+ | |
+ `-------------------------------------------'
+ */
+struct pdu_reset_query {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t flags;
+ uint32_t len;
+};
+
+struct pdu_end_of_data_v0 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_end_of_data_v1 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+ uint32_t refresh_interval;
+ uint32_t retry_interval;
+ uint32_t expire_interval;
+};
+
+/* @brief pfx_record.
+ * @param asn Origin AS number.
+ * @param prefix IP prefix.
+ * @param min_len Minimum prefix length.
+ * @param max_len Maximum prefix length.
+ */
+struct pfx_record {
+ uint32_t asn;
+ ip_addr prefix;
+ uint8_t min_len;
+ uint8_t max_len;
+};
+
+static int rtr_send_error_pdu(struct rpki_cache *cache, const void *erroneous_pdu, const uint32_t pdu_len, const enum pdu_error_type error, const char *text, const uint32_t text_len);
+
+static inline enum pdu_type rtr_get_pdu_type(const void *pdu)
+{
+ return *((char *) pdu + 1);
+}
+
+static int
+pfx_table_add(struct rpki_cache *cache, const struct pfx_record *pfxr)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Import %I/%u max %u as %u", pfxr->prefix, pfxr->min_len, pfxr->max_len, pfxr->asn);
+ roa_add_item(cache->p->cf->roa_table_cf->table, pfxr->prefix, pfxr->min_len, pfxr->max_len, pfxr->asn, cache->roa_src);
+ return RTR_SUCCESS;
+}
+
+static int
+pfx_table_remove(struct rpki_cache *cache, const struct pfx_record *pfxr)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Remove %I/%u max %u as %u", pfxr->prefix, pfxr->min_len, pfxr->max_len, pfxr->asn);
+ roa_delete_item(cache->p->cf->roa_table_cf->table, pfxr->prefix, pfxr->min_len, pfxr->max_len, pfxr->asn, cache->roa_src);
+ return RTR_SUCCESS;
+}
+
+void
+pfx_table_src_remove(struct rpki_cache *cache)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Remove all ROA entries learned from %s", get_cache_ident(cache));
+ roa_flush(cache->p->cf->roa_table_cf->table, cache->roa_src);
+}
+
+void
+rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state)
+{
+ const enum rtr_socket_state old_state = rtr_socket->state;
+
+ if (old_state == new_state)
+ return;
+
+ rtr_socket->state = new_state;
+
+ struct rpki_cache *cache = rtr_socket->cache;
+ CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s", rtr_state_to_str(old_state), rtr_state_to_str(new_state));
+
+ switch (new_state)
+ {
+ case RTR_CONNECTING:
+ if (cache->sk == NULL || cache->sk->fd < 0)
+ {
+ if (rpki_open_connection(cache) == TR_SUCCESS)
+ cache->rtr_socket->state = RTR_SYNC; /* Need call a setup the bird socket in io.c loop */
+ }
+ else
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ break;
+
+ case RTR_ESTABLISHED:
+ /* Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */
+ break;
+
+ case RTR_RESET:
+ /* Resetting RTR connection. */
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ break;
+
+ case RTR_SYNC:
+ /* Requesting for receive validation records from the RTR server. */
+ if (rtr_socket->request_session_id)
+ {
+ //change to state RESET, if socket dont has a session_id
+ if (rtr_send_reset_query(cache) != RTR_SUCCESS)
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+ else
+ {
+ //if we already have a session_id, send a serial query and start to sync
+ if (rtr_send_serial_query(cache) != RTR_SUCCESS)
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+ break;
+
+ case RTR_ERROR_NO_INCR_UPDATE_AVAIL:
+ /* Server was unable to answer the last serial or reset query. */
+ rtr_purge_records_if_outdated(cache);
+ /* Fall through */
+
+ case RTR_ERROR_NO_DATA_AVAIL:
+ /* No validation records are available on the RTR server. */
+ rtr_change_socket_state(rtr_socket, RTR_RESET);
+ break;
+
+ case RTR_ERROR_FATAL:
+ /* Fatal protocol error occurred. */
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ pfx_table_src_remove(cache);
+ /* Fall through */
+
+ case RTR_ERROR_TRANSPORT:
+ /* Error on the transport socket occurred. */
+ rpki_close_connection(cache);
+ rtr_schedule_next_retry(cache);
+ break;
+
+ case RTR_FAST_RECONNECT:
+ /* Reconnect without any waiting period */
+ rpki_close_connection(cache);
+ rtr_change_socket_state(rtr_socket, RTR_CONNECTING);
+ break;
+
+ case RTR_SHUTDOWN:
+ /* RTR Socket is stopped. */
+ rpki_close_connection(cache);
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ pfx_table_src_remove(cache);
+ break;
+ };
+}
+
+static void rtr_pdu_to_network_byte_order(void *pdu)
+{
+ struct pdu_header *header = pdu;
+
+ header->reserved = htons(header->reserved);
+ header->len = htonl(header->len);
+
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+ switch (type) {
+ case SERIAL_QUERY:
+ ((struct pdu_serial_query *) pdu)->sn = htonl(((struct pdu_serial_query *) pdu)->sn);
+ break;
+ case ERROR:
+ ((struct pdu_error *) pdu)->len_enc_pdu = htonl(((struct pdu_error *) pdu)->len_enc_pdu);
+ break;
+ default:
+ break;
+ }
+}
+
+static void rtr_pdu_footer_to_host_byte_order(void *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+ struct pdu_header *header = pdu;
+
+ uint32_t addr6[4];
+
+ switch (type)
+ {
+ case SERIAL_NOTIFY:
+ {
+ struct pdu_serial_notify *sn_pdu = pdu;
+ sn_pdu->sn = ntohl(sn_pdu->sn);
+ break;
+ }
+
+ case END_OF_DATA:
+ {
+ struct pdu_end_of_data_v0 *eod0 = pdu;
+ eod0->sn = ntohl(eod0->sn); /* same either for version 1 */
+
+ if (header->ver == RTR_PROTOCOL_VERSION_1)
+ {
+ struct pdu_end_of_data_v1 *eod1 = pdu;
+ eod1->expire_interval = ntohl(eod1->expire_interval);
+ eod1->refresh_interval = ntohl(eod1->refresh_interval);
+ eod1->retry_interval = ntohl(eod1->retry_interval);
+ }
+ break;
+ }
+
+ case IPV4_PREFIX:
+ {
+ struct pdu_ipv4 *ipv4 = pdu;
+ ipv4->prefix = ntohl(ipv4->prefix);
+ ipv4->asn = ntohl(ipv4->asn);
+ break;
+ }
+
+ case IPV6_PREFIX:
+ {
+ struct pdu_ipv6 *ipv6 = pdu;
+ ip6_addr addr6 = ip6_ntoh(ip6_build(ipv6->prefix[0], ipv6->prefix[1], ipv6->prefix[2], ipv6->prefix[3]));
+ memcpy(ipv6->prefix, &addr6, sizeof(ipv6->prefix));
+ ipv6->asn = ntohl(ipv6->asn);
+ break;
+ }
+
+ case ERROR:
+ {
+ struct pdu_error *err = pdu;
+ err->len_enc_pdu = ntohl(err->len_enc_pdu);
+ break;
+ }
+ }
+}
+
+static void rtr_pdu_header_to_host_byte_order(void *pdu)
+{
+ struct pdu_header *header = pdu;
+
+ //The ROUTER_KEY PDU has two 1 Byte fields instead of the 2 Byte reserved field.
+ if (header->type != ROUTER_KEY)
+ {
+ uint16_t reserved_tmp = ntohs(header->reserved);
+ header->reserved = reserved_tmp;
+ }
+
+ uint32_t len_tmp = ntohl(header->len);
+ header->len = len_tmp;
+}
+
+static void
+rpki_log_packet(struct rpki_cache *cache, const void *pdu, const size_t len, const enum rpki_transmit_type action)
+{
+ const char *str_type = str_pdu_type[rtr_get_pdu_type(pdu)];
+ const struct pdu_header *header = pdu;
+
+ /* Append session id and serial number */
+ char additional_info[100];
+ switch (header->type)
+ {
+ case SERIAL_NOTIFY:
+ case SERIAL_QUERY:
+ case END_OF_DATA:
+ bsnprintf(additional_info, 100, "(session id: %u, serial number: %u)", header->reserved, ((struct pdu_end_of_data_v0 *)header)->sn);
+ break;
+
+ case CACHE_RESPONSE:
+ bsnprintf(additional_info, 100, "(session id: %u)", header->reserved);
+ break;
+
+ default:
+ *additional_info = '\0';
+ }
+
+ if (action == RPKI_RECV)
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Receive a %s packet %s", str_type, additional_info);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Send a %s packet %s", str_type, additional_info);
+ }
+
+ int seq = 0;
+ for(const byte *c = pdu; c != pdu + len; c++)
+ {
+ if ((seq % 4) == 0)
+ DBG("%2d: ", seq);
+
+ DBG(" 0x%02X %-3u", *c, *c);
+
+ if ((++seq % 4) == 0)
+ DBG("\n");
+ }
+ if ((seq % 4) != 0)
+ DBG("\n");
+}
+
+static int rtr_send_pdu(struct rpki_cache *cache, const void *pdu, const unsigned len)
+{
+ const struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+ sock *sk = cache->sk;
+
+ if (!sk)
+ {
+ RPKI_WARN(p, "Want send a %s packet, but the bird socket is NULL!", str_pdu_type[rtr_get_pdu_type(pdu)]);
+ ASSERT(0);
+ return RTR_ERROR;
+ }
+
+ if (sk->fd < 0)
+ {
+ RPKI_WARN(p, "Want send a %s packet, but the bird socket FD is %d!", str_pdu_type[rtr_get_pdu_type(pdu)], sk->fd);
+ ASSERT(0);
+ return RTR_ERROR;
+ }
+
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ {
+ RPKI_WARN(p, "Want send a %s packet, but the rtr_socket state is SHUTDOWN!", str_pdu_type[rtr_get_pdu_type(pdu)]);
+ ASSERT(0);
+ return RTR_ERROR;
+ }
+
+ rpki_log_packet(cache, pdu, len, RPKI_SEND);
+
+ byte pdu_converted[len];
+ memcpy(pdu_converted, pdu, len);
+ rtr_pdu_to_network_byte_order(pdu_converted);
+
+ sk->tbuf = pdu_converted;
+ if (!sk_send(sk, len))
+ {
+ DBG("Cannot send just the whole data. It will be sended via a call of tx_hook()");
+ }
+
+ return RTR_SUCCESS;
+}
+
+/*
+ * @param len must <= RTR_MAX_PDU_LEN bytes
+ * @return RTR_SUCCESS, pdu is converted to host order byte
+ * @return RTR_ERROR, error pdu was sent
+ */
+static int
+rtr_check_receive_packet(struct rpki_cache *cache, void *pdu, const size_t len)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+ //error values:
+ // 0 = no_err
+ // 1 = internal error
+ // 2 = unknown pdu type
+ // 4 = pdu to big
+ // 8 = corrupt data
+ // 16 = unknown pdu version
+ int error = RTR_SUCCESS;
+
+ //header in hostbyte order, retain original received pdu, in case we need to detach it to an error pdu
+ struct pdu_header header;
+ memcpy(&header, pdu, sizeof(header));
+ rtr_pdu_header_to_host_byte_order(&header);
+
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ {
+ RPKI_WARN(p, "Received %s packet, but rtr_socket->state == RTR_SHUTDOWN", str_pdu_type[header.type]);
+ ASSERT(rtr_socket->state != RTR_SHUTDOWN);
+ return RTR_ERROR;
+ }
+
+ // Do dont handle error PDUs here, leave this task to rtr_handle_error_pdu()
+ if (header.ver != rtr_socket->version && header.type != ERROR)
+ {
+ // If this is the first PDU we have received -> Downgrade.
+ if (rtr_socket->request_session_id == true && rtr_socket->last_update == 0
+ && header.ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION
+ && header.ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION
+ && header.ver < rtr_socket->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", get_cache_ident(cache), rtr_socket->version, header.ver);
+ rtr_socket->version = header.ver;
+ }
+ else
+ {
+ // If this is not the first PDU we have received, something is wrong with
+ // the server implementation -> Error
+ error = UNSUPPORTED_PROTOCOL_VER;
+ goto error;
+ }
+ }
+
+ if ((header.type > 10) || (header.ver == RTR_PROTOCOL_VERSION_0 && header.type == ROUTER_KEY))
+ {
+ error = UNSUPPORTED_PDU_TYPE;
+ goto error;
+ }
+
+ if (header.len < sizeof(header))
+ {
+ //if header->len is < packet_header = corrupt data received
+ error = CORRUPT_DATA;
+ goto error;
+ }
+ else if (header.len > RPKI_PDU_MAX_LEN)
+ {
+ //PDU too big, > than MAX_PDU_LEN Bytes
+ error = PDU_TOO_BIG;
+ goto error;
+ }
+
+ memcpy(pdu, &header, sizeof(header)); //copy header in host_byte_order to pdu
+ rtr_pdu_footer_to_host_byte_order(pdu);
+
+ if (header.type == IPV4_PREFIX || header.type == IPV6_PREFIX) {
+ if (((struct pdu_ipv4 *) pdu)->zero != 0)
+ CACHE_TRACE(D_PACKETS, cache, "Warning: Zero field of received Prefix PDU doesn't contain 0");
+ }
+
+ rpki_log_packet(cache, pdu, len, RPKI_RECV);
+ return RTR_SUCCESS;
+
+ error:
+ //send error msg to server, including unmodified pdu header(pdu variable instead header)
+ switch (error)
+ {
+ case CORRUPT_DATA:
+ {
+ const char *txt = "Corrupt data received, length value in PDU is too small";
+ CACHE_TRACE(D_PACKETS, cache, "%s", txt);
+ rtr_send_error_pdu(cache, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt));
+ break;
+ }
+
+ case PDU_TOO_BIG:
+ {
+ char txt2[64];
+ snprintf(txt2, sizeof(txt2),"PDU too big, max. PDU size is: %u bytes", RPKI_PDU_MAX_LEN);
+ CACHE_TRACE(D_EVENTS, cache, "%s", txt2);
+ rtr_send_error_pdu(cache, pdu, sizeof(header), CORRUPT_DATA, txt2, strlen(txt2)+1);
+ break;
+ }
+
+ case UNSUPPORTED_PDU_TYPE:
+ CACHE_DBG(cache, "Unsupported PDU type %zu received", header.type);
+ rtr_send_error_pdu(cache, pdu, header.len, UNSUPPORTED_PDU_TYPE, NULL, 0);
+ break;
+
+ case UNSUPPORTED_PROTOCOL_VER:
+ CACHE_TRACE(D_EVENTS, cache, "PDU with unsupported Protocol version received");
+ rtr_send_error_pdu(cache, pdu, header.len, UNSUPPORTED_PROTOCOL_VER, NULL, 0);
+ break;
+
+ default:
+ bug("Uncatched error");
+ }
+
+ return RTR_ERROR;
+}
+
+static int rtr_handle_error_pdu(struct rtr_socket *rtr_socket, const void *buf)
+{
+ struct rpki_cache *cache = rtr_socket->cache;
+ struct rpki_proto *p = cache->p;
+ const struct pdu_error *pdu = buf;
+
+ const uint32_t len_err_txt = ntohl(*((uint32_t *) (pdu->rest + pdu->len_enc_pdu)));
+ if (len_err_txt > 0)
+ {
+ if ((sizeof(pdu->ver) + sizeof(pdu->type) + sizeof(pdu->error_code) + sizeof(pdu->len) + sizeof(pdu->len_enc_pdu) + pdu->len_enc_pdu + 4 + len_err_txt) != pdu->len)
+ CACHE_TRACE(D_PACKETS, cache, "Error: Length of error text contains an incorrect value");
+ else
+ {
+ //assure that the error text contains an terminating \0 char
+ char txt[len_err_txt + 1];
+ char *pdu_txt = (char *) pdu->rest + pdu->len_enc_pdu + 4;
+ snprintf(txt, len_err_txt + 1, "%s", pdu_txt);
+ CACHE_TRACE(D_PACKETS, cache, "Error PDU included the following error msg: \'%s\'", txt);
+ }
+ }
+
+ switch (pdu->error_code)
+ {
+ case CORRUPT_DATA:
+ CACHE_TRACE(D_PACKETS, cache, "Corrupt data received");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+
+ case INTERNAL_ERROR:
+ CACHE_TRACE(D_PACKETS, cache, "Internal error on server-side");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+
+ case NO_DATA_AVAIL:
+ CACHE_TRACE(D_PACKETS, cache, "No data available");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_DATA_AVAIL);
+ break;
+
+ case INVALID_REQUEST:
+ CACHE_TRACE(D_PACKETS, cache, "Invalid request from client");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+
+ case UNSUPPORTED_PROTOCOL_VER:
+ CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version");
+ if (pdu->ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION &&
+ pdu->ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION &&
+ pdu->ver < rtr_socket->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrading from %i to version %i", rtr_socket->version, pdu->ver);
+ rtr_socket->version = pdu->ver;
+ rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \
+ "current version: %i, PDU version: %i", rtr_socket->version, pdu->ver);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+ break;
+
+ case UNSUPPORTED_PDU_TYPE:
+ CACHE_TRACE(D_PACKETS, cache, "Client set unsupported PDU type");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "error unknown, server sent unsupported error code %u", pdu->error_code);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ }
+
+ return RTR_SUCCESS;
+}
+
+static int rtr_handle_cache_response_pdu(struct rpki_cache *cache, char *pdu)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct pdu_cache_response *cr_pdu = (struct pdu_cache_response *) pdu;
+ //set connection session_id
+ if (rtr_socket->request_session_id)
+ {
+ if (rtr_socket->last_update != 0)
+ {
+ //if this isnt the first sync, but we already received records, delete old records in the pfx_table
+ roa_flush(cache->p->cf->roa_table_cf->table, cache->roa_src);
+ rtr_socket->last_update = 0;
+ }
+ rtr_socket->session_id = cr_pdu->session_id;
+ rtr_socket->request_session_id = false;
+ }
+ else
+ {
+ if (rtr_socket->session_id != cr_pdu->session_id)
+ {
+ char txt[100];
+ snprintf(txt, 100, "Wrong session_id %u in Cache Response PDU", cr_pdu->session_id);
+ rtr_send_error_pdu(cache, NULL, 0, CORRUPT_DATA, txt, strlen(txt)+1);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ }
+ return RTR_SUCCESS;
+}
+
+static void
+rtr_prefix_pdu_2_pfx_record(const struct rtr_socket *rtr_socket, const void *pdu, struct pfx_record *pfxr, const enum pdu_type type)
+{
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+ if (type == IPV4_PREFIX)
+ {
+ const struct pdu_ipv4 *ipv4 = pdu;
+ pfxr->prefix = ip4_from_u32(ipv4->prefix);
+ pfxr->asn = ipv4->asn;
+ pfxr->min_len = ipv4->prefix_len;
+ pfxr->max_len = ipv4->max_prefix_len;
+ }
+ else if (type == IPV6_PREFIX)
+ {
+ const struct pdu_ipv6 *ipv6 = pdu;
+ memcpy(&pfxr->prefix, ipv6->prefix, sizeof(pfxr->prefix));
+ pfxr->asn = ipv6->asn;
+ pfxr->min_len = ipv6->prefix_len;
+ pfxr->max_len = ipv6->max_prefix_len;
+ }
+}
+
+static int
+rtr_update_pfx_table(struct rpki_cache *cache, const void *pdu)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+#ifdef IPV6
+ if (type == IPV4_PREFIX)
+#else
+ if (type == IPV6_PREFIX)
+#endif
+ {
+ CACHE_DBG(cache, "Skip %s prefix", (type == IPV4_PREFIX) ? "IPv4" : "IPv6");
+ return RTR_ERROR;
+ }
+
+ struct pfx_record pfxr;
+ size_t pdu_size = (type == IPV4_PREFIX ? sizeof(struct pdu_ipv4) : sizeof(struct pdu_ipv6));
+ rtr_prefix_pdu_2_pfx_record(rtr_socket, pdu, &pfxr, type);
+
+ int rtval;
+ switch (((struct pdu_ipv4 *) pdu)->flags)
+ {
+ case 1:
+ rtval = pfx_table_add(cache, &pfxr);
+ break;
+
+ case 0:
+ rtval = pfx_table_remove(cache, &pfxr);
+ break;
+
+ default:
+ {
+ const char *txt = "Prefix PDU with invalid flags value received";
+ CACHE_DBG(cache, "%s", txt);
+ rtr_send_error_pdu(cache, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt));
+ return RTR_ERROR;
+ }
+ }
+
+ /*
+ if (rtval == PFX_DUPLICATE_RECORD) {
+ CACHE_TRACE(D_EVENTS, cache, "Duplicate Announcement for record: %I/%u max %u as %u, received", pfxr.prefix, pfxr.min_len, pfxr.max_len, pfxr.asn);
+ rtr_send_error_pdu(cache, pdu, pdu_size, DUPLICATE_ANNOUNCEMENT , NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == PFX_RECORD_NOT_FOUND) {
+ CACHE_TRACE(D_EVENTS, cache, "Withdrawal of unknown record");
+ rtr_send_error_pdu(cache, pdu, pdu_size, WITHDRAWAL_OF_UNKNOWN_RECORD, NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == PFX_ERROR) {
+ const char *txt = "PFX_TABLE Error";
+ RPKI_DBG(cache, "%s", txt);
+ rtr_send_error_pdu(cache, pdu, pdu_size, INTERNAL_ERROR, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ */
+
+ return RTR_SUCCESS;
+}
+
+static void
+rtr_handle_end_of_data_pdu(struct rpki_cache *cache, void *pdu)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct pdu_end_of_data_v1 *eod_pdu = pdu;
+ struct rpki_proto *p = cache->p;
+
+ if (eod_pdu->ver == RTR_PROTOCOL_VERSION_1)
+ {
+ rtr_socket->expire_interval = eod_pdu->expire_interval;
+ rtr_socket->refresh_interval = eod_pdu->refresh_interval;
+ rtr_socket->retry_interval = eod_pdu->retry_interval;
+ CACHE_TRACE(D_EVENTS, cache, "New interval values: " \
+ "expire_interval: %us, " \
+ "refresh_interval: %us, " \
+ "retry_interval: %us", \
+ rtr_socket->expire_interval, rtr_socket->refresh_interval, rtr_socket->retry_interval);
+ }
+
+ if (eod_pdu->session_id != rtr_socket->session_id)
+ {
+ char txt[67];
+ snprintf(txt, sizeof(txt),"Expected session_id: %u, received session_id. %u in EOD PDU",rtr_socket->session_id, eod_pdu->session_id);
+ CACHE_TRACE(D_EVENTS, cache, "%s", txt);
+ rtr_send_error_pdu(cache, pdu, eod_pdu->len, CORRUPT_DATA, txt, strlen(txt) + 1);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+
+ rtr_socket->last_update = now;
+ rtr_socket->serial_number = eod_pdu->sn;
+ rtr_change_socket_state(rtr_socket, RTR_ESTABLISHED);
+ rtr_schedule_next_refresh(cache);
+ rtr_schedule_next_expire_check(cache);
+}
+
+static void
+rpki_rx_packet(struct rpki_cache *cache, byte *pdu, uint len)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+ enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ if (rtr_check_receive_packet(cache, pdu, len) == RTR_ERROR)
+ {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return;
+ }
+
+ /* the pdu is in host order already */
+
+ switch (type)
+ {
+ case RESET_QUERY:
+ case SERIAL_QUERY:
+ RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type[type]);
+ break;
+
+ case SERIAL_NOTIFY:
+ /* Need synchronize with cache server right now */
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ break;
+
+ case CACHE_RESPONSE:
+ rtr_handle_cache_response_pdu(cache,pdu);
+ break;
+
+ case IPV4_PREFIX:
+ case IPV6_PREFIX:
+ rtr_update_pfx_table(cache, pdu);
+ break;
+
+ case END_OF_DATA:
+ rtr_handle_end_of_data_pdu(cache, pdu);
+ break;
+
+ case CACHE_RESET:
+ /* The cache may respond to a Serial Query informing the router that the
+ * cache cannot provide an incremental update starting from the Serial
+ * Number specified by the router. The router must decide whether to
+ * issue a Reset Query or switch to a different cache. */
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_INCR_UPDATE_AVAIL);
+ break;
+
+ case ERROR:
+ rtr_handle_error_pdu(cache->rtr_socket, pdu);
+ break;
+
+ case ROUTER_KEY:
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "Received unsupported type of RPKI PDU (%u)", type);
+ };
+}
+
+int
+rpki_rx_hook(struct birdsock *sk, int size)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+
+ byte *pkt_start = sk->rbuf;
+ byte *end = pkt_start + size;
+ unsigned i, len;
+
+ DBG("Rx hook got %d bytes", size);
+
+ while (end >= pkt_start + RPKI_PDU_HEADER_LEN)
+ {
+ struct pdu_header header;
+ memcpy(&header, pkt_start, sizeof(header));
+ rtr_pdu_header_to_host_byte_order(&header);
+
+ if (header.len < RPKI_PDU_HEADER_LEN || header.len > RPKI_PDU_MAX_LEN)
+ {
+ RPKI_WARN(p, "Received invalid packet length %u. Purge the whole receive buffer.", header.len);
+ return 1; /* Purge recv buffer */
+ }
+
+ if (end < pkt_start + header.len)
+ break;
+
+ rpki_rx_packet(cache, pkt_start, header.len);
+
+ /* It is possible that bird socket was freed/closed */
+ if (sk != cache->sk)
+ return 0;
+
+ pkt_start += header.len;
+ }
+
+ if (pkt_start != sk->rbuf)
+ {
+ memmove(sk->rbuf, pkt_start, end - pkt_start);
+ sk->rpos = sk->rbuf + (end - pkt_start);
+ }
+
+ return 0; /* Not purge sk->rbuf */
+}
+
+void
+rpki_err_hook(struct birdsock *sk, int error_num)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+
+ if (error_num && sk->err == NULL)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Connection lost %s: %M", get_cache_ident(cache), error_num);
+ }
+ else
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Connection lost %s: %s", get_cache_ident(cache), sk->err);
+ }
+
+ rtr_change_socket_state(cache->rtr_socket, RTR_ERROR_TRANSPORT);
+}
+
+static int
+rpki_fire_tx(struct rpki_cache *cache)
+{
+ struct rpki_proto *p = cache->p;
+ sock *sk = cache->sk;
+
+ uint bytes_to_send = sk->tpos - sk->tbuf;
+ DBG("Sending %u bytes", bytes_to_send);
+ return sk_send(sk, bytes_to_send);
+}
+
+void
+rpki_kick_tx(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+ struct tr_socket *tr = cache->rtr_socket->tr_socket;
+
+ while (rpki_fire_tx(cache) > 0)
+ ;
+}
+
+void
+rpki_tx_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+ struct tr_socket *tr = cache->rtr_socket->tr_socket;
+
+ while (rpki_fire_tx(cache) > 0)
+ ;
+}
+
+void
+rpki_connected_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+
+ CACHE_TRACE(D_EVENTS, cache, "Connected to %s", get_cache_ident(cache));
+
+ sk->rx_hook = rpki_rx_hook;
+ sk->tx_hook = rpki_tx_hook;
+
+ rtr_change_socket_state(cache->rtr_socket, RTR_CONNECTING);
+}
+
+int rtr_send_error_pdu(struct rpki_cache *cache, const void *erroneous_pdu, const uint32_t pdu_len, const enum pdu_error_type error, const char *text, const uint32_t text_len)
+{
+ const struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+ //dont send errors for erroneous error PDUs
+ if (pdu_len >= 2)
+ {
+ if (rtr_get_pdu_type(erroneous_pdu) == ERROR)
+ return RTR_SUCCESS;
+ }
+
+ unsigned int msg_size = 16 + pdu_len + text_len;
+ char msg[msg_size];
+ struct pdu_header *header = (struct pdu_header *) msg;
+ header->ver = rtr_socket->version;
+ header->type = 10;
+ header->reserved = error;
+ header->len = msg_size;
+
+ memcpy(msg+8, &pdu_len, sizeof(pdu_len));
+ if (pdu_len > 0)
+ memcpy(msg + 12, erroneous_pdu, pdu_len);
+ *(msg + 12 + pdu_len) = htonl(text_len);
+ if (text_len > 0)
+ memcpy(msg+16+pdu_len, text, text_len);
+
+ return rtr_send_pdu(cache, msg, msg_size);
+}
+
+int rtr_send_serial_query(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct pdu_serial_query pdu;
+ pdu.ver = rtr_socket->version;
+ pdu.type = SERIAL_QUERY;
+ pdu.session_id = rtr_socket->session_id;
+ pdu.len = sizeof(pdu);
+ pdu.sn = rtr_socket->serial_number;
+
+ if (rtr_send_pdu(cache, &pdu, sizeof(pdu)) != RTR_SUCCESS) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ }
+ return RTR_SUCCESS;
+}
+
+int rtr_send_reset_query(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ CACHE_TRACE(D_EVENTS, cache, "Sending reset query");
+ struct pdu_reset_query pdu = {
+ .ver = rtr_socket->version,
+ .type = RESET_QUERY,
+ .len = 8,
+ };
+
+ if (rtr_send_pdu(cache, &pdu, sizeof(pdu)) != RTR_SUCCESS) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ }
+ return RTR_SUCCESS;
+}
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file is part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef RTR_PACKETS_H
+#define RTR_PACKETS_H
+#include <arpa/inet.h>
+#include "rtr.h"
+
+#define RPKI_RX_BUFFER_SIZE 65536
+#define RPKI_TX_BUFFER_SIZE 65536
+#define RPKI_PDU_HEADER_LEN 8
+#define RPKI_PDU_MAX_LEN 848 /* Error PDU size is the biggest (has encapsulate PDU inside):
+ * header(8) +
+ * len_of_encapsulated_pdu(4) +
+ * encapsulated_pdu_ipv6(32) +
+ * len_of_text(4) +
+ * utf-8 text(400*2) = 848
+ */
+#define RPKI_RECV_TIMEOUT 60
+#define RPKI_SEND_TIMEOUT 60
+
+void rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state);
+int rtr_sync(struct rpki_cache *cache);
+int rtr_wait_for_sync(struct rpki_cache *cache);
+int rtr_send_serial_query(struct rpki_cache *cache);
+int rtr_send_reset_query(struct rpki_cache *cache);
+int rpki_rx_hook(struct birdsock *sk, int size);
+void rpki_connected_hook(sock *sk);
+void rpki_err_hook(struct birdsock *sk, int size);
+void pfx_table_src_remove(struct rpki_cache *cache);
+
+#endif
--- /dev/null
+/*
+ * 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: The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ */
+
+#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);
+
+ 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,
+};
--- /dev/null
+/*
+ * 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.
+ */
+
+#ifndef _BIRD_RPKI_H_
+#define _BIRD_RPKI_H_
+
+#include "nest/bird.h"
+#include "nest/route.h"
+
+#include "lib/socket.h"
+#include "lib/ip.h"
+
+#include "tcp_transport.h"
+#include "ssh_transport.h"
+
+#include "rtr.h"
+#include "packets.h"
+
+#define RPKI_DEFAULT_PORT 8282
+#define RPKI_DEFAULT_SSH_PORT 22
+#define RPKI_DEFAULT_RETRY_INTERVAL 10
+#define RPKI_DEFAULT_REFRESH_INTERVAL 15
+#define RPKI_DEFAULT_EXPIRE_INTERVAL 30
+#define RPKI_DEFAULT_CACHE_PREFERENCE 1 /* The most important priority */
+
+struct rpki_cache_ssh_cfg {
+ char *bird_private_key; /* Filepath to the BIRD server private key */
+ char *cache_public_key; /* Filepath to the public key of cache server, can be file known_hosts */
+ char *username; /* Username for SSH connection */
+};
+
+/* Used in parsing of configuration file */
+struct rpki_cache_cfg {
+ node n;
+ char *hostname; /* Full domain name of cache server or NULL */
+ ip_addr ip; /* IP address of cache server or IPA_NONE */
+ u16 port; /* Port of cache server */
+ u8 preference; /* Preference: the most prioritized are the lowest numbers and starts with 1 */
+ uint refresh_interval; /* Time interval (in seconds) for refreshing ROA from server */
+ uint expire_interval; /* Time interval (in seconds) */
+ uint retry_interval; /* Time interval (in seconds) for an unreachable server */
+ struct rpki_cache_ssh_cfg *ssh; /* SSH configuration or NULL */
+};
+
+struct rpki_cache {
+ node n;
+ struct rpki_proto *p;
+ struct rpki_cache_cfg *cfg;
+ struct rtr_socket *rtr_socket; /* RTRLib's socket data structure */
+ sock *sk; /* BIRD's socket data structure */
+ timer *retry_timer; /* Timer for Cache server */
+ timer *refresh_timer; /* Timer for Cache server */
+ timer *expire_timer; /* Timer for Cache server */
+ u8 state; /* RPKI_CACHE_STATE_* */
+ u8 roa_src; /* For kicking off all ROA learned from this cache */
+};
+
+struct rpki_cache_group {
+ node n;
+ u8 preference; /* Preference: the most prioritized are the lowest numbers and starts with 1 */
+ list cache_list; /* List of cache servers (struct rpki_cache) * */
+ u8 state; /* RPKI_CACHE_GROUP_STATE_* */
+};
+
+struct rpki_config {
+ struct proto_config c;
+ list cache_cfg_list; /* Unordered list of cache servers configurations (struct rpki_cache_cfg) */
+ struct roa_table_config *roa_table_cf;/* The ROA table for routes importing from cache servers */
+};
+
+struct rpki_proto {
+ struct proto p;
+ struct rpki_config *cf;
+ list group_list; /* Sorted list of cache groups (struct rpki_cache_group) */
+ timer *timer; /* Main timer */
+};
+
+struct rpki_cache_cfg *rpki_new_cache_cfg(void);
+void rpki_init_all(void);
+void rpki_close_connection(struct rpki_cache *cache);
+int rpki_open_connection(struct rpki_cache *cache);
+const char *get_cache_ident(struct rpki_cache *cache);
+
+#define RPKI_LOG(log_level, rpki, msg, args...) \
+ do { \
+ log(log_level "%s: " msg, (rpki)->p.name , ## args); \
+ } while(0)
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+#define CACHE_DBG(cache,msg,args...) \
+ do { \
+ RPKI_LOG(L_DEBUG, (cache)->p, "%s: %s() " msg, get_cache_ident(cache),__func__, ## args); \
+ } while(0)
+#else
+#define CACHE_DBG(cache,msg,args...) do { } while(0)
+#endif
+
+#define RPKI_TRACE(level,rpki,msg,args...) \
+ do { \
+ if ((rpki)->p.debug & level) \
+ RPKI_LOG(L_TRACE, rpki, msg, ## args); \
+ } while(0)
+
+#define CACHE_TRACE(level,cache,msg,args...) \
+ do { \
+ if ((cache)->p->p.debug & level) \
+ RPKI_LOG(L_TRACE, (cache)->p, "%s: " msg, get_cache_ident(cache), ## args); \
+ } while(0)
+
+#define RPKI_WARN(p, msg, args...) RPKI_LOG(L_WARN, p, msg, ## args);
+
+#define RPKI_ERROR(p, msg, args...) RPKI_LOG(L_ERR, p, msg, ## args);
+
+#define RPKI_DIE(p, msg, args...) \
+ do { \
+ RPKI_LOG(L_FATAL, p, msg, ## args); \
+ exit(1); \
+ } while(0)
+
+#endif /* _BIRD_RPKI_H_ */
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <pthread.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "rpki.h"
+
+#include "packets.h"
+#include "rtr.h"
+#include "lib/timer.h"
+
+static const char *rtr_socket_str_states[] = {
+ [RTR_CONNECTING] = "RTR_CONNECTING",
+ [RTR_ESTABLISHED] = "RTR_ESTABLISHED",
+ [RTR_RESET] = "RTR_RESET",
+ [RTR_SYNC] = "RTR_SYNC",
+ [RTR_FAST_RECONNECT] = "RTR_FAST_RECONNECT",
+ [RTR_ERROR_NO_DATA_AVAIL] = "RTR_ERROR_NO_DATA_AVAIL",
+ [RTR_ERROR_NO_INCR_UPDATE_AVAIL] = "RTR_ERROR_NO_INCR_UPDATE_AVAIL",
+ [RTR_ERROR_FATAL] = "RTR_ERROR_FATAL",
+ [RTR_ERROR_TRANSPORT] = "RTR_ERROR_TRANSPORT",
+ [RTR_SHUTDOWN] = "RTR_SHUTDOWN"
+};
+
+void
+rtr_init(struct rtr_socket *rtr_socket, const unsigned int refresh_interval, const unsigned int expire_interval, const unsigned int retry_interval)
+{
+ if(refresh_interval == 0)
+ rtr_socket->refresh_interval = 300;
+ else if (refresh_interval > 3600)
+ {
+ CACHE_TRACE(D_EVENTS, rtr_socket->cache, "The refresh interval %u is too big, setting it to 3600 seconds", refresh_interval);
+ rtr_socket->refresh_interval = 3600;
+ }
+ else
+ rtr_socket->refresh_interval = (refresh_interval > (3600 - RPKI_RECV_TIMEOUT) ? (3600 - RPKI_RECV_TIMEOUT) : refresh_interval);
+
+ rtr_socket->expire_interval = (expire_interval == 0 ? (rtr_socket->refresh_interval * 2) : expire_interval);
+ rtr_socket->retry_interval = (retry_interval == 0) ? 600 : retry_interval;
+
+ rtr_socket->state = RTR_SHUTDOWN;
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ rtr_socket->version = RTR_PROTOCOL_MAX_SUPPORTED_VERSION;
+}
+
+void
+rtr_purge_records_if_outdated(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+ if (rtr_socket->last_update == 0)
+ return;
+
+ if ((rtr_socket->last_update + rtr_socket->expire_interval) < now)
+ {
+ pfx_table_src_remove(cache);
+ CACHE_TRACE(D_EVENTS, cache, "Remove outdated records from pfx_table");
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ }
+ else
+ {
+ CACHE_DBG(cache, "There are no outdated roa records, it remains %u seconds to become obsolete", (now - (rtr_socket->last_update + rtr_socket->expire_interval)));
+ }
+}
+
+void
+rtr_stop(struct rtr_socket *rtr_socket)
+{
+ rtr_change_socket_state(rtr_socket, RTR_SHUTDOWN);
+ CACHE_TRACE(D_EVENTS, rtr_socket->cache, "Socket shut down");
+}
+
+const char *
+rtr_state_to_str(enum rtr_socket_state state)
+{
+ return rtr_socket_str_states[state];
+}
+
+/*
+ * Timers
+ */
+
+void
+rtr_schedule_next_refresh(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+
+ if (cache->rtr_socket->state == RTR_SHUTDOWN)
+ {
+ CACHE_DBG(cache, "Stop refreshing");
+ return;
+ }
+
+ unsigned time_to_wait = MAX(((int)rtr_socket->refresh_interval - (int)(now - rtr_socket->last_update)), 1);
+
+ CACHE_DBG(cache, "Next refresh of cache(%s) will be after %u seconds", tr_ident(rtr_socket->tr_socket), time_to_wait);
+ tm_start(cache->refresh_timer, time_to_wait);
+}
+
+void
+rtr_schedule_next_retry(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+
+ switch (cache->rtr_socket->state)
+ {
+ case RTR_ESTABLISHED:
+ case RTR_SYNC:
+ case RTR_RESET:
+ CACHE_DBG(cache, "Stop retrying connection");
+ break;
+
+ default:
+ CACHE_TRACE(D_EVENTS, cache, "Connection will retry after %u seconds again", cache->rtr_socket->retry_interval);
+ tm_start(cache->retry_timer, cache->rtr_socket->retry_interval);
+ }
+}
+
+void
+rtr_schedule_next_expire_check(struct rpki_cache *cache)
+{
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+
+ unsigned time_to_wait = MAX(((int)rtr_socket->expire_interval - (int)(now - rtr_socket->last_update)), 1);
+
+ CACHE_TRACE(D_EVENTS, cache, "Next ROA expiration check will be after %u seconds again", time_to_wait);
+ tm_stop(cache->expire_timer);
+ tm_start(cache->expire_timer, time_to_wait);
+}
+
+void
+rpki_refresh_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+ switch (rtr_socket->state)
+ {
+ case RTR_ESTABLISHED:
+ CACHE_DBG(cache, "Refreshing");
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ rtr_schedule_next_refresh(cache);
+ break;
+
+ case RTR_CONNECTING:
+ case RTR_SYNC:
+ /* Wait small amout of time to transite state */
+ tm_start(tm, 1);
+ break;
+
+ default:
+ CACHE_DBG(cache, "Stop Refreshing (%s)", rtr_socket_str_states[rtr_socket->state]);
+ break;
+ }
+}
+
+void
+rpki_retry_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+ struct rpki_proto *p = cache->p;
+
+ switch (rtr_socket->state)
+ {
+ case RTR_ESTABLISHED:
+ case RTR_CONNECTING:
+ case RTR_SYNC:
+ case RTR_SHUTDOWN:
+ CACHE_DBG(cache, "Stop Retry Connecting (%s)", rtr_socket_str_states[rtr_socket->state]);
+ break;
+
+ default:
+ CACHE_DBG(cache, "Retry Connecting (%s)", rtr_socket_str_states[rtr_socket->state]);
+ rtr_change_socket_state(rtr_socket, RTR_CONNECTING);
+ break;
+ }
+}
+
+void
+rpki_expire_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+ struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+ if (rtr_socket->last_update == 0)
+ return;
+
+ CACHE_DBG(cache, "Expire Hook");
+
+ rtr_purge_records_if_outdated(cache);
+ rtr_schedule_next_expire_check(cache);
+}
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * @defgroup mod_rtr_h RTR socket
+ * @brief An RTR socket implements the RPKI-RTR protocol scheme.
+ * @details One rtr_socket communicates with a single RPKI-RTR server.
+ * @{
+ */
+
+#ifndef RTR_H
+#define RTR_H
+#include <time.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "transport.h"
+
+#include "nest/bird.h"
+
+static const uint8_t RTR_PROTOCOL_VERSION_0 = 0;
+static const uint8_t RTR_PROTOCOL_VERSION_1 = 1;
+
+static const uint8_t RTR_PROTOCOL_MIN_SUPPORTED_VERSION = 0;
+static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 1;
+
+enum rtr_rtvals {
+ RTR_SUCCESS = 0,
+ RTR_ERROR = -1
+};
+
+/**
+ * @brief States of the RTR socket.
+ */
+enum rtr_socket_state {
+ /** Socket is establishing the transport connection. */
+ RTR_CONNECTING,
+
+ /** Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */
+ RTR_ESTABLISHED,
+
+ /** Resetting RTR connection. */
+ RTR_RESET,
+
+ /** Receiving validation records from the RTR server. */
+ RTR_SYNC,
+
+ /** Reconnect without any waiting period */
+ RTR_FAST_RECONNECT,
+
+ /** No validation records are available on the RTR server. */
+ RTR_ERROR_NO_DATA_AVAIL,
+
+ /** Server was unable to answer the last serial or reset query. */
+ RTR_ERROR_NO_INCR_UPDATE_AVAIL,
+
+ /** Fatal protocol error occurred. */
+ RTR_ERROR_FATAL,
+
+ /** Error on the transport socket occurred. */
+ RTR_ERROR_TRANSPORT,
+
+ /** RTR Socket is stopped. */
+ RTR_SHUTDOWN,
+};
+
+struct rtr_socket;
+
+/**
+ * @brief A function pointer that is called if the state of the rtr socket has changed.
+ */
+typedef void (*rtr_connection_state_fp)(const struct rtr_socket *rtr_socket, const enum rtr_socket_state state, void *connection_state_fp_param);
+
+/**
+ * @brief A RTR socket.
+ * @param tr_socket Pointer to an initialized tr_socket that will be used to communicate with the RTR server.
+ * @param refresh_interval Time period in seconds. Tells the router how long to wait before next attempting to poll the cache, using a Serial Query or
+ * Reset Query PDU.
+ * @param last_update Timestamp of the last validation record update. Is 0 if the pfx_table doesn't stores any
+ * validation reords from this rtr_socket.
+ * @param expire_interval Time period in seconds. Received records are deleted if the client was unable to refresh data for this time period.
+ * If 0 is specified, the expire_interval is twice the refresh_interval.
+ * @param retry_interval Time period in seconds between a faild quary and the next attempt.
+ * @param state Current state of the socket.
+ * @param session_id session_id of the RTR session.
+ * @param request_session_id True, if the rtr_client have to request a new none from the server.
+ * @param serial_number Last serial number of the obtained validation records.
+ */
+struct rtr_socket {
+ struct tr_socket *tr_socket;
+ struct rpki_cache *cache;
+ bird_clock_t last_update;
+ unsigned int retry_interval; /* Use if the cache server is down */
+ unsigned int refresh_interval;
+ unsigned int expire_interval; /* After this period without successfull refresh will be ROAs discard */
+ enum rtr_socket_state state;
+ uint32_t session_id;
+ bool request_session_id;
+ uint32_t serial_number;
+ unsigned int version;
+};
+
+/**
+ * @brief Initializes a rtr_socket.
+ * @param[out] rtr_socket Pointer to the allocated rtr_socket that will be initialized.
+ * @param[in] refresh_interval Interval in seconds between serial queries that are sent to the server. Must be <= 3600
+ * @param[in] expire_interval Stored validation records will be deleted if cache was unable to refresh data for this period.\n
+ * The default value is twice the refresh_interval.
+ */
+void rtr_init(struct rtr_socket *rtr_socket, const unsigned int refresh_interval, const unsigned int expire_interval, const unsigned int retry_interval);
+
+/**
+ * @brief Stops the RTR connection and terminate the transport connection.
+ * @param[in] rtr_socket rtr_socket that will be used.
+ */
+void rtr_stop(struct rtr_socket *rtr_socket);
+
+/**
+ * @brief Converts a rtr_socket_state to a String.
+ * @param[in] state state to convert to a string
+ * @return NULL If state isn't a valid rtr_socket_state
+ * @return !=NULL The rtr_socket_state as String.
+ */
+const char *rtr_state_to_str(enum rtr_socket_state state);
+
+void rpki_retry_hook(struct timer *tm);
+void rpki_expire_hook(struct timer *tm);
+void rpki_refresh_hook(struct timer *tm);
+void rtr_purge_records_if_outdated(struct rpki_cache *cache);
+
+void rtr_schedule_next_refresh(struct rpki_cache *cache);
+void rtr_schedule_next_retry(struct rpki_cache *cache);
+void rtr_schedule_next_expire_check(struct rpki_cache *cache);
+
+#endif
+/* @} */
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include "utils.h"
+#include "ssh_transport.h"
+
+#include "rpki.h"
+
+static int tr_ssh_open(void *tr_ssh_sock);
+static void tr_ssh_close(void *tr_ssh_sock);
+static void tr_ssh_free(struct tr_socket *tr_sock);
+static const char *tr_ssh_ident(void *tr_ssh_sock);
+
+int tr_ssh_open(void *socket)
+{
+ struct tr_ssh_socket *ssh_socket = socket;
+ struct rpki_cache *cache = ssh_socket->cache;
+ struct rpki_proto *p = cache->p;
+
+ sock *s = cache->sk;
+ s->type = SK_SSH_ACTIVE;
+ s->ssh = mb_allocz(s->pool, sizeof(struct ssh_sock));
+ s->ssh->username = cache->cfg->ssh->username;
+ s->ssh->client_privkey_path = cache->cfg->ssh->bird_private_key;
+ s->ssh->server_hostkey_path = cache->cfg->ssh->cache_public_key;
+ s->ssh->subsystem = "rpki-rtr";
+ s->ssh->state = BIRD_SSH_CONNECT;
+
+ if (sk_open(s) != 0)
+ return TR_ERROR;
+
+ return TR_SUCCESS;
+}
+
+void tr_ssh_close(void *tr_ssh_sock)
+{
+ struct tr_ssh_socket *socket = tr_ssh_sock;
+ struct rpki_cache *cache = socket->cache;
+ struct rpki_proto *p = cache->p;
+
+ sock *sk = cache->sk;
+ if (sk && sk->ssh)
+ {
+ if (sk->ssh->channel)
+ {
+ if (ssh_channel_is_open(sk->ssh->channel))
+ ssh_channel_close(sk->ssh->channel);
+ ssh_channel_free(sk->ssh->channel);
+ sk->ssh->channel = NULL;
+ }
+
+ if (sk->ssh->session)
+ {
+ ssh_disconnect(sk->ssh->session);
+ ssh_free(sk->ssh->session);
+ sk->ssh->session = NULL;
+ }
+ }
+}
+
+void tr_ssh_free(struct tr_socket *tr_sock)
+{
+ struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket;
+ struct rpki_cache *cache = tr_ssh_sock->cache;
+ sock *s = cache->sk;
+
+ if (tr_ssh_sock)
+ {
+ if (tr_ssh_sock->ident != NULL)
+ mb_free(tr_ssh_sock->ident);
+ mb_free(tr_ssh_sock);
+ tr_sock->socket = NULL;
+ }
+}
+
+const char *tr_ssh_ident(void *tr_ssh_sock)
+{
+ size_t len;
+ struct tr_ssh_socket *ssh_sock = tr_ssh_sock;
+ struct rpki_cache *cache = ssh_sock->cache;
+
+ assert(ssh_sock != NULL);
+
+ if (ssh_sock->ident != NULL)
+ return ssh_sock->ident;
+
+ const char *username = cache->cfg->ssh->username;
+ const char *host = cache->cfg->hostname;
+
+ len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* <user> + '@' + <host> + ':' + <port> + '\0' */
+ ssh_sock->ident = mb_alloc(cache->p->p.pool, len);
+ if (ssh_sock->ident == NULL)
+ return NULL;
+ snprintf(ssh_sock->ident, len, "%s@%s:%u", username, host, cache->cfg->port);
+ return ssh_sock->ident;
+}
+
+int tr_ssh_init(struct rpki_cache *cache)
+{
+ struct rpki_proto *p = cache->p;
+ struct rpki_cache_cfg *cache_cfg = cache->cfg;
+ struct tr_socket *tr_socket = cache->rtr_socket->tr_socket;
+
+ tr_socket->close_fp = &tr_ssh_close;
+ tr_socket->free_fp = &tr_ssh_free;
+ tr_socket->open_fp = &tr_ssh_open;
+ tr_socket->ident_fp = &tr_ssh_ident;
+
+ tr_socket->socket = mb_allocz(p->p.pool, sizeof(struct tr_ssh_socket));
+ struct tr_ssh_socket *ssh = tr_socket->socket;
+
+ ssh->cache = cache;
+
+ return TR_SUCCESS;
+}
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * @defgroup mod_ssh_transport_h SSH transport socket
+ * @ingroup mod_transport_h
+ * @brief An implementation of the SSH protocol for the RTR transport.
+ * @details This transport implementation uses libssh
+ * (http://www.libssh.org/) for all ssh specific operations.\n
+ * See @ref mod_transport_h "transport interface" for a list of supported
+ * operations.
+ *
+ * @{
+ *
+ * @example ssh_tr.c
+ * Example of how to open a SSH transport connection.
+ */
+
+#ifndef SSH_TRANSPORT_H
+#define SSH_TRANSPORT_H
+#include <libssh/libssh.h>
+#include "transport.h"
+
+/**
+ * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket.
+ * @param host Hostname or IP address to connect to.
+ * @param port Port to connect to.
+ * @param bindaddr Hostname or IP address to connect from. NULL for
+ * determination by OS.
+ * @param username Username for authentication.
+ * @param server_hostkey_path Path to public SSH key of the server or NULL to
+ don't verify host authenticity.
+ * @param client_privkey_path Path to private key of the authentication keypair
+ * or NULL to use ~/.ssh/id_rsa.
+ */
+struct tr_ssh_config {
+ char *host;
+ unsigned int port;
+ char *username;
+};
+
+struct tr_ssh_socket {
+ struct rpki_cache *cache;
+ struct tr_ssh_config config;
+ char *ident;
+};
+
+/**
+ * @brief Initializes the tr_socket struct for a SSH connection.
+ * @param[in] config SSH configuration for the connection.
+ * @param[out] socket Initialized transport socket.
+ * @returns TR_SUCCESS On success.
+ * @returns TR_ERROR On error.
+ */
+int tr_ssh_init(struct rpki_cache *cache);
+
+#endif
+/* @} */
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "rpki.h"
+
+#include "tcp_transport.h"
+#include "lib/unix.h"
+
+
+static int tr_tcp_open(void *tr_tcp_sock);
+static void tr_tcp_close(void *tr_tcp_sock);
+static void tr_tcp_free(struct tr_socket *tr_sock);
+static const char *tr_tcp_ident(void *socket);
+
+int tr_tcp_open(void *tr_tcp_sock)
+{
+ struct tr_tcp_socket *tcp_socket = tr_tcp_sock;
+ struct rpki_cache *cache = tcp_socket->cache;
+ struct rpki_proto *p = cache->p;
+
+ sock *sk = cache->sk;
+ sk->type = SK_TCP_ACTIVE;
+
+ if (sk_open(sk) != 0)
+ return TR_ERROR;
+
+ return TR_SUCCESS;
+}
+
+void tr_tcp_close(void *tr_tcp_sock)
+{
+ struct tr_tcp_socket *tcp_socket = tr_tcp_sock;
+ struct rpki_cache *cache = tcp_socket->cache;
+ struct rpki_proto *p = cache->p;
+
+ sock *s = cache->sk;
+ if (s && s->fd > 0)
+ {
+ /* TODO: ??? */
+ }
+}
+
+void tr_tcp_free(struct tr_socket *tr_sock)
+{
+ struct tr_tcp_socket *tcp_sock = tr_sock->socket;
+
+ if (tcp_sock)
+ {
+ if (tcp_sock->ident != NULL)
+ mb_free(tcp_sock->ident);
+ tr_sock->socket = NULL;
+ mb_free(tcp_sock);
+ }
+}
+
+const char *tr_tcp_ident(void *socket)
+{
+ assert(socket != NULL);
+
+ struct tr_tcp_socket *sock = socket;
+ struct rpki_proto *p = sock->cache->p;
+
+ if (sock->ident != NULL)
+ return sock->ident;
+
+ size_t colon_and_port_len = 6; /* max ":65535" */
+ size_t ident_len;
+ if (sock->config.host)
+ ident_len = strlen(sock->config.host) + colon_and_port_len + 1;
+ else
+ ident_len = STD_ADDRESS_P_LENGTH + colon_and_port_len + 1;
+
+ sock->ident = mb_allocz(p->p.pool, ident_len);
+ if (sock->ident == NULL)
+ return NULL;
+
+ if (sock->config.host)
+ bsnprintf(sock->ident, ident_len, "%s:%u", sock->config.host, sock->config.port);
+ else
+ bsnprintf(sock->ident, ident_len, "%I:%u", sock->config.ip, sock->config.port);
+
+ return sock->ident;
+}
+
+/*
+ * Fulfill the (ip_addr) tcp_socket->config.ip
+ * Return TR_SUCCESS or TR_ERROR
+ */
+static int
+fulfill_ip_addr(struct tr_tcp_socket *tcp_socket)
+{
+ struct rpki_proto *p = tcp_socket->cache->p;
+
+ struct addrinfo hints;
+ struct addrinfo *res;
+ struct addrinfo *bind_addrinfo = NULL;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ char port_buf[6]; /* max is "65535" + '\0' */
+ snprintf(port_buf, sizeof(port_buf), "%u", tcp_socket->config.port);
+
+ if (getaddrinfo(tcp_socket->config.host, port_buf, &hints, &res) != 0)
+ {
+ RPKI_ERROR(p, "getaddrinfo error, %s", tcp_socket, gai_strerror(errno));
+ return TR_ERROR;
+ }
+
+ sockaddr sa = {
+ .sa = *res->ai_addr,
+ };
+
+ uint unused;
+ sockaddr_read(&sa, res->ai_family, &tcp_socket->config.ip, NULL, &unused);
+
+ freeaddrinfo(res);
+ return TR_SUCCESS;
+}
+
+int tr_tcp_init(struct rpki_cache *cache)
+{
+ struct rpki_proto *p = cache->p;
+ struct rpki_cache_cfg *cache_cfg = cache->cfg;
+ struct tr_socket *tr_socket = cache->rtr_socket->tr_socket;
+
+ tr_socket->close_fp = &tr_tcp_close;
+ tr_socket->free_fp = &tr_tcp_free;
+ tr_socket->open_fp = &tr_tcp_open;
+ tr_socket->ident_fp = &tr_tcp_ident;
+
+ tr_socket->socket = mb_allocz(p->p.pool, sizeof(struct tr_tcp_socket));
+ struct tr_tcp_socket *tcp = tr_socket->socket;
+
+ tcp->cache = cache;
+ tcp->config.host = cache_cfg->hostname;
+ tcp->config.ip = cache_cfg->ip;
+ tcp->config.port = cache_cfg->port;
+
+ assert(ipa_nonzero(tcp->config.ip) || tcp->config.host != NULL);
+ if (ipa_zero(tcp->config.ip))
+ {
+ if (fulfill_ip_addr(tcp) == TR_ERROR)
+ return TR_ERROR;
+ }
+
+ return TR_SUCCESS;
+}
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * @defgroup mod_tcp_transport_h TCP transport socket
+ * @ingroup mod_transport_h
+ * @brief An implementation of the TCP protocol for the RTR transport.
+ * See @ref mod_transport_h "transport interface" for a list of supported operations.
+ *
+ * @{
+ */
+
+#ifndef RTR_TCP_TRANSPORT_H
+#define RTR_TCP_TRANSPORT_H
+#include "transport.h"
+#include "nest/bird.h"
+#include "lib/ip.h"
+
+/**
+ * @brief A tr_tcp_config struct holds configuration for a TCP connection.
+ * @param host Hostname or IP address to connect to.
+ * @param port Port to connect to.
+ * @param bindaddr Hostname or IP address to connect from. NULL for
+ * determination by OS.
+ * to use the source address of the system's default route to the server
+ */
+struct tr_tcp_config {
+ ip_addr ip; char *host; /* at least one of @ip or @host must be defined */
+ uint port;
+ char *bindaddr; /* TODO: NEED THIS? */
+};
+
+struct tr_tcp_socket {
+ struct rpki_cache *cache;
+ struct tr_tcp_config config;
+ char *ident;
+};
+
+/**
+ * @brief Initializes the tr_socket struct for a TCP connection.
+ * @param[in] config TCP configuration for the connection.
+ * @param[out] socket Initialized transport socket.
+ * @returns TR_SUCCESS On success.
+ * @returns TR_ERROR On error.
+ */
+int tr_tcp_init(struct rpki_cache *cache);
+#endif
+/* @} */
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "rpki.h"
+#include "transport.h"
+
+inline int tr_open(struct tr_socket *socket)
+{
+ return socket->open_fp(socket->socket);
+}
+
+inline void tr_close(struct tr_socket *socket)
+{
+ socket->close_fp(socket->socket);
+}
+
+inline void tr_free(struct tr_socket *socket)
+{
+ socket->free_fp(socket);
+}
+
+inline const char *tr_ident(struct tr_socket *socket)
+{
+ return socket->ident_fp(socket->socket);
+}
--- /dev/null
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * This file was part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * @defgroup mod_transport_h Transport sockets
+ * @brief The RTR transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RTR server and client.
+ * @details Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * tr_tcp_init()).\n
+ * The tr_* functions call the corresponding function pointers, which are
+ * passed in the tr_socket struct, and forward the remaining arguments.
+ *
+ * @{
+ */
+
+#ifndef RTR_TRANSPORT_H
+#define RTR_TRANSPORT_H
+#include <time.h>
+
+/**
+ * @brief The return values for tr_ functions.
+ */
+enum tr_rtvals {
+ /** @brief Operation was successfull. */
+ TR_SUCCESS = 0,
+
+ /** Error occured. */
+ TR_ERROR = -1,
+
+ /** No data is available on the socket. */
+ TR_WOULDBLOCK = -2,
+
+ /** Call was interrupted from a signal */
+ TR_INTR = -3,
+
+ /** Connection closed */
+ TR_CLOSED = -4
+};
+
+struct tr_socket;
+
+/**
+ * @brief A transport socket datastructure.
+ *
+ * @param socket A pointer to a technology specific socket.
+ * @param open_fp Pointer to a function that establishes the socket connection.
+ * @param close_fp Pointer to a function that closes the socket.
+ * @param free_fp Pointer to a function that frees all memory allocated with this socket.
+ */
+struct tr_socket {
+ void *socket;
+ int (*open_fp)(void *socket) ;
+ void (*close_fp)(void *socket) ;
+ void (*free_fp)(struct tr_socket *tr_sock);
+ const char *(*ident_fp)(void *socket);
+};
+
+/**
+ * @brief Establish the connection.
+ * @param[in] socket Socket that will be used.
+ * @return TR_SUCCESS On success.
+ * @return TR_ERROR On error.
+ */
+int tr_open(struct tr_socket *socket);
+
+/**
+ * @brief Close the socket connection.
+ * @param[in] socket Socket that will be closed.
+ */
+void tr_close(struct tr_socket *socket);
+
+/**
+ * @brief Deallocates all memory that the passed socket uses.
+ * Socket have to be closed before.
+ * @param[in] socket which will be freed.
+ */
+void tr_free(struct tr_socket *socket);
+
+/**
+ * Returns an identifier for the socket endpoint, eg host:port.
+ * @param[in] socket
+ * return Pointer to a \0 terminated String
+ * return NULL on error
+ */
+const char *tr_ident(struct tr_socket *socket);
+
+#endif
+/* @} */
#undef CONFIG_BGP
#undef CONFIG_OSPF
#undef CONFIG_PIPE
+#undef CONFIG_RPKI
/* We use multithreading */
#undef USE_PTHREADS
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/icmp6.h>
+#include <libssh/libssh.h>
#include "nest/bird.h"
#include "lib/lists.h"
}
}
+static void
+sk_ssh_free(struct ssh_sock *ssh)
+{
+ if (ssh->channel)
+ {
+ if (ssh_channel_is_open(ssh->channel))
+ ssh_channel_close(ssh->channel);
+ ssh_channel_free(ssh->channel);
+ ssh->channel = NULL;
+ }
+
+ if (ssh->session)
+ {
+ ssh_disconnect(ssh->session);
+ ssh_free(ssh->session);
+ ssh->session = NULL;
+ }
+}
+
static void
sk_free(resource *r)
{
sock *s = (sock *) r;
sk_free_bufs(s);
+ if (s->type == SK_SSH || s->type == SK_SSH_ACTIVE)
+ sk_ssh_free(s->ssh);
+
if (s->fd >= 0)
{
close(s->fd);
int y = 1;
int fd = s->fd;
+ if (s->type == SK_SSH_ACTIVE)
+ return 0;
+
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
ERR("O_NONBLOCK");
s->tx_hook(s);
}
+static void
+sk_ssh_connected(sock *s)
+{
+ sk_alloc_bufs(s);
+ s->type = SK_SSH;
+ s->tx_hook(s);
+}
+
static int
sk_passive_connected(sock *s, int type)
{
return 1;
}
+/*
+ * Return SSH_OK or SSH_AGAIN or SSH_ERROR
+ */
+static int
+sk_ssh_connect(sock *s)
+{
+ s->fd = ssh_get_fd(s->ssh->session);
+
+ /* Big fall thru automat */
+ switch (s->ssh->state)
+ {
+ case BIRD_SSH_CONNECT:
+ {
+ switch (ssh_connect(s->ssh->session))
+ {
+ case SSH_AGAIN:
+ return SSH_AGAIN;
+
+ case SSH_OK:
+ break;
+
+ default:
+ return SSH_ERROR;
+ }
+ }
+
+ case BIRD_SSH_IS_SERVER_KNOWN:
+ {
+ s->ssh->state = BIRD_SSH_IS_SERVER_KNOWN;
+
+ if (s->ssh->server_hostkey_path)
+ {
+ int server_identity_is_ok = 1;
+
+ /* Check server identity */
+ switch (ssh_is_server_known(s->ssh->session))
+ {
+#define LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s,msg,args...) log(L_WARN "SSH Identity %s@%s:%u: " msg, (s)->ssh->username, (s)->host, (s)->dport, ## args);
+ case SSH_SERVER_KNOWN_OK:
+ /* The server is known and has not changed. */
+ break;
+
+ case SSH_SERVER_NOT_KNOWN:
+ LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server is unknown, its public key was not found in the known host file %s", s->ssh->server_hostkey_path);
+ break;
+
+ case SSH_SERVER_KNOWN_CHANGED:
+ LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server key has changed. Either you are under attack or the administrator changed the key.");
+ server_identity_is_ok = 0;
+ break;
+
+ case SSH_SERVER_FILE_NOT_FOUND:
+ LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The known host file %s does not exist", s->ssh->server_hostkey_path);
+ server_identity_is_ok = 0;
+ break;
+
+ case SSH_SERVER_ERROR:
+ LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "Some error happened");
+ server_identity_is_ok = 0;
+ break;
+
+ case SSH_SERVER_FOUND_OTHER:
+ LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server gave use a key of a type while we had an other type recorded. " \
+ "It is a possible attack.");
+ server_identity_is_ok = 0;
+ break;
+ }
+
+ if (!server_identity_is_ok)
+ return SSH_ERROR;
+ }
+ }
+
+ case BIRD_SSH_USERAUTH_PUBLICKEY_AUTO:
+ {
+ s->ssh->state = BIRD_SSH_USERAUTH_PUBLICKEY_AUTO;
+ switch (ssh_userauth_publickey_auto(s->ssh->session, NULL, NULL))
+ {
+ case SSH_AUTH_AGAIN:
+ return SSH_AGAIN;
+
+ case SSH_AUTH_SUCCESS:
+ break;
+
+ default:
+ return SSH_ERROR;
+ }
+ }
+
+ case BIRD_SSH_CHANNEL_NEW:
+ {
+ s->ssh->state = BIRD_SSH_CHANNEL_NEW;
+ s->ssh->channel = ssh_channel_new(s->ssh->session);
+ if (s->ssh->channel == NULL)
+ return SSH_ERROR;
+ }
+
+ case BIRD_SSH_CHANNEL_OPEN_SESSION:
+ {
+ s->ssh->state = BIRD_SSH_CHANNEL_OPEN_SESSION;
+ switch (ssh_channel_open_session(s->ssh->channel))
+ {
+ case SSH_AGAIN:
+ return SSH_AGAIN;
+
+ case SSH_OK:
+ break;
+
+ default:
+ return SSH_ERROR;
+ }
+ }
+
+ case BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM:
+ {
+ s->ssh->state = BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM;
+ if (s->ssh->subsystem)
+ {
+ switch (ssh_channel_request_subsystem(s->ssh->channel, s->ssh->subsystem))
+ {
+ case SSH_AGAIN:
+ return SSH_AGAIN;
+
+ case SSH_OK:
+ break;
+
+ default:
+ return SSH_ERROR;
+ }
+ }
+ }
+
+ case BIRD_SSH_CONNECTION_ESTABLISHED:
+ s->ssh->state = BIRD_SSH_CONNECTION_ESTABLISHED;
+ }
+
+ return SSH_OK;
+}
+
+/*
+ * Return file descriptor number if success
+ * Return -1 if failed
+ */
+static int
+sk_open_ssh(sock *s)
+{
+ if (!s->ssh)
+ bug("sk_open() sock->ssh is not allocated");
+
+ s->ssh->session = ssh_new();
+ if (s->ssh->session == NULL)
+ ERR2("Cannot create a ssh session");
+
+ const int verbosity = SSH_LOG_NOLOG;
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_HOST, s->host);
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_PORT, &(s->dport));
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_USER, s->ssh->username);
+
+ if (s->ssh->server_hostkey_path)
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_KNOWNHOSTS, s->ssh->server_hostkey_path);
+
+ if (s->ssh->client_privkey_path)
+ ssh_options_set(s->ssh->session, SSH_OPTIONS_IDENTITY, s->ssh->client_privkey_path);
+
+ ssh_set_blocking(s->ssh->session, 0);
+
+ switch (sk_ssh_connect(s))
+ {
+ case SSH_AGAIN:
+ break;
+
+ case SSH_OK:
+ sk_ssh_connected(s);
+ break;
+
+ case SSH_ERROR:
+ ERR2(ssh_get_error(s->ssh->session));
+ break;
+ }
+
+ return ssh_get_fd(s->ssh->session);
+
+ err:
+ return -1;
+}
+
/**
* sk_open - open a socket
* @s: socket
do_bind = bind_port || ipa_nonzero(bind_addr);
break;
+ case SK_SSH_ACTIVE:
+ s->ttx = ""; /* Force s->ttx != s->tpos */
+ fd = sk_open_ssh(s);
+ break;
+
case SK_UDP:
fd = socket(af, SOCK_DGRAM, IPPROTO_UDP);
bind_port = s->sport;
ERR2("listen");
break;
+ case SK_SSH_ACTIVE:
case SK_MAGIC:
break;
if (!(s->flags & SKF_THREAD))
sk_insert(s);
+
return 0;
err:
reset_tx_buffer(s);
return 1;
+ case SK_SSH:
+ while (s->ttx != s->tpos)
+ {
+ e = ssh_channel_write(s->ssh->channel, s->ttx, s->tpos - s->ttx);
+
+ if (e < 0)
+ {
+ s->err = ssh_get_error(s->ssh->session);
+ s->err_hook(s, ssh_get_error_code(s->ssh->session));
+
+ reset_tx_buffer(s);
+ /* EPIPE is just a connection close notification during TX */
+ s->err_hook(s, (errno != EPIPE) ? errno : 0);
+ return -1;
+ }
+ s->ttx += e;
+ }
+ reset_tx_buffer(s);
+ return 1;
+
case SK_UDP:
case SK_IP:
{
reset_tx_buffer(s);
return 1;
}
+
default:
bug("sk_maybe_write: unknown socket type %d", s->type);
}
}
*/
+static void
+call_rx_hook(sock *s, int size)
+{
+ if (s->rx_hook(s, size))
+ {
+ /* We need to be careful since the socket could have been deleted by the hook */
+ if (current_sock == s)
+ s->rpos = s->rbuf;
+ }
+}
+
+static int
+sk_read_ssh(sock *s)
+{
+ ssh_channel rchans[2] = { s->ssh->channel, NULL };
+ struct timeval timev = { 1, 0 };
+
+ if (ssh_channel_select(rchans, NULL, NULL, &timev) == SSH_EINTR)
+ return 1; /* Try again */
+
+ if (ssh_channel_is_eof(s->ssh->channel) != 0)
+ {
+ /* The remote side is closing the connection */
+ s->err_hook(s, 0);
+ return 0;
+ }
+
+ if (rchans[0] == NULL)
+ return 0; /* No data is available on the socket */
+
+ const uint used_bytes = s->rpos - s->rbuf;
+ const int read_bytes = ssh_channel_read_nonblocking(s->ssh->channel, s->rpos, s->rbsize - used_bytes, 0);
+ if (read_bytes > 0)
+ {
+ /* Received data */
+ s->rpos += read_bytes;
+ call_rx_hook(s, used_bytes + read_bytes);
+ return 1;
+ }
+ else if (read_bytes == 0)
+ {
+ if (ssh_channel_is_eof(s->ssh->channel) != 0)
+ {
+ /* The remote side is closing the connection */
+ s->err_hook(s, 0);
+ }
+ }
+ else
+ {
+ s->err = ssh_get_error(s->ssh->session);
+ s->err_hook(s, ssh_get_error_code(s->ssh->session));
+ }
+
+ return 0; /* No data is available on the socket */
+}
+
/* sk_read() and sk_write() are called from BFD's event loop */
int
else
{
s->rpos += c;
- if (s->rx_hook(s, s->rpos - s->rbuf))
- {
- /* We need to be careful since the socket could have been deleted by the hook */
- if (current_sock == s)
- s->rpos = s->rbuf;
- }
+ call_rx_hook(s, s->rpos - s->rbuf);
return 1;
}
return 0;
}
+ case SK_SSH:
+ return sk_read_ssh(s);
+
case SK_MAGIC:
return s->rx_hook(s, 0);
return 0;
}
+ case SK_SSH_ACTIVE:
+ {
+ switch (sk_ssh_connect(s))
+ {
+ case SSH_OK:
+ sk_ssh_connected(s);
+ break;
+
+ case SSH_AGAIN:
+ return 1;
+
+ case SSH_ERROR:
+ s->err = ssh_get_error(s->ssh->session);
+ s->err_hook(s, ssh_get_error_code(s->ssh->session));
+ break;
+ }
+ return 0;
+ }
+
default:
if (s->ttx != s->tpos && sk_maybe_write(s) > 0)
{
set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done
$(exedir)/bird: $(bird-dep)
- @echo LD $(LDFLAGS) -o $@ $^ $(LIBS)
- @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+ @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) $(BIRD_LIBS)
+ @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(BIRD_LIBS)
$(exedir)/birdc: $(birdc-dep)
@echo LD $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS)
CFLAGS=$(CPPFLAGS) @CFLAGS@
LDFLAGS=@LDFLAGS@
LIBS=@LIBS@
+BIRD_LIBS=@BIRD_LIBS@
CLIENT_LIBS=@CLIENT_LIBS@
CC=@CC@
M4=@M4@