bin_PROGRAMS = rtr_server
rtr_server_SOURCES = main.c
+rtr_server_SOURCES += address.c address.h
rtr_server_SOURCES += common.c common.h
rtr_server_SOURCES += configuration.c configuration.h
+rtr_server_SOURCES += csv.c csv.h
+rtr_server_SOURCES += line_file.c line_file.h
rtr_server_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
rtr_server_SOURCES += rtr/pdu.c rtr/pdu.h
--- /dev/null
+#include "address.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h> /* inet_ntop */
+
+static char const *
+addr2str4(struct in_addr *addr, char *buffer)
+{
+ return inet_ntop(AF_INET, addr, buffer, INET_ADDRSTRLEN);
+}
+
+static char const *
+addr2str6(struct in6_addr *addr, char *buffer)
+{
+ return inet_ntop(AF_INET6, addr, buffer, INET6_ADDRSTRLEN);
+}
+
+static int const
+str2addr4(const char *addr, struct in_addr *dst)
+{
+ if (!inet_pton(AF_INET, addr, dst))
+ return -EINVAL;
+ return 0;
+}
+
+static int const
+str2addr6(const char *addr, struct in6_addr *dst)
+{
+ if (!inet_pton(AF_INET6, addr, dst))
+ return -EINVAL;
+ return 0;
+}
+
+/*
+ * Returns a mask you can use to extract the suffix bits of a 32-bit unsigned
+ * number whose prefix lengths @prefix_len.
+ * For example: Suppose that your number is 192.0.2.0/24.
+ * u32_suffix_mask(24) returns 0.0.0.255.
+ *
+ * The result is in host byte order.
+ */
+static uint32_t
+u32_suffix_mask(unsigned int prefix_len)
+{
+ /* `a >> 32` is undefined if `a` is 32 bits. */
+ return (prefix_len < 32) ? (0xFFFFFFFFu >> prefix_len) : 0;
+}
+
+/**
+ * Same as u32_suffix_mask(), except the result is in network byte order
+ * ("be", for "big endian").
+ */
+static uint32_t
+be32_suffix_mask(unsigned int prefix_len)
+{
+ return htonl(u32_suffix_mask(prefix_len));
+}
+
+static void
+ipv6_suffix_mask(unsigned int prefix_len, struct in6_addr *result)
+{
+ if (prefix_len < 32) {
+ result->s6_addr32[0] |= be32_suffix_mask(prefix_len);
+ result->s6_addr32[1] = 0xFFFFFFFFu;
+ result->s6_addr32[2] = 0xFFFFFFFFu;
+ result->s6_addr32[3] = 0xFFFFFFFFu;
+ } else if (prefix_len < 64) {
+ result->s6_addr32[1] |= be32_suffix_mask(prefix_len - 32);
+ result->s6_addr32[2] = 0xFFFFFFFFu;
+ result->s6_addr32[3] = 0xFFFFFFFFu;
+ } else if (prefix_len < 96) {
+ result->s6_addr32[2] |= be32_suffix_mask(prefix_len - 64);
+ result->s6_addr32[3] = 0xFFFFFFFFu;
+ } else {
+ result->s6_addr32[3] |= be32_suffix_mask(prefix_len - 96);
+ }
+}
+
+int
+prefix4_decode(const char *str, struct ipv4_prefix *result)
+{
+ int error;
+
+ if (str == NULL) {
+ err(-EINVAL, "Null string received, can't decode IPv4 prefix");
+ return -EINVAL;
+ }
+
+ error = str2addr4(str, &result->addr);
+ if (error) {
+ err(error, "Invalid IPv4 prefix %s", str);
+ return error;
+ }
+
+ return 0;
+}
+
+int
+prefix6_decode(const char *str, struct ipv6_prefix *result)
+{
+ int error;
+
+ if (str == NULL) {
+ err(-EINVAL, "Null string received, can't decode IPv6 prefix");
+ return -EINVAL;
+ }
+
+ error = str2addr6(str, &result->addr);
+ if (error) {
+ err(error, "Invalid IPv6 prefix %s", str);
+ return error;
+ }
+
+ return 0;
+ }
+
+int
+prefix_length_decode (const char *text, unsigned int *dst, int max_value)
+{
+ unsigned long len;
+
+ if (text == NULL) {
+ err(-EINVAL, "Null string received, can't decode prefix length");
+ return -EINVAL;
+ }
+
+ errno = 0;
+ len = strtoul(text, NULL, 10);
+ if (errno) {
+ err(errno, "Invalid prefix length '%s': %s", text, strerror(errno));
+ return -EINVAL;
+ }
+ /* An underflow or overflow will be considered here */
+ if (len < 0 || max_value < len) {
+ err(-EINVAL, "Prefix length (%ld) is out of bounds (0-%d).",
+ len, max_value);
+ return -EINVAL;
+ }
+ *dst = (unsigned int) len;
+ return 0;
+}
+
+int
+prefix4_validate (struct ipv4_prefix *prefix)
+{
+ char buffer[INET_ADDRSTRLEN];
+
+ if ((prefix->addr.s_addr & be32_suffix_mask(prefix->len)) != 0) {
+ err(-EINVAL, "IPv4 prefix %s/%u has enabled suffix bits.",
+ addr2str4(&prefix->addr, buffer), prefix->len);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int
+prefix6_validate (struct ipv6_prefix *prefix)
+{
+ struct in6_addr suffix;
+ char buffer[INET6_ADDRSTRLEN];
+
+ memset(&suffix, 0, sizeof(suffix));
+ ipv6_suffix_mask(prefix->len, &suffix);
+ if ( (prefix->addr.s6_addr32[0] & suffix.s6_addr32[0])
+ || (prefix->addr.s6_addr32[1] & suffix.s6_addr32[1])
+ || (prefix->addr.s6_addr32[2] & suffix.s6_addr32[2])
+ || (prefix->addr.s6_addr32[3] & suffix.s6_addr32[3])) {
+ err(-EINVAL, "IPv6 prefix %s/%u has enabled suffix bits.",
+ addr2str6(&prefix->addr, buffer), prefix->len);
+ return -EINVAL;
+ }
+ return 0;
+}
--- /dev/null
+#ifndef SRC_ADDRESS_H_
+#define SRC_ADDRESS_H_
+
+#include <netinet/in.h>
+
+struct ipv4_prefix {
+ struct in_addr addr;
+ unsigned int len;
+};
+
+struct ipv6_prefix {
+ struct in6_addr addr;
+ unsigned int len;
+};
+
+int prefix4_decode(const char *, struct ipv4_prefix *);
+int prefix6_decode(const char *, struct ipv6_prefix *);
+
+int prefix_length_decode(const char *, unsigned int *, int);
+
+int prefix4_validate (struct ipv4_prefix *);
+int prefix6_validate (struct ipv6_prefix *);
+
+#endif /* SRC_ADDRESS_H_ */
#include <err.h>
#include <errno.h>
#include <jansson.h>
+#include <stdbool.h>
#include <string.h>
#include "common.h"
+#include "csv.h"
#define OPTNAME_LISTEN "listen"
#define OPTNAME_LISTEN_ADDRESS "address"
#define OPTNAME_LISTEN_PORT "port"
+#define OPTNAME_VRPS "vrps"
#define DEFAULT_ADDR NULL
#define DEFAULT_PORT "323"
+#define DEFAULT_VRPS NULL
struct rtr_config {
/** The listener address of the RTR server. */
struct addrinfo *address;
/** Stored aside only for printing purposes. */
char *port;
+ /** VRPs (Validated ROA Payload) location */
+ char *vrps;
} config;
static int handle_json(json_t *);
static int json_get_string(json_t *, char const *, char *, char const **);
static int init_addrinfo(char const *, char const *);
+static int init_vrps_db(char const *);
int
config_init(char const *json_file_path)
json_error_t json_error;
int error;
+ /*
+ * TODO What's the point of a default start if there's
+ * no vrps input?
+ */
if (json_file_path == NULL)
return init_addrinfo(DEFAULT_ADDR, DEFAULT_PORT);
freeaddrinfo(config.address);
if (config.port != NULL)
free(config.port);
+ if (config.vrps != NULL)
+ free(config.vrps);
}
static int
json_t *listen;
char const *address;
char const *port;
+ char const *vrps;
int error;
if (!json_is_object(root)) {
port = DEFAULT_PORT;
}
+ error = json_get_string(root, OPTNAME_VRPS,
+ DEFAULT_VRPS, &vrps);
+ if (error)
+ return error;
+
+ error = init_vrps_db(vrps);
+ if (error)
+ return error;
+
return init_addrinfo(address, port);
}
return 0;
}
+static int
+init_vrps_db(char const *vrps_location)
+{
+ /* FIXME Complete me! */
+ int error;
+
+ if (vrps_location == NULL || strlen(vrps_location) < 1) {
+ warnx("VRPs location must be set");
+ return -EINVAL;
+ }
+
+ error = parse_file(vrps_location);
+ if (error)
+ return error; /* Error msg already printed. */
+
+ return 0;
+}
+
static int
init_addrinfo(char const *hostname, char const *service)
{
{
return config.port;
}
+
+char const *
+config_get_vrps(void)
+{
+ return config.vrps;
+}
--- /dev/null
+#include "csv.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "address.h"
+#include "line_file.h"
+
+struct csv_data {
+ char *asn;
+ char *prefix;
+ int max_length;
+ char *trust_anchor;
+};
+
+/* @ext must include the period. */
+static bool
+location_has_extension(char const *location, char const *ext)
+{
+ size_t ext_len, loc_len;
+ int cmp;
+
+ ext_len = strlen(ext);
+ loc_len = strlen(location);
+ if (loc_len < ext_len)
+ return false;
+
+ cmp = strncmp(location + loc_len - ext_len, ext, ext_len);
+ return cmp == 0;
+}
+
+static int
+parse_asn(char *text)
+{
+ if (text == NULL)
+ return -EINVAL;
+ return 0;
+}
+
+static int
+parse_prefix4(char *text, struct ipv4_prefix *prefixv4)
+{
+ if (text == NULL)
+ return -EINVAL;
+ return prefix4_decode(text, prefixv4);
+}
+
+static int
+parse_prefix6(char *text, struct ipv6_prefix *prefixv6)
+{
+ if (text == NULL)
+ return -EINVAL;
+ return prefix6_decode(text, prefixv6);
+}
+
+static int
+parse_prefix_length(char *text, unsigned int *value, int max_value)
+{
+ if (text == NULL)
+ return -EINVAL;
+ return prefix_length_decode(text, value, max_value);
+}
+
+static int
+add_vrp(char *line)
+{
+ struct ipv4_prefix prefixv4;
+ struct ipv6_prefix prefixv6;
+ unsigned int prefix_length, max_prefix_length;
+ int error;
+ bool isv4;
+ char *token, *line_copy;
+
+ line_copy = malloc(strlen(line) + 1);
+ if (line_copy == NULL) {
+ error = -ENOMEM;
+ err(error, "Out of memory allocating CSV line copy");
+ }
+ strcpy(line_copy, line);
+
+ error = 0;
+
+ /* First column: ASN in format "AS###" */
+ token = strtok(line_copy, ",");
+ error = parse_asn(token);
+ if (error)
+ goto error;
+
+ /* Second column (first part): Prefix in string format */
+ token = strtok(NULL, "/");
+ isv4 = strchr(token, ':') == NULL;
+ if (isv4)
+ error = parse_prefix4(token, &prefixv4);
+ else
+ error = parse_prefix6(token, &prefixv6);
+
+ if (error)
+ goto error;
+
+ /* Second column (second part): Prefix length in numeric format */
+ token = strtok(NULL, ",");
+ error = parse_prefix_length(token, &prefix_length, isv4 ? 32 : 128);
+ if (error)
+ goto error;
+
+ /* Third column: Prefix max length in numeric format */
+ token = strtok(NULL, ",");
+ error = parse_prefix_length(token, &max_prefix_length, isv4 ? 32 : 128);
+ if (error)
+ goto error;
+
+ /* Now validate the prefix */
+ if (isv4) {
+ prefixv4.len = prefix_length;
+ error = prefix4_validate(&prefixv4);
+ } else {
+ prefixv6.len = prefix_length;
+ error = prefix6_validate(&prefixv6);
+ }
+ if (error)
+ goto error;
+
+ if (prefix_length > max_prefix_length) {
+ error = -EINVAL;
+ err(error, "Prefix length is greater than max prefix length [%u > %u]",
+ prefix_length, max_prefix_length);
+ }
+
+ /* TODO Now store the values in memory */
+error:
+ return error;
+}
+
+static int
+read_vrps(struct line_file *lfile)
+{
+ char *line;
+ int current_line;
+ int error;
+
+ /* First line is expected to be the header, ignore it */
+ current_line = 1;
+ error = lfile_read(lfile, &line);
+ if (error) {
+ err(error, "Error at first line, stop processing CSV file.");
+ return error;
+ }
+ if (line == NULL) {
+ error = -EINVAL;
+ err(error, "Empty file, stop processing.");
+ return error;
+ }
+ do {
+ ++current_line;
+ error = lfile_read(lfile, &line);
+ if (error) {
+ err(error, "Error at line %d, stop processing file.", current_line);
+ if (line != NULL)
+ free(line);
+ return error;
+ }
+ if (line == NULL) {
+ free(line);
+ return 0;
+ }
+ if (strcmp(line, "") == 0) {
+ warn("There's nothing at line %d, ignoring.", current_line);
+ continue;
+ }
+
+ error = add_vrp(line);
+ if (error) {
+ free(line);
+ return error;
+ }
+ } while (true);
+}
+
+int
+parse_file(char const *location)
+{
+ struct line_file *lfile;
+ int error;
+
+ if (!location_has_extension(location, ".csv")) {
+ warn("%s isn't a CSV file", location);
+ error = -EINVAL;
+ goto end1;
+ }
+
+ error = lfile_open(location, &lfile);
+ if (error)
+ goto end1; /* Error msg already printed. */
+
+ error = read_vrps(lfile);
+ if (error)
+ goto end2;
+
+end2:
+ lfile_close(lfile);
+end1:
+ return error;
+}
--- /dev/null
+#ifndef SRC_CSV_H_
+#define SRC_CSV_H_
+
+int parse_file(char const *);
+
+#endif /* SRC_CSV_H_ */
--- /dev/null
+#include "line_file.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct line_file {
+ FILE *file;
+ const char *file_name;
+ size_t offset;
+};
+
+/*
+ * @file_name is expected to outlive the lfile.
+ */
+int
+lfile_open(const char *file_name, struct line_file **result)
+{
+ struct line_file *lfile;
+ int error;
+
+ lfile = malloc(sizeof(struct line_file));
+ if (lfile == NULL)
+ return -ENOMEM;
+
+ lfile->file = fopen(file_name, "r");
+ if (lfile->file == NULL) {
+ error = errno;
+ free(lfile);
+ return error;
+ }
+ lfile->file_name = file_name;
+ lfile->offset = 0;
+
+ *result = lfile;
+ return 0;
+}
+
+void
+lfile_close(struct line_file *lf)
+{
+ if (fclose(lf->file) == -1)
+ err(errno, "fclose() failed: %s", strerror(errno));
+ free(lf);
+}
+
+/*
+ * On success, places the string in *result.
+ * On failure, returns error code.
+ * On EOF reached, returns zero but nullifies result.
+ *
+ * @result is allocated in the heap.
+ */
+int
+lfile_read(struct line_file *lfile, char **result)
+{
+ char *string;
+ size_t alloc_len;
+ ssize_t len;
+ ssize_t i;
+ int error;
+
+ /*
+ * Note to myself:
+ *
+ * getline() is very convoluted. I really don't like it. I'm actually
+ * considering getting rid of it and pulling off something that doesn't
+ * seem like it was designed by an alien, but it doesn't warrant going
+ * that far yet. Do not read its Linux man page; it didn't answer my
+ * questions. Go straight to POSIX instead.
+ *
+ * - If the file is empty, or all that's left is an empty line, it
+ * (confusingly) returns -1. errno will be 0, feof() should return
+ * 1, ferror() should return 0.
+ * - The fact that it returns the newline in the buffer is puzzling,
+ * because who the fuck wants that nonsense. You will want to remove
+ * it, BUT DON'T SWEAT IT IF IT'S NOT THERE, because the last line of
+ * the file might not be newline-terminated.
+ * - The string WILL be NULL-terminated, but the NULL chara will not be
+ * included in the returned length. BUT IT'S THERE. Don't worry about
+ * writing past the allocated space on the last line.
+ * - Newline is `\n` according to POSIX, which is good, because RFC 7730
+ * agrees. You will have to worry about `\r`, though.
+ *
+ * Also, the Linux man page claims the following:
+ *
+ * [The out] buffer should be freed by the user program even if
+ * getline() failed.
+ *
+ * This... does not exist in the POSIX spec. But it does make sense
+ * because getline is normally meant to be used repeatedly with a
+ * recycled buffer. (free() is a no-op if its argument is NULL so go
+ * nuts.)
+ */
+
+ string = NULL;
+ alloc_len = 0;
+ len = getline(&string, &alloc_len, lfile->file);
+
+ if (len == -1) {
+ error = errno;
+ free(string);
+ *result = NULL;
+ if (ferror(lfile->file)) {
+ err(error, "Error while reading file: %s\n", strerror(error));
+ return error;
+ }
+ if (feof(lfile->file))
+ return 0;
+
+ error = -EINVAL;
+ err(error, "Supposedly unreachable code reached. ferror:%d feof:%d\n",
+ ferror(lfile->file), feof(lfile->file));
+ return error;
+ }
+
+ lfile->offset += len;
+
+ /*
+ * Make sure that strlen() matches len.
+ * We should make the best out of the fact that we didn't use fgets(),
+ * after all.
+ */
+ for (i = 0; i < len; i++) {
+ if (string[i] == '\0') {
+ error = -EINVAL;
+ err(error,
+ "File '%s' has an illegal null character in its body. Please remove it.\n",
+ lfile_name(lfile));
+ free(string);
+ return error;
+ }
+ }
+
+ if (len >= 2) {
+ if (string[len - 2] == '\r' && string[len - 1] == '\n')
+ string[len - 2] = '\0';
+ }
+ if (len >= 1) {
+ if (string[len - 1] == '\n')
+ string[len - 1] = '\0';
+ }
+
+ *result = string;
+ return 0;
+}
+
+FILE *
+lfile_fd(struct line_file *lfile)
+{
+ return lfile->file;
+}
+
+const char *
+lfile_name(struct line_file *lfile)
+{
+ return lfile->file_name;
+}
+
+size_t
+lfile_offset(struct line_file *lfile)
+{
+ return lfile->offset;
+}
--- /dev/null
+#ifndef LINE_FILE_H_
+#define LINE_FILE_H_
+
+/*
+ * A "line file" is a text file that you want to read line-by-line.
+ *
+ * Lines are terminated by either CRLF or LF.
+ * (...which is the same as saying "lines are terminated by LF.")
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+
+struct line_file;
+
+int lfile_open(const char *, struct line_file **);
+void lfile_close();
+
+int lfile_read(struct line_file *, char **);
+
+FILE *lfile_fd(struct line_file *);
+const char *lfile_name(struct line_file *);
+size_t lfile_offset(struct line_file *);
+
+#endif /* LINE_FILE_H_ */
}
}
+ /* TODO This will be overriden when reading from config file */
+ if (json_file == NULL) {
+ fprintf(stderr, "Missing flag '-f <file name>'\n");
+ return -EINVAL;
+ }
+
err = config_init(json_file);
if (err)
return err;
# target.
MY_LDADD = $(CHECK_LIBS)
-check_PROGRAMS = rtr/primitive_reader.test rtr/pdu.test
+check_PROGRAMS = rtr/primitive_reader.test rtr/pdu.test address.test
TESTS = $(check_PROGRAMS)
rtr_primitive_reader_test_SOURCES = \
$(top_builddir)/src/rtr/primitive_reader.c \
$(top_builddir)/src/rtr/pdu_handler.c
rtr_pdu_test_LDADD = $(MY_LDADD)
+
+address_test_SOURCES = ../src/address.h
+address_test_SOURCES += address_test.c
+address_test_LDADD = $(MY_LDADD)
--- /dev/null
+#include "address.c"
+
+#include <check.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+START_TEST(load_normal)
+{
+
+}
+END_TEST
+
+static void
+test_get_address_from_string(char *text_prefix)
+{
+ struct ipv4_prefix prefix;
+ const char *result;
+ int error;
+ char buffer[INET_ADDRSTRLEN];
+
+ error = prefix4_decode(text_prefix, &prefix);
+ if (error)
+ return;
+
+ result = addr2str4(&prefix.addr, buffer);
+
+ ck_assert_str_eq(text_prefix, result);
+}
+
+START_TEST(address_test_get_addr)
+{
+ char *text;
+ text = "198.248.146.0";
+
+ test_get_address_from_string(text);
+
+}
+END_TEST
+
+Suite *address_load_suite(void)
+{
+ Suite *suite;
+ TCase *core, *test_get_address;
+
+ core = tcase_create("Core");
+ tcase_add_test(core, load_normal);
+
+ test_get_address = tcase_create("test_get_address");
+ tcase_add_test(test_get_address, address_test_get_addr);
+
+ suite = suite_create("address_test()");
+ suite_add_tcase(suite, core);
+ suite_add_tcase(suite, test_get_address);
+
+ return suite;
+}
+
+int main(void)
+{
+ Suite *suite;
+ SRunner *runner;
+ int tests_failed;
+
+ suite = address_load_suite();
+
+ runner = srunner_create(suite);
+ srunner_run_all(runner, CK_NORMAL);
+ tests_failed = srunner_ntests_failed(runner);
+ srunner_free(runner);
+
+ return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}