]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
RPKI protocol with one cache server per protocol
authorPavel Tvrdík <pawel.tvrdik@gmail.com>
Thu, 17 Sep 2015 15:15:30 +0000 (17:15 +0200)
committerPavel Tvrdik <pawel.tvrdik@gmail.com>
Thu, 26 May 2016 11:57:19 +0000 (13:57 +0200)
The RPKI protocol (RFC 6810) using the RTRLib
(http://rpki.realmv6.org/) that is integrated inside
the BIRD's code.

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

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

  protocol rpki {
    debug all;

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

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

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

  protocol rpki {
    debug all;

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

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

    # Use SSH transport instead of unprotected transport over TCP
    ssh encryption {
      bird private key "/home/birdgeek/.ssh/id_rsa";
      remote public key "/home/birdgeek/.ssh/known_hosts";
      user "birdgeek";
    };
  }
  ...

32 files changed:
Makefile.in
configure.in
doc/bird.sgml
filter/filter.c
lib/Makefile
lib/libssh.c [new file with mode: 0644]
lib/libssh.h [new file with mode: 0644]
lib/net.c
lib/net.h
lib/resource.c
lib/resource.h
lib/socket.h
nest/proto.c
nest/protocol.h
nest/route.h
nest/rt-table.c
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/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

index c2f8ad4ebcbce15629994c97bac18eb9a16110ad..0f3cb36f6b432010d9c229bcc2e529963fb28d01 100644 (file)
@@ -10,6 +10,7 @@ CPPFLAGS=-I$(objdir) -I$(srcdir) @CPPFLAGS@
 CFLAGS=$(CPPFLAGS) @CFLAGS@
 LDFLAGS=@LDFLAGS@
 LIBS=@LIBS@
+DAEMON_LIBS=@DAEMON_LIBS@
 CLIENT_LIBS=@CLIENT_LIBS@
 CC=@CC@
 M4=@M4@
@@ -55,6 +56,8 @@ all: daemon cli
 daemon: $(daemon)
 cli: $(client)
 
+$(daemon): LIBS += $(DAEMON_LIBS)
+
 # Include directories
 dirs := client conf doc filter lib nest $(addprefix proto/,$(protocols)) @sysdep_dirs@
 
index f1697c1217568b13541e8b359fc69d445036b35f..e5e43e1b7ebfece250f31312bfceaa39a544ddca 100644 (file)
@@ -168,7 +168,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"
 
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
@@ -226,6 +226,10 @@ if test "$enable_debug" = yes ; then
        fi
 fi
 
+DAEMON_LIBS=
+AC_CHECK_LIB(dl, dlopen, DAEMON_LIBS="-ldl")
+AC_SUBST(DAEMON_LIBS)
+
 CLIENT=birdcl
 CLIENT_LIBS=
 if test "$enable_client" = yes ; then
index 5e5aeee496754ade5a77615e825f14990ab08bfd..a93725500196dbe8957288e1db1f7ff6bdf5f56f 100644 (file)
@@ -464,7 +464,7 @@ protocol rip {
        Create a new ROA (Route Origin Authorization) table. ROA tables can be
        used to validate route origination of BGP routes. A ROA table contains
        ROA entries, each consist of a network prefix, a max prefix length and
-       an AS number. A ROA entry specifies prefixes which could be originated
+       an AS (Autonomous System) number. A ROA entry specifies prefixes which could be originated
        by that AS number. ROA tables could be filled with data from RPKI (RFC
        6480) or from public databases like Whois. ROA tables are examined by
        <cf/roa_check()/ operator in filters.
@@ -3494,6 +3494,136 @@ protocol rip {
 }
 </code>
 
+<sect>RPKI
+
+<p>The Resource Public Key Infrastructure (RPKI) to Router Protocol (RFC 6810)
+is a protocol for receiving Route Origin Authorizations (ROAs) from trusted 
+caches (RPKI validators). ROAs are documents used to link a set of prefixes
+with an origin ASN.
+The ROAs could be 
+In BIRD, it is implemented only receiving prefix origin data from a trusted cache.
+RPKI-based origin validation uses some of the RPKI data to allow a router
+to version ... FIXME! srify that the autonomous system announcing an IP address prefix is in fact
+authorized to do so. This is not crypto checked so can be violated. But it
+should prevent the vast majority of accidental 'hijackings' on the internet
+
+It is possible to configure only one remote cache server per protocol yet.
+
+<code>
+protocol rpki [&lt;name&gt;] {
+        remote &lt;ip&gt; | "&lt;domain&gt;" [port &lt;num&gt;];
+        port &lt;num&gt;;
+        retry &lt;num&gt;;
+        refresh &lt;num&gt;;
+        expire &lt;num&gt;;
+        ssh encryption {
+                bird private key "&lt;/path/to/id_rsa&gt;";
+                remote public key "&lt;/path/to/known_host&gt;";
+                user "&lt;name&gt;";
+        };
+}
+</code>
+
+<sect1>RPKI protocol options
+<descrip>
+       <tag>roa table <m/name/</tag>
+       Specifies the roa table into which will import the routes from cache.
+       This option is required.
+
+       <tag>remote <m/ip/ | "<m/hostname/" [port &lt;num&gt;]</tag>
+       Specifies a destination address of the cache server.
+       Can be specified by an IP address or by full domain name.
+       Only one cache can be specified per protocol.
+
+       <tag>port <m/num/</tag>
+       Specifies the port number.
+       The default port number is 323 for transport without any encryption
+       and 22 for transport with SSH encryption.
+
+       <tag>retry <m/num/</tag>
+       Time period in seconds between a failed query and the next attempt.
+       Default: 600 seconds
+
+       <tag>refresh <m/num/</tag>
+       Time period in seconds.
+       Tells how long to wait before next attempting to poll the cache, using
+       a Serial Query or Reset Query PDU. Must be lower than 1 hour.
+       Default: 3600 seconds
+
+       <tag>expire <m/num/</tag>
+       Time period in seconds.
+       Received records are deleted if the client was unable to refresh data
+       for this time period.
+       Default: 7200 seconds
+
+       <tag>ssh encryption { <m/ssh encryption options.../ }</tag>
+       This enables a SSH encryption.
+       Default: off
+</descrip>
+
+<sect1>SSH encryption options in RPKI protocol
+<descrip>
+       <tag>bird private key "<m///path/to/id_rsa"</tag>
+       A path to the BIRD's private SSH key for authentication.
+       It can be a <cf/id_rsa/ file.
+
+       <tag>remote public key "<m///path/to/known_host"</tag>
+       A path to the cache's public SSH key for verification identity
+       of the cache server. It could be a <cf/known_host/ file.
+
+       <tag>user "<m/name/"</tag>
+       A SSH user name for authentication. This option is a required.
+</descrip>
+
+<sect1>Examples:
+<p>Typical RPKI configuration with BGP origin validation
+<code>
+roa4 table r4;
+roa6 table r6;
+
+protocol rpki {
+       debug all;
+       roa4 { table r4; };
+       roa6 { table r6; };
+       remote "rpki-validator.realmv6.org";
+}
+
+filter peer_in {
+       if (roa_check(r4, net, bgp_path.last) = ROA_INVALID ||
+           roa_check(r6, net, bgp_path.last) = ROA_INVALID) then
+       {
+               print "ROA check failed for ", net, " ASN ", bgp_path.last;
+               reject;
+       }
+       accept;
+}
+
+protocol bgp my_peer {
+       local as 65000;
+       neighbor 192.0.2.1 as 65001;
+       import filter peer_in;
+}
+</code>
+
+<p>A configuration using SSHv2 transport encryption:
+<code>
+roa4 table r4;
+roa6 table r6;
+
+protocol rpki {
+       debug all;
+       roa4 { table r4; };
+       roa6 { table r6; };
+       remote 127.0.0.1 port 2345;
+       ssh encryption {
+               bird private key "/home/birdgeek/.ssh/id_rsa";
+               remote public key "/home/birdgeek/.ssh/known_hosts";
+               user "birdgeek";
+       };
+}
+</code>
+
+
 
 <sect>Static
 
index cc1bb3dcf35562711b79fe18ca1a9dc8112d30a3..a524ac4dacd78bc9c8668da45c21dddd8c300eae 100644 (file)
@@ -1277,11 +1277,15 @@ interpret(struct f_inst *what)
     }
 
     struct rtable *table = ((struct f_inst_roa_check *) what)->rtc->table;
-    if (!table || table->addr_type != (v1.val.net->type == NET_IP4 ? NET_ROA4 : NET_ROA6))
+    if (!table || (table->addr_type != NET_ROA4 && table->addr_type != NET_ROA6))
       runtime("Missing ROA table");
 
     res.type = T_ENUM_ROA;
-    res.val.i = net_roa_check(table, v1.val.net, as);
+
+    if (table->addr_type != (v1.val.net->type == NET_IP4 ? NET_ROA4 : NET_ROA6))
+      res.val.i = ROA_UNKNOWN; /* Prefix and table type mismatch */
+    else
+      res.val.i = net_roa_check(table, v1.val.net, as);
 
     break;
 
index a9aae66f931c8c6696f1b8cfafb8d24b471a21eb..1634e5e5c7d95326dc8278dc630dd3695f2b9bad 100644 (file)
@@ -2,6 +2,6 @@ src := bitops.c checksum.c ip.c lists.c md5.c net.c patmatch.c printf.c sha1.c s
 obj := $(src-o-files)
 $(all-client)
 
-src := bitops.c checksum.c event.c idm.c ip.c lists.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c tbf.c xmalloc.c
+src := bitops.c checksum.c event.c idm.c ip.c libssh.c lists.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c tbf.c xmalloc.c
 obj := $(src-o-files)
 $(all-daemon)
diff --git a/lib/libssh.c b/lib/libssh.c
new file mode 100644 (file)
index 0000000..8620c95
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ *     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"
+
+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);
+
+
+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..485cacb
--- /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
+
+extern ssh_session (*ssh_new)(void);
+extern void (*ssh_set_blocking)(ssh_session session, int blocking);
+extern int (*ssh_options_set)(ssh_session session, enum ssh_options_e type, const void *value);
+extern int (*ssh_connect)(ssh_session session);
+extern socket_t (*ssh_get_fd)(ssh_session session);
+extern int (*ssh_is_server_known)(ssh_session session);
+extern int (*ssh_userauth_publickey_auto)(ssh_session session, const char *username, const char *passphrase);
+extern const char * (*ssh_get_error)(void *error);
+extern int (*ssh_get_error_code)(void *error);
+extern void (*ssh_disconnect)(ssh_session session);
+extern void (*ssh_free)(ssh_session session);
+
+extern ssh_channel (*ssh_channel_new)(ssh_session session);
+extern int (*ssh_channel_is_open)(ssh_channel channel);
+extern int (*ssh_channel_close)(ssh_channel channel);
+extern void (*ssh_channel_free)(ssh_channel channel);
+extern int (*ssh_channel_open_session)(ssh_channel channel);
+extern int (*ssh_channel_request_subsystem)(ssh_channel channel, const char *subsystem);
+extern int (*ssh_channel_read_nonblocking)(ssh_channel channel, void *dest, uint32_t count, int is_stderr);
+extern int (*ssh_channel_is_eof)(ssh_channel channel);
+extern int (*ssh_channel_select)(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct timeval * timeout);
+extern int (*ssh_channel_write)(ssh_channel channel, const void *data, uint32_t len);
+
+const char *load_libssh(void);
+
+#endif /* _BIRD_LIBSSH_H_ */
index 71fbe6ffb13c2d1e805203737999d872c34da184..2ef2d91a94458cfb8d75563f2fb1c6023cbace50 100644 (file)
--- a/lib/net.c
+++ b/lib/net.c
@@ -8,7 +8,9 @@ const char * const net_label[] = {
   [NET_IP4] = "ipv4",
   [NET_IP6] = "ipv6",
   [NET_VPN4] = "vpn4",
-  [NET_VPN6] = "vpn6"
+  [NET_VPN6] = "vpn6",
+  [NET_ROA4] = "roa4",
+  [NET_ROA6] = "roa6",
 };
 
 const u16 net_addr_length[] = {
index fbce2811ce3df49a647f189405d27dcaa17c77f9..0ab278ae22457360a145a582bae9d4a7194acad2 100644 (file)
--- a/lib/net.h
+++ b/lib/net.h
@@ -25,6 +25,8 @@
 #define NB_IP6         (1 << NET_IP6)
 #define NB_VPN4                (1 << NET_VPN4)
 #define NB_VPN6                (1 << NET_VPN6)
+#define NB_ROA4                (1 << NET_ROA4)
+#define NB_ROA6                (1 << NET_ROA6)
 
 #define NB_IP          (NB_IP4 | NB_IP6)
 #define NB_ANY         0xffffffff
index 68718dfb617cb4c4ba5f452af8c5ffe8776c0fc8..ab8c800f5b9e1cadb1ce20c1ea5bc4b31c6ff8c2 100644 (file)
@@ -31,7 +31,7 @@
 struct pool {
   resource r;
   list inside;
-  char *name;
+  const char *name;
 };
 
 static void pool_dump(resource *);
@@ -61,7 +61,7 @@ static int indent;
  * parent pool.
  */
 pool *
-rp_new(pool *p, char *name)
+rp_new(pool *p, const char *name)
 {
   pool *z = ralloc(p, &pool_class);
   z->name = name;
index 1a62d3891b5a0297330d0f94198da5443b7be283..1a0568b48b72b4317504c87da707028e790cbd53 100644 (file)
@@ -37,7 +37,7 @@ struct resclass {
 typedef struct pool pool;
 
 void resource_init(void);
-pool *rp_new(pool *, char *);          /* Create new pool */
+pool *rp_new(pool *, const char *);    /* Create new pool */
 void rfree(void *);                    /* Free single resource */
 void rdump(void *);                    /* Dump to debug output */
 size_t rmemsize(void *res);            /* Return size of memory used by the resource */
index 91ae9db3ae2a785493e8d45a05e0a3b398658057..aea1c8d60f310d6e32465f40ae3a702552ef9b03 100644 (file)
 #include <errno.h>
 
 #include "lib/resource.h"
+#include "lib/libssh.h"
+
+struct ssh_sock {
+    const char *username;              /* (Required) SSH user name */
+    const char *server_hostkey_path;   /* (Optional) Filepath to the SSH public key of remote side, can be knownhost file */
+    const char *client_privkey_path;   /* (Optional) Filepath to the SSH private key of BIRD */
+    const char *subsystem;             /* (Optional) Name of SSH subsytem */
+    ssh_session session;               /* Internal */
+    ssh_channel channel;               /* Internal */
+    int state;                         /* Internal */
+#define SK_SSH_CONNECT         0       /* Start state */
+#define SK_SSH_SERVER_KNOWN    1       /* Internal */
+#define SK_SSH_USERAUTH                2       /* Internal */
+#define SK_SSH_CHANNEL         3       /* Internal */
+#define SK_SSH_SESSION         4       /* Internal */
+#define SK_SSH_SUBSYSTEM       5       /* Internal */
+#define SK_SSH_ESTABLISHED     6       /* Final state */
+};
 
 typedef struct birdsock {
   resource r;
@@ -19,6 +37,7 @@ typedef struct birdsock {
   int type;                            /* Socket type */
   void *data;                          /* User data */
   ip_addr saddr, daddr;                        /* IPA_NONE = unspecified */
+  const 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 */
@@ -51,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 */
@@ -115,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 df4952b736373eef2fd678738e9a943f47ef28e1..f5285ea57331b31989e1b892f5bf88d6fb509d94 100644 (file)
@@ -39,12 +39,10 @@ static int graceful_restart_state;
 static u32 graceful_restart_locks;
 
 static char *p_states[] = { "DOWN", "START", "UP", "STOP" };
-static char *cs_states[] = {
-    [CS_DOWN] = "DOWN",
-    [CS_START] = "START",
-    [CS_UP] = "UP",
-    [CS_FLUSHING] = "FLUSHING"
-};
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+static char *cs_states[] = { "DOWN", "START", "UP", "FLUSHING" };
+#endif
 
 extern struct protocol proto_unix_iface;
 
@@ -1263,6 +1261,9 @@ protos_build(void)
   proto_build(&proto_bfd);
   bfd_init_all();
 #endif
+#ifdef CONFIG_RPKI
+  proto_build(&proto_rpki);
+#endif
 
   proto_pool = rp_new(&root_pool, "Protocols");
   proto_shutdown_timer = tm_new(proto_pool);
index 2d640504a8d092d09480c9b8c2486d2039d16f39..69f3bcdb484c1346b8eea921fa0b4081274098d3 100644 (file)
@@ -81,7 +81,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
@@ -271,7 +271,7 @@ proto_get_router_id(struct proto_config *pc)
 }
 
 /* Moved from route.h to avoid dependency conflicts */
-static inline void rte_update(struct proto *p, net_addr *n, rte *new) { rte_update2(p->main_channel, n, new, p->main_source); }
+static inline void rte_update(struct proto *p, const net_addr *n, rte *new) { rte_update2(p->main_channel, n, new, p->main_source); }
 
 extern list proto_list;
 
@@ -562,11 +562,9 @@ int proto_configure_channel(struct proto *p, struct channel **c, struct channel_
 
 void channel_set_state(struct channel *c, uint state);
 
-/*
 static inline void channel_init(struct channel *c) { channel_set_state(c, CS_START); }
 static inline void channel_open(struct channel *c) { channel_set_state(c, CS_UP); }
 static inline void channel_close(struct channel *c) { channel_set_state(c, CS_FLUSHING); }
-*/
 
 void channel_request_feeding(struct channel *c);
 void *channel_config_new(const struct channel_class *cc, uint net_type, struct proto_config *proto);
index 3947530945ab937020f68789974ac388611a66fa..85da8a7f237d0d2f8bdf652753f09ee2ec7cb1f7 100644 (file)
@@ -276,7 +276,7 @@ static inline net *net_get(rtable *tab, const net_addr *addr) { return (net *) f
 
 rte *rte_find(net *net, struct rte_src *src);
 rte *rte_get_temp(struct rta *);
-void rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src);
+void rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src);
 /* rte_update() moved to protocol.h to avoid dependency conflicts */
 void rte_discard(rtable *tab, rte *old);
 int rt_examine(rtable *t, net_addr *a, struct proto *p, struct filter *filter);
@@ -376,6 +376,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
@@ -551,6 +552,7 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX];
 #define DEF_PREF_OSPF          150     /* OSPF intra-area, inter-area and type 1 external routes */
 #define DEF_PREF_RIP           120     /* RIP */
 #define DEF_PREF_BGP           100     /* BGP */
+#define DEF_PREF_RPKI          80      /* RPKI */
 #define DEF_PREF_INHERITED     10      /* Routes inherited from other routing daemons */
 
 /*
index 9614d9ef28bfa29c2b469e35a34acf651462be3e..264e63c361c5ea36cb52940264e7c538d71565c3 100644 (file)
@@ -1267,7 +1267,7 @@ rte_unhide_dummy_routes(net *net, rte **dummy)
  */
 
 void
-rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
+rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
 {
   struct proto *p = c->proto;
   struct proto_stats *stats = &c->stats;
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..6de8eac
--- /dev/null
@@ -0,0 +1,5 @@
+S rpki.c
+S packets.c
+S transport.c
+S tcp_transport.c
+S ssh_transport.c
\ No newline at end of file
diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile
new file mode 100644 (file)
index 0000000..bd76145
--- /dev/null
@@ -0,0 +1,4 @@
+src := rpki.c packets.c tcp_transport.c ssh_transport.c transport.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y
new file mode 100644 (file)
index 0000000..0811649
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ *     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 void
+rpki_check_unused_hostname(void)
+{
+  if (RPKI_CFG->hostname != NULL)
+    cf_error("Only one remote cache server per protocol allowed");
+}
+
+CF_DECLS
+
+CF_KEYWORDS(RPKI, REMOTE, BIRD, PRIVATE, PUBLIC, KEY, SSH, ENCRYPTION, USER,
+           RETRY, REFRESH, EXPIRE)
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, rpki_proto)
+
+rpki_proto_start: proto_start RPKI {
+  this_proto = proto_config_new(&proto_rpki, $1);
+  RPKI_CFG->retry_interval = RPKI_DEFAULT_RETRY_INTERVAL;
+  RPKI_CFG->refresh_interval = RPKI_DEFAULT_REFRESH_INTERVAL;
+  RPKI_CFG->expire_interval = RPKI_DEFAULT_EXPIRE_INTERVAL;
+};
+
+rpki_proto: rpki_proto_start proto_name '{' rpki_proto_opts '}' { rpki_check_config(RPKI_CFG); };
+
+rpki_proto_opts:
+   /* empty */
+ | rpki_proto_opts rpki_proto_item ';'
+ ;
+
+rpki_proto_item:
+   proto_item
+ | proto_channel
+ | REMOTE rpki_cache_addr
+ | REMOTE rpki_cache_addr rpki_proto_item_port
+ | rpki_proto_item_port
+ | SSH ENCRYPTION rpki_transport_ssh
+ | REFRESH expr {
+     if (rpki_check_refresh_interval($2))
+       cf_error(rpki_check_refresh_interval($2));
+     RPKI_CFG->refresh_interval = $2;
+   }
+ | RETRY expr {
+     if (rpki_check_retry_interval($2))
+       cf_error(rpki_check_retry_interval($2));
+     RPKI_CFG->retry_interval = $2;
+   }
+ | EXPIRE expr {
+     if (rpki_check_expire_interval($2))
+       cf_error(rpki_check_expire_interval($2));
+     RPKI_CFG->expire_interval = $2;
+   }
+ ;
+
+rpki_transport_ssh:
+   OFF { RPKI_CFG->ssh = NULL; }
+ | rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_check
+ ;
+
+rpki_proto_item_port: PORT expr { check_u16($2); RPKI_CFG->port = $2; };
+
+rpki_cache_addr:
+   text {
+     rpki_check_unused_hostname();
+     RPKI_CFG->hostname = $1;
+   }
+ | ipa {
+     rpki_check_unused_hostname();
+     RPKI_CFG->ip = $1;
+     char *hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN+1));
+     bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip);
+     RPKI_CFG->hostname = hostname;
+   }
+ ;
+
+rpki_transport_ssh_init:
+{
+  /* allow extending ssh configuration */
+  if (RPKI_CFG->ssh == NULL)
+    RPKI_CFG->ssh = cfg_allocz(sizeof(struct rpki_config_ssh));
+};
+
+rpki_transport_ssh_opts:
+   /* empty */
+ | rpki_transport_ssh_opts rpki_transport_ssh_item ';'
+ ;
+
+rpki_transport_ssh_item:
+   BIRD PRIVATE KEY text { RPKI_CFG->ssh->bird_private_key = $4; }
+ | REMOTE PUBLIC KEY text { RPKI_CFG->ssh->cache_public_key = $4; }
+ | USER text { RPKI_CFG->ssh->user = $2; }
+ ;
+
+rpki_transport_ssh_check:
+{
+  if (RPKI_CFG->ssh->user == NULL)
+    cf_error("User must be set");
+};
+
+CF_CODE
+
+CF_END
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c
new file mode 100644 (file)
index 0000000..839cddd
--- /dev/null
@@ -0,0 +1,1043 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "transport.h"
+#include "packets.h"
+
+#define RPKI_ADD_FLAG          1
+#define RPKI_DELETE_FLAG       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"
+};
+
+/*
+   0          8          16         24        31
+   .-------------------------------------------.
+   | Protocol |   PDU    |                     |
+   | Version  |   Type   |    reserved = zero  |
+   |  0 or 1  |  0 - 10  |                     |
+   +-------------------------------------------+
+   |                                           |
+   |                 Length >= 8               |
+   |                                           |
+   `-------------------------------------------'
+ */
+
+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[];
+};
+
+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 rpki_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 get_pdu_type(const void *pdu)
+{
+  return *((char *) pdu + 1);
+}
+
+static void
+rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+  struct rpki_proto *p = cache->p;
+
+  CACHE_TRACE(D_ROUTES, cache, "Importing route %N", pfxr);
+
+  net *n = net_get(channel->table, &pfxr->n);
+
+  rta a0 = {
+      .src = p->p.main_source,
+      .source = RTS_RPKI,
+      .scope = SCOPE_UNIVERSE,
+      .cast = RTC_UNICAST,
+      .dest = RTD_BLACKHOLE,
+  };
+
+  rta *a = rta_lookup(&a0);
+  rte *e = rte_get_temp(a);
+
+  e->net = n;
+  e->pflags = 0;
+
+  rte_update2(channel, &pfxr->n, e, a0.src);
+}
+
+static void
+rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+  struct rpki_proto *p = cache->p;
+
+  CACHE_TRACE(D_ROUTES, cache, "Removing route %N", pfxr);
+
+  rte_update(&p->p, &pfxr->n, NULL);
+}
+
+void
+rpki_table_remove_all(struct rpki_cache *cache)
+{
+  CACHE_TRACE(D_ROUTES, cache, "Removing all routes");
+
+  if (cache->roa4_channel && cache->roa4_channel->channel_state != CS_DOWN)
+    channel_close(cache->roa4_channel);
+
+  if (cache->roa6_channel && cache->roa6_channel->channel_state != CS_DOWN)
+    channel_close(cache->roa6_channel);
+}
+
+static void
+rpki_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 = 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;
+  }
+
+  case RESET_QUERY:
+    break;
+
+  default:
+    bug("PDU type %s should not be sent by router!", str_pdu_type[type]);
+  }
+}
+
+static void
+rpki_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_pdu_body_to_host_byte_order(void *pdu)
+{
+  const enum pdu_type type = 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 == RPKI_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 ROUTER_KEY:
+  case SERIAL_QUERY:
+  case RESET_QUERY:
+  case CACHE_RESPONSE:
+  case CACHE_RESET:
+    break;
+  }
+}
+
+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))
+    return;
+
+  const char *str_type = str_pdu_type[get_pdu_type(pdu)];
+  const struct pdu_header *header = pdu;
+
+  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, "Received a %s packet %s", str_type, detail);
+  }
+  else
+  {
+    CACHE_TRACE(D_PACKETS, cache, "Sending a %s packet %s", str_type, detail);
+  }
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+  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");
+#endif
+}
+
+static int
+rpki_send_pdu(struct rpki_cache *cache, const void *pdu, const unsigned len)
+{
+  struct rpki_proto *p = cache->p;
+  sock *sk = cache->tr_sock->sk;
+
+  if (!sk)
+  {
+    RPKI_WARN(p, "Want send a %s packet, but the bird socket is NULL!", str_pdu_type[get_pdu_type(pdu)]);
+    ASSERT(0);
+    return RPKI_ERROR;
+  }
+
+  if (sk->fd < 0)
+  {
+    RPKI_WARN(p, "Want send a %s packet, but the bird socket FD is %d!", str_pdu_type[get_pdu_type(pdu)], sk->fd);
+    ASSERT(0);
+    return RPKI_ERROR;
+  }
+
+  if (cache->state == RPKI_CS_SHUTDOWN)
+  {
+    RPKI_WARN(p, "Want send a %s packet, but the protocol state is SHUTDOWN!", str_pdu_type[get_pdu_type(pdu)]);
+    ASSERT(0);
+    return RPKI_ERROR;
+  }
+
+  rpki_log_packet(cache, pdu, len, RPKI_SEND);
+
+  byte pdu_converted[len];
+  memcpy(pdu_converted, pdu, len);
+  rpki_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 sent using a call of tx_hook()");
+  }
+
+  return RPKI_SUCCESS;
+}
+
+/**
+ * rpki_check_receive_packet - Make a basic validation of received RPKI PDU header
+ * @cache: cache connection
+ * @pdu: RPKI PDU
+ * @len: length of @pdu
+ *
+ * It checks protocol version, PDU type and PDU size. If all good then
+ * function returns %RPKI_SUCCESS otherwise sends Error PDU and returns %RPKI_ERROR
+ */
+static int
+rpki_check_receive_packet(struct rpki_cache *cache, const void *pdu, const size_t len)
+{
+  struct rpki_proto *p = cache->p;
+  int error = RPKI_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));
+  rpki_pdu_header_to_host_byte_order(&header);
+
+  if (cache->state == RPKI_CS_SHUTDOWN)
+  {
+    RPKI_WARN(p, "Received %s packet, but cache->state == RPKI_CACHE_SHUTDOWN", str_pdu_type[header.type]);
+    ASSERT(cache->state != RPKI_CS_SHUTDOWN);
+    return RPKI_ERROR;
+  }
+
+  // Do not handle error PDUs here, leave this task to rtr_handle_error_pdu()
+  if (header.ver != cache->version && header.type != ERROR)
+  {
+    // If this is the first PDU we have received -> Downgrade.
+    if (cache->request_session_id && cache->last_update == 0
+       && header.ver >= RPKI_MIN_VERSION
+       && header.ver <= RPKI_MAX_VERSION
+       && header.ver < cache->version)
+    {
+      CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", rpki_get_cache_ident(cache), cache->version, header.ver);
+      cache->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 == RPKI_VERSION_0 && header.type == ROUTER_KEY))
+  {
+    error = UNSUPPORTED_PDU_TYPE;
+    goto error;
+  }
+
+  if (header.len < sizeof(header))
+  {
+    error = CORRUPT_DATA;
+    goto error;
+  }
+  else if (header.len > RPKI_PDU_MAX_LEN)
+  {
+    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 RPKI_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);
+    rpki_send_error_pdu(cache, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt));
+    break;
+  }
+
+  case PDU_TOO_BIG:
+  {
+    char txt2[64];
+    bsnprintf(txt2, sizeof(txt2),"PDU too big, max. PDU size is: %u bytes", RPKI_PDU_MAX_LEN);
+    CACHE_TRACE(D_EVENTS, cache, "%s", txt2);
+    rpki_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);
+    rpki_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");
+    rpki_send_error_pdu(cache, pdu, header.len, UNSUPPORTED_PROTOCOL_VER, NULL, 0);
+    break;
+
+  default:
+    bug("Uncaught error");
+  }
+
+  return RPKI_ERROR;
+}
+
+static int
+rpki_handle_error_pdu(struct rpki_cache *cache, const void *buf)
+{
+  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
+    {
+      char txt[len_err_txt + 1];
+      char *pdu_txt = (char *) pdu->rest + pdu->len_enc_pdu + 4;
+      bsnprintf(txt, sizeof(txt), "%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");
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    break;
+
+  case INTERNAL_ERROR:
+    CACHE_TRACE(D_PACKETS, cache, "Internal error on server-side");
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    break;
+
+  case NO_DATA_AVAIL:
+    CACHE_TRACE(D_PACKETS, cache, "No data available");
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_DATA_AVAIL);
+    break;
+
+  case INVALID_REQUEST:
+    CACHE_TRACE(D_PACKETS, cache, "Invalid request from client");
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    break;
+
+  case UNSUPPORTED_PROTOCOL_VER:
+    CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version");
+    if (pdu->ver <= RPKI_MAX_VERSION &&
+       pdu->ver >= RPKI_MIN_VERSION &&
+       pdu->ver < cache->version)
+    {
+      CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %i to version %i", cache->version, pdu->ver);
+      cache->version = pdu->ver;
+      rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+    }
+    else
+    {
+      CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \
+                 "current version: %i, PDU version: %i", cache->version, pdu->ver);
+      rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    }
+    break;
+
+  case UNSUPPORTED_PDU_TYPE:
+    CACHE_TRACE(D_PACKETS, cache, "Client set unsupported PDU type");
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    break;
+
+  default:
+    CACHE_TRACE(D_PACKETS, cache, "error unknown, server sent unsupported error code %u", pdu->error_code);
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    break;
+  }
+
+  return RPKI_SUCCESS;
+}
+
+static int
+rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_response *pdu)
+{
+  if (cache->request_session_id)
+  {
+    if (cache->last_update != 0)
+    {
+      /*
+       * This point is before import new records from remote cache.
+       * If this isn't the first sync, but we already received records,
+       * delete old records and be ready for receive new records.
+       */
+      if (cache->roa4_channel)
+       rt_refresh_begin(cache->roa4_channel->table, cache->roa4_channel);
+      if (cache->roa6_channel)
+       rt_refresh_begin(cache->roa6_channel->table, cache->roa6_channel);
+
+      cache->refresh_channels = 1;
+      cache->last_update = 0;
+    }
+    cache->session_id = pdu->session_id;
+    cache->request_session_id = 0;
+  }
+  else
+  {
+    if (cache->session_id != pdu->session_id)
+    {
+      char txt[100];
+      bsnprintf(txt, sizeof(txt), "Wrong session_id %u in Cache Response PDU", pdu->session_id);
+      rpki_send_error_pdu(cache, NULL, 0, CORRUPT_DATA, txt, strlen(txt)+1);
+      rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+      return RPKI_ERROR;
+    }
+  }
+  return RPKI_SUCCESS;
+}
+
+static net_addr_union
+rpki_prefix_pdu_2_net_addr(const void *pdu)
+{
+  net_addr_union n = {};
+  const enum pdu_type type = 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
+rpki_handle_prefix_pdu(struct rpki_cache *cache, const void *pdu)
+{
+  struct channel *channel = NULL;
+
+  const enum pdu_type type = get_pdu_type(pdu);
+  ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+  if (type == IPV4_PREFIX)
+    channel = cache->roa4_channel;
+  if (type == IPV6_PREFIX)
+    channel = cache->roa6_channel;
+
+  net_addr_union addr = rpki_prefix_pdu_2_net_addr(pdu);
+
+  if (!channel)
+  {
+    CACHE_TRACE(D_ROUTES, cache, "Skipping route %N, missing %s channel", &addr, (type == IPV4_PREFIX ? "roa4" : "roa6"), addr);
+    return RPKI_ERROR;
+  }
+
+  switch (((struct pdu_ipv4 *) pdu)->flags)
+  {
+  case RPKI_ADD_FLAG:
+    rpki_table_add_roa(cache, channel, &addr);
+    break;
+
+  case RPKI_DELETE_FLAG:
+    rpki_table_remove_roa(cache, channel, &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);
+    rpki_send_error_pdu(cache, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt));
+    return RPKI_ERROR;
+  }
+  }
+
+  return RPKI_SUCCESS;
+}
+
+static uint
+rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval)
+{
+  if (check_fn(interval))
+  {
+    RPKI_WARN(cache->p, "%s, received %u seconds", check_fn(interval), interval);
+    return 0;
+  }
+  return 1;
+}
+
+static void
+rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const void *pdu)
+{
+  const struct pdu_end_of_data_v1 *eod_pdu = pdu;
+
+  if (eod_pdu->ver == RPKI_VERSION_1)
+  {
+    if (rpki_check_interval(cache, rpki_check_expire_interval, eod_pdu->expire_interval))
+      cache->expire_interval = eod_pdu->expire_interval;
+
+    if (rpki_check_interval(cache, rpki_check_refresh_interval, eod_pdu->refresh_interval))
+      cache->refresh_interval = eod_pdu->refresh_interval;
+
+    if (rpki_check_interval(cache, rpki_check_retry_interval, eod_pdu->retry_interval))
+      cache->retry_interval = eod_pdu->retry_interval;
+
+    CACHE_TRACE(D_EVENTS, cache, "New interval values: "
+              "expire_interval: %us, "
+              "refresh_interval: %us, "
+              "retry_interval: %us",
+              cache->expire_interval, cache->refresh_interval, cache->retry_interval);
+  }
+
+  if (eod_pdu->session_id != cache->session_id)
+  {
+    char txt[67];
+    bsnprintf(txt, sizeof(txt), "Received Session ID %u, but expected %u", eod_pdu->session_id, cache->session_id);
+    CACHE_TRACE(D_EVENTS, cache, "%s", txt);
+    rpki_send_error_pdu(cache, pdu, eod_pdu->len, CORRUPT_DATA, txt, strlen(txt) + 1);
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+  }
+
+  if (cache->refresh_channels)
+  {
+    cache->refresh_channels = 0;
+    if (cache->roa4_channel)
+      rt_refresh_begin(cache->roa4_channel->table, cache->roa4_channel);
+    if (cache->roa6_channel)
+      rt_refresh_begin(cache->roa6_channel->table, cache->roa6_channel);
+  }
+
+  cache->last_update = now;
+  cache->serial_number = eod_pdu->sn;
+  rpki_cache_change_state(cache, RPKI_CS_ESTABLISHED);
+  rpki_schedule_next_refresh(cache);
+  rpki_schedule_next_expire_check(cache);
+}
+
+static void
+rpki_rx_packet(struct rpki_cache *cache, void *pdu, uint len)
+{
+  struct rpki_proto *p = cache->p;
+  enum pdu_type type = get_pdu_type(pdu);
+
+  if (rpki_check_receive_packet(cache, pdu, len) == RPKI_ERROR)
+  {
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    return;
+  }
+
+  rpki_pdu_header_to_host_byte_order(pdu);
+  rpki_pdu_body_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 */
+    rpki_cache_change_state(cache, RPKI_CS_SYNC);
+    break;
+
+  case CACHE_RESPONSE:
+    rpki_handle_cache_response_pdu(cache, pdu);
+    break;
+
+  case IPV4_PREFIX:
+  case IPV6_PREFIX:
+    rpki_handle_prefix_pdu(cache, pdu);
+    break;
+
+  case END_OF_DATA:
+    rpki_handle_end_of_data_pdu(cache, 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. */
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL);
+    break;
+
+  case ERROR:
+    rpki_handle_error_pdu(cache, pdu);
+    break;
+
+  case ROUTER_KEY:
+  default:
+    CACHE_TRACE(D_PACKETS, cache, "Received unsupported type of RPKI PDU: %u", type);
+  };
+}
+
+static int
+rpki_open_channel(struct channel *cs)
+{
+  if (cs)
+    if (cs->channel_state != CS_FLUSHING)
+      channel_open(cs);
+    else
+      return 0;
+
+  return 1;
+}
+
+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 \n", size);
+
+  if (!rpki_open_channel(cache->roa4_channel) || !rpki_open_channel(cache->roa6_channel))
+  {
+    DBG("Channels are busy, must wait \n");
+    return 0; /* Channels are busy, must wait, don't purge sk->rbuf */
+  }
+
+  while (end >= pkt_start + RPKI_PDU_HEADER_LEN)
+  {
+    struct pdu_header header;
+    memcpy(&header, pkt_start, sizeof(header));
+    rpki_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 receiving 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->tr_sock->sk)
+      return 0;
+
+    pkt_start += header.len;
+  }
+
+  if (pkt_start != sk->rbuf)
+  {
+    CACHE_DBG(cache, "Move %u bytes of a memory at the start of buffer", end - pkt_start);
+    memmove(sk->rbuf, pkt_start, end - pkt_start);
+    sk->rpos = sk->rbuf + (end - pkt_start);
+  }
+
+  return 0; /* Not purge sk->rbuf */
+}
+
+void
+rpki_err_hook(struct birdsock *sk, int error_num)
+{
+  struct rpki_cache *cache = sk->data;
+
+  if (error_num && sk->err == NULL)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Lost connection: %M", error_num);
+  }
+  else
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Lost connection: %s", sk->err);
+  }
+
+  rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+}
+
+static int
+rpki_fire_tx(struct rpki_cache *cache)
+{
+  sock *sk = cache->tr_sock->sk;
+
+  uint bytes_to_send = sk->tpos - sk->tbuf;
+  DBG("Sending %u bytes", bytes_to_send);
+  return sk_send(sk, bytes_to_send);
+}
+
+void
+rpki_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");
+
+  sk->rx_hook = rpki_rx_hook;
+  sk->tx_hook = rpki_tx_hook;
+
+  rpki_cache_change_state(cache, RPKI_CS_SYNC);
+}
+
+int
+rpki_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)
+{
+  /* Don't send errors for erroneous error PDUs */
+  if (pdu_len >= 2)
+  {
+    if (get_pdu_type(erroneous_pdu) == ERROR)
+      return RPKI_SUCCESS;
+  }
+
+  uint msg_size = 16 + pdu_len + text_len;
+  char msg[msg_size];
+  struct pdu_header *header = (struct pdu_header *) msg;
+  header->ver = cache->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 rpki_send_pdu(cache, msg, msg_size);
+}
+
+int
+rpki_send_serial_query(struct rpki_cache *cache)
+{
+  struct pdu_serial_query pdu = {
+      .ver = cache->version,
+      .type = SERIAL_QUERY,
+      .session_id = cache->session_id,
+      .len = sizeof(pdu),
+      .sn = cache->serial_number
+  };
+
+  if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) {
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+    return RPKI_ERROR;
+  }
+  return RPKI_SUCCESS;
+}
+
+int
+rpki_send_reset_query(struct rpki_cache *cache)
+{
+  struct pdu_reset_query pdu = {
+      .ver = cache->version,
+      .type = RESET_QUERY,
+      .len = 8,
+  };
+
+  if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) {
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+    return RPKI_ERROR;
+  }
+  return RPKI_SUCCESS;
+}
diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h
new file mode 100644 (file)
index 0000000..1876e49
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RPKI_PACKETS_H_
+#define _BIRD_RPKI_PACKETS_H_
+
+#include <arpa/inet.h>
+
+#define RPKI_RX_BUFFER_SIZE    65536
+#define RPKI_TX_BUFFER_SIZE    65536
+#define RPKI_PDU_HEADER_LEN    8
+
+/* Error PDU size is the biggest (has encapsulate PDU inside):
+ *     Header size 8 Bytes +
+ *     Length of Encapsulated PDU 4 Bytes +
+ *     Encapsulated PDU IPv6 32 Bytes +
+ *     Length of Text 4 Bytes +
+ *     UTF-8 Text 400*2 Bytes
+ *     = 848 Bytes
+ */
+#define RPKI_PDU_MAX_LEN       848
+
+int rpki_send_serial_query(struct rpki_cache *cache);
+int rpki_send_reset_query(struct rpki_cache *cache);
+int rpki_rx_hook(sock *sk, int size);
+void rpki_connected_hook(sock *sk);
+void rpki_err_hook(sock *sk, int size);
+void rpki_table_remove_all(struct rpki_cache *cache);
+
+#endif
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
new file mode 100644 (file)
index 0000000..91533ff
--- /dev/null
@@ -0,0 +1,737 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     Using RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: RPKI to Router Protocol
+ *
+ * The Resource Public Key Infrastructure (RPKI) to Router Protocol (RFC 6810)
+ * is protocol for communication between router (BIRD) and RPKI cache server
+ * (RPKI validator). Validator sends   implementation
+ * is based on the RTRlib (http://rpki.realmv6.org/). The BIRD takes over
+ * |packets.c|, |rtr.c| (inside |rpki.c|), |transport.c|, |tcp_transport.c| and |ssh_transport.c| files
+ * from RTRlib.
+ *
+ * A SSH transport requires LibSSH library. LibSSH is loading dynamically using dlopen
+ * function.
+ */
+
+/*
+ * TODO list
+ *  - Receive Router Key PDU with End-Entity certificate
+ *     https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-5.10
+ *     It's implemented in RTRlib.
+ *  - Saving EE Certificate
+ */
+
+#include <stdlib.h>
+#include <netdb.h>
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "lib/string.h"
+#include "nest/cli.h"
+
+static const char *str_cache_states[] = {
+    [RPKI_CS_CONNECTING] = "CONNECTING",
+    [RPKI_CS_ESTABLISHED] = "ESTABLISHED",
+    [RPKI_CS_RESET] = "RESET",
+    [RPKI_CS_SYNC] = "SYNC",
+    [RPKI_CS_FAST_RECONNECT] = "FAST_RECONNECT",
+    [RPKI_CS_ERROR_NO_DATA_AVAIL] = "ERROR_NO_DATA_AVAIL",
+    [RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL] = "ERROR_NO_INCR_UPDATE_AVAIL",
+    [RPKI_CS_ERROR_FATAL] = "ERROR_FATAL",
+    [RPKI_CS_ERROR_TRANSPORT] = "ERROR_TRANSPORT",
+    [RPKI_CS_SHUTDOWN] = "SHUTDOWN"
+};
+
+const char *
+rpki_cache_state_to_str(enum rpki_cache_state state)
+{
+  return str_cache_states[state];
+}
+
+/* Return 0 if non-valid transition,
+ * return 1 if valid transition */
+static int
+rpki_is_allowed_transition_cache_state(const enum rpki_cache_state old, const enum rpki_cache_state new)
+{
+  switch (new)
+  {
+  case RPKI_CS_CONNECTING:                     return old == RPKI_CS_SHUTDOWN || old == RPKI_CS_ERROR_TRANSPORT || old == RPKI_CS_FAST_RECONNECT;
+  case RPKI_CS_ESTABLISHED:                    return old == RPKI_CS_SYNC;
+  case RPKI_CS_RESET:                          return old == RPKI_CS_ERROR_NO_DATA_AVAIL || old == RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL;
+  case RPKI_CS_SYNC:                           return old == RPKI_CS_RESET || old == RPKI_CS_CONNECTING || old == RPKI_CS_ESTABLISHED;
+  case RPKI_CS_FAST_RECONNECT:                 return old == RPKI_CS_ESTABLISHED || old == RPKI_CS_SYNC;
+  case RPKI_CS_ERROR_NO_DATA_AVAIL:            return old == RPKI_CS_SYNC;
+  case RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL:     return old == RPKI_CS_SYNC;
+  case RPKI_CS_ERROR_FATAL:                    return 1;
+  case RPKI_CS_ERROR_TRANSPORT:                        return 1;
+  case RPKI_CS_SHUTDOWN:                       return 1;
+  }
+  return 0;
+}
+
+static struct proto *
+rpki_init(struct proto_config *CF)
+{
+  struct proto *P = proto_new(CF);
+
+  return P;
+}
+
+const char *
+rpki_get_cache_ident(struct rpki_cache *cache)
+{
+  return rpki_tr_ident(cache->tr_sock);
+}
+
+/*
+ * Timers
+ */
+
+void
+rpki_schedule_next_refresh(struct rpki_cache *cache)
+{
+  if (cache->state == RPKI_CS_SHUTDOWN)
+  {
+    CACHE_DBG(cache, "Stop refreshing");
+    return;
+  }
+
+  unsigned time_to_wait = cache->refresh_interval;
+
+  CACHE_DBG(cache, "Scheduling next refresh after %u seconds", time_to_wait);
+  tm_start(cache->refresh_timer, time_to_wait);
+}
+
+void
+rpki_schedule_next_retry(struct rpki_cache *cache)
+{
+  uint time_to_wait = cache->retry_interval;
+
+  switch (cache->state)
+  {
+  case RPKI_CS_ESTABLISHED:
+  case RPKI_CS_SYNC:
+  case RPKI_CS_RESET:
+    CACHE_DBG(cache, "Stop retrying connection");
+    break;
+
+  default:
+    CACHE_DBG(cache, "Scheduling next retry after %u seconds", time_to_wait);
+    tm_start(cache->retry_timer, time_to_wait);
+  }
+}
+
+void
+rpki_schedule_next_expire_check(struct rpki_cache *cache)
+{
+  /* minimum time to wait is 1 second */
+  unsigned time_to_wait = MAX(((int)cache->expire_interval - (int)(now - cache->last_update)), 1);
+
+  CACHE_DBG(cache, "Scheduling next expiration check after %u seconds", time_to_wait);
+  tm_start(cache->expire_timer, time_to_wait);
+}
+
+static void
+rpki_refresh_hook(struct timer *tm)
+{
+  struct rpki_cache *cache = tm->data;
+
+  CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+  switch (cache->state)
+  {
+  case RPKI_CS_ESTABLISHED:
+    rpki_cache_change_state(cache, RPKI_CS_SYNC);
+    break;
+
+  case RPKI_CS_SYNC:
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+    break;
+
+  default:
+    break;
+  }
+
+  rpki_schedule_next_refresh(cache);
+}
+
+static void
+rpki_retry_hook(struct timer *tm)
+{
+  struct rpki_cache *cache = tm->data;
+
+  CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+  switch (cache->state)
+  {
+  case RPKI_CS_ESTABLISHED:
+  case RPKI_CS_CONNECTING:
+  case RPKI_CS_SYNC:
+  case RPKI_CS_SHUTDOWN:
+    break;
+
+  default:
+    rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+    break;
+  }
+
+  rpki_schedule_next_retry(cache);
+}
+
+static void
+rpki_purge_records_if_outdated(struct rpki_cache *cache)
+{
+  if (cache->last_update == 0)
+    return;
+
+  if ((cache->last_update + cache->expire_interval) < now)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "All routes expired");
+    rpki_table_remove_all(cache);
+    cache->request_session_id = 1;
+    cache->serial_number = 0;
+    cache->last_update = 0;
+  }
+  else
+  {
+    CACHE_DBG(cache, "No outdated records, remains %d seconds to become obsolete", (int)cache->expire_interval - (int)(now - cache->last_update));
+  }
+}
+
+static void
+rpki_expire_hook(struct timer *tm)
+{
+  struct rpki_cache *cache = tm->data;
+
+  if (cache->last_update == 0)
+    return;
+
+  CACHE_DBG(cache, ""); /* Show name of function */
+
+  rpki_purge_records_if_outdated(cache);
+  rpki_schedule_next_expire_check(cache);
+}
+
+static int
+rpki_open_connection(struct rpki_cache *cache)
+{
+  CACHE_TRACE(D_EVENTS, cache, "Opening a connection");
+
+  if (rpki_tr_open(cache->tr_sock) == TR_ERROR)
+  {
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+    return TR_ERROR;
+  }
+
+  return TR_SUCCESS;
+}
+
+static void
+rpki_close_connection(struct rpki_cache *cache)
+{
+  CACHE_TRACE(D_EVENTS, cache, "Closing a connection");
+  rpki_tr_close(cache->tr_sock);
+}
+
+/**
+ * rpki_cache_change_state - check and change cache state
+ * @cache: RPKI cache instance
+ * @new_state: suggested new state
+ *
+ * Validates and makes transition. Does appropriate actions after change
+ */
+void
+rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state)
+{
+  const enum rpki_cache_state old_state = cache->state;
+
+  if (old_state == new_state)
+    return;
+
+  if (!rpki_is_allowed_transition_cache_state(old_state, new_state))
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s is not allowed", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state));
+    ASSERT(0);
+    return;
+  }
+
+  CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state));
+  cache->state = new_state;
+
+  switch (new_state)
+  {
+  case RPKI_CS_CONNECTING:
+  {
+    sock *sk = cache->tr_sock->sk;
+
+    if (sk == NULL || sk->fd < 0)
+      rpki_open_connection(cache);
+    else
+      rpki_cache_change_state(cache, RPKI_CS_SYNC);
+
+    break;
+  }
+
+  case RPKI_CS_ESTABLISHED:
+    break;
+
+  case RPKI_CS_RESET:
+    /* Resetting RTR connection. */
+    cache->request_session_id = 1;
+    cache->serial_number = 0;
+    rpki_cache_change_state(cache, RPKI_CS_SYNC);
+    break;
+
+  case RPKI_CS_SYNC:
+    /* Requesting for receive validation records from the RTR server. */
+    if (cache->request_session_id)
+    {
+      /* Change to state RESET, if socket dont has a session_id */
+      if (rpki_send_reset_query(cache) != RPKI_SUCCESS)
+       rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    }
+    else
+    {
+      /* if we already have a session_id, send a serial query and start to sync */
+      if (rpki_send_serial_query(cache) != RPKI_SUCCESS)
+       rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    }
+    break;
+
+  case RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL:
+    /* Server was unable to answer the last serial or reset query. */
+    rpki_purge_records_if_outdated(cache);
+    rpki_cache_change_state(cache, RPKI_CS_RESET);
+    break;
+
+  case RPKI_CS_ERROR_NO_DATA_AVAIL:
+    /* No validation records are available on the RTR server. */
+    rpki_cache_change_state(cache, RPKI_CS_RESET);
+    break;
+
+  case RPKI_CS_ERROR_FATAL:
+    /* Fatal protocol error occurred. */
+    cache->request_session_id = 1;
+    cache->serial_number = 0;
+    cache->last_update = 0;
+    rpki_table_remove_all(cache);
+    /* Fall through */
+
+  case RPKI_CS_ERROR_TRANSPORT:
+    /* Error on the transport socket occurred. */
+    rpki_close_connection(cache);
+    rpki_schedule_next_retry(cache);
+    break;
+
+  case RPKI_CS_FAST_RECONNECT:
+    /* Reconnect without any waiting period */
+    rpki_close_connection(cache);
+    rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+    break;
+
+  case RPKI_CS_SHUTDOWN:
+    /* RTR Socket is stopped. */
+    rpki_close_connection(cache);
+    cache->request_session_id = 1;
+    cache->serial_number = 0;
+    cache->last_update = 0;
+    rpki_table_remove_all(cache);
+    break;
+  };
+}
+
+/**
+ * rpki_check_refresh_interval - check validity of refresh interval value
+ * @seconds: suggested value
+ *
+ * Validate value and return NULL if check passed or error message if check failed.
+ */
+const char *
+rpki_check_refresh_interval(uint seconds)
+{
+  if (seconds < 1)
+    return "Minimum allowed refresh interval is 1 second";
+  if (seconds > 86400)
+    return "Maximum allowed refresh interval is 86400 seconds";
+  return NULL;
+}
+
+/**
+ * rpki_check_retry_interval - check validity of retry interval value
+ * @seconds: suggested value
+ *
+ * Validate value and return NULL if check passed or error message if check failed.
+ */
+const char *
+rpki_check_retry_interval(uint seconds)
+{
+  if (seconds < 1)
+    return "Minimum allowed retry interval is 1 second";
+  if (seconds > 7200)
+    return "Maximum allowed retry interval is 7200 seconds";
+  return NULL;
+}
+
+/**
+ * rpki_check_expire_interval - check validity of expire interval value
+ * @seconds: suggested value
+ *
+ * Validate value and return NULL if check passed or error message if check failed.
+ */
+const char *
+rpki_check_expire_interval(uint seconds)
+{
+  if (seconds < 600)
+    return "Minimum allowed expire interval is 600 seconds";
+  if (seconds > 172800)
+    return "Maximum allowed expire interval is 172800 seconds";
+  return NULL;
+}
+
+static struct rpki_cache *
+rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf)
+{
+  pool *pool = rp_new(p->p.pool, cf->hostname);
+
+  struct rpki_cache *cache = mb_allocz(pool, sizeof(struct rpki_cache));
+
+  cache->pool = pool;
+  cache->p = p;
+
+  proto_configure_channel(&p->p, &cache->roa4_channel, proto_cf_find_channel(p->p.cf, NET_ROA4));
+  proto_configure_channel(&p->p, &cache->roa6_channel, proto_cf_find_channel(p->p.cf, NET_ROA6));
+
+  cache->state = RPKI_CS_SHUTDOWN;
+  cache->request_session_id = 1;
+  cache->serial_number = 0;
+  cache->last_update = 0;
+  cache->version = RPKI_MAX_VERSION;
+
+  cache->refresh_interval = cf->refresh_interval;
+  cache->retry_interval = cf->retry_interval;
+  cache->expire_interval = cf->expire_interval;
+  cache->retry_timer = tm_new_set(pool, &rpki_retry_hook, cache, 0, 0);
+  cache->refresh_timer = tm_new_set(pool, &rpki_refresh_hook, cache, 0, 0);
+  cache->expire_timer = tm_new_set(pool, &rpki_expire_hook, cache, 0, 0);
+
+  cache->tr_sock = mb_allocz(pool, sizeof(struct rpki_tr_sock));
+  cache->tr_sock->cache = cache;
+
+  if (cf->ssh)
+    rpki_tr_ssh_init(cache->tr_sock);
+  else
+    rpki_tr_tcp_init(cache->tr_sock);
+
+  CACHE_TRACE(D_EVENTS, cache, "Created");
+
+  return cache;
+}
+
+static void
+rpki_free_cache(struct rpki_cache *cache)
+{
+  struct rpki_proto *p = cache->p;
+
+  rpki_table_remove_all(cache);
+
+  CACHE_TRACE(D_EVENTS, p->cache, "Destroyed");
+  rfree(cache->pool);
+  p->cache = NULL;
+}
+
+static int
+rpki_shutdown(struct proto *P)
+{
+  struct rpki_proto *p = (void *) P;
+  p->cache = NULL;
+
+  /* protocol memory pool will be automatically freed */
+  return PS_DOWN;
+}
+
+static void
+rpki_start_cache(struct rpki_cache *cache)
+{
+  rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+}
+
+static void
+rpki_replace_cache(struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+  struct rpki_proto *p = cache->p;
+
+  rpki_free_cache(cache);
+
+  p->cache = rpki_init_cache(p, new);
+  rpki_start_cache(p->cache);
+}
+
+static void
+rpki_fast_reconnect_cache(struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+  if (cache->state == RPKI_CS_ESTABLISHED)
+    rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+  else
+    rpki_replace_cache(cache, old, new);
+}
+
+/*
+ * Return 0 if need to restart
+ * Return 1 if reconfiguration finished successful
+ */
+static int
+rpki_reconfigure_cache(struct rpki_proto *p, struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+  u8 try_fast_reconnect = 0;
+
+  if (!proto_configure_channel(&p->p, &cache->roa4_channel, proto_cf_find_channel(p->p.cf, NET_ROA4)) ||
+      !proto_configure_channel(&p->p, &cache->roa6_channel, proto_cf_find_channel(p->p.cf, NET_ROA6)))
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Channels changed");
+    return 0;
+  }
+
+  if (strcmp(old->hostname, new->hostname) != 0)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Remote cache server address changed to %s", new->hostname);
+    goto hard_cache_replace;
+  }
+
+  if (old->port != new->port)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "Remote cache server port changed to %u", new->port);
+    goto hard_cache_replace;
+  }
+
+  if (!!old->ssh != !!new->ssh)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "SSH encryption toggled");
+    goto hard_cache_replace;
+  }
+  else if (old->ssh && new->ssh)
+  {
+    if ((strcmp(old->ssh->bird_private_key, new->ssh->bird_private_key) != 0) ||
+       (strcmp(old->ssh->cache_public_key, new->ssh->cache_public_key) != 0) ||
+       (strcmp(old->ssh->user, new->ssh->user) != 0))
+    {
+      CACHE_TRACE(D_EVENTS, cache, "Settings of SSH transport encryption changed");
+      try_fast_reconnect = 1;
+    }
+  }
+
+  if (cache->expire_interval != new->expire_interval)
+  {
+    cache->expire_interval = new->expire_interval;
+    CACHE_TRACE(D_EVENTS, cache, "Expire interval changed to %u seconds", cache->expire_interval);
+    try_fast_reconnect = 1;
+  }
+
+  if (cache->refresh_interval != new->refresh_interval)
+  {
+    cache->refresh_interval = new->refresh_interval;
+    CACHE_TRACE(D_EVENTS, cache, "Refresh interval changed to %u seconds", cache->refresh_interval);
+    try_fast_reconnect = 1;
+  }
+
+  if (cache->retry_interval != new->retry_interval)
+  {
+    cache->retry_interval = new->retry_interval;
+    CACHE_TRACE(D_EVENTS, cache, "Retry interval changed to %u seconds", cache->retry_interval);
+    try_fast_reconnect = 1;
+  }
+
+  if (try_fast_reconnect)
+    rpki_fast_reconnect_cache(cache, new, old);
+
+  return 1;
+
+ hard_cache_replace:
+  rpki_replace_cache(cache, new, old);
+  return 1;
+}
+
+/*
+ * Return 0 if need to restart
+ * Return 1 if reconfiguration finished successful
+ */
+static int
+rpki_reconfigure_proto(struct rpki_proto *p, struct rpki_config *new_cf, struct rpki_config *old_cf)
+{
+  u8 new = new_cf && new_cf->hostname;
+  u8 old = old_cf && old_cf->hostname;
+  struct rpki_cache *cache = p->cache;
+
+  if (new && !old)
+  {
+    p->cache = rpki_init_cache(p, new_cf);
+    rpki_start_cache(p->cache);
+  }
+  else if (!new && old && cache)
+    rpki_free_cache(cache);
+  else if (new && old && cache)
+    return rpki_reconfigure_cache(p, cache, new_cf, old_cf);
+
+  return 1;
+}
+
+/*
+ * Return 0 if need to restart
+ * Return 1 if reconfiguration finished successful
+ */
+static int
+rpki_reconfigure(struct proto *P, struct proto_config *CF)
+{
+  struct rpki_proto *p = (void *) P;
+  struct rpki_config *new = (void *) CF;
+  struct rpki_config *old = (void *) p->p.cf;
+
+  P->cf = CF;
+  if (rpki_reconfigure_proto(p, new, old))
+    return 1;
+
+  P->cf = (void *) old;
+  return 0;
+}
+
+static void
+rpki_get_status(struct proto *P, byte *buf)
+{
+  struct rpki_proto *p = (struct rpki_proto *) P;
+
+  if (P->proto_state == PS_DOWN)
+  {
+    *buf = 0;
+    return;
+  }
+
+  if (p->cache)
+    bsprintf(buf, "%s", rpki_cache_state_to_str(p->cache->state));
+  else
+    bsprintf(buf, "No cache server configured");
+}
+
+static void
+rpki_show_proto_info_timer(const char *name, uint num, timer *t)
+{
+  if (t->expires)
+    cli_msg(-1006, "  %-17s %us (remains %us)", name, num, tm_remains(t));
+  else
+    cli_msg(-1006, "  %-17s %us", name, num);
+}
+
+static void
+rpki_show_proto_info(struct proto *P)
+{
+  struct rpki_proto *p = (struct rpki_proto *) P;
+  struct rpki_config *cf = (void *) p->p.cf;
+  struct rpki_cache *cache = p->cache;
+
+  if (cache)
+  {
+    cli_msg(-1006, "  Remote server:    %s", rpki_get_cache_ident(cache));
+    cli_msg(-1006, "  Status:           %s", rpki_cache_state_to_str(cache->state));
+    cli_msg(-1006, "  Transport:        %s", cf->ssh ? "SSHv2" : "Unprotected over TCP");
+    cli_msg(-1006, "  Protocol version: %u", cache->version);
+
+    if (cache->last_update)
+      cli_msg(-1006, "  Last update:      before %us", now - cache->last_update);
+    else
+      cli_msg(-1006, "  Last update:      ---");
+
+    rpki_show_proto_info_timer("Retry interval:", cache->retry_interval, cache->retry_timer);
+    rpki_show_proto_info_timer("Refresh interval:", cache->refresh_interval, cache->refresh_timer);
+    rpki_show_proto_info_timer("Expire interval:", cache->expire_interval, cache->expire_timer);
+
+    if (cache->roa4_channel)
+      channel_show_info(cache->roa4_channel);
+    else
+      cli_msg(-1006, "  No roa4 channel");
+
+    if (cache->roa6_channel)
+      channel_show_info(cache->roa6_channel);
+    else
+      cli_msg(-1006, "  No roa6 channel");
+  }
+}
+
+static int
+rpki_start(struct proto *P)
+{
+  struct rpki_proto *p = (void *) P;
+  struct rpki_config *cf = (void *) P->cf;
+
+  rpki_reconfigure_proto(p, cf, NULL);
+
+  return PS_UP;
+}
+
+static void
+rpki_postconfig(struct proto_config *CF)
+{
+  /* Define default channel */
+  if (EMPTY_LIST(CF->channels))
+    channel_config_new(NULL, CF->net_type, CF);
+}
+
+static void
+rpki_copy_config(struct proto_config *dest, struct proto_config *src)
+{
+  struct rpki_config *d = (void *) dest;
+  struct rpki_config *s = (void *) src;
+
+  /*
+   * Make a deep copy.
+   * The SSH configuration block can be reopened and extended.
+   */
+  if (s->ssh)
+  {
+    d->ssh = cfg_alloc(sizeof(struct rpki_config_ssh));
+    memcpy(d->ssh, s->ssh, sizeof(struct rpki_config_ssh));
+  }
+}
+
+void
+rpki_check_config(struct rpki_config *cf)
+{
+  /* Do not check templates at all */
+  if (cf->c.class == SYM_TEMPLATE)
+    return;
+
+  if (cf->hostname == NULL)
+    cf_error("Address or hostname of remote cache server must be set");
+
+  if (cf->port == 0)
+  {
+    if (cf->ssh != NULL)
+      cf->port = RPKI_SSH_PORT;
+    else
+      cf->port = RPKI_PORT;
+  }
+}
+
+struct protocol proto_rpki = {
+    .name =            "RPKI",
+    .template =        "rpki%d",
+    .preference =      DEF_PREF_RPKI,
+    .proto_size =      sizeof(struct rpki_proto),
+    .config_size =     sizeof(struct rpki_config),
+    .init =            rpki_init,
+    .start =           rpki_start,
+    .postconfig =      rpki_postconfig,
+    .channel_mask =    (NB_ROA4 | NB_ROA6),
+    .show_proto_info = rpki_show_proto_info,
+    .shutdown =        rpki_shutdown,
+    .copy_config =     rpki_copy_config,
+    .reconfigure =     rpki_reconfigure,
+    .get_status =      rpki_get_status,
+};
diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h
new file mode 100644 (file)
index 0000000..c0115a9
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ *     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 "nest/protocol.h"
+#include "lib/socket.h"
+#include "lib/ip.h"
+
+#include "ssh_transport.h"
+#include "tcp_transport.h"
+#include "packets.h"
+
+#define RPKI_PORT                      323
+#define RPKI_SSH_PORT                  22
+#define RPKI_DEFAULT_RETRY_INTERVAL    600
+#define RPKI_DEFAULT_REFRESH_INTERVAL  3600
+#define RPKI_DEFAULT_EXPIRE_INTERVAL   7200
+
+#define RPKI_VERSION_0                 0
+#define RPKI_VERSION_1                 1
+#define RPKI_MIN_VERSION               0
+#define RPKI_MAX_VERSION               1
+
+/*
+ * Used in parsing of configuration file
+ */
+
+struct rpki_config_ssh {
+  const char *bird_private_key;                /* Filepath to the BIRD server private key */
+  const char *cache_public_key;                /* Filepath to the public key of cache server, can be file known_hosts */
+  const char *user;                    /* Username for SSH connection */
+};
+
+/*
+ * Cache server
+ */
+
+enum rpki_cache_state {
+    RPKI_CS_CONNECTING,                /* Socket is establishing the transport connection. */
+    RPKI_CS_ESTABLISHED,               /* Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */
+    RPKI_CS_RESET,                     /* Resetting RTR connection. */
+    RPKI_CS_SYNC,                      /* Receiving validation records from the RTR server. */
+    RPKI_CS_FAST_RECONNECT,            /* Reconnect without any waiting period */
+    RPKI_CS_ERROR_NO_DATA_AVAIL,       /* No validation records are available on the RTR server. */
+    RPKI_CS_ERROR_NO_INCR_UPDATE_AVAIL, /* Server was unable to answer the last serial or reset query. */
+    RPKI_CS_ERROR_FATAL,               /* Fatal protocol error occurred. */
+    RPKI_CS_ERROR_TRANSPORT,           /* Error on the transport socket occurred. */
+    RPKI_CS_SHUTDOWN,                  /* RTR Socket is stopped. */
+};
+
+/* return values */
+enum rpki_rtvals {
+    RPKI_SUCCESS = 0,
+    RPKI_ERROR = -1
+};
+
+struct rpki_cache {
+  pool *pool;                          /* Pool containing cache objects */
+  struct rpki_proto *p;
+
+  struct channel *roa4_channel;
+  struct channel *roa6_channel;
+  u8 refresh_channels;                 /* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */
+
+  struct rpki_tr_sock *tr_sock;                /* Transport specific socket */
+  enum rpki_cache_state state;         /* RPKI_CS_* */
+  u32 session_id;
+  u8 request_session_id;               /* 1 => have to request new session id; 0 => we have already session id */
+  u32 serial_number;
+  uint version;                                /* Protocol version */
+  bird_clock_t last_update;            /* Last successful synchronization with cache server */
+
+  /* Intervals can be changed by remote cache server on the fly */
+  uint refresh_interval;
+  uint expire_interval;
+  uint retry_interval;
+  timer *retry_timer;
+  timer *refresh_timer;
+  timer *expire_timer;
+};
+
+/*
+ * Rest of RPKI
+ */
+
+struct rpki_config {
+  struct proto_config c;
+  const char *hostname;                        /* Full domain name of remote cache server */
+  ip_addr ip;                          /* IP address of cache server or IPA_NONE */
+  u16 port;                            /* Port of cache server */
+  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_config_ssh *ssh;         /* SSH configuration or NULL */
+};
+
+struct rpki_proto {
+  struct proto p;
+  struct rpki_cache *cache;
+};
+
+const char *rpki_get_cache_ident(struct rpki_cache *cache);
+
+void rpki_check_config(struct rpki_config *cf);
+const char *rpki_check_refresh_interval(uint seconds);
+const char *rpki_check_retry_interval(uint seconds);
+const char *rpki_check_expire_interval(uint seconds);
+
+void rpki_schedule_next_refresh(struct rpki_cache *cache);
+void rpki_schedule_next_retry(struct rpki_cache *cache);
+void rpki_schedule_next_expire_check(struct rpki_cache *cache);
+
+void rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state);
+
+/*
+ * Debug/log outputs
+ */
+
+#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, rpki_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, rpki_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/ssh_transport.c b/proto/rpki/ssh_transport.c
new file mode 100644 (file)
index 0000000..f813049
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *     BIRD -- An implementation of the SSH protocol for the RPKI transport
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *     This transport implementation uses libssh (http://www.libssh.org/)
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "rpki.h"
+#include "ssh_transport.h"
+#include "lib/libssh.h"
+
+static int
+rpki_tr_ssh_open(struct rpki_tr_sock *tr)
+{
+  struct rpki_cache *cache = tr->cache;
+  struct rpki_config *cf = (void *) cache->p->p.cf;
+  sock *sk = tr->sk;
+
+  const char *err_msg;
+  if ((err_msg = load_libssh()) != NULL)
+  {
+    CACHE_TRACE(D_EVENTS, cache, "%s", err_msg);
+    return TR_ERROR;
+  }
+
+  sk->type = SK_SSH_ACTIVE;
+  sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock));
+  sk->ssh->username = cf->ssh->user;
+  sk->ssh->client_privkey_path = cf->ssh->bird_private_key;
+  sk->ssh->server_hostkey_path = cf->ssh->cache_public_key;
+  sk->ssh->subsystem = "rpki-rtr";
+  sk->ssh->state = SK_SSH_CONNECT;
+
+  if (sk_open(sk) != 0)
+    return TR_ERROR;
+
+  return TR_SUCCESS;
+}
+
+static void
+rpki_tr_ssh_close(struct rpki_tr_sock *tr)
+{
+  struct rpki_tr_ssh *ssh = tr->data;
+
+  if (ssh && ssh->ident != NULL)
+  {
+    mb_free((char *) ssh->ident);
+    ssh->ident = NULL;
+  }
+
+  /* tr->sk is closed in tr_close() */
+}
+
+static const char *
+rpki_tr_ssh_ident(struct rpki_tr_sock *tr)
+{
+  ASSERT(tr != NULL);
+
+  struct rpki_cache *cache = tr->cache;
+  struct rpki_config *cf = (void *) cache->p->p.cf;
+  struct rpki_tr_ssh *ssh = tr->data;
+
+  if (ssh->ident != NULL)
+    return ssh->ident;
+
+  const char *username = cf->ssh->user;
+  const char *host = cf->hostname;
+  u16 port = cf->port;
+
+  size_t len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* <user> + '@' + <host> + ':' + <port> + '\0' */
+  char *ident = mb_alloc(cache->pool, len);
+  bsnprintf(ident, len, "%s@%s:%u", username, host, port);
+  ssh->ident = ident;
+
+  return ssh->ident;
+}
+
+/*
+ * Initializes the rpki_tr_sock struct for a SSH connection.
+ */
+void
+rpki_tr_ssh_init(struct rpki_tr_sock *tr)
+{
+  struct rpki_cache *cache = tr->cache;
+
+  tr->close_fp = &rpki_tr_ssh_close;
+  tr->open_fp = &rpki_tr_ssh_open;
+  tr->ident_fp = &rpki_tr_ssh_ident;
+
+  tr->data = mb_allocz(cache->pool, sizeof(struct rpki_tr_ssh));
+}
diff --git a/proto/rpki/ssh_transport.h b/proto/rpki/ssh_transport.h
new file mode 100644 (file)
index 0000000..9b2efd3
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ *     BIRD -- An implementation of the SSH protocol for the RPKI transport
+ *
+ *     This transport implementation uses libssh (http://www.libssh.org/)
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RPKI_SSH_TRANSPORT_H_
+#define _BIRD_RPKI_SSH_TRANSPORT_H_
+
+#include "transport.h"
+
+struct rpki_tr_ssh {
+  const char *ident;
+};
+
+void rpki_tr_ssh_init(struct rpki_tr_sock *tr);
+
+#endif
diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c
new file mode 100644 (file)
index 0000000..ce2ab29
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *     BIRD -- An implementation of the TCP protocol for the RPKI protocol transport
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "rpki.h"
+#include "tcp_transport.h"
+#include "sysdep/unix/unix.h"
+
+static int
+rpki_tr_tcp_open(struct rpki_tr_sock *tr)
+{
+  sock *sk = tr->sk;
+
+  sk->type = SK_TCP_ACTIVE;
+
+  if (sk_open(sk) != 0)
+    return TR_ERROR;
+
+  return TR_SUCCESS;
+}
+
+static void
+rpki_tr_tcp_close(struct rpki_tr_sock *tr)
+{
+  struct rpki_tr_tcp *tcp = tr->data;
+
+  if (tcp && tcp->ident != NULL)
+  {
+    mb_free((char *) tcp->ident);
+    tcp->ident = NULL;
+  }
+
+  /* tr->sk is closed in tr_close() */
+}
+
+static const char *
+rpki_tr_tcp_ident(struct rpki_tr_sock *tr)
+{
+  ASSERT(tr != NULL);
+
+  struct rpki_cache *cache = tr->cache;
+  struct rpki_config *cf = (void *) cache->p->p.cf;
+  struct rpki_tr_tcp *tcp = tr->data;
+
+  if (tcp->ident != NULL)
+    return tcp->ident;
+
+  const char *host = cf->hostname;
+  ip_addr ip = cf->ip;
+  u16 port = cf->port;
+
+  size_t colon_and_port_len = 6; /* max ":65535" */
+  size_t ident_len;
+  if (host)
+    ident_len = strlen(host) + colon_and_port_len + 1;
+  else
+    ident_len = IPA_MAX_TEXT_LENGTH + colon_and_port_len + 1;
+
+  char *ident = mb_alloc(cache->pool, ident_len);
+  if (host)
+    bsnprintf(ident, ident_len, "%s:%u", host, port);
+  else
+    bsnprintf(ident, ident_len, "%I:%u", ip, port);
+
+  tcp->ident = ident;
+  return tcp->ident;
+}
+
+/* Initializes the rpki_tr_sock struct for a TCP connection. */
+void
+rpki_tr_tcp_init(struct rpki_tr_sock *tr)
+{
+  struct rpki_cache *cache = tr->cache;
+
+  tr->close_fp = &rpki_tr_tcp_close;
+  tr->open_fp = &rpki_tr_tcp_open;
+  tr->ident_fp = &rpki_tr_tcp_ident;
+
+  tr->data = mb_allocz(cache->pool, sizeof(struct rpki_tr_tcp));
+}
diff --git a/proto/rpki/tcp_transport.h b/proto/rpki/tcp_transport.h
new file mode 100644 (file)
index 0000000..b0497a4
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ *     BIRD -- An implementation of the TCP protocol for the RPKI protocol transport
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RPKI_TCP_TRANSPORT_H_
+#define _BIRD_RPKI_TCP_TRANSPORT_H_
+
+#include "transport.h"
+
+struct rpki_tr_tcp {
+  const char *ident;
+};
+
+void rpki_tr_tcp_init(struct rpki_tr_sock *tr);
+
+#endif
diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c
new file mode 100644 (file)
index 0000000..76c166c
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "rpki.h"
+#include "transport.h"
+#include "sysdep/unix/unix.h"
+
+/*
+ * Fulfill sock->af and sock->daddr if sock->daddr is empty and hostname is defined
+ * Return TR_SUCCESS or TR_ERROR
+ */
+static int
+rpki_hostname_autoresolv(sock *sk)
+{
+  if (ipa_zero(sk->daddr) && sk->host)
+  {
+    struct addrinfo *res;
+    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;
+    }
+
+    if (res->ai_family == AF_INET)
+      sk->fam = SK_FAM_IPV4;
+    else
+      sk->fam = SK_FAM_IPV6;
+
+    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->fam = ip6_is_v4mapped(sk->daddr) ? SK_FAM_IPV4 : SK_FAM_IPV6;
+
+  return TR_SUCCESS;
+}
+
+/*
+ * Establish the connection.
+ * Returns TR_SUCCESS or TR_ERROR
+ */
+int
+rpki_tr_open(struct rpki_tr_sock *tr)
+{
+  struct rpki_cache *cache = tr->cache;
+  struct rpki_config *cf = (void *) cache->p->p.cf;
+
+  ASSERT(tr->sk == NULL);
+  tr->sk = sk_new(cache->pool);
+  sock *sk = tr->sk;
+
+  sk->tx_hook = rpki_connected_hook;
+  sk->err_hook = rpki_err_hook;
+  sk->data = cache;
+  sk->daddr = cf->ip;
+  sk->dport = cf->port;
+  sk->host = cf->hostname;
+  sk->rbsize = RPKI_RX_BUFFER_SIZE;
+  sk->tbsize = RPKI_TX_BUFFER_SIZE;
+  sk->tos = IP_PREC_INTERNET_CONTROL;
+  sk->type = -1; /* must be set in the specific transport layer in tr_open() */
+  rpki_hostname_autoresolv(sk);
+
+  return tr->open_fp(tr);
+}
+
+/* Close socket and prepare it for possible next open */
+inline void
+rpki_tr_close(struct rpki_tr_sock *tr)
+{
+  tr->close_fp(tr);
+
+  rfree(tr->sk);
+  tr->sk = NULL;
+}
+
+/* Returns a \0 terminated string identifier for the socket endpoint, eg host:port */
+inline const char *
+rpki_tr_ident(struct rpki_tr_sock *tr)
+{
+  return tr->ident_fp(tr);
+}
diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h
new file mode 100644 (file)
index 0000000..1b7892e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *     BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ *     (c) 2015 CZ.NIC
+ *
+ *     This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/*
+ * The RPKI transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RPKI server and client.
+ *
+ * Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * rpki_tr_tcp_init()).
+ *
+ * The rpki_tr_* functions call the corresponding function pointers, which are
+ * passed in the rpki_tr_sock struct, and forward the remaining arguments.
+ */
+
+#ifndef _BIRD_RPKI_TRANSPORT_H_
+#define _BIRD_RPKI_TRANSPORT_H_
+
+#include <time.h>
+
+/* The return values for tr_ functions */
+enum tr_rtvals {
+  TR_SUCCESS = 0,                      /* Operation was successfull */
+  TR_ERROR = -1,                       /* Error occured */
+  TR_WOULDBLOCK = -2,                  /* No data is available on the socket */
+  TR_INTR = -3,                                /* Call was interrupted from a signal */
+  TR_CLOSED = -4                       /* Connection closed */
+};
+
+/* A transport socket datastructure */
+struct rpki_tr_sock {
+  void *data;                          /* Technology specific data */
+  sock *sk;                            /* Standard BIRD socket */
+  struct rpki_cache *cache;            /* Cache server */
+  int (*open_fp)(struct rpki_tr_sock *); /* Pointer to a function that establishes the socket connection */
+  void (*close_fp)(struct rpki_tr_sock *); /* Pointer to a function that close and frees all memory allocated with this socket */
+  const char *(*ident_fp)(struct rpki_tr_sock *); /* Pointer to a function that returns an identifier for the socket endpoint */
+};
+
+int rpki_tr_open(struct rpki_tr_sock *tr);
+void rpki_tr_close(struct rpki_tr_sock *tr);
+const char *rpki_tr_ident(struct rpki_tr_sock *tr);
+
+#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 684ea132914b262da88bf05b58ccf1dfb9152b8f..a9d0cc950da6ac76b6eeb1c1cc67002d0bfc7705 100644 (file)
@@ -36,6 +36,7 @@
 #include "lib/socket.h"
 #include "lib/event.h"
 #include "lib/string.h"
+#include "lib/libssh.h"
 #include "nest/iface.h"
 
 #include "sysdep/unix/unix.h"
@@ -1060,26 +1061,58 @@ sk_free_bufs(sock *s)
   }
 }
 
