--- /dev/null
+#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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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(¶m, 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);
+}
--- /dev/null
+#ifndef RTR_RTR_H_
+#define RTR_RTR_H_
+
+#include "../common.h"
+
+__BEGIN_DECLS
+int rtr_listen(void);
+__END_DECLS
+
+#endif /* RTR_RTR_H_ */