# Uhhh... this one starts with "PKG_" so it's probably different.
# No idea.
PKG_CHECK_MODULES([CHECK], [check])
+PKG_CHECK_MODULES([JANSSON], [jansson])
# Spit out the makefiles.
AC_OUTPUT(Makefile src/Makefile man/Makefile test/Makefile)
AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3
-AM_LDFLAGS =
+AM_LDFLAGS =
bin_PROGRAMS = rtr_server
rtr_server_SOURCES += rtr/primitive_reader.h
rtr_server_SOURCES += rtr/rtr.c
rtr_server_SOURCES += rtr/rtr.h
+rtr_server_SOURCES += log.h
+rtr_server_SOURCES += file.c
+rtr_server_SOURCES += file.h
+rtr_server_SOURCES += types.h
+rtr_server_SOURCES += configuration.c
+rtr_server_SOURCES += configuration.h
+rtr_server_SOURCES += str_utils.c
+rtr_server_SOURCES += str_utils.h
+
+rtr_server_LDADD = ${JANSSON_LIBS}
\ No newline at end of file
-#ifndef COMMON_H_
-#define COMMON_H_
+#ifndef _SRC_COMMON_H_
+#define _SRC_COMMON_H_
+
+#include <string.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
#define EUNIMPLEMENTED 566456
-#endif /* COMMON_H_ */
+#define warnxerror0(error, msg) \
+ warnx(msg ": %s", strerror(error))
+#define warnxerrno0(msg) \
+ warnxerror0(errno, msg)
+#define warnxerror(error, msg, ...) \
+ warnx(msg ": %s", ##__VA_ARGS__, strerror(error))
+#define warnxerrno(msg, ...) \
+ warnxerror(errno, msg, ##__VA_ARGS__)
+
+#define pr_debug0(msg) printf("Debug: " msg "\n");
+#define pr_debug(msg, ...) printf("Debug: " msg "\n", ##__VA_ARGS__);
+
+#endif /* _SRC_COMMON_H_ */
--- /dev/null
+#include "configuration.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <regex.h>
+#include <jansson.h>
+
+#include "common.h"
+#include "file.h"
+#include "str_utils.h"
+
+#define OPTNAME_LISTEN "listen"
+#define OPTNAME_LISTEN_IPV4 "ipv4_server_addr"
+
+static int json_to_config(json_t *, struct rtr_config *);
+static int handle_listen_config(json_t *, struct ipv4_transport_addr *);
+static json_t *load_json(const char *);
+
+
+int
+read_config_from_file(char *json_file_path, struct rtr_config **result)
+{
+ int error;
+ int is_json_file;
+ json_t *root_json;
+ struct rtr_config *config;
+ struct file_contents fc;
+
+ is_json_file = endsWith(json_file_path, ".json");
+ if (!is_json_file) {
+ log_err("Invalid Json file extension for file '%s'", json_file_path);
+ return -EINVAL;
+ }
+
+ *result = NULL;
+ error = file_load(json_file_path, &fc);
+ if (error)
+ return error;
+
+ root_json = load_json(fc.buffer);
+ file_free(&fc);
+ if (!root_json)
+ return -ENOENT;
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return -ENOMEM;
+
+ error = json_to_config(root_json, config);
+ if (error != 0)
+ free(config);
+
+ *result = config;
+ json_decref(root_json);
+ return error;
+}
+
+static void
+check_duplicates(bool *found, char *section)
+{
+ if (*found)
+ log_info("Note: I found multiple '%s' sections.", section);
+ *found = true;
+}
+
+static int
+json_to_config(json_t *json, struct rtr_config *config)
+{
+ bool listen_found = false;
+ int error = 0;
+ const char *key;
+ json_t *value;
+
+ if (!json || json->type != JSON_OBJECT) {
+ log_err0("Invalid JSON config.");
+ return -EINVAL;
+ }
+
+ json_object_foreach(json, key, value) {
+ if(strcasecmp(OPTNAME_LISTEN, key) == 0) {
+ check_duplicates(&listen_found, OPTNAME_LISTEN);
+ error = handle_listen_config(value, &config->ipv4_server_addr);
+ }
+ }
+
+ return error;
+}
+
+
+static int
+handle_listen_config(json_t *json, struct ipv4_transport_addr *ipv4_server_addr)
+{
+ bool listen_ipv4_found = false;
+ const char *key;
+ json_t *value;
+
+ if (!json || json->type != JSON_OBJECT) {
+ log_err0("Invalid JSON config.");
+ return -EINVAL;
+ }
+
+ json_object_foreach(json, key, value) {
+ if (strcasecmp(OPTNAME_LISTEN_IPV4, key) == 0) {
+ check_duplicates(&listen_ipv4_found, OPTNAME_LISTEN_IPV4);
+ if (json_typeof(value) != JSON_STRING) {
+ log_err("Invalid value for key '%s'", key);
+ return -EINVAL;
+ }
+
+ str_to_addr4_port(json_string_value(value), ipv4_server_addr);
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Parse text into a JSON object. If text is valid JSON, returns a
+ * json_t structure, otherwise prints and error and returns null.
+ */
+static json_t *load_json(const char *text) {
+ json_t *root;
+ json_error_t error;
+
+ root = json_loads(text, 0, &error);
+
+ if (root)
+ return root;
+ else {
+ log_err("json error on line %d column %d: %s\n", error.line, error.column, error.text);
+ return (json_t *)0;
+ }
+}
+
--- /dev/null
+#ifndef _SRC_CONFIGURATION_H_
+#define _SRC_CONFIGURATION_H_
+
+#include "types.h"
+
+struct rtr_config {
+ /** The listener address of the RTR server. */
+ struct ipv4_transport_addr ipv4_server_addr;
+};
+
+
+int read_config_from_file(char *, struct rtr_config **);
+
+
+#endif /* _SRC_CONFIGURATION_H_ */
--- /dev/null
+#include "file.h"
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "common.h"
+
+/*
+ * Will also rewind the file as a side effect.
+ * This is currently perfect for calling users.
+ */
+static int
+get_file_size(FILE *file, long int *size)
+{
+ if (fseek(file, 0L, SEEK_END) == -1)
+ return errno ? errno : -EINVAL;
+ *size = ftell(file);
+ rewind(file);
+ return 0;
+}
+
+int
+file_load(const char *file_name, struct file_contents *fc)
+{
+ FILE *file;
+ long int file_size;
+ size_t fread_result;
+ int error;
+
+ file = fopen(file_name, "rb");
+ if (file == NULL) {
+ warnxerrno("Could not open file '%s'", file_name);
+ return errno;
+ }
+
+ /* TODO if @file is a directory, this returns a very large integer. */
+ error = get_file_size(file, &file_size);
+ if (error) {
+ warnxerror0(error, "Could not compute file size");
+ fclose(file);
+ return error;
+ }
+
+ fc->buffer_size = file_size;
+ fc->buffer = malloc(fc->buffer_size);
+ if (fc->buffer == NULL) {
+ warnx("Out of memory.");
+ fclose(file);
+ return -ENOMEM;
+ }
+
+ fread_result = fread(fc->buffer, 1, fc->buffer_size, file);
+ if (fread_result < fc->buffer_size) {
+ error = ferror(file);
+ if (error) {
+ /*
+ * The manpage doesn't say that the result is an error
+ * code. It literally doesn't say how to obtain the
+ * error code.
+ */
+ warnx("File read error. The errcode is presumably %d. (%s)",
+ error, strerror(error));
+ free(fc->buffer);
+ fclose(file);
+ return error;
+ }
+
+ /*
+ * As far as I can tell from the man page, feof() cannot return
+ * less bytes that requested like read() does.
+ */
+ warnx("Likely programming error: fread() < file size");
+ warnx("fr:%zu bs:%zu EOF:%d", fread_result, fc->buffer_size,
+ feof(file));
+ free(fc->buffer);
+ fclose(file);
+ return -EINVAL;
+ }
+
+ fclose(file);
+ return 0;
+}
+
+void
+file_free(struct file_contents *fc)
+{
+ free(fc->buffer);
+}
--- /dev/null
+#ifndef SRC_FILE_H_
+#define SRC_FILE_H_
+
+#include <stddef.h>
+
+/*
+ * The entire contents of the file, loaded into a buffer.
+ *
+ * Instances of this struct are expected to live on the stack.
+ */
+struct file_contents {
+ char *buffer;
+ size_t buffer_size;
+};
+
+int file_load(const char *, struct file_contents *);
+void file_free(struct file_contents *);
+
+#endif /* SRC_FILE_H_ */
--- /dev/null
+#ifndef _SRC_LOG_H
+#define _SRC_LOG_H
+
+
+#include <stdio.h>
+#define log_debug(text, ...) printf(text "\n", ##__VA_ARGS__)
+#define log_info(text, ...) log_debug(text, ##__VA_ARGS__)
+#define log_err(text, ...) fprintf(stderr, text "\n", ##__VA_ARGS__)
+#define log_err0(text) fprintf(stderr, text "\n")
+
+
+
+#endif /* _SRC_LOG_H */
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include "rtr/rtr.h"
-
+#include "configuration.h"
/*
* This program is an RTR server.
*
int
main(int argc, char *argv[])
{
+ int err = 0;
+ char *json_file = NULL;
+ struct rtr_config *config;
+ int c;
+ int fflag=0;
+ static char usage[] = "usage: %s -f fname \n";
+
puts("!!!Hello World!!!");
- rtr_listen();
+
+ while ((c = getopt(argc, argv, "f:")) != -1)
+ switch (c) {
+ case 'f':
+ fflag = 1;
+ json_file = optarg;
+ break;
+ case '?':
+ err = 1;
+ break;
+ }
+
+ if (fflag == 0) { /* -f was mandatory */
+ fprintf(stderr, "%s: missing -f option\n", argv[0]);
+ fprintf(stderr, usage, argv[0]);
+ exit(1);
+ } else if (err) {
+ fprintf(stderr, usage, argv[0]);
+ exit(1);
+ }
+
+ err = read_config_from_file(json_file, &config);
+ if (err)
+ return err;
+
+ err = rtr_listen(&config->ipv4_server_addr.l3,
+ config->ipv4_server_addr.l4);
+ if (config)
+ free(config);
+
+ if (err)
+ return err;
+
return EXIT_SUCCESS;
}
#include <unistd.h>
#include "../common.h"
+#include "../types.h"
#include "pdu.h"
/*
* from the clients.
*/
static int
-create_server_socket(void)
+create_server_socket(struct in_addr *server_addr, __u16 port)
{
int fd; /* "file descriptor" */
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
- address.sin_addr.s_addr = INADDR_ANY;
- address.sin_port = htons(5001);
+ address.sin_addr.s_addr = server_addr->s_addr;
+ address.sin_port = htons(port);
if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
err = errno;
warn("Could not bind the address");
* This function blocks.
*/
int
-rtr_listen(void)
+rtr_listen(struct in_addr *server_addr, __u16 port)
{
int server_fd; /* "file descriptor" */
- server_fd = create_server_socket();
+ server_fd = create_server_socket(server_addr, port);
if (server_fd < 0)
return server_fd;
#define RTR_RTR_H_
#include "../common.h"
+#include "../types.h"
__BEGIN_DECLS
-int rtr_listen(void);
+int rtr_listen(struct in_addr *, __u16);
__END_DECLS
#endif /* RTR_RTR_H_ */
--- /dev/null
+#include "str_utils.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <regex.h>
+#include "types.h"
+
+
+#define MAX_PORT 0xFFFF
+
+int
+validate_int(const char *str)
+{
+ regex_t integer_regex;
+ int error;
+
+ if (!str) {
+ log_err0("Programming error: 'str' is NULL.");
+ return -EINVAL;
+ }
+
+ /* It seems this RE implementation doesn't understand '+'. */
+ if (regcomp(&integer_regex, "^[0-9][0-9]*", 0)) {
+ log_err0("Warning: Integer regex didn't compile.");
+ log_err0("(I will be unable to validate integer inputs.)");
+ regfree(&integer_regex);
+ /*
+ * Don't punish the user over our incompetence.
+ * If the number is valid, this will not bother the user.
+ * Otherwise strtoull() will just read a random value, but then
+ * the user is at fault.
+ */
+ return 0;
+ }
+
+ error = regexec(&integer_regex, str, 0, NULL, 0);
+ if (error) {
+ log_err("'%s' is not a number. (error code %d)", str, error);
+ regfree(&integer_regex);
+ return error;
+ }
+
+ regfree(&integer_regex);
+ return 0;
+}
+
+static int
+str_to_ull(const char *str, char **endptr,
+ const unsigned long long int min,
+ const unsigned long long int max,
+ unsigned long long int *result)
+{
+ unsigned long long int parsed;
+ int error;
+
+ error = validate_int(str);
+ if (error)
+ return error;
+
+ errno = 0;
+ parsed = strtoull(str, endptr, 10);
+ if (errno) {
+ log_err("Parsing of '%s' threw error code %d.", str, errno);
+ return errno;
+ }
+
+ if (parsed < min || max < parsed) {
+ log_err("'%s' is out of bounds (%llu-%llu).", str, min, max);
+ return -EINVAL;
+ }
+
+ *result = parsed;
+ return 0;
+}
+
+int
+str_to_bool(const char *str, __u8 *bool_out)
+{
+ if (strcasecmp(str, "true") == 0
+ || strcasecmp(str, "1") == 0
+ || strcasecmp(str, "yes") == 0
+ || strcasecmp(str, "on") == 0) {
+ *bool_out = true;
+ return 0;
+ }
+
+ if (strcasecmp(str, "false") == 0
+ || strcasecmp(str, "0") == 0
+ || strcasecmp(str, "no") == 0
+ || strcasecmp(str, "off") == 0) {
+ *bool_out = false;
+ return 0;
+ }
+
+ log_err("Cannot parse '%s' as a bool (true|false|1|0|yes|no|on|off).",
+ str);
+ return -EINVAL;
+}
+
+int
+str_to_u8(const char *str, __u8 *u8_out, __u8 min, __u8 max)
+{
+ unsigned long long int result;
+ int error;
+
+ error = str_to_ull(str, NULL, min, max, &result);
+
+ *u8_out = result;
+ return error;
+}
+
+int
+str_to_u16(const char *str, __u16 *u16_out, __u16 min, __u16 max)
+{
+ unsigned long long int result;
+ int error;
+
+ error = str_to_ull(str, NULL, min, max, &result);
+
+ *u16_out = result;
+ return error;
+}
+
+int
+str_to_u32(const char *str, __u32 *u32_out, __u32 min, __u32 max)
+{
+ unsigned long long int result;
+ int error;
+
+ error = str_to_ull(str, NULL, min, max, &result);
+
+ *u32_out = result;
+ return error;
+}
+
+int
+str_to_u64(const char *str, __u64 *u64_out, __u64 min, __u64 max)
+{
+ unsigned long long int result;
+ int error;
+
+ error = str_to_ull(str, NULL, min, max, &result);
+
+ *u64_out = result;
+ return error;
+}
+
+#define STR_MAX_LEN 2048
+int
+str_to_u16_array(const char *str, __u16 **array_out, size_t *array_len_out)
+{
+ /* strtok corrupts the string, so we'll be using this copy instead. */
+ char str_copy[STR_MAX_LEN];
+ char *token;
+ __u16 *array;
+ size_t array_len;
+
+ /* Validate str and copy it to the temp buffer. */
+ if (strlen(str) + 1 > STR_MAX_LEN) {
+ log_err("'%s' is too long for this poor, limited parser...", str);
+ return -EINVAL;
+ }
+ strcpy(str_copy, str);
+
+ /* Count the number of ints in the string. */
+ array_len = 0;
+ token = strtok(str_copy, ",");
+ while (token) {
+ array_len++;
+ token = strtok(NULL, ",");
+ }
+
+ if (array_len == 0) {
+ log_err("'%s' seems to be an empty list, which is not supported.", str);
+ return -EINVAL;
+ }
+
+ /* Build the result. */
+ array = malloc(array_len * sizeof(*array));
+ if (!array) {
+ log_err0("Memory allocation failed. Cannot parse the input...");
+ return -ENOMEM;
+ }
+
+ strcpy(str_copy, str);
+
+ array_len = 0;
+ token = strtok(str_copy, ",");
+ while (token) {
+ int error;
+
+ error = str_to_u16(token, &array[array_len], 0, 0xFFFF);
+ if (error) {
+ free(array);
+ return error; /* Error msg already printed. */
+ }
+
+ array_len++;
+ token = strtok(NULL, ",");
+ }
+
+ /* Finish. */
+ *array_out = array;
+ *array_len_out = array_len;
+ return 0;
+}
+
+int
+str_to_addr4(const char *str, struct in_addr *result)
+{
+ if (!inet_pton(AF_INET, str, result)) {
+ log_err("Cannot parse '%s' as an IPv4 address.", str);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int
+str_to_addr6(const char *str, struct in6_addr *result)
+{
+ if (!inet_pton(AF_INET6, str, result)) {
+ log_err("Cannot parse '%s' as an IPv6 address.", str);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#undef STR_MAX_LEN
+#define STR_MAX_LEN (INET_ADDRSTRLEN + 1 + 5) /* [addr + null chara] + # + port */
+int
+str_to_addr4_port(const char *str, struct ipv4_transport_addr *addr)
+{
+ const char *FORMAT = "<IPv4 address>#<port> (eg. 203.0.113.8#80)";
+ /* strtok corrupts the string, so we'll be using this copy instead. */
+ char str_copy[STR_MAX_LEN];
+ char *token;
+ int error;
+
+ if (strlen(str) + 1 > STR_MAX_LEN) {
+ log_err("'%s' is too long for this poor, limited parser...", str);
+ return -EINVAL;
+ }
+ strcpy(str_copy, str);
+
+ token = strtok(str_copy, "#");
+ if (!token) {
+ log_err("Cannot parse '%s' as a %s.", str, FORMAT);
+ return -EINVAL;
+ }
+
+ error = str_to_addr4(token, &addr->l3);
+ if (error)
+ return error;
+
+ token = strtok(NULL, "#");
+ if (!token) {
+ log_err("'%s' does not seem to contain a port (format: %s).", str, FORMAT);
+ return -EINVAL;
+ }
+ return str_to_u16(token, &addr->l4, 0, MAX_PORT); /* Error msg already printed. */
+}
+
+#undef STR_MAX_LEN
+#define STR_MAX_LEN (INET6_ADDRSTRLEN + 1 + 5) /* [addr + null chara] + # + port */
+int
+str_to_addr6_port(const char *str, struct ipv6_transport_addr *addr)
+{
+ const char *FORMAT = "<IPv6 address>#<port> (eg. 2001:db8::1#96)";
+ /* strtok corrupts the string, so we'll be using this copy instead. */
+ char str_copy[STR_MAX_LEN];
+ char *token;
+ int error;
+
+ if (strlen(str) + 1 > STR_MAX_LEN) {
+ log_err("'%s' is too long for this poor, limited parser...", str);
+ return -EINVAL;
+ }
+ strcpy(str_copy, str);
+
+ token = strtok(str_copy, "#");
+ if (!token) {
+ log_err("Cannot parse '%s' as a %s.", str, FORMAT);
+ return -EINVAL;
+ }
+
+ error = str_to_addr6(token, &addr->l3);
+ if (error)
+ return error;
+
+ token = strtok(NULL, "#");
+ if (!token) {
+ log_err("'%s' does not seem to contain a port (format: %s).", str, FORMAT);
+ return -EINVAL;
+ }
+ return str_to_u16(token, &addr->l4, 0, MAX_PORT); /* Error msg already printed. */
+}
+
+bool
+endsWith(char *string, char *suffix)
+{
+ size_t strilen;
+ size_t suflen;
+ if (!string || !suffix)
+ return false;
+
+ strilen = strlen(string);
+ suflen = strlen(suffix);
+
+ return ((strilen >= suflen) && (0 == strcmp(string + strilen - suflen, suffix)));
+}
--- /dev/null
+#ifndef _SRC_STR_UTILS_H
+#define _SRC_STR_UTILS_H
+
+/**
+ * @file
+ * Two-liners (since you need to check the return value) for string-to-something
+ * else conversions.
+ * This is very noisy on the console on purpose because it is only used by the
+ * parser of the userspace app's arguments.
+ */
+
+#include "types.h"
+
+/** Maximum storable value on a __u8. */
+#define MAX_U8 0xFFU
+/** Maximum storable value on a __u16. */
+#define MAX_U16 0xFFFFU
+/** Maximum storable value on a __u32. */
+#define MAX_U32 0xFFFFFFFFU
+/** Maximum storable value on a __u64. */
+#define MAX_U64 0xFFFFFFFFFFFFFFFFU
+
+
+/**
+ * Converts "str" to a IPv4 address. Stores the result in "result".
+ *
+ * Useful mainly in code common to kernelspace and userspace, since their conversion functions
+ * differ, but meant to be used everywhere to strip the parameters from in4_pton() we don't want.
+ */
+int str_to_addr4(const char *, struct in_addr *);
+/**
+ * Converts "str" to a IPv6 address. Stores the result in "result".
+ *
+ * Useful mainly in code common to kernelspace and userspace, since their conversion functions
+ * differ, but meant to be used everywhere to strip the parameters from in6_pton() we don't want.
+ */
+int str_to_addr6(const char *, struct in6_addr *);
+
+/**
+ * Parses @str as a boolean value, which it then copies to @out.
+ */
+int str_to_bool(const char *, __u8 *);
+
+int validate_int(const char *);
+
+/**
+ * Parses @str" as a number, which it then copies to @out.
+ * Refuses to succeed if @out is less than @min or higher than @max.
+ */
+int str_to_u8(const char *, __u8 *, __u8, __u8);
+int str_to_u16(const char *, __u16 *, __u16, __u16);
+int str_to_u32(const char *, __u32 *, __u32, __u32);
+int str_to_u64(const char *, __u64 *, __u64, __u64);
+
+/**
+ * Parses @str as a comma-separated array of __u16s, which it then copies to
+ * @out.
+ * It sets @out_len as @out's length in elements (not bytes).
+ */
+int str_to_u16_array(const char *, __u16 **, size_t *);
+
+/**
+ * Parses @str as a '#' separated l3-address and l4-identifier, which it then
+ * copies to @out".
+ */
+int str_to_addr4_port(const char *, struct ipv4_transport_addr *);
+int str_to_addr6_port(const char *, struct ipv6_transport_addr *);
+
+bool endsWith(char *, char *);
+
+#endif /* _JOOL_COMM_STR_UTILS_H */
--- /dev/null
+#ifndef _SRC_COMMON_TYPES_H
+#define _SRC_COMMON_TYPES_H
+
+/**
+ * @file
+ * The NAT64's core data types. Structures used all over the code.
+ *
+ * Both the kernel module and the userspace application can see this file.
+ */
+
+#include <asm/types.h>
+#include <linux/types.h>
+#include <stdbool.h>
+#include <arpa/inet.h>
+#include "log.h"
+
+
+
+/**
+ * A layer-3 (IPv4) identifier attached to a layer-4 identifier.
+ * Because they're paired all the time in this project.
+ */
+struct ipv4_transport_addr {
+ /** The layer-3 identifier. */
+ struct in_addr l3;
+ /** The layer-4 identifier (Either the TCP/UDP port or the ICMP id). */
+ __u16 l4;
+};
+
+/**
+ * A layer-3 (IPv6) identifier attached to a layer-4 identifier.
+ * Because they're paired all the time in this project.
+ */
+struct ipv6_transport_addr {
+ /** The layer-3 identifier. */
+ struct in6_addr l3;
+ /** The layer-4 identifier (Either the TCP/UDP port or the ICMP id). */
+ __u16 l4;
+};
+
+#endif /* _COMMON_TYPES_H */