From: Pavel TvrdĂ­k Date: Thu, 17 Sep 2015 15:15:30 +0000 (+0200) Subject: RPKI protocol with integrated RTRLib inside X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=41f4b5940f5bda144ced93cae9337876bc2f74b1;p=thirdparty%2Fbird.git RPKI protocol with integrated RTRLib inside Add the RPKI protocol (RFC 6810) using the RTRLib (http://rpki.realmv6.org/) that is integrated inside the BIRD's code. Implemeted transports are: - unprotected transport over TCP - secure transport over SSHv2 The code should work properly with one or more cache servers per protocol. Example configuration of bird.conf: ... roa4 table roatable; protocol rpki { table roatable; cache 127.0.0.1; # defaults: port 8282, preference 1, no encryption cache 127.0.0.1 { preference 1; port 2222; ssh encryption { bird private key "/home/birdgeek/.ssh/id_rsa"; cache public key "/home/birdgeek/.ssh/known_hosts"; user "birdgeek"; }; }; cache "rpki-validator.realmv6.org" { preference 2; }; } ... --- diff --git a/conf/confbase.Y b/conf/confbase.Y index 94a20fe71..3fec331f0 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -10,6 +10,8 @@ CF_HDR #define PARSER 1 +#include + #include "nest/bird.h" #include "conf/conf.h" #include "lib/resource.h" @@ -26,6 +28,13 @@ CF_HDR CF_DEFINES +static void +check_u8(unsigned val) +{ + if (val > 0xFF) + cf_error("Value %d out of range (0-255)", val); +} + static void check_u16(unsigned val) { @@ -33,6 +42,16 @@ check_u16(unsigned val) cf_error("Value %d out of range (0-65535)", val); } +static void +check_file_readability(const char *file_path) +{ + FILE *file = fopen(file_path, "r"); + if (file) + fclose(file); + else + cf_error("File '%s' cannot be open for read: %m", file_path); +} + CF_DECLS %union { diff --git a/configure.in b/configure.in index 1c2c2fe1d..8754b506b 100644 --- a/configure.in +++ b/configure.in @@ -175,9 +175,7 @@ fi AC_SUBST(iproutedir) -# all_protocols="$proto_bfd bgp ospf pipe radv rip static" -all_protocols="$proto_bfd ospf pipe radv rip static" - +all_protocols="$proto_bfd ospf pipe radv rip rpki static" # TODO: add BGP all_protocols=`echo $all_protocols | sed 's/ /,/g'` if test "$with_protocols" = all ; then @@ -234,6 +232,10 @@ if test "$enable_debug" = yes ; then fi fi +BIRD_LIBS= +AC_CHECK_LIB(dl, dlopen, BIRD_LIBS="-ldl") +AC_SUBST(BIRD_LIBS) + CLIENT= CLIENT_LIBS= if test "$enable_client" = yes ; then diff --git a/lib/Modules b/lib/Modules index 6b9b4b0f2..dfed9edd9 100644 --- a/lib/Modules +++ b/lib/Modules @@ -34,3 +34,5 @@ checksum.c checksum.h alloca.h net.c +libssh.c +libssh.h \ No newline at end of file diff --git a/lib/libssh.c b/lib/libssh.c new file mode 100644 index 000000000..c8b00da1b --- /dev/null +++ b/lib/libssh.c @@ -0,0 +1,126 @@ +/* + * BIRD -- Mockup of SSH Library for loading LibSSH using dlopen + * + * (c) 2015 CZ.NIC + * + * This file was part of SSH Library: http://www.libssh.org/ + * (c) 2003-2009 by Aris Adamantiadis (SSH Library) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include "nest/bird.h" +#include "lib/libssh.h" + +#define FILENAME_OF_SHARED_OBJECT_LIBSSH "libssh.so" + +static void *libssh; + +/* + * @return NULL if success + * @return string with error if failed + */ +const char * +load_libssh(void) +{ + char *err_buf; + + libssh = dlopen(FILENAME_OF_SHARED_OBJECT_LIBSSH, RTLD_LAZY); + if (!libssh) + { + /* This would be probably often repeated problem */ + char *help_msg = "You have to install libssh library."; + err_buf = mb_alloc(&root_pool, 512); /* FIXME: free memory */ + bsnprintf(err_buf, 512, "%s. %s", dlerror(), help_msg); + return err_buf; + } + + dlerror(); /* Clear any existing error */ + + ssh_new = (ssh_session (*)(void)) dlsym(libssh, "ssh_new"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_set_blocking = (void (*)(ssh_session, int)) dlsym(libssh, "ssh_set_blocking"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_options_set = (int (*)(ssh_session, enum ssh_options_e, const void *)) dlsym(libssh, "ssh_options_set"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_connect = (int (*)(ssh_session)) dlsym(libssh, "ssh_connect"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_get_fd = (socket_t (*)(ssh_session)) dlsym(libssh, "ssh_get_fd"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_is_server_known = (int (*)(ssh_session)) dlsym(libssh, "ssh_is_server_known"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_userauth_publickey_auto = (int (*)(ssh_session, const char *, const char *)) dlsym(libssh, "ssh_userauth_publickey_auto"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_get_error = (const char * (*)(void *)) dlsym(libssh, "ssh_get_error"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_get_error_code = (int (*)(void *)) dlsym(libssh, "ssh_get_error_code"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_disconnect = (void (*)(ssh_session)) dlsym(libssh, "ssh_disconnect"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_free = (void (*)(ssh_session)) dlsym(libssh, "ssh_free"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_new = (ssh_channel (*)(ssh_session)) dlsym(libssh, "ssh_channel_new"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_is_open = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_is_open"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_close = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_close"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_free = (void (*)(ssh_channel)) dlsym(libssh, "ssh_channel_free"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_open_session = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_open_session"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_request_subsystem = (int (*)(ssh_channel, const char *)) dlsym(libssh, "ssh_channel_request_subsystem"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_read_nonblocking = (int (*)(ssh_channel, void *, uint32_t, int)) dlsym(libssh, "ssh_channel_read_nonblocking"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_is_eof = (int (*)(ssh_channel)) dlsym(libssh, "ssh_channel_is_eof"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_select = (int (*)(ssh_channel *, ssh_channel *, ssh_channel *, struct timeval *)) dlsym(libssh, "ssh_channel_select"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + ssh_channel_write = (int (*)(ssh_channel, const void *, uint32_t)) dlsym(libssh, "ssh_channel_write"); + if ((err_buf = dlerror()) != NULL) + return err_buf; + + return NULL; +} diff --git a/lib/libssh.h b/lib/libssh.h new file mode 100644 index 000000000..15e4579ae --- /dev/null +++ b/lib/libssh.h @@ -0,0 +1,133 @@ +/* + * BIRD -- Mockup headers of SSH Library for loading LibSSH using dlopen + * + * (c) 2015 CZ.NIC + * + * This file was part of SSH Library: http://www.libssh.org/ + * (c) 2003-2009 by Aris Adamantiadis (SSH Library) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_LIBSSH_H_ +#define _BIRD_LIBSSH_H_ + +#include +#include + +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_channel_struct* ssh_channel; + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, +}; + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only warnings + */ + SSH_LOG_WARNING, + /** High level protocol information + */ + SSH_LOG_PROTOCOL, + /** Lower level protocol infomations, packet level + */ + SSH_LOG_PACKET, + /** Every function path + */ + SSH_LOG_FUNCTIONS +}; + +#ifndef socket_t +typedef int socket_t; +#endif + +ssh_session (*ssh_new)(void); +void (*ssh_set_blocking)(ssh_session session, int blocking); +int (*ssh_options_set)(ssh_session session, enum ssh_options_e type, const void *value); +int (*ssh_connect)(ssh_session session); +socket_t (*ssh_get_fd)(ssh_session session); +int (*ssh_is_server_known)(ssh_session session); +int (*ssh_userauth_publickey_auto)(ssh_session session, const char *username, const char *passphrase); +const char * (*ssh_get_error)(void *error); +int (*ssh_get_error_code)(void *error); +void (*ssh_disconnect)(ssh_session session); +void (*ssh_free)(ssh_session session); + +ssh_channel (*ssh_channel_new)(ssh_session session); +int (*ssh_channel_is_open)(ssh_channel channel); +int (*ssh_channel_close)(ssh_channel channel); +void (*ssh_channel_free)(ssh_channel channel); +int (*ssh_channel_open_session)(ssh_channel channel); +int (*ssh_channel_request_subsystem)(ssh_channel channel, const char *subsystem); +int (*ssh_channel_read_nonblocking)(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +int (*ssh_channel_is_eof)(ssh_channel channel); +int (*ssh_channel_select)(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct timeval * timeout); +int (*ssh_channel_write)(ssh_channel channel, const void *data, uint32_t len); + +const char *load_libssh(void); + +#endif /* _BIRD_LIBSSH_H_ */ diff --git a/lib/socket.h b/lib/socket.h index 1b03098d6..7a04d99a5 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -10,8 +10,27 @@ #define _BIRD_SOCKET_H_ #include +// #include #include "lib/resource.h" +#include "lib/libssh.h" + +struct ssh_sock { + char *username; /* (Required) SSH user name */ + char *server_hostkey_path; /* (Optional) Filepath to the SSH public key of remote side, can be knownhost file */ + char *client_privkey_path; /* (Optional) Filepath to the SSH private key of BIRD */ + char *subsystem; /* (Optional) Name of SSH subsytem */ + ssh_session session; /* Internal */ + ssh_channel channel; /* Internal */ + int state; /* Internal */ +#define BIRD_SSH_CONNECT 0 /* Start state */ +#define BIRD_SSH_IS_SERVER_KNOWN 1 +#define BIRD_SSH_USERAUTH_PUBLICKEY_AUTO 2 +#define BIRD_SSH_CHANNEL_NEW 3 +#define BIRD_SSH_CHANNEL_OPEN_SESSION 4 +#define BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM 5 +#define BIRD_SSH_CONNECTION_ESTABLISHED 6 /* Final state */ +}; typedef struct birdsock { resource r; @@ -19,6 +38,7 @@ typedef struct birdsock { int type; /* Socket type */ void *data; /* User data */ ip_addr saddr, daddr; /* IPA_NONE = unspecified */ + char *host; /* Alternative to daddr, NULL = unspecified */ uint sport, dport; /* 0 = unspecified (for IP: protocol type) */ int tos; /* TOS / traffic class, -1 = default */ int priority; /* Local socket priority, -1 = default */ @@ -50,7 +70,8 @@ typedef struct birdsock { node n; void *rbuf_alloc, *tbuf_alloc; char *password; /* Password for MD5 authentication */ - char *err; /* Error message */ + const char *err; /* Error message */ + struct ssh_sock *ssh; /* Used in SK_SSH */ } sock; sock *sock_new(pool *); /* Allocate new socket */ @@ -114,6 +135,8 @@ extern int sk_priority_control; /* Suggested priority for control traffic, shou #define SK_MAGIC 7 /* Internal use by sysdep code */ #define SK_UNIX_PASSIVE 8 #define SK_UNIX 9 +#define SK_SSH_ACTIVE 10 /* - - * * - ? - DA = host */ +#define SK_SSH 11 /* Socket families */ diff --git a/nest/proto.c b/nest/proto.c index d04da333c..b5179d007 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -877,6 +877,7 @@ proto_build(struct protocol *p) /* FIXME: convert this call to some protocol hook */ extern void bfd_init_all(void); +extern void rpki_init_all(void); /** * protos_build - build a protocol list @@ -919,6 +920,10 @@ protos_build(void) proto_build(&proto_bfd); bfd_init_all(); #endif +#ifdef CONFIG_RPKI + proto_build(&proto_rpki); + rpki_init_all(); +#endif proto_pool = rp_new(&root_pool, "Protocols"); proto_flush_event = ev_new(proto_pool); diff --git a/nest/protocol.h b/nest/protocol.h index 8c49154ff..384f805b4 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -76,7 +76,7 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, - proto_ospf, proto_pipe, proto_bgp, proto_bfd; + proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_rpki; /* * Routing Protocol Instance diff --git a/nest/route.h b/nest/route.h index eba3d9b05..f3a1a9aca 100644 --- a/nest/route.h +++ b/nest/route.h @@ -386,6 +386,7 @@ typedef struct rta { #define RTS_OSPF_EXT2 10 /* OSPF external route type 2 */ #define RTS_BGP 11 /* BGP route */ #define RTS_PIPE 12 /* Inter-table wormhole */ +#define RTS_RPKI 13 /* Route Origin Authorization */ #define RTC_UNICAST 0 #define RTC_BROADCAST 1 diff --git a/proto/Doc b/proto/Doc index 7863472f3..48f754ceb 100644 --- a/proto/Doc +++ b/proto/Doc @@ -3,7 +3,8 @@ C bfd C bgp C ospf C pipe -C rip C radv +C rip +C rpki C static S ../nest/rt-dev.c diff --git a/proto/rpki/Doc b/proto/rpki/Doc new file mode 100644 index 000000000..3ffa7cb0b --- /dev/null +++ b/proto/rpki/Doc @@ -0,0 +1 @@ +C rpki.c diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile new file mode 100644 index 000000000..eca40c2b0 --- /dev/null +++ b/proto/rpki/Makefile @@ -0,0 +1,5 @@ +source=rpki.c packets.c rtr.c tcp_transport.c ssh_transport.c transport.c +root-rel=../../ +dir-name=proto/rpki + +include ../../Rules diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y new file mode 100644 index 000000000..f1444e46c --- /dev/null +++ b/proto/rpki/config.Y @@ -0,0 +1,152 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "proto/rpki/rpki.h" + +CF_DEFINES + +#define RPKI_CFG ((struct rpki_config *) this_proto) + +static struct rpki_cache_cfg *this_rpki_cache_cfg; + +CF_DECLS + +CF_KEYWORDS(RPKI, CACHE, LIST, PREFERENCE, BIRD, PRIVATE, PUBLIC, KEY, SSH, ENCRYPTION, USER) +CF_KEYWORDS(RETRY, REFRESH, EXPIRE) + +CF_GRAMMAR + +CF_ADDTO(proto, rpki_proto) + +rpki_proto: +rpki_proto_start proto_name '{' rpki_proto_opts '}' rpki_proto_finish +; + +rpki_proto_start: +proto_start RPKI { + this_proto = proto_config_new(&proto_rpki, $1); + init_list(&RPKI_CFG->cache_cfg_list); +} +; + +rpki_proto_finish: +{ +// if (RPKI_CFG->roa_table_cf == NULL) +// cf_error("For the RPKI protocol must be specified a roa table"); +}; + +rpki_proto_opts: +/* empty */ +| rpki_proto_opts rpki_proto_item ';' +; + +rpki_proto_item: +proto_item +| CACHE rpki_cache +/* | ROA TABLE roa_table_cf { RPKI_CFG->roa_table_cf = $3; } */ +; + +rpki_cache: +rpki_cache_init rpki_cache_addr rpki_optional_cache_opts rpki_cache_finish { + add_tail(&RPKI_CFG->cache_cfg_list, &this_rpki_cache_cfg->n); +} +; + +rpki_cache_finish: +{ + if (this_rpki_cache_cfg->port == 0) /* empty? */ + { + if (this_rpki_cache_cfg->ssh != NULL) + this_rpki_cache_cfg->port = RPKI_DEFAULT_SSH_PORT; + else + this_rpki_cache_cfg->port = RPKI_DEFAULT_PORT; + } +} +; + +rpki_cache_init: +{ + this_rpki_cache_cfg = rpki_new_cache_cfg(); +} +; + +rpki_cache_addr: +text { + this_rpki_cache_cfg->hostname = $1; +} +| ipa { + this_rpki_cache_cfg->ip = $1; + this_rpki_cache_cfg->hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN+1)); + bsnprintf(this_rpki_cache_cfg->hostname, INET6_ADDRSTRLEN+1, "%I", this_rpki_cache_cfg->ip); +} +; + +rpki_optional_cache_opts: +/* empty */ +| '{' rpki_cache_opts '}' +; + +rpki_cache_opts: + /* empty */ + | rpki_cache_opts rpki_cache_opts_item ';' + ; + +rpki_cache_opts_item: + PORT expr { + check_u16($2); + this_rpki_cache_cfg->port = $2; + } + | PREFERENCE expr { + if ($2 < 1 || $2 > 0xFF) + cf_error("Value %d is out of range (1-255)", $2); + this_rpki_cache_cfg->preference = $2; + } + | REFRESH expr { this_rpki_cache_cfg->refresh_interval = $2; } + | RETRY expr { this_rpki_cache_cfg->retry_interval = $2; } + | EXPIRE expr { this_rpki_cache_cfg->expire_interval = $2; } + | SSH ENCRYPTION rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_finish + ; + +rpki_transport_ssh_init: +{ + this_rpki_cache_cfg->ssh = cfg_allocz(sizeof(struct rpki_cache_ssh_cfg)); +} +; + +rpki_transport_ssh_opts: +/* empty */ +| rpki_transport_ssh_opts rpki_transport_ssh_item ';' +; + +rpki_transport_ssh_item: +BIRD PRIVATE KEY text { + check_file_readability($4); + this_rpki_cache_cfg->ssh->bird_private_key = $4; +} +| CACHE PUBLIC KEY text { + check_file_readability($4); + this_rpki_cache_cfg->ssh->cache_public_key = $4; +} +| USER text { + this_rpki_cache_cfg->ssh->username = $2; +} +; + +rpki_transport_ssh_finish: +{ +#define RPKI_PARSE_CACHE_MISS_SSH_OPT(what) "Miss '" what ";' option in the %s protocol at cache server %s inside the ssh encryption block" + + if (!this_rpki_cache_cfg->ssh->username) + cf_error(RPKI_PARSE_CACHE_MISS_SSH_OPT("user \"ssh_username\""), RPKI_CFG->c.name, this_rpki_cache_cfg->hostname); +} + +CF_CODE + +CF_END diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c new file mode 100644 index 000000000..7e628cbf4 --- /dev/null +++ b/proto/rpki/packets.c @@ -0,0 +1,1020 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include + +#include "rpki.h" + +#include "transport.h" +#include "packets.h" +#include "utils.h" +#include "rtr.h" + +#define RPKI_PREFIX_FLAG_ADD 1 +#define RPKI_PREFIX_FLAG_DELETE 0 + +enum rpki_transmit_type { + RPKI_RECV = 0, + RPKI_SEND = 1, +}; + +enum pdu_error_type { + CORRUPT_DATA = 0, + INTERNAL_ERROR = 1, + NO_DATA_AVAIL = 2, + INVALID_REQUEST = 3, + UNSUPPORTED_PROTOCOL_VER = 4, + UNSUPPORTED_PDU_TYPE = 5, + WITHDRAWAL_OF_UNKNOWN_RECORD = 6, + DUPLICATE_ANNOUNCEMENT = 7, + PDU_TOO_BIG = 32 +}; + +enum pdu_type { + SERIAL_NOTIFY = 0, + SERIAL_QUERY = 1, + RESET_QUERY = 2, + CACHE_RESPONSE = 3, + IPV4_PREFIX = 4, + IPV6_PREFIX = 6, + END_OF_DATA = 7, + CACHE_RESET = 8, + ROUTER_KEY = 9, + ERROR = 10 +}; + +static const char *str_pdu_type[] = { + [SERIAL_NOTIFY] = "Serial Notify", + [SERIAL_QUERY] = "Serial Query", + [RESET_QUERY] = "Reset Query", + [CACHE_RESPONSE] = "Cache Response", + [IPV4_PREFIX] = "IPv4 Prefix", + [IPV6_PREFIX] = "IPv6 Prefix", + [END_OF_DATA] = "End of Data", + [CACHE_RESET] = "Cache Reset", + [ROUTER_KEY] = "Router Key", + [ERROR] = "Error" +}; + +struct pdu_header { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; +}; + +struct pdu_cache_response { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; +}; + +struct pdu_serial_notify { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_serial_query { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_ipv4 { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; + uint8_t flags; + uint8_t prefix_len; + uint8_t max_prefix_len; + uint8_t zero; + uint32_t prefix; + uint32_t asn; +}; + +struct pdu_ipv6 { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; + uint8_t flags; + uint8_t prefix_len; + uint8_t max_prefix_len; + uint8_t zero; + uint32_t prefix[4]; + uint32_t asn; +}; + +struct pdu_error { + uint8_t ver; + uint8_t type; + uint16_t error_code; + uint32_t len; + uint32_t len_enc_pdu; + uint8_t rest[]; +}; + +/* + 0 8 16 24 31 + .-------------------------------------------. + | Protocol | PDU | | + | Version | Type | reserved = zero | + | 0 | 2 | | + +-------------------------------------------+ + | | + | Length=8 | + | | + `-------------------------------------------' + */ +struct pdu_reset_query { + uint8_t ver; + uint8_t type; + uint16_t flags; + uint32_t len; +}; + +struct pdu_end_of_data_v0 { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_end_of_data_v1 { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; + uint32_t refresh_interval; + uint32_t retry_interval; + uint32_t expire_interval; +}; + +static int rtr_send_error_pdu(struct rpki_cache *cache, const void *erroneous_pdu, const uint32_t pdu_len, const enum pdu_error_type error, const char *text, const uint32_t text_len); + +static inline enum pdu_type rtr_get_pdu_type(const void *pdu) +{ + return *((char *) pdu + 1); +} + +static void +pfx_table_add(struct rpki_cache *cache, const net_addr_union *pfxr) +{ + struct rpki_proto *p = cache->p; + + char addr_buf[100]; + net_format((net_addr *)pfxr, addr_buf, sizeof(addr_buf)); + CACHE_TRACE(D_EVENTS, cache, "Import %s", addr_buf); + + net *n = net_get(cache->p->p.table, &pfxr->n); + + rta a0 = { + .src = rt_get_source(&p->p, cache->cache_id), + .source = RTS_RPKI, + .scope = SCOPE_UNIVERSE, + .cast = RTC_UNICAST, + .dest = RTD_BLACKHOLE, + }; + + rta *a = rta_lookup(&a0); + rte *e = rte_get_temp(a); + + e->net = n; + e->pflags = 0; + + rte_update2(p->p.main_ahook, n, e, a0.src); +} + +static void +pfx_table_remove(struct rpki_cache *cache, const net_addr_union *pfxr) +{ + struct rpki_proto *p = cache->p; + + char addr_buf[100]; + net_format((net_addr *)pfxr, addr_buf, sizeof(addr_buf)); + CACHE_TRACE(D_EVENTS, cache, "Remove %s", addr_buf); + + net *n = net_get(cache->p->p.table, &pfxr->n); + rte_update2(p->p.main_ahook, n, NULL, rt_get_source(&cache->p->p, cache->cache_id)); +} + +void +pfx_table_src_remove(struct rpki_cache *cache) +{ + CACHE_TRACE(D_EVENTS, cache, "Remove all ROA entries learned from %s", get_cache_ident(cache)); + + /* + * TODO: The code below will be replaced with using channels technology + */ + + rtable *tab = cache->p->p.table; + struct fib_iterator fit; + struct fib *fib = &tab->fib; + + FIB_ITERATE_INIT(&fit, fib); + FIB_ITERATE_START(fib, &fit, net, n) + { + if (n->routes) + { + rte *e, *safety_next; + for (e = n->routes; rte_is_valid(e); ) + { + safety_next = e->next; + if (e->attrs && e->attrs->src && e->attrs->src->private_id == cache->cache_id) + rte_discard(tab, e); + e = safety_next; + } + } + } FIB_ITERATE_END; +} + + +static void rtr_pdu_to_network_byte_order(void *pdu) +{ + struct pdu_header *header = pdu; + + header->reserved = htons(header->reserved); + header->len = htonl(header->len); + + const enum pdu_type type = rtr_get_pdu_type(pdu); + switch (type) + { + case SERIAL_QUERY: + { + struct pdu_serial_query *sq_pdu = pdu; + sq_pdu->sn = htonl(sq_pdu->sn); + break; + } + + case ERROR: + { + struct pdu_error *err_pdu = pdu; + err_pdu->len_enc_pdu = htonl(err_pdu->len_enc_pdu); + break; + } + + default: + break; + } +} + +static void rtr_pdu_footer_to_host_byte_order(void *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + struct pdu_header *header = pdu; + + switch (type) + { + case SERIAL_NOTIFY: + { + struct pdu_serial_notify *sn_pdu = pdu; + sn_pdu->sn = ntohl(sn_pdu->sn); + break; + } + + case END_OF_DATA: + { + struct pdu_end_of_data_v0 *eod0 = pdu; + eod0->sn = ntohl(eod0->sn); /* same either for version 1 */ + + if (header->ver == RTR_PROTOCOL_VERSION_1) + { + struct pdu_end_of_data_v1 *eod1 = pdu; + eod1->expire_interval = ntohl(eod1->expire_interval); + eod1->refresh_interval = ntohl(eod1->refresh_interval); + eod1->retry_interval = ntohl(eod1->retry_interval); + } + break; + } + + case IPV4_PREFIX: + { + struct pdu_ipv4 *ipv4 = pdu; + ipv4->prefix = ntohl(ipv4->prefix); + ipv4->asn = ntohl(ipv4->asn); + break; + } + + case IPV6_PREFIX: + { + struct pdu_ipv6 *ipv6 = pdu; + ip6_addr addr6 = ip6_ntoh(ip6_build(ipv6->prefix[0], ipv6->prefix[1], ipv6->prefix[2], ipv6->prefix[3])); + memcpy(ipv6->prefix, &addr6, sizeof(ipv6->prefix)); + ipv6->asn = ntohl(ipv6->asn); + break; + } + + case ERROR: + { + struct pdu_error *err = pdu; + err->len_enc_pdu = ntohl(err->len_enc_pdu); + break; + } + + case SERIAL_QUERY: + case RESET_QUERY: + case CACHE_RESPONSE: + case CACHE_RESET: + case ROUTER_KEY: + break; + } +} + +static void rtr_pdu_header_to_host_byte_order(void *pdu) +{ + struct pdu_header *header = pdu; + + //The ROUTER_KEY PDU has two 1 Byte fields instead of the 2 Byte reserved field. + if (header->type != ROUTER_KEY) + { + uint16_t reserved_tmp = ntohs(header->reserved); + header->reserved = reserved_tmp; + } + + uint32_t len_tmp = ntohl(header->len); + header->len = len_tmp; +} + +static void +rpki_log_packet(struct rpki_cache *cache, const void *pdu, const size_t len, const enum rpki_transmit_type action) +{ + if (cache->p->p.debug & D_PACKETS) + { + const char *str_type = str_pdu_type[rtr_get_pdu_type(pdu)]; + const struct pdu_header *header = pdu; + + /* Append session id and serial number */ + char detail[100]; + switch (header->type) + { + case SERIAL_NOTIFY: + case SERIAL_QUERY: + case END_OF_DATA: + bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", header->reserved, ((struct pdu_end_of_data_v0 *)header)->sn); + break; + + case CACHE_RESPONSE: + bsnprintf(detail, sizeof(detail), "(session id: %u)", header->reserved); + break; + + case IPV4_PREFIX: + { + const struct pdu_ipv4 *ipv4 = pdu; + bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ip4_from_u32(ipv4->prefix), ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn); + break; + } + + case IPV6_PREFIX: + { + const struct pdu_ipv6 *ipv6 = pdu; + ip6_addr a = ip6_build(ipv6->prefix[0], ipv6->prefix[1], ipv6->prefix[2], ipv6->prefix[3]); + bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", a, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn); + break; + } + + default: + *detail = '\0'; + } + + if (action == RPKI_RECV) + { + CACHE_TRACE(D_PACKETS, cache, "Receive a %s packet %s", str_type, detail); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Send a %s packet %s", str_type, detail); + } + } + + int seq = 0; + for(const byte *c = pdu; c != pdu + len; c++) + { + if ((seq % 4) == 0) + DBG("%2d: ", seq); + + DBG(" 0x%02X %-3u", *c, *c); + + if ((++seq % 4) == 0) + DBG("\n"); + } + if ((seq % 4) != 0) + DBG("\n"); +} + +static int +rtr_send_pdu(struct rpki_cache *cache, const void *pdu, const unsigned len) +{ + const struct rtr_socket *rtr_socket = cache->rtr_socket; + struct rpki_proto *p = cache->p; + sock *sk = cache->sk; + + if (!sk) + { + RPKI_WARN(p, "Want send a %s packet, but the bird socket is NULL!", str_pdu_type[rtr_get_pdu_type(pdu)]); + ASSERT(0); + return RTR_ERROR; + } + + if (sk->fd < 0) + { + RPKI_WARN(p, "Want send a %s packet, but the bird socket FD is %d!", str_pdu_type[rtr_get_pdu_type(pdu)], sk->fd); + ASSERT(0); + return RTR_ERROR; + } + + if (rtr_socket->state == RTR_SHUTDOWN) + { + RPKI_WARN(p, "Want send a %s packet, but the rtr_socket state is SHUTDOWN!", str_pdu_type[rtr_get_pdu_type(pdu)]); + ASSERT(0); + return RTR_ERROR; + } + + rpki_log_packet(cache, pdu, len, RPKI_SEND); + + byte pdu_converted[len]; + memcpy(pdu_converted, pdu, len); + rtr_pdu_to_network_byte_order(pdu_converted); + + sk->tbuf = pdu_converted; + if (!sk_send(sk, len)) + { + DBG("Cannot send just the whole data. It will be sended via a call of tx_hook()"); + } + + return RTR_SUCCESS; +} + +/** + * rtr_check_receive_packet - Make a basic validation of received RPKI PDU header: + * - check protocol version + * - check pdu type + * - check size + * + * @cache cache connection + * @param len must <= RTR_MAX_PDU_LEN bytes + * @return RTR_SUCCESS + * @return RTR_ERROR, error pdu was sent + */ +static int +rtr_check_receive_packet(struct rpki_cache *cache, void *pdu, const size_t len) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct rpki_proto *p = cache->p; + int error = RTR_SUCCESS; + + //header in hostbyte order, retain original received pdu, in case we need to detach it to an error pdu + struct pdu_header header; + memcpy(&header, pdu, sizeof(header)); + rtr_pdu_header_to_host_byte_order(&header); + + if (rtr_socket->state == RTR_SHUTDOWN) + { + RPKI_WARN(p, "Received %s packet, but rtr_socket->state == RTR_SHUTDOWN", str_pdu_type[header.type]); + ASSERT(rtr_socket->state != RTR_SHUTDOWN); + return RTR_ERROR; + } + + // Do dont handle error PDUs here, leave this task to rtr_handle_error_pdu() + if (header.ver != rtr_socket->version && header.type != ERROR) + { + // If this is the first PDU we have received -> Downgrade. + if (rtr_socket->request_session_id == true && rtr_socket->last_update == 0 + && header.ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION + && header.ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION + && header.ver < rtr_socket->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", get_cache_ident(cache), rtr_socket->version, header.ver); + rtr_socket->version = header.ver; + } + else + { + // If this is not the first PDU we have received, something is wrong with + // the server implementation -> Error + error = UNSUPPORTED_PROTOCOL_VER; + goto error; + } + } + + if ((header.type > 10) || (header.ver == RTR_PROTOCOL_VERSION_0 && header.type == ROUTER_KEY)) + { + error = UNSUPPORTED_PDU_TYPE; + goto error; + } + + if (header.len < sizeof(header)) + { + //if header->len is < packet_header = corrupt data received + error = CORRUPT_DATA; + goto error; + } + else if (header.len > RPKI_PDU_MAX_LEN) + { + //PDU too big, > than MAX_PDU_LEN Bytes + error = PDU_TOO_BIG; + goto error; + } + + if (header.type == IPV4_PREFIX || header.type == IPV6_PREFIX) { + if (((struct pdu_ipv4 *) pdu)->zero != 0) + CACHE_TRACE(D_PACKETS, cache, "Warning: Zero field of received Prefix PDU doesn't contain 0"); + } + + return RTR_SUCCESS; + + error: + //send error msg to server, including unmodified pdu header(pdu variable instead header) + switch (error) + { + case CORRUPT_DATA: + { + const char *txt = "Corrupt data received, length value in PDU is too small"; + CACHE_TRACE(D_PACKETS, cache, "%s", txt); + rtr_send_error_pdu(cache, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt)); + break; + } + + case PDU_TOO_BIG: + { + char txt2[64]; + snprintf(txt2, sizeof(txt2),"PDU too big, max. PDU size is: %u bytes", RPKI_PDU_MAX_LEN); + CACHE_TRACE(D_EVENTS, cache, "%s", txt2); + rtr_send_error_pdu(cache, pdu, sizeof(header), CORRUPT_DATA, txt2, strlen(txt2)+1); + break; + } + + case UNSUPPORTED_PDU_TYPE: + CACHE_DBG(cache, "Unsupported PDU type %zu received", header.type); + rtr_send_error_pdu(cache, pdu, header.len, UNSUPPORTED_PDU_TYPE, NULL, 0); + break; + + case UNSUPPORTED_PROTOCOL_VER: + CACHE_TRACE(D_EVENTS, cache, "PDU with unsupported Protocol version received"); + rtr_send_error_pdu(cache, pdu, header.len, UNSUPPORTED_PROTOCOL_VER, NULL, 0); + break; + + default: + bug("Uncatched error"); + } + + return RTR_ERROR; +} + +static int +rtr_handle_error_pdu(struct rtr_socket *rtr_socket, const void *buf) +{ + struct rpki_cache *cache = rtr_socket->cache; + const struct pdu_error *pdu = buf; + + const uint32_t len_err_txt = ntohl(*((uint32_t *) (pdu->rest + pdu->len_enc_pdu))); + if (len_err_txt > 0) + { + if ((sizeof(pdu->ver) + sizeof(pdu->type) + sizeof(pdu->error_code) + sizeof(pdu->len) + sizeof(pdu->len_enc_pdu) + pdu->len_enc_pdu + 4 + len_err_txt) != pdu->len) + CACHE_TRACE(D_PACKETS, cache, "Error: Length of error text contains an incorrect value"); + else + { + //assure that the error text contains an terminating \0 char + char txt[len_err_txt + 1]; + char *pdu_txt = (char *) pdu->rest + pdu->len_enc_pdu + 4; + snprintf(txt, len_err_txt + 1, "%s", pdu_txt); + CACHE_TRACE(D_PACKETS, cache, "Error PDU included the following error msg: \'%s\'", txt); + } + } + + switch (pdu->error_code) + { + case CORRUPT_DATA: + CACHE_TRACE(D_PACKETS, cache, "Corrupt data received"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + + case INTERNAL_ERROR: + CACHE_TRACE(D_PACKETS, cache, "Internal error on server-side"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + + case NO_DATA_AVAIL: + CACHE_TRACE(D_PACKETS, cache, "No data available"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_DATA_AVAIL); + break; + + case INVALID_REQUEST: + CACHE_TRACE(D_PACKETS, cache, "Invalid request from client"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + + case UNSUPPORTED_PROTOCOL_VER: + CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version"); + if (pdu->ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION && + pdu->ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION && + pdu->ver < rtr_socket->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrading from %i to version %i", rtr_socket->version, pdu->ver); + rtr_socket->version = pdu->ver; + rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \ + "current version: %i, PDU version: %i", rtr_socket->version, pdu->ver); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + break; + + case UNSUPPORTED_PDU_TYPE: + CACHE_TRACE(D_PACKETS, cache, "Client set unsupported PDU type"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + + default: + CACHE_TRACE(D_PACKETS, cache, "error unknown, server sent unsupported error code %u", pdu->error_code); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + } + + return RTR_SUCCESS; +} + +static int rtr_handle_cache_response_pdu(struct rpki_cache *cache, char *pdu) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct pdu_cache_response *cr_pdu = (struct pdu_cache_response *) pdu; + //set connection session_id + if (rtr_socket->request_session_id) + { + if (rtr_socket->last_update != 0) + { + // if this isnt the first sync, but we already received records, delete old records in the pfx_table + pfx_table_src_remove(cache); + rtr_socket->last_update = 0; + } + rtr_socket->session_id = cr_pdu->session_id; + rtr_socket->request_session_id = false; + } + else + { + if (rtr_socket->session_id != cr_pdu->session_id) + { + char txt[100]; + snprintf(txt, 100, "Wrong session_id %u in Cache Response PDU", cr_pdu->session_id); + rtr_send_error_pdu(cache, NULL, 0, CORRUPT_DATA, txt, strlen(txt)+1); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + } + return RTR_SUCCESS; +} + +static net_addr_union +rtr_prefix_pdu_2_net_addr(const void *pdu) +{ + net_addr_union n = {}; + const enum pdu_type type = rtr_get_pdu_type(pdu); + + if (type == IPV4_PREFIX) + { + const struct pdu_ipv4 *ipv4 = pdu; + n.roa4.type = NET_ROA4; + n.roa4.length = sizeof(net_addr_roa4); + n.roa4.prefix = ip4_from_u32(ipv4->prefix); + n.roa4.asn = ipv4->asn; + n.roa4.pxlen = ipv4->prefix_len; + n.roa4.max_pxlen = ipv4->max_prefix_len; + } + else if (type == IPV6_PREFIX) + { + const struct pdu_ipv6 *ipv6 = pdu; + n.roa6.type = NET_ROA6; + n.roa6.length = sizeof(net_addr_roa6); + memcpy(&n.roa6.prefix, ipv6->prefix, sizeof(n.roa6.prefix)); + n.roa6.asn = ipv6->asn; + n.roa6.pxlen = ipv6->prefix_len; + n.roa6.max_pxlen = ipv6->max_prefix_len; + } + + return n; +} + +static int +rtr_handle_prefix_pdu(struct rpki_cache *cache, const void *pdu) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + + const enum pdu_type type = rtr_get_pdu_type(pdu); + ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX); + + /* TODO: Use channels and this wipe out */ + if (type == IPV4_PREFIX && cache->p->p.table->addr_type != NET_ROA4) + return RTR_ERROR; + if (type == IPV6_PREFIX && cache->p->p.table->addr_type != NET_ROA6) + return RTR_ERROR; + + net_addr_union addr = rtr_prefix_pdu_2_net_addr(pdu); + + switch (((struct pdu_ipv4 *) pdu)->flags) + { + case RPKI_PREFIX_FLAG_ADD: + pfx_table_add(cache, &addr); + break; + + case RPKI_PREFIX_FLAG_DELETE: + pfx_table_remove(cache, &addr); + break; + + default: + { + const char *txt = "Prefix PDU with invalid flags value received"; + size_t pdu_size = (type == IPV4_PREFIX ? sizeof(struct pdu_ipv4) : sizeof(struct pdu_ipv6)); + CACHE_DBG(cache, "%s", txt); + rtr_send_error_pdu(cache, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt)); + return RTR_ERROR; + } + } + + return RTR_SUCCESS; +} + +static void +rtr_handle_end_of_data_pdu(struct rpki_cache *cache, void *pdu) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct pdu_end_of_data_v1 *eod_pdu = pdu; + + if (eod_pdu->ver == RTR_PROTOCOL_VERSION_1) + { + rtr_socket->expire_interval = eod_pdu->expire_interval; + rtr_socket->refresh_interval = eod_pdu->refresh_interval; + rtr_socket->retry_interval = eod_pdu->retry_interval; + CACHE_TRACE(D_EVENTS, cache, "New interval values: " \ + "expire_interval: %us, " \ + "refresh_interval: %us, " \ + "retry_interval: %us", \ + rtr_socket->expire_interval, rtr_socket->refresh_interval, rtr_socket->retry_interval); + } + + if (eod_pdu->session_id != rtr_socket->session_id) + { + char txt[67]; + snprintf(txt, sizeof(txt),"Received session_id %u, but expected was session_id %u", eod_pdu->session_id, rtr_socket->session_id); + CACHE_TRACE(D_EVENTS, cache, "%s", txt); + rtr_send_error_pdu(cache, pdu, eod_pdu->len, CORRUPT_DATA, txt, strlen(txt) + 1); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + + rtr_socket->last_update = now; + rtr_socket->serial_number = eod_pdu->sn; + rtr_change_socket_state(rtr_socket, RTR_ESTABLISHED); + rtr_schedule_next_refresh(cache); + rtr_schedule_next_expire_check(cache); +} + +static void +rtr_transform_pdu_to_host_byte_order(byte *pdu) +{ + rtr_pdu_header_to_host_byte_order(pdu); + rtr_pdu_footer_to_host_byte_order(pdu); +} + +static void +rpki_rx_packet(struct rpki_cache *cache, byte *pdu, uint len) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct rpki_proto *p = cache->p; + enum pdu_type type = rtr_get_pdu_type(pdu); + + if (rtr_check_receive_packet(cache, pdu, len) == RTR_ERROR) + { + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return; + } + + rtr_transform_pdu_to_host_byte_order(pdu); + rpki_log_packet(cache, pdu, len, RPKI_RECV); + + switch (type) + { + case RESET_QUERY: + case SERIAL_QUERY: + RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type[type]); + break; + + case SERIAL_NOTIFY: + /* This is a signal to synchronize with the cache server just now */ + rtr_change_socket_state(rtr_socket, RTR_SYNC); + break; + + case CACHE_RESPONSE: + rtr_handle_cache_response_pdu(cache, pdu); + break; + + case IPV4_PREFIX: + case IPV6_PREFIX: + rtr_handle_prefix_pdu(cache, pdu); + break; + + case END_OF_DATA: + rtr_handle_end_of_data_pdu(cache, pdu); + break; + + case CACHE_RESET: + /* The cache may respond to a Serial Query informing the router that the + * cache cannot provide an incremental update starting from the Serial + * Number specified by the router. The router must decide whether to + * issue a Reset Query or switch to a different cache. */ + rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_INCR_UPDATE_AVAIL); + break; + + case ERROR: + rtr_handle_error_pdu(cache->rtr_socket, pdu); + break; + + case ROUTER_KEY: + default: + CACHE_TRACE(D_PACKETS, cache, "Received unsupported type of RPKI PDU (%u)", type); + }; +} + +int +rpki_rx_hook(struct birdsock *sk, int size) +{ + struct rpki_cache *cache = sk->data; + struct rpki_proto *p = cache->p; + + byte *pkt_start = sk->rbuf; + byte *end = pkt_start + size; + + DBG("Rx hook got %d bytes", size); + + while (end >= pkt_start + RPKI_PDU_HEADER_LEN) + { + struct pdu_header header; + memcpy(&header, pkt_start, sizeof(header)); + rtr_pdu_header_to_host_byte_order(&header); + + if (header.len < RPKI_PDU_HEADER_LEN || header.len > RPKI_PDU_MAX_LEN) + { + RPKI_WARN(p, "Received invalid packet length %u. Purge the whole receive buffer.", header.len); + return 1; /* Purge recv buffer */ + } + + if (end < pkt_start + header.len) + break; + + rpki_rx_packet(cache, pkt_start, header.len); + + /* It is possible that bird socket was freed/closed */ + if (sk != cache->sk) + return 0; + + pkt_start += header.len; + } + + if (pkt_start != sk->rbuf) + { + memmove(sk->rbuf, pkt_start, end - pkt_start); + sk->rpos = sk->rbuf + (end - pkt_start); + } + + return 0; /* Not purge sk->rbuf */ +} + +void +rpki_err_hook(struct birdsock *sk, int error_num) +{ + struct rpki_cache *cache = sk->data; + + if (error_num && sk->err == NULL) + { + CACHE_TRACE(D_EVENTS, cache, "Connection lost %s: %M", get_cache_ident(cache), error_num); + } + else + { + CACHE_TRACE(D_EVENTS, cache, "Connection lost %s: %s", get_cache_ident(cache), sk->err); + } + + rtr_change_socket_state(cache->rtr_socket, RTR_ERROR_TRANSPORT); +} + +static int +rpki_fire_tx(struct rpki_cache *cache) +{ + sock *sk = cache->sk; + + uint bytes_to_send = sk->tpos - sk->tbuf; + DBG("Sending %u bytes", bytes_to_send); + return sk_send(sk, bytes_to_send); +} + +void +rpki_kick_tx(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + while (rpki_fire_tx(cache) > 0) + ; +} + +void +rpki_tx_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + while (rpki_fire_tx(cache) > 0) + ; +} + +void +rpki_connected_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + CACHE_TRACE(D_EVENTS, cache, "Connected to %s", get_cache_ident(cache)); + + sk->rx_hook = rpki_rx_hook; + sk->tx_hook = rpki_tx_hook; + + rtr_change_socket_state(cache->rtr_socket, RTR_CONNECTING); +} + +int rtr_send_error_pdu(struct rpki_cache *cache, const void *erroneous_pdu, const uint32_t pdu_len, const enum pdu_error_type error, const char *text, const uint32_t text_len) +{ + const struct rtr_socket *rtr_socket = cache->rtr_socket; + + //dont send errors for erroneous error PDUs + if (pdu_len >= 2) + { + if (rtr_get_pdu_type(erroneous_pdu) == ERROR) + return RTR_SUCCESS; + } + + unsigned int msg_size = 16 + pdu_len + text_len; + char msg[msg_size]; + struct pdu_header *header = (struct pdu_header *) msg; + header->ver = rtr_socket->version; + header->type = 10; + header->reserved = error; + header->len = msg_size; + + memcpy(msg+8, &pdu_len, sizeof(pdu_len)); + if (pdu_len > 0) + memcpy(msg + 12, erroneous_pdu, pdu_len); + *(msg + 12 + pdu_len) = htonl(text_len); + if (text_len > 0) + memcpy(msg+16+pdu_len, text, text_len); + + return rtr_send_pdu(cache, msg, msg_size); +} + +int rtr_send_serial_query(struct rpki_cache *cache) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct pdu_serial_query pdu; + pdu.ver = rtr_socket->version; + pdu.type = SERIAL_QUERY; + pdu.session_id = rtr_socket->session_id; + pdu.len = sizeof(pdu); + pdu.sn = rtr_socket->serial_number; + + if (rtr_send_pdu(cache, &pdu, sizeof(pdu)) != RTR_SUCCESS) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } + return RTR_SUCCESS; +} + +int rtr_send_reset_query(struct rpki_cache *cache) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + CACHE_TRACE(D_EVENTS, cache, "Sending reset query"); + struct pdu_reset_query pdu = { + .ver = rtr_socket->version, + .type = RESET_QUERY, + .len = 8, + }; + + if (rtr_send_pdu(cache, &pdu, sizeof(pdu)) != RTR_SUCCESS) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } + return RTR_SUCCESS; +} diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h new file mode 100644 index 000000000..0fa62cd55 --- /dev/null +++ b/proto/rpki/packets.h @@ -0,0 +1,38 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file is part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef RTR_PACKETS_H +#define RTR_PACKETS_H +#include +#include "rtr.h" + +#define RPKI_RX_BUFFER_SIZE 65536 +#define RPKI_TX_BUFFER_SIZE 65536 +#define RPKI_PDU_HEADER_LEN 8 +#define RPKI_PDU_MAX_LEN 848 /* Error PDU size is the biggest (has encapsulate PDU inside): + * header(8) + + * len_of_encapsulated_pdu(4) + + * encapsulated_pdu_ipv6(32) + + * len_of_text(4) + + * utf-8 text(400*2) = 848 + */ +#define RPKI_RECV_TIMEOUT 60 +#define RPKI_SEND_TIMEOUT 60 + +int rtr_sync(struct rpki_cache *cache); +int rtr_wait_for_sync(struct rpki_cache *cache); +int rtr_send_serial_query(struct rpki_cache *cache); +int rtr_send_reset_query(struct rpki_cache *cache); +int rpki_rx_hook(struct birdsock *sk, int size); +void rpki_connected_hook(sock *sk); +void rpki_err_hook(struct birdsock *sk, int size); +void pfx_table_src_remove(struct rpki_cache *cache); + +#endif diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c new file mode 100644 index 000000000..fc3b7c12f --- /dev/null +++ b/proto/rpki/rpki.c @@ -0,0 +1,645 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * Using RTRLib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: RPKI to Router Protocol + * + * The Resource Public Key Infrastructure (RPKI) to router protocol implementation + * is based on the RTRlib (http://rpki.realmv6.org/). The BIRD takes over + * |packets.c|, |rtr.c|, |transport.c|, |tcp_transport.c| and |ssh_transport.c| files + * from RTRlib. + * + * A SSH transport requires LibSSH library. LibSSH is loading dynamically using dlopen + * function. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include +#include +#include "rpki.h" +#include "lib/idm.h" +#include "lib/string.h" +#include "lib/unix.h" + +static struct idm cache_uniq_id_generator; + +static const char *mgr_str_status[] = { + [RTR_MGR_CLOSED] = "RTR_MGR_CLOSED", + [RTR_MGR_CONNECTING] = "RTR_MGR_CONNECTING", + [RTR_MGR_ESTABLISHED] = "RTR_MGR_ESTABLISHED", + [RTR_MGR_ERROR] = "RTR_MGR_ERROR", +}; + +const char * +get_group_status(struct rpki_cache_group *group) +{ + return mgr_str_status[group->status]; +} + +void +rpki_init_all(void) +{ + idm_init(&cache_uniq_id_generator, &root_pool, 1); +} + +static struct proto * +rpki_init(struct proto_config *C) +{ + struct proto *P = proto_new(C, sizeof(struct rpki_proto)); + struct rpki_proto *p = (void *) P; + p->cf = (void *) C; + + init_list(&p->group_list); + + return P; +} + +const char * +get_cache_ident(struct rpki_cache *cache) +{ + return tr_ident(cache->rtr_socket->tr_socket); +} + +void +rpki_print_groups(struct rpki_proto *p) +{ + struct rpki_cache_group *g; + WALK_LIST(g, p->group_list) + { + DBG("Group(%u) %s \n", g->preference, get_group_status(g)); + + struct rpki_cache *c; + WALK_LIST(c, g->cache_list) + { + DBG(" Cache(%s) %s \n", get_cache_ident(c), rtr_state_to_str(c->rtr_socket->state)); + } + } +} + +static struct rpki_cache_group * +rpki_cache_group_alloc(struct rpki_proto *p, u8 preference) +{ + struct rpki_cache_group *new = mb_allocz(p->p.pool, sizeof(struct rpki_cache_group)); + init_list(&new->cache_list); + new->preference = preference; + return new; +} + +static struct rpki_cache_group * +rpki_new_cache_group_before(struct rpki_proto *p, struct rpki_cache_group *before, list *group_list, u8 preference) +{ + struct rpki_cache_group *new = rpki_cache_group_alloc(p, preference); + + if (&before->n == group_list->head) + add_head(group_list, &new->n); + else + insert_node(&new->n, before->n.prev); + + return new; +} + +static void +rpki_insert_cache_into_group(struct rpki_cache *cache) +{ + struct rpki_proto *p = cache->p; + struct rpki_cache_group *group_iter; + WALK_LIST(group_iter, p->group_list) + { + if (group_iter->preference == cache->cfg->preference) + { + add_tail(&group_iter->cache_list, &cache->n); + cache->group = group_iter; + return; + } + + if (group_iter->preference > cache->cfg->preference) + { + struct rpki_cache_group *new_group = rpki_new_cache_group_before(p, group_iter, &p->group_list, cache->cfg->preference); + add_tail(&new_group->cache_list, &cache->n); + cache->group = new_group; + return; + } + } + + struct rpki_cache_group *new_group = rpki_cache_group_alloc(p, cache->cfg->preference); + add_tail(&p->group_list, &new_group->n); + add_tail(&new_group->cache_list, &cache->n); + cache->group = new_group; +} + +struct rpki_cache_cfg * +rpki_new_cache_cfg(void) +{ + struct rpki_cache_cfg *cache = cfg_allocz(sizeof(struct rpki_cache_cfg)); + cache->preference = RPKI_DEFAULT_CACHE_PREFERENCE; + cache->ip = IPA_NONE; + + cache->retry_interval = RPKI_DEFAULT_RETRY_INTERVAL; + cache->refresh_interval = RPKI_DEFAULT_REFRESH_INTERVAL; + cache->expire_interval = RPKI_DEFAULT_EXPIRE_INTERVAL; + + /* The port number will be set afterwards */ + return cache; +} + +struct rpki_cache * +rpki_new_cache(struct rpki_proto *p, struct rpki_cache_cfg *cache_cfg) +{ + struct rpki_cache *cache = mb_allocz(p->p.pool, sizeof(struct rpki_cache)); + struct rtr_socket *rtr_socket = mb_allocz(p->p.pool, sizeof(struct rtr_socket)); + struct tr_socket *tr_socket = mb_allocz(p->p.pool, sizeof(struct tr_socket)); + + cache->p = p; + cache->cfg = cache_cfg; + cache->cache_id = idm_alloc(&cache_uniq_id_generator); + cache->retry_timer = tm_new_set(p->p.pool, &rpki_retry_hook, cache, 0, 0); + cache->refresh_timer = tm_new_set(p->p.pool, &rpki_refresh_hook, cache, 0, 0); + cache->expire_timer = tm_new_set(p->p.pool, &rpki_expire_hook, cache, 0, 0); + cache->rtr_socket = rtr_socket; + cache->rtr_socket->tr_socket = tr_socket; + cache->rtr_socket->cache = cache; + + if (cache_cfg->ssh) + tr_ssh_init(cache); + else + tr_tcp_init(cache); + + rtr_init(rtr_socket, cache_cfg->refresh_interval, cache_cfg->expire_interval, cache_cfg->retry_interval); + + return cache; +} + +/* + * Close connection without change a status + */ +void +rpki_close_connection(struct rpki_cache *cache) +{ + sock *sk = cache->sk; + + if (sk) + { + CACHE_TRACE(D_EVENTS, cache, "Close the connection"); + tr_close(cache->rtr_socket->tr_socket); + rfree(sk); + cache->sk = NULL; + } +} + +/* + * Fulfill sock->af and sock->daddr if empty + * Return TR_SUCCESS or TR_ERROR + */ +static int +rpki_sock_dst_autoresolv(sock *sk) +{ + if (ipa_zero(sk->daddr) && sk->host) + { + struct addrinfo *res; + struct addrinfo *bind_addrinfo = NULL; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG, + }; + + char port[6]; /* max is "65535" + '\0' */ + bsnprintf(port, sizeof(port), "%u", sk->dport); + + if (getaddrinfo(sk->host, port, &hints, &res) != 0) + { + CACHE_TRACE(D_EVENTS, (struct rpki_cache *)sk->data, "getaddrinfo error, %s", gai_strerror(errno)); + return TR_ERROR; + } + + sk->af = res->ai_family; + + sockaddr sa = { + .sa = *res->ai_addr, + }; + + uint unused; + sockaddr_read(&sa, res->ai_family, &sk->daddr, NULL, &unused); + + freeaddrinfo(res); + } + else if (ipa_zero(sk->daddr) && !sk->host) + return TR_ERROR; + else + sk->af = ip6_is_v4mapped(sk->daddr) ? AF_INET : AF_INET6; + + return TR_SUCCESS; +} + +int +rpki_open_connection(struct rpki_cache *cache) +{ + struct rpki_proto *p = cache->p; + struct tr_socket *tr_socket = cache->rtr_socket->tr_socket; + CACHE_TRACE(D_EVENTS, cache, "Open a connection"); + + ASSERT(cache->sk == NULL); + + cache->sk = sk_new(p->p.pool); + sock *sk = cache->sk; + rtr_change_socket_state(cache->rtr_socket, RTR_OPENING); + + sk->tx_hook = rpki_connected_hook; + sk->err_hook = rpki_err_hook; + sk->data = cache; + sk->daddr = cache->cfg->ip; + sk->dport = cache->cfg->port; + sk->host = cache->cfg->hostname; + sk->rbsize = RPKI_RX_BUFFER_SIZE; + sk->tbsize = RPKI_TX_BUFFER_SIZE; + sk->tos = IP_PREC_INTERNET_CONTROL; + sk->type = -1; /* must be set in the specific transport layer in tr_open() */ + rpki_sock_dst_autoresolv(sk); + + if (tr_open(tr_socket) == TR_ERROR) + { + sk_log_error(sk, p->p.name); + rtr_change_socket_state(cache->rtr_socket, RTR_ERROR_TRANSPORT); + return TR_ERROR; + } + + return TR_SUCCESS; +} + +/* + * Open connections to all caches in group + */ +static void +rpki_open_group(struct rpki_cache_group *group) +{ + struct rpki_cache *cache; + WALK_LIST(cache, group->cache_list) + { + if (cache->rtr_socket->state == RTR_SHUTDOWN) + rpki_open_connection(cache); + } +} + +static void +rpki_close_group(struct rpki_cache_group *group) +{ + struct rpki_cache *cache; + WALK_LIST(cache, group->cache_list) + { + if (cache->rtr_socket->state != RTR_SHUTDOWN) + rtr_change_socket_state(cache->rtr_socket, RTR_SHUTDOWN); + } +} + +static void +rpki_remove_cache_from_group(struct rpki_cache *cache) +{ + rem2_node(&cache->n); +} + +static void +rpki_free_cache(struct rpki_cache *cache) +{ + rpki_remove_cache_from_group(cache); + rpki_close_connection(cache); + pfx_table_src_remove(cache); + + tr_free(cache->rtr_socket->tr_socket); + mb_free(cache->rtr_socket->tr_socket); + mb_free(cache->rtr_socket); + + /* Timers */ + tm_stop(cache->retry_timer); + tm_stop(cache->refresh_timer); + tm_stop(cache->expire_timer); + + rfree(cache->retry_timer); + rfree(cache->refresh_timer); + rfree(cache->expire_timer); + + idm_free(&cache_uniq_id_generator, cache->cache_id); + + mb_free(cache); +} + +static void +rpki_stop_and_free_caches(struct rpki_proto *p) +{ + struct rpki_cache_group *group; + WALK_LIST_FIRST(group, p->group_list) + { + struct rpki_cache *cache; + WALK_LIST_FIRST(cache, group->cache_list) + { + rem_node(NODE cache); + rpki_free_cache(cache); + } + rem_node(NODE group); + mb_free(group); + } + + proto_notify_state(&p->p, PS_DOWN); +} + +static int +rpki_shutdown(struct proto *P) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + + rpki_stop_and_free_caches(p); + + return PS_DOWN; +} + +static int +are_port_and_host_same(struct rpki_cache_cfg *a, struct rpki_cache_cfg *b) +{ + return ( + (a->port == b->port) && + ( + (a->hostname && b->hostname && strcmp(a->hostname, b->hostname) == 0) || + (ipa_nonzero(a->ip) && (ipa_compare(a->ip, b->ip) == 0)) + ) + ); +} + +static struct rpki_cache_cfg * +find_cache_cfg_by_host_and_port(list *cache_list, struct rpki_cache_cfg *needle) +{ + struct rpki_cache_cfg *cache_cfg; + WALK_LIST(cache_cfg, *cache_list) + { + if (are_port_and_host_same(needle, cache_cfg)) + return cache_cfg; + } + return NULL; +} + +static struct rpki_cache * +find_cache_in_proto_by_host_and_port(struct rpki_proto *p, struct rpki_cache_cfg *needle) +{ + struct rpki_cache_group *group; + WALK_LIST(group, p->group_list) + { + struct rpki_cache *cache; + WALK_LIST(cache, group->cache_list) + { + if (are_port_and_host_same(needle, cache->cfg)) + return cache; + } + } + return NULL; +} + +static void +remove_empty_cache_groups(struct rpki_proto *p) +{ + struct rpki_cache_group *group, *group_nxt; + WALK_LIST_DELSAFE(group, group_nxt, p->group_list) + { + if (EMPTY_LIST(group->cache_list)) + rem_node(&group->n); + } +} + +/* + * Move cache into `cache->cfg->preference` preference + */ +static void +move_cache_into_group(struct rpki_cache *cache) +{ + rpki_remove_cache_from_group(cache); + rpki_insert_cache_into_group(cache); + remove_empty_cache_groups(cache->p); +} + +/* + * Go through the group list ordered by priority. + * Open the first CLOSED group or stop opening groups if the processed group state is CONNECTING or ESTABLISHED + * Then close all groups with the more unimportant priority + */ +void +rpki_relax_groups(struct rpki_proto *p) +{ + DBG("Relaxing groups...\n"); + if (EMPTY_LIST(p->group_list)) + { + RPKI_WARN(p, "No cache in configuration found"); + return; + } + + bool close_all_next_groups = false; + + struct rpki_cache_group *group; + WALK_LIST(group, p->group_list) + { + if (!close_all_next_groups) + { + switch (group->status) + { + case RTR_MGR_CLOSED: + RPKI_TRACE(D_EVENTS, p, "Open cache group(%u)", group->preference); + /* Fall through */ + case RTR_MGR_CONNECTING: + case RTR_MGR_ESTABLISHED: + close_all_next_groups = 1; + /* Fall through */ + case RTR_MGR_ERROR: + rpki_open_group(group); + break; + } + } + else + { + RPKI_TRACE(D_EVENTS, p, "Close cache group(%u)", group->preference); + rpki_close_group(group); + } + } + + rpki_print_groups(p); + return; +} + +static int +rpki_reconfigure_proto(struct rpki_proto *p, struct rpki_config *new_cf, struct rpki_config *old_cf) +{ + if (old_cf->c.table && new_cf->c.table && old_cf->c.table->table != new_cf->c.table->table) + { + RPKI_TRACE(D_EVENTS, p, "Table changed"); + return 0; /* Need to restart the protocol */ + } + + struct rpki_cache_cfg *old; + WALK_LIST(old, old_cf->cache_cfg_list) + { + struct rpki_cache *cache = find_cache_in_proto_by_host_and_port(p, old); + if (!cache) + bug("Weird..."); + + struct rpki_cache_cfg *new = find_cache_cfg_by_host_and_port(&new_cf->cache_cfg_list, old); + if (!new) + { + /* The cache was in new configuration deleted */ + rpki_free_cache(cache); + continue; + } + + cache->cfg = new; + + if (old->preference != new->preference) + { + /* The preference of cache was changed */ + move_cache_into_group(cache); + } + + if (!!old->ssh != !!new->ssh) + { + /* toggled SSH enable/disable */ + return 0; /* Need to restart the protocol */ + } + + if (old->ssh && new->ssh) + { + /* TODO: RTR_FAST_RECONNECT will be probably enough */ + + if (strcmp(old->ssh->bird_private_key, new->ssh->bird_private_key) != 0) + return 0; /* Need to restart the protocol */ + + if (strcmp(old->ssh->cache_public_key, new->ssh->cache_public_key) != 0) + return 0; /* Need to restart the protocol */ + + if (strcmp(old->ssh->username, new->ssh->username) != 0) + return 0; /* Need to restart the protocol */ + } + } + + struct rpki_cache_cfg *new; + WALK_LIST(new, new_cf->cache_cfg_list) + { + struct rpki_cache *cache = find_cache_in_proto_by_host_and_port(p, new); + if (cache) + cache->cfg = new; + + struct rpki_cache_cfg *old = find_cache_cfg_by_host_and_port(&old_cf->cache_cfg_list, new); + if (!old) + { + /* Some cache was added to new configuration */ + struct rpki_cache *new_cache = rpki_new_cache(p, new); + rpki_insert_cache_into_group(new_cache); + } + } + + rpki_print_groups(p); + + return 1; +} + +/* + * Return 0 if need to restart rtrlib manager + * Return 1 if not need to restart rtrlib manager + */ +static int +rpki_reconfigure(struct proto *P, struct proto_config *c) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + struct rpki_config *old_cf = p->cf; + struct rpki_config *new_cf = (struct rpki_config *) c; + + int continue_without_restart = rpki_reconfigure_proto(p, new_cf, old_cf); + + p->cf = new_cf; + + if (continue_without_restart) + rpki_relax_groups(p); + else + { + RPKI_TRACE(D_EVENTS, p, "Have to restart whole protocol"); + } + + return continue_without_restart; +} + +static void +rpki_get_status(struct proto *P, byte *buf) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + + uint established_connections = 0; + uint cache_servers = 0; + uint connecting = 0; + + struct rpki_cache_group *group; + WALK_LIST(group, p->group_list) + { + struct rpki_cache *cache; + WALK_LIST(cache, group->cache_list) + { + cache_servers++; + + switch (cache->rtr_socket->state) + { + case RTR_ESTABLISHED: + case RTR_SYNC: + established_connections++; + break; + + case RTR_SHUTDOWN: + break; + + default: + connecting++; + } + } + } + + if (established_connections > 0) + bsprintf(buf, "Keep synchronized with %u cache server%s", established_connections, (established_connections > 1) ? "s" : ""); + else if (connecting > 0) + bsprintf(buf, "Connecting to %u cache server%s", connecting, (connecting > 1) ? "s" : ""); + else if (cache_servers == 0) + bsprintf(buf, "No cache server is configured"); + else if (cache_servers == 1) + bsprintf(buf, "Cannot connect to a cache server"); + else + bsprintf(buf, "Cannot connect to any cache servers"); +} + +static int +rpki_start(struct proto *P) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + struct rpki_config *cf = (struct rpki_config *) (P->cf); + + struct rpki_config empty_configuration = { + .roa_table_cf = cf->roa_table_cf + }; + init_list(&empty_configuration.cache_cfg_list); + rpki_reconfigure_proto(p, cf, &empty_configuration); + + rpki_relax_groups(p); + + return PS_UP; +} + +struct protocol proto_rpki = { + .name = "RPKI", + .template = "rpki%d", + .config_size = sizeof(struct rpki_config), + .init = rpki_init, + .start = rpki_start, +// .show_proto_info = rpki_show_proto_info, // TODO: be nice to be implemented + .shutdown = rpki_shutdown, + .reconfigure = rpki_reconfigure, + .get_status = rpki_get_status, +}; diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h new file mode 100644 index 000000000..5afab494f --- /dev/null +++ b/proto/rpki/rpki.h @@ -0,0 +1,143 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * Using RTRLib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_RPKI_H_ +#define _BIRD_RPKI_H_ + +#include "nest/bird.h" +#include "nest/route.h" +#include "lib/socket.h" +#include "lib/ip.h" + +#include "ssh_transport.h" +#include "tcp_transport.h" +#include "rtr.h" +#include "packets.h" + +#define RPKI_DEFAULT_PORT 8282 +#define RPKI_DEFAULT_SSH_PORT 22 +#define RPKI_DEFAULT_RETRY_INTERVAL 30 +#define RPKI_DEFAULT_REFRESH_INTERVAL 600 +#define RPKI_DEFAULT_EXPIRE_INTERVAL 1200 +#define RPKI_DEFAULT_CACHE_PREFERENCE 1 /* The most important priority */ + +/* + * +-------------------------------------------+ + * v | + * RTR_MGR_CLOSED <--> RTR_MGR_CONNECTING --> RTR_MGR_ESTABLISHED <--> RTR_MGR_ERROR + * ^ | ^ | + * | +-----------------------------------------+ | + * | | + * +-----------------------------------------------------------------+ + */ +enum rtr_mgr_status { + /* RTR sockets are disconnected */ + RTR_MGR_CLOSED, + + /* RTR sockets trying to establish a connection. */ + RTR_MGR_CONNECTING, + + /* All RTR sockets of the group are synchronized with the rtr servers. */ + RTR_MGR_ESTABLISHED, + + /* Error occured on at least one RTR socket. */ + RTR_MGR_ERROR, +}; + +struct rpki_cache_ssh_cfg { + char *bird_private_key; /* Filepath to the BIRD server private key */ + char *cache_public_key; /* Filepath to the public key of cache server, can be file known_hosts */ + char *username; /* Username for SSH connection */ +}; + +/* Used in parsing of configuration file */ +struct rpki_cache_cfg { + node n; + char *hostname; /* Full domain name of cache server or NULL */ + ip_addr ip; /* IP address of cache server or IPA_NONE */ + u16 port; /* Port of cache server */ + u8 preference; /* Preference: the most prioritized are the lowest numbers and starts with 1 */ + uint refresh_interval; /* Time interval (in seconds) for refreshing ROA from server */ + uint expire_interval; /* Time interval (in seconds) */ + uint retry_interval; /* Time interval (in seconds) for an unreachable server */ + struct rpki_cache_ssh_cfg *ssh; /* SSH configuration or NULL */ +}; + +struct rpki_cache { + node n; + struct rpki_proto *p; + struct rpki_cache_cfg *cfg; + struct rpki_cache_group *group; + struct rtr_socket *rtr_socket; /* RTRlib's socket data structure */ + sock *sk; /* BIRD's socket data structure */ + timer *retry_timer; /* Timer for Cache server */ + timer *refresh_timer; /* Timer for Cache server */ + timer *expire_timer; /* Timer for Cache server */ + u32 cache_id; /* For purge ROAs learned only from this cache */ +}; + +struct rpki_cache_group { + node n; + u8 preference; /* Preference: the most prioritized are the lowest numbers and starts with 1 */ + list cache_list; /* List of cache servers (struct rpki_cache) * */ + enum rtr_mgr_status status; +}; + +struct rpki_config { + struct proto_config c; + list cache_cfg_list; /* Unordered list of cache servers configurations (struct rpki_cache_cfg) */ + struct roa_table_config *roa_table_cf;/* The ROA table for routes importing from cache servers */ +}; + +struct rpki_proto { + struct proto p; + struct rpki_config *cf; + list group_list; /* Sorted list of cache groups (struct rpki_cache_group) */ + timer *timer; /* Main timer */ +}; + +void rpki_init_all(void); +struct rpki_cache_cfg *rpki_new_cache_cfg(void); +void rpki_init_all(void); +void rpki_close_connection(struct rpki_cache *cache); +int rpki_open_connection(struct rpki_cache *cache); +const char *get_cache_ident(struct rpki_cache *cache); +void rpki_relax_groups(struct rpki_proto *p); +void rpki_print_groups(struct rpki_proto *p); + +#define RPKI_LOG(log_level, rpki, msg, args...) \ + do { \ + log(log_level "%s: " msg, (rpki)->p.name , ## args); \ + } while(0) + +#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG) +#define CACHE_DBG(cache,msg,args...) \ + do { \ + RPKI_LOG(L_DEBUG, (cache)->p, "%s: %s() " msg, get_cache_ident(cache),__func__, ## args); \ + } while(0) +#else +#define CACHE_DBG(cache,msg,args...) do { } while(0) +#endif + +#define RPKI_TRACE(level,rpki,msg,args...) \ + do { \ + if ((rpki)->p.debug & level) \ + RPKI_LOG(L_TRACE, rpki, msg, ## args); \ + } while(0) + +#define CACHE_TRACE(level,cache,msg,args...) \ + do { \ + if ((cache)->p->p.debug & level) \ + RPKI_LOG(L_TRACE, (cache)->p, "%s: " msg, get_cache_ident(cache), ## args); \ + } while(0) + +#define RPKI_WARN(p, msg, args...) RPKI_LOG(L_WARN, p, msg, ## args); + +#endif /* _BIRD_RPKI_H_ */ diff --git a/proto/rpki/rtr.c b/proto/rpki/rtr.c new file mode 100644 index 000000000..c8c1e97b5 --- /dev/null +++ b/proto/rpki/rtr.c @@ -0,0 +1,338 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include + +#include "rpki.h" + +#include "packets.h" +#include "rtr.h" +#include "lib/timer.h" + +static const char *rtr_socket_str_states[] = { + [RTR_OPENING] = "RTR_OPENING", + [RTR_CONNECTING] = "RTR_CONNECTING", + [RTR_ESTABLISHED] = "RTR_ESTABLISHED", + [RTR_RESET] = "RTR_RESET", + [RTR_SYNC] = "RTR_SYNC", + [RTR_FAST_RECONNECT] = "RTR_FAST_RECONNECT", + [RTR_ERROR_NO_DATA_AVAIL] = "RTR_ERROR_NO_DATA_AVAIL", + [RTR_ERROR_NO_INCR_UPDATE_AVAIL] = "RTR_ERROR_NO_INCR_UPDATE_AVAIL", + [RTR_ERROR_FATAL] = "RTR_ERROR_FATAL", + [RTR_ERROR_TRANSPORT] = "RTR_ERROR_TRANSPORT", + [RTR_SHUTDOWN] = "RTR_SHUTDOWN" +}; + +void +rtr_init(struct rtr_socket *rtr_socket, const unsigned int refresh_interval, const unsigned int expire_interval, const unsigned int retry_interval) +{ + if(refresh_interval == 0) + rtr_socket->refresh_interval = 300; + else if (refresh_interval > 3600) + { + CACHE_TRACE(D_EVENTS, rtr_socket->cache, "The refresh interval %u is too big, setting it to 3600 seconds", refresh_interval); + rtr_socket->refresh_interval = 3600; + } + else + rtr_socket->refresh_interval = (refresh_interval > (3600 - RPKI_RECV_TIMEOUT) ? (3600 - RPKI_RECV_TIMEOUT) : refresh_interval); + + rtr_socket->expire_interval = (expire_interval == 0 ? (rtr_socket->refresh_interval * 2) : expire_interval); + rtr_socket->retry_interval = (retry_interval == 0) ? 600 : retry_interval; + + rtr_socket->state = RTR_SHUTDOWN; + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + rtr_socket->version = RTR_PROTOCOL_MAX_SUPPORTED_VERSION; +} + +void +rtr_purge_records_if_outdated(struct rpki_cache *cache) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + + if (rtr_socket->last_update == 0) + return; + + if ((rtr_socket->last_update + rtr_socket->expire_interval) < now) + { + switch (rtr_socket->state) + { + case RTR_ESTABLISHED: + case RTR_SYNC: + CACHE_DBG(cache, "There are obsolete roa records, but cache is in ESTABLISHED/SYNC state. Bad timing..."); + return; + } + + pfx_table_src_remove(cache); + CACHE_TRACE(D_EVENTS, cache, "Remove outdated records from pfx_table"); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + } + else + { + CACHE_DBG(cache, "There are no outdated roa records, it remains %u seconds to become obsolete", (now - (rtr_socket->last_update + rtr_socket->expire_interval))); + } +} + +void +rtr_stop(struct rtr_socket *rtr_socket) +{ + rtr_change_socket_state(rtr_socket, RTR_SHUTDOWN); + CACHE_TRACE(D_EVENTS, rtr_socket->cache, "Socket shut down"); +} + +const char * +rtr_state_to_str(enum rtr_socket_state state) +{ + return rtr_socket_str_states[state]; +} + +/* + * Set group status to @mgr_status if all sockets of caches in the @group are @socket_state + */ +static void +set_group_status_to_if_all_sockets_are(struct rpki_cache_group *group, const enum rtr_mgr_status mgr_status, const enum rtr_socket_state socket_state) +{ + bool do_all_sockets_pass = true; + + struct rpki_cache *cache; + WALK_LIST(cache, group->cache_list) + { + if (cache->rtr_socket->state != socket_state) + do_all_sockets_pass = false; + } + if (do_all_sockets_pass) + group->status = mgr_status; +} + +void +rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state) +{ + const enum rtr_socket_state old_state = rtr_socket->state; + + if (old_state == new_state) + return; + + rtr_socket->state = new_state; + + struct rpki_cache *cache = rtr_socket->cache; + CACHE_TRACE(D_EVENTS, cache, "Change state %s -> %s", rtr_state_to_str(old_state), rtr_state_to_str(new_state)); + + switch (new_state) + { + case RTR_CONNECTING: + if (old_state == RTR_SHUTDOWN) + cache->group->status = RTR_MGR_CONNECTING; + + if (cache->sk == NULL || cache->sk->fd < 0) + { + if (rpki_open_connection(cache) == TR_SUCCESS) + cache->rtr_socket->state = RTR_SYNC; /* Need call a setup the bird socket in io.c loop */ + } + else + rtr_change_socket_state(rtr_socket, RTR_SYNC); + break; + + case RTR_ESTABLISHED: + /* set status of group to RTR_MGR_ESTABLISHED if all caches in the common group are RTR_ESTABLISHED */ + set_group_status_to_if_all_sockets_are(cache->group, RTR_MGR_ESTABLISHED, RTR_ESTABLISHED); + rpki_relax_groups(cache->p); + break; + + case RTR_RESET: + /* Resetting RTR connection. */ + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_change_socket_state(rtr_socket, RTR_SYNC); + break; + + case RTR_SYNC: + /* Requesting for receive validation records from the RTR server. */ + if (rtr_socket->request_session_id) + { + //change to state RESET, if socket dont has a session_id + if (rtr_send_reset_query(cache) != RTR_SUCCESS) + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + else + { + //if we already have a session_id, send a serial query and start to sync + if (rtr_send_serial_query(cache) != RTR_SUCCESS) + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + break; + + case RTR_ERROR_NO_INCR_UPDATE_AVAIL: + /* Server was unable to answer the last serial or reset query. */ + rtr_purge_records_if_outdated(cache); + /* Fall through */ + + case RTR_ERROR_NO_DATA_AVAIL: + /* No validation records are available on the RTR server. */ + rtr_change_socket_state(rtr_socket, RTR_RESET); + break; + + case RTR_ERROR_FATAL: + /* Fatal protocol error occurred. */ + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + pfx_table_src_remove(cache); + /* Fall through */ + + case RTR_ERROR_TRANSPORT: + /* Error on the transport socket occurred. */ + rpki_close_connection(cache); + rtr_schedule_next_retry(cache); + cache->group->status = RTR_MGR_ERROR; + rpki_relax_groups(cache->p); + break; + + case RTR_FAST_RECONNECT: + /* Reconnect without any waiting period */ + rpki_close_connection(cache); + rtr_change_socket_state(rtr_socket, RTR_CONNECTING); + break; + + case RTR_SHUTDOWN: + /* RTR Socket is stopped. */ + rpki_close_connection(cache); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + pfx_table_src_remove(cache); + + /* set status of group to RTR_MGR_CLOSED if all caches in the common group are RTR_SHUTDOWN */ + set_group_status_to_if_all_sockets_are(cache->group, RTR_MGR_CLOSED, RTR_SHUTDOWN); + rpki_relax_groups(cache->p); + break; + }; +} + +/* + * Timers + */ + +void +rtr_schedule_next_refresh(struct rpki_cache *cache) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + + if (cache->rtr_socket->state == RTR_SHUTDOWN) + { + CACHE_DBG(cache, "Stop refreshing"); + return; + } + + unsigned time_to_wait = MAX(((int)rtr_socket->refresh_interval - (int)(now - rtr_socket->last_update)), 1); + + CACHE_DBG(cache, "Next refresh of cache(%s) will be after %u seconds", tr_ident(rtr_socket->tr_socket), time_to_wait); + tm_start(cache->refresh_timer, time_to_wait); +} + +void +rtr_schedule_next_retry(struct rpki_cache *cache) +{ + switch (cache->rtr_socket->state) + { + case RTR_ESTABLISHED: + case RTR_SYNC: + case RTR_RESET: + CACHE_DBG(cache, "Stop retrying connection"); + break; + + default: + CACHE_TRACE(D_EVENTS, cache, "Connection will retry after %u seconds again", cache->rtr_socket->retry_interval); + tm_start(cache->retry_timer, cache->rtr_socket->retry_interval); + } +} + +void +rtr_schedule_next_expire_check(struct rpki_cache *cache) +{ + struct rtr_socket *rtr_socket = cache->rtr_socket; + + unsigned time_to_wait = MAX(((int)rtr_socket->expire_interval - (int)(now - rtr_socket->last_update))+3, 1); + + CACHE_DBG(cache, "Next ROA expiration check will be after %u seconds again", time_to_wait); + tm_stop(cache->expire_timer); + tm_start(cache->expire_timer, time_to_wait); +} + +void +rpki_refresh_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + struct rtr_socket *rtr_socket = cache->rtr_socket; + + switch (rtr_socket->state) + { + case RTR_ESTABLISHED: + CACHE_DBG(cache, "Refreshing"); + rtr_change_socket_state(rtr_socket, RTR_SYNC); + rtr_schedule_next_refresh(cache); + break; + + case RTR_CONNECTING: + case RTR_SYNC: + /* Wait small amout of time to transite state */ + tm_start(tm, 1); + break; + + default: + CACHE_DBG(cache, "Stop Refreshing (%s)", rtr_socket_str_states[rtr_socket->state]); + break; + } +} + +void +rpki_retry_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + struct rtr_socket *rtr_socket = cache->rtr_socket; + struct rpki_proto *p = cache->p; + + switch (rtr_socket->state) + { + case RTR_ESTABLISHED: + case RTR_CONNECTING: + case RTR_SYNC: + case RTR_SHUTDOWN: + CACHE_DBG(cache, "Stop Retry Connecting (%s)", rtr_socket_str_states[rtr_socket->state]); + break; + + default: + CACHE_DBG(cache, "Retry Connecting (%s)", rtr_socket_str_states[rtr_socket->state]); + rtr_change_socket_state(rtr_socket, RTR_CONNECTING); + rpki_print_groups(p); + break; + } +} + +void +rpki_expire_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + struct rtr_socket *rtr_socket = cache->rtr_socket; + + if (rtr_socket->last_update == 0) + return; + + CACHE_DBG(cache, "Expire Hook"); + + rtr_purge_records_if_outdated(cache); + rtr_schedule_next_expire_check(cache); +} diff --git a/proto/rpki/rtr.h b/proto/rpki/rtr.h new file mode 100644 index 000000000..67b0422b3 --- /dev/null +++ b/proto/rpki/rtr.h @@ -0,0 +1,140 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef RTR_H +#define RTR_H +#include +#include +#include +#include "transport.h" + +#include "nest/bird.h" + +static const uint8_t RTR_PROTOCOL_VERSION_0 = 0; +static const uint8_t RTR_PROTOCOL_VERSION_1 = 1; + +static const uint8_t RTR_PROTOCOL_MIN_SUPPORTED_VERSION = 0; +static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 1; + +enum rtr_rtvals { + RTR_SUCCESS = 0, + RTR_ERROR = -1 +}; + +/** + * @brief States of the RTR socket. + */ +enum rtr_socket_state { + /* State between request for open new socket and asynchronous finish the opening socket */ + RTR_OPENING, + + /** Socket is establishing the transport connection. */ + RTR_CONNECTING, + + /** Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */ + RTR_ESTABLISHED, + + /** Resetting RTR connection. */ + RTR_RESET, + + /** Receiving validation records from the RTR server. */ + RTR_SYNC, + + /** Reconnect without any waiting period */ + RTR_FAST_RECONNECT, + + /** No validation records are available on the RTR server. */ + RTR_ERROR_NO_DATA_AVAIL, + + /** Server was unable to answer the last serial or reset query. */ + RTR_ERROR_NO_INCR_UPDATE_AVAIL, + + /** Fatal protocol error occurred. */ + RTR_ERROR_FATAL, + + /** Error on the transport socket occurred. */ + RTR_ERROR_TRANSPORT, + + /** RTR Socket is stopped. */ + RTR_SHUTDOWN, +}; + +struct rtr_socket; + +/** + * @brief A function pointer that is called if the state of the rtr socket has changed. + */ +typedef void (*rtr_connection_state_fp)(const struct rtr_socket *rtr_socket, const enum rtr_socket_state state, void *connection_state_fp_param); + +/** + * @brief A RTR socket. + * @param tr_socket Pointer to an initialized tr_socket that will be used to communicate with the RTR server. + * @param refresh_interval Time period in seconds. Tells the router how long to wait before next attempting to poll the cache, using a Serial Query or + * Reset Query PDU. + * @param last_update Timestamp of the last validation record update. Is 0 if the pfx_table doesn't stores any + * validation reords from this rtr_socket. + * @param expire_interval Time period in seconds. Received records are deleted if the client was unable to refresh data for this time period. + * If 0 is specified, the expire_interval is twice the refresh_interval. + * @param retry_interval Time period in seconds between a faild quary and the next attempt. + * @param state Current state of the socket. + * @param session_id session_id of the RTR session. + * @param request_session_id True, if the rtr_client have to request a new none from the server. + * @param serial_number Last serial number of the obtained validation records. + */ +struct rtr_socket { + struct tr_socket *tr_socket; + struct rpki_cache *cache; + bird_clock_t last_update; + unsigned int retry_interval; /* Use if the cache server is down */ + unsigned int refresh_interval; + unsigned int expire_interval; /* After this period from last refresh will be ROAs discard */ + enum rtr_socket_state state; + uint32_t session_id; + bool request_session_id; + uint32_t serial_number; + unsigned int version; +}; + +/** + * @brief Initializes a rtr_socket. + * @param[out] rtr_socket Pointer to the allocated rtr_socket that will be initialized. + * @param[in] refresh_interval Interval in seconds between serial queries that are sent to the server. Must be <= 3600 + * @param[in] expire_interval Stored validation records will be deleted if cache was unable to refresh data for this period.\n + * The default value is twice the refresh_interval. + */ +void rtr_init(struct rtr_socket *rtr_socket, const unsigned int refresh_interval, const unsigned int expire_interval, const unsigned int retry_interval); + +/** + * @brief Stops the RTR connection and terminate the transport connection. + * @param[in] rtr_socket rtr_socket that will be used. + */ +void rtr_stop(struct rtr_socket *rtr_socket); + +/** + * @brief Converts a rtr_socket_state to a String. + * @param[in] state state to convert to a string + * @return NULL If state isn't a valid rtr_socket_state + * @return !=NULL The rtr_socket_state as String. + */ +const char *rtr_state_to_str(enum rtr_socket_state state); + +void rtr_purge_records_if_outdated(struct rpki_cache *cache); +void rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state); + +void rpki_retry_hook(struct timer *tm); +void rpki_expire_hook(struct timer *tm); +void rpki_refresh_hook(struct timer *tm); + +void rtr_schedule_next_refresh(struct rpki_cache *cache); +void rtr_schedule_next_retry(struct rpki_cache *cache); +void rtr_schedule_next_expire_check(struct rpki_cache *cache); + +#endif +/* @} */ diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c new file mode 100644 index 000000000..ec7d7421c --- /dev/null +++ b/proto/rpki/ssh_transport.c @@ -0,0 +1,131 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "ssh_transport.h" +#include "lib/libssh.h" + +#include "rpki.h" + +static int tr_ssh_open(void *tr_ssh_sock); +static void tr_ssh_close(void *tr_ssh_sock); +static void tr_ssh_free(struct tr_socket *tr_sock); +static const char *tr_ssh_ident(void *tr_ssh_sock); + +int tr_ssh_open(void *socket) +{ + struct tr_ssh_socket *ssh_socket = socket; + struct rpki_cache *cache = ssh_socket->cache; + + const char *err_msg; + if ((err_msg = load_libssh()) != NULL) + { + CACHE_TRACE(D_EVENTS, cache, "%s", err_msg); + return TR_ERROR; + } + + sock *sk = cache->sk; + sk->type = SK_SSH_ACTIVE; + sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock)); + sk->ssh->username = cache->cfg->ssh->username; + sk->ssh->client_privkey_path = cache->cfg->ssh->bird_private_key; + sk->ssh->server_hostkey_path = cache->cfg->ssh->cache_public_key; + sk->ssh->subsystem = "rpki-rtr"; + sk->ssh->state = BIRD_SSH_CONNECT; + + if (sk_open(sk) != 0) + return TR_ERROR; + + return TR_SUCCESS; +} + +void tr_ssh_close(void *tr_ssh_sock) +{ + struct tr_ssh_socket *socket = tr_ssh_sock; + struct rpki_cache *cache = socket->cache; + + sock *sk = cache->sk; + if (sk && sk->ssh) + { + if (sk->ssh->channel) + { + if (ssh_channel_is_open(sk->ssh->channel)) + ssh_channel_close(sk->ssh->channel); + ssh_channel_free(sk->ssh->channel); + sk->ssh->channel = NULL; + } + + if (sk->ssh->session) + { + ssh_disconnect(sk->ssh->session); + ssh_free(sk->ssh->session); + sk->ssh->session = NULL; + } + } +} + +void tr_ssh_free(struct tr_socket *tr_sock) +{ + struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket; + + if (tr_ssh_sock) + { + if (tr_ssh_sock->ident != NULL) + mb_free(tr_ssh_sock->ident); + mb_free(tr_ssh_sock); + tr_sock->socket = NULL; + } +} + +const char *tr_ssh_ident(void *tr_ssh_sock) +{ + size_t len; + struct tr_ssh_socket *ssh_sock = tr_ssh_sock; + struct rpki_cache *cache = ssh_sock->cache; + + assert(ssh_sock != NULL); + + if (ssh_sock->ident != NULL) + return ssh_sock->ident; + + const char *username = cache->cfg->ssh->username; + const char *host = cache->cfg->hostname; + + len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* + '@' + + ':' + + '\0' */ + ssh_sock->ident = mb_alloc(cache->p->p.pool, len); + if (ssh_sock->ident == NULL) + return NULL; + snprintf(ssh_sock->ident, len, "%s@%s:%u", username, host, cache->cfg->port); + return ssh_sock->ident; +} + +int tr_ssh_init(struct rpki_cache *cache) +{ + struct rpki_proto *p = cache->p; + struct tr_socket *tr_socket = cache->rtr_socket->tr_socket; + + tr_socket->close_fp = &tr_ssh_close; + tr_socket->free_fp = &tr_ssh_free; + tr_socket->open_fp = &tr_ssh_open; + tr_socket->ident_fp = &tr_ssh_ident; + + tr_socket->socket = mb_allocz(p->p.pool, sizeof(struct tr_ssh_socket)); + struct tr_ssh_socket *ssh = tr_socket->socket; + + ssh->cache = cache; + + return TR_SUCCESS; +} diff --git a/proto/rpki/ssh_transport.h b/proto/rpki/ssh_transport.h new file mode 100644 index 000000000..aa282347f --- /dev/null +++ b/proto/rpki/ssh_transport.h @@ -0,0 +1,63 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * @defgroup mod_ssh_transport_h SSH transport socket + * @ingroup mod_transport_h + * @brief An implementation of the SSH protocol for the RTR transport. + * @details This transport implementation uses libssh + * (http://www.libssh.org/) for all ssh specific operations.\n + * See @ref mod_transport_h "transport interface" for a list of supported + * operations. + * + * @{ + * + * @example ssh_tr.c + * Example of how to open a SSH transport connection. + */ + +#ifndef SSH_TRANSPORT_H +#define SSH_TRANSPORT_H +#include "transport.h" + +/** + * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * @param username Username for authentication. + * @param server_hostkey_path Path to public SSH key of the server or NULL to + don't verify host authenticity. + * @param client_privkey_path Path to private key of the authentication keypair + * or NULL to use ~/.ssh/id_rsa. + */ +struct tr_ssh_config { + char *host; + unsigned int port; + char *username; +}; + +struct tr_ssh_socket { + struct rpki_cache *cache; + char *ident; +}; + +/** + * @brief Initializes the tr_socket struct for a SSH connection. + * @param[in] config SSH configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_ssh_init(struct rpki_cache *cache); + +#endif +/* @} */ diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c new file mode 100644 index 000000000..7c81b5370 --- /dev/null +++ b/proto/rpki/tcp_transport.c @@ -0,0 +1,121 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpki.h" + +#include "tcp_transport.h" +#include "lib/unix.h" + + +static int tr_tcp_open(void *tr_tcp_sock); +static void tr_tcp_close(void *tr_tcp_sock); +static void tr_tcp_free(struct tr_socket *tr_sock); +static const char *tr_tcp_ident(void *socket); + +int tr_tcp_open(void *tr_tcp_sock) +{ + struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + struct rpki_cache *cache = tcp_socket->cache; + + sock *sk = cache->sk; + sk->type = SK_TCP_ACTIVE; + sk->daddr = tcp_socket->config.ip; + + if (sk_open(sk) != 0) + return TR_ERROR; + + return TR_SUCCESS; +} + +void tr_tcp_close(void *tr_tcp_sock) +{ + struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + struct rpki_cache *cache = tcp_socket->cache; + + sock *sk = cache->sk; + if (sk && sk->fd > 0) + { + /* TODO: ??? */ + } +} + +void tr_tcp_free(struct tr_socket *tr_sock) +{ + struct tr_tcp_socket *tcp_sock = tr_sock->socket; + + if (tcp_sock) + { + if (tcp_sock->ident != NULL) + mb_free(tcp_sock->ident); + tr_sock->socket = NULL; + mb_free(tcp_sock); + } +} + +const char *tr_tcp_ident(void *socket) +{ + assert(socket != NULL); + + struct tr_tcp_socket *sock = socket; + struct rpki_proto *p = sock->cache->p; + + if (sock->ident != NULL) + return sock->ident; + + size_t colon_and_port_len = 6; /* max ":65535" */ + size_t ident_len; + if (sock->config.host) + ident_len = strlen(sock->config.host) + colon_and_port_len + 1; + else + ident_len = IPA_MAX_TEXT_LENGTH + colon_and_port_len + 1; + + sock->ident = mb_allocz(p->p.pool, ident_len); + if (sock->ident == NULL) + return NULL; + + if (sock->config.host) + bsnprintf(sock->ident, ident_len, "%s:%u", sock->config.host, sock->config.port); + else + bsnprintf(sock->ident, ident_len, "%I:%u", sock->config.ip, sock->config.port); + + return sock->ident; +} + +int tr_tcp_init(struct rpki_cache *cache) +{ + struct rpki_proto *p = cache->p; + struct rpki_cache_cfg *cache_cfg = cache->cfg; + struct tr_socket *tr_socket = cache->rtr_socket->tr_socket; + + tr_socket->close_fp = &tr_tcp_close; + tr_socket->free_fp = &tr_tcp_free; + tr_socket->open_fp = &tr_tcp_open; + tr_socket->ident_fp = &tr_tcp_ident; + + tr_socket->socket = mb_allocz(p->p.pool, sizeof(struct tr_tcp_socket)); + struct tr_tcp_socket *tcp = tr_socket->socket; + + tcp->cache = cache; + tcp->config.host = cache_cfg->hostname; + tcp->config.ip = cache_cfg->ip; + tcp->config.port = cache_cfg->port; + + return TR_SUCCESS; +} diff --git a/proto/rpki/tcp_transport.h b/proto/rpki/tcp_transport.h new file mode 100644 index 000000000..07e8acdb8 --- /dev/null +++ b/proto/rpki/tcp_transport.h @@ -0,0 +1,55 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * @defgroup mod_tcp_transport_h TCP transport socket + * @ingroup mod_transport_h + * @brief An implementation of the TCP protocol for the RTR transport. + * See @ref mod_transport_h "transport interface" for a list of supported operations. + * + * @{ + */ + +#ifndef RTR_TCP_TRANSPORT_H +#define RTR_TCP_TRANSPORT_H +#include "transport.h" +#include "nest/bird.h" +#include "lib/ip.h" + +/** + * @brief A tr_tcp_config struct holds configuration for a TCP connection. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * to use the source address of the system's default route to the server + */ +struct tr_tcp_config { + ip_addr ip; char *host; /* at least one of @ip or @host must be defined */ + uint port; + char *bindaddr; /* TODO: NEED THIS? */ +}; + +struct tr_tcp_socket { + struct rpki_cache *cache; + struct tr_tcp_config config; + char *ident; +}; + +/** + * @brief Initializes the tr_socket struct for a TCP connection. + * @param[in] config TCP configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_tcp_init(struct rpki_cache *cache); +#endif +/* @} */ diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c new file mode 100644 index 000000000..8b6de6d6d --- /dev/null +++ b/proto/rpki/transport.c @@ -0,0 +1,32 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "rpki.h" +#include "transport.h" + +inline int tr_open(struct tr_socket *socket) +{ + return socket->open_fp(socket->socket); +} + +inline void tr_close(struct tr_socket *socket) +{ + socket->close_fp(socket->socket); +} + +inline void tr_free(struct tr_socket *socket) +{ + socket->free_fp(socket); +} + +inline const char *tr_ident(struct tr_socket *socket) +{ + return socket->ident_fp(socket->socket); +} diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h new file mode 100644 index 000000000..c577ef62f --- /dev/null +++ b/proto/rpki/transport.h @@ -0,0 +1,96 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * This file was part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * @defgroup mod_transport_h Transport sockets + * @brief The RTR transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RTR server and client. + * @details Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * tr_tcp_init()).\n + * The tr_* functions call the corresponding function pointers, which are + * passed in the tr_socket struct, and forward the remaining arguments. + * + * @{ + */ + +#ifndef RTR_TRANSPORT_H +#define RTR_TRANSPORT_H +#include + +/** + * @brief The return values for tr_ functions. + */ +enum tr_rtvals { + /** @brief Operation was successfull. */ + TR_SUCCESS = 0, + + /** Error occured. */ + TR_ERROR = -1, + + /** No data is available on the socket. */ + TR_WOULDBLOCK = -2, + + /** Call was interrupted from a signal */ + TR_INTR = -3, + + /** Connection closed */ + TR_CLOSED = -4 +}; + +struct tr_socket; + +/** + * @brief A transport socket datastructure. + * + * @param socket A pointer to a technology specific socket. + * @param open_fp Pointer to a function that establishes the socket connection. + * @param close_fp Pointer to a function that closes the socket. + * @param free_fp Pointer to a function that frees all memory allocated with this socket. + */ +struct tr_socket { + void *socket; + int (*open_fp)(void *socket) ; + void (*close_fp)(void *socket) ; + void (*free_fp)(struct tr_socket *tr_sock); + const char *(*ident_fp)(void *socket); +}; + +/** + * @brief Establish the connection. + * @param[in] socket Socket that will be used. + * @return TR_SUCCESS On success. + * @return TR_ERROR On error. + */ +int tr_open(struct tr_socket *socket); + +/** + * @brief Close the socket connection. + * @param[in] socket Socket that will be closed. + */ +void tr_close(struct tr_socket *socket); + +/** + * @brief Deallocates all memory that the passed socket uses. + * Socket have to be closed before. + * @param[in] socket which will be freed. + */ +void tr_free(struct tr_socket *socket); + +/** + * Returns an identifier for the socket endpoint, eg host:port. + * @param[in] socket + * return Pointer to a \0 terminated String + * return NULL on error + */ +const char *tr_ident(struct tr_socket *socket); + +#endif +/* @} */ diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in index a9e46e274..047a49ca2 100644 --- a/sysdep/autoconf.h.in +++ b/sysdep/autoconf.h.in @@ -43,6 +43,7 @@ #undef CONFIG_BGP #undef CONFIG_OSPF #undef CONFIG_PIPE +#undef CONFIG_RPKI /* We use multithreading */ #undef USE_PTHREADS diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c index 8f61fc680..a3e11ff17 100644 --- a/sysdep/unix/io.c +++ b/sysdep/unix/io.c @@ -35,6 +35,7 @@ #include "lib/socket.h" #include "lib/event.h" #include "lib/string.h" +#include "lib/libssh.h" #include "nest/iface.h" #include "lib/unix.h" @@ -1060,12 +1061,42 @@ sk_free_bufs(sock *s) } } +static void +sk_ssh_free(sock *s) +{ + struct ssh_sock *ssh = s->ssh; + + if (s->ssh == NULL) + return; + + if (ssh->channel) + { + if (ssh_channel_is_open(ssh->channel)) + ssh_channel_close(ssh->channel); + ssh_channel_free(ssh->channel); + ssh->channel = NULL; + } + + if (ssh->session) + { + ssh_disconnect(ssh->session); + ssh_free(ssh->session); + ssh->session = NULL; + } + + mb_free(ssh); + s->ssh = NULL; +} + static void sk_free(resource *r) { sock *s = (sock *) r; sk_free_bufs(s); + if (s->type == SK_SSH || s->type == SK_SSH_ACTIVE) + sk_ssh_free(s); + if (s->fd >= 0) { close(s->fd); @@ -1182,6 +1213,9 @@ sk_setup(sock *s) int y = 1; int fd = s->fd; + if (s->type == SK_SSH_ACTIVE) + return 0; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) ERR("O_NONBLOCK"); @@ -1295,6 +1329,14 @@ sk_tcp_connected(sock *s) s->tx_hook(s); } +static void +sk_ssh_connected(sock *s) +{ + sk_alloc_bufs(s); + s->type = SK_SSH; + s->tx_hook(s); +} + static int sk_passive_connected(sock *s, int type) { @@ -1359,6 +1401,193 @@ sk_passive_connected(sock *s, int type) return 1; } +/* + * Return SSH_OK or SSH_AGAIN or SSH_ERROR + */ +static int +sk_ssh_connect(sock *s) +{ + s->fd = ssh_get_fd(s->ssh->session); + + /* Big fall thru automat */ + switch (s->ssh->state) + { + case BIRD_SSH_CONNECT: + { + switch (ssh_connect(s->ssh->session)) + { + case SSH_AGAIN: + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + + case BIRD_SSH_IS_SERVER_KNOWN: + { + s->ssh->state = BIRD_SSH_IS_SERVER_KNOWN; + + if (s->ssh->server_hostkey_path) + { + int server_identity_is_ok = 1; + + /* Check server identity */ + switch (ssh_is_server_known(s->ssh->session)) + { +#define LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s,msg,args...) log(L_WARN "SSH Identity %s@%s:%u: " msg, (s)->ssh->username, (s)->host, (s)->dport, ## args); + case SSH_SERVER_KNOWN_OK: + /* The server is known and has not changed. */ + break; + + case SSH_SERVER_NOT_KNOWN: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server is unknown, its public key was not found in the known host file %s", s->ssh->server_hostkey_path); + break; + + case SSH_SERVER_KNOWN_CHANGED: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server key has changed. Either you are under attack or the administrator changed the key."); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_FILE_NOT_FOUND: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The known host file %s does not exist", s->ssh->server_hostkey_path); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_ERROR: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "Some error happened"); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_FOUND_OTHER: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server gave use a key of a type while we had an other type recorded. " \ + "It is a possible attack."); + server_identity_is_ok = 0; + break; + } + + if (!server_identity_is_ok) + return SSH_ERROR; + } + } + + case BIRD_SSH_USERAUTH_PUBLICKEY_AUTO: + { + s->ssh->state = BIRD_SSH_USERAUTH_PUBLICKEY_AUTO; + switch (ssh_userauth_publickey_auto(s->ssh->session, NULL, NULL)) + { + case SSH_AUTH_AGAIN: + return SSH_AGAIN; + + case SSH_AUTH_SUCCESS: + break; + + default: + return SSH_ERROR; + } + } + + case BIRD_SSH_CHANNEL_NEW: + { + s->ssh->state = BIRD_SSH_CHANNEL_NEW; + s->ssh->channel = ssh_channel_new(s->ssh->session); + if (s->ssh->channel == NULL) + return SSH_ERROR; + } + + case BIRD_SSH_CHANNEL_OPEN_SESSION: + { + s->ssh->state = BIRD_SSH_CHANNEL_OPEN_SESSION; + switch (ssh_channel_open_session(s->ssh->channel)) + { + case SSH_AGAIN: + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + + case BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM: + { + s->ssh->state = BIRD_SSH_CHANNEL_REQUEST_SUBSYSTEM; + if (s->ssh->subsystem) + { + switch (ssh_channel_request_subsystem(s->ssh->channel, s->ssh->subsystem)) + { + case SSH_AGAIN: + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + } + + case BIRD_SSH_CONNECTION_ESTABLISHED: + s->ssh->state = BIRD_SSH_CONNECTION_ESTABLISHED; + } + + return SSH_OK; +} + +/* + * Return file descriptor number if success + * Return -1 if failed + */ +static int +sk_open_ssh(sock *s) +{ + if (!s->ssh) + bug("sk_open() sock->ssh is not allocated"); + + s->ssh->session = ssh_new(); + if (s->ssh->session == NULL) + ERR2("Cannot create a ssh session"); + + const int verbosity = SSH_LOG_NOLOG; + ssh_options_set(s->ssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh->session, SSH_OPTIONS_HOST, s->host); + ssh_options_set(s->ssh->session, SSH_OPTIONS_PORT, &(s->dport)); + ssh_options_set(s->ssh->session, SSH_OPTIONS_USER, s->ssh->username); + + if (s->ssh->server_hostkey_path) + ssh_options_set(s->ssh->session, SSH_OPTIONS_KNOWNHOSTS, s->ssh->server_hostkey_path); + + if (s->ssh->client_privkey_path) + ssh_options_set(s->ssh->session, SSH_OPTIONS_IDENTITY, s->ssh->client_privkey_path); + + ssh_set_blocking(s->ssh->session, 0); + + switch (sk_ssh_connect(s)) + { + case SSH_AGAIN: + break; + + case SSH_OK: + sk_ssh_connected(s); + break; + + case SSH_ERROR: + ERR2(ssh_get_error(s->ssh->session)); + break; + } + + return ssh_get_fd(s->ssh->session); + + err: + return -1; +} + /** * sk_open - open a socket * @s: socket @@ -1390,6 +1619,11 @@ sk_open(sock *s) do_bind = bind_port || ipa_nonzero(bind_addr); break; + case SK_SSH_ACTIVE: + s->ttx = ""; /* Force s->ttx != s->tpos */ + fd = sk_open_ssh(s); + break; + case SK_UDP: fd = socket(fam_to_af[s->fam], SOCK_DGRAM, IPPROTO_UDP); bind_port = s->sport; @@ -1472,6 +1706,7 @@ sk_open(sock *s) ERR2("listen"); break; + case SK_SSH_ACTIVE: case SK_MAGIC: break; @@ -1481,6 +1716,7 @@ sk_open(sock *s) if (!(s->flags & SKF_THREAD)) sk_insert(s); + return 0; err: @@ -1663,6 +1899,26 @@ sk_maybe_write(sock *s) reset_tx_buffer(s); return 1; + case SK_SSH: + while (s->ttx != s->tpos) + { + e = ssh_channel_write(s->ssh->channel, s->ttx, s->tpos - s->ttx); + + if (e < 0) + { + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + + reset_tx_buffer(s); + /* EPIPE is just a connection close notification during TX */ + s->err_hook(s, (errno != EPIPE) ? errno : 0); + return -1; + } + s->ttx += e; + } + reset_tx_buffer(s); + return 1; + case SK_UDP: case SK_IP: { @@ -1687,6 +1943,7 @@ sk_maybe_write(sock *s) reset_tx_buffer(s); return 1; } + default: bug("sk_maybe_write: unknown socket type %d", s->type); } @@ -1773,6 +2030,62 @@ sk_send_full(sock *s, unsigned len, struct iface *ifa, } */ +static void +call_rx_hook(sock *s, int size) +{ + if (s->rx_hook(s, size)) + { + /* We need to be careful since the socket could have been deleted by the hook */ + if (current_sock == s) + s->rpos = s->rbuf; + } +} + +static int +sk_read_ssh(sock *s) +{ + ssh_channel rchans[2] = { s->ssh->channel, NULL }; + struct timeval timev = { 1, 0 }; + + if (ssh_channel_select(rchans, NULL, NULL, &timev) == SSH_EINTR) + return 1; /* Try again */ + + if (ssh_channel_is_eof(s->ssh->channel) != 0) + { + /* The remote side is closing the connection */ + s->err_hook(s, 0); + return 0; + } + + if (rchans[0] == NULL) + return 0; /* No data is available on the socket */ + + const uint used_bytes = s->rpos - s->rbuf; + const int read_bytes = ssh_channel_read_nonblocking(s->ssh->channel, s->rpos, s->rbsize - used_bytes, 0); + if (read_bytes > 0) + { + /* Received data */ + s->rpos += read_bytes; + call_rx_hook(s, used_bytes + read_bytes); + return 1; + } + else if (read_bytes == 0) + { + if (ssh_channel_is_eof(s->ssh->channel) != 0) + { + /* The remote side is closing the connection */ + s->err_hook(s, 0); + } + } + else + { + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + } + + return 0; /* No data is available on the socket */ +} + /* sk_read() and sk_write() are called from BFD's event loop */ int @@ -1801,17 +2114,15 @@ sk_read(sock *s) else { s->rpos += c; - if (s->rx_hook(s, s->rpos - s->rbuf)) - { - /* We need to be careful since the socket could have been deleted by the hook */ - if (current_sock == s) - s->rpos = s->rbuf; - } + call_rx_hook(s, s->rpos - s->rbuf); return 1; } return 0; } + case SK_SSH: + return sk_read_ssh(s); + case SK_MAGIC: return s->rx_hook(s, 0); @@ -1850,6 +2161,25 @@ sk_write(sock *s) return 0; } + case SK_SSH_ACTIVE: + { + switch (sk_ssh_connect(s)) + { + case SSH_OK: + sk_ssh_connected(s); + break; + + case SSH_AGAIN: + return 1; + + case SSH_ERROR: + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + break; + } + return 0; + } + default: if (s->ttx != s->tpos && sk_maybe_write(s) > 0) { diff --git a/tools/Makefile.in b/tools/Makefile.in index 5de323ab4..05a9d3951 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -37,8 +37,8 @@ subdir: sysdep/paths.h .dir-stamp .dep-stamp set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done $(exedir)/bird: $(bird-dep) - @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) - @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) $(BIRD_LIBS) + @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(BIRD_LIBS) $(exedir)/birdc: $(birdc-dep) @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS) diff --git a/tools/Rules.in b/tools/Rules.in index f00c85d1e..68409b9df 100644 --- a/tools/Rules.in +++ b/tools/Rules.in @@ -23,6 +23,7 @@ CPPFLAGS=-I$(root-rel) -I$(srcdir) @CPPFLAGS@ CFLAGS=$(CPPFLAGS) @CFLAGS@ LDFLAGS=@LDFLAGS@ LIBS=@LIBS@ +BIRD_LIBS=@BIRD_LIBS@ CLIENT_LIBS=@CLIENT_LIBS@ CC=@CC@ M4=@M4@