summaryrefslogtreecommitdiff
path: root/proto/rpki
diff options
context:
space:
mode:
Diffstat (limited to 'proto/rpki')
-rw-r--r--proto/rpki/Doc5
-rw-r--r--proto/rpki/Makefile6
-rw-r--r--proto/rpki/config.Y144
-rw-r--r--proto/rpki/packets.c1073
-rw-r--r--proto/rpki/packets.h45
-rw-r--r--proto/rpki/rpki.c927
-rw-r--r--proto/rpki/rpki.h165
-rw-r--r--proto/rpki/ssh_transport.c75
-rw-r--r--proto/rpki/tcp_transport.c78
-rw-r--r--proto/rpki/transport.c135
-rw-r--r--proto/rpki/transport.h79
11 files changed, 2732 insertions, 0 deletions
diff --git a/proto/rpki/Doc b/proto/rpki/Doc
new file mode 100644
index 00000000..d1d1bf55
--- /dev/null
+++ b/proto/rpki/Doc
@@ -0,0 +1,5 @@
+S rpki.c
+S packets.c
+S transport.c
+S tcp_transport.c
+S ssh_transport.c
diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile
new file mode 100644
index 00000000..eb09b7df
--- /dev/null
+++ b/proto/rpki/Makefile
@@ -0,0 +1,6 @@
+src := rpki.c packets.c tcp_transport.c ssh_transport.c transport.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files) \ No newline at end of file
diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y
new file mode 100644
index 00000000..39fdfd01
--- /dev/null
+++ b/proto/rpki/config.Y
@@ -0,0 +1,144 @@
+/*
+ * 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)
+#define RPKI_TR_SSH_CFG ((struct rpki_tr_ssh_config *) RPKI_CFG->tr_config.spec)
+
+static void
+rpki_check_unused_hostname(void)
+{
+ if (RPKI_CFG->hostname != NULL)
+ cf_error("Only one cache server per protocol allowed");
+}
+
+static void
+rpki_check_unused_transport(void)
+{
+ if (RPKI_CFG->tr_config.spec != NULL)
+ cf_error("At the most one transport per protocol allowed");
+}
+
+CF_DECLS
+
+CF_KEYWORDS(RPKI, REMOTE, BIRD, PRIVATE, PUBLIC, KEY, TCP, SSH, TRANSPORT, USER,
+ RETRY, REFRESH, EXPIRE, KEEP)
+
+%type <i> rpki_keep_interval
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, rpki_proto)
+
+rpki_proto_start: proto_start RPKI {
+ this_proto = proto_config_new(&proto_rpki, $1);
+ RPKI_CFG->retry_interval = RPKI_RETRY_INTERVAL;
+ RPKI_CFG->refresh_interval = RPKI_REFRESH_INTERVAL;
+ RPKI_CFG->expire_interval = RPKI_EXPIRE_INTERVAL;
+};
+
+rpki_proto: rpki_proto_start proto_name '{' rpki_proto_opts '}' { rpki_check_config(RPKI_CFG); };
+
+rpki_proto_opts:
+ /* empty */
+ | rpki_proto_opts rpki_proto_item ';'
+ ;
+
+rpki_proto_item:
+ proto_item
+ | proto_channel
+ | REMOTE rpki_cache_addr
+ | REMOTE rpki_cache_addr rpki_proto_item_port
+ | rpki_proto_item_port
+ | TRANSPORT rpki_transport
+ | REFRESH rpki_keep_interval expr {
+ if (rpki_check_refresh_interval($3))
+ cf_error(rpki_check_refresh_interval($3));
+ RPKI_CFG->refresh_interval = $3;
+ RPKI_CFG->keep_refresh_interval = $2;
+ }
+ | RETRY rpki_keep_interval expr {
+ if (rpki_check_retry_interval($3))
+ cf_error(rpki_check_retry_interval($3));
+ RPKI_CFG->retry_interval = $3;
+ RPKI_CFG->keep_retry_interval = $2;
+ }
+ | EXPIRE rpki_keep_interval expr {
+ if (rpki_check_expire_interval($3))
+ cf_error(rpki_check_expire_interval($3));
+ RPKI_CFG->expire_interval = $3;
+ RPKI_CFG->keep_expire_interval = $2;
+ }
+ ;
+
+rpki_keep_interval:
+ /* empty */ { $$ = 0; }
+ | KEEP { $$ = 1; }
+ ;
+
+rpki_proto_item_port: PORT expr { check_u16($2); RPKI_CFG->port = $2; };
+
+rpki_cache_addr:
+ text {
+ rpki_check_unused_hostname();
+ RPKI_CFG->hostname = $1;
+ }
+ | ipa {
+ rpki_check_unused_hostname();
+ RPKI_CFG->ip = $1;
+ /* Ensure hostname is filled */
+ char *hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN + 1));
+ bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip);
+ RPKI_CFG->hostname = hostname;
+ }
+ ;
+
+rpki_transport:
+ TCP rpki_transport_tcp_init
+ | SSH rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_check
+ ;
+
+rpki_transport_tcp_init:
+{
+ rpki_check_unused_transport();
+ RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config));
+ RPKI_CFG->tr_config.type = RPKI_TR_TCP;
+};
+
+rpki_transport_ssh_init:
+{
+ rpki_check_unused_transport();
+ RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_ssh_config));
+ RPKI_CFG->tr_config.type = RPKI_TR_SSH;
+};
+
+rpki_transport_ssh_opts:
+ /* empty */
+ | rpki_transport_ssh_opts rpki_transport_ssh_item ';'
+ ;
+
+rpki_transport_ssh_item:
+ BIRD PRIVATE KEY text { RPKI_TR_SSH_CFG->bird_private_key = $4; }
+ | REMOTE PUBLIC KEY text { RPKI_TR_SSH_CFG->cache_public_key = $4; }
+ | USER text { RPKI_TR_SSH_CFG->user = $2; }
+ ;
+
+rpki_transport_ssh_check:
+{
+ if (RPKI_TR_SSH_CFG->user == NULL)
+ cf_error("User must be set");
+};
+
+CF_CODE
+
+CF_END
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c
new file mode 100644
index 00000000..60ca3936
--- /dev/null
+++ b/proto/rpki/packets.c
@@ -0,0 +1,1073 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "transport.h"
+#include "packets.h"
+
+#define RPKI_ADD_FLAG 0b00000001
+
+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
+};
+
+static const char *str_pdu_error_type[] = {
+ [CORRUPT_DATA] = "Corrupt-Data",
+ [INTERNAL_ERROR] = "Internal-Error",
+ [NO_DATA_AVAIL] = "No-Data-Available",
+ [INVALID_REQUEST] = "Invalid-Request",
+ [UNSUPPORTED_PROTOCOL_VER] = "Unsupported-Protocol-Version",
+ [UNSUPPORTED_PDU_TYPE] = "Unsupported-PDU-Type",
+ [WITHDRAWAL_OF_UNKNOWN_RECORD]= "Withdrawal-Of-Unknown-Record",
+ [DUPLICATE_ANNOUNCEMENT] = "Duplicate-Announcement",
+ [PDU_TOO_BIG] = "PDU-Too-Big",
+};
+
+enum pdu_type {
+ SERIAL_NOTIFY = 0,
+ SERIAL_QUERY = 1,
+ RESET_QUERY = 2,
+ CACHE_RESPONSE = 3,
+ IPV4_PREFIX = 4,
+ RESERVED = 5,
+ IPV6_PREFIX = 6,
+ END_OF_DATA = 7,
+ CACHE_RESET = 8,
+ ROUTER_KEY = 9,
+ ERROR = 10,
+ PDU_TYPE_MAX
+};
+
+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",
+ [RESERVED] = "Reserved",
+ [IPV6_PREFIX] = "IPv6 Prefix",
+ [END_OF_DATA] = "End of Data",
+ [CACHE_RESET] = "Cache Reset",
+ [ROUTER_KEY] = "Router Key",
+ [ERROR] = "Error"
+};
+
+static const char *str_pdu_type(uint type) {
+ if (type < PDU_TYPE_MAX)
+ return str_pdu_type_[type];
+ else
+ return "Undefined packet type";
+}
+
+/*
+ * 0 8 16 24 31
+ * .-------------------------------------------.
+ * | Protocol | PDU | |
+ * | Version | Type | reserved = zero |
+ * | 0 or 1 | 0 - 10 | |
+ * +-------------------------------------------+
+ * | |
+ * | Length >= 8 |
+ * | |
+ * `-------------------------------------------' */
+struct pdu_header {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+} PACKED;
+
+struct pdu_cache_response {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+} PACKED;
+
+struct pdu_serial_notify {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_serial_query {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_ipv4 {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+ u8 flags;
+ u8 prefix_len;
+ u8 max_prefix_len;
+ u8 zero;
+ ip4_addr prefix;
+ u32 asn;
+} PACKED;
+
+struct pdu_ipv6 {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+ u8 flags;
+ u8 prefix_len;
+ u8 max_prefix_len;
+ u8 zero;
+ ip6_addr prefix;
+ u32 asn;
+} PACKED;
+
+/*
+ * 0 8 16 24 31
+ * .-------------------------------------------.
+ * | Protocol | PDU | |
+ * | Version | Type | Error Code |
+ * | 1 | 10 | |
+ * +-------------------------------------------+
+ * | |
+ * | Length |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Length of Encapsulated PDU |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * ~ Copy of Erroneous PDU ~
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Length of Error Text |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Arbitrary Text |
+ * | of |
+ * ~ Error Diagnostic Message ~
+ * | |
+ * `-------------------------------------------' */
+struct pdu_error {
+ u8 ver;
+ u8 type;
+ u16 error_code;
+ u32 len;
+ u32 len_enc_pdu; /* Length of Encapsulated PDU */
+ byte rest[]; /* Copy of Erroneous PDU
+ * Length of Error Text
+ * Error Diagnostic Message */
+} PACKED;
+
+struct pdu_reset_query {
+ u8 ver;
+ u8 type;
+ u16 flags;
+ u32 len;
+} PACKED;
+
+struct pdu_end_of_data_v0 {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_end_of_data_v1 {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+ u32 refresh_interval;
+ u32 retry_interval;
+ u32 expire_interval;
+} PACKED;
+
+static const size_t min_pdu_size[] = {
+ [SERIAL_NOTIFY] = sizeof(struct pdu_serial_notify),
+ [SERIAL_QUERY] = sizeof(struct pdu_serial_query),
+ [RESET_QUERY] = sizeof(struct pdu_reset_query),
+ [CACHE_RESPONSE] = sizeof(struct pdu_cache_response),
+ [IPV4_PREFIX] = sizeof(struct pdu_ipv4),
+ [RESERVED] = sizeof(struct pdu_header),
+ [IPV6_PREFIX] = sizeof(struct pdu_ipv6),
+ [END_OF_DATA] = sizeof(struct pdu_end_of_data_v0),
+ [CACHE_RESET] = sizeof(struct pdu_cache_response),
+ [ROUTER_KEY] = sizeof(struct pdu_header), /* FIXME */
+ [ERROR] = 16,
+};
+
+static int rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...);
+
+static void
+rpki_pdu_to_network_byte_order(struct pdu_header *pdu)
+{
+ pdu->reserved = htons(pdu->reserved);
+ pdu->len = htonl(pdu->len);
+
+ switch (pdu->type)
+ {
+ case SERIAL_QUERY:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_serial_query *sq_pdu = (void *) pdu;
+ sq_pdu->serial_num = htonl(sq_pdu->serial_num);
+ break;
+ }
+
+ case ERROR:
+ {
+ struct pdu_error *err = (void *) pdu;
+ u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu);
+ *err_text_len = htonl(*err_text_len);
+ err->len_enc_pdu = htonl(err->len_enc_pdu);
+ break;
+ }
+
+ case RESET_QUERY:
+ break;
+
+ default:
+ bug("PDU type %s should not be sent by us", str_pdu_type(pdu->type));
+ }
+}
+
+static void
+rpki_pdu_to_host_byte_order(struct pdu_header *pdu)
+{
+ /* The Router Key PDU has two one-byte fields instead of one two-bytes field. */
+ if (pdu->type != ROUTER_KEY)
+ pdu->reserved = ntohs(pdu->reserved);
+
+ pdu->len = ntohl(pdu->len);
+
+ switch (pdu->type)
+ {
+ case SERIAL_NOTIFY:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_serial_notify *sn_pdu = (void *) pdu;
+ sn_pdu->serial_num = ntohl(sn_pdu->serial_num);
+ break;
+ }
+
+ case END_OF_DATA:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_end_of_data_v0 *eod0 = (void *) pdu;
+ eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */
+
+ if (pdu->ver == RPKI_VERSION_1)
+ {
+ struct pdu_end_of_data_v1 *eod1 = (void *) 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 = (void *) pdu;
+ ipv4->prefix = ip4_ntoh(ipv4->prefix);
+ ipv4->asn = ntohl(ipv4->asn);
+ break;
+ }
+
+ case IPV6_PREFIX:
+ {
+ struct pdu_ipv6 *ipv6 = (void *) pdu;
+ ipv6->prefix = ip6_ntoh(ipv6->prefix);
+ ipv6->asn = ntohl(ipv6->asn);
+ break;
+ }
+
+ case ERROR:
+ {
+ /* Note that a error_code is converted using converting header->reserved */
+ struct pdu_error *err = (void *) pdu;
+ err->len_enc_pdu = ntohl(err->len_enc_pdu);
+ u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu);
+ *err_text_len = htonl(*err_text_len);
+ break;
+ }
+
+ case ROUTER_KEY:
+ /* Router Key PDU is not supported yet */
+
+ case SERIAL_QUERY:
+ case RESET_QUERY:
+ /* Serial/Reset Query are sent only in direction router to cache.
+ * We don't care here. */
+
+ case CACHE_RESPONSE:
+ case CACHE_RESET:
+ /* Converted with pdu->reserved */
+ break;
+ }
+}
+
+/**
+ * rpki_convert_pdu_back_to_network_byte_order - convert host-byte order PDU back to network-byte order
+ * @out: allocated memory for writing a converted PDU of size @in->len
+ * @in: host-byte order PDU
+ *
+ * Assumed: |A == ntoh(ntoh(A))|
+ */
+static struct pdu_header *
+rpki_pdu_back_to_network_byte_order(struct pdu_header *out, const struct pdu_header *in)
+{
+ memcpy(out, in, in->len);
+ rpki_pdu_to_host_byte_order(out);
+ return out;
+}
+
+static void
+rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const enum rpki_transmit_type action)
+{
+ if (!(cache->p->p.debug & D_PACKETS))
+ return;
+
+ const char *str_type = str_pdu_type(pdu->type);
+ char detail[256];
+
+#define SAVE(fn) \
+ do { \
+ if (fn < 0) \
+ { \
+ bsnprintf(detail + sizeof(detail) - 16, 16, "... <too long>)"); \
+ goto detail_finished; \
+ } \
+ } while(0) \
+
+ switch (pdu->type)
+ {
+ case SERIAL_NOTIFY:
+ case SERIAL_QUERY:
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", pdu->reserved, ((struct pdu_serial_notify *) pdu)->serial_num));
+ break;
+
+ case END_OF_DATA:
+ {
+ const struct pdu_end_of_data_v1 *eod = (void *) pdu;
+ if (eod->ver == RPKI_VERSION_1)
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u, refresh: %us, retry: %us, expire: %us)", eod->session_id, eod->serial_num, eod->refresh_interval, eod->retry_interval, eod->expire_interval));
+ else
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", eod->session_id, eod->serial_num));
+ break;
+ }
+
+ case CACHE_RESPONSE:
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u)", pdu->reserved));
+ break;
+
+ case IPV4_PREFIX:
+ {
+ const struct pdu_ipv4 *ipv4 = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ipv4->prefix, ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn));
+ break;
+ }
+
+ case IPV6_PREFIX:
+ {
+ const struct pdu_ipv6 *ipv6 = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", ipv6->prefix, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn));
+ break;
+ }
+
+ case ROUTER_KEY:
+ /* We don't support saving Router Key PDUs yet */
+ SAVE(bsnprintf(detail, sizeof(detail), "(ignored)"));
+ break;
+
+ case ERROR:
+ {
+ const struct pdu_error *err = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%s", str_pdu_error_type[err->error_code]));
+
+ /* Optional description of error */
+ const u32 len_err_txt = *((u32 *) (err->rest + err->len_enc_pdu));
+ if (len_err_txt > 0)
+ {
+ size_t expected_len = err->len_enc_pdu + len_err_txt + 16;
+ if (expected_len == err->len)
+ {
+ char txt[len_err_txt + 1];
+ char *pdu_txt = (char *) err->rest + err->len_enc_pdu + 4;
+ bsnprintf(txt, sizeof(txt), "%s", pdu_txt); /* it's ensured that txt is ended with a null byte */
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ": '%s'", txt));
+ }
+ else
+ {
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", malformed size"));
+ }
+ }
+
+ /* Optional encapsulated erroneous packet */
+ if (err->len_enc_pdu)
+ {
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", %s packet:", str_pdu_type(((struct pdu_header *) err->rest)->type)));
+ if (err->rest + err->len_enc_pdu <= (byte *)err + err->len)
+ {
+ for (const byte *c = err->rest; c != err->rest + err->len_enc_pdu; c++)
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), " %02X", *c));
+ }
+ }
+
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ")"));
+ break;
+ }
+
+ default:
+ *detail = '\0';
+ }
+#undef SAVE
+
+ detail_finished:
+
+ if (action == RPKI_RECV)
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Received %s packet %s", str_type, detail);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Sending %s packet %s", str_type, detail);
+ }
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+ int seq = 0;
+ for(const byte *c = pdu; c != pdu + 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");
+#endif
+}
+
+static int
+rpki_send_pdu(struct rpki_cache *cache, const void *pdu, const uint len)
+{
+ struct rpki_proto *p = cache->p;
+ sock *sk = cache->tr_sock->sk;
+
+ rpki_log_packet(cache, pdu, RPKI_SEND);
+
+ if (sk->tbuf != sk->tpos)
+ {
+ RPKI_WARN(p, "Old packet overwritten in TX buffer");
+ }
+
+ if (len > sk->tbsize)
+ {
+ RPKI_WARN(p, "%u bytes is too much for send", len);
+ ASSERT(0);
+ return RPKI_ERROR;
+ }
+
+ memcpy(sk->tbuf, pdu, len);
+ rpki_pdu_to_network_byte_order((void *) sk->tbuf);
+
+ if (!sk_send(sk, len))
+ {
+ DBG("Cannot send just the whole data. It will be sent using a call of tx_hook()");
+ }
+
+ return RPKI_SUCCESS;
+}
+
+/**
+ * rpki_check_receive_packet - make a basic validation of received RPKI PDU header
+ * @cache: cache connection instance
+ * @pdu: RPKI PDU in network byte order
+ *
+ * This function checks protocol version, PDU type, version and size. If all is all right then
+ * function returns |RPKI_SUCCESS| otherwise sends Error PDU and returns
+ * |RPKI_ERROR|.
+ */
+static int
+rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu)
+{
+ u32 pdu_len = ntohl(pdu->len);
+
+ /*
+ * Minimal and maximal allowed PDU size is treated in rpki_rx_hook() function.
+ * @header.len corresponds to number of bytes of @pdu and
+ * it is in range from RPKI_PDU_HEADER_LEN to RPKI_PDU_MAX_LEN bytes.
+ */
+
+ /* Do not handle error PDUs here, leave this task to rpki_handle_error_pdu() */
+ if (pdu->ver != cache->version && pdu->type != ERROR)
+ {
+ /* If this is the first PDU we have received */
+ if (cache->request_session_id)
+ {
+ if (pdu->type == SERIAL_NOTIFY)
+ {
+ /*
+ * The router MUST ignore any Serial Notify PDUs it might receive from
+ * the cache during this initial start-up period, regardless of the
+ * Protocol Version field in the Serial Notify PDU.
+ * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7)
+ */
+ }
+ else if (cache->last_update == 0
+ && pdu->ver <= RPKI_MAX_VERSION
+ && pdu->ver < cache->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", rpki_get_cache_ident(cache), cache->version, pdu->ver);
+ cache->version = pdu->ver;
+ }
+ else
+ {
+ /* If this is not the first PDU we have received, something is wrong with
+ * the server implementation -> Error */
+ rpki_send_error_pdu(cache, UNSUPPORTED_PROTOCOL_VER, pdu_len, pdu, "PDU with unsupported Protocol version received");
+ return RPKI_ERROR;
+ }
+ }
+ }
+
+ if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY))
+ {
+ rpki_send_error_pdu(cache, UNSUPPORTED_PDU_TYPE, pdu_len, pdu, "Unsupported PDU type %u received", pdu->type);
+ return RPKI_ERROR;
+ }
+
+ if (pdu_len < min_pdu_size[pdu->type])
+ {
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu_len, pdu, "Received %s packet with %d bytes, but expected at least %d bytes", str_pdu_type(pdu->type), pdu_len, min_pdu_size[pdu->type]);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+static int
+rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu)
+{
+ switch (pdu->error_code)
+ {
+ case CORRUPT_DATA:
+ case INTERNAL_ERROR:
+ case INVALID_REQUEST:
+ case UNSUPPORTED_PDU_TYPE:
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ break;
+
+ case NO_DATA_AVAIL:
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_DATA_AVAIL);
+ break;
+
+ case UNSUPPORTED_PROTOCOL_VER:
+ CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version");
+ if (pdu->ver <= RPKI_MAX_VERSION &&
+ pdu->ver < cache->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %d to version %d", cache->version, pdu->ver);
+ cache->version = pdu->ver;
+ rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \
+ "current version: %d, PDU version: %d", cache->version, pdu->ver);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ }
+ break;
+
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "Error unknown, server sent unsupported error code %u", pdu->error_code);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ break;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+static void
+rpki_handle_serial_notify_pdu(struct rpki_cache *cache, const struct pdu_serial_notify *pdu)
+{
+ /* The router MUST ignore any Serial Notify PDUs it might receive from
+ * the cache during this initial start-up period, regardless of the
+ * Protocol Version field in the Serial Notify PDU.
+ * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7)
+ */
+ if (cache->request_session_id)
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Ignore a Serial Notify packet during initial start-up period");
+ return;
+ }
+
+ /* XXX Serial number should be compared using method RFC 1982 (3.2) */
+ if (cache->serial_num != pdu->serial_num)
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+}
+
+static int
+rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_response *pdu)
+{
+ if (cache->request_session_id)
+ {
+ if (cache->last_update != 0)
+ {
+ /*
+ * This isn't the first sync and we already received records. This point
+ * is after Reset Query and before importing new records from cache
+ * server. We need to load new ones and kick out missing ones. So start
+ * a refresh cycle.
+ */
+ if (cache->p->roa4_channel)
+ rt_refresh_begin(cache->p->roa4_channel->table, cache->p->roa4_channel);
+ if (cache->p->roa6_channel)
+ rt_refresh_begin(cache->p->roa6_channel->table, cache->p->roa6_channel);
+
+ cache->p->refresh_channels = 1;
+ }
+ cache->session_id = pdu->session_id;
+ cache->request_session_id = 0;
+ }
+ else
+ {
+ if (cache->session_id != pdu->session_id)
+ {
+ byte tmp[pdu->len];
+ const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Wrong session_id %u in Cache Response PDU", pdu->session_id);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return RPKI_ERROR;
+ }
+ }
+
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_RUNNING);
+ return RPKI_SUCCESS;
+}
+
+/**
+ * rpki_prefix_pdu_2_net_addr - convert IPv4/IPv6 Prefix PDU into net_addr_union
+ * @pdu: host byte order IPv4/IPv6 Prefix PDU
+ * @n: allocated net_addr_union for save ROA
+ *
+ * This function reads ROA data from IPv4/IPv6 Prefix PDU and
+ * write them into net_addr_roa4 or net_addr_roa6 data structure.
+ */
+static net_addr_union *
+rpki_prefix_pdu_2_net_addr(const struct pdu_header *pdu, net_addr_union *n)
+{
+ /*
+ * Note that sizeof(net_addr_roa6) > sizeof(net_addr)
+ * and thence we must use net_addr_union and not only net_addr
+ */
+
+ if (pdu->type == IPV4_PREFIX)
+ {
+ const struct pdu_ipv4 *ipv4 = (void *) pdu;
+ n->roa4.type = NET_ROA4;
+ n->roa4.length = sizeof(net_addr_roa4);
+ n->roa4.prefix = ipv4->prefix;
+ n->roa4.asn = ipv4->asn;
+ n->roa4.pxlen = ipv4->prefix_len;
+ n->roa4.max_pxlen = ipv4->max_prefix_len;
+ }
+ else
+ {
+ const struct pdu_ipv6 *ipv6 = (void *) pdu;
+ n->roa6.type = NET_ROA6;
+ n->roa6.length = sizeof(net_addr_roa6);
+ n->roa6.prefix = ipv6->prefix;
+ n->roa6.asn = ipv6->asn;
+ n->roa6.pxlen = ipv6->prefix_len;
+ n->roa6.max_pxlen = ipv6->max_prefix_len;
+ }
+
+ return n;
+}
+
+static int
+rpki_handle_prefix_pdu(struct rpki_cache *cache, const struct pdu_header *pdu)
+{
+ const enum pdu_type type = pdu->type;
+ ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+ net_addr_union addr = {};
+ rpki_prefix_pdu_2_net_addr(pdu, &addr);
+
+ struct channel *channel = NULL;
+
+ if (type == IPV4_PREFIX)
+ channel = cache->p->roa4_channel;
+ if (type == IPV6_PREFIX)
+ channel = cache->p->roa6_channel;
+
+ if (!channel)
+ {
+ CACHE_TRACE(D_ROUTES, cache, "Skip %N, missing %s channel", &addr, (type == IPV4_PREFIX ? "roa4" : "roa6"), addr);
+ return RPKI_ERROR;
+ }
+
+ cache->last_rx_prefix = now;
+
+ /* A place for 'flags' is same for both data structures pdu_ipv4 or pdu_ipv6 */
+ struct pdu_ipv4 *pfx = (void *) pdu;
+ if (pfx->flags & RPKI_ADD_FLAG)
+ rpki_table_add_roa(cache, channel, &addr);
+ else
+ rpki_table_remove_roa(cache, channel, &addr);
+
+ return RPKI_SUCCESS;
+}
+
+static uint
+rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval)
+{
+ if (check_fn(interval))
+ {
+ RPKI_WARN(cache->p, "%s, received %u seconds", check_fn(interval), interval);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_data_v1 *pdu)
+{
+ const struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ if (pdu->session_id != cache->session_id)
+ {
+ byte tmp[pdu->len];
+ const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Received Session ID %u, but expected %u", pdu->session_id, cache->session_id);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return;
+ }
+
+ if (pdu->ver == RPKI_VERSION_1)
+ {
+ if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval))
+ cache->refresh_interval = pdu->refresh_interval;
+
+ if (!cf->keep_retry_interval && rpki_check_interval(cache, rpki_check_retry_interval, pdu->retry_interval))
+ cache->retry_interval = pdu->retry_interval;
+
+ if (!cf->keep_expire_interval && rpki_check_interval(cache, rpki_check_expire_interval, pdu->expire_interval))
+ cache->expire_interval = pdu->expire_interval;
+
+ CACHE_TRACE(D_EVENTS, cache, "New interval values: "
+ "refresh: %s%us, "
+ "retry: %s%us, "
+ "expire: %s%us",
+ (cf->keep_refresh_interval ? "keeps " : ""), cache->refresh_interval,
+ (cf->keep_retry_interval ? "keeps " : ""), cache->retry_interval,
+ (cf->keep_expire_interval ? "keeps " : ""), cache->expire_interval);
+ }
+
+ if (cache->p->refresh_channels)
+ {
+ cache->p->refresh_channels = 0;
+ if (cache->p->roa4_channel)
+ rt_refresh_end(cache->p->roa4_channel->table, cache->p->roa4_channel);
+ if (cache->p->roa6_channel)
+ rt_refresh_end(cache->p->roa6_channel->table, cache->p->roa6_channel);
+ }
+
+ cache->last_update = now;
+ cache->serial_num = pdu->serial_num;
+ rpki_cache_change_state(cache, RPKI_CS_ESTABLISHED);
+}
+
+/**
+ * rpki_rx_packet - process a received RPKI PDU
+ * @cache: RPKI connection instance
+ * @pdu: a RPKI PDU in network byte order
+ */
+static void
+rpki_rx_packet(struct rpki_cache *cache, struct pdu_header *pdu)
+{
+ struct rpki_proto *p = cache->p;
+
+ if (rpki_check_receive_packet(cache, pdu) == RPKI_ERROR)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return;
+ }
+
+ rpki_pdu_to_host_byte_order(pdu);
+ rpki_log_packet(cache, pdu, RPKI_RECV);
+
+ switch (pdu->type)
+ {
+ case RESET_QUERY:
+ case SERIAL_QUERY:
+ RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type(pdu->type));
+ break;
+
+ case SERIAL_NOTIFY:
+ /* This is a signal to synchronize with the cache server just now */
+ rpki_handle_serial_notify_pdu(cache, (void *) pdu);
+ break;
+
+ case CACHE_RESPONSE:
+ rpki_handle_cache_response_pdu(cache, (void *) pdu);
+ break;
+
+ case IPV4_PREFIX:
+ case IPV6_PREFIX:
+ rpki_handle_prefix_pdu(cache, pdu);
+ break;
+
+ case END_OF_DATA:
+ rpki_handle_end_of_data_pdu(cache, (void *) pdu);
+ break;
+
+ case CACHE_RESET:
+ /* Cache cannot provide an incremental update. */
+ rpki_cache_change_state(cache, RPKI_CS_NO_INCR_UPDATE_AVAIL);
+ break;
+
+ case ERROR:
+ rpki_handle_error_pdu(cache, (void *) pdu);
+ break;
+
+ case ROUTER_KEY:
+ /* TODO: Implement Router Key PDU handling */
+ break;
+
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type);
+ };
+}
+
+int
+rpki_rx_hook(struct birdsock *sk, uint size)
+{
+ struct rpki_cache *cache = sk->data;
+ struct rpki_proto *p = cache->p;
+
+ byte *pkt_start = sk->rbuf;
+ byte *end = pkt_start + size;
+
+ DBG("rx hook got %u bytes \n", size);
+
+ while (end >= pkt_start + RPKI_PDU_HEADER_LEN)
+ {
+ struct pdu_header *pdu = (void *) pkt_start;
+ u32 pdu_size = ntohl(pdu->len);
+
+ if (pdu_size < RPKI_PDU_HEADER_LEN || pdu_size > RPKI_PDU_MAX_LEN)
+ {
+ RPKI_WARN(p, "Received invalid packet length %u, purge the whole receiving buffer", pdu_size);
+ return 1; /* Purge recv buffer */
+ }
+
+ if (end < pkt_start + pdu_size)
+ break;
+
+ rpki_rx_packet(cache, pdu);
+
+ /* It is possible that bird socket was freed/closed */
+ if (p->p.proto_state == PS_DOWN || sk != cache->tr_sock->sk)
+ return 0;
+
+ pkt_start += pdu_size;
+ }
+
+ if (pkt_start != sk->rbuf)
+ {
+ CACHE_DBG(cache, "Move %u bytes of a memory at the start of buffer", end - pkt_start);
+ 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;
+
+ if (error_num)
+ {
+ /* sk->err may contains a SSH error description */
+ if (sk->err)
+ CACHE_TRACE(D_EVENTS, cache, "Lost connection: %s", sk->err);
+ else
+ CACHE_TRACE(D_EVENTS, cache, "Lost connection: %M", error_num);
+ }
+ else
+ {
+ CACHE_TRACE(D_EVENTS, cache, "The other side closed a connection");
+ }
+
+
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+}
+
+static int
+rpki_fire_tx(struct rpki_cache *cache)
+{
+ sock *sk = cache->tr_sock->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_tx_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+
+ while (rpki_fire_tx(cache) > 0)
+ ;
+}
+
+void
+rpki_connected_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+
+ CACHE_TRACE(D_EVENTS, cache, "Connected");
+ proto_notify_state(&cache->p->p, PS_UP);
+
+ sk->rx_hook = rpki_rx_hook;
+ sk->tx_hook = rpki_tx_hook;
+
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+}
+
+/**
+ * rpki_send_error_pdu - send RPKI Error PDU
+ * @cache: RPKI connection instance
+ * @error_code: PDU Error type
+ * @err_pdu_len: length of @erroneous_pdu
+ * @erroneous_pdu: optional network byte-order PDU that invokes Error by us or NULL
+ * @fmt: optional description text of error or NULL
+ * @args: optional arguments for @fmt
+ *
+ * This function prepares Error PDU and sends it to a cache server.
+ */
+static int
+rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...)
+{
+ va_list args;
+ char msg[128];
+
+ /* Size including the terminating null byte ('\0') */
+ int msg_len = 0;
+
+ /* Don't send errors for erroneous error PDUs */
+ if (err_pdu_len >= 2)
+ {
+ if (erroneous_pdu->type == ERROR)
+ return RPKI_SUCCESS;
+ }
+
+ if (fmt)
+ {
+ va_start(args, fmt);
+ msg_len = bvsnprintf(msg, sizeof(msg), fmt, args) + 1;
+ }
+
+ u32 pdu_size = 16 + err_pdu_len + msg_len;
+ byte pdu[pdu_size];
+ memset(pdu, 0, sizeof(pdu));
+
+ struct pdu_error *e = (void *) pdu;
+ e->ver = cache->version;
+ e->type = ERROR;
+ e->error_code = error_code;
+ e->len = pdu_size;
+
+ e->len_enc_pdu = err_pdu_len;
+ if (err_pdu_len > 0)
+ memcpy(e->rest, erroneous_pdu, err_pdu_len);
+
+ *((u32 *)(e->rest + err_pdu_len)) = msg_len;
+ if (msg_len > 0)
+ memcpy(e->rest + err_pdu_len + 4, msg, msg_len);
+
+ return rpki_send_pdu(cache, pdu, pdu_size);
+}
+
+int
+rpki_send_serial_query(struct rpki_cache *cache)
+{
+ struct pdu_serial_query pdu = {
+ .ver = cache->version,
+ .type = SERIAL_QUERY,
+ .session_id = cache->session_id,
+ .len = sizeof(pdu),
+ .serial_num = cache->serial_num
+ };
+
+ if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+int
+rpki_send_reset_query(struct rpki_cache *cache)
+{
+ struct pdu_reset_query pdu = {
+ .ver = cache->version,
+ .type = RESET_QUERY,
+ .len = sizeof(pdu),
+ };
+
+ if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h
new file mode 100644
index 00000000..d6f8a249
--- /dev/null
+++ b/proto/rpki/packets.h
@@ -0,0 +1,45 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RPKI_PACKETS_H_
+#define _BIRD_RPKI_PACKETS_H_
+
+#include <arpa/inet.h>
+
+#define RPKI_PDU_HEADER_LEN 8
+
+/* A Error PDU size is the biggest (has encapsulate PDU inside):
+ * +8 bytes (Header size)
+ * +4 bytes (Length of Encapsulated PDU)
+ * +32 bytes (Encapsulated PDU IPv6 32)
+ * +4 bytes (Length of inserted text)
+ * +800 bytes (UTF-8 text 400*2 bytes)
+ * ------------
+ * = 848 bytes (Maximal expected PDU size) */
+#define RPKI_PDU_MAX_LEN 848
+
+/* RX buffer size has a great impact to scheduler granularity */
+#define RPKI_RX_BUFFER_SIZE 4096
+#define RPKI_TX_BUFFER_SIZE RPKI_PDU_MAX_LEN
+
+/* Return values */
+enum rpki_rtvals {
+ RPKI_SUCCESS = 0,
+ RPKI_ERROR = -1
+};
+
+int rpki_send_serial_query(struct rpki_cache *cache);
+int rpki_send_reset_query(struct rpki_cache *cache);
+int rpki_rx_hook(sock *sk, uint size);
+void rpki_connected_hook(sock *sk);
+void rpki_err_hook(sock *sk, int size);
+
+#endif
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
new file mode 100644
index 00000000..0d4b1fd3
--- /dev/null
+++ b/proto/rpki/rpki.c
@@ -0,0 +1,927 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * Using RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: RPKI To Router (RPKI-RTR)
+ *
+ * The RPKI-RTR protocol is implemented in several files: |rpki.c| containing
+ * the routes handling, protocol logic, timer events, cache connection,
+ * reconfiguration, configuration and protocol glue with BIRD core, |packets.c|
+ * containing the RPKI packets handling and finally all transports files:
+ * |transport.c|, |tcp_transport.c| and |ssh_transport.c|.
+ *
+ * The |transport.c| is a middle layer and interface for each specific
+ * transport. Transport is a way how to wrap a communication with a cache
+ * server. There is supported an unprotected TCP transport and an encrypted
+ * SSHv2 transport. The SSH transport requires LibSSH library. LibSSH is
+ * loading dynamically using |dlopen()| function. SSH support is integrated in
+ * |sysdep/unix/io.c|. Each transport must implement an initialization
+ * function, an open function and a socket identification function. That's all.
+ *
+ * This implementation is based on the RTRlib (http://rpki.realmv6.org/). The
+ * BIRD takes over files |packets.c|, |rtr.c| (inside |rpki.c|), |transport.c|,
+ * |tcp_transport.c| and |ssh_transport.c| from RTRlib.
+ *
+ * A RPKI-RTR connection is described by a structure &rpki_cache. The main
+ * logic is located in |rpki_cache_change_state()| function. There is a state
+ * machine. The standard starting state flow looks like |Down| ~> |Connecting|
+ * ~> |Sync-Start| ~> |Sync-Running| ~> |Established| and then the last three
+ * states are periodically repeated.
+ *
+ * |Connecting| state establishes the transport connection. The state from a
+ * call |rpki_cache_change_state(CONNECTING)| to a call |rpki_connected_hook()|
+ *
+ * |Sync-Start| state starts with sending |Reset Query| or |Serial Query| and
+ * then waits for |Cache Response|. The state from |rpki_connected_hook()| to
+ * |rpki_handle_cache_response_pdu()|
+ *
+ * During |Sync-Running| BIRD receives data with IPv4/IPv6 Prefixes from cache
+ * server. The state starts from |rpki_handle_cache_response_pdu()| and ends
+ * in |rpki_handle_end_of_data_pdu()|.
+ *
+ * |Established| state means that BIRD has synced all data with cache server.
+ * Schedules a refresh timer event that invokes |Sync-Start|. Schedules Expire
+ * timer event and stops a Retry timer event.
+ *
+ * |Transport Error| state means that we have some troubles with a network
+ * connection. We cannot connect to a cache server or we wait too long for some
+ * expected PDU for received - |Cache Response| or |End of Data|. It closes
+ * current connection and schedules a Retry timer event.
+ *
+ * |Fatal Protocol Error| is occurred e.g. by received a bad Session ID. We
+ * restart a protocol, so all ROAs are flushed immediately.
+ *
+ * The RPKI-RTR protocol (RFC 6810 bis) defines configurable refresh, retry and
+ * expire intervals. For maintaining a connection are used timer events that
+ * are scheduled by |rpki_schedule_next_refresh()|,
+ * |rpki_schedule_next_retry()| and |rpki_schedule_next_expire()| functions.
+ *
+ * A Refresh timer event performs a sync of |Established| connection. So it
+ * shifts state to |Sync-Start|. If at the beginning of second call of a
+ * refresh event is connection in |Sync-Start| state then we didn't receive a
+ * |Cache Response| from a cache server and we invoke |Transport Error| state.
+ *
+ * A Retry timer event attempts to connect cache server. It is activated after
+ * |Transport Error| state and terminated by reaching |Established| state.
+ * If cache connection is still connecting to the cache server at the beginning
+ * of an event call then the Retry timer event invokes |Transport Error| state.
+ *
+ * An Expire timer event checks expiration of ROAs. If a last successful sync
+ * was more ago than the expire interval then the Expire timer event invokes a
+ * protocol restart thereby removes all ROAs learned from that cache server and
+ * continue trying to connect to cache server. The Expire event is activated
+ * by initial successful loading of ROAs, receiving End of Data PDU.
+ *
+ * A reconfiguration of cache connection works well without restarting when we
+ * change only intervals values.
+ *
+ * Supported standards:
+ * - RFC 6810 - main RPKI-RTR standard
+ * - RFC 6810 bis - an explicit timing parameters and protocol version number negotiation
+ */
+
+#include <stdlib.h>
+#include <netdb.h>
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "lib/string.h"
+#include "nest/cli.h"
+
+/* Return values for reconfiguration functions */
+#define NEED_RESTART 0
+#define SUCCESSFUL_RECONF 1
+
+static int rpki_open_connection(struct rpki_cache *cache);
+static void rpki_close_connection(struct rpki_cache *cache);
+static void rpki_schedule_next_refresh(struct rpki_cache *cache);
+static void rpki_schedule_next_retry(struct rpki_cache *cache);
+static void rpki_schedule_next_expire_check(struct rpki_cache *cache);
+static void rpki_stop_refresh_timer_event(struct rpki_cache *cache);
+static void rpki_stop_retry_timer_event(struct rpki_cache *cache);
+static void rpki_stop_expire_timer_event(struct rpki_cache *cache);
+
+
+/*
+ * Routes handling
+ */
+
+void
+rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+ struct rpki_proto *p = cache->p;
+
+ rta a0 = {
+ .src = p->p.main_source,
+ .source = RTS_RPKI,
+ .scope = SCOPE_UNIVERSE,
+ .cast = RTC_UNICAST,
+ .dest = RTD_BLACKHOLE,
+ };
+
+ rta *a = rta_lookup(&a0);
+ rte *e = rte_get_temp(a);
+
+ e->pflags = 0;
+
+ rte_update2(channel, &pfxr->n, e, a0.src);
+}
+
+void
+rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+ struct rpki_proto *p = cache->p;
+ rte_update2(channel, &pfxr->n, NULL, p->p.main_source);
+}
+
+
+/*
+ * RPKI Protocol Logic
+ */
+
+static const char *str_cache_states[] = {
+ [RPKI_CS_CONNECTING] = "Connecting",
+ [RPKI_CS_ESTABLISHED] = "Established",
+ [RPKI_CS_RESET] = "Reseting",
+ [RPKI_CS_SYNC_START] = "Sync-Start",
+ [RPKI_CS_SYNC_RUNNING] = "Sync-Running",
+ [RPKI_CS_FAST_RECONNECT] = "Fast-Reconnect",
+ [RPKI_CS_NO_INCR_UPDATE_AVAIL]= "No-Increment-Update-Available",
+ [RPKI_CS_ERROR_NO_DATA_AVAIL] = "Cache-Error-No-Data-Available",
+ [RPKI_CS_ERROR_FATAL] = "Fatal-Protocol-Error",
+ [RPKI_CS_ERROR_TRANSPORT] = "Transport-Error",
+ [RPKI_CS_SHUTDOWN] = "Down"
+};
+
+/**
+ * rpki_cache_state_to_str - give a text representation of cache state
+ * @state: A cache state
+ *
+ * The function converts logic cache state into string.
+ */
+const char *
+rpki_cache_state_to_str(enum rpki_cache_state state)
+{
+ return str_cache_states[state];
+}
+
+/**
+ * rpki_start_cache - connect to a cache server
+ * @cache: RPKI connection instance
+ *
+ * This function is a high level method to kick up a connection to a cache server.
+ */
+static void
+rpki_start_cache(struct rpki_cache *cache)
+{
+ rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+}
+
+/**
+ * rpki_force_restart_proto - force shutdown and start protocol again
+ * @p: RPKI protocol instance
+ *
+ * This function calls shutdown and frees all protocol resources as well.
+ * After calling this function should be no operations with protocol data,
+ * they could be freed already.
+ */
+static void
+rpki_force_restart_proto(struct rpki_proto *p)
+{
+ if (p->cache)
+ {
+ CACHE_DBG(p->cache, "Connection object destroying");
+ }
+
+ /* Sign as freed */
+ p->cache = NULL;
+
+ proto_notify_state(&p->p, PS_DOWN);
+}
+
+/**
+ * rpki_cache_change_state - check and change cache state
+ * @cache: RPKI cache instance
+ * @new_state: suggested new state
+ *
+ * This function makes transitions between internal states.
+ * It represents the core of logic management of RPKI protocol.
+ * Cannot transit into the same state as cache is in already.
+ */
+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;
+
+ cache->state = new_state;
+ CACHE_TRACE(D_EVENTS, cache, "Changing from %s to %s state", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(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_START);
+
+ rpki_schedule_next_retry(cache);
+ break;
+ }
+
+ case RPKI_CS_ESTABLISHED:
+ rpki_schedule_next_refresh(cache);
+ rpki_schedule_next_expire_check(cache);
+ rpki_stop_retry_timer_event(cache);
+ break;
+
+ case RPKI_CS_RESET:
+ /* Resetting cache connection. */
+ cache->request_session_id = 1;
+ cache->serial_num = 0;
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+ break;
+
+ case RPKI_CS_SYNC_START:
+ /* Requesting for receive ROAs from a cache server. */
+ if (cache->request_session_id)
+ {
+ /* Send request for Session ID */
+ if (rpki_send_reset_query(cache) != RPKI_SUCCESS)
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ else
+ {
+ /* We have already a session_id. So send a Serial Query and start an incremental sync */
+ if (rpki_send_serial_query(cache) != RPKI_SUCCESS)
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ case RPKI_CS_SYNC_RUNNING:
+ /* The state between Cache Response and End of Data. Only waiting for
+ * receiving all IP Prefix PDUs and finally a End of Data PDU. */
+ break;
+
+ case RPKI_CS_NO_INCR_UPDATE_AVAIL:
+ /* Server was unable to answer the last Serial Query and sent Cache Reset. */
+ rpki_cache_change_state(cache, RPKI_CS_RESET);
+ break;
+
+ case RPKI_CS_ERROR_NO_DATA_AVAIL:
+ /* No validation records are available on the cache server. */
+ rpki_cache_change_state(cache, RPKI_CS_RESET);
+ break;
+
+ case RPKI_CS_ERROR_FATAL:
+ /* Fatal protocol error occurred. */
+ rpki_force_restart_proto(cache->p);
+ break;
+
+ case RPKI_CS_ERROR_TRANSPORT:
+ /* Error on the transport socket occurred. */
+ rpki_close_connection(cache);
+ rpki_schedule_next_retry(cache);
+ rpki_stop_refresh_timer_event(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:
+ bug("This isn't never really called.");
+ break;
+ };
+}
+
+
+/*
+ * RPKI Timer Events
+ */
+
+static void
+rpki_schedule_next_refresh(struct rpki_cache *cache)
+{
+ uint time_to_wait = cache->refresh_interval;
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->refresh_timer, time_to_wait);
+}
+
+static void
+rpki_schedule_next_retry(struct rpki_cache *cache)
+{
+ uint time_to_wait = cache->retry_interval;
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->retry_timer, time_to_wait);
+}
+
+static void
+rpki_schedule_next_expire_check(struct rpki_cache *cache)
+{
+ /* A minimum time to wait is 1 second */
+ uint time_to_wait = MAX(((int)cache->expire_interval - (int)(now - cache->last_update)), 1);
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->expire_timer, time_to_wait);
+}
+
+static void
+rpki_stop_refresh_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->refresh_timer);
+}
+
+static void
+rpki_stop_retry_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->retry_timer);
+}
+
+static void UNUSED
+rpki_stop_expire_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->expire_timer);
+}
+
+static int
+rpki_do_we_recv_prefix_pdu_in_last_seconds(struct rpki_cache *cache)
+{
+ if (cache->last_rx_prefix == 0)
+ return 0;
+
+ return ((now - cache->last_rx_prefix) <= 2);
+}
+
+/**
+ * rpki_refresh_hook - control a scheduling of downloading data from cache server
+ * @tm: refresh timer with cache connection instance in data
+ *
+ * This function is periodically called during &ESTABLISHED or &SYNC* state
+ * cache connection. The first refresh schedule is invoked after receiving a
+ * |End of Data| PDU and has run by some &ERROR is occurred.
+ */
+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_START);
+ break;
+
+ case RPKI_CS_SYNC_START:
+ /* We sent Serial/Reset Query in last refresh hook call
+ * and didn't receive Cache Response yet. It is probably
+ * troubles with network. */
+ case RPKI_CS_SYNC_RUNNING:
+ /* We sent Serial/Reset Query in last refresh hook call
+ * and we got Cache Response but didn't get End-Of-Data yet.
+ * It could be a trouble with network or only too long synchronization. */
+ if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than refresh interval %us, resetting connection", cache->refresh_interval);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (cache->state != RPKI_CS_SHUTDOWN && cache->state != RPKI_CS_ERROR_TRANSPORT)
+ rpki_schedule_next_refresh(cache);
+ else
+ rpki_stop_refresh_timer_event(cache);
+}
+
+/**
+ * rpki_retry_hook - control a scheduling of retrying connection to cache server
+ * @tm: retry timer with cache connection instance in data
+ *
+ * This function is periodically called during &ERROR* state cache connection.
+ * The first retry schedule is invoked after any &ERROR* state occurred and
+ * ends by reaching of &ESTABLISHED state again.
+ */
+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_SHUTDOWN:
+ break;
+
+ case RPKI_CS_CONNECTING:
+ case RPKI_CS_SYNC_START:
+ case RPKI_CS_SYNC_RUNNING:
+ if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache))
+ {
+ /* We tried to establish a connection in last retry hook call and haven't done
+ * yet. It looks like troubles with network. We are aggressive here. */
+ CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than retry interval %us, resetting connection.", cache->retry_interval);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ default:
+ rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+ break;
+ }
+
+ if (cache->state != RPKI_CS_ESTABLISHED)
+ rpki_schedule_next_retry(cache);
+ else
+ rpki_stop_retry_timer_event(cache);
+}
+
+/**
+ * rpki_expire_hook - control a expiration of ROA entries
+ * @tm: expire timer with cache connection instance in data
+ *
+ * This function is scheduled after received a |End of Data| PDU.
+ * A waiting interval is calculated dynamically by last update.
+ * If we reach an expiration time then we invoke a restarting
+ * of the protocol.
+ */
+static void
+rpki_expire_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+
+ if (cache->last_update == 0)
+ return;
+
+ CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+ if ((cache->last_update + cache->expire_interval) < now)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "All ROAs expired");
+ rpki_force_restart_proto(cache->p);
+ }
+ else
+ {
+ CACHE_DBG(cache, "Remains %d seconds to become ROAs obsolete", (int)cache->expire_interval - (int)(now - cache->last_update));
+ rpki_schedule_next_expire_check(cache);
+ }
+}
+
+/**
+ * rpki_check_refresh_interval - check validity of refresh interval value
+ * @seconds: suggested value
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+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
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+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
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+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;
+}
+
+
+/*
+ * RPKI Cache
+ */
+
+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;
+
+ cache->state = RPKI_CS_SHUTDOWN;
+ cache->request_session_id = 1;
+ cache->version = RPKI_MAX_VERSION;
+
+ cache->refresh_interval = cf->refresh_interval;
+ cache->retry_interval = cf->retry_interval;
+ cache->expire_interval = cf->expire_interval;
+ cache->refresh_timer = tm_new_set(pool, &rpki_refresh_hook, cache, 0, 0);
+ cache->retry_timer = tm_new_set(pool, &rpki_retry_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;
+
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_TCP: rpki_tr_tcp_init(cache->tr_sock); break;
+ case RPKI_TR_SSH: rpki_tr_ssh_init(cache->tr_sock); break;
+ };
+
+ CACHE_DBG(cache, "Connection object created");
+
+ return cache;
+}
+
+/**
+ * rpki_get_cache_ident - give a text representation of cache server name
+ * @cache: RPKI connection instance
+ *
+ * The function converts cache connection into string.
+ */
+const char *
+rpki_get_cache_ident(struct rpki_cache *cache)
+{
+ return rpki_tr_ident(cache->tr_sock);
+}
+
+static int
+rpki_open_connection(struct rpki_cache *cache)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Opening a connection");
+
+ if (rpki_tr_open(cache->tr_sock) == RPKI_TR_ERROR)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_TR_ERROR;
+ }
+
+ return RPKI_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);
+ proto_notify_state(&cache->p->p, PS_START);
+}
+
+static int
+rpki_shutdown(struct proto *P)
+{
+ struct rpki_proto *p = (void *) P;
+
+ rpki_force_restart_proto(p);
+
+ /* Protocol memory pool will be automatically freed */
+ return PS_DOWN;
+}
+
+
+/*
+ * RPKI Reconfiguration
+ */
+
+static int
+rpki_try_fast_reconnect(struct rpki_cache *cache)
+{
+ if (cache->state == RPKI_CS_ESTABLISHED)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+ return SUCCESSFUL_RECONF;
+ }
+
+ return NEED_RESTART;
+}
+
+/**
+ * rpki_reconfigure_cache - a cache reconfiguration
+ * @p: RPKI protocol instance
+ * @cache: a cache connection
+ * @new: new RPKI configuration
+ * @old: old RPKI configuration
+ *
+ * This function reconfigures existing single cache server connection with new
+ * existing configuration. Generally, a change of time intervals could be
+ * reconfigured without restarting and all others changes requires a restart of
+ * protocol. Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|.
+ */
+static int
+rpki_reconfigure_cache(struct rpki_proto *p UNUSED, struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+ u8 try_fast_reconnect = 0;
+
+ if (strcmp(old->hostname, new->hostname) != 0)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cache server address changed to %s", new->hostname);
+ return NEED_RESTART;
+ }
+
+ if (old->port != new->port)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cache server port changed to %u", new->port);
+ return NEED_RESTART;
+ }
+
+ if (old->tr_config.type != new->tr_config.type)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Transport type changed");
+ return NEED_RESTART;
+ }
+ else if (new->tr_config.type == RPKI_TR_SSH)
+ {
+ struct rpki_tr_ssh_config *ssh_old = (void *) old->tr_config.spec;
+ struct rpki_tr_ssh_config *ssh_new = (void *) new->tr_config.spec;
+ if ((strcmp(ssh_old->bird_private_key, ssh_new->bird_private_key) != 0) ||
+ (strcmp(ssh_old->cache_public_key, ssh_new->cache_public_key) != 0) ||
+ (strcmp(ssh_old->user, ssh_new->user) != 0))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Settings of SSH transport configuration changed");
+ try_fast_reconnect = 1;
+ }
+ }
+
+#define TEST_INTERVAL(name, Name) \
+ if (cache->name##_interval != new->name##_interval || \
+ old->keep_##name##_interval != new->keep_##name##_interval) \
+ { \
+ cache->name##_interval = new->name##_interval; \
+ CACHE_TRACE(D_EVENTS, cache, #Name " interval changed to %u seconds %s", cache->name##_interval, (new->keep_##name##_interval ? "and keep it" : "")); \
+ try_fast_reconnect = 1; \
+ }
+ TEST_INTERVAL(refresh, Refresh);
+ TEST_INTERVAL(retry, Retry);
+ TEST_INTERVAL(expire, Expire);
+#undef TEST_INTERVAL
+
+ if (try_fast_reconnect)
+ return rpki_try_fast_reconnect(cache);
+
+ return SUCCESSFUL_RECONF;
+}
+
+/**
+ * rpki_reconfigure - a protocol reconfiguration hook
+ * @P: a protocol instance
+ * @CF: a new protocol configuration
+ *
+ * This function reconfigures whole protocol.
+ * It sets new protocol configuration into a protocol structure.
+ * Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|.
+ */
+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;
+ struct rpki_cache *cache = p->cache;
+
+ if (!proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)) ||
+ !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)))
+ return NEED_RESTART;
+
+ if (rpki_reconfigure_cache(p, cache, new, old) != SUCCESSFUL_RECONF)
+ return NEED_RESTART;
+
+ return SUCCESSFUL_RECONF;
+}
+
+
+/*
+ * RPKI Protocol Glue
+ */
+
+static struct proto *
+rpki_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct rpki_proto *p = (void *) P;
+
+ proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4));
+ proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6));
+
+ return P;
+}
+
+static int
+rpki_start(struct proto *P)
+{
+ struct rpki_proto *p = (void *) P;
+ struct rpki_config *cf = (void *) P->cf;
+
+ p->cache = rpki_init_cache(p, cf);
+ rpki_start_cache(p->cache);
+
+ return PS_START;
+}
+
+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 ---", name);
+}
+
+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 (P->proto_state == PS_DOWN)
+ return;
+
+ if (cache)
+ {
+ const char *transport_name = "---";
+
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_SSH: transport_name = "SSHv2"; break;
+ case RPKI_TR_TCP: transport_name = "Unprotected over TCP"; break;
+ };
+
+ cli_msg(-1006, " Cache server: %s", rpki_get_cache_ident(cache));
+ cli_msg(-1006, " Status: %s", rpki_cache_state_to_str(cache->state));
+ cli_msg(-1006, " Transport: %s", transport_name);
+ cli_msg(-1006, " Protocol version: %u", cache->version);
+
+ if (cache->request_session_id)
+ cli_msg(-1006, " Session ID: ---");
+ else
+ cli_msg(-1006, " Session ID: %u", cache->session_id);
+
+ if (cache->last_update)
+ {
+ cli_msg(-1006, " Serial number: %u", cache->serial_num);
+ cli_msg(-1006, " Last update: before %us", now - cache->last_update);
+ }
+ else
+ {
+ cli_msg(-1006, " Serial number: ---");
+ cli_msg(-1006, " Last update: ---");
+ }
+
+ rpki_show_proto_info_timer("Refresh interval:", cache->refresh_interval, cache->refresh_timer);
+ rpki_show_proto_info_timer("Retry interval:", cache->retry_interval, cache->retry_timer);
+ rpki_show_proto_info_timer("Expire interval:", cache->expire_interval, cache->expire_timer);
+
+ if (p->roa4_channel)
+ channel_show_info(p->roa4_channel);
+ else
+ cli_msg(-1006, " No roa4 channel");
+
+ if (p->roa6_channel)
+ channel_show_info(p->roa6_channel);
+ else
+ cli_msg(-1006, " No roa6 channel");
+ }
+}
+
+
+/*
+ * RPKI Protocol Configuration
+ */
+
+/**
+ * rpki_check_config - check and complete configuration of RPKI protocol
+ * @cf: RPKI configuration
+ *
+ * This function is called at the end of parsing RPKI protocol configuration.
+ */
+void
+rpki_check_config(struct rpki_config *cf)
+{
+ /* Do not check templates at all */
+ if (cf->c.class == SYM_TEMPLATE)
+ return;
+
+ if (ipa_zero(cf->ip) && cf->hostname == NULL)
+ cf_error("IP address or hostname of cache server must be set");
+
+ /* Set default transport type */
+ if (cf->tr_config.spec == NULL)
+ {
+ cf->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config));
+ cf->tr_config.type = RPKI_TR_TCP;
+ }
+
+ if (cf->port == 0)
+ {
+ /* Set default port numbers */
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_SSH:
+ cf->port = RPKI_SSH_PORT;
+ break;
+ default:
+ cf->port = RPKI_TCP_PORT;
+ }
+ }
+}
+
+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)
+{
+ /* FIXME: Should copy transport */
+}
+
+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,
+};
diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h
new file mode 100644
index 00000000..80ef83df
--- /dev/null
+++ b/proto/rpki/rpki.h
@@ -0,0 +1,165 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * 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 "nest/protocol.h"
+#include "lib/socket.h"
+#include "lib/ip.h"
+
+#include "transport.h"
+#include "packets.h"
+
+#define RPKI_TCP_PORT 323
+#define RPKI_SSH_PORT 22
+#define RPKI_RETRY_INTERVAL 600
+#define RPKI_REFRESH_INTERVAL 3600
+#define RPKI_EXPIRE_INTERVAL 7200
+
+#define RPKI_VERSION_0 0
+#define RPKI_VERSION_1 1
+#define RPKI_MAX_VERSION RPKI_VERSION_1
+
+
+/*
+ * RPKI Cache
+ */
+
+enum rpki_cache_state {
+ RPKI_CS_CONNECTING, /* Socket is establishing the transport connection. */
+ RPKI_CS_ESTABLISHED, /* Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */
+ RPKI_CS_RESET, /* Resetting RTR connection. */
+ RPKI_CS_SYNC_START, /* Sending a Serial/Reset Query PDU and expecting a Cache Response PDU */
+ RPKI_CS_SYNC_RUNNING, /* Receiving validation records from the RTR server. A state between Cache Response PDU and End of Data PDU */
+ RPKI_CS_FAST_RECONNECT, /* Reconnect without any waiting period */
+ RPKI_CS_NO_INCR_UPDATE_AVAIL, /* Server is unable to answer the last Serial Query and sent Cache Reset. */
+ RPKI_CS_ERROR_NO_DATA_AVAIL, /* Server is unable to answer either a Serial Query or a Reset Query because it has no useful data available at this time. */
+ RPKI_CS_ERROR_FATAL, /* Fatal protocol error occurred. */
+ RPKI_CS_ERROR_TRANSPORT, /* Error on the transport socket occurred. */
+ RPKI_CS_SHUTDOWN, /* RTR Socket is stopped. */
+};
+
+struct rpki_cache {
+ pool *pool; /* Pool containing cache objects */
+ struct rpki_proto *p;
+
+ struct rpki_tr_sock *tr_sock; /* Transport specific socket */
+ enum rpki_cache_state state; /* RPKI_CS_* */
+ u32 session_id;
+ u8 request_session_id; /* 1: have to request new session id; 0: we have already received session id */
+ u32 serial_num; /* Serial number denotes the logical version of data from cache server */
+ u8 version; /* Protocol version */
+ bird_clock_t last_update; /* Last successful synchronization with cache server */
+ bird_clock_t last_rx_prefix; /* Last received prefix PDU */
+
+ /* Intervals can be changed by cache server on the fly */
+ u32 refresh_interval; /* Actual refresh interval */
+ u32 retry_interval;
+ u32 expire_interval;
+ timer *retry_timer; /* Retry timer event */
+ timer *refresh_timer; /* Refresh timer event */
+ timer *expire_timer; /* Expire timer event */
+};
+
+const char *rpki_get_cache_ident(struct rpki_cache *cache);
+const char *rpki_cache_state_to_str(enum rpki_cache_state state);
+
+
+/*
+ * Routes handling
+ */
+
+void rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+void rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+
+
+/*
+ * RPKI Protocol Logic
+ */
+
+void rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state);
+
+
+/*
+ * RPKI Timer Events
+ */
+
+const char *rpki_check_refresh_interval(uint seconds);
+const char *rpki_check_retry_interval(uint seconds);
+const char *rpki_check_expire_interval(uint seconds);
+
+
+/*
+ * RPKI Protocol Configuration
+ */
+
+struct rpki_proto {
+ struct proto p;
+ struct rpki_cache *cache;
+
+ struct channel *roa4_channel;
+ struct channel *roa6_channel;
+ u8 refresh_channels; /* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */
+};
+
+struct rpki_config {
+ struct proto_config c;
+ const char *hostname; /* Full domain name or stringified IP address of cache server */
+ ip_addr ip; /* IP address of cache server or IPA_NONE */
+ u16 port; /* Port number of cache server */
+ struct rpki_tr_config tr_config; /* Specific transport configuration structure */
+ u32 refresh_interval; /* Time interval (in seconds) for periodical downloading data from cache server */
+ u32 retry_interval; /* Time interval (in seconds) for an unreachable server */
+ u32 expire_interval; /* Maximal lifetime (in seconds) of ROAs without any successful refreshment */
+ u8 keep_refresh_interval:1; /* Do not overwrite refresh interval by cache server update */
+ u8 keep_retry_interval:1; /* Do not overwrite retry interval by cache server update */
+ u8 keep_expire_interval:1; /* Do not overwrite expire interval by cache server update */
+};
+
+void rpki_check_config(struct rpki_config *cf);
+
+
+/*
+ * Logger
+ */
+
+#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] %s " msg, rpki_get_cache_ident(cache), rpki_cache_state_to_str((cache)->state), __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, msg, ## args); \
+ } while(0)
+
+#define RPKI_WARN(p, msg, args...) RPKI_LOG(L_WARN, p, msg, ## args);
+
+#endif /* _BIRD_RPKI_H_ */
diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c
new file mode 100644
index 00000000..cd49ab90
--- /dev/null
+++ b/proto/rpki/ssh_transport.c
@@ -0,0 +1,75 @@
+/*
+ * BIRD -- An implementation of the SSH protocol for the RPKI transport
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ * This transport implementation uses libssh (http://www.libssh.org/)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "rpki.h"
+
+static int
+rpki_tr_ssh_open(struct rpki_tr_sock *tr)
+{
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+ struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec;
+ sock *sk = tr->sk;
+
+ sk->type = SK_SSH_ACTIVE;
+ sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock));
+ sk->ssh->username = ssh_cf->user;
+ sk->ssh->client_privkey_path = ssh_cf->bird_private_key;
+ sk->ssh->server_hostkey_path = ssh_cf->cache_public_key;
+ sk->ssh->subsystem = "rpki-rtr";
+ sk->ssh->state = SK_SSH_CONNECT;
+
+ if (sk_open(sk) != 0)
+ return RPKI_TR_ERROR;
+
+ return RPKI_TR_SUCCESS;
+}
+
+static const char *
+rpki_tr_ssh_ident(struct rpki_tr_sock *tr)
+{
+ ASSERT(tr != NULL);
+
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+ struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec;
+
+ if (tr->ident != NULL)
+ return tr->ident;
+
+ const char *username = ssh_cf->user;
+ const char *host = cf->hostname;
+ u16 port = cf->port;
+
+ size_t len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* <user> + '@' + <host> + ':' + <port> + '\0' */
+ char *ident = mb_alloc(cache->pool, len);
+ bsnprintf(ident, len, "%s@%s:%u", username, host, port);
+ tr->ident = ident;
+
+ return tr->ident;
+}
+
+/**
+ * rpki_tr_ssh_init - initializes the RPKI transport structure for a SSH connection
+ * @tr: allocated RPKI transport structure
+ */
+void
+rpki_tr_ssh_init(struct rpki_tr_sock *tr)
+{
+ tr->open_fp = &rpki_tr_ssh_open;
+ tr->ident_fp = &rpki_tr_ssh_ident;
+}
diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c
new file mode 100644
index 00000000..6c05964a
--- /dev/null
+++ b/proto/rpki/tcp_transport.c
@@ -0,0 +1,78 @@
+/*
+ * BIRD -- An implementation of the TCP protocol for the RPKI protocol transport
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#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 "sysdep/unix/unix.h"
+
+static int
+rpki_tr_tcp_open(struct rpki_tr_sock *tr)
+{
+ sock *sk = tr->sk;
+
+ sk->type = SK_TCP_ACTIVE;
+
+ if (sk_open(sk) != 0)
+ return RPKI_TR_ERROR;
+
+ return RPKI_TR_SUCCESS;
+}
+
+static const char *
+rpki_tr_tcp_ident(struct rpki_tr_sock *tr)
+{
+ ASSERT(tr != NULL);
+
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ if (tr->ident != NULL)
+ return tr->ident;
+
+ const char *host = cf->hostname;
+ ip_addr ip = cf->ip;
+ u16 port = cf->port;
+
+ size_t colon_and_port_len = 6; /* max ":65535" */
+ size_t ident_len;
+ if (host)
+ ident_len = strlen(host) + colon_and_port_len + 1;
+ else
+ ident_len = IPA_MAX_TEXT_LENGTH + colon_and_port_len + 1;
+
+ char *ident = mb_alloc(cache->pool, ident_len);
+ if (host)
+ bsnprintf(ident, ident_len, "%s:%u", host, port);
+ else
+ bsnprintf(ident, ident_len, "%I:%u", ip, port);
+
+ tr->ident = ident;
+ return tr->ident;
+}
+
+/**
+ * rpki_tr_tcp_init - initializes the RPKI transport structure for a TCP connection
+ * @tr: allocated RPKI transport structure
+ */
+void
+rpki_tr_tcp_init(struct rpki_tr_sock *tr)
+{
+ tr->open_fp = &rpki_tr_tcp_open;
+ tr->ident_fp = &rpki_tr_tcp_ident;
+}
diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c
new file mode 100644
index 00000000..182667be
--- /dev/null
+++ b/proto/rpki/transport.c
@@ -0,0 +1,135 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "rpki.h"
+#include "transport.h"
+#include "sysdep/unix/unix.h"
+
+/**
+ * rpki_hostname_autoresolv - auto-resolve an IP address from a hostname
+ * @host: domain name of host, e.g. "rpki-validator.realmv6.org"
+ *
+ * This function resolves an IP address from a hostname.
+ * Returns &ip_addr structure with IP address or |IPA_NONE|.
+ */
+static ip_addr
+rpki_hostname_autoresolv(const char *host)
+{
+ ip_addr addr = {};
+ struct addrinfo *res;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_ADDRCONFIG,
+ };
+
+ if (!host)
+ return IPA_NONE;
+
+ int err_code = getaddrinfo(host, NULL, &hints, &res);
+ if (err_code != 0)
+ {
+ log(L_DEBUG "getaddrinfo failed: %s", gai_strerror(err_code));
+ return IPA_NONE;
+ }
+
+ sockaddr sa = {
+ .sa = *res->ai_addr,
+ };
+
+ uint unused;
+ sockaddr_read(&sa, res->ai_family, &addr, NULL, &unused);
+
+ freeaddrinfo(res);
+ return addr;
+}
+
+/**
+ * rpki_tr_open - prepare and open a socket connection
+ * @tr: initialized transport socket
+ *
+ * Prepare and open a socket connection specified by @tr that must be initialized before.
+ * This function ends with a calling the sk_open() function.
+ * Returns RPKI_TR_SUCCESS or RPKI_TR_ERROR.
+ */
+int
+rpki_tr_open(struct rpki_tr_sock *tr)
+{
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ ASSERT(tr->sk == NULL);
+ tr->sk = sk_new(cache->pool);
+ sock *sk = tr->sk;
+
+ /* sk->type -1 is invalid value, a correct value MUST be set in the specific transport layer in open_fp() hook */
+ sk->type = -1;
+
+ sk->tx_hook = rpki_connected_hook;
+ sk->err_hook = rpki_err_hook;
+ sk->data = cache;
+ sk->daddr = cf->ip;
+ sk->dport = cf->port;
+ sk->host = cf->hostname;
+ sk->rbsize = RPKI_RX_BUFFER_SIZE;
+ sk->tbsize = RPKI_TX_BUFFER_SIZE;
+ sk->tos = IP_PREC_INTERNET_CONTROL;
+
+ if (ipa_zero2(sk->daddr) && sk->host)
+ {
+ sk->daddr = rpki_hostname_autoresolv(sk->host);
+ if (ipa_zero(sk->daddr))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cannot resolve the hostname '%s'", sk->host);
+ return RPKI_TR_ERROR;
+ }
+ }
+
+ return tr->open_fp(tr);
+}
+
+/**
+ * rpki_tr_close - close socket and prepare it for possible next open
+ * @tr: successfully opened transport socket
+ *
+ * Close socket and free resources.
+ */
+void
+rpki_tr_close(struct rpki_tr_sock *tr)
+{
+ if (tr->ident)
+ {
+ mb_free((char *) tr->ident);
+ tr->ident = NULL;
+ }
+
+ if (tr->sk)
+ {
+ rfree(tr->sk);
+ tr->sk = NULL;
+ }
+}
+
+/**
+ * rpki_tr_ident - Returns a string identifier for the rpki transport socket
+ * @tr: successfully opened transport socket
+ *
+ * Returns a \0 terminated string identifier for the socket endpoint, e.g. "<host>:<port>".
+ * Memory is allocated inside @tr structure.
+ */
+inline const char *
+rpki_tr_ident(struct rpki_tr_sock *tr)
+{
+ return tr->ident_fp(tr);
+}
diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h
new file mode 100644
index 00000000..f90b7e42
--- /dev/null
+++ b/proto/rpki/transport.h
@@ -0,0 +1,79 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com>
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/*
+ * The RPKI transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RPKI server and client.
+ *
+ * Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * rpki_tr_tcp_init()).
+ *
+ * The rpki_tr_* functions call the corresponding function pointers, which are
+ * passed in the rpki_tr_sock structure, and forward the remaining arguments.
+ */
+
+#ifndef _BIRD_RPKI_TRANSPORT_H_
+#define _BIRD_RPKI_TRANSPORT_H_
+
+#include <time.h>
+
+/* The return values for rpki_tr_ functions */
+enum rpki_tr_rtvals {
+ RPKI_TR_SUCCESS = 0, /* Operation was successful */
+ RPKI_TR_ERROR = -1, /* Error occurred */
+ RPKI_TR_WOULDBLOCK = -2, /* No data is available on the socket */
+ RPKI_TR_INTR = -3, /* Call was interrupted from a signal */
+ RPKI_TR_CLOSED = -4 /* Connection closed */
+};
+
+/* A transport socket structure */
+struct rpki_tr_sock {
+ sock *sk; /* Standard BIRD socket */
+ struct rpki_cache *cache; /* Cache server */
+ int (*open_fp)(struct rpki_tr_sock *); /* Function that establishes the socket connection */
+ const char *(*ident_fp)(struct rpki_tr_sock *); /* Function that returns an identifier for the socket endpoint */
+ const char *ident; /* Internal. Use ident_fp() hook instead of this pointer */
+};
+
+int rpki_tr_open(struct rpki_tr_sock *tr);
+void rpki_tr_close(struct rpki_tr_sock *tr);
+const char *rpki_tr_ident(struct rpki_tr_sock *tr);
+
+/* Types of supported transports */
+enum rpki_tr_type {
+ RPKI_TR_TCP, /* Unprotected transport over TCP */
+ RPKI_TR_SSH, /* Protected transport by SSHv2 connection */
+};
+
+/* Common configure structure for transports */
+struct rpki_tr_config {
+ enum rpki_tr_type type; /* RPKI_TR_TCP or RPKI_TR_SSH */
+ const void *spec; /* Specific transport configuration, i.e. rpki_tr_tcp_config or rpki_tr_ssh_config */
+};
+
+struct rpki_tr_tcp_config {
+ /* No internal configuration data */
+};
+
+struct rpki_tr_ssh_config {
+ const char *bird_private_key; /* Filepath to the BIRD server private key */
+ const char *cache_public_key; /* Filepath to the public key of cache server, can be file known_hosts */
+ const char *user; /* Username for SSH connection */
+};
+
+/* ssh_transport.c */
+void rpki_tr_ssh_init(struct rpki_tr_sock *tr);
+
+/* tcp_transport.c */
+void rpki_tr_tcp_init(struct rpki_tr_sock *tr);
+
+#endif /* _BIRD_RPKI_TRANSPORT_H_ */