]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Add RTR socket draft
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 16 Aug 2018 16:41:12 +0000 (11:41 -0500)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 16 Aug 2018 16:41:12 +0000 (11:41 -0500)
src/common.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/rtr/pdu.c [new file with mode: 0644]
src/rtr/pdu.h [new file with mode: 0644]
src/rtr/pdu_handler.c [new file with mode: 0644]
src/rtr/pdu_handler.h [new file with mode: 0644]
src/rtr/primitive_reader.c [new file with mode: 0644]
src/rtr/primitive_reader.h [new file with mode: 0644]
src/rtr/rtr.c [new file with mode: 0644]
src/rtr/rtr.h [new file with mode: 0644]

diff --git a/src/common.h b/src/common.h
new file mode 100644 (file)
index 0000000..1f54d5d
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+
+/* __BEGIN_DECLS should be used at the beginning of your declarations,
+   so that C++ compilers don't mangle their names.  Use __END_DECLS at
+   the end of C declarations. */
+#undef __BEGIN_DECLS
+#undef __END_DECLS
+#ifdef __cplusplus
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS }
+#else
+# define __BEGIN_DECLS /* empty */
+# define __END_DECLS /* empty */
+#endif
+
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+
+#define EUNIMPLEMENTED 566456
+
+#endif /* COMMON_H_ */
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..bb1ab32
--- /dev/null
@@ -0,0 +1,20 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "rtr/rtr.h"
+
+/*
+ * This program is an RTR server.
+ *
+ * RTR ("RPKI-to-Router") is a protocol (defined in RFCs 6810 and 8210) that
+ * reports the work of an RPKI validator (cryptographcally-verified attestations
+ * that define the ASN that owns a given routing prefix). It is normally served
+ * to routers who wish to verify BGP claims.
+ */
+int
+main(int argc, char *argv[])
+{
+       puts("!!!Hello World!!!");
+       rtr_listen();
+       return EXIT_SUCCESS;
+}
diff --git a/src/rtr/pdu.c b/src/rtr/pdu.c
new file mode 100644 (file)
index 0000000..7adbb82
--- /dev/null
@@ -0,0 +1,225 @@
+#include "pdu.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common.h"
+#include "pdu_handler.h"
+#include "primitive_reader.h"
+
+static int     pdu_header_from_stream(int, struct pdu_header *);
+static int     serial_notify_from_stream(struct pdu_header *, int, void *);
+static int     serial_query_from_stream(struct pdu_header *, int, void *);
+static int     reset_query_from_stream(struct pdu_header *, int, void *);
+static int     cache_response_from_stream(struct pdu_header *, int, void *);
+static int     ipv4_prefix_from_stream(struct pdu_header *, int, void *);
+static int     ipv6_prefix_from_stream(struct pdu_header *, int, void *);
+static int     end_of_data_from_stream(struct pdu_header *, int, void *);
+static int     cache_reset_from_stream(struct pdu_header *, int, void *);
+static int     error_report_from_stream(struct pdu_header *, int, void *);
+static void    error_report_destroy(void *);
+
+int
+pdu_load(int fd, void **pdu, struct pdu_metadata const **metadata)
+{
+       struct pdu_header header;
+       struct pdu_metadata const *meta;
+       int err;
+
+       err = pdu_header_from_stream(fd, &header);
+       if (err)
+               return err;
+
+       meta = pdu_get_metadata(header.pdu_type);
+       if (!meta)
+               return -ENOENT; /* TODO try to skip it anyway? */
+
+       pdu = malloc(meta->length);
+       if (!pdu)
+               return -ENOMEM;
+
+       err = meta->from_stream(&header, fd, pdu);
+       if (err) {
+               free(pdu);
+               return err;
+       }
+
+       if (metadata)
+               *metadata = meta;
+       return 0;
+}
+
+static int
+pdu_header_from_stream(int fd, struct pdu_header *header)
+{
+       return read_int8(fd, &header->protocol_version)
+           || read_int8(fd, &header->pdu_type)
+           || read_int16(fd, &header->session_id)
+           || read_int32(fd, &header->length);
+}
+
+static int
+serial_notify_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct serial_notify_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+}
+
+static int
+serial_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct serial_query_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+}
+
+static int
+reset_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct reset_query_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+}
+
+static int
+cache_response_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct cache_response_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+}
+
+static int
+ipv4_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct ipv4_prefix_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int8(fd, &pdu->flags)
+           || read_int8(fd, &pdu->prefix_length)
+           || read_int8(fd, &pdu->max_length)
+           || read_int8(fd, &pdu->zero)
+           || read_in_addr(fd, &pdu->ipv4_prefix)
+           || read_int32(fd, &pdu->asn);
+}
+
+static int
+ipv6_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct ipv6_prefix_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int8(fd, &pdu->flags)
+           || read_int8(fd, &pdu->prefix_length)
+           || read_int8(fd, &pdu->max_length)
+           || read_int8(fd, &pdu->zero)
+           || read_in6_addr(fd, &pdu->ipv6_prefix)
+           || read_int32(fd, &pdu->asn);
+}
+
+static int
+end_of_data_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct end_of_data_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+}
+
+static int
+cache_reset_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct cache_reset_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+}
+
+static int
+error_report_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+{
+       struct error_report_pdu *pdu = pdu_void;
+       u_int32_t sub_pdu_len; /* TODO use this for something */
+       int err;
+
+       memcpy(&pdu->header, header, sizeof(*header));
+
+       err = read_int32(fd, &sub_pdu_len);
+       if (err)
+               return err;
+       err = pdu_load(fd, &pdu->erroneous_pdu, NULL);
+       if (err)
+               return -EINVAL;
+
+       err = read_string(fd, &pdu->error_message);
+       if (err) {
+               free(pdu->erroneous_pdu);
+               return err;
+       }
+
+       return 0;
+}
+
+static void
+error_report_destroy(void *pdu_void)
+{
+       struct error_report_pdu *pdu = pdu_void;
+       struct pdu_header *sub_hdr;
+       struct pdu_metadata const *sub_meta;
+
+       sub_hdr = pdu_get_header(pdu->erroneous_pdu);
+       sub_meta = pdu_get_metadata(sub_hdr->pdu_type);
+       if (sub_meta)
+               sub_meta->destructor(pdu->erroneous_pdu);
+       else
+               warnx("Unknown PDU type (%u).", sub_hdr->pdu_type);
+
+       free(pdu->error_message);
+       free(pdu);
+}
+
+#define DEFINE_METADATA(name, dtor)                                    \
+       static struct pdu_metadata const name ## _meta = {              \
+               .length = sizeof(struct name ## _pdu),                  \
+               .from_stream = name ## _from_stream,                    \
+               .handle = handle_ ## name ## _pdu,                      \
+               .destructor = dtor,                                     \
+       }
+
+DEFINE_METADATA(serial_notify, free);
+DEFINE_METADATA(serial_query, free);
+DEFINE_METADATA(reset_query, free);
+DEFINE_METADATA(cache_response, free);
+DEFINE_METADATA(ipv4_prefix, free);
+DEFINE_METADATA(ipv6_prefix, free);
+DEFINE_METADATA(end_of_data, free);
+DEFINE_METADATA(cache_reset, free);
+DEFINE_METADATA(error_report, error_report_destroy);
+
+struct pdu_metadata const *const pdu_metadatas[] = {
+       /* 0 */  &serial_notify_meta,
+       /* 1 */  &serial_query_meta,
+       /* 2 */  &reset_query_meta,
+       /* 3 */  &cache_response_meta,
+       /* 4 */  &ipv4_prefix_meta,
+       /* 5 */  NULL,
+       /* 6 */  &ipv6_prefix_meta,
+       /* 7 */  &end_of_data_meta,
+       /* 8 */  &cache_reset_meta,
+       /* 9 */  NULL,
+       /* 10 */ &error_report_meta,
+};
+
+struct pdu_metadata const *
+pdu_get_metadata(u_int8_t type)
+{
+       return (type < 0 || ARRAY_SIZE(pdu_metadatas) <= type)
+           ? NULL
+           : pdu_metadatas[type];
+}
+
+struct pdu_header *
+pdu_get_header(void *pdu)
+{
+       /* The header is by definition the first field of every PDU. */
+       return pdu;
+}
diff --git a/src/rtr/pdu.h b/src/rtr/pdu.h
new file mode 100644 (file)
index 0000000..da33850
--- /dev/null
@@ -0,0 +1,85 @@
+#ifndef RTR_PDU_H_
+#define RTR_PDU_H_
+
+#include <netinet/in.h>
+
+#include "../common.h"
+
+struct pdu_header {
+       u_int8_t        protocol_version;
+       u_int8_t        pdu_type;
+       union {
+               u_int16_t       session_id;
+               u_int16_t       reserved;
+               u_int16_t       error_code;
+       };
+       u_int32_t       length;
+};
+
+struct serial_notify_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+};
+
+struct serial_query_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+};
+
+struct reset_query_pdu {
+       struct  pdu_header header;
+};
+
+struct cache_response_pdu {
+       struct pdu_header header;
+};
+
+struct ipv4_prefix_pdu {
+       struct  pdu_header header;
+       u_int8_t        flags;
+       u_int8_t        prefix_length;
+       u_int8_t        max_length;
+       u_int8_t        zero;
+       struct  in_addr ipv4_prefix;
+       u_int32_t       asn;
+};
+
+struct ipv6_prefix_pdu {
+       struct  pdu_header header;
+       u_int8_t        flags;
+       u_int8_t        prefix_length;
+       u_int8_t        max_length;
+       u_int8_t        zero;
+       struct  in6_addr ipv6_prefix;
+       u_int32_t       asn;
+};
+
+struct end_of_data_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+};
+
+struct cache_reset_pdu {
+       struct  pdu_header header;
+};
+
+struct error_report_pdu {
+       struct  pdu_header header;
+       void    *erroneous_pdu;
+       char    *error_message;
+};
+
+struct pdu_metadata {
+       size_t  length;
+       int     (*from_stream)(struct pdu_header *, int, void *);
+       int     (*handle)(void *);
+       void    (*destructor)(void *);
+};
+
+__BEGIN_DECLS
+int pdu_load(int, void **, struct pdu_metadata const **);
+struct pdu_metadata const *pdu_get_metadata(u_int8_t);
+struct pdu_header *pdu_get_header(void *);
+__END_DECLS
+
+#endif /* RTR_PDU_H_ */
diff --git a/src/rtr/pdu_handler.c b/src/rtr/pdu_handler.c
new file mode 100644 (file)
index 0000000..5d91f31
--- /dev/null
@@ -0,0 +1,71 @@
+#include "pdu_handler.h"
+
+#include <err.h>
+#include <errno.h>
+
+static int warn_unexpected_pdu(char *);
+
+static int
+warn_unexpected_pdu(char *pdu_name)
+{
+       warnx("RTR servers are not expected to receive %s PDUs, but we got one anyway (Closing socket.)",
+           pdu_name);
+       return -EINVAL;
+}
+
+int
+handle_serial_notify_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("Serial Notify");
+}
+
+int
+handle_serial_query_pdu(void *pdu)
+{
+       /* TODO */
+       return -EUNIMPLEMENTED;
+}
+
+int
+handle_reset_query_pdu(void *pdu)
+{
+       /* TODO */
+       return -EUNIMPLEMENTED;
+}
+
+int
+handle_cache_response_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("Cache Response");
+}
+
+int
+handle_ipv4_prefix_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("IPv4 Prefix");
+}
+
+int
+handle_ipv6_prefix_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("IPv6 Prefix");
+}
+
+int
+handle_end_of_data_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("End of Data");
+}
+
+int
+handle_cache_reset_pdu(void *pdu)
+{
+       return warn_unexpected_pdu("Cache Reset");
+}
+
+int
+handle_error_report_pdu(void *pdu)
+{
+       /* TODO */
+       return -EUNIMPLEMENTED;
+}
diff --git a/src/rtr/pdu_handler.h b/src/rtr/pdu_handler.h
new file mode 100644 (file)
index 0000000..481cb06
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef RTR_PDU_HANDLER_H_
+#define RTR_PDU_HANDLER_H_
+
+#include "../common.h"
+
+__BEGIN_DECLS
+int handle_serial_notify_pdu(void *);
+int handle_serial_query_pdu(void *);
+int handle_reset_query_pdu(void *);
+int handle_cache_response_pdu(void *);
+int handle_ipv4_prefix_pdu(void *);
+int handle_ipv6_prefix_pdu(void *);
+int handle_end_of_data_pdu(void *);
+int handle_cache_reset_pdu(void *);
+int handle_error_report_pdu(void *);
+__END_DECLS
+
+#endif /* RTR_PDU_HANDLER_H_ */
diff --git a/src/rtr/primitive_reader.c b/src/rtr/primitive_reader.c
new file mode 100644 (file)
index 0000000..74ea07d
--- /dev/null
@@ -0,0 +1,108 @@
+#include "primitive_reader.h"
+
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+
+static int
+read_exact(int fd, unsigned char *buffer, size_t length)
+{
+       int n, m;
+       int err;
+
+       for (n = 0; n < length;) {
+               m = read(fd, &buffer[n], length - n);
+               if (m < 0) {
+                       err = errno;
+                       warn("Client socket read interrupted");
+                       return err;
+               }
+
+               if (m == 0 && n == 0) {
+                       /* Stream ended gracefully. */
+                       return 0;
+               }
+
+               if (m == 0) {
+                       err = -EPIPE;
+                       warn("Stream ended mid-PDU");
+                       return err;
+               }
+
+               n += m;
+       }
+
+       return 0;
+}
+
+int
+read_int8(int fd, u_int8_t *result)
+{
+       return read_exact(fd, result, sizeof(u_int8_t));
+}
+
+/** Big Endian. */
+int
+read_int16(int fd, u_int16_t *result)
+{
+       unsigned char buffer[2];
+       int err;
+
+       err = read_exact(fd, buffer, sizeof(buffer));
+       if (err)
+               return err;
+
+       *result = (((u_int16_t)buffer[0]) << 8) | ((u_int16_t)buffer[1]);
+       return 0;
+}
+
+/** Big Endian. */
+int
+read_int32(int fd, u_int32_t *result)
+{
+       unsigned char buffer[4];
+       int err;
+
+       err = read_exact(fd, buffer, sizeof(buffer));
+       if (err)
+               return err;
+
+       *result = (((u_int32_t)buffer[0]) << 24)
+               | (((u_int32_t)buffer[1]) << 16)
+               | (((u_int32_t)buffer[2]) <<  8)
+               | (((u_int32_t)buffer[3])      );
+       return 0;
+}
+
+int
+read_in_addr(int fd, struct in_addr *result)
+{
+       return read_int32(fd, &result->s_addr);
+}
+
+int
+read_in6_addr(int fd, struct in6_addr *result)
+{
+       return read_int32(fd, &result->s6_addr32[0])
+           || read_int32(fd, &result->s6_addr32[1])
+           || read_int32(fd, &result->s6_addr32[2])
+           || read_int32(fd, &result->s6_addr32[3]);
+}
+
+int
+read_string(int fd, char **result)
+{
+       u_int32_t length;
+       int err;
+
+       err = read_int32(fd, &length);
+       if (err)
+               return err;
+
+       /*
+        * TODO the RFC doesn't say if the length is in bytes, code points or
+        * graphemes...
+        */
+       *result = NULL;
+       return 0;
+}
diff --git a/src/rtr/primitive_reader.h b/src/rtr/primitive_reader.h
new file mode 100644 (file)
index 0000000..346f59f
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef RTR_PRIMITIVE_READER_H_
+#define RTR_PRIMITIVE_READER_H_
+
+#include <netinet/ip.h>
+
+#include "../common.h"
+
+__BEGIN_DECLS
+int read_int8(int, u_int8_t *);
+int read_int16(int, u_int16_t *);
+int read_int32(int, u_int32_t *);
+int read_in_addr(int, struct in_addr *);
+int read_in6_addr(int, struct in6_addr *);
+int read_string(int, char **);
+__END_DECLS
+
+#endif /* RTR_PRIMITIVE_READER_H_ */
diff --git a/src/rtr/rtr.c b/src/rtr/rtr.c
new file mode 100644 (file)
index 0000000..a89240d
--- /dev/null
@@ -0,0 +1,201 @@
+#include "rtr.h"
+
+#include <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common.h"
+#include "pdu.h"
+
+/*
+ * Creates the socket that will stay put and wait for new connections started
+ * from the clients.
+ */
+static int
+create_server_socket(void)
+{
+       int fd; /* "file descriptor" */
+       struct sockaddr_in address;
+       int err;
+
+       fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (fd < 0) {
+               err = errno;
+               warn("Error opening socket");
+               return -abs(err);
+       }
+
+       memset(&address, 0, sizeof(address));
+       address.sin_family = AF_INET;
+       address.sin_addr.s_addr = INADDR_ANY;
+       address.sin_port = htons(5001);
+       if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
+               err = errno;
+               warn("Could not bind the address");
+               close(fd);
+               return -abs(err);
+       }
+
+       return fd;
+}
+
+/*
+ * Arguments that the server socket thread will send to the client socket
+ * threads whenever it creates them.
+ */
+struct thread_param {
+       int     client_fd;
+};
+
+enum verdict {
+       /* No errors; continue happily. */
+       VERDICT_SUCCESS,
+       /* A temporal error just happened. Try again. */
+       VERDICT_RETRY,
+       /* "Stop whatever you're doing and return." */
+       VERDICT_EXIT,
+};
+
+/*
+ * Converts an error code to a verdict.
+ * The error code is assumed to have been spewed by the `accept()` function.
+ */
+static enum verdict
+handle_accept_result(int client_fd, int err)
+{
+       if (client_fd == 0)
+               return VERDICT_SUCCESS;
+
+       /*
+        * Note: I can't just use a single nice switch because EAGAIN and
+        * EWOULDBLOCK are the same value in at least one supported system
+        * (Linux).
+        */
+
+       /*
+        * TODO this `if` is a Linux quirk and should probably not exist in the
+        * BSDs. See `man 2 accept`.
+        */
+       if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT
+           || err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH
+           || err == EOPNOTSUPP || err == ENETUNREACH)
+               return VERDICT_RETRY;
+
+       if (err == EAGAIN || err == EWOULDBLOCK)
+               return VERDICT_RETRY;
+
+       errno = err;
+       warn("Connection acceptor thread interrupted");
+       return VERDICT_EXIT;
+}
+
+/*
+ * The client socket threads' entry routine.
+ *
+ * Please remember that this function needs to always release @param_void before
+ * returning.
+ */
+static void *
+client_thread_cb(void *param_void)
+{
+       struct thread_param param;
+       struct pdu_metadata const *meta;
+       void *pdu;
+       int err;
+
+       memcpy(&param, param_void, sizeof(param));
+       free(param_void);
+
+       while (true) { /* For each PDU... */
+               err = pdu_load(param.client_fd, &pdu, &meta);
+               if (err)
+                       return NULL;
+
+               err = meta->handle(&pdu);
+               meta->destructor(pdu);
+               if (err)
+                       return NULL;
+       }
+
+       return NULL; /* Unreachable. */
+}
+
+/*
+ * Waits for client connections and spawns threads to handle them.
+ */
+static int
+handle_client_connections(int server_fd)
+{
+       int client_fd;
+       struct sockaddr_in client_addr;
+       socklen_t sizeof_client_addr;
+       struct thread_param *arg;
+       pthread_t thread;
+
+       listen(server_fd, 5);
+
+       sizeof_client_addr = sizeof(client_addr);
+
+       do {
+               client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
+                   &sizeof_client_addr);
+               switch (handle_accept_result(client_fd, errno)) {
+               case VERDICT_SUCCESS:
+                       break;
+               case VERDICT_RETRY:
+                       continue;
+               case VERDICT_EXIT:
+                       return 0;
+               }
+
+               /*
+                * Note: My gut says that errors from now on (even the unknown
+                * ones) should be treated as temporary; maybe the next accept()
+                * will work.
+                * So don't interrupt the thread when this happens.
+                */
+
+               arg = malloc(sizeof(struct thread_param));
+               if (!arg) {
+                       warnx("Thread parameter allocation failure");
+                       continue;
+               }
+               arg->client_fd = client_fd;
+
+               errno = pthread_create(&thread, NULL, client_thread_cb, arg);
+               if (errno) {
+                       warn("Could not spawn the client's thread");
+                       free(arg);
+                       close(client_fd);
+                       continue;
+               }
+
+               /* BTW: The thread will be responsible for releasing @arg. */
+               pthread_detach(thread);
+
+       } while (true);
+
+       return 0; /* Unreachable. */
+}
+
+/*
+ * Starts the server, using the current thread to listen for RTR client
+ * requests.
+ *
+ * This function blocks.
+ */
+int
+rtr_listen(void)
+{
+       int server_fd; /* "file descriptor" */
+
+       server_fd = create_server_socket();
+       if (server_fd < 0)
+               return server_fd;
+
+       return handle_client_connections(server_fd);
+}
diff --git a/src/rtr/rtr.h b/src/rtr/rtr.h
new file mode 100644 (file)
index 0000000..f68668b
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef RTR_RTR_H_
+#define RTR_RTR_H_
+
+#include "../common.h"
+
+__BEGIN_DECLS
+int rtr_listen(void);
+__END_DECLS
+
+#endif /* RTR_RTR_H_ */