]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
RPKI protocol with integrated RTRLib inside
authorPavel Tvrdík <pawel.tvrdik@gmail.com>
Thu, 17 Sep 2015 15:15:30 +0000 (17:15 +0200)
committerPavel Tvrdík <pawel.tvrdik@gmail.com>
Mon, 25 Jan 2016 14:39:38 +0000 (15:39 +0100)
Add the RPKI protocol (RFC 6810) using the RTRLib
(http://rpki.realmv6.org/) that is integrated inside
the BIRD's code.

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

The code should work properly with one or more cache servers per protocol.

Example configuration of bird.conf:
  ...
  roa4 table roatable;

  protocol rpki {
    table roatable;

    cache 127.0.0.1; # defaults: port 8282, preference 1, no encryption

    cache 127.0.0.1 {
      preference 1;
      port 2222;
      ssh encryption {
        bird private key "/home/birdgeek/.ssh/id_rsa";
        cache public key "/home/birdgeek/.ssh/known_hosts";
        user "birdgeek";
      };
    };

    cache "rpki-validator.realmv6.org" {
      preference 2;
    };
  }
  ...

29 files changed:
conf/confbase.Y
configure.in
lib/Modules
lib/libssh.c [new file with mode: 0644]
lib/libssh.h [new file with mode: 0644]
lib/socket.h
nest/proto.c
nest/protocol.h
nest/route.h
proto/Doc
proto/rpki/Doc [new file with mode: 0644]
proto/rpki/Makefile [new file with mode: 0644]
proto/rpki/config.Y [new file with mode: 0644]
proto/rpki/packets.c [new file with mode: 0644]
proto/rpki/packets.h [new file with mode: 0644]
proto/rpki/rpki.c [new file with mode: 0644]
proto/rpki/rpki.h [new file with mode: 0644]
proto/rpki/rtr.c [new file with mode: 0644]
proto/rpki/rtr.h [new file with mode: 0644]
proto/rpki/ssh_transport.c [new file with mode: 0644]
proto/rpki/ssh_transport.h [new file with mode: 0644]
proto/rpki/tcp_transport.c [new file with mode: 0644]
proto/rpki/tcp_transport.h [new file with mode: 0644]
proto/rpki/transport.c [new file with mode: 0644]
proto/rpki/transport.h [new file with mode: 0644]
sysdep/autoconf.h.in
sysdep/unix/io.c
tools/Makefile.in
tools/Rules.in

index 94a20fe71097c11513e87522a4cb1eb9cb361d68..3fec331f0b3478bb2357bf451414f4db35f42d3c 100644 (file)
@@ -10,6 +10,8 @@ CF_HDR
 
 #define PARSER 1
 
+#include <stdio.h>
+
 #include "nest/bird.h"
 #include "conf/conf.h"
 #include "lib/resource.h"
@@ -26,6 +28,13 @@ CF_HDR
 
 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)
 {
@@ -33,6 +42,16 @@ 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 {
index 1c2c2fe1dae3e17186a164a144056a079bc2afbb..8754b506b6c42ec939d2f2651f7372d7f44cfa68 100644 (file)
@@ -175,9 +175,7 @@ fi
 
 AC_SUBST(iproutedir)
 
-# all_protocols="$proto_bfd bgp ospf pipe radv rip static"
-all_protocols="$proto_bfd ospf pipe radv rip static"
-
+all_protocols="$proto_bfd ospf pipe radv rip rpki static" # TODO: add BGP
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
 if test "$with_protocols" = all ; then
@@ -234,6 +232,10 @@ if test "$enable_debug" = yes ; 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
index 6b9b4b0f28eb3a4e280ba9c7185e663eb866fa06..dfed9edd97cf02ef2a78d0b608692f90c3822893 100644 (file)
@@ -34,3 +34,5 @@ checksum.c
 checksum.h
 alloca.h
 net.c
+libssh.c
+libssh.h
\ No newline at end of file
diff --git a/lib/libssh.c b/lib/libssh.c
new file mode 100644 (file)
index 0000000..c8b00da
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *     BIRD -- Mockup of SSH Library for loading LibSSH using dlopen
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was part of SSH Library: http://www.libssh.org/
+ *     (c) 2003-2009 by Aris Adamantiadis (SSH Library)
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <dlfcn.h>
+#include "nest/bird.h"
+#include "lib/libssh.h"
+
+#define FILENAME_OF_SHARED_OBJECT_LIBSSH "libssh.so"
+
+static void *libssh;
+
+/*
+ * @return NULL if success
+ * @return string with error if failed
+ */
+const char *
+load_libssh(void)
+{
+  char *err_buf;
+
+  libssh = dlopen(FILENAME_OF_SHARED_OBJECT_LIBSSH, RTLD_LAZY);
+  if (!libssh)
+  {
+    /* This would be probably often repeated problem */
+    char *help_msg = "You have to install libssh library.";
+    err_buf = mb_alloc(&root_pool, 512); /* FIXME: free memory */
+    bsnprintf(err_buf, 512, "%s. %s", dlerror(), help_msg);
+    return err_buf;
+  }
+
+  dlerror(); /* Clear any existing error */
+
+  ssh_new = (ssh_session (*)(void)) dlsym(libssh, "ssh_new");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_set_blocking = (void (*)(ssh_session, int)) dlsym(libssh, "ssh_set_blocking");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_options_set = (int (*)(ssh_session, enum ssh_options_e, const void *)) dlsym(libssh, "ssh_options_set");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_connect = (int (*)(ssh_session)) dlsym(libssh, "ssh_connect");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_get_fd = (socket_t (*)(ssh_session)) dlsym(libssh, "ssh_get_fd");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_is_server_known = (int (*)(ssh_session)) dlsym(libssh, "ssh_is_server_known");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_userauth_publickey_auto = (int (*)(ssh_session, const char *, const char *)) dlsym(libssh, "ssh_userauth_publickey_auto");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_get_error = (const char * (*)(void *)) dlsym(libssh, "ssh_get_error");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_get_error_code = (int (*)(void *)) dlsym(libssh, "ssh_get_error_code");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_disconnect = (void (*)(ssh_session)) dlsym(libssh, "ssh_disconnect");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_free = (void (*)(ssh_session)) dlsym(libssh, "ssh_free");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_new = (ssh_channel (*)(ssh_session)) dlsym(libssh, "ssh_channel_new");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_is_open = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_is_open");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_close = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_close");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_free = (void (*)(ssh_channel)) dlsym(libssh, "ssh_channel_free");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_open_session = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_open_session");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_request_subsystem = (int (*)(ssh_channel, const char *)) dlsym(libssh, "ssh_channel_request_subsystem");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_read_nonblocking = (int (*)(ssh_channel, void *, uint32_t, int)) dlsym(libssh, "ssh_channel_read_nonblocking");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_is_eof = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_is_eof");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_select = (int (*)(ssh_channel *, ssh_channel *, ssh_channel *, struct timeval *)) dlsym(libssh, "ssh_channel_select");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  ssh_channel_write = (int (*)(ssh_channel, const void *, uint32_t)) dlsym(libssh, "ssh_channel_write");
+  if ((err_buf = dlerror()) != NULL)
+    return err_buf;
+
+  return NULL;
+}
diff --git a/lib/libssh.h b/lib/libssh.h
new file mode 100644 (file)
index 0000000..15e4579
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *     BIRD -- Mockup headers of SSH Library for loading LibSSH using dlopen
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was part of SSH Library: http://www.libssh.org/
+ *     (c) 2003-2009 by Aris Adamantiadis (SSH Library)
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_LIBSSH_H_
+#define _BIRD_LIBSSH_H_
+
+#include <unistd.h>
+#include <inttypes.h>
+
+typedef struct ssh_session_struct* ssh_session;
+typedef struct ssh_channel_struct* ssh_channel;
+
+/* Error return codes */
+#define SSH_OK 0     /* No error */
+#define SSH_ERROR -1 /* Error of some kind */
+#define SSH_AGAIN -2 /* The nonblocking call must be repeated */
+#define SSH_EOF -127 /* We have already a eof */
+
+enum ssh_server_known_e {
+  SSH_SERVER_ERROR=-1,
+  SSH_SERVER_NOT_KNOWN=0,
+  SSH_SERVER_KNOWN_OK,
+  SSH_SERVER_KNOWN_CHANGED,
+  SSH_SERVER_FOUND_OTHER,
+  SSH_SERVER_FILE_NOT_FOUND
+};
+
+enum ssh_auth_e {
+  SSH_AUTH_SUCCESS=0,
+  SSH_AUTH_DENIED,
+  SSH_AUTH_PARTIAL,
+  SSH_AUTH_INFO,
+  SSH_AUTH_AGAIN,
+  SSH_AUTH_ERROR=-1
+};
+
+enum ssh_error_types_e {
+  SSH_NO_ERROR=0,
+  SSH_REQUEST_DENIED,
+  SSH_FATAL,
+  SSH_EINTR
+};
+
+enum ssh_options_e {
+  SSH_OPTIONS_HOST,
+  SSH_OPTIONS_PORT,
+  SSH_OPTIONS_PORT_STR,
+  SSH_OPTIONS_FD,
+  SSH_OPTIONS_USER,
+  SSH_OPTIONS_SSH_DIR,
+  SSH_OPTIONS_IDENTITY,
+  SSH_OPTIONS_ADD_IDENTITY,
+  SSH_OPTIONS_KNOWNHOSTS,
+  SSH_OPTIONS_TIMEOUT,
+  SSH_OPTIONS_TIMEOUT_USEC,
+  SSH_OPTIONS_SSH1,
+  SSH_OPTIONS_SSH2,
+  SSH_OPTIONS_LOG_VERBOSITY,
+  SSH_OPTIONS_LOG_VERBOSITY_STR,
+  SSH_OPTIONS_CIPHERS_C_S,
+  SSH_OPTIONS_CIPHERS_S_C,
+  SSH_OPTIONS_COMPRESSION_C_S,
+  SSH_OPTIONS_COMPRESSION_S_C,
+  SSH_OPTIONS_PROXYCOMMAND,
+  SSH_OPTIONS_BINDADDR,
+  SSH_OPTIONS_STRICTHOSTKEYCHECK,
+  SSH_OPTIONS_COMPRESSION,
+  SSH_OPTIONS_COMPRESSION_LEVEL,
+  SSH_OPTIONS_KEY_EXCHANGE,
+  SSH_OPTIONS_HOSTKEYS,
+  SSH_OPTIONS_GSSAPI_SERVER_IDENTITY,
+  SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY,
+  SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS,
+  SSH_OPTIONS_HMAC_C_S,
+  SSH_OPTIONS_HMAC_S_C,
+};
+
+enum {
+  /** No logging at all
+   */
+  SSH_LOG_NOLOG=0,
+  /** Only warnings
+   */
+  SSH_LOG_WARNING,
+  /** High level protocol information
+   */
+  SSH_LOG_PROTOCOL,
+  /** Lower level protocol infomations, packet level
+   */
+  SSH_LOG_PACKET,
+  /** Every function path
+   */
+  SSH_LOG_FUNCTIONS
+};
+
+#ifndef socket_t
+typedef int socket_t;
+#endif
+
+ssh_session (*ssh_new)(void);
+void (*ssh_set_blocking)(ssh_session session, int blocking);
+int (*ssh_options_set)(ssh_session session, enum ssh_options_e type, const void *value);
+int (*ssh_connect)(ssh_session session);
+socket_t (*ssh_get_fd)(ssh_session session);
+int (*ssh_is_server_known)(ssh_session session);
+int (*ssh_userauth_publickey_auto)(ssh_session session, const char *username, const char *passphrase);
+const char * (*ssh_get_error)(void *error);
+int (*ssh_get_error_code)(void *error);
+void (*ssh_disconnect)(ssh_session session);
+void (*ssh_free)(ssh_session session);
+
+ssh_channel (*ssh_channel_new)(ssh_session session);
+int (*ssh_channel_is_open)(ssh_channel channel);
+int (*ssh_channel_close)(ssh_channel channel);
+void (*ssh_channel_free)(ssh_channel channel);
+int (*ssh_channel_open_session)(ssh_channel channel);
+int (*ssh_channel_request_subsystem)(ssh_channel channel, const char *subsystem);
+int (*ssh_channel_read_nonblocking)(ssh_channel channel, void *dest, uint32_t count, int is_stderr);
+int (*ssh_channel_is_eof)(ssh_channel channel);
+int (*ssh_channel_select)(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct timeval * timeout);
+int (*ssh_channel_write)(ssh_channel channel, const void *data, uint32_t len);
+
+const char *load_libssh(void);
+
+#endif /* _BIRD_LIBSSH_H_ */
index 1b03098d6467e72f01140d614ec9d63390c3454a..7a04d99a5541c7a044f0a44ea41dbe06957a9e7e 100644 (file)
 #define _BIRD_SOCKET_H_
 
 #include <errno.h>
