From 154fac96692425fc6ec32791e128c498532f2c94 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Thu, 16 Aug 2018 11:41:12 -0500 Subject: [PATCH] Add RTR socket draft --- src/common.h | 21 ++++ src/main.c | 20 ++++ src/rtr/pdu.c | 225 +++++++++++++++++++++++++++++++++++++ src/rtr/pdu.h | 85 ++++++++++++++ src/rtr/pdu_handler.c | 71 ++++++++++++ src/rtr/pdu_handler.h | 18 +++ src/rtr/primitive_reader.c | 108 ++++++++++++++++++ src/rtr/primitive_reader.h | 17 +++ src/rtr/rtr.c | 201 +++++++++++++++++++++++++++++++++ src/rtr/rtr.h | 10 ++ 10 files changed, 776 insertions(+) create mode 100644 src/common.h create mode 100644 src/main.c create mode 100644 src/rtr/pdu.c create mode 100644 src/rtr/pdu.h create mode 100644 src/rtr/pdu_handler.c create mode 100644 src/rtr/pdu_handler.h create mode 100644 src/rtr/primitive_reader.c create mode 100644 src/rtr/primitive_reader.h create mode 100644 src/rtr/rtr.c create mode 100644 src/rtr/rtr.h diff --git a/src/common.h b/src/common.h new file mode 100644 index 00000000..1f54d5d6 --- /dev/null +++ b/src/common.h @@ -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 index 00000000..bb1ab320 --- /dev/null +++ b/src/main.c @@ -0,0 +1,20 @@ +#include +#include + +#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 index 00000000..7adbb824 --- /dev/null +++ b/src/rtr/pdu.c @@ -0,0 +1,225 @@ +#include "pdu.h" + +#include +#include +#include +#include + +#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 index 00000000..da338508 --- /dev/null +++ b/src/rtr/pdu.h @@ -0,0 +1,85 @@ +#ifndef RTR_PDU_H_ +#define RTR_PDU_H_ + +#include + +#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 index 00000000..5d91f31c --- /dev/null +++ b/src/rtr/pdu_handler.c @@ -0,0 +1,71 @@ +#include "pdu_handler.h" + +#include +#include + +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 index 00000000..481cb06c --- /dev/null +++ b/src/rtr/pdu_handler.h @@ -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 index 00000000..74ea07db --- /dev/null +++ b/src/rtr/primitive_reader.c @@ -0,0 +1,108 @@ +#include "primitive_reader.h" + +#include +#include +#include + +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 index 00000000..346f59fd --- /dev/null +++ b/src/rtr/primitive_reader.h @@ -0,0 +1,17 @@ +#ifndef RTR_PRIMITIVE_READER_H_ +#define RTR_PRIMITIVE_READER_H_ + +#include + +#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 index 00000000..a89240da --- /dev/null +++ b/src/rtr/rtr.c @@ -0,0 +1,201 @@ +#include "rtr.h" + +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/rtr/rtr.h b/src/rtr/rtr.h new file mode 100644 index 00000000..f68668b0 --- /dev/null +++ b/src/rtr/rtr.h @@ -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_ */ -- 2.47.3