diff --git a/Makefile.in b/Makefile.in
index 63d3351f..a5e80ff4 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -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@
@@ -58,6 +59,8 @@ all: daemon cli
daemon: $(daemon)
cli: $(client)
+$(daemon): LIBS += $(DAEMON_LIBS)
+
# Include directories
dirs := client conf doc filter lib nest test $(addprefix proto/,$(protocols)) @sysdep_dirs@
diff --git a/configure.in b/configure.in
index 8a825bf2..32344d1f 100644
--- a/configure.in
+++ b/configure.in
@@ -10,6 +10,7 @@ AC_ARG_ENABLE(debug, [ --enable-debug enable internal debugging routin
AC_ARG_ENABLE(memcheck, [ --enable-memcheck check memory allocations when debugging (default: enabled)],,enable_memcheck=yes)
AC_ARG_ENABLE(client, [ --enable-client enable building of BIRD client (default: enabled)],,enable_client=yes)
AC_ARG_ENABLE(pthreads, [ --enable-pthreads enable POSIX threads support (default: detect)],,enable_pthreads=try)
+AC_ARG_ENABLE(libssh, [ --enable-libssh enable LibSSH support together with RPKI protocol (default: detect)],,enable_libssh=try)
AC_ARG_WITH(sysconfig, [ --with-sysconfig=FILE use specified BIRD system configuration file])
AC_ARG_WITH(protocols, [ --with-protocols=LIST include specified routing protocols (default: all)],,[with_protocols="all"])
AC_ARG_WITH(sysinclude, [ --with-sysinclude=PATH search for system includes on specified place])
@@ -86,6 +87,21 @@ if test "$enable_pthreads" != no ; then
fi
fi
+if test "$enable_libssh" != no ; then
+ AC_CHECK_LIB(ssh, ssh_connect)
+ if test $ac_cv_lib_ssh_ssh_connect = yes ; then
+ proto_rpki=rpki
+ enable_libssh=yes
+ AC_DEFINE(HAVE_LIBSSH)
+ else
+ if test "$enable_libssh" = yes ; then
+ AC_MSG_ERROR([LibSSH not available.])
+ else
+ enable_libssh=no
+ fi
+ fi
+fi
+
if test "$bird_cflags_default" = yes ; then
BIRD_CHECK_GCC_OPTION(bird_cv_c_option_wno_pointer_sign, -Wno-pointer-sign, -Wall)
BIRD_CHECK_GCC_OPTION(bird_cv_c_option_wno_missing_init, -Wno-missing-field-initializers, -Wall -Wextra)
@@ -169,8 +185,8 @@ fi
AC_SUBST(iproutedir)
-# all_protocols="$proto_bfd babel bgp ospf pipe radv rip static"
-all_protocols="$proto_bfd bgp ospf pipe radv rip static"
+# all_protocols="$proto_bfd babel bgp ospf pipe radv rip $proto_rpki static"
+all_protocols="$proto_bfd bgp ospf pipe radv rip $proto_rpki static "
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -231,6 +247,9 @@ if test "$enable_debug" = yes ; then
fi
fi
+DAEMON_LIBS=
+AC_SUBST(DAEMON_LIBS)
+
CLIENT=birdcl
CLIENT_LIBS=
if test "$enable_client" = yes ; then
diff --git a/doc/bird.sgml b/doc/bird.sgml
index e70232d1..a734b2ff 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -3788,8 +3788,8 @@ protocol rip [<name>] {
RIP defines two route attributes:
- int rip_metric/
- RIP metric of the route (ranging from 0 to int
+ RIP metric of the route (ranging from 0 to
+RPKI
+
+Introduction
+
+The Resource Public Key Infrastructure (RPKI) is mechanism for origin
+validation of BGP routes (RFC 6480). BIRD supports only so-called RPKI-based
+origin validation. There is implemented RPKI to Router (RPKI-RTR) protocol (RFC
+6810). It uses some of the RPKI data to allow a router to verify 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 today, e.g. the famous
+Pakastani accidental announcement of YouTube's address space.
+
+
The RPKI-RTR protocol receives and maintains a set of ROAs from a cache
+server (also called validator). You can validate routes (RFC 6483) using
+function reload in for manual call of revalidation of all
+routes.
+
+Supported transports
+
+ - Unprotected transport over TCP uses a port 323. The cache server
+ and BIRD router should be on the same trusted and controlled network
+ for security reasons.
+
- SSHv2 encrypted transport connection uses the normal SSH port
+ 22.
+
+
+Configuration
+
+We currently support just one cache server per protocol. However you can
+define more RPKI protocols generally.
+
+
+protocol rpki [<name>] {
+ roa4 { table <tab>; };
+ roa6 { table <tab>; };
+ remote <ip> | "<domain>" [port <num>];
+ port <num>;
+ refresh [keep] <num>;
+ retry [keep] <num>;
+ expire [keep] <num>;
+ transport tcp;
+ transport ssh {
+ bird private key "</path/to/id_rsa>";
+ remote public key "</path/to/known_host>";
+ user "<name>";
+ };
+}
+
+
+
Alse note that you have to specify ROA table into which will be imported
+routes from a cache server. If you want to import only IPv4 prefixes you have
+to specify only roa4 table. Similarly with IPv6 prefixes only. If you want to
+fetch both IPv4 and even IPv6 ROAs you have to specify both types of ROA
+tables.
+
+RPKI protocol options
+
+
+ remote Specifies
+ a destination address of the cache server. Can be specified by an IP
+ address or by full domain name string. Only one cache can be specified
+ per protocol. This option is required.
+
+ port Specifies the port number. The default port
+ number is 323 for transport without any encryption and 22 for transport
+ with SSH encryption.
+
+ refresh [keep] Time period in seconds. Tells how
+ long to wait before next attempting to poll the cache using a Serial
+ Query or a Reset Query packet. Must be lower than 86400 seconds (one
+ day). Too low value can caused a false positive detection of
+ network connection problems. A keyword retry [keep] Time period in seconds between a failed
+ Serial/Reset Query and a next attempt. Maximum allowed value is 7200
+ seconds (two hours). Too low value can caused a false positive
+ detection of network connection problems. A keyword expire [keep] Time period in seconds. Received
+ records are deleted if the client was unable to successfully refresh
+ data for this time period. Must be in range from 600 seconds (ten
+ minutes) to 172800 seconds (two days). A keyword transport tcp Unprotected transport over TCP. It's a default
+ transport. Should be used only on secure private networks.
+ Default: tcp
+
+ transport ssh { It enables a
+ SSHv2 transport encryption. Cannot be combined with a TCP transport.
+ Default: off
+
+
+SSH transport options
+
+ bird private key "/path/to/id_rsa "
+ A path to the BIRD's private SSH key for authentication.
+ It can be a id_rsa file.
+
+ remote public key "/path/to/known_host "
+ A path to the cache's public SSH key for verification identity
+ of the cache server. It could be a path to known_host file.
+
+ user "
+ A SSH user name for authentication. This option is a required.
+
+
+Examples
+BGP origin validation
+Policy: Don't import
+roa4 table r4;
+roa6 table r6;
+
+protocol rpki {
+ debug all;
+
+ roa4 { table r4; };
+ roa6 { table r6; };
+
+ # Please, do not use rpki-validator.realmv6.org in production
+ remote "rpki-validator.realmv6.org" port 8282;
+
+ retry keep 5;
+ refresh keep 30;
+ expire 600;
+}
+
+filter peer_in {
+ if (roa_check(r4, net, bgp_path.last) = ROA_INVALID ||
+ roa_check(r6, net, bgp_path.last) = ROA_INVALID) then
+ {
+ print "Ignore invalid ROA ", net, " for ASN ", bgp_path.last;
+ reject;
+ }
+ accept;
+}
+
+protocol bgp {
+ debug all;
+ local as 65000;
+ neighbor 192.168.2.1 as 65001;
+ import filter peer_in;
+}
+
+
+SSHv2 transport encryption
+
+roa4 table r4;
+roa6 table r6;
+
+protocol rpki {
+ debug all;
+
+ roa4 { table r4; };
+ roa6 { table r6; };
+
+ remote 127.0.0.1 port 2345;
+ transport ssh {
+ bird private key "/home/birdgeek/.ssh/id_rsa";
+ remote public key "/home/birdgeek/.ssh/known_hosts";
+ user "birdgeek";
+ };
+
+ # Default interval values
+}
+
+
+
Static
diff --git a/filter/config.Y b/filter/config.Y
index eecebf61..7b4178be 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -399,8 +399,8 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, CAST, DEST, IFNAME, IFINDEX,
PREFERENCE,
- ROA_CHECK,
- LEN,
+ ROA_CHECK, ASN,
+ LEN, MAXLEN,
DEFINED,
ADD, DELETE, CONTAINS, RESET,
PREPEND, FIRST, LAST, LAST_NONAGGREGATED, MATCH,
@@ -891,6 +891,8 @@ term:
| term '.' IP { $$ = f_new_inst(); $$->code = P('c','p'); $$->a1.p = $1; $$->aux = T_IP; }
| term '.' LEN { $$ = f_new_inst(); $$->code = 'L'; $$->a1.p = $1; }
+ | term '.' MAXLEN { $$ = f_new_inst(); $$->code = P('R','m'); $$->a1.p = $1; }
+ | term '.' ASN { $$ = f_new_inst(); $$->code = P('R','a'); $$->a1.p = $1; }
| term '.' MASK '(' term ')' { $$ = f_new_inst(); $$->code = P('i','M'); $$->a1.p = $1; $$->a2.p = $5; }
| term '.' FIRST { $$ = f_new_inst(); $$->code = P('a','f'); $$->a1.p = $1; }
| term '.' LAST { $$ = f_new_inst(); $$->code = P('a','l'); $$->a1.p = $1; }
diff --git a/filter/filter.c b/filter/filter.c
index 3bd425ac..4ec04554 100644
--- a/filter/filter.c
+++ b/filter/filter.c
@@ -1183,6 +1183,26 @@ interpret(struct f_inst *what)
default: runtime( "Prefix, path, clist or eclist expected" );
}
break;
+ case P('R','m'): /* Get ROA max prefix length */
+ ONEARG;
+ if (v1.type != T_NET || !net_is_roa(v1.val.net))
+ runtime( "ROA expected" );
+
+ res.type = T_INT;
+ res.val.i = (v1.val.net->type == NET_ROA4) ?
+ ((net_addr_roa4 *) v1.val.net)->max_pxlen :
+ ((net_addr_roa6 *) v1.val.net)->max_pxlen;
+ break;
+ case P('R','a'): /* Get ROA ASN */
+ ONEARG;
+ if (v1.type != T_NET || !net_is_roa(v1.val.net))
+ runtime( "ROA expected" );
+
+ res.type = T_INT;
+ res.val.i = (v1.val.net->type == NET_ROA4) ?
+ ((net_addr_roa4 *) v1.val.net)->asn :
+ ((net_addr_roa6 *) v1.val.net)->asn;
+ break;
case P('c','p'): /* Convert prefix to ... */
ONEARG;
if (v1.type != T_NET)
@@ -1476,12 +1496,15 @@ interpret(struct f_inst *what)
if (!table)
runtime("Missing ROA table");
- /* Table type is either NET_ROA4 or NET_ROA6, checked in parser */
- if (v1.val.net->type != ((table->addr_type == NET_ROA4) ? NET_IP4 : NET_IP6))
- runtime("Incompatible net type");
+ if (table->addr_type != NET_ROA4 && table->addr_type != NET_ROA6)
+ runtime("Table type must be either ROA4 or ROA6");
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;
diff --git a/filter/test.conf b/filter/test.conf
index 16ef7a86..18aeaae1 100644
--- a/filter/test.conf
+++ b/filter/test.conf
@@ -1139,30 +1139,80 @@ int j;
accept "ok I take that";
}
-/*
-roa table rl
+roa4 table r4;
+roa6 table r6;
+
+protocol static
{
- roa 10.110.0.0/16 max 16 as 1000;
- roa 10.120.0.0/16 max 24 as 1000;
- roa 10.130.0.0/16 max 24 as 2000;
- roa 10.130.128.0/18 max 24 as 3000;
+ roa4 { table r4; };
+ route 10.110.0.0/16 max 16 as 1000 blackhole;
+ route 10.120.0.0/16 max 24 as 1000 blackhole ;
+ route 10.130.0.0/16 max 24 as 2000 blackhole;
+ route 10.130.128.0/18 max 24 as 3000 blackhole;
}
-function test_roa()
+protocol static
+{
+ roa6 { table r6; };
+ route 2001:0db8:85a3:8a2e::/64 max 96 as 1000 blackhole;
+}
+
+function test_roa_check()
{
# cannot be tested in __startup(), sorry
- print "Testing ROA";
- print "Should be true: ", roa_check(rl, 10.10.0.0/16, 1000) = ROA_UNKNOWN,
- " ", roa_check(rl, 10.0.0.0/8, 1000) = ROA_UNKNOWN,
- " ", roa_check(rl, 10.110.0.0/16, 1000) = ROA_VALID,
- " ", roa_check(rl, 10.110.0.0/16, 2000) = ROA_INVALID,
- " ", roa_check(rl, 10.110.32.0/20, 1000) = ROA_INVALID,
- " ", roa_check(rl, 10.120.32.0/20, 1000) = ROA_VALID;
- print "Should be true: ", roa_check(rl, 10.120.32.0/20, 2000) = ROA_INVALID,
- " ", roa_check(rl, 10.120.32.32/28, 1000) = ROA_INVALID,
- " ", roa_check(rl, 10.130.130.0/24, 1000) = ROA_INVALID,
- " ", roa_check(rl, 10.130.130.0/24, 2000) = ROA_VALID,
- " ", roa_check(rl, 10.130.30.0/24, 3000) = ROA_INVALID,
- " ", roa_check(rl, 10.130.130.0/24, 3000) = ROA_VALID;
+ print "Should be true: ", roa_check(r4, 10.10.0.0/16, 1000) = ROA_UNKNOWN,
+ " ", roa_check(r4, 10.0.0.0/8, 1000) = ROA_UNKNOWN,
+ " ", roa_check(r4, 10.110.0.0/16, 1000) = ROA_VALID,
+ " ", roa_check(r4, 10.110.0.0/16, 2000) = ROA_INVALID,
+ " ", roa_check(r4, 10.110.32.0/20, 1000) = ROA_INVALID,
+ " ", roa_check(r4, 10.120.32.0/20, 1000) = ROA_VALID;
+ print "Should be true: ", roa_check(r4, 10.120.32.0/20, 2000) = ROA_INVALID,
+ " ", roa_check(r4, 10.120.32.32/28, 1000) = ROA_INVALID,
+ " ", roa_check(r4, 10.130.130.0/24, 1000) = ROA_INVALID,
+ " ", roa_check(r4, 10.130.130.0/24, 2000) = ROA_VALID,
+ " ", roa_check(r4, 10.130.30.0/24, 3000) = ROA_INVALID,
+ " ", roa_check(r4, 10.130.130.0/24, 3000) = ROA_VALID;
+ print "Should be true: ", roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_VALID,
+ " ", roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID,
+ " ", roa_check(r6, 2001:0db8:85a3:8a2e::/64, 1000) = ROA_VALID,
+ " ", roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN;
+
+ print "Should be true: ", roa_check(r4, 10.10.0.0/16, 1000) = ROA_UNKNOWN,
+ " ", roa_check(r4, 10.0.0.0/8, 1000) = ROA_UNKNOWN,
+ " ", roa_check(r4, 10.110.0.0/16, 1000) = ROA_VALID,
+ " ", roa_check(r4, 10.110.0.0/16, 2000) = ROA_INVALID,
+ " ", roa_check(r4, 10.110.32.0/20, 1000) = ROA_INVALID,
+ " ", roa_check(r4, 10.120.32.0/20, 1000) = ROA_VALID;
+
+ print "Should be true: ", roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_VALID,
+ " ", roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID,
+ " ", roa_check(r6, 2001:0db8:85a3:8a2e::/64, 1000) = ROA_VALID,
+ " ", roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN;
+
+ print "Should be true: ", roa_check(r4, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID ||
+ roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID;
+
+ print "Should be false: ", roa_check(r4, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_INVALID ||
+ roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_INVALID,
+ " ", roa_check(r4, 2001:0db8:85a3::/48, 1000) = ROA_INVALID ||
+ roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_INVALID;
+
+ print "Should be true: ", 10.130.130.0/24 ~ 0.0.0.0/0,
+ " ", 2001:0db8:85a3:8a2e::/64 ~ ::/0;
+ print "Should be false: ", 10.130.130.0/24 ~ ::/0,
+ " ", 2001:0db8:85a3:8a2e::/64 ~ 0.0.0.0/0;
+}
+
+function roa_operators_test()
+prefix pfx;
+{
+ print "Testing ROA prefix operators '.maxlen' and '.asn':";
+
+ pfx = 12.13.0.0/16 max 24 as 1234;
+ print pfx;
+ print "Should be true: ", pfx.len = 16, " ", pfx.maxlen = 24, " ", pfx.asn = 1234;
+
+ pfx = 1000::/8 max 32 as 1234;
+ print pfx;
+ print "Should be true: ", pfx.len = 8, " ", pfx.maxlen = 32, " ", pfx.asn = 1234;
}
-*/
\ No newline at end of file
diff --git a/lib/Makefile b/lib/Makefile
index 26d8bf65..a7da9802 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,3 +1,7 @@
+src := bitops.c checksum.c ip.c lists.c md5.c net.c patmatch.c printf.c sha1.c sha256.c sha512.c slists.c xmalloc.c
+obj := $(src-o-files)
+$(all-client)
+
src := bitops.c checksum.c event.c idm.c ip.c lists.c mac.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/net.h b/lib/net.h
index 9b22c6be..e8310894 100644
--- a/lib/net.h
+++ b/lib/net.h
@@ -171,6 +171,9 @@ static inline int net_type_match(const net_addr *a, u32 mask)
static inline int net_is_ip(const net_addr *a)
{ return (a->type == NET_IP4) || (a->type == NET_IP6); }
+static inline int net_is_roa(const net_addr *a)
+{ return (a->type == NET_ROA4) || (a->type == NET_ROA6); }
+
static inline ip4_addr net4_prefix(const net_addr *a)
{ return ((net_addr_ip4 *) a)->prefix; }
diff --git a/lib/resource.c b/lib/resource.c
index 68718dfb..ab8c800f 100644
--- a/lib/resource.c
+++ b/lib/resource.c
@@ -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;
diff --git a/lib/resource.h b/lib/resource.h
index 1a62d389..1a0568b4 100644
--- a/lib/resource.h
+++ b/lib/resource.h
@@ -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 */
diff --git a/lib/socket.h b/lib/socket.h
index 43db3cab..d5281b83 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -12,6 +12,29 @@
#include
#include "lib/resource.h"
+#ifdef HAVE_LIBSSH
+#define LIBSSH_LEGACY_0_4
+#include
+#endif
+
+#ifdef HAVE_LIBSSH
+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 */
+};
+#endif
typedef struct birdsock {
resource r;
@@ -20,6 +43,7 @@ typedef struct birdsock {
int subtype; /* Socket subtype */
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 */
@@ -52,7 +76,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 +140,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 subtypes
diff --git a/nest/proto.c b/nest/proto.c
index bfbf80d8..815d0652 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -1265,6 +1265,9 @@ protos_build(void)
#ifdef CONFIG_BABEL
proto_build(&proto_babel);
#endif
+#ifdef CONFIG_RPKI
+ proto_build(&proto_rpki);
+#endif
proto_pool = rp_new(&root_pool, "Protocols");
proto_shutdown_timer = tm_new(proto_pool);
diff --git a/nest/protocol.h b/nest/protocol.h
index 19414525..6efaaaf7 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -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_babel;
+ proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel, 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 pool *proto_pool;
extern list proto_list;
@@ -568,11 +568,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);
diff --git a/nest/route.h b/nest/route.h
index 8a238860..d652ca15 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -283,7 +283,7 @@ void *net_route(rtable *tab, const net_addr *n);
int net_roa_check(rtable *tab, const net_addr *n, u32 asn);
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 */
int rt_examine(rtable *t, net_addr *a, struct proto *p, struct filter *filter);
rte *rt_export_merged(struct channel *c, net *net, rte **rt_free, struct ea_list **tmpa, linpool *pool, int silent);
@@ -383,6 +383,8 @@ typedef struct rta {
#define RTS_BGP 11 /* BGP route */
#define RTS_PIPE 12 /* Inter-table wormhole */
#define RTS_BABEL 13 /* Babel route */
+#define RTS_RPKI 14 /* Route Origin Authorization */
+
#define RTC_UNICAST 0
#define RTC_BROADCAST 1
@@ -572,6 +574,7 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX];
#define DEF_PREF_BABEL 130 /* Babel */
#define DEF_PREF_RIP 120 /* RIP */
#define DEF_PREF_BGP 100 /* BGP */
+#define DEF_PREF_RPKI 100 /* RPKI */
#define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */
/*
diff --git a/nest/rt-table.c b/nest/rt-table.c
index ae56e50a..6bf6c2fe 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -1301,7 +1301,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;
@@ -2695,6 +2695,12 @@ rt_show(struct rt_show_data *d)
}
}
+ if (d->table->addr_type != d->addr->type)
+ {
+ cli_msg(8001, "Incompatible type of prefix/ip with table");
+ return;
+ }
+
if (d->show_for)
n = net_route(d->table, d->addr);
else
diff --git a/proto/Doc b/proto/Doc
index 04c25bc0..ef573d2a 100644
--- a/proto/Doc
+++ b/proto/Doc
@@ -4,7 +4,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/babel/Makefile b/proto/babel/Makefile
index 400ffbac..d7684705 100644
--- a/proto/babel/Makefile
+++ b/proto/babel/Makefile
@@ -1,5 +1,4 @@
-source=babel.c packets.c
-root-rel=../../
-dir-name=proto/babel
-
-include ../../Rules
+src := babel.c packets.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index 6a95d82f..e8b6c314 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -21,11 +21,7 @@
#include "lib/lists.h"
#include "lib/socket.h"
#include "lib/string.h"
-#include "lib/timer.h"
-
-#ifndef IPV6
-#error "The Babel protocol only speaks IPv6"
-#endif
+#include "sysdep/unix/timer.h"
#define EA_BABEL_METRIC EA_CODE(EAP_BABEL, 0)
#define EA_BABEL_ROUTER_ID EA_CODE(EAP_BABEL, 1)
diff --git a/proto/rpki/Doc b/proto/rpki/Doc
new file mode 100644
index 00000000..d1d1bf55
--- /dev/null
+++ b/proto/rpki/Doc
@@ -0,0 +1,5 @@
+S rpki.c
+S packets.c
+S transport.c
+S tcp_transport.c
+S ssh_transport.c
diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile
new file mode 100644
index 00000000..bd76145e
--- /dev/null
+++ b/proto/rpki/Makefile
@@ -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
index 00000000..39fdfd01
--- /dev/null
+++ b/proto/rpki/config.Y
@@ -0,0 +1,144 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/rpki/rpki.h"
+
+CF_DEFINES
+
+#define RPKI_CFG ((struct rpki_config *) this_proto)
+#define RPKI_TR_SSH_CFG ((struct rpki_tr_ssh_config *) RPKI_CFG->tr_config.spec)
+
+static void
+rpki_check_unused_hostname(void)
+{
+ if (RPKI_CFG->hostname != NULL)
+ cf_error("Only one cache server per protocol allowed");
+}
+
+static void
+rpki_check_unused_transport(void)
+{
+ if (RPKI_CFG->tr_config.spec != NULL)
+ cf_error("At the most one transport per protocol allowed");
+}
+
+CF_DECLS
+
+CF_KEYWORDS(RPKI, REMOTE, BIRD, PRIVATE, PUBLIC, KEY, TCP, SSH, TRANSPORT, USER,
+ RETRY, REFRESH, EXPIRE, KEEP)
+
+%type rpki_keep_interval
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, rpki_proto)
+
+rpki_proto_start: proto_start RPKI {
+ this_proto = proto_config_new(&proto_rpki, $1);
+ RPKI_CFG->retry_interval = RPKI_RETRY_INTERVAL;
+ RPKI_CFG->refresh_interval = RPKI_REFRESH_INTERVAL;
+ RPKI_CFG->expire_interval = RPKI_EXPIRE_INTERVAL;
+};
+
+rpki_proto: rpki_proto_start proto_name '{' rpki_proto_opts '}' { rpki_check_config(RPKI_CFG); };
+
+rpki_proto_opts:
+ /* empty */
+ | rpki_proto_opts rpki_proto_item ';'
+ ;
+
+rpki_proto_item:
+ proto_item
+ | proto_channel
+ | REMOTE rpki_cache_addr
+ | REMOTE rpki_cache_addr rpki_proto_item_port
+ | rpki_proto_item_port
+ | TRANSPORT rpki_transport
+ | REFRESH rpki_keep_interval expr {
+ if (rpki_check_refresh_interval($3))
+ cf_error(rpki_check_refresh_interval($3));
+ RPKI_CFG->refresh_interval = $3;
+ RPKI_CFG->keep_refresh_interval = $2;
+ }
+ | RETRY rpki_keep_interval expr {
+ if (rpki_check_retry_interval($3))
+ cf_error(rpki_check_retry_interval($3));
+ RPKI_CFG->retry_interval = $3;
+ RPKI_CFG->keep_retry_interval = $2;
+ }
+ | EXPIRE rpki_keep_interval expr {
+ if (rpki_check_expire_interval($3))
+ cf_error(rpki_check_expire_interval($3));
+ RPKI_CFG->expire_interval = $3;
+ RPKI_CFG->keep_expire_interval = $2;
+ }
+ ;
+
+rpki_keep_interval:
+ /* empty */ { $$ = 0; }
+ | KEEP { $$ = 1; }
+ ;
+
+rpki_proto_item_port: PORT expr { check_u16($2); RPKI_CFG->port = $2; };
+
+rpki_cache_addr:
+ text {
+ rpki_check_unused_hostname();
+ RPKI_CFG->hostname = $1;
+ }
+ | ipa {
+ rpki_check_unused_hostname();
+ RPKI_CFG->ip = $1;
+ /* Ensure hostname is filled */
+ char *hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN + 1));
+ bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip);
+ RPKI_CFG->hostname = hostname;
+ }
+ ;
+
+rpki_transport:
+ TCP rpki_transport_tcp_init
+ | SSH rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_check
+ ;
+
+rpki_transport_tcp_init:
+{
+ rpki_check_unused_transport();
+ RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config));
+ RPKI_CFG->tr_config.type = RPKI_TR_TCP;
+};
+
+rpki_transport_ssh_init:
+{
+ rpki_check_unused_transport();
+ RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_ssh_config));
+ RPKI_CFG->tr_config.type = RPKI_TR_SSH;
+};
+
+rpki_transport_ssh_opts:
+ /* empty */
+ | rpki_transport_ssh_opts rpki_transport_ssh_item ';'
+ ;
+
+rpki_transport_ssh_item:
+ BIRD PRIVATE KEY text { RPKI_TR_SSH_CFG->bird_private_key = $4; }
+ | REMOTE PUBLIC KEY text { RPKI_TR_SSH_CFG->cache_public_key = $4; }
+ | USER text { RPKI_TR_SSH_CFG->user = $2; }
+ ;
+
+rpki_transport_ssh_check:
+{
+ if (RPKI_TR_SSH_CFG->user == NULL)
+ cf_error("User must be set");
+};
+
+CF_CODE
+
+CF_END
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c
new file mode 100644
index 00000000..22b0b54f
--- /dev/null
+++ b/proto/rpki/packets.c
@@ -0,0 +1,1077 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * 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
+#include
+#include
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "transport.h"
+#include "packets.h"
+
+#define RPKI_ADD_FLAG 0b00000001
+
+enum rpki_transmit_type {
+ RPKI_RECV = 0,
+ RPKI_SEND = 1,
+};
+
+enum pdu_error_type {
+ CORRUPT_DATA = 0,
+ INTERNAL_ERROR = 1,
+ NO_DATA_AVAIL = 2,
+ INVALID_REQUEST = 3,
+ UNSUPPORTED_PROTOCOL_VER = 4,
+ UNSUPPORTED_PDU_TYPE = 5,
+ WITHDRAWAL_OF_UNKNOWN_RECORD = 6,
+ DUPLICATE_ANNOUNCEMENT = 7,
+ PDU_TOO_BIG = 32
+};
+
+static const char *str_pdu_error_type[] = {
+ [CORRUPT_DATA] = "Corrupt-Data",
+ [INTERNAL_ERROR] = "Internal-Error",
+ [NO_DATA_AVAIL] = "No-Data-Available",
+ [INVALID_REQUEST] = "Invalid-Request",
+ [UNSUPPORTED_PROTOCOL_VER] = "Unsupported-Protocol-Version",
+ [UNSUPPORTED_PDU_TYPE] = "Unsupported-PDU-Type",
+ [WITHDRAWAL_OF_UNKNOWN_RECORD]= "Withdrawal-Of-Unknown-Record",
+ [DUPLICATE_ANNOUNCEMENT] = "Duplicate-Announcement",
+ [PDU_TOO_BIG] = "PDU-Too-Big",
+};
+
+enum pdu_type {
+ SERIAL_NOTIFY = 0,
+ SERIAL_QUERY = 1,
+ RESET_QUERY = 2,
+ CACHE_RESPONSE = 3,
+ IPV4_PREFIX = 4,
+ RESERVED = 5,
+ IPV6_PREFIX = 6,
+ END_OF_DATA = 7,
+ CACHE_RESET = 8,
+ ROUTER_KEY = 9,
+ ERROR = 10,
+ PDU_TYPE_MAX
+};
+
+static const char *str_pdu_type_[] = {
+ [SERIAL_NOTIFY] = "Serial Notify",
+ [SERIAL_QUERY] = "Serial Query",
+ [RESET_QUERY] = "Reset Query",
+ [CACHE_RESPONSE] = "Cache Response",
+ [IPV4_PREFIX] = "IPv4 Prefix",
+ [RESERVED] = "Reserved",
+ [IPV6_PREFIX] = "IPv6 Prefix",
+ [END_OF_DATA] = "End of Data",
+ [CACHE_RESET] = "Cache Reset",
+ [ROUTER_KEY] = "Router Key",
+ [ERROR] = "Error"
+};
+
+static const char * const str_pdu_type(uint type) {
+ if (type < PDU_TYPE_MAX)
+ return str_pdu_type_[type];
+ else
+ return "Undefined packet type";
+}
+
+/*
+ * 0 8 16 24 31
+ * .-------------------------------------------.
+ * | Protocol | PDU | |
+ * | Version | Type | reserved = zero |
+ * | 0 or 1 | 0 - 10 | |
+ * +-------------------------------------------+
+ * | |
+ * | Length >= 8 |
+ * | |
+ * `-------------------------------------------' */
+struct pdu_header {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+} PACKED;
+
+struct pdu_cache_response {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+} PACKED;
+
+struct pdu_serial_notify {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_serial_query {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_ipv4 {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+ u8 flags;
+ u8 prefix_len;
+ u8 max_prefix_len;
+ u8 zero;
+ ip4_addr prefix;
+ u32 asn;
+} PACKED;
+
+struct pdu_ipv6 {
+ u8 ver;
+ u8 type;
+ u16 reserved;
+ u32 len;
+ u8 flags;
+ u8 prefix_len;
+ u8 max_prefix_len;
+ u8 zero;
+ ip6_addr prefix;
+ u32 asn;
+} PACKED;
+
+/*
+ * 0 8 16 24 31
+ * .-------------------------------------------.
+ * | Protocol | PDU | |
+ * | Version | Type | Error Code |
+ * | 1 | 10 | |
+ * +-------------------------------------------+
+ * | |
+ * | Length |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Length of Encapsulated PDU |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * ~ Copy of Erroneous PDU ~
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Length of Error Text |
+ * | |
+ * +-------------------------------------------+
+ * | |
+ * | Arbitrary Text |
+ * | of |
+ * ~ Error Diagnostic Message ~
+ * | |
+ * `-------------------------------------------' */
+struct pdu_error {
+ u8 ver;
+ u8 type;
+ u16 error_code;
+ u32 len;
+ u32 len_enc_pdu; /* Length of Encapsulated PDU */
+ byte rest[]; /* Copy of Erroneous PDU
+ * Length of Error Text
+ * Error Diagnostic Message */
+} PACKED;
+
+struct pdu_reset_query {
+ u8 ver;
+ u8 type;
+ u16 flags;
+ u32 len;
+} PACKED;
+
+struct pdu_end_of_data_v0 {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+} PACKED;
+
+struct pdu_end_of_data_v1 {
+ u8 ver;
+ u8 type;
+ u16 session_id;
+ u32 len;
+ u32 serial_num;
+ u32 refresh_interval;
+ u32 retry_interval;
+ u32 expire_interval;
+} PACKED;
+
+static const size_t min_pdu_size[] = {
+ [SERIAL_NOTIFY] = sizeof(struct pdu_serial_notify),
+ [SERIAL_QUERY] = sizeof(struct pdu_serial_query),
+ [RESET_QUERY] = sizeof(struct pdu_reset_query),
+ [CACHE_RESPONSE] = sizeof(struct pdu_cache_response),
+ [IPV4_PREFIX] = sizeof(struct pdu_ipv4),
+ [RESERVED] = sizeof(struct pdu_header),
+ [IPV6_PREFIX] = sizeof(struct pdu_ipv6),
+ [END_OF_DATA] = sizeof(struct pdu_end_of_data_v0),
+ [CACHE_RESET] = sizeof(struct pdu_cache_response),
+ [ROUTER_KEY] = sizeof(struct pdu_header), /* FIXME */
+ [ERROR] = 16,
+};
+
+static int rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...);
+
+static void
+rpki_pdu_to_network_byte_order(struct pdu_header *pdu)
+{
+ pdu->reserved = htons(pdu->reserved);
+ pdu->len = htonl(pdu->len);
+
+ switch (pdu->type)
+ {
+ case SERIAL_QUERY:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_serial_query *sq_pdu = (void *) pdu;
+ sq_pdu->serial_num = htonl(sq_pdu->serial_num);
+ break;
+ }
+
+ case ERROR:
+ {
+ struct pdu_error *err = (void *) pdu;
+ u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu);
+ *err_text_len = htonl(*err_text_len);
+ err->len_enc_pdu = htonl(err->len_enc_pdu);
+ break;
+ }
+
+ case RESET_QUERY:
+ break;
+
+ default:
+ bug("PDU type %s should not be sent by us", str_pdu_type(pdu->type));
+ }
+}
+
+static void
+rpki_pdu_to_host_byte_order(struct pdu_header *pdu)
+{
+ /* The Router Key PDU has two one-byte fields instead of one two-bytes field. */
+ if (pdu->type != ROUTER_KEY)
+ pdu->reserved = ntohs(pdu->reserved);
+
+ pdu->len = ntohl(pdu->len);
+
+ switch (pdu->type)
+ {
+ case SERIAL_NOTIFY:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_serial_notify *sn_pdu = (void *) pdu;
+ sn_pdu->serial_num = ntohl(sn_pdu->serial_num);
+ break;
+ }
+
+ case END_OF_DATA:
+ {
+ /* Note that a session_id is converted using converting header->reserved */
+ struct pdu_end_of_data_v0 *eod0 = (void *) pdu;
+ eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */
+
+ if (pdu->ver == RPKI_VERSION_1)
+ {
+ struct pdu_end_of_data_v1 *eod1 = (void *) pdu;
+ eod1->expire_interval = ntohl(eod1->expire_interval);
+ eod1->refresh_interval = ntohl(eod1->refresh_interval);
+ eod1->retry_interval = ntohl(eod1->retry_interval);
+ }
+ break;
+ }
+
+ case IPV4_PREFIX:
+ {
+ struct pdu_ipv4 *ipv4 = (void *) pdu;
+ ipv4->prefix = ip4_ntoh(ipv4->prefix);
+ ipv4->asn = ntohl(ipv4->asn);
+ break;
+ }
+
+ case IPV6_PREFIX:
+ {
+ struct pdu_ipv6 *ipv6 = (void *) pdu;
+ ipv6->prefix = ip6_ntoh(ipv6->prefix);
+ ipv6->asn = ntohl(ipv6->asn);
+ break;
+ }
+
+ case ERROR:
+ {
+ /* Note that a error_code is converted using converting header->reserved */
+ struct pdu_error *err = (void *) pdu;
+ err->len_enc_pdu = ntohl(err->len_enc_pdu);
+ u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu);
+ *err_text_len = htonl(*err_text_len);
+ break;
+ }
+
+ case ROUTER_KEY:
+ /* Router Key PDU is not supported yet */
+
+ case SERIAL_QUERY:
+ case RESET_QUERY:
+ /* Serial/Reset Query are sent only in direction router to cache.
+ * We don't care here. */
+
+ case CACHE_RESPONSE:
+ case CACHE_RESET:
+ /* Converted with pdu->reserved */
+ break;
+ }
+}
+
+/**
+ * rpki_convert_pdu_back_to_network_byte_order - convert host-byte order PDU back to network-byte order
+ * @out: allocated memory for writing a converted PDU of size @in->len
+ * @in: host-byte order PDU
+ *
+ * Assumed: |A == ntoh(ntoh(A))|
+ */
+static struct pdu_header *
+rpki_pdu_back_to_network_byte_order(struct pdu_header *out, const struct pdu_header *in)
+{
+ memcpy(out, in, in->len);
+ rpki_pdu_to_host_byte_order(out);
+ return out;
+}
+
+static void
+rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const enum rpki_transmit_type action)
+{
+ if (!(cache->p->p.debug & D_PACKETS))
+ return;
+
+ const char *str_type = str_pdu_type(pdu->type);
+ char detail[256];
+
+#define SAVE(fn) \
+ do { \
+ if (fn < 0) \
+ { \
+ bsnprintf(detail + sizeof(detail) - 16, 16, "... )"); \
+ goto detail_finished; \
+ } \
+ } while(0) \
+
+ switch (pdu->type)
+ {
+ case SERIAL_NOTIFY:
+ case SERIAL_QUERY:
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", pdu->reserved, ((struct pdu_serial_notify *) pdu)->serial_num));
+ break;
+
+ case END_OF_DATA:
+ {
+ const struct pdu_end_of_data_v1 *eod = (void *) pdu;
+ if (eod->ver == RPKI_VERSION_1)
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u, refresh: %us, retry: %us, expire: %us)", eod->session_id, eod->serial_num, eod->refresh_interval, eod->retry_interval, eod->expire_interval));
+ else
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", eod->session_id, eod->serial_num));
+ break;
+ }
+
+ case CACHE_RESPONSE:
+ SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u)", pdu->reserved));
+ break;
+
+ case IPV4_PREFIX:
+ {
+ const struct pdu_ipv4 *ipv4 = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ipv4->prefix, ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn));
+ break;
+ }
+
+ case IPV6_PREFIX:
+ {
+ const struct pdu_ipv6 *ipv6 = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", ipv6->prefix, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn));
+ break;
+ }
+
+ case ROUTER_KEY:
+ /* We don't support saving Router Key PDUs yet */
+ SAVE(bsnprintf(detail, sizeof(detail), "(ignored)"));
+ break;
+
+ case ERROR:
+ {
+ const struct pdu_error *err = (void *) pdu;
+ SAVE(bsnprintf(detail, sizeof(detail), "(%s", str_pdu_error_type[err->error_code]));
+
+ /* Optional description of error */
+ const u32 len_err_txt = *((u32 *) (err->rest + err->len_enc_pdu));
+ if (len_err_txt > 0)
+ {
+ size_t expected_len = err->len_enc_pdu + len_err_txt + 16;
+ if (expected_len == err->len)
+ {
+ char txt[len_err_txt + 1];
+ char *pdu_txt = (char *) err->rest + err->len_enc_pdu + 4;
+ bsnprintf(txt, sizeof(txt), "%s", pdu_txt); /* it's ensured that txt is ended with a null byte */
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ": '%s'", txt));
+ }
+ else
+ {
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", malformed size"));
+ }
+ }
+
+ /* Optional encapsulated erroneous packet */
+ if (err->len_enc_pdu)
+ {
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", %s packet:", str_pdu_type(((struct pdu_header *) err->rest)->type)));
+ if (err->rest + err->len_enc_pdu <= (byte *)err + err->len)
+ {
+ for (const byte *c = err->rest; c != err->rest + err->len_enc_pdu; c++)
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), " %02X", *c));
+ }
+ }
+
+ SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ")"));
+ break;
+ }
+
+ default:
+ *detail = '\0';
+ }
+#undef SAVE
+
+ detail_finished:
+
+ if (action == RPKI_RECV)
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Received %s packet %s", str_type, detail);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Sending %s packet %s", str_type, detail);
+ }
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+ int seq = 0;
+ for(const byte *c = pdu; c != pdu + pdu->len; c++)
+ {
+ if ((seq % 4) == 0)
+ DBG("%2d: ", seq);
+
+ DBG(" 0x%02X %-3u", *c, *c);
+
+ if ((++seq % 4) == 0)
+ DBG("\n");
+ }
+ if ((seq % 4) != 0)
+ DBG("\n");
+#endif
+}
+
+static int
+rpki_send_pdu(struct rpki_cache *cache, const void *pdu, const uint len)
+{
+ struct rpki_proto *p = cache->p;
+ sock *sk = cache->tr_sock->sk;
+
+ rpki_log_packet(cache, pdu, RPKI_SEND);
+
+ if (sk->tbuf != sk->tpos)
+ {
+ RPKI_WARN(p, "Old packet overwritten in TX buffer");
+ }
+
+ if (len > sk->tbsize)
+ {
+ RPKI_WARN(p, "%u bytes is too much for send", len);
+ ASSERT(0);
+ return RPKI_ERROR;
+ }
+
+ memcpy(sk->tbuf, pdu, len);
+ rpki_pdu_to_network_byte_order((void *) sk->tbuf);
+
+ if (!sk_send(sk, len))
+ {
+ DBG("Cannot send just the whole data. It will be sent using a call of tx_hook()");
+ }
+
+ return RPKI_SUCCESS;
+}
+
+/**
+ * rpki_check_receive_packet - make a basic validation of received RPKI PDU header
+ * @cache: cache connection instance
+ * @pdu: RPKI PDU in network byte order
+ *
+ * This function checks protocol version, PDU type, version and size. If all is all right then
+ * function returns |RPKI_SUCCESS| otherwise sends Error PDU and returns
+ * |RPKI_ERROR|.
+ */
+static int
+rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu)
+{
+ struct rpki_proto *p = cache->p;
+ int error = RPKI_SUCCESS;
+ u32 pdu_len = ntohl(pdu->len);
+
+ /*
+ * Minimal and maximal allowed PDU size is treated in rpki_rx_hook() function.
+ * @header.len corresponds to number of bytes of @pdu and
+ * it is in range from RPKI_PDU_HEADER_LEN to RPKI_PDU_MAX_LEN bytes.
+ */
+
+ /* Do not handle error PDUs here, leave this task to rpki_handle_error_pdu() */
+ if (pdu->ver != cache->version && pdu->type != ERROR)
+ {
+ /* If this is the first PDU we have received */
+ if (cache->request_session_id)
+ {
+ if (pdu->type == SERIAL_NOTIFY)
+ {
+ /*
+ * The router MUST ignore any Serial Notify PDUs it might receive from
+ * the cache during this initial start-up period, regardless of the
+ * Protocol Version field in the Serial Notify PDU.
+ * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7)
+ */
+ }
+ else if (cache->last_update == 0
+ && pdu->ver >= RPKI_MIN_VERSION
+ && pdu->ver <= RPKI_MAX_VERSION
+ && pdu->ver < cache->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", rpki_get_cache_ident(cache), cache->version, pdu->ver);
+ cache->version = pdu->ver;
+ }
+ else
+ {
+ /* If this is not the first PDU we have received, something is wrong with
+ * the server implementation -> Error */
+ rpki_send_error_pdu(cache, UNSUPPORTED_PROTOCOL_VER, pdu_len, pdu, "PDU with unsupported Protocol version received");
+ return RPKI_ERROR;
+ }
+ }
+ }
+
+ if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY))
+ {
+ rpki_send_error_pdu(cache, UNSUPPORTED_PDU_TYPE, pdu_len, pdu, "Unsupported PDU type %u received", pdu->type);
+ return RPKI_ERROR;
+ }
+
+ if (pdu_len < min_pdu_size[pdu->type])
+ {
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu_len, pdu, "Received %s packet with %d bytes, but expected at least %d bytes", str_pdu_type(pdu->type), pdu_len, min_pdu_size[pdu->type]);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+static int
+rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu)
+{
+ switch (pdu->error_code)
+ {
+ case CORRUPT_DATA:
+ case INTERNAL_ERROR:
+ case INVALID_REQUEST:
+ case UNSUPPORTED_PDU_TYPE:
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ break;
+
+ case NO_DATA_AVAIL:
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_DATA_AVAIL);
+ break;
+
+ case UNSUPPORTED_PROTOCOL_VER:
+ CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version");
+ if (pdu->ver <= RPKI_MAX_VERSION &&
+ pdu->ver >= RPKI_MIN_VERSION &&
+ pdu->ver < cache->version)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %d to version %d", cache->version, pdu->ver);
+ cache->version = pdu->ver;
+ rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+ }
+ else
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \
+ "current version: %d, PDU version: %d", cache->version, pdu->ver);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ }
+ break;
+
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "Error unknown, server sent unsupported error code %u", pdu->error_code);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ break;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+static void
+rpki_handle_serial_notify_pdu(struct rpki_cache *cache, const struct pdu_serial_notify *pdu)
+{
+ /* The router MUST ignore any Serial Notify PDUs it might receive from
+ * the cache during this initial start-up period, regardless of the
+ * Protocol Version field in the Serial Notify PDU.
+ * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7)
+ */
+ if (cache->request_session_id)
+ {
+ CACHE_TRACE(D_PACKETS, cache, "Ignore a Serial Notify packet during initial start-up period");
+ return;
+ }
+
+ /* XXX Serial number should be compared using method RFC 1982 (3.2) */
+ if (cache->serial_num != pdu->serial_num)
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+}
+
+static int
+rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_response *pdu)
+{
+ if (cache->request_session_id)
+ {
+ if (cache->last_update != 0)
+ {
+ /*
+ * This isn't the first sync and we already received records. This point
+ * is after Reset Query and before importing new records from cache
+ * server. We need to load new ones and kick out missing ones. So start
+ * a refresh cycle.
+ */
+ if (cache->p->roa4_channel)
+ rt_refresh_begin(cache->p->roa4_channel->table, cache->p->roa4_channel);
+ if (cache->p->roa6_channel)
+ rt_refresh_begin(cache->p->roa6_channel->table, cache->p->roa6_channel);
+
+ cache->p->refresh_channels = 1;
+ }
+ cache->session_id = pdu->session_id;
+ cache->request_session_id = 0;
+ }
+ else
+ {
+ if (cache->session_id != pdu->session_id)
+ {
+ byte tmp[pdu->len];
+ const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Wrong session_id %u in Cache Response PDU", pdu->session_id);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return RPKI_ERROR;
+ }
+ }
+
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_RUNNING);
+ return RPKI_SUCCESS;
+}
+
+/**
+ * rpki_prefix_pdu_2_net_addr - convert IPv4/IPv6 Prefix PDU into net_addr_union
+ * @pdu: host byte order IPv4/IPv6 Prefix PDU
+ * @n: allocated net_addr_union for save ROA
+ *
+ * This function reads ROA data from IPv4/IPv6 Prefix PDU and
+ * write them into net_addr_roa4 or net_addr_roa6 data structure.
+ */
+static net_addr_union *
+rpki_prefix_pdu_2_net_addr(const struct pdu_header *pdu, net_addr_union *n)
+{
+ /*
+ * Note that sizeof(net_addr_roa6) > sizeof(net_addr)
+ * and thence we must use net_addr_union and not only net_addr
+ */
+
+ if (pdu->type == IPV4_PREFIX)
+ {
+ const struct pdu_ipv4 *ipv4 = (void *) pdu;
+ n->roa4.type = NET_ROA4;
+ n->roa4.length = sizeof(net_addr_roa4);
+ n->roa4.prefix = ipv4->prefix;
+ n->roa4.asn = ipv4->asn;
+ n->roa4.pxlen = ipv4->prefix_len;
+ n->roa4.max_pxlen = ipv4->max_prefix_len;
+ }
+ else
+ {
+ const struct pdu_ipv6 *ipv6 = (void *) pdu;
+ n->roa6.type = NET_ROA6;
+ n->roa6.length = sizeof(net_addr_roa6);
+ n->roa6.prefix = ipv6->prefix;
+ n->roa6.asn = ipv6->asn;
+ n->roa6.pxlen = ipv6->prefix_len;
+ n->roa6.max_pxlen = ipv6->max_prefix_len;
+ }
+
+ return n;
+}
+
+static int
+rpki_handle_prefix_pdu(struct rpki_cache *cache, const struct pdu_header *pdu)
+{
+ const enum pdu_type type = pdu->type;
+ ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+ net_addr_union addr = {};
+ rpki_prefix_pdu_2_net_addr(pdu, &addr);
+
+ struct channel *channel = NULL;
+
+ if (type == IPV4_PREFIX)
+ channel = cache->p->roa4_channel;
+ if (type == IPV6_PREFIX)
+ channel = cache->p->roa6_channel;
+
+ if (!channel)
+ {
+ CACHE_TRACE(D_ROUTES, cache, "Skip %N, missing %s channel", &addr, (type == IPV4_PREFIX ? "roa4" : "roa6"), addr);
+ return RPKI_ERROR;
+ }
+
+ cache->last_rx_prefix = now;
+
+ /* A place for 'flags' is same for both data structures pdu_ipv4 or pdu_ipv6 */
+ struct pdu_ipv4 *pfx = (void *) pdu;
+ if (pfx->flags & RPKI_ADD_FLAG)
+ rpki_table_add_roa(cache, channel, &addr);
+ else
+ rpki_table_remove_roa(cache, channel, &addr);
+
+ return RPKI_SUCCESS;
+}
+
+static uint
+rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval)
+{
+ if (check_fn(interval))
+ {
+ RPKI_WARN(cache->p, "%s, received %u seconds", check_fn(interval), interval);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_data_v1 *pdu)
+{
+ const struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ if (pdu->session_id != cache->session_id)
+ {
+ byte tmp[pdu->len];
+ const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+ rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Received Session ID %u, but expected %u", pdu->session_id, cache->session_id);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return;
+ }
+
+ if (pdu->ver == RPKI_VERSION_1)
+ {
+ if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval))
+ cache->refresh_interval = pdu->refresh_interval;
+
+ if (!cf->keep_retry_interval && rpki_check_interval(cache, rpki_check_retry_interval, pdu->retry_interval))
+ cache->retry_interval = pdu->retry_interval;
+
+ if (!cf->keep_expire_interval && rpki_check_interval(cache, rpki_check_expire_interval, pdu->expire_interval))
+ cache->expire_interval = pdu->expire_interval;
+
+ CACHE_TRACE(D_EVENTS, cache, "New interval values: "
+ "refresh: %s%us, "
+ "retry: %s%us, "
+ "expire: %s%us",
+ (cf->keep_refresh_interval ? "keeps " : ""), cache->refresh_interval,
+ (cf->keep_retry_interval ? "keeps " : ""), cache->retry_interval,
+ (cf->keep_expire_interval ? "keeps " : ""), cache->expire_interval);
+ }
+
+ if (cache->p->refresh_channels)
+ {
+ cache->p->refresh_channels = 0;
+ if (cache->p->roa4_channel)
+ rt_refresh_end(cache->p->roa4_channel->table, cache->p->roa4_channel);
+ if (cache->p->roa6_channel)
+ rt_refresh_end(cache->p->roa6_channel->table, cache->p->roa6_channel);
+ }
+
+ cache->last_update = now;
+ cache->serial_num = pdu->serial_num;
+ rpki_cache_change_state(cache, RPKI_CS_ESTABLISHED);
+}
+
+/**
+ * rpki_rx_packet - process a received RPKI PDU
+ * @cache: RPKI connection instance
+ * @pdu: a RPKI PDU in network byte order
+ */
+static void
+rpki_rx_packet(struct rpki_cache *cache, struct pdu_header *pdu)
+{
+ struct rpki_proto *p = cache->p;
+
+ if (rpki_check_receive_packet(cache, pdu) == RPKI_ERROR)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+ return;
+ }
+
+ rpki_pdu_to_host_byte_order(pdu);
+ rpki_log_packet(cache, pdu, RPKI_RECV);
+
+ switch (pdu->type)
+ {
+ case RESET_QUERY:
+ case SERIAL_QUERY:
+ RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type(pdu->type));
+ break;
+
+ case SERIAL_NOTIFY:
+ /* This is a signal to synchronize with the cache server just now */
+ rpki_handle_serial_notify_pdu(cache, (void *) pdu);
+ break;
+
+ case CACHE_RESPONSE:
+ rpki_handle_cache_response_pdu(cache, (void *) pdu);
+ break;
+
+ case IPV4_PREFIX:
+ case IPV6_PREFIX:
+ rpki_handle_prefix_pdu(cache, pdu);
+ break;
+
+ case END_OF_DATA:
+ rpki_handle_end_of_data_pdu(cache, (void *) pdu);
+ break;
+
+ case CACHE_RESET:
+ /* Cache cannot provide an incremental update. */
+ rpki_cache_change_state(cache, RPKI_CS_NO_INCR_UPDATE_AVAIL);
+ break;
+
+ case ERROR:
+ rpki_handle_error_pdu(cache, (void *) pdu);
+ break;
+
+ case ROUTER_KEY:
+ /* TODO: Implement Router Key PDU handling */
+ break;
+
+ default:
+ CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type);
+ };
+}
+
+int
+rpki_rx_hook(struct birdsock *sk, 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);
+
+ while (end >= pkt_start + RPKI_PDU_HEADER_LEN)
+ {
+ struct pdu_header *pdu = (void *) pkt_start;
+ u32 pdu_size = ntohl(pdu->len);
+
+ if (pdu_size < RPKI_PDU_HEADER_LEN || pdu_size > RPKI_PDU_MAX_LEN)
+ {
+ RPKI_WARN(p, "Received invalid packet length %u, purge the whole receiving buffer", pdu_size);
+ return 1; /* Purge recv buffer */
+ }
+
+ if (end < pkt_start + pdu_size)
+ break;
+
+ rpki_rx_packet(cache, pdu);
+
+ /* It is possible that bird socket was freed/closed */
+ if (p->p.proto_state == PS_DOWN || sk != cache->tr_sock->sk)
+ return 0;
+
+ pkt_start += pdu_size;
+ }
+
+ if (pkt_start != sk->rbuf)
+ {
+ CACHE_DBG(cache, "Move %u bytes of a memory at the start of buffer", end - pkt_start);
+ memmove(sk->rbuf, pkt_start, end - pkt_start);
+ sk->rpos = sk->rbuf + (end - pkt_start);
+ }
+
+ return 0; /* Not purge sk->rbuf */
+}
+
+void
+rpki_err_hook(struct birdsock *sk, int error_num)
+{
+ struct rpki_cache *cache = sk->data;
+
+ if (error_num)
+ {
+ /* sk->err may contains a SSH error description */
+ if (sk->err)
+ CACHE_TRACE(D_EVENTS, cache, "Lost connection: %s", sk->err);
+ else
+ CACHE_TRACE(D_EVENTS, cache, "Lost connection: %M", error_num);
+ }
+ else
+ {
+ CACHE_TRACE(D_EVENTS, cache, "The other side closed a connection");
+ }
+
+
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+}
+
+static int
+rpki_fire_tx(struct rpki_cache *cache)
+{
+ sock *sk = cache->tr_sock->sk;
+
+ uint bytes_to_send = sk->tpos - sk->tbuf;
+ DBG("Sending %u bytes", bytes_to_send);
+ return sk_send(sk, bytes_to_send);
+}
+
+void
+rpki_tx_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+
+ while (rpki_fire_tx(cache) > 0)
+ ;
+}
+
+void
+rpki_connected_hook(sock *sk)
+{
+ struct rpki_cache *cache = sk->data;
+
+ CACHE_TRACE(D_EVENTS, cache, "Connected");
+ proto_notify_state(&cache->p->p, PS_UP);
+
+ sk->rx_hook = rpki_rx_hook;
+ sk->tx_hook = rpki_tx_hook;
+
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+}
+
+/**
+ * rpki_send_error_pdu - send RPKI Error PDU
+ * @cache: RPKI connection instance
+ * @error_code: PDU Error type
+ * @err_pdu_len: length of @erroneous_pdu
+ * @erroneous_pdu: optional network byte-order PDU that invokes Error by us or NULL
+ * @fmt: optional description text of error or NULL
+ * @args: optional arguments for @fmt
+ *
+ * This function prepares Error PDU and sends it to a cache server.
+ */
+static int
+rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...)
+{
+ va_list args;
+ char msg[128];
+
+ /* Size including the terminating null byte ('\0') */
+ int msg_len = 0;
+
+ /* Don't send errors for erroneous error PDUs */
+ if (err_pdu_len >= 2)
+ {
+ if (erroneous_pdu->type == ERROR)
+ return RPKI_SUCCESS;
+ }
+
+ if (fmt)
+ {
+ va_start(args, fmt);
+ msg_len = bvsnprintf(msg, sizeof(msg), fmt, args) + 1;
+ }
+
+ u32 pdu_size = 16 + err_pdu_len + msg_len;
+ byte pdu[pdu_size];
+ memset(pdu, 0, sizeof(pdu));
+
+ struct pdu_error *e = (void *) pdu;
+ e->ver = cache->version;
+ e->type = ERROR;
+ e->error_code = error_code;
+ e->len = pdu_size;
+
+ e->len_enc_pdu = err_pdu_len;
+ if (err_pdu_len > 0)
+ memcpy(e->rest, erroneous_pdu, err_pdu_len);
+
+ *((u32 *)(e->rest + err_pdu_len)) = msg_len;
+ if (msg_len > 0)
+ memcpy(e->rest + err_pdu_len + 4, msg, msg_len);
+
+ return rpki_send_pdu(cache, pdu, pdu_size);
+}
+
+int
+rpki_send_serial_query(struct rpki_cache *cache)
+{
+ struct pdu_serial_query pdu = {
+ .ver = cache->version,
+ .type = SERIAL_QUERY,
+ .session_id = cache->session_id,
+ .len = sizeof(pdu),
+ .serial_num = cache->serial_num
+ };
+
+ if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
+
+int
+rpki_send_reset_query(struct rpki_cache *cache)
+{
+ struct pdu_reset_query pdu = {
+ .ver = cache->version,
+ .type = RESET_QUERY,
+ .len = sizeof(pdu),
+ };
+
+ if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_ERROR;
+ }
+
+ return RPKI_SUCCESS;
+}
diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h
new file mode 100644
index 00000000..d2b180bd
--- /dev/null
+++ b/proto/rpki/packets.h
@@ -0,0 +1,45 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * 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
+
+#define RPKI_PDU_HEADER_LEN 8
+
+/* A Error PDU size is the biggest (has encapsulate PDU inside):
+ * +8 bytes (Header size)
+ * +4 bytes (Length of Encapsulated PDU)
+ * +32 bytes (Encapsulated PDU IPv6 32)
+ * +4 bytes (Length of inserted text)
+ * +800 bytes (UTF-8 text 400*2 bytes)
+ * ------------
+ * = 848 bytes (Maximal expected PDU size) */
+#define RPKI_PDU_MAX_LEN 848
+
+/* RX buffer size has a great impact to scheduler granularity */
+#define RPKI_RX_BUFFER_SIZE 4096
+#define RPKI_TX_BUFFER_SIZE RPKI_PDU_MAX_LEN
+
+/* Return values */
+enum rpki_rtvals {
+ RPKI_SUCCESS = 0,
+ RPKI_ERROR = -1
+};
+
+int rpki_send_serial_query(struct rpki_cache *cache);
+int rpki_send_reset_query(struct rpki_cache *cache);
+int rpki_rx_hook(sock *sk, int size);
+void rpki_connected_hook(sock *sk);
+void rpki_err_hook(sock *sk, int size);
+
+#endif
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
new file mode 100644
index 00000000..6360dbaf
--- /dev/null
+++ b/proto/rpki/rpki.c
@@ -0,0 +1,928 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * Using RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: RPKI To Router (RPKI-RTR)
+ *
+ * The RPKI-RTR protocol is implemented in several files: |rpki.c| containing
+ * the routes handling, protocol logic, timer events, cache connection,
+ * reconfiguration, configuration and protocol glue with BIRD core, |packets.c|
+ * containing the RPKI packets handling and finally all transports files:
+ * |transport.c|, |tcp_transport.c| and |ssh_transport.c|.
+ *
+ * The |transport.c| is a middle layer and interface for each specific
+ * transport. Transport is a way how to wrap a communication with a cache
+ * server. There is supported an unprotected TCP transport and an encrypted
+ * SSHv2 transport. The SSH transport requires LibSSH library. LibSSH is
+ * loading dynamically using |dlopen()| function. SSH support is integrated in
+ * |sysdep/unix/io.c|. Each transport must implement an initialization
+ * function, an open function and a socket identification function. That's all.
+ *
+ * This implementation is based on the RTRlib (http://rpki.realmv6.org/). The
+ * BIRD takes over files |packets.c|, |rtr.c| (inside |rpki.c|), |transport.c|,
+ * |tcp_transport.c| and |ssh_transport.c| from RTRlib.
+ *
+ * A RPKI-RTR connection is described by a structure &rpki_cache. The main
+ * logic is located in |rpki_cache_change_state()| function. There is a state
+ * machine. The standard starting state flow looks like |Down| ~> |Connecting|
+ * ~> |Sync-Start| ~> |Sync-Running| ~> |Established| and then the last three
+ * states are periodically repeated.
+ *
+ * |Connecting| state establishes the transport connection. The state from a
+ * call |rpki_cache_change_state(CONNECTING)| to a call |rpki_connected_hook()|
+ *
+ * |Sync-Start| state starts with sending |Reset Query| or |Serial Query| and
+ * then waits for |Cache Response|. The state from |rpki_connected_hook()| to
+ * |rpki_handle_cache_response_pdu()|
+ *
+ * During |Sync-Running| BIRD receives data with IPv4/IPv6 Prefixes from cache
+ * server. The state starts from |rpki_handle_cache_response_pdu()| and ends
+ * in |rpki_handle_end_of_data_pdu()|.
+ *
+ * |Established| state means that BIRD has synced all data with cache server.
+ * Schedules a refresh timer event that invokes |Sync-Start|. Schedules Expire
+ * timer event and stops a Retry timer event.
+ *
+ * |Transport Error| state means that we have some troubles with a network
+ * connection. We cannot connect to a cache server or we wait too long for some
+ * expected PDU for received - |Cache Response| or |End of Data|. It closes
+ * current connection and schedules a Retry timer event.
+ *
+ * |Fatal Protocol Error| is occurred e.g. by received a bad Session ID. We
+ * restart a protocol, so all ROAs are flushed immediately.
+ *
+ * The RPKI-RTR protocol (RFC 6810 bis) defines configurable refresh, retry and
+ * expire intervals. For maintaining a connection are used timer events that
+ * are scheduled by |rpki_schedule_next_refresh()|,
+ * |rpki_schedule_next_retry()| and |rpki_schedule_next_expire()| functions.
+ *
+ * A Refresh timer event performs a sync of |Established| connection. So it
+ * shifts state to |Sync-Start|. If at the beginning of second call of a
+ * refresh event is connection in |Sync-Start| state then we didn't receive a
+ * |Cache Response| from a cache server and we invoke |Transport Error| state.
+ *
+ * A Retry timer event attempts to connect cache server. It is activated after
+ * |Transport Error| state and terminated by reaching |Established| state.
+ * If cache connection is still connecting to the cache server at the beginning
+ * of an event call then the Retry timer event invokes |Transport Error| state.
+ *
+ * An Expire timer event checks expiration of ROAs. If a last successful sync
+ * was more ago than the expire interval then the Expire timer event invokes a
+ * protocol restart thereby removes all ROAs learned from that cache server and
+ * continue trying to connect to cache server. The Expire event is activated
+ * by initial successful loading of ROAs, receiving End of Data PDU.
+ *
+ * A reconfiguration of cache connection works well without restarting when we
+ * change only intervals values.
+ *
+ * Supported standards:
+ * - RFC 6810 - main RPKI-RTR standard
+ * - RFC 6810 bis - an explicit timing parameters and protocol version number negotiation
+ */
+
+#include
+#include
+
+#undef LOCAL_DEBUG
+
+#include "rpki.h"
+#include "lib/string.h"
+#include "nest/cli.h"
+
+/* Return values for reconfiguration functions */
+#define NEED_RESTART 0
+#define SUCCESSFUL_RECONF 1
+
+static int rpki_open_connection(struct rpki_cache *cache);
+static void rpki_close_connection(struct rpki_cache *cache);
+static void rpki_schedule_next_refresh(struct rpki_cache *cache);
+static void rpki_schedule_next_retry(struct rpki_cache *cache);
+static void rpki_schedule_next_expire_check(struct rpki_cache *cache);
+static void rpki_stop_refresh_timer_event(struct rpki_cache *cache);
+static void rpki_stop_retry_timer_event(struct rpki_cache *cache);
+static void rpki_stop_expire_timer_event(struct rpki_cache *cache);
+
+
+/*
+ * Routes handling
+ */
+
+void
+rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+ struct rpki_proto *p = cache->p;
+
+ rta a0 = {
+ .src = p->p.main_source,
+ .source = RTS_RPKI,
+ .scope = SCOPE_UNIVERSE,
+ .cast = RTC_UNICAST,
+ .dest = RTD_BLACKHOLE,
+ };
+
+ rta *a = rta_lookup(&a0);
+ rte *e = rte_get_temp(a);
+
+ e->pflags = 0;
+
+ rte_update2(channel, &pfxr->n, e, a0.src);
+}
+
+void
+rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+{
+ struct rpki_proto *p = cache->p;
+ rte_update2(channel, &pfxr->n, NULL, p->p.main_source);
+}
+
+
+/*
+ * RPKI Protocol Logic
+ */
+
+static const char *str_cache_states[] = {
+ [RPKI_CS_CONNECTING] = "Connecting",
+ [RPKI_CS_ESTABLISHED] = "Established",
+ [RPKI_CS_RESET] = "Reseting",
+ [RPKI_CS_SYNC_START] = "Sync-Start",
+ [RPKI_CS_SYNC_RUNNING] = "Sync-Running",
+ [RPKI_CS_FAST_RECONNECT] = "Fast-Reconnect",
+ [RPKI_CS_NO_INCR_UPDATE_AVAIL]= "No-Increment-Update-Available",
+ [RPKI_CS_ERROR_NO_DATA_AVAIL] = "Cache-Error-No-Data-Available",
+ [RPKI_CS_ERROR_FATAL] = "Fatal-Protocol-Error",
+ [RPKI_CS_ERROR_TRANSPORT] = "Transport-Error",
+ [RPKI_CS_SHUTDOWN] = "Down"
+};
+
+/**
+ * rpki_cache_state_to_str - give a text representation of cache state
+ * @state: A cache state
+ *
+ * The function converts logic cache state into string.
+ */
+const char *
+rpki_cache_state_to_str(enum rpki_cache_state state)
+{
+ return str_cache_states[state];
+}
+
+/**
+ * rpki_start_cache - connect to a cache server
+ * @cache: RPKI connection instance
+ *
+ * This function is a high level method to kick up a connection to a cache server.
+ */
+static void
+rpki_start_cache(struct rpki_cache *cache)
+{
+ rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+}
+
+/**
+ * rpki_force_restart_proto - force shutdown and start protocol again
+ * @p: RPKI protocol instance
+ *
+ * This function calls shutdown and frees all protocol resources as well.
+ * After calling this function should be no operations with protocol data,
+ * they could be freed already.
+ */
+static void
+rpki_force_restart_proto(struct rpki_proto *p)
+{
+ if (p->cache)
+ {
+ CACHE_DBG(p->cache, "Connection object destroying");
+ }
+
+ /* Sign as freed */
+ p->cache = NULL;
+
+ proto_notify_state(&p->p, PS_DOWN);
+}
+
+/**
+ * rpki_cache_change_state - check and change cache state
+ * @cache: RPKI cache instance
+ * @new_state: suggested new state
+ *
+ * This function makes transitions between internal states.
+ * It represents the core of logic management of RPKI protocol.
+ * Cannot transit into the same state as cache is in already.
+ */
+void
+rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state)
+{
+ const enum rpki_cache_state old_state = cache->state;
+
+ if (old_state == new_state)
+ return;
+
+ cache->state = new_state;
+ CACHE_TRACE(D_EVENTS, cache, "Changing from %s to %s state", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state));
+
+ switch (new_state)
+ {
+ case RPKI_CS_CONNECTING:
+ {
+ sock *sk = cache->tr_sock->sk;
+
+ if (sk == NULL || sk->fd < 0)
+ rpki_open_connection(cache);
+ else
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+
+ rpki_schedule_next_retry(cache);
+ break;
+ }
+
+ case RPKI_CS_ESTABLISHED:
+ rpki_schedule_next_refresh(cache);
+ rpki_schedule_next_expire_check(cache);
+ rpki_stop_retry_timer_event(cache);
+ break;
+
+ case RPKI_CS_RESET:
+ /* Resetting cache connection. */
+ cache->request_session_id = 1;
+ cache->serial_num = 0;
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+ break;
+
+ case RPKI_CS_SYNC_START:
+ /* Requesting for receive ROAs from a cache server. */
+ if (cache->request_session_id)
+ {
+ /* Send request for Session ID */
+ if (rpki_send_reset_query(cache) != RPKI_SUCCESS)
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ else
+ {
+ /* We have already a session_id. So send a Serial Query and start an incremental sync */
+ if (rpki_send_serial_query(cache) != RPKI_SUCCESS)
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ case RPKI_CS_SYNC_RUNNING:
+ /* The state between Cache Response and End of Data. Only waiting for
+ * receiving all IP Prefix PDUs and finally a End of Data PDU. */
+ break;
+
+ case RPKI_CS_NO_INCR_UPDATE_AVAIL:
+ /* Server was unable to answer the last Serial Query and sent Cache Reset. */
+ rpki_cache_change_state(cache, RPKI_CS_RESET);
+ break;
+
+ case RPKI_CS_ERROR_NO_DATA_AVAIL:
+ /* No validation records are available on the cache server. */
+ rpki_cache_change_state(cache, RPKI_CS_RESET);
+ break;
+
+ case RPKI_CS_ERROR_FATAL:
+ /* Fatal protocol error occurred. */
+ rpki_force_restart_proto(cache->p);
+ break;
+
+ case RPKI_CS_ERROR_TRANSPORT:
+ /* Error on the transport socket occurred. */
+ rpki_close_connection(cache);
+ rpki_schedule_next_retry(cache);
+ rpki_stop_refresh_timer_event(cache);
+ break;
+
+ case RPKI_CS_FAST_RECONNECT:
+ /* Reconnect without any waiting period */
+ rpki_close_connection(cache);
+ rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+ break;
+
+ case RPKI_CS_SHUTDOWN:
+ bug("This isn't never really called.");
+ break;
+ };
+}
+
+
+/*
+ * RPKI Timer Events
+ */
+
+static void
+rpki_schedule_next_refresh(struct rpki_cache *cache)
+{
+ uint time_to_wait = cache->refresh_interval;
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->refresh_timer, time_to_wait);
+}
+
+static void
+rpki_schedule_next_retry(struct rpki_cache *cache)
+{
+ uint time_to_wait = cache->retry_interval;
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->retry_timer, time_to_wait);
+}
+
+static void
+rpki_schedule_next_expire_check(struct rpki_cache *cache)
+{
+ /* A minimum time to wait is 1 second */
+ uint time_to_wait = MAX(((int)cache->expire_interval - (int)(now - cache->last_update)), 1);
+
+ CACHE_DBG(cache, "after %u seconds", time_to_wait);
+ tm_start(cache->expire_timer, time_to_wait);
+}
+
+static void
+rpki_stop_refresh_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->refresh_timer);
+}
+
+static void
+rpki_stop_retry_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->retry_timer);
+}
+
+static void
+rpki_stop_expire_timer_event(struct rpki_cache *cache)
+{
+ CACHE_DBG(cache, "Stop");
+ tm_stop(cache->expire_timer);
+}
+
+static int
+rpki_do_we_recv_prefix_pdu_in_last_seconds(struct rpki_cache *cache)
+{
+ if (cache->last_rx_prefix == 0)
+ return 0;
+
+ return ((now - cache->last_rx_prefix) <= 2);
+}
+
+/**
+ * rpki_refresh_hook - control a scheduling of downloading data from cache server
+ * @tm: refresh timer with cache connection instance in data
+ *
+ * This function is periodically called during &ESTABLISHED or &SYNC* state
+ * cache connection. The first refresh schedule is invoked after receiving a
+ * |End of Data| PDU and has run by some &ERROR is occurred.
+ */
+static void
+rpki_refresh_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+
+ CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+ switch (cache->state)
+ {
+ case RPKI_CS_ESTABLISHED:
+ rpki_cache_change_state(cache, RPKI_CS_SYNC_START);
+ break;
+
+ case RPKI_CS_SYNC_START:
+ /* We sent Serial/Reset Query in last refresh hook call
+ * and didn't receive Cache Response yet. It is probably
+ * troubles with network. */
+ case RPKI_CS_SYNC_RUNNING:
+ /* We sent Serial/Reset Query in last refresh hook call
+ * and we got Cache Response but didn't get End-Of-Data yet.
+ * It could be a trouble with network or only too long synchronization. */
+ if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than refresh interval %us, resetting connection", cache->refresh_interval);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (cache->state != RPKI_CS_SHUTDOWN && cache->state != RPKI_CS_ERROR_TRANSPORT)
+ rpki_schedule_next_refresh(cache);
+ else
+ rpki_stop_refresh_timer_event(cache);
+}
+
+/**
+ * rpki_retry_hook - control a scheduling of retrying connection to cache server
+ * @tm: retry timer with cache connection instance in data
+ *
+ * This function is periodically called during &ERROR* state cache connection.
+ * The first retry schedule is invoked after any &ERROR* state occurred and
+ * ends by reaching of &ESTABLISHED state again.
+ */
+static void
+rpki_retry_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+
+ CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+ switch (cache->state)
+ {
+ case RPKI_CS_ESTABLISHED:
+ case RPKI_CS_SHUTDOWN:
+ break;
+
+ case RPKI_CS_CONNECTING:
+ case RPKI_CS_SYNC_START:
+ case RPKI_CS_SYNC_RUNNING:
+ if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache))
+ {
+ /* We tried to establish a connection in last retry hook call and haven't done
+ * yet. It looks like troubles with network. We are aggressive here. */
+ CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than retry interval %us, resetting connection.", cache->retry_interval);
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ }
+ break;
+
+ default:
+ rpki_cache_change_state(cache, RPKI_CS_CONNECTING);
+ break;
+ }
+
+ if (cache->state != RPKI_CS_ESTABLISHED)
+ rpki_schedule_next_retry(cache);
+ else
+ rpki_stop_retry_timer_event(cache);
+}
+
+/**
+ * rpki_expire_hook - control a expiration of ROA entries
+ * @tm: expire timer with cache connection instance in data
+ *
+ * This function is scheduled after received a |End of Data| PDU.
+ * A waiting interval is calculated dynamically by last update.
+ * If we reach an expiration time then we invoke a restarting
+ * of the protocol.
+ */
+static void
+rpki_expire_hook(struct timer *tm)
+{
+ struct rpki_cache *cache = tm->data;
+
+ if (cache->last_update == 0)
+ return;
+
+ CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
+
+ if ((cache->last_update + cache->expire_interval) < now)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "All ROAs expired");
+ rpki_force_restart_proto(cache->p);
+ }
+ else
+ {
+ CACHE_DBG(cache, "Remains %d seconds to become ROAs obsolete", (int)cache->expire_interval - (int)(now - cache->last_update));
+ rpki_schedule_next_expire_check(cache);
+ }
+}
+
+/**
+ * rpki_check_refresh_interval - check validity of refresh interval value
+ * @seconds: suggested value
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+const char *
+rpki_check_refresh_interval(uint seconds)
+{
+ if (seconds < 1)
+ return "Minimum allowed refresh interval is 1 second";
+ if (seconds > 86400)
+ return "Maximum allowed refresh interval is 86400 seconds";
+ return NULL;
+}
+
+/**
+ * rpki_check_retry_interval - check validity of retry interval value
+ * @seconds: suggested value
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+const char *
+rpki_check_retry_interval(uint seconds)
+{
+ if (seconds < 1)
+ return "Minimum allowed retry interval is 1 second";
+ if (seconds > 7200)
+ return "Maximum allowed retry interval is 7200 seconds";
+ return NULL;
+}
+
+/**
+ * rpki_check_expire_interval - check validity of expire interval value
+ * @seconds: suggested value
+ *
+ * This function validates value and should return |NULL|.
+ * If the check doesn't pass then returns error message.
+ */
+const char *
+rpki_check_expire_interval(uint seconds)
+{
+ if (seconds < 600)
+ return "Minimum allowed expire interval is 600 seconds";
+ if (seconds > 172800)
+ return "Maximum allowed expire interval is 172800 seconds";
+ return NULL;
+}
+
+
+/*
+ * RPKI Cache
+ */
+
+static struct rpki_cache *
+rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf)
+{
+ pool *pool = rp_new(p->p.pool, cf->hostname);
+
+ struct rpki_cache *cache = mb_allocz(pool, sizeof(struct rpki_cache));
+
+ cache->pool = pool;
+ cache->p = p;
+
+ cache->state = RPKI_CS_SHUTDOWN;
+ cache->request_session_id = 1;
+ cache->version = RPKI_MAX_VERSION;
+
+ cache->refresh_interval = cf->refresh_interval;
+ cache->retry_interval = cf->retry_interval;
+ cache->expire_interval = cf->expire_interval;
+ cache->refresh_timer = tm_new_set(pool, &rpki_refresh_hook, cache, 0, 0);
+ cache->retry_timer = tm_new_set(pool, &rpki_retry_hook, cache, 0, 0);
+ cache->expire_timer = tm_new_set(pool, &rpki_expire_hook, cache, 0, 0);
+
+ cache->tr_sock = mb_allocz(pool, sizeof(struct rpki_tr_sock));
+ cache->tr_sock->cache = cache;
+
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_TCP: rpki_tr_tcp_init(cache->tr_sock); break;
+ case RPKI_TR_SSH: rpki_tr_ssh_init(cache->tr_sock); break;
+ };
+
+ CACHE_DBG(cache, "Connection object created");
+
+ return cache;
+}
+
+/**
+ * rpki_get_cache_ident - give a text representation of cache server name
+ * @cache: RPKI connection instance
+ *
+ * The function converts cache connection into string.
+ */
+const char *
+rpki_get_cache_ident(struct rpki_cache *cache)
+{
+ return rpki_tr_ident(cache->tr_sock);
+}
+
+static int
+rpki_open_connection(struct rpki_cache *cache)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Opening a connection");
+
+ if (rpki_tr_open(cache->tr_sock) == RPKI_TR_ERROR)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
+ return RPKI_TR_ERROR;
+ }
+
+ return RPKI_TR_SUCCESS;
+}
+
+static void
+rpki_close_connection(struct rpki_cache *cache)
+{
+ CACHE_TRACE(D_EVENTS, cache, "Closing a connection");
+ rpki_tr_close(cache->tr_sock);
+ proto_notify_state(&cache->p->p, PS_START);
+}
+
+static int
+rpki_shutdown(struct proto *P)
+{
+ struct rpki_proto *p = (void *) P;
+
+ rpki_force_restart_proto(p);
+
+ /* Protocol memory pool will be automatically freed */
+ return PS_DOWN;
+}
+
+
+/*
+ * RPKI Reconfiguration
+ */
+
+static int
+rpki_try_fast_reconnect(struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+ if (cache->state == RPKI_CS_ESTABLISHED)
+ {
+ rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT);
+ return SUCCESSFUL_RECONF;
+ }
+
+ return NEED_RESTART;
+}
+
+/**
+ * rpki_reconfigure_cache - a cache reconfiguration
+ * @p: RPKI protocol instance
+ * @cache: a cache connection
+ * @new: new RPKI configuration
+ * @old: old RPKI configuration
+ *
+ * This function reconfigures existing single cache server connection with new
+ * existing configuration. Generally, a change of time intervals could be
+ * reconfigured without restarting and all others changes requires a restart of
+ * protocol. Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|.
+ */
+static int
+rpki_reconfigure_cache(struct rpki_proto *p, struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old)
+{
+ u8 try_fast_reconnect = 0;
+
+
+ if (strcmp(old->hostname, new->hostname) != 0)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cache server address changed to %s", new->hostname);
+ return NEED_RESTART;
+ }
+
+ if (old->port != new->port)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cache server port changed to %u", new->port);
+ return NEED_RESTART;
+ }
+
+ if (old->tr_config.type != new->tr_config.type)
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Transport type changed");
+ return NEED_RESTART;
+ }
+ else if (new->tr_config.type == RPKI_TR_SSH)
+ {
+ struct rpki_tr_ssh_config *ssh_old = (void *) old->tr_config.spec;
+ struct rpki_tr_ssh_config *ssh_new = (void *) new->tr_config.spec;
+ if ((strcmp(ssh_old->bird_private_key, ssh_new->bird_private_key) != 0) ||
+ (strcmp(ssh_old->cache_public_key, ssh_new->cache_public_key) != 0) ||
+ (strcmp(ssh_old->user, ssh_new->user) != 0))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Settings of SSH transport configuration changed");
+ try_fast_reconnect = 1;
+ }
+ }
+
+#define TEST_INTERVAL(name, Name) \
+ if (cache->name##_interval != new->name##_interval || \
+ old->keep_##name##_interval != new->keep_##name##_interval) \
+ { \
+ cache->name##_interval = new->name##_interval; \
+ CACHE_TRACE(D_EVENTS, cache, #Name " interval changed to %u seconds %s", cache->name##_interval, (new->keep_##name##_interval ? "and keep it" : "")); \
+ try_fast_reconnect = 1; \
+ }
+ TEST_INTERVAL(refresh, Refresh);
+ TEST_INTERVAL(retry, Retry);
+ TEST_INTERVAL(expire, Expire);
+#undef TEST_INTERVAL
+
+ if (try_fast_reconnect)
+ return rpki_try_fast_reconnect(cache, new, old);
+
+ return SUCCESSFUL_RECONF;
+}
+
+/**
+ * rpki_reconfigure - a protocol reconfiguration hook
+ * @P: a protocol instance
+ * @CF: a new protocol configuration
+ *
+ * This function reconfigures whole protocol.
+ * It sets new protocol configuration into a protocol structure.
+ * Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|.
+ */
+static int
+rpki_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct rpki_proto *p = (void *) P;
+ struct rpki_config *new = (void *) CF;
+ struct rpki_config *old = (void *) p->p.cf;
+ struct rpki_cache *cache = p->cache;
+
+ if (!proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)) ||
+ !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)))
+ return NEED_RESTART;
+
+ if (rpki_reconfigure_cache(p, cache, new, old) != SUCCESSFUL_RECONF)
+ return NEED_RESTART;
+
+ return SUCCESSFUL_RECONF;
+}
+
+
+/*
+ * RPKI Protocol Glue
+ */
+
+static struct proto *
+rpki_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct rpki_proto *p = (void *) P;
+
+ proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4));
+ proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6));
+
+ return P;
+}
+
+static int
+rpki_start(struct proto *P)
+{
+ struct rpki_proto *p = (void *) P;
+ struct rpki_config *cf = (void *) P->cf;
+
+ p->cache = rpki_init_cache(p, cf);
+ rpki_start_cache(p->cache);
+
+ return PS_START;
+}
+
+static void
+rpki_get_status(struct proto *P, byte *buf)
+{
+ struct rpki_proto *p = (struct rpki_proto *) P;
+
+ if (P->proto_state == PS_DOWN)
+ {
+ *buf = 0;
+ return;
+ }
+
+ if (p->cache)
+ bsprintf(buf, "%s", rpki_cache_state_to_str(p->cache->state));
+ else
+ bsprintf(buf, "No cache server configured");
+}
+
+static void
+rpki_show_proto_info_timer(const char *name, uint num, timer *t)
+{
+ if (t->expires)
+ cli_msg(-1006, " %-17s %us (remains %us)", name, num, tm_remains(t));
+ else
+ cli_msg(-1006, " %-17s ---", name);
+}
+
+static void
+rpki_show_proto_info(struct proto *P)
+{
+ struct rpki_proto *p = (struct rpki_proto *) P;
+ struct rpki_config *cf = (void *) p->p.cf;
+ struct rpki_cache *cache = p->cache;
+
+ if (P->proto_state == PS_DOWN)
+ return;
+
+ if (cache)
+ {
+ const char *transport_name = "---";
+
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_SSH: transport_name = "SSHv2"; break;
+ case RPKI_TR_TCP: transport_name = "Unprotected over TCP"; break;
+ };
+
+ cli_msg(-1006, " Cache server: %s", rpki_get_cache_ident(cache));
+ cli_msg(-1006, " Status: %s", rpki_cache_state_to_str(cache->state));
+ cli_msg(-1006, " Transport: %s", transport_name);
+ cli_msg(-1006, " Protocol version: %u", cache->version);
+
+ if (cache->request_session_id)
+ cli_msg(-1006, " Session ID: ---");
+ else
+ cli_msg(-1006, " Session ID: %u", cache->session_id);
+
+ if (cache->last_update)
+ {
+ cli_msg(-1006, " Serial number: %u", cache->serial_num);
+ cli_msg(-1006, " Last update: before %us", now - cache->last_update);
+ }
+ else
+ {
+ cli_msg(-1006, " Serial number: ---");
+ cli_msg(-1006, " Last update: ---");
+ }
+
+ rpki_show_proto_info_timer("Refresh interval:", cache->refresh_interval, cache->refresh_timer);
+ rpki_show_proto_info_timer("Retry interval:", cache->retry_interval, cache->retry_timer);
+ rpki_show_proto_info_timer("Expire interval:", cache->expire_interval, cache->expire_timer);
+
+ if (p->roa4_channel)
+ channel_show_info(p->roa4_channel);
+ else
+ cli_msg(-1006, " No roa4 channel");
+
+ if (p->roa6_channel)
+ channel_show_info(p->roa6_channel);
+ else
+ cli_msg(-1006, " No roa6 channel");
+ }
+}
+
+
+/*
+ * RPKI Protocol Configuration
+ */
+
+/**
+ * rpki_check_config - check and complete configuration of RPKI protocol
+ * @cf: RPKI configuration
+ *
+ * This function is called at the end of parsing RPKI protocol configuration.
+ */
+void
+rpki_check_config(struct rpki_config *cf)
+{
+ /* Do not check templates at all */
+ if (cf->c.class == SYM_TEMPLATE)
+ return;
+
+ if (ipa_zero(cf->ip) && cf->hostname == NULL)
+ cf_error("IP address or hostname of cache server must be set");
+
+ /* Set default transport type */
+ if (cf->tr_config.spec == NULL)
+ {
+ cf->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config));
+ cf->tr_config.type = RPKI_TR_TCP;
+ }
+
+ if (cf->port == 0)
+ {
+ /* Set default port numbers */
+ switch (cf->tr_config.type)
+ {
+ case RPKI_TR_SSH:
+ cf->port = RPKI_SSH_PORT;
+ break;
+ default:
+ cf->port = RPKI_TCP_PORT;
+ }
+ }
+}
+
+static void
+rpki_postconfig(struct proto_config *CF)
+{
+ /* Define default channel */
+ if (EMPTY_LIST(CF->channels))
+ channel_config_new(NULL, CF->net_type, CF);
+}
+
+static void
+rpki_copy_config(struct proto_config *dest, struct proto_config *src)
+{
+ /* Just a shallow copy */
+}
+
+struct protocol proto_rpki = {
+ .name = "RPKI",
+ .template = "rpki%d",
+ .preference = DEF_PREF_RPKI,
+ .proto_size = sizeof(struct rpki_proto),
+ .config_size = sizeof(struct rpki_config),
+ .init = rpki_init,
+ .start = rpki_start,
+ .postconfig = rpki_postconfig,
+ .channel_mask = (NB_ROA4 | NB_ROA6),
+ .show_proto_info = rpki_show_proto_info,
+ .shutdown = rpki_shutdown,
+ .copy_config = rpki_copy_config,
+ .reconfigure = rpki_reconfigure,
+ .get_status = rpki_get_status,
+};
diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h
new file mode 100644
index 00000000..eaeed858
--- /dev/null
+++ b/proto/rpki/rpki.h
@@ -0,0 +1,166 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * Using RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RPKI_H_
+#define _BIRD_RPKI_H_
+
+#include "nest/bird.h"
+#include "nest/route.h"
+#include "nest/protocol.h"
+#include "lib/socket.h"
+#include "lib/ip.h"
+
+#include "transport.h"
+#include "packets.h"
+
+#define RPKI_TCP_PORT 323
+#define RPKI_SSH_PORT 22
+#define RPKI_RETRY_INTERVAL 600
+#define RPKI_REFRESH_INTERVAL 3600
+#define RPKI_EXPIRE_INTERVAL 7200
+
+#define RPKI_VERSION_0 0
+#define RPKI_VERSION_1 1
+#define RPKI_MIN_VERSION RPKI_VERSION_0
+#define RPKI_MAX_VERSION RPKI_VERSION_1
+
+
+/*
+ * RPKI Cache
+ */
+
+enum rpki_cache_state {
+ RPKI_CS_CONNECTING, /* Socket is establishing the transport connection. */
+ RPKI_CS_ESTABLISHED, /* Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */
+ RPKI_CS_RESET, /* Resetting RTR connection. */
+ RPKI_CS_SYNC_START, /* Sending a Serial/Reset Query PDU and expecting a Cache Response PDU */
+ RPKI_CS_SYNC_RUNNING, /* Receiving validation records from the RTR server. A state between Cache Response PDU and End of Data PDU */
+ RPKI_CS_FAST_RECONNECT, /* Reconnect without any waiting period */
+ RPKI_CS_NO_INCR_UPDATE_AVAIL, /* Server is unable to answer the last Serial Query and sent Cache Reset. */
+ RPKI_CS_ERROR_NO_DATA_AVAIL, /* Server is unable to answer either a Serial Query or a Reset Query because it has no useful data available at this time. */
+ RPKI_CS_ERROR_FATAL, /* Fatal protocol error occurred. */
+ RPKI_CS_ERROR_TRANSPORT, /* Error on the transport socket occurred. */
+ RPKI_CS_SHUTDOWN, /* RTR Socket is stopped. */
+};
+
+struct rpki_cache {
+ pool *pool; /* Pool containing cache objects */
+ struct rpki_proto *p;
+
+ struct rpki_tr_sock *tr_sock; /* Transport specific socket */
+ enum rpki_cache_state state; /* RPKI_CS_* */
+ u32 session_id;
+ u8 request_session_id; /* 1: have to request new session id; 0: we have already received session id */
+ u32 serial_num; /* Serial number denotes the logical version of data from cache server */
+ u8 version; /* Protocol version */
+ bird_clock_t last_update; /* Last successful synchronization with cache server */
+ bird_clock_t last_rx_prefix; /* Last received prefix PDU */
+
+ /* Intervals can be changed by cache server on the fly */
+ u32 refresh_interval; /* Actual refresh interval */
+ u32 retry_interval;
+ u32 expire_interval;
+ timer *retry_timer; /* Retry timer event */
+ timer *refresh_timer; /* Refresh timer event */
+ timer *expire_timer; /* Expire timer event */
+};
+
+const char *rpki_get_cache_ident(struct rpki_cache *cache);
+const char *rpki_cache_state_to_str(enum rpki_cache_state state);
+
+
+/*
+ * Routes handling
+ */
+
+void rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+void rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+
+
+/*
+ * RPKI Protocol Logic
+ */
+
+void rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state);
+
+
+/*
+ * RPKI Timer Events
+ */
+
+const char *rpki_check_refresh_interval(uint seconds);
+const char *rpki_check_retry_interval(uint seconds);
+const char *rpki_check_expire_interval(uint seconds);
+
+
+/*
+ * RPKI Protocol Configuration
+ */
+
+struct rpki_proto {
+ struct proto p;
+ struct rpki_cache *cache;
+
+ struct channel *roa4_channel;
+ struct channel *roa6_channel;
+ u8 refresh_channels; /* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */
+};
+
+struct rpki_config {
+ struct proto_config c;
+ const char *hostname; /* Full domain name or stringified IP address of cache server */
+ ip_addr ip; /* IP address of cache server or IPA_NONE */
+ u16 port; /* Port number of cache server */
+ struct rpki_tr_config tr_config; /* Specific transport configuration structure */
+ u32 refresh_interval; /* Time interval (in seconds) for periodical downloading data from cache server */
+ u32 retry_interval; /* Time interval (in seconds) for an unreachable server */
+ u32 expire_interval; /* Maximal lifetime (in seconds) of ROAs without any successful refreshment */
+ u8 keep_refresh_interval:1; /* Do not overwrite refresh interval by cache server update */
+ u8 keep_retry_interval:1; /* Do not overwrite retry interval by cache server update */
+ u8 keep_expire_interval:1; /* Do not overwrite expire interval by cache server update */
+};
+
+void rpki_check_config(struct rpki_config *cf);
+
+
+/*
+ * Logger
+ */
+
+#define RPKI_LOG(log_level, rpki, msg, args...) \
+ do { \
+ log(log_level "%s: " msg, (rpki)->p.name , ## args); \
+ } while(0)
+
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+#define CACHE_DBG(cache,msg,args...) \
+ do { \
+ RPKI_LOG(L_DEBUG, (cache)->p, "%s [%s] %s " msg, rpki_get_cache_ident(cache), rpki_cache_state_to_str((cache)->state), __func__, ## args); \
+ } while(0)
+#else
+#define CACHE_DBG(cache,msg,args...) do { } while(0)
+#endif
+
+#define RPKI_TRACE(level,rpki,msg,args...) \
+ do { \
+ if ((rpki)->p.debug & level) \
+ RPKI_LOG(L_TRACE, rpki, msg, ## args); \
+ } while(0)
+
+#define CACHE_TRACE(level,cache,msg,args...) \
+ do { \
+ if ((cache)->p->p.debug & level) \
+ RPKI_LOG(L_TRACE, (cache)->p, msg, ## args); \
+ } while(0)
+
+#define RPKI_WARN(p, msg, args...) RPKI_LOG(L_WARN, p, msg, ## args);
+
+#endif /* _BIRD_RPKI_H_ */
diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c
new file mode 100644
index 00000000..cd49ab90
--- /dev/null
+++ b/proto/rpki/ssh_transport.c
@@ -0,0 +1,75 @@
+/*
+ * BIRD -- An implementation of the SSH protocol for the RPKI transport
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * 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
+#include
+#include
+#include
+
+#include "rpki.h"
+
+static int
+rpki_tr_ssh_open(struct rpki_tr_sock *tr)
+{
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+ struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec;
+ sock *sk = tr->sk;
+
+ sk->type = SK_SSH_ACTIVE;
+ sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock));
+ sk->ssh->username = ssh_cf->user;
+ sk->ssh->client_privkey_path = ssh_cf->bird_private_key;
+ sk->ssh->server_hostkey_path = ssh_cf->cache_public_key;
+ sk->ssh->subsystem = "rpki-rtr";
+ sk->ssh->state = SK_SSH_CONNECT;
+
+ if (sk_open(sk) != 0)
+ return RPKI_TR_ERROR;
+
+ return RPKI_TR_SUCCESS;
+}
+
+static const char *
+rpki_tr_ssh_ident(struct rpki_tr_sock *tr)
+{
+ ASSERT(tr != NULL);
+
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+ struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec;
+
+ if (tr->ident != NULL)
+ return tr->ident;
+
+ const char *username = ssh_cf->user;
+ const char *host = cf->hostname;
+ u16 port = cf->port;
+
+ size_t len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* + '@' + + ':' + + '\0' */
+ char *ident = mb_alloc(cache->pool, len);
+ bsnprintf(ident, len, "%s@%s:%u", username, host, port);
+ tr->ident = ident;
+
+ return tr->ident;
+}
+
+/**
+ * rpki_tr_ssh_init - initializes the RPKI transport structure for a SSH connection
+ * @tr: allocated RPKI transport structure
+ */
+void
+rpki_tr_ssh_init(struct rpki_tr_sock *tr)
+{
+ tr->open_fp = &rpki_tr_ssh_open;
+ tr->ident_fp = &rpki_tr_ssh_ident;
+}
diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c
new file mode 100644
index 00000000..6c05964a
--- /dev/null
+++ b/proto/rpki/tcp_transport.c
@@ -0,0 +1,78 @@
+/*
+ * BIRD -- An implementation of the TCP protocol for the RPKI protocol transport
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "rpki.h"
+#include "sysdep/unix/unix.h"
+
+static int
+rpki_tr_tcp_open(struct rpki_tr_sock *tr)
+{
+ sock *sk = tr->sk;
+
+ sk->type = SK_TCP_ACTIVE;
+
+ if (sk_open(sk) != 0)
+ return RPKI_TR_ERROR;
+
+ return RPKI_TR_SUCCESS;
+}
+
+static const char *
+rpki_tr_tcp_ident(struct rpki_tr_sock *tr)
+{
+ ASSERT(tr != NULL);
+
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ if (tr->ident != NULL)
+ return tr->ident;
+
+ const char *host = cf->hostname;
+ ip_addr ip = cf->ip;
+ u16 port = cf->port;
+
+ size_t colon_and_port_len = 6; /* max ":65535" */
+ size_t ident_len;
+ if (host)
+ ident_len = strlen(host) + colon_and_port_len + 1;
+ else
+ ident_len = IPA_MAX_TEXT_LENGTH + colon_and_port_len + 1;
+
+ char *ident = mb_alloc(cache->pool, ident_len);
+ if (host)
+ bsnprintf(ident, ident_len, "%s:%u", host, port);
+ else
+ bsnprintf(ident, ident_len, "%I:%u", ip, port);
+
+ tr->ident = ident;
+ return tr->ident;
+}
+
+/**
+ * rpki_tr_tcp_init - initializes the RPKI transport structure for a TCP connection
+ * @tr: allocated RPKI transport structure
+ */
+void
+rpki_tr_tcp_init(struct rpki_tr_sock *tr)
+{
+ tr->open_fp = &rpki_tr_tcp_open;
+ tr->ident_fp = &rpki_tr_tcp_ident;
+}
diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c
new file mode 100644
index 00000000..182667be
--- /dev/null
+++ b/proto/rpki/transport.c
@@ -0,0 +1,135 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * 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
+#include
+
+#include "rpki.h"
+#include "transport.h"
+#include "sysdep/unix/unix.h"
+
+/**
+ * rpki_hostname_autoresolv - auto-resolve an IP address from a hostname
+ * @host: domain name of host, e.g. "rpki-validator.realmv6.org"
+ *
+ * This function resolves an IP address from a hostname.
+ * Returns &ip_addr structure with IP address or |IPA_NONE|.
+ */
+static ip_addr
+rpki_hostname_autoresolv(const char *host)
+{
+ ip_addr addr = {};
+ struct addrinfo *res;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_ADDRCONFIG,
+ };
+
+ if (!host)
+ return IPA_NONE;
+
+ int err_code = getaddrinfo(host, NULL, &hints, &res);
+ if (err_code != 0)
+ {
+ log(L_DEBUG "getaddrinfo failed: %s", gai_strerror(err_code));
+ return IPA_NONE;
+ }
+
+ sockaddr sa = {
+ .sa = *res->ai_addr,
+ };
+
+ uint unused;
+ sockaddr_read(&sa, res->ai_family, &addr, NULL, &unused);
+
+ freeaddrinfo(res);
+ return addr;
+}
+
+/**
+ * rpki_tr_open - prepare and open a socket connection
+ * @tr: initialized transport socket
+ *
+ * Prepare and open a socket connection specified by @tr that must be initialized before.
+ * This function ends with a calling the sk_open() function.
+ * Returns RPKI_TR_SUCCESS or RPKI_TR_ERROR.
+ */
+int
+rpki_tr_open(struct rpki_tr_sock *tr)
+{
+ struct rpki_cache *cache = tr->cache;
+ struct rpki_config *cf = (void *) cache->p->p.cf;
+
+ ASSERT(tr->sk == NULL);
+ tr->sk = sk_new(cache->pool);
+ sock *sk = tr->sk;
+
+ /* sk->type -1 is invalid value, a correct value MUST be set in the specific transport layer in open_fp() hook */
+ sk->type = -1;
+
+ sk->tx_hook = rpki_connected_hook;
+ sk->err_hook = rpki_err_hook;
+ sk->data = cache;
+ sk->daddr = cf->ip;
+ sk->dport = cf->port;
+ sk->host = cf->hostname;
+ sk->rbsize = RPKI_RX_BUFFER_SIZE;
+ sk->tbsize = RPKI_TX_BUFFER_SIZE;
+ sk->tos = IP_PREC_INTERNET_CONTROL;
+
+ if (ipa_zero2(sk->daddr) && sk->host)
+ {
+ sk->daddr = rpki_hostname_autoresolv(sk->host);
+ if (ipa_zero(sk->daddr))
+ {
+ CACHE_TRACE(D_EVENTS, cache, "Cannot resolve the hostname '%s'", sk->host);
+ return RPKI_TR_ERROR;
+ }
+ }
+
+ return tr->open_fp(tr);
+}
+
+/**
+ * rpki_tr_close - close socket and prepare it for possible next open
+ * @tr: successfully opened transport socket
+ *
+ * Close socket and free resources.
+ */
+void
+rpki_tr_close(struct rpki_tr_sock *tr)
+{
+ if (tr->ident)
+ {
+ mb_free((char *) tr->ident);
+ tr->ident = NULL;
+ }
+
+ if (tr->sk)
+ {
+ rfree(tr->sk);
+ tr->sk = NULL;
+ }
+}
+
+/**
+ * rpki_tr_ident - Returns a string identifier for the rpki transport socket
+ * @tr: successfully opened transport socket
+ *
+ * Returns a \0 terminated string identifier for the socket endpoint, e.g. ":".
+ * Memory is allocated inside @tr structure.
+ */
+inline const char *
+rpki_tr_ident(struct rpki_tr_sock *tr)
+{
+ return tr->ident_fp(tr);
+}
diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h
new file mode 100644
index 00000000..f90b7e42
--- /dev/null
+++ b/proto/rpki/transport.h
@@ -0,0 +1,79 @@
+/*
+ * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol
+ *
+ * (c) 2015 CZ.NIC
+ * (c) 2015 Pavel Tvrdik
+ *
+ * This file was a part of RTRlib: http://rpki.realmv6.org/
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/*
+ * The RPKI transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RPKI server and client.
+ *
+ * Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * rpki_tr_tcp_init()).
+ *
+ * The rpki_tr_* functions call the corresponding function pointers, which are
+ * passed in the rpki_tr_sock structure, and forward the remaining arguments.
+ */
+
+#ifndef _BIRD_RPKI_TRANSPORT_H_
+#define _BIRD_RPKI_TRANSPORT_H_
+
+#include
+
+/* The return values for rpki_tr_ functions */
+enum rpki_tr_rtvals {
+ RPKI_TR_SUCCESS = 0, /* Operation was successful */
+ RPKI_TR_ERROR = -1, /* Error occurred */
+ RPKI_TR_WOULDBLOCK = -2, /* No data is available on the socket */
+ RPKI_TR_INTR = -3, /* Call was interrupted from a signal */
+ RPKI_TR_CLOSED = -4 /* Connection closed */
+};
+
+/* A transport socket structure */
+struct rpki_tr_sock {
+ sock *sk; /* Standard BIRD socket */
+ struct rpki_cache *cache; /* Cache server */
+ int (*open_fp)(struct rpki_tr_sock *); /* Function that establishes the socket connection */
+ const char *(*ident_fp)(struct rpki_tr_sock *); /* Function that returns an identifier for the socket endpoint */
+ const char *ident; /* Internal. Use ident_fp() hook instead of this pointer */
+};
+
+int rpki_tr_open(struct rpki_tr_sock *tr);
+void rpki_tr_close(struct rpki_tr_sock *tr);
+const char *rpki_tr_ident(struct rpki_tr_sock *tr);
+
+/* Types of supported transports */
+enum rpki_tr_type {
+ RPKI_TR_TCP, /* Unprotected transport over TCP */
+ RPKI_TR_SSH, /* Protected transport by SSHv2 connection */
+};
+
+/* Common configure structure for transports */
+struct rpki_tr_config {
+ enum rpki_tr_type type; /* RPKI_TR_TCP or RPKI_TR_SSH */
+ const void *spec; /* Specific transport configuration, i.e. rpki_tr_tcp_config or rpki_tr_ssh_config */
+};
+
+struct rpki_tr_tcp_config {
+ /* No internal configuration data */
+};
+
+struct rpki_tr_ssh_config {
+ const char *bird_private_key; /* Filepath to the BIRD server private key */
+ const char *cache_public_key; /* Filepath to the public key of cache server, can be file known_hosts */
+ const char *user; /* Username for SSH connection */
+};
+
+/* ssh_transport.c */
+void rpki_tr_ssh_init(struct rpki_tr_sock *tr);
+
+/* tcp_transport.c */
+void rpki_tr_tcp_init(struct rpki_tr_sock *tr);
+
+#endif /* _BIRD_RPKI_TRANSPORT_H_ */
diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in
index 0d0627f3..4887c433 100644
--- a/sysdep/autoconf.h.in
+++ b/sysdep/autoconf.h.in
@@ -44,6 +44,7 @@
#undef CONFIG_OSPF
#undef CONFIG_PIPE
#undef CONFIG_BABEL
+#undef CONFIG_RPKI
/* We use multithreading */
#undef USE_PTHREADS
@@ -70,4 +71,7 @@
/* We have execinfo.h */
#undef HAVE_EXECINFO_H
+/* We have LibSSH */
+#undef HAVE_LIBSSH
+
#define CONFIG_PATH ?
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 146d401f..c8916378 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -1071,26 +1071,63 @@ sk_free_bufs(sock *s)
}
}
+#ifdef HAVE_LIBSSH
+static void
+sk_ssh_free(sock *s)
+{
+ struct ssh_sock *ssh = s->ssh;
+
+ if (s->ssh == NULL)
+ return;
+
+ s->ssh = NULL;
+
+ if (ssh->channel)
+ {
+ if (ssh_channel_is_open(ssh->channel))
+ ssh_channel_close(ssh->channel);
+ ssh_channel_free(ssh->channel);
+ ssh->channel = NULL;
+ }
+
+ if (ssh->session)
+ {
+ ssh_disconnect(ssh->session);
+ ssh_free(ssh->session);
+ ssh->session = NULL;
+ }
+}
+#endif
+
static void
sk_free(resource *r)
{
sock *s = (sock *) r;
sk_free_bufs(s);
- if (s->fd >= 0)
+
+#ifdef HAVE_LIBSSH
+ if (s->type == SK_SSH || s->type == SK_SSH_ACTIVE)
+ sk_ssh_free(s);
+#endif
+
+ if (s->fd < 0)
+ return;
+
+ /* FIXME: we should call sk_stop() for SKF_THREAD sockets */
+ if (!(s->flags & SKF_THREAD))
{
- close(s->fd);
-
- /* FIXME: we should call sk_stop() for SKF_THREAD sockets */
- if (s->flags & SKF_THREAD)
- return;
-
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
@@ -1141,7 +1178,7 @@ static void
sk_dump(resource *r)
{
sock *s = (sock *) r;
- static char *sk_type_names[] = { "TCP<", "TCP>", "TCP", "UDP", NULL, "IP", NULL, "MAGIC", "UNIX<", "UNIX", "DEL!" };
+ static char *sk_type_names[] = { "TCP<", "TCP>", "TCP", "UDP", NULL, "IP", NULL, "MAGIC", "UNIX<", "UNIX", "SSH>", "SSH", "DEL!" };
debug("(%s, ud=%p, sa=%I, sp=%d, da=%I, dp=%d, tos=%d, ttl=%d, if=%s)\n",
sk_type_names[s->type],
@@ -1192,6 +1229,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");
@@ -1304,6 +1344,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)
{
@@ -1356,6 +1404,201 @@ sk_passive_connected(sock *s, int type)
return 1;
}
+#ifdef HAVE_LIBSSH
+/*
+ * 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:
+ /* A quick look into libSSH shows that ssh_get_fd() should return non-(-1)
+ * after SSH_AGAIN is returned by ssh_connect(). This is however nowhere
+ * documented but our code relies on that.
+ */
+ 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));
+ /* TODO: Add SSH_OPTIONS_BINDADDR */
+ 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;
+}
+#endif
+
/**
* sk_open - open a socket
* @s: socket
@@ -1421,6 +1664,13 @@ sk_open(sock *s)
do_bind = bind_port || ipa_nonzero(bind_addr);
break;
+#ifdef HAVE_LIBSSH
+ case SK_SSH_ACTIVE:
+ s->ttx = ""; /* Force s->ttx != s->tpos */
+ fd = sk_open_ssh(s);
+ break;
+#endif
+
case SK_UDP:
fd = socket(af, SOCK_DGRAM, IPPROTO_UDP);
bind_port = s->sport;
@@ -1501,6 +1751,7 @@ sk_open(sock *s)
ERR2("listen");
break;
+ case SK_SSH_ACTIVE:
case SK_MAGIC:
break;
@@ -1510,6 +1761,7 @@ sk_open(sock *s)
if (!(s->flags & SKF_THREAD))
sk_insert(s);
+
return 0;
err:
@@ -1692,6 +1944,28 @@ sk_maybe_write(sock *s)
reset_tx_buffer(s);
return 1;
+#ifdef HAVE_LIBSSH
+ 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;
+#endif
+
case SK_UDP:
case SK_IP:
{
@@ -1716,6 +1990,7 @@ sk_maybe_write(sock *s)
reset_tx_buffer(s);
return 1;
}
+
default:
bug("sk_maybe_write: unknown socket type %d", s->type);
}
@@ -1795,6 +2070,64 @@ 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;
+ }
+}
+
+#ifdef HAVE_LIBSSH
+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 */
+}
+#endif
+
/* sk_read() and sk_write() are called from BFD's event loop */
int
@@ -1828,17 +2161,17 @@ 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;
}
+#ifdef HAVE_LIBSSH
+ case SK_SSH:
+ return sk_read_ssh(s);
+#endif
+
case SK_MAGIC:
return s->rx_hook(s, 0);
@@ -1877,6 +2210,27 @@ sk_write(sock *s)
return 0;
}
+#ifdef HAVE_LIBSSH
+ 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;
+ }
+#endif
+
default:
if (s->ttx != s->tpos && sk_maybe_write(s) > 0)
{