+// #include <sys/socket.h>
 
 #include "lib/resource.h"
+#include "lib/libssh.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;
@@ -19,6 +38,7 @@ typedef struct birdsock {
   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 */
@@ -50,7 +70,8 @@ typedef struct birdsock {
   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 */
@@ -114,6 +135,8 @@ extern int sk_priority_control;             /* Suggested priority for control traffic, shou
 #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
 
 /* Socket families */
 
index d04da333c6731809acabb21dce1e834ac64b2181..b5179d0074869806c565ba039735d9eb308cc575 100644 (file)
@@ -877,6 +877,7 @@ proto_build(struct protocol *p)
 
 /* FIXME: convert this call to some protocol hook */
 extern void bfd_init_all(void);
+extern void rpki_init_all(void);
 
 /**
  * protos_build - build a protocol list
@@ -919,6 +920,10 @@ protos_build(void)
   proto_build(&proto_bfd);
   bfd_init_all();
 #endif
+#ifdef CONFIG_RPKI
+  proto_build(&proto_rpki);
+  rpki_init_all();
+#endif
 
   proto_pool = rp_new(&root_pool, "Protocols");
   proto_flush_event = ev_new(proto_pool);
index 8c49154ffc656d11c932bd21b04c0007cb93af9c..384f805b4002f43a00cadf329a8ec496bf3db479 100644 (file)
@@ -76,7 +76,7 @@ void protos_dump_all(void);
 
 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
index eba3d9b051ecd9ef87941ec3facef9c81568cce2..f3a1a9aca8ebf54fb068f24deb706c2a1474ee40 100644 (file)
@@ -386,6 +386,7 @@ typedef struct rta {
 #define RTS_OSPF_EXT2 10               /* OSPF external route type 2 */
 #define RTS_BGP 11                     /* BGP route */
 #define RTS_PIPE 12                    /* Inter-table wormhole */
+#define RTS_RPKI 13                    /* Route Origin Authorization */
 
 #define RTC_UNICAST 0
 #define RTC_BROADCAST 1
index 7863472f3c36dfa725d93f32357dfe04a2821a7c..48f754ceb5ee1503e9ee9736c02393a20cd00d38 100644 (file)
--- a/proto/Doc
+++ b/proto/Doc
@@ -3,7 +3,8 @@ C bfd
 C bgp
 C ospf
 C pipe
-C rip
 C radv
+C rip
+C rpki
 C static
 S ../nest/rt-dev.c
diff --git a/proto/rpki/Doc b/proto/rpki/Doc
new file mode 100644 (file)
index 0000000..3ffa7cb
--- /dev/null
@@ -0,0 +1 @@
+C rpki.c
diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile
new file mode 100644 (file)
index 0000000..eca40c2
--- /dev/null
@@ -0,0 +1,5 @@
+source=rpki.c packets.c rtr.c tcp_transport.c ssh_transport.c transport.c
+root-rel=../../
+dir-name=proto/rpki
+
+include ../../Rules
diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y
new file mode 100644 (file)
index 0000000..f1444e4
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ *     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_KEYWORDS(RETRY, REFRESH, EXPIRE)
+
+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;
+ }
+ | REFRESH expr { this_rpki_cache_cfg->refresh_interval = $2; }
+ | RETRY   expr { this_rpki_cache_cfg->retry_interval = $2;   }
+ | EXPIRE  expr { this_rpki_cache_cfg->expire_interval = $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
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c
new file mode 100644 (file)
index 0000000..7e628cb
--- /dev/null
@@ -0,0 +1,1020 @@
+/*
+ *     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"
+
+#define RPKI_PREFIX_FLAG_ADD   1
+#define RPKI_PREFIX_FLAG_DELETE        0
+
+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;
+};
+
+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 void
+pfx_table_add(struct rpki_cache *cache, const net_addr_union *pfxr)
+{
+  struct rpki_proto *p = cache->p;
+
+  char addr_buf[100];
+  net_format((net_addr *)pfxr, addr_buf, sizeof(addr_buf));
+  CACHE_TRACE(D_EVENTS, cache, "Import %s", addr_buf);
+
+  net *n = net_get(cache->p->p.table, &pfxr->n);
+
+  rta a0 = {
+    .src = rt_get_source(&p->p, cache->cache_id),
+    .source = RTS_RPKI,
+    .scope = SCOPE_UNIVERSE,
+    .cast = RTC_UNICAST,
+    .dest = RTD_BLACKHOLE,
+  };
+
+  rta *a = rta_lookup(&a0);
+  rte *e = rte_get_temp(a);
+
+  e->net = n;
+  e->pflags = 0;
+
+  rte_update2(p->p.main_ahook, n, e, a0.src);
+}
+
+static void
+pfx_table_remove(struct rpki_cache *cache, const net_addr_union *pfxr)
+{
+  struct rpki_proto *p = cache->p;
+
+  char addr_buf[100];
+  net_format((net_addr *)pfxr, addr_buf, sizeof(addr_buf));
+  CACHE_TRACE(D_EVENTS, cache, "Remove %s", addr_buf);
+
+  net *n = net_get(cache->p->p.table, &pfxr->n);
+  rte_update2(p->p.main_ahook, n, NULL, rt_get_source(&cache->p->p, cache->cache_id));
+}
+
+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));
+
+  /*
+   * TODO: The code below will be replaced with using channels technology
+   */
+
+  rtable *tab = cache->p->p.table;
+  struct fib_iterator fit;
+  struct fib *fib = &tab->fib;
+
+  FIB_ITERATE_INIT(&fit, fib);
+  FIB_ITERATE_START(fib, &fit, net, n)
+  {
+    if (n->routes)
+    {
+      rte *e, *safety_next;
+      for (e = n->routes; rte_is_valid(e); )
+      {
+       safety_next = e->next;
+        if (e->attrs && e->attrs->src && e->attrs->src->private_id == cache->cache_id)
+          rte_discard(tab, e);
+        e = safety_next;
+      }
+    }
+  } FIB_ITERATE_END;
+}
+
+
+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 *sq_pdu = pdu;
+      sq_pdu->sn = htonl(sq_pdu->sn);
+      break;
+    }
+
+    case ERROR:
+    {
+      struct pdu_error *err_pdu = pdu;
+      err_pdu->len_enc_pdu = htonl(err_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;
+
+  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;
+    }
+
+    case SERIAL_QUERY:
+    case RESET_QUERY:
+    case CACHE_RESPONSE:
+    case CACHE_RESET:
+    case ROUTER_KEY:
+      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)
+{
+  if (cache->p->p.debug & D_PACKETS)
+  {
+    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 detail[100];
+    switch (header->type)
+    {
+      case SERIAL_NOTIFY:
+      case SERIAL_QUERY:
+      case END_OF_DATA:
+        bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", header->reserved, ((struct pdu_end_of_data_v0 *)header)->sn);
+        break;
+
+      case CACHE_RESPONSE:
+        bsnprintf(detail, sizeof(detail), "(session id: %u)", header->reserved);
+        break;
+
+      case IPV4_PREFIX:
+      {
+        const struct pdu_ipv4 *ipv4 = pdu;
+        bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ip4_from_u32(ipv4->prefix), ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn);
+        break;
+      }
+
+      case IPV6_PREFIX:
+      {
+        const struct pdu_ipv6 *ipv6 = pdu;
+        ip6_addr a = ip6_build(ipv6->prefix[0], ipv6->prefix[1], ipv6->prefix[2], ipv6->prefix[3]);
+        bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", a, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn);
+        break;
+      }
+
+      default:
+        *detail = '\0';
+    }
+
+    if (action == RPKI_RECV)
+    {
+      CACHE_TRACE(D_PACKETS, cache, "Receive a %s packet %s", str_type, detail);
+    }
+    else
+    {
+      CACHE_TRACE(D_PACKETS, cache, "Send a %s packet %s", str_type, detail);
+    }
+  }
+
+  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;
+}
+
+/**
+ * rtr_check_receive_packet - Make a basic validation of received RPKI PDU header:
+ *  - check protocol version
+ *  - check pdu type
+ *  - check size
+ *
+ * @cache cache connection
+ * @param len must <= RTR_MAX_PDU_LEN bytes
+ * @return RTR_SUCCESS
+ * @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;
+  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;
+  }
+
+  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");
+  }
+
+  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;
+  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
+      pfx_table_src_remove(cache);
+      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 net_addr_union
+rtr_prefix_pdu_2_net_addr(const void *pdu)
+{
+  net_addr_union n = {};
+  const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+  if (type == IPV4_PREFIX)
+  {
+    const struct pdu_ipv4 *ipv4 = pdu;
+    n.roa4.type = NET_ROA4;
+    n.roa4.length = sizeof(net_addr_roa4);
+    n.roa4.prefix = ip4_from_u32(ipv4->prefix);
+    n.roa4.asn = ipv4->asn;
+    n.roa4.pxlen = ipv4->prefix_len;
+    n.roa4.max_pxlen = ipv4->max_prefix_len;
+  }
+  else if (type == IPV6_PREFIX)
+  {
+    const struct pdu_ipv6 *ipv6 = pdu;
+    n.roa6.type = NET_ROA6;
+    n.roa6.length = sizeof(net_addr_roa6);
+    memcpy(&n.roa6.prefix, ipv6->prefix, sizeof(n.roa6.prefix));
+    n.roa6.asn = ipv6->asn;
+    n.roa6.pxlen = ipv6->prefix_len;
+    n.roa6.max_pxlen = ipv6->max_prefix_len;
+  }
+
+  return n;
+}
+
+static int
+rtr_handle_prefix_pdu(struct rpki_cache *cache, const void *pdu)
+{
+  struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+  const enum pdu_type type = rtr_get_pdu_type(pdu);
+  ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+  /* TODO: Use channels and this wipe out */
+  if (type == IPV4_PREFIX && cache->p->p.table->addr_type != NET_ROA4)
+    return RTR_ERROR;
+  if (type == IPV6_PREFIX && cache->p->p.table->addr_type != NET_ROA6)
+    return RTR_ERROR;
+
+  net_addr_union addr = rtr_prefix_pdu_2_net_addr(pdu);
+
+  switch (((struct pdu_ipv4 *) pdu)->flags)
+  {
+    case RPKI_PREFIX_FLAG_ADD:
+      pfx_table_add(cache, &addr);
+      break;
+
+    case RPKI_PREFIX_FLAG_DELETE:
+      pfx_table_remove(cache, &addr);
+      break;
+
+    default:
+    {
+      const char *txt = "Prefix PDU with invalid flags value received";
+      size_t pdu_size = (type == IPV4_PREFIX ? sizeof(struct pdu_ipv4) : sizeof(struct pdu_ipv6));
+      CACHE_DBG(cache, "%s", txt);
+      rtr_send_error_pdu(cache, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt));
+      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;
+
+  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),"Received session_id %u, but expected was session_id %u", eod_pdu->session_id, rtr_socket->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
+rtr_transform_pdu_to_host_byte_order(byte *pdu)
+{
+  rtr_pdu_header_to_host_byte_order(pdu);
+  rtr_pdu_footer_to_host_byte_order(pdu);
+}
+
+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;
+  }
+
+  rtr_transform_pdu_to_host_byte_order(pdu);
+  rpki_log_packet(cache, pdu, len, RPKI_RECV);
+
+  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:
+      /* This is a signal to synchronize with the cache server just 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_handle_prefix_pdu(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;
+
+  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;
+
+  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)
+{
+  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;
+
+  while (rpki_fire_tx(cache) > 0)
+    ;
+}
+
+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 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;
+}
diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h
new file mode 100644 (file)
index 0000000..0fa62cd
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *     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
+
+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
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
new file mode 100644 (file)
index 0000000..fc3b7c1
--- /dev/null
@@ -0,0 +1,645 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     Using RTRLib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: RPKI to Router Protocol
+ *
+ * The Resource Public Key Infrastructure (RPKI) to router protocol implementation
+ * is based on the RTRlib (http://rpki.realmv6.org/). The BIRD takes over
+ * |packets.c|, |rtr.c|, |transport.c|, |tcp_transport.c| and |ssh_transport.c| files
+ * from RTRlib.
+ *
+ * A SSH transport requires LibSSH library. LibSSH is loading dynamically using dlopen
+ * function.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <stdlib.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include "rpki.h"
+#include "lib/idm.h"
+#include "lib/string.h"
+#include "lib/unix.h"
+
+static struct idm cache_uniq_id_generator;
+
+static const char *mgr_str_status[] = {
+    [RTR_MGR_CLOSED] = "RTR_MGR_CLOSED",
+    [RTR_MGR_CONNECTING] = "RTR_MGR_CONNECTING",
+    [RTR_MGR_ESTABLISHED] = "RTR_MGR_ESTABLISHED",
+    [RTR_MGR_ERROR] = "RTR_MGR_ERROR",
+};
+
+const char *
+get_group_status(struct rpki_cache_group *group)
+{
+  return mgr_str_status[group->status];
+}
+
+void
+rpki_init_all(void)
+{
+  idm_init(&cache_uniq_id_generator, &root_pool, 1);
+}
+
+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);
+}
+
+void
+rpki_print_groups(struct rpki_proto *p)
+{
+  struct rpki_cache_group *g;
+  WALK_LIST(g, p->group_list)
+  {
+    DBG("Group(%u) %s \n", g->preference, get_group_status(g));
+
+    struct rpki_cache *c;
+    WALK_LIST(c, g->cache_list)
+    {
+      DBG("  Cache(%s) %s \n", get_cache_ident(c), rtr_state_to_str(c->rtr_socket->state));
+    }
+  }
+}
+
+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);
+      cache->group = group_iter;
+      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);
+      cache->group = new_group;
+      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);
+  cache->group = new_group;
+}
+
+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;
+}
+
+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->cache_id = idm_alloc(&cache_uniq_id_generator);
+  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;
+  }
+}
+
+/*
+ * Fulfill sock->af and sock->daddr if empty
+ * Return TR_SUCCESS or TR_ERROR
+ */
+static int
+rpki_sock_dst_autoresolv(sock *sk)
+{
+  if (ipa_zero(sk->daddr) && sk->host)
+  {
+    struct addrinfo *res;
+    struct addrinfo *bind_addrinfo = NULL;
+    struct addrinfo hints = {
+       .ai_family = AF_UNSPEC,
+       .ai_socktype = SOCK_STREAM,
+       .ai_flags = AI_ADDRCONFIG,
+    };
+
+    char port[6]; /* max is "65535" + '\0' */
+    bsnprintf(port, sizeof(port), "%u", sk->dport);
+
+    if (getaddrinfo(sk->host, port, &hints, &res) != 0)
+    {
+      CACHE_TRACE(D_EVENTS, (struct rpki_cache *)sk->data, "getaddrinfo error, %s", gai_strerror(errno));
+      return TR_ERROR;
+    }
+
+    sk->af = res->ai_family;
+
+    sockaddr sa = {
+        .sa = *res->ai_addr,
+    };
+
+    uint unused;
+    sockaddr_read(&sa, res->ai_family, &sk->daddr, NULL, &unused);
+
+    freeaddrinfo(res);
+  }
+  else if (ipa_zero(sk->daddr) && !sk->host)
+    return TR_ERROR;
+  else
+    sk->af = ip6_is_v4mapped(sk->daddr) ? AF_INET : AF_INET6;
+
+  return TR_SUCCESS;
+}
+
+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;
+  rtr_change_socket_state(cache->rtr_socket, RTR_OPENING);
+
+  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() */
+  rpki_sock_dst_autoresolv(sk);
+
+  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;
+}
+
+/*
+ * Open connections to all caches in group
+ */
+static void
+rpki_open_group(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_cache_group *group)
+{
+  struct rpki_cache *cache;
+  WALK_LIST(cache, group->cache_list)
+  {
+    if (cache->rtr_socket->state != RTR_SHUTDOWN)
+      rtr_change_socket_state(cache->rtr_socket, RTR_SHUTDOWN);
+  }
+}
+
+static void
+rpki_remove_cache_from_group(struct rpki_cache *cache)
+{
+  rem2_node(&cache->n);
+}
+
+static void
+rpki_free_cache(struct rpki_cache *cache)
+{
+  rpki_remove_cache_from_group(cache);
+  rpki_close_connection(cache);
+  pfx_table_src_remove(cache);
+
+  tr_free(cache->rtr_socket->tr_socket);
+  mb_free(cache->rtr_socket->tr_socket);
+  mb_free(cache->rtr_socket);
+
+  /* Timers */
+  tm_stop(cache->retry_timer);
+  tm_stop(cache->refresh_timer);
+  tm_stop(cache->expire_timer);
+
+  rfree(cache->retry_timer);
+  rfree(cache->refresh_timer);
+  rfree(cache->expire_timer);
+
+  idm_free(&cache_uniq_id_generator, cache->cache_id);
+
+  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;
+}
+
+static void
+remove_empty_cache_groups(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);
+  remove_empty_cache_groups(cache->p);
+}
+
+/*
+ * Go through the group list ordered by priority.
+ * Open the first CLOSED group or stop opening groups if the processed group state is CONNECTING or ESTABLISHED
+ * Then close all groups with the more unimportant priority
+ */
+void
+rpki_relax_groups(struct rpki_proto *p)
+{
+  DBG("Relaxing groups...\n");
+  if (EMPTY_LIST(p->group_list))
+  {
+    RPKI_WARN(p, "No cache in configuration found");
+    return;
+  }
+
+  bool close_all_next_groups = false;
+
+  struct rpki_cache_group *group;
+  WALK_LIST(group, p->group_list)
+  {
+    if (!close_all_next_groups)
+    {
+      switch (group->status)
+      {
+        case RTR_MGR_CLOSED:
+          RPKI_TRACE(D_EVENTS, p, "Open cache group(%u)", group->preference);
+         /* Fall through */
+        case RTR_MGR_CONNECTING:
+        case RTR_MGR_ESTABLISHED:
+          close_all_next_groups = 1;
+         /* Fall through */
+        case RTR_MGR_ERROR:
+          rpki_open_group(group);
+          break;
+      }
+    }
+    else
+    {
+      RPKI_TRACE(D_EVENTS, p, "Close cache group(%u)", group->preference);
+      rpki_close_group(group);
+    }
+  }
+
+  rpki_print_groups(p);
+  return;
+}
+
+static int
+rpki_reconfigure_proto(struct rpki_proto *p, struct rpki_config *new_cf, struct rpki_config *old_cf)
+{
+  if (old_cf->c.table && new_cf->c.table && old_cf->c.table->table != new_cf->c.table->table)
+  {
+    RPKI_TRACE(D_EVENTS, p, "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);
+    }
+  }
+
+  rpki_print_groups(p);
+
+  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);
+  else
+  {
+    RPKI_TRACE(D_EVENTS, p, "Have to restart whole protocol");
+  }
+
+  return continue_without_restart;
+}
+
+static void
+rpki_get_status(struct proto *P, byte *buf)
+{
+  struct rpki_proto *p = (struct rpki_proto *) P;
+
+  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,
+};
diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h
new file mode 100644 (file)
index 0000000..5afab49
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ *     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 "ssh_transport.h"
+#include "tcp_transport.h"
+#include "rtr.h"
+#include "packets.h"
+
+#define RPKI_DEFAULT_PORT              8282
+#define RPKI_DEFAULT_SSH_PORT          22
+#define RPKI_DEFAULT_RETRY_INTERVAL    30
+#define RPKI_DEFAULT_REFRESH_INTERVAL  600
+#define RPKI_DEFAULT_EXPIRE_INTERVAL   1200
+#define RPKI_DEFAULT_CACHE_PREFERENCE  1       /* The most important priority */
+
+/*
+ *             +-------------------------------------------+
+ *             v                                           |
+ *     RTR_MGR_CLOSED <--> RTR_MGR_CONNECTING --> RTR_MGR_ESTABLISHED <--> RTR_MGR_ERROR
+ *             ^                   |                                         ^   |
+ *             |                   +-----------------------------------------+   |
+ *             |                                                                 |
+ *             +-----------------------------------------------------------------+
+ */
+enum rtr_mgr_status {
+  /* RTR sockets are disconnected */
+  RTR_MGR_CLOSED,
+
+  /* RTR sockets trying to establish a connection. */
+  RTR_MGR_CONNECTING,
+
+  /* All RTR sockets of the group are synchronized with the rtr servers. */
+  RTR_MGR_ESTABLISHED,
+
+  /* Error occured on at least one RTR socket. */
+  RTR_MGR_ERROR,
+};
+
+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 rpki_cache_group *group;
+  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 */
+  u32 cache_id;                                /* For purge ROAs learned only 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) * */
+  enum rtr_mgr_status status;
+};
+
+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 */
+};
+
+void rpki_init_all(void);
+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);
+void rpki_relax_groups(struct rpki_proto *p);
+void rpki_print_groups(struct rpki_proto *p);
+
+#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);
+
+#endif /* _BIRD_RPKI_H_ */
diff --git a/proto/rpki/rtr.c b/proto/rpki/rtr.c
new file mode 100644 (file)
index 0000000..c8c1e97
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ *     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_OPENING] = "RTR_OPENING",
+    [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)
+  {
+    switch (rtr_socket->state)
+    {
+      case RTR_ESTABLISHED:
+      case RTR_SYNC:
+       CACHE_DBG(cache, "There are obsolete roa records, but cache is in ESTABLISHED/SYNC state. Bad timing...");
+       return;
+    }
+
+    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];
+}
+
+/*
+ * Set group status to @mgr_status if all sockets of caches in the @group are @socket_state
+ */
+static void
+set_group_status_to_if_all_sockets_are(struct rpki_cache_group *group, const enum rtr_mgr_status mgr_status, const enum rtr_socket_state socket_state)
+{
+  bool do_all_sockets_pass = true;
+
+  struct rpki_cache *cache;
+  WALK_LIST(cache, group->cache_list)
+  {
+    if (cache->rtr_socket->state != socket_state)
+      do_all_sockets_pass = false;
+  }
+  if (do_all_sockets_pass)
+    group->status = mgr_status;
+}
+
+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 (old_state == RTR_SHUTDOWN)
+       cache->group->status = RTR_MGR_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:
+      /* set status of group to RTR_MGR_ESTABLISHED if all caches in the common group are RTR_ESTABLISHED */
+      set_group_status_to_if_all_sockets_are(cache->group, RTR_MGR_ESTABLISHED, RTR_ESTABLISHED);
+      rpki_relax_groups(cache->p);
+      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);
+      cache->group->status = RTR_MGR_ERROR;
+      rpki_relax_groups(cache->p);
+      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);
+
+      /* set status of group to RTR_MGR_CLOSED if all caches in the common group are RTR_SHUTDOWN */
+      set_group_status_to_if_all_sockets_are(cache->group, RTR_MGR_CLOSED, RTR_SHUTDOWN);
+      rpki_relax_groups(cache->p);
+      break;
+  };
+}
+
+/*
+ * Timers
+ */
+
+void
+rtr_schedule_next_refresh(struct rpki_cache *cache)
+{
+  struct rtr_socket *rtr_socket = cache->rtr_socket;
+
+  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)
+{
+  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;
+
+  unsigned time_to_wait = MAX(((int)rtr_socket->expire_interval - (int)(now - rtr_socket->last_update))+3, 1);
+
+  CACHE_DBG(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);
+      rpki_print_groups(p);
+      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);
+}
diff --git a/proto/rpki/rtr.h b/proto/rpki/rtr.h
new file mode 100644 (file)
index 0000000..67b0422
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ *     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.
+ */
+
+#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 {
+    /* State between request for open new socket and asynchronous finish the opening socket */
+    RTR_OPENING,
+
+    /** 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 from last 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 rtr_purge_records_if_outdated(struct rpki_cache *cache);
+void rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state);
+
+void rpki_retry_hook(struct timer *tm);
+void rpki_expire_hook(struct timer *tm);
+void rpki_refresh_hook(struct timer *tm);
+
+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
+/* @} */
diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c
new file mode 100644 (file)
index 0000000..ec7d742
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ *     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 "lib/libssh.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;
+
+  const char *err_msg;
+  if ((err_msg = load_libssh()) != NULL)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "%s", err_msg);
+    return TR_ERROR;
+  }
+
+  sock *sk = cache->sk;
+  sk->type = SK_SSH_ACTIVE;
+  sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock));
+  sk->ssh->username = cache->cfg->ssh->username;
+  sk->ssh->client_privkey_path = cache->cfg->ssh->bird_private_key;
+  sk->ssh->server_hostkey_path = cache->cfg->ssh->cache_public_key;
+  sk->ssh->subsystem = "rpki-rtr";
+  sk->ssh->state = BIRD_SSH_CONNECT;
+
+  if (sk_open(sk) != 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;
+
+  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;
+
+  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 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;
+}
diff --git a/proto/rpki/ssh_transport.h b/proto/rpki/ssh_transport.h
new file mode 100644 (file)
index 0000000..aa28234
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *     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 "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;
+  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
+/* @} */
diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c
new file mode 100644 (file)
index 0000000..7c81b53
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ *     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;
+
+  sock *sk = cache->sk;
+  sk->type = SK_TCP_ACTIVE;
+  sk->daddr = tcp_socket->config.ip;
+
+  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;
+
+  sock *sk = cache->sk;
+  if (sk && sk->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 = IPA_MAX_TEXT_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;
+}
+
+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;
+
+  return TR_SUCCESS;
+}
diff --git a/proto/rpki/tcp_transport.h b/proto/rpki/tcp_transport.h
new file mode 100644 (file)
index 0000000..07e8acd
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *     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
+/* @} */
diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c
new file mode 100644 (file)
index 0000000..8b6de6d
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *     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);
+}
diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h
new file mode 100644 (file)
index 0000000..c577ef6
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *     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
+/* @} */
index a9e46e274c2f85488bf4f3b2f88295404ad139a6..047a49ca2d49ec08f6134a1ab0f746f295bc90ae 100644 (file)
@@ -43,6 +43,7 @@
 #undef CONFIG_BGP
 #undef CONFIG_OSPF
 #undef CONFIG_PIPE
