From: pcarana Date: Thu, 14 Feb 2019 15:38:04 +0000 (-0600) Subject: Read VRPs from a CSV file set at JSON config X-Git-Tag: v0.0.2~52^2~68 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3e475893383ad98dfc8e74ad703421f4645be483;p=thirdparty%2FFORT-validator.git Read VRPs from a CSV file set at JSON config --- diff --git a/src/Makefile.am b/src/Makefile.am index 00eca9af..d9764d9d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,8 +4,11 @@ AM_LDFLAGS = 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 diff --git a/src/address.c b/src/address.c new file mode 100644 index 00000000..f1b2a9c3 --- /dev/null +++ b/src/address.c @@ -0,0 +1,176 @@ +#include "address.h" + +#include +#include +#include +#include +#include /* 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; +} diff --git a/src/address.h b/src/address.h new file mode 100644 index 00000000..1969f625 --- /dev/null +++ b/src/address.h @@ -0,0 +1,24 @@ +#ifndef SRC_ADDRESS_H_ +#define SRC_ADDRESS_H_ + +#include + +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_ */ diff --git a/src/configuration.c b/src/configuration.c index 3c99482a..69df376d 100644 --- a/src/configuration.c +++ b/src/configuration.c @@ -4,27 +4,34 @@ #include #include #include +#include #include #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) @@ -33,6 +40,10 @@ 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); @@ -57,6 +68,8 @@ config_cleanup(void) freeaddrinfo(config.address); if (config.port != NULL) free(config.port); + if (config.vrps != NULL) + free(config.vrps); } static int @@ -65,6 +78,7 @@ handle_json(json_t *root) json_t *listen; char const *address; char const *port; + char const *vrps; int error; if (!json_is_object(root)) { @@ -95,6 +109,15 @@ handle_json(json_t *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); } @@ -119,6 +142,24 @@ json_get_string(json_t *parent, char const *name, char *default_value, 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) { @@ -153,3 +194,9 @@ config_get_server_port(void) { return config.port; } + +char const * +config_get_vrps(void) +{ + return config.vrps; +} diff --git a/src/csv.c b/src/csv.c new file mode 100644 index 00000000..bd8d0514 --- /dev/null +++ b/src/csv.c @@ -0,0 +1,206 @@ +#include "csv.h" + +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/csv.h b/src/csv.h new file mode 100644 index 00000000..8fc6a343 --- /dev/null +++ b/src/csv.h @@ -0,0 +1,6 @@ +#ifndef SRC_CSV_H_ +#define SRC_CSV_H_ + +int parse_file(char const *); + +#endif /* SRC_CSV_H_ */ diff --git a/src/line_file.c b/src/line_file.c new file mode 100644 index 00000000..1742bc6c --- /dev/null +++ b/src/line_file.c @@ -0,0 +1,165 @@ +#include "line_file.h" + +#include +#include +#include +#include + +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; +} diff --git a/src/line_file.h b/src/line_file.h new file mode 100644 index 00000000..15b9208a --- /dev/null +++ b/src/line_file.h @@ -0,0 +1,25 @@ +#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 +#include + +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_ */ diff --git a/src/main.c b/src/main.c index 81998c04..c8e8fb31 100644 --- a/src/main.c +++ b/src/main.c @@ -32,6 +32,12 @@ main(int argc, char *argv[]) } } + /* TODO This will be overriden when reading from config file */ + if (json_file == NULL) { + fprintf(stderr, "Missing flag '-f '\n"); + return -EINVAL; + } + err = config_init(json_file); if (err) return err; diff --git a/test/Makefile.am b/test/Makefile.am index 5fefa073..fdacf089 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -10,7 +10,7 @@ AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src @CHECK_CFLAGS@ # 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 = \ @@ -24,3 +24,7 @@ rtr_pdu_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) diff --git a/test/address_test.c b/test/address_test.c new file mode 100644 index 00000000..bb23a855 --- /dev/null +++ b/test/address_test.c @@ -0,0 +1,73 @@ +#include "address.c" + +#include +#include +#include +#include + +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; +}