+static void
+sk_ssh_free(sock *s)
+{
+  struct ssh_sock *ssh = s->ssh;
+
+  if (s->ssh == NULL)
+    return;
+
+  s->ssh = NULL;
+
+  if (ssh->channel)
+  {
+    ssh_channel_close(ssh->channel);
+    ssh_channel_free(ssh->channel);
+    ssh->channel = NULL;
+  }
+
+  if (ssh->session)
+  {
+    ssh_disconnect(ssh->session);
+    ssh_free(ssh->session);
+    ssh->session = NULL;
+  }
+}
+
 static void
 sk_free(resource *r)
 {
   sock *s = (sock *) r;
 
   sk_free_bufs(s);
-  if (s->fd >= 0)
-  {
-    close(s->fd);
 
-    /* FIXME: we should call sk_stop() for SKF_THREAD sockets */
-    if (s->flags & SKF_THREAD)
-      return;
+  if (s->type == SK_SSH || s->type == SK_SSH_ACTIVE)
+    sk_ssh_free(s);
+
+  if (s->fd < 0)
+    return;
 
+  /* FIXME: we should call sk_stop() for SKF_THREAD sockets */
+  if (!(s->flags & SKF_THREAD))
+  {
     if (s == current_sock)
       current_sock = sk_next(s);
     if (s == stored_sock)
       stored_sock = sk_next(s);
     rem_node(&s->n);
   }
+
+  if (s->type != SK_SSH && s->type != SK_SSH_ACTIVE)
+    close(s->fd);
+
+  s->fd = -1;
 }
 
 void
@@ -1181,6 +1214,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");
 
@@ -1293,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)
 {
@@ -1345,6 +1389,194 @@ 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 automata */
+  switch (s->ssh->state)
+  {
+  case SK_SSH_CONNECT:
+  {
+    switch (ssh_connect(s->ssh->session))
+    {
+    case SSH_AGAIN:
+      return SSH_AGAIN;
+
+    case SSH_OK:
+      break;
+
+    default:
+      return SSH_ERROR;
+    }
+  }
+
+  case SK_SSH_SERVER_KNOWN:
+  {
+    s->ssh->state = SK_SSH_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 SK_SSH_USERAUTH:
+  {
+    s->ssh->state = SK_SSH_USERAUTH;
+    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 SK_SSH_CHANNEL:
+  {
+    s->ssh->state = SK_SSH_CHANNEL;
+    s->ssh->channel = ssh_channel_new(s->ssh->session);
+    if (s->ssh->channel == NULL)
+      return SSH_ERROR;
+  }
+
+  case SK_SSH_SESSION:
+  {
+    s->ssh->state = SK_SSH_SESSION;
+    switch (ssh_channel_open_session(s->ssh->channel))
+    {
+    case SSH_AGAIN:
+      return SSH_AGAIN;
+
+    case SSH_OK:
+      break;
+
+    default:
+      return SSH_ERROR;
+    }
+  }
+
+  case SK_SSH_SUBSYSTEM:
+  {
+    s->ssh->state = SK_SSH_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 SK_SSH_ESTABLISHED:
+    s->ssh->state = SK_SSH_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");
+
+  ssh_session sess = ssh_new();
+  if (sess == NULL)
+    ERR2("Cannot create a ssh session");
+  s->ssh->session = sess;
+
+  const int verbosity = SSH_LOG_NOLOG;
+  ssh_options_set(sess, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+  ssh_options_set(sess, SSH_OPTIONS_HOST, s->host);
+  ssh_options_set(sess, SSH_OPTIONS_PORT, &(s->dport));
+  ssh_options_set(sess, SSH_OPTIONS_USER, s->ssh->username);
+
+  if (s->ssh->server_hostkey_path)
+    ssh_options_set(sess, SSH_OPTIONS_KNOWNHOSTS, s->ssh->server_hostkey_path);
+
+  if (s->ssh->client_privkey_path)
+    ssh_options_set(sess, SSH_OPTIONS_IDENTITY, s->ssh->client_privkey_path);
+
+  ssh_set_blocking(sess, 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(sess));
+      break;
+  }
+
+  return ssh_get_fd(sess);
+
+ err:
+  return -1;
+}
+
 /**
  * sk_open - open a socket
  * @s: socket
@@ -1376,6 +1608,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;
@@ -1455,6 +1692,7 @@ sk_open(sock *s)
       ERR2("listen");
     break;
 
+  case SK_SSH_ACTIVE:
   case SK_MAGIC:
     break;
 
@@ -1464,6 +1702,7 @@ sk_open(sock *s)
 
   if (!(s->flags & SKF_THREAD))
     sk_insert(s);
+
   return 0;
 
 err:
@@ -1647,6 +1886,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:
     {
@@ -1671,6 +1930,7 @@ sk_maybe_write(sock *s)
       reset_tx_buffer(s);
       return 1;
     }
+
   default:
     bug("sk_maybe_write: unknown socket type %d", s->type);
   }
@@ -1750,6 +2010,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
@@ -1783,17 +2099,15 @@ sk_read(sock *s, int revents)
       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);
 
@@ -1832,6 +2146,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)
     {