+#undef CONFIG_RPKI
 
 /* We use multithreading */
 #undef USE_PTHREADS
index 8f61fc680bd8e1411d79c7eeec9e581876daab58..a3e11ff1748dca35fd0b4c2ea66fabee04da813f 100644 (file)
@@ -35,6 +35,7 @@
 #include "lib/socket.h"
 #include "lib/event.h"
 #include "lib/string.h"
+#include "lib/libssh.h"
 #include "nest/iface.h"
 
 #include "lib/unix.h"
@@ -1060,12 +1061,42 @@ sk_free_bufs(sock *s)
   }
 }
 
+static void
+sk_ssh_free(sock *s)
+{
+  struct ssh_sock *ssh = s->ssh;
+
+  if (s->ssh == NULL)
+    return;
+
+  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;
+  }
+
+  mb_free(ssh);
+  s->ssh = 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);
+
   if (s->fd >= 0)
   {
     close(s->fd);
@@ -1182,6 +1213,9 @@ sk_setup(sock *s)
   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");
 
@@ -1295,6 +1329,14 @@ sk_tcp_connected(sock *s)
   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)
 {
@@ -1359,6 +1401,193 @@ 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
@@ -1390,6 +1619,11 @@ sk_open(sock *s)
     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(fam_to_af[s->fam], SOCK_DGRAM, IPPROTO_UDP);
     bind_port = s->sport;
@@ -1472,6 +1706,7 @@ sk_open(sock *s)
       ERR2("listen");
     break;
 
+  case SK_SSH_ACTIVE:
   case SK_MAGIC:
     break;
 
@@ -1481,6 +1716,7 @@ sk_open(sock *s)
 
   if (!(s->flags & SKF_THREAD))
     sk_insert(s);
+
   return 0;
 
 err:
@@ -1663,6 +1899,26 @@ sk_maybe_write(sock *s)
     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:
     {
@@ -1687,6 +1943,7 @@ sk_maybe_write(sock *s)
       reset_tx_buffer(s);
       return 1;
     }
+
   default:
     bug("sk_maybe_write: unknown socket type %d", s->type);
   }
@@ -1773,6 +2030,62 @@ sk_send_full(sock *s, unsigned len, struct iface *ifa,
 }
 */
 
+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
@@ -1801,17 +2114,15 @@ sk_read(sock *s)
       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);
 
@@ -1850,6 +2161,25 @@ sk_write(sock *s)
       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)
     {
index 5de323ab4e85adfb78bb6c7daa11ad15b891f540..05a9d39514123a9b081b176db1d16c912e5dbc0c 100644 (file)
@@ -37,8 +37,8 @@ subdir: sysdep/paths.h .dir-stamp .dep-stamp
        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)
index f00c85d1e95bcf2acb8289e3b693a1193f25b421..68409b9df5b9a097c50c0aeb299f533c997ea5ee 100644 (file)
@@ -23,6 +23,7 @@ CPPFLAGS=-I$(root-rel) -I$(srcdir) @CPPFLAGS@
 CFLAGS=$(CPPFLAGS) @CFLAGS@
 LDFLAGS=@LDFLAGS@
 LIBS=@LIBS@
+BIRD_LIBS=@BIRD_LIBS@
 CLIENT_LIBS=@CLIENT_LIBS@
 CC=@CC@
 M4=@M4@