summaryrefslogtreecommitdiffstats
path: root/rtrlib
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rtrlib/lib/alloc_utils.c81
-rw-r--r--rtrlib/lib/alloc_utils.h25
-rw-r--r--rtrlib/lib/alloc_utils_private.h34
-rw-r--r--rtrlib/lib/convert_byte_order.c34
-rw-r--r--rtrlib/lib/convert_byte_order_private.h39
-rw-r--r--rtrlib/lib/ip.c79
-rw-r--r--rtrlib/lib/ip.h87
-rw-r--r--rtrlib/lib/ip_private.h58
-rw-r--r--rtrlib/lib/ipv4.c64
-rw-r--r--rtrlib/lib/ipv4.h23
-rw-r--r--rtrlib/lib/ipv4_private.h79
-rw-r--r--rtrlib/lib/ipv6.c220
-rw-r--r--rtrlib/lib/ipv6.h22
-rw-r--r--rtrlib/lib/ipv6_private.h81
-rw-r--r--rtrlib/lib/log.c48
-rw-r--r--rtrlib/lib/log_private.h19
-rw-r--r--rtrlib/lib/utils.c54
-rw-r--r--rtrlib/lib/utils_private.h34
-rw-r--r--rtrlib/pfx/pfx.h160
-rw-r--r--rtrlib/pfx/pfx_private.h60
-rw-r--r--rtrlib/pfx/trie/trie-pfx.c645
-rw-r--r--rtrlib/pfx/trie/trie-pfx.h73
-rw-r--r--rtrlib/pfx/trie/trie.c235
-rw-r--r--rtrlib/pfx/trie/trie_private.h98
-rw-r--r--rtrlib/rtr/packets.c1517
-rw-r--r--rtrlib/rtr/packets_private.h32
-rw-r--r--rtrlib/rtr/rtr.c272
-rw-r--r--rtrlib/rtr/rtr.h161
-rw-r--r--rtrlib/rtr/rtr_private.h88
-rw-r--r--rtrlib/rtr_mgr.c675
-rw-r--r--rtrlib/rtr_mgr.h261
-rw-r--r--rtrlib/rtr_mgr_private.h27
-rw-r--r--rtrlib/rtrlib.h.cmake40
-rw-r--r--rtrlib/rtrlib_export_private.h16
-rw-r--r--rtrlib/spki/hashtable/ht-spkitable.c368
-rw-r--r--rtrlib/spki/hashtable/ht-spkitable_private.h35
-rw-r--r--rtrlib/spki/spkitable.h54
-rw-r--r--rtrlib/spki/spkitable_private.h143
-rw-r--r--rtrlib/transport/ssh/ssh_transport.c392
-rw-r--r--rtrlib/transport/ssh/ssh_transport.h74
-rw-r--r--rtrlib/transport/ssh/ssh_transport_private.h13
-rw-r--r--rtrlib/transport/tcp/tcp_transport.c368
-rw-r--r--rtrlib/transport/tcp/tcp_transport.h59
-rw-r--r--rtrlib/transport/tcp/tcp_transport_private.h23
-rw-r--r--rtrlib/transport/transport.c87
-rw-r--r--rtrlib/transport/transport.h113
-rw-r--r--rtrlib/transport/transport_private.h107
47 files changed, 7277 insertions, 0 deletions
diff --git a/rtrlib/lib/alloc_utils.c b/rtrlib/lib/alloc_utils.c
new file mode 100644
index 0000000..e3d6831
--- /dev/null
+++ b/rtrlib/lib/alloc_utils.c
@@ -0,0 +1,81 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "alloc_utils_private.h"
+
+#include "rtrlib/rtrlib_export_private.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void *(*MALLOC_PTR)(size_t size) = malloc;
+static void *(*REALLOC_PTR)(void *ptr, size_t size) = realloc;
+static void (*FREE_PTR)(void *ptr) = free;
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT void lrtr_set_alloc_functions(void *(*malloc_function)(size_t size),
+ void *(*realloc_function)(void *ptr, size_t size),
+ void(free_function)(void *ptr))
+{
+ MALLOC_PTR = malloc_function;
+ REALLOC_PTR = realloc_function;
+ FREE_PTR = free_function;
+}
+
+inline void *lrtr_malloc(size_t size)
+{
+ return MALLOC_PTR(size);
+}
+
+/* cppcheck-suppress unusedFunction */
+void *lrtr_calloc(size_t nmemb, size_t size)
+{
+ int bytes = 0;
+
+#if __GNUC__ >= 5
+ if (__builtin_mul_overflow(nmemb, size, &bytes)) {
+ errno = ENOMEM;
+ return 0;
+ }
+#else
+ if (size && nmemb > (size_t)-1 / size) {
+ errno = ENOMEM;
+ return 0;
+ }
+ bytes = size * nmemb;
+#endif
+ void *p = lrtr_malloc(bytes);
+
+ if (!p)
+ return p;
+
+ return memset(p, 0, bytes);
+}
+
+inline void lrtr_free(void *ptr)
+{
+ return FREE_PTR(ptr);
+}
+
+inline void *lrtr_realloc(void *ptr, size_t size)
+{
+ return REALLOC_PTR(ptr, size);
+}
+
+char *lrtr_strdup(const char *string)
+{
+ assert(string);
+
+ size_t length = strlen(string) + 1;
+ char *new_string = lrtr_malloc(length);
+
+ return new_string ? memcpy(new_string, string, length) : NULL;
+}
diff --git a/rtrlib/lib/alloc_utils.h b/rtrlib/lib/alloc_utils.h
new file mode 100644
index 0000000..b265915
--- /dev/null
+++ b/rtrlib/lib/alloc_utils.h
@@ -0,0 +1,25 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_ALLOC_UTILS_H
+#define LRTR_ALLOC_UTILS_H
+
+#include <stdlib.h>
+
+/**
+ * @brief Sets custom malloc, realloc and free function
+ * that is used throughout rtrlib.
+ * @param[in] Pointer to malloc function
+ * @param[in] Pointer to realloc function
+ * @param[in] Pointer to free function
+ */
+void lrtr_set_alloc_functions(void *(*malloc_function)(size_t size), void *(*realloc_function)(void *ptr, size_t size),
+ void (*free_function)(void *ptr));
+
+#endif
diff --git a/rtrlib/lib/alloc_utils_private.h b/rtrlib/lib/alloc_utils_private.h
new file mode 100644
index 0000000..b6c67cc
--- /dev/null
+++ b/rtrlib/lib/alloc_utils_private.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_ALLOC_UTILS_PRIVATE_H
+#define LRTR_ALLOC_UTILS_PRIVATE_H
+
+#include "alloc_utils.h"
+
+#include <stdlib.h>
+
+void *lrtr_malloc(size_t size);
+
+void *lrtr_calloc(size_t nmemb, size_t size);
+
+void lrtr_free(void *ptr);
+
+void *lrtr_realloc(void *ptr, size_t size);
+
+/**
+ * @brief Duplicates a string
+ * @pre string != NULL
+ * @param[in] string
+ * @returns Duplicated string
+ * @returns NULL on error
+ */
+char *lrtr_strdup(const char *string);
+
+#endif
diff --git a/rtrlib/lib/convert_byte_order.c b/rtrlib/lib/convert_byte_order.c
new file mode 100644
index 0000000..402c141
--- /dev/null
+++ b/rtrlib/lib/convert_byte_order.c
@@ -0,0 +1,34 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "convert_byte_order_private.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <inttypes.h>
+
+uint16_t lrtr_convert_short(const enum target_byte_order tbo, const uint16_t value)
+{
+ if (tbo == TO_NETWORK_BYTE_ORDER)
+ return htons(value);
+ else if (tbo == TO_HOST_HOST_BYTE_ORDER)
+ return ntohs(value);
+
+ assert(0);
+}
+
+uint32_t lrtr_convert_long(const enum target_byte_order tbo, const uint32_t value)
+{
+ if (tbo == TO_NETWORK_BYTE_ORDER)
+ return htonl(value);
+ else if (tbo == TO_HOST_HOST_BYTE_ORDER)
+ return ntohl(value);
+
+ assert(0);
+}
diff --git a/rtrlib/lib/convert_byte_order_private.h b/rtrlib/lib/convert_byte_order_private.h
new file mode 100644
index 0000000..839f82b
--- /dev/null
+++ b/rtrlib/lib/convert_byte_order_private.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_CONVERT_BYTE_ORDER_PRIVATE_H
+#define LRTR_CONVERT_BYTE_ORDER_PRIVATE_H
+
+#include <inttypes.h>
+
+/**
+ * @brief Target byte order for conversion.
+ */
+enum target_byte_order {
+ TO_NETWORK_BYTE_ORDER,
+ TO_HOST_HOST_BYTE_ORDER,
+};
+
+/**
+ * Converts the passed short value to the given target byte order.
+ * @param[in] tbo Target byte order.
+ * @param[in] value Input (uint16_t) for conversion.
+ * @result Converted uint16_t value.
+ */
+uint16_t lrtr_convert_short(const enum target_byte_order tbo, const uint16_t value);
+
+/**
+ * Converts the passed long value to the given target byte order.
+ * @param[in] tbo Target byte order.
+ * @param[in] value Input (uint32_t) for conversion.
+ * @result Converted uint32_t value.
+ */
+uint32_t lrtr_convert_long(const enum target_byte_order tbo, const uint32_t value);
+
+#endif /* LRTR_CONVERT_BYTE_ORDER_H */
diff --git a/rtrlib/lib/ip.c b/rtrlib/lib/ip.c
new file mode 100644
index 0000000..39df774
--- /dev/null
+++ b/rtrlib/lib/ip.c
@@ -0,0 +1,79 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "ip_private.h"
+
+#include "rtrlib/rtrlib_export_private.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+bool lrtr_ip_addr_is_zero(const struct lrtr_ip_addr prefix)
+{
+ if (prefix.ver == LRTR_IPV6) {
+ if (prefix.u.addr6.addr[0] == 0 && prefix.u.addr6.addr[1] == 0 && prefix.u.addr6.addr[2] == 0 &&
+ prefix.u.addr6.addr[3] == 0) {
+ return true;
+ }
+ } else if (prefix.u.addr4.addr == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+struct lrtr_ip_addr lrtr_ip_addr_get_bits(const struct lrtr_ip_addr *val, const uint8_t from, const uint8_t number)
+{
+ struct lrtr_ip_addr result;
+
+ if (val->ver == LRTR_IPV6) {
+ result.ver = LRTR_IPV6;
+ result.u.addr6 = lrtr_ipv6_get_bits(&(val->u.addr6), from, number);
+ } else {
+ result.ver = LRTR_IPV4;
+ result.u.addr4 = lrtr_ipv4_get_bits(&(val->u.addr4), from, number);
+ }
+ return result;
+}
+
+RTRLIB_EXPORT bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b)
+{
+ if (a.ver != b.ver)
+ return false;
+ if (a.ver == LRTR_IPV6)
+ return lrtr_ipv6_addr_equal(&(a.u.addr6), &(b.u.addr6));
+ return lrtr_ipv4_addr_equal(&(a.u.addr4), &(b.u.addr4));
+}
+
+RTRLIB_EXPORT int lrtr_ip_addr_to_str(const struct lrtr_ip_addr *ip, char *str, const unsigned int len)
+{
+ if (ip->ver == LRTR_IPV6)
+ return lrtr_ipv6_addr_to_str(&(ip->u.addr6), str, len);
+ return lrtr_ipv4_addr_to_str(&(ip->u.addr4), str, len);
+}
+
+RTRLIB_EXPORT int lrtr_ip_str_to_addr(const char *str, struct lrtr_ip_addr *ip)
+{
+ if (!strchr(str, ':')) {
+ ip->ver = LRTR_IPV4;
+ return lrtr_ipv4_str_to_addr(str, &(ip->u.addr4));
+ }
+ ip->ver = LRTR_IPV6;
+ return lrtr_ipv6_str_to_addr(str, &(ip->u.addr6));
+}
+
+// cppcheck-suppress unusedFunction
+RTRLIB_EXPORT bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2)
+{
+ struct lrtr_ip_addr tmp;
+
+ if (lrtr_ip_str_to_addr(addr2, &tmp) == -1)
+ return false;
+ return lrtr_ip_addr_equal(*addr1, tmp);
+}
diff --git a/rtrlib/lib/ip.h b/rtrlib/lib/ip.h
new file mode 100644
index 0000000..df3bb06
--- /dev/null
+++ b/rtrlib/lib/ip.h
@@ -0,0 +1,87 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IP_PUBLIC_H
+#define LRTR_IP_PUBLIC_H
+
+#include "rtrlib/lib/ipv4.h"
+#include "rtrlib/lib/ipv6.h"
+
+#include <stdbool.h>
+/**
+ * @defgroup util_h Utility functions
+ * @{
+ */
+
+/**
+ * @brief Version of the IP protocol.
+ */
+enum lrtr_ip_version {
+ /** IPV4 */
+ LRTR_IPV4,
+
+ /** LRTR_IPV6 */
+ LRTR_IPV6
+};
+
+/**
+ * @brief The lrtr_ip_addr struct stores a IPv4 or IPv6 address in host byte order.
+ * @param ver Specifies the type of the stored address.
+ * @param u Union holding a lrtr_ipv4_addr or lrtr_ipv6_addr.
+ */
+struct lrtr_ip_addr {
+ enum lrtr_ip_version ver;
+ union {
+ struct lrtr_ipv4_addr addr4;
+ struct lrtr_ipv6_addr addr6;
+ } u;
+};
+
+/**
+ * Converts the passed lrtr_ip_addr struct to string representation.
+ * @param[in] ip lrtr_ip_addr
+ * @param[out] str Pointer to a char array.
+ * The array must be at least INET_ADDRSTRLEN bytes long if the passed lrtr_ip_addr stores an IPv4 address.
+ * If lrtr_ip_addr stores an IPv6 address, str must be at least INET6_ADDRSTRLEN bytes long.
+ * @param[in] len Length of the str array.
+ * @result 0 On success.
+ * @result -1 On error.
+ */
+int lrtr_ip_addr_to_str(const struct lrtr_ip_addr *ip, char *str, const unsigned int len);
+
+/**
+ * Converts the passed IP address in string representation to an lrtr_ip_addr.
+ * @param[in] str Pointer to a Null terminated char array.
+ * @param[out] ip Pointer to a lrtr_ip_addr struct.
+ * @result 0 On success.
+ * @result -1 On error.
+ */
+int lrtr_ip_str_to_addr(const char *str, struct lrtr_ip_addr *ip);
+
+/**
+ *
+ * @brief Checks if two lrtr_ip_addr structs are equal.
+ * @param[in] a lrtr_ip_addr
+ * @param[in] b lrtr_ip_addr
+ * @return true If a == b.
+ * @return false If a != b.
+ */
+bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b);
+
+/**
+ * Compares addr1 in the lrtr_ip_addr struct with addr2 in string representation.
+ * @param[in] addr1 lrtr_ip_addr
+ * @param[in] addr2 IP-address as string
+ * @return true If a == b
+ * @return false If a != b
+ */
+bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2);
+
+#endif
+/** @} */
diff --git a/rtrlib/lib/ip_private.h b/rtrlib/lib/ip_private.h
new file mode 100644
index 0000000..623fd96
--- /dev/null
+++ b/rtrlib/lib/ip_private.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IP_PRIVATE_H
+#define LRTR_IP_PRIVATE_H
+
+#include "ip.h"
+
+#include "rtrlib/lib/ipv4_private.h"
+#include "rtrlib/lib/ipv6_private.h"
+
+/**
+ * @brief Detects if the lrtr_ip_addr only contains 0 bits.
+ * @param[in] lrtr_ip_addr
+ * @returns true If the saved lrtr_ip_addr is 0.
+ * @returns false If the saved lrtr_ip_addr isn't 0.
+ */
+bool lrtr_ip_addr_is_zero(const struct lrtr_ip_addr);
+
+/**
+ * @brief Extracts number bits from the passed lrtr_ip_addr, starting at bit number from. The bit with the highest
+ * significance is bit 0. All bits that aren't in the specified range will be 0.
+ * @param[in] val lrtr_ip_addr
+ * @param[in] from Position of the first bit that is extracted.
+ * @param[in] number How many bits will be extracted.
+ * @returns An lrtr_ipv_addr, where all bits that aren't in the specified range are set to 0.
+ */
+struct lrtr_ip_addr lrtr_ip_addr_get_bits(const struct lrtr_ip_addr *val, const uint8_t from, const uint8_t number);
+
+/**
+ * @defgroup util_h Utility functions
+ * @{
+ *
+ * @brief Checks if two lrtr_ip_addr structs are equal.
+ * @param[in] a lrtr_ip_addr
+ * @param[in] b lrtr_ip_addr
+ * @return true If a == b.
+ * @return false If a != b.
+ */
+bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b);
+
+/**
+ * Compares addr1 in the lrtr_ip_addr struct with addr2 in string representation.
+ * @param[in] addr1 lrtr_ip_addr
+ * @param[in] addr2 IP-address as string
+ * @return true If a == b
+ * @return false If a != b
+ */
+bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2);
+
+#endif
+/** @} */
diff --git a/rtrlib/lib/ipv4.c b/rtrlib/lib/ipv4.c
new file mode 100644
index 0000000..d7f10e1
--- /dev/null
+++ b/rtrlib/lib/ipv4.c
@@ -0,0 +1,64 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "ipv4_private.h"
+
+#include "rtrlib/lib/convert_byte_order_private.h"
+#include "rtrlib/lib/utils_private.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+struct lrtr_ipv4_addr lrtr_ipv4_get_bits(const struct lrtr_ipv4_addr *val, const uint8_t from, const uint8_t quantity)
+{
+ struct lrtr_ipv4_addr result;
+
+ result.addr = lrtr_get_bits(val->addr, from, quantity);
+ return result;
+}
+
+int lrtr_ipv4_addr_to_str(const struct lrtr_ipv4_addr *ip, char *str, unsigned int len)
+{
+ uint8_t buff[4];
+
+ buff[0] = ip->addr >> 24 & 0xff;
+ buff[1] = ip->addr >> 16 & 0xff;
+ buff[2] = ip->addr >> 8 & 0xff;
+ buff[3] = ip->addr & 0xff;
+
+ if (snprintf(str, len, "%hhu.%hhu.%hhu.%hhu", buff[0], buff[1], buff[2], buff[3]) < 0)
+ return -1;
+
+ return 0;
+}
+
+int lrtr_ipv4_str_to_addr(const char *str, struct lrtr_ipv4_addr *ip)
+{
+ uint8_t buff[4];
+
+ if (sscanf(str, "%3hhu.%3hhu.%3hhu.%3hhu", &buff[0], &buff[1], &buff[2], &buff[3]) != 4)
+ return -1;
+
+ ip->addr = buff[0] << 24 | buff[1] << 16 | buff[2] << 8 | buff[3];
+
+ return 0;
+}
+
+bool lrtr_ipv4_addr_equal(const struct lrtr_ipv4_addr *a, const struct lrtr_ipv4_addr *b)
+{
+ if (a->addr == b->addr)
+ return true;
+
+ return false;
+}
+
+void lrtr_ipv4_addr_convert_byte_order(const uint32_t src, uint32_t *dest, const enum target_byte_order tbo)
+{
+ *dest = lrtr_convert_long(tbo, src);
+}
diff --git a/rtrlib/lib/ipv4.h b/rtrlib/lib/ipv4.h
new file mode 100644
index 0000000..9aff5aa
--- /dev/null
+++ b/rtrlib/lib/ipv4.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IPV4_H
+#define LRTR_IPV4_H
+
+#include <stdint.h>
+
+/**
+ * @brief Struct storing an IPv4 address in host byte order.
+ * @param addr The IPv4 address.
+ */
+struct lrtr_ipv4_addr {
+ uint32_t addr;
+};
+
+#endif
diff --git a/rtrlib/lib/ipv4_private.h b/rtrlib/lib/ipv4_private.h
new file mode 100644
index 0000000..73d0170
--- /dev/null
+++ b/rtrlib/lib/ipv4_private.h
@@ -0,0 +1,79 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IPV4_PRIVATE_H
+#define LRTR_IPV4_PRIVATE_H
+
+#include "ipv4.h"
+
+#include "rtrlib/lib/convert_byte_order_private.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+/**
+ * @brief Extracts number bits from the passed ipv4_addr,
+ *
+ * Starting at bit number from. The bit with the highest significance is bit 0.
+ * All bits that aren't in the specified range will be 0.
+ *
+ * @param[in] val ipv4_addr
+ * @param[in] from Position of the first bit that is extracted.
+ * @param[in] number How many bits will be extracted.
+ *
+ * @returns An ipv4_addr, with all bits not in the specified range set to 0.
+ */
+struct lrtr_ipv4_addr lrtr_ipv4_get_bits(const struct lrtr_ipv4_addr *val, const uint8_t from, const uint8_t number);
+
+/**
+ * @brief Converts ab IPv4 address from string to ipv4_addr struct.
+ *
+ * @param[in] str Pointer to a string buffer.
+ * @param[out] ip ipv4_addr
+ *
+ * @result 0 on success
+ * @result -1 on error
+ */
+int lrtr_ipv4_str_to_addr(const char *str, struct lrtr_ipv4_addr *ip);
+
+/**
+ * @brief Converts an ipv4_addr struct to its string representation.
+ *
+ * @param[in] ip ipv4_addr
+ * @param[out] str Pointer to a string buffer, of at least INET_ADDRSTRLEN.
+ * @param[in] length of *str
+ *
+ * @result 0 on success
+ * @result -1 on error
+ */
+int lrtr_ipv4_addr_to_str(const struct lrtr_ipv4_addr *ip, char *str, const unsigned int len);
+
+/**
+ * @brief Compares two ipv4_addr structs.
+ *
+ * @param[in] a ipv4_addr
+ * @param[in] b ipv4_addr
+ *
+ * @return true if a == b
+ * @return false if a != b
+ */
+bool lrtr_ipv4_addr_equal(const struct lrtr_ipv4_addr *a, const struct lrtr_ipv4_addr *b);
+
+/**
+ * @ingroup util_h[{
+ * @brief Converts the passed IPv4 address to given byte order.
+ *
+ * @param[in] src IPv4 address in source byte order.
+ * @param[out] dest IPv4 address in target byte order.
+ * @param[in] tbo Target byte order for address conversion.
+ * }
+ */
+void lrtr_ipv4_addr_convert_byte_order(const uint32_t src, uint32_t *dest, const enum target_byte_order tbo);
+
+#endif
diff --git a/rtrlib/lib/ipv6.c b/rtrlib/lib/ipv6.c
new file mode 100644
index 0000000..5034c3c
--- /dev/null
+++ b/rtrlib/lib/ipv6.c
@@ -0,0 +1,220 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "ipv6_private.h"
+
+#include "rtrlib/lib/convert_byte_order_private.h"
+#include "rtrlib/lib/ipv4_private.h"
+#include "rtrlib/lib/utils_private.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+inline bool lrtr_ipv6_addr_equal(const struct lrtr_ipv6_addr *a, const struct lrtr_ipv6_addr *b)
+{
+ if (a->addr[0] == b->addr[0] && a->addr[1] == b->addr[1] && a->addr[2] == b->addr[2] &&
+ a->addr[3] == b->addr[3])
+ return true;
+ return false;
+}
+
+struct lrtr_ipv6_addr lrtr_ipv6_get_bits(const struct lrtr_ipv6_addr *val, const uint8_t first_bit,
+ const uint8_t quantity)
+{
+ assert(first_bit <= 127);
+ assert(quantity <= 128);
+ assert(first_bit + quantity <= 128);
+
+ // if no bytes get extracted the result has to be 0
+ struct lrtr_ipv6_addr result;
+
+ memset(&result, 0, sizeof(result));
+
+ uint8_t bits_left = quantity;
+
+ if (first_bit <= 31) {
+ const uint8_t q = quantity > 32 ? 32 : quantity;
+
+ assert(bits_left >= q);
+ bits_left -= q;
+ result.addr[0] = lrtr_get_bits(val->addr[0], first_bit, q);
+ }
+
+ if ((first_bit <= 63) && ((first_bit + quantity) > 32)) {
+ const uint8_t fr = first_bit < 32 ? 0 : first_bit - 32;
+ const uint8_t q = bits_left > 32 ? 32 : bits_left;
+
+ assert(bits_left >= q);
+ bits_left -= q;
+ result.addr[1] = lrtr_get_bits(val->addr[1], fr, q);
+ }
+
+ if ((first_bit <= 95) && ((first_bit + quantity) > 64)) {
+ const uint8_t fr = first_bit < 64 ? 0 : first_bit - 64;
+ const uint8_t q = bits_left > 32 ? 32 : bits_left;
+
+ assert(bits_left >= q);
+ bits_left -= q;
+ result.addr[2] = lrtr_get_bits(val->addr[2], fr, q);
+ }
+
+ if ((first_bit <= 127) && ((first_bit + quantity) > 96)) {
+ const uint8_t fr = first_bit < 96 ? 0 : first_bit - 127;
+ const uint8_t q = bits_left > 32 ? 32 : bits_left;
+
+ assert(bits_left >= q);
+ result.addr[3] = lrtr_get_bits(val->addr[3], fr, q);
+ }
+ return result;
+}
+
+/*
+ * This function was copied from the bird routing daemon's ip_pton(..) function.
+ */
+int lrtr_ipv6_str_to_addr(const char *a, struct lrtr_ipv6_addr *ip)
+{
+ uint32_t *o = ip->addr;
+ uint16_t words[8];
+ int i, j, k, l, hfil;
+ const char *start;
+
+ if (a[0] == ':') { /* Leading :: */
+ if (a[1] != ':')
+ return -1;
+ a++;
+ }
+ hfil = -1;
+ i = 0;
+ while (*a) {
+ if (*a == ':') { /* :: */
+ if (hfil >= 0)
+ return -1;
+ hfil = i;
+ a++;
+ continue;
+ }
+ j = 0;
+ l = 0;
+ start = a;
+ for (;;) {
+ if (*a >= '0' && *a <= '9')
+ k = *a++ - '0';
+ else if (*a >= 'A' && *a <= 'F')
+ k = *a++ - 'A' + 10;
+ else if (*a >= 'a' && *a <= 'f')
+ k = *a++ - 'a' + 10;
+ else
+ break;
+ j = (j << 4) + k;
+ if (j >= 0x10000 || ++l > 4)
+ return -1;
+ }
+ if (*a == ':' && a[1]) {
+ a++;
+ } else if (*a == '.' && (i == 6 || (i < 6 && hfil >= 0))) { /* Embedded IPv4 address */
+ struct lrtr_ipv4_addr addr4;
+
+ if (lrtr_ipv4_str_to_addr(start, &addr4) == -1)
+ return -1;
+ words[i++] = addr4.addr >> 16;
+ words[i++] = addr4.addr;
+ break;
+ } else if (*a) {
+ return -1;
+ }
+ if (i >= 8)
+ return -1;
+ words[i++] = j;
+ }
+
+ /* Replace :: with an appropriate quantity of zeros */
+ if (hfil >= 0) {
+ j = 8 - i;
+ for (i = 7; i - j >= hfil; i--)
+ words[i] = words[i - j];
+ for (; i >= hfil; i--)
+ words[i] = 0;
+ }
+
+ /* Convert the address to lrtr_ip_addr format */
+ for (i = 0; i < 4; i++)
+ o[i] = (words[2 * i] << 16) | words[2 * i + 1];
+ return 0;
+}
+
+/*
+ * This function was copied from the bird routing daemon's ip_ntop(..) function.
+ */
+int lrtr_ipv6_addr_to_str(const struct lrtr_ipv6_addr *ip_addr, char *b, const unsigned int len)
+{
+ if (len < INET6_ADDRSTRLEN)
+ return -1;
+ const uint32_t *a = ip_addr->addr;
+ uint16_t words[8];
+ int bestpos = 0;
+ int bestlen = 0;
+ int curpos = 0;
+ int curlen = 0;
+ int i;
+
+ /* First of all, preprocess the address and find the longest run of zeros */
+ for (i = 0; i < 8; i++) {
+ uint32_t x = a[i / 2];
+
+ words[i] = ((i % 2) ? x : (x >> 16)) & 0xffff;
+ if (words[i]) {
+ curlen = 0;
+ } else {
+ if (!curlen)
+ curpos = i;
+ curlen++;
+ if (curlen > bestlen) {
+ bestpos = curpos;
+ bestlen = curlen;
+ }
+ }
+ }
+ if (bestlen < 2)
+ bestpos = -1;
+
+ /* Is it an encapsulated IPv4 address? */
+ if (!bestpos && ((bestlen == 5 && a[2] == 0xffff) || bestlen == 6))
+ // if (!bestpos && ((bestlen == 5 && (a[2] == 0xffff)) || bestlen == 6))
+ {
+ uint32_t x = a[3];
+
+ b += sprintf(b, "::%s%d.%d.%d.%d", a[2] ? "ffff:" : "", ((x >> 24) & 0xff), ((x >> 16) & 0xff),
+ ((x >> 8) & 0xff), (x & 0xff));
+ return 0;
+ }
+
+ /* Normal IPv6 formatting, compress the largest sequence of zeros */
+ for (i = 0; i < 8; i++) {
+ if (i == bestpos) {
+ i += bestlen - 1;
+ *b++ = ':';
+ if (i == 7)
+ *b++ = ':';
+ } else {
+ if (i)
+ *b++ = ':';
+ b += sprintf(b, "%x", words[i]);
+ }
+ }
+ *b = '\0';
+ return 0;
+}
+
+void lrtr_ipv6_addr_convert_byte_order(const uint32_t *src, uint32_t *dest, const enum target_byte_order tbo)
+{
+ for (int i = 0; i < 4; i++)
+ dest[i] = lrtr_convert_long(tbo, src[i]);
+}
diff --git a/rtrlib/lib/ipv6.h b/rtrlib/lib/ipv6.h
new file mode 100644
index 0000000..6be7485
--- /dev/null
+++ b/rtrlib/lib/ipv6.h
@@ -0,0 +1,22 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IPV6_PUBLIC_H
+#define LRTR_IPV6_PUBLIC_H
+
+#include <stdint.h>
+
+/**
+ * @brief Struct holding an IPv6 address in host byte order.
+ */
+struct lrtr_ipv6_addr {
+ uint32_t addr[4]; /**< The IPv6 address. */
+};
+
+#endif /* LRTR_IPV6_PUBLIC_H */
diff --git a/rtrlib/lib/ipv6_private.h b/rtrlib/lib/ipv6_private.h
new file mode 100644
index 0000000..1007e73
--- /dev/null
+++ b/rtrlib/lib/ipv6_private.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_IPV6_PRIVATE_H
+#define LRTR_IPV6_PRIVATE_H
+
+#include "ipv6.h"
+
+#include "rtrlib/lib/convert_byte_order_private.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * @brief Compares two lrtr_ipv6_addr structs
+ *
+ * @param[in] a lrtr_ipv6_addr
+ * @param[in] b lrtr_ipv6_addr
+ *
+ * @return true if a == b
+ * @return false if a != b
+ */
+bool lrtr_ipv6_addr_equal(const struct lrtr_ipv6_addr *a, const struct lrtr_ipv6_addr *b);
+
+/**
+ * @brief Extracts quantity bits from an IPv6 address.
+ *
+ * The bit with the highest significance is bit 0. All bits that aren't in the
+ * specified range will be 0.
+ *
+ * @param[in] val ipv6_addr
+ * @param[in] first_bit Position of the first bit that is extracted, inclusive.
+ * @param[in] quantity How many bits will be extracted.
+ *
+ * @returns ipv6_addr, with all bits not in specified range set to 0.
+ */
+struct lrtr_ipv6_addr lrtr_ipv6_get_bits(const struct lrtr_ipv6_addr *val, const uint8_t first_bit,
+ const uint8_t quantity);
+
+/**
+ * @brief Converts the passed ipv6_addr to string representation
+ *
+ * @param[in] ip_addr Pointer to an IPv6 address
+ * @param[out] str Pointer to string buf, at least INET6_ADDRSTRLEN bytes.
+ * @param[in] len Length of string buffer @p str
+ *
+ * @result 0 on success
+ * @result -1 on error
+ */
+int lrtr_ipv6_addr_to_str(const struct lrtr_ipv6_addr *ip, char *str, const unsigned int len);
+
+/**
+ * @brief Converts the passed IPv6 address string in to lrtr_ipv6_addr struct.
+ *
+ * @param[in] str Pointer to a string buffer
+ * @param[out] ip Pointer to lrtr_ipv6_addr
+ *
+ * @result 0 on success
+ * @result -1 on error
+ */
+int lrtr_ipv6_str_to_addr(const char *str, struct lrtr_ipv6_addr *ip);
+
+/**
+ * @ingroup util_h
+ * @{
+ * @brief Converts the passed IPv6 address to given byte order.
+ *
+ * @param[in] src IPv6 address (uint32_t array) in source byte order.
+ * @param[out] dest IPv6 address (uint32_t array) in target byte order.
+ * @param[in] tbo Target byte order for address conversion.
+ */
+void lrtr_ipv6_addr_convert_byte_order(const uint32_t *src, uint32_t *dest, const enum target_byte_order tbo);
+/** @} */
+#endif /* LRTR_IPV6_H */
diff --git a/rtrlib/lib/log.c b/rtrlib/lib/log.c
new file mode 100644
index 0000000..6ceaaa3
--- /dev/null
+++ b/rtrlib/lib/log.c
@@ -0,0 +1,48 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "log_private.h"
+
+#include <arpa/inet.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+
+void lrtr_dbg(const char *frmt, ...)
+{
+#ifndef NDEBUG
+ va_list argptr;
+ struct timeval tv;
+ struct timezone tz;
+
+ va_start(argptr, frmt);
+
+ bool fail = true;
+
+ if (gettimeofday(&tv, &tz) == 0) {
+ struct tm tm;
+
+ if (localtime_r(&tv.tv_sec, &tm)) {
+ fprintf(stderr, "(%04d/%02d/%02d %02d:%02d:%02d:%06ld): ", tm.tm_year + 1900, tm.tm_mon + 1,
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec);
+ fail = false;
+ }
+ }
+
+ if (fail)
+ fprintf(stderr, "(%jd): ", (intmax_t)time(0));
+
+ vfprintf(stderr, frmt, argptr);
+ fprintf(stderr, "\n");
+ va_end(argptr);
+#endif
+}
diff --git a/rtrlib/lib/log_private.h b/rtrlib/lib/log_private.h
new file mode 100644
index 0000000..e33ebf3
--- /dev/null
+++ b/rtrlib/lib/log_private.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_LOG_PRIVATE_H
+#define LRTR_LOG_PRIVATE_H
+
+/**
+ * @brief Writes a message to stdout if NDEBUG isn't defined.
+ * @param[in] frmt log message in printf format style.
+ */
+void lrtr_dbg(const char *frmt, ...) __attribute__((format(printf, 1, 2)));
+
+#endif
diff --git a/rtrlib/lib/utils.c b/rtrlib/lib/utils.c
new file mode 100644
index 0000000..ca708e9
--- /dev/null
+++ b/rtrlib/lib/utils.c
@@ -0,0 +1,54 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "utils_private.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <time.h>
+
+#ifdef __MACH__
+#include <mach/mach_time.h>
+static double timeconvert = 0.0;
+#endif
+
+int lrtr_get_monotonic_time(time_t *seconds)
+{
+#ifdef __MACH__
+ if (timeconvert == 0.0) {
+ mach_timebase_info_data_t time_base;
+ (void)mach_timebase_info(&time_base);
+ timeconvert = (double)time_base.numer / (double)time_base.denom / 1000000000.0;
+ }
+ *seconds = (time_t)mach_absolute_time() * timeconvert;
+#else
+ struct timespec time;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &time) == -1)
+ return -1;
+ *seconds = time.tv_sec;
+ if ((time.tv_nsec * 1000000000) >= 5)
+ *seconds += 1;
+#endif
+ return 0;
+}
+
+uint32_t lrtr_get_bits(const uint32_t val, const uint8_t from, const uint8_t number)
+{
+ assert(number < 33);
+ assert(number > 0);
+
+ uint32_t mask = ~0;
+
+ if (number != 32)
+ mask = ~(mask >> number);
+
+ mask >>= from;
+ return (mask & val);
+}
diff --git a/rtrlib/lib/utils_private.h b/rtrlib/lib/utils_private.h
new file mode 100644
index 0000000..40e99ee
--- /dev/null
+++ b/rtrlib/lib/utils_private.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef LRTR_UTILS_PRIVATE_H
+#define LRTR_UTILS_PRIVATE_H
+
+#include <stdint.h>
+#include <time.h>
+
+/**
+ * @brief Returns the current time of the CLOCK_MONOTONIC clock.
+ * @param[in] seconds Time in seconds since some unspecified starting point.
+ * @return 0 on successs
+ * @return -1 on error
+ */
+int lrtr_get_monotonic_time(time_t *seconds);
+
+/**
+ * @brief Extracts number bits from the passed uint32_t, starting at bit number from. The bit with the highest
+ * significance is bit 0. All bits that aren't in the specified range will be 0.
+ * @param[in] val uint32_t
+ * @param[in] from Position of the first bit that is extracted.
+ * @param[in] number How many bits will be extracted.
+ * @returns a uint32_t, where all bits that aren't in the specified range are set to 0.
+ */
+uint32_t lrtr_get_bits(const uint32_t val, const uint8_t from, const uint8_t number);
+
+#endif
diff --git a/rtrlib/pfx/pfx.h b/rtrlib/pfx/pfx.h
new file mode 100644
index 0000000..712f416
--- /dev/null
+++ b/rtrlib/pfx/pfx.h
@@ -0,0 +1,160 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_pfx_h Prefix validation table
+ * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data
+ * received from an RPKI-RTR cache server.
+ *
+ * @{
+ */
+
+#ifndef RTR_PFX_H
+#define RTR_PFX_H
+
+#include "rtrlib/lib/ip.h"
+#include "rtrlib/pfx/trie/trie-pfx.h"
+
+#include <inttypes.h>
+
+/**
+ * @brief Possible return values for pfx_ functions.
+ */
+enum pfx_rtvals {
+ /** Operation was successful. */
+ PFX_SUCCESS = 0,
+
+ /** Error occurred. */
+ PFX_ERROR = -1,
+
+ /** The supplied pfx_record already exists in the pfx_table. */
+ PFX_DUPLICATE_RECORD = -2,
+
+ /** pfx_record wasn't found in the pfx_table. */
+ PFX_RECORD_NOT_FOUND = -3
+};
+
+/**
+ * @brief Validation states returned from pfx_validate_origin.
+ */
+enum pfxv_state {
+ /** A valid certificate for the pfx_record exists. */
+ BGP_PFXV_STATE_VALID,
+
+ /** @brief No certificate for the route exists. */
+ BGP_PFXV_STATE_NOT_FOUND,
+
+ /** @brief One or more records that match the input prefix exists in the pfx_table
+ * but the prefix max_len or ASN doesn't match.
+ */
+ BGP_PFXV_STATE_INVALID
+};
+
+/**
+ * @brief A function pointer that is called for each record in the pfx_table.
+ * @param pfx_record
+ * @param data forwarded data which the user has passed to pfx_table_for_each_ipv4_record() or
+ * pfx_table_for_each_ipv6_record()
+ */
+typedef void (*pfx_for_each_fp)(const struct pfx_record *pfx_record, void *data);
+
+/**
+ * @brief Initializes the pfx_table struct.
+ * @param[in] pfx_table pfx_table that will be initialized.
+ * @param[in] update_fp A function pointer that will be called if a record was added or removed.
+ */
+void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp);
+
+/**
+ * @brief Frees all memory associated with the pfx_table.
+ * @param[in] pfx_table pfx_table that will be freed.
+ */
+void pfx_table_free(struct pfx_table *pfx_table);
+
+/**
+ * @brief Adds a pfx_record to a pfx_table.
+ * @param[in] pfx_table pfx_table to use.
+ * @param[in] pfx_record pfx_record that will be added.
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR On error.
+ * @return PFX_DUPLICATE_RECORD If the pfx_record already exists.
+ */
+int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *pfx_record);
+
+/**
+ * @brief Removes a pfx_record from a pfx_table.
+ * @param[in] pfx_table pfx_table to use.
+ * @param[in] pfx_record Record that will be removed.
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR On error.
+ * @return PFX_RECORD_NOT_FOUND If pfx_records couldn't be found.
+ */
+int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *pfx_record);
+
+/**
+ * @brief Removes all entries in the pfx_table that match the passed socket_id value from a pfx_table.
+ * @param[in] pfx_table pfx_table to use.
+ * @param[in] socket origin socket of the record
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR On error.
+ */
+int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket);
+
+/**
+ * @brief Validates the origin of a BGP-Route.
+ * @param[in] pfx_table pfx_table to use.
+ * @param[in] asn Autonomous system number of the Origin-AS of the route.
+ * @param[in] prefix Announced network Prefix.
+ * @param[in] mask_len Length of the network mask of the announced prefix.
+ * @param[out] result Result of the validation.
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR On error.
+ */
+int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix,
+ const uint8_t mask_len, enum pfxv_state *result);
+
+/**
+ * @brief Validates the origin of a BGP-Route and returns a list of pfx_record that decided the result.
+ * @param[in] pfx_table pfx_table to use.
+ * @param[out] reason Pointer to a memory area that will be used as array of pfx_records.
+ * The memory area will be overwritten. Reason must point to NULL or an allocated memory area.
+ * @param[out] reason_len Size of the array reason.
+ * @param[in] asn Autonomous system number of the Origin-AS of the route.
+ * @param[in] prefix Announced network Prefix
+ * @param[in] mask_len Length of the network mask of the announced prefix
+ * @param[out] result Result of the validation.
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR On error.
+ */
+int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason, unsigned int *reason_len,
+ const uint32_t asn, const struct lrtr_ip_addr *prefix, const uint8_t mask_len,
+ enum pfxv_state *result);
+
+/**
+ * @brief Iterates over all IPv4 records in the pfx_table.
+ * @details For every pfx_record the function fp is called. The pfx_record and
+ * the data pointer is passed to the fp.
+ * @param[in] pfx_table
+ * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp.
+ * @param[in] data This parameter is forwarded to the callback function.
+ */
+void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data);
+
+/**
+ * @brief Iterates over all IPv6 records in the pfx_table.
+ * @details For every pfx_record the function fp is called. The pfx_record and
+ * the data pointer is passed to the fp.
+ * @param[in] pfx_table
+ * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp.
+ * @param[in] data This parameter is forwarded to the callback function.
+ */
+void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data);
+
+#endif
+/** @} */
diff --git a/rtrlib/pfx/pfx_private.h b/rtrlib/pfx/pfx_private.h
new file mode 100644
index 0000000..6ec1d8a
--- /dev/null
+++ b/rtrlib/pfx/pfx_private.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_pfx_h Prefix validation table
+ * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data
+ * received from an RPKI-RTR cache server.
+ *
+ * @{
+ */
+
+#ifndef RTR_PFX_PRIVATE_H
+#define RTR_PFX_PRIVATE_H
+
+#include "pfx.h"
+
+#include "rtrlib/lib/ip_private.h"
+
+#include <stdint.h>
+
+/**
+ * @brief Frees all memory associated with the pfx_table without calling the update callback.
+ * @param[in] pfx_table pfx_table that will be freed.
+ */
+void pfx_table_free_without_notify(struct pfx_table *pfx_table);
+
+/**
+ * @brief Swap root nodes of the argument tables
+ * @param[in,out] a First table
+ * @param[in,out] b second table
+ */
+void pfx_table_swap(struct pfx_table *a, struct pfx_table *b);
+
+/**
+ * @brief Copy content of @p src_table into @p dst_table
+ * @details dst must be empty and initialized
+ * @param[in] src_table Source table
+ * @param[out] dst_table Destination table
+ * @param[in] socket socket which prefixes should not be copied
+ */
+int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table,
+ const struct rtr_socket *socket);
+
+/**
+ * @brief Notify client about changes between to pfx tables regarding one specific socket
+ * @details old_table will be modified it should be freed after calling this function
+ * @param[in] new_table
+ * @param[in] old_table
+ * @param[in] socket socket which prefixes should be diffed
+ */
+void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket);
+
+#endif
+/** @} */
diff --git a/rtrlib/pfx/trie/trie-pfx.c b/rtrlib/pfx/trie/trie-pfx.c
new file mode 100644
index 0000000..ffb2416
--- /dev/null
+++ b/rtrlib/pfx/trie/trie-pfx.c
@@ -0,0 +1,645 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "trie-pfx.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/ip_private.h"
+#include "rtrlib/pfx/pfx_private.h"
+#include "rtrlib/pfx/trie/trie_private.h"
+#include "rtrlib/rtrlib_export_private.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+struct data_elem {
+ uint32_t asn;
+ uint8_t max_len;
+ const struct rtr_socket *socket;
+};
+
+struct node_data {
+ unsigned int len;
+ struct data_elem *ary;
+};
+
+struct copy_cb_args {
+ struct pfx_table *pfx_table;
+ const struct rtr_socket *socket;
+ bool error;
+};
+
+struct notify_diff_cb_args {
+ struct pfx_table *old_table;
+ struct pfx_table *new_table;
+ const struct rtr_socket *socket;
+ pfx_update_fp pfx_update_fp;
+ bool added;
+};
+
+static struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver);
+static int pfx_table_del_elem(struct node_data *data, const unsigned int index);
+static int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record);
+static int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record);
+static struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record,
+ unsigned int *index);
+static bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len);
+static void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added);
+static int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node,
+ const struct rtr_socket *socket, const unsigned int level);
+static int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record records[], const unsigned int ary_len);
+static void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len);
+
+void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added)
+{
+ if (pfx_table->update_fp)
+ pfx_table->update_fp(pfx_table, *record, added);
+}
+
+RTRLIB_EXPORT void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp)
+{
+ pfx_table->ipv4 = NULL;
+ pfx_table->ipv6 = NULL;
+ pfx_table->update_fp = update_fp;
+ pthread_rwlock_init(&(pfx_table->lock), NULL);
+}
+
+void pfx_table_free_without_notify(struct pfx_table *pfx_table)
+{
+ pfx_table->update_fp = NULL;
+ pfx_table_free(pfx_table);
+}
+
+RTRLIB_EXPORT void pfx_table_free(struct pfx_table *pfx_table)
+{
+ for (int i = 0; i < 2; i++) {
+ struct trie_node *root = (i == 0 ? pfx_table->ipv4 : pfx_table->ipv6);
+
+ if (root) {
+ struct trie_node *rm_node;
+
+ pthread_rwlock_wrlock(&(pfx_table->lock));
+ do {
+ struct node_data *data = (struct node_data *)(root->data);
+
+ for (unsigned int j = 0; j < data->len; j++) {
+ struct pfx_record record = {data->ary[j].asn, (root->prefix), root->len,
+ data->ary[j].max_len, data->ary[j].socket};
+ pfx_table_notify_clients(pfx_table, &record, false);
+ }
+ rm_node = (trie_remove(root, &(root->prefix), root->len, 0));
+ assert(rm_node);
+ lrtr_free(((struct node_data *)rm_node->data)->ary);
+ lrtr_free(rm_node->data);
+ lrtr_free(rm_node);
+ } while (rm_node != root);
+ if (i == 0)
+ pfx_table->ipv4 = NULL;
+ else
+ pfx_table->ipv6 = NULL;
+
+ pthread_rwlock_unlock(&(pfx_table->lock));
+ }
+ }
+ pthread_rwlock_destroy(&(pfx_table->lock));
+}
+
+int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record)
+{
+ struct data_elem *tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * ((data->len) + 1));
+
+ if (!tmp)
+ return PFX_ERROR;
+ data->len++;
+ data->ary = tmp;
+ data->ary[data->len - 1].asn = record->asn;
+ data->ary[data->len - 1].max_len = record->max_len;
+ data->ary[data->len - 1].socket = record->socket;
+ return PFX_SUCCESS;
+}
+
+int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record)
+{
+ int err;
+
+ *node = lrtr_malloc(sizeof(struct trie_node));
+ if (!*node)
+ return PFX_ERROR;
+
+ (*node)->prefix = record->prefix;
+ (*node)->len = record->min_len;
+ (*node)->lchild = NULL;
+ (*node)->rchild = NULL;
+ (*node)->parent = NULL;
+
+ (*node)->data = lrtr_malloc(sizeof(struct node_data));
+ if (!(*node)->data) {
+ err = PFX_ERROR;
+ goto free_node;
+ }
+
+ ((struct node_data *)(*node)->data)->len = 0;
+ ((struct node_data *)(*node)->data)->ary = NULL;
+
+ err = pfx_table_append_elem(((struct node_data *)(*node)->data), record);
+ if (err)
+ goto free_node_data;
+
+ return PFX_SUCCESS;
+
+free_node_data:
+ lrtr_free((*node)->data);
+free_node:
+ lrtr_free(*node);
+
+ return err;
+}
+
+struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record,
+ unsigned int *index)
+{
+ for (unsigned int i = 0; i < data->len; i++) {
+ if (data->ary[i].asn == record->asn && data->ary[i].max_len == record->max_len &&
+ data->ary[i].socket == record->socket) {
+ if (index)
+ *index = i;
+ return &(data->ary[i]);
+ }
+ }
+ return NULL;
+}
+
+// returns pfx_table->ipv4 if record version is LRTR_IPV4 else pfx_table->ipv6
+inline struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver)
+{
+ return ver == LRTR_IPV4 ? pfx_table->ipv4 : pfx_table->ipv6;
+}
+
+int pfx_table_del_elem(struct node_data *data, const unsigned int index)
+{
+ struct data_elem *tmp;
+ struct data_elem deleted_elem = data->ary[index];
+
+ // if index is not the last elem in the list, move all other elems backwards in the array
+ if (index != data->len - 1) {
+ for (unsigned int i = index; i < data->len - 1; i++)
+ data->ary[i] = data->ary[i + 1];
+ }
+
+ data->len--;
+ if (!data->len) {
+ lrtr_free(data->ary);
+ data->ary = NULL;
+ return PFX_SUCCESS;
+ }
+
+ tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * data->len);
+ if (!tmp) {
+ data->ary[data->len] = deleted_elem;
+ data->len++;
+ return PFX_ERROR;
+ }
+
+ data->ary = tmp;
+
+ return PFX_SUCCESS;
+}
+
+RTRLIB_EXPORT int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *record)
+{
+ pthread_rwlock_wrlock(&(pfx_table->lock));
+
+ struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver);
+ unsigned int lvl = 0;
+
+ if (root) {
+ bool found;
+ struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found);
+
+ if (found) { // node with prefix exists
+ if (pfx_table_find_elem(node->data, record, NULL)) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_DUPLICATE_RECORD;
+ }
+ // append record to note_data array
+ int rtval = pfx_table_append_elem(node->data, record);
+
+ pthread_rwlock_unlock(&pfx_table->lock);
+ if (rtval == PFX_SUCCESS)
+ pfx_table_notify_clients(pfx_table, record, true);
+ return rtval;
+ }
+
+ // no node with same prefix and prefix_len found
+ struct trie_node *new_node = NULL;
+
+ if (pfx_table_create_node(&new_node, record) == PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_ERROR;
+ }
+ trie_insert(node, new_node, lvl);
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_notify_clients(pfx_table, record, true);
+ return PFX_SUCCESS;
+ }
+
+ // tree is empty, record will be the root_node
+ struct trie_node *new_node = NULL;
+
+ if (pfx_table_create_node(&new_node, record) == PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_ERROR;
+ }
+ if (record->prefix.ver == LRTR_IPV4)
+ pfx_table->ipv4 = new_node;
+ else
+ pfx_table->ipv6 = new_node;
+
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_notify_clients(pfx_table, record, true);
+ return PFX_SUCCESS;
+}
+
+RTRLIB_EXPORT int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *record)
+{
+ pthread_rwlock_wrlock(&(pfx_table->lock));
+ struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver);
+
+ unsigned int lvl = 0; // tree depth were node was found
+ bool found;
+ struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found);
+
+ if (!found) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_RECORD_NOT_FOUND;
+ }
+
+ unsigned int index;
+ struct data_elem *elem = pfx_table_find_elem(node->data, record, &index);
+
+ if (!elem) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_RECORD_NOT_FOUND;
+ }
+
+ struct node_data *ndata = (struct node_data *)node->data;
+
+ if (pfx_table_del_elem(ndata, index) == PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_ERROR;
+ }
+
+ if (ndata->len == 0) {
+ node = trie_remove(node, &(record->prefix), record->min_len, lvl);
+ assert(node);
+
+ if (node == root) {
+ if (record->prefix.ver == LRTR_IPV4)
+ pfx_table->ipv4 = NULL;
+ else
+ pfx_table->ipv6 = NULL;
+ }
+ assert(((struct node_data *)node->data)->len == 0);
+ lrtr_free(node->data);
+ lrtr_free(node);
+ }
+ pthread_rwlock_unlock(&pfx_table->lock);
+
+ pfx_table_notify_clients(pfx_table, record, false);
+
+ return PFX_SUCCESS;
+}
+
+bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len)
+{
+ for (unsigned int i = 0; i < data->len; i++) {
+ if (data->ary[i].asn != 0 && data->ary[i].asn == asn && prefix_len <= data->ary[i].max_len)
+ return true;
+ }
+ return false;
+}
+
+int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record *records, const unsigned int ary_len)
+{
+ struct node_data *data = node->data;
+
+ if (ary_len < data->len)
+ return PFX_ERROR;
+
+ for (unsigned int i = 0; i < data->len; i++) {
+ records[i].asn = data->ary[i].asn;
+ records[i].prefix = node->prefix;
+ records[i].min_len = node->len;
+ records[i].max_len = data->ary[i].max_len;
+ records[i].socket = data->ary[i].socket;
+ }
+ return data->len;
+}
+
+inline void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len)
+{
+ if (reason) {
+ lrtr_free(*reason);
+ *reason = NULL;
+ }
+ if (reason_len)
+ *reason_len = 0;
+}
+
+RTRLIB_EXPORT int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason,
+ unsigned int *reason_len, const uint32_t asn, const struct lrtr_ip_addr *prefix,
+ const uint8_t prefix_len, enum pfxv_state *result)
+{
+ // assert(reason_len == NULL || *reason_len == 0);
+ // assert(reason == NULL || *reason == NULL);
+
+ pthread_rwlock_rdlock(&(pfx_table->lock));
+ struct trie_node *root = pfx_table_get_root(pfx_table, prefix->ver);
+
+ if (!root) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ *result = BGP_PFXV_STATE_NOT_FOUND;
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_SUCCESS;
+ }
+
+ unsigned int lvl = 0;
+ struct trie_node *node = trie_lookup(root, prefix, prefix_len, &lvl);
+
+ if (!node) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ *result = BGP_PFXV_STATE_NOT_FOUND;
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_SUCCESS;
+ }
+
+ if (reason_len && reason) {
+ *reason_len = ((struct node_data *)node->data)->len;
+ *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record));
+ if (!*reason) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_ERROR;
+ }
+ if (pfx_table_node2pfx_record(node, *reason, *reason_len) == PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_ERROR;
+ }
+ }
+
+ while (!pfx_table_elem_matches(node->data, asn, prefix_len)) {
+ if (lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits(
+ prefix, lvl++,
+ 1))) //post-incr lvl, trie_lookup is performed on child_nodes => parent lvl + 1
+ node = trie_lookup(node->lchild, prefix, prefix_len, &lvl);
+ else
+ node = trie_lookup(node->rchild, prefix, prefix_len, &lvl);
+
+ if (!node) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ *result = BGP_PFXV_STATE_INVALID;
+ return PFX_SUCCESS;
+ }
+
+ if (reason_len && reason) {
+ unsigned int r_len_old = *reason_len;
+ *reason_len += ((struct node_data *)node->data)->len;
+ *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record));
+ struct pfx_record *start = *reason + r_len_old;
+
+ if (!*reason) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_ERROR;
+ }
+ if (pfx_table_node2pfx_record(node, start, ((struct node_data *)node->data)->len) ==
+ PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ pfx_table_free_reason(reason, reason_len);
+ return PFX_ERROR;
+ }
+ }
+ }
+
+ pthread_rwlock_unlock(&pfx_table->lock);
+ *result = BGP_PFXV_STATE_VALID;
+ return PFX_SUCCESS;
+}
+
+RTRLIB_EXPORT int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix,
+ const uint8_t prefix_len, enum pfxv_state *result)
+{
+ return pfx_table_validate_r(pfx_table, NULL, NULL, asn, prefix, prefix_len, result);
+}
+
+RTRLIB_EXPORT int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket)
+{
+ for (unsigned int i = 0; i < 2; i++) {
+ struct trie_node **root = (i == 0 ? &(pfx_table->ipv4) : &(pfx_table->ipv6));
+
+ pthread_rwlock_wrlock(&(pfx_table->lock));
+ if (*root) {
+ int rtval = pfx_table_remove_id(pfx_table, root, *root, socket, 0);
+
+ if (rtval == PFX_ERROR) {
+ pthread_rwlock_unlock(&pfx_table->lock);
+ return PFX_ERROR;
+ }
+ }
+ pthread_rwlock_unlock(&pfx_table->lock);
+ }
+ return PFX_SUCCESS;
+}
+
+int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node,
+ const struct rtr_socket *socket, const unsigned int level)
+{
+ assert(node);
+ assert(root);
+ assert(*root);
+ bool check_node = true;
+
+ while (check_node) {
+ // data from removed node are replaced from data from child nodes (if children exists),
+ // same node must be checked again if it was replaced with previous child node data
+ struct node_data *data = node->data;
+
+ for (unsigned int i = 0; i < data->len; i++) {
+ while (data->len > i && data->ary[i].socket == socket) {
+ struct pfx_record record = {data->ary[i].asn, node->prefix, node->len,
+ data->ary[i].max_len, data->ary[i].socket};
+ if (pfx_table_del_elem(data, i) == PFX_ERROR)
+ return PFX_ERROR;
+ pfx_table_notify_clients(pfx_table, &record, false);
+ }
+ }
+ if (data->len == 0) {
+ struct trie_node *rm_node = trie_remove(node, &(node->prefix), node->len, level);
+
+ assert(rm_node);
+ assert(((struct node_data *)rm_node->data)->len == 0);
+ lrtr_free(((struct node_data *)rm_node->data));
+ lrtr_free(rm_node);
+
+ if (rm_node == *root) {
+ *root = NULL;
+ return PFX_SUCCESS;
+ } else if (rm_node == node) {
+ return PFX_SUCCESS;
+ }
+ } else {
+ check_node = false;
+ }
+ }
+
+ if (node->lchild) {
+ if (pfx_table_remove_id(pfx_table, root, node->lchild, socket, level + 1) == PFX_ERROR)
+ return PFX_ERROR;
+ }
+ if (node->rchild)
+ return pfx_table_remove_id(pfx_table, root, node->rchild, socket, level + 1);
+ return PFX_SUCCESS;
+}
+
+static void pfx_table_for_each_rec(struct trie_node *n, pfx_for_each_fp fp, void *data)
+{
+ struct pfx_record pfxr;
+ struct node_data *nd;
+
+ assert(n);
+ assert(fp);
+
+ nd = (struct node_data *)n->data;
+ assert(nd);
+
+ if (n->lchild)
+ pfx_table_for_each_rec(n->lchild, fp, data);
+
+ for (unsigned int i = 0; i < nd->len; i++) {
+ pfxr.asn = nd->ary[i].asn;
+ pfxr.prefix = n->prefix;
+ pfxr.min_len = n->len;
+ pfxr.max_len = nd->ary[i].max_len;
+ pfxr.socket = nd->ary[i].socket;
+ fp(&pfxr, data);
+ }
+
+ if (n->rchild)
+ pfx_table_for_each_rec(n->rchild, fp, data);
+}
+
+RTRLIB_EXPORT void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data)
+{
+ assert(pfx_table);
+
+ if (!pfx_table->ipv4)
+ return;
+
+ pthread_rwlock_rdlock(&(pfx_table->lock));
+ pfx_table_for_each_rec(pfx_table->ipv4, fp, data);
+ pthread_rwlock_unlock(&pfx_table->lock);
+}
+
+RTRLIB_EXPORT void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data)
+{
+ assert(pfx_table);
+
+ if (!pfx_table->ipv6)
+ return;
+
+ pthread_rwlock_rdlock(&(pfx_table->lock));
+ pfx_table_for_each_rec(pfx_table->ipv6, fp, data);
+ pthread_rwlock_unlock(&pfx_table->lock);
+}
+
+static void pfx_table_copy_cb(const struct pfx_record *record, void *data)
+{
+ struct copy_cb_args *args = data;
+
+ if (record->socket != args->socket) {
+ if (pfx_table_add(args->pfx_table, record) != PFX_SUCCESS)
+ args->error = true;
+ }
+}
+
+int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table,
+ const struct rtr_socket *socket)
+{
+ struct copy_cb_args args = {dst_table, socket, false};
+
+ pfx_table_for_each_ipv4_record(src_table, pfx_table_copy_cb, &args);
+ if (args.error)
+ return PFX_ERROR;
+
+ pfx_table_for_each_ipv6_record(src_table, pfx_table_copy_cb, &args);
+ if (args.error)
+ return PFX_ERROR;
+
+ return PFX_SUCCESS;
+}
+
+void pfx_table_swap(struct pfx_table *a, struct pfx_table *b)
+{
+ struct trie_node *ipv4_tmp;
+ struct trie_node *ipv6_tmp;
+
+ pthread_rwlock_wrlock(&(a->lock));
+ pthread_rwlock_wrlock(&(b->lock));
+
+ ipv4_tmp = a->ipv4;
+ ipv6_tmp = a->ipv6;
+
+ a->ipv4 = b->ipv4;
+ a->ipv6 = b->ipv6;
+
+ b->ipv4 = ipv4_tmp;
+ b->ipv6 = ipv6_tmp;
+
+ pthread_rwlock_unlock(&(b->lock));
+ pthread_rwlock_unlock(&(a->lock));
+}
+
+static void pfx_table_notify_diff_cb(const struct pfx_record *record, void *data)
+{
+ struct notify_diff_cb_args *args = data;
+
+ if (args->socket == record->socket && args->added) {
+ if (pfx_table_remove(args->old_table, record) != PFX_SUCCESS)
+ pfx_table_notify_clients(args->new_table, record, args->added);
+ } else if (args->socket == record->socket && !args->added) {
+ pfx_table_notify_clients(args->new_table, record, args->added);
+ }
+}
+
+void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket)
+{
+ pfx_update_fp old_table_fp;
+ struct notify_diff_cb_args args = {old_table, new_table, socket, new_table->update_fp, true};
+
+ // Disable update callback for old_table table
+ old_table_fp = old_table->update_fp;
+ old_table->update_fp = NULL;
+
+ // Iterate new_table and try to delete every prefix from the given socket in old_table
+ // If the prefix could not be removed it was added in new_table and the update cb must be called
+ pfx_table_for_each_ipv4_record(new_table, pfx_table_notify_diff_cb, &args);
+ pfx_table_for_each_ipv6_record(new_table, pfx_table_notify_diff_cb, &args);
+
+ // Iterate over old_table table and search and remove remaining prefixes from the socket
+ // issue a remove notification for every one of them, because they are not present in new_table.
+ args.added = false;
+ pfx_table_for_each_ipv4_record(old_table, pfx_table_notify_diff_cb, &args);
+ pfx_table_for_each_ipv6_record(old_table, pfx_table_notify_diff_cb, &args);
+
+ // Restore original state of old_tables update_fp
+ old_table->update_fp = old_table_fp;
+}
diff --git a/rtrlib/pfx/trie/trie-pfx.h b/rtrlib/pfx/trie/trie-pfx.h
new file mode 100644
index 0000000..7a8f69a
--- /dev/null
+++ b/rtrlib/pfx/trie/trie-pfx.h
@@ -0,0 +1,73 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_trie_pfx_h Trie
+ * @ingroup mod_pfx_h
+ * @brief An implementation of a \ref mod_pfx_h "pfx_table" data structure
+ * using a shortest prefix first tree (trie) for storing @ref pfx_record "pfx_records".
+ * @details This implementation uses two separate lpfs-trees, one for IPv4
+ * validation records and one for IPv6 records.\n
+ * See \ref mod_pfx_h "pfx_table" for a list of supported operations of
+ * this data structure.\n
+ *
+ * @{
+ */
+
+#ifndef RTR_TRIE_PFX
+#define RTR_TRIE_PFX
+
+#include "rtrlib/lib/ip.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+struct pfx_table;
+
+/**
+ * @brief pfx_record.
+ * @param asn Origin AS number.
+ * @param prefix IP prefix.
+ * @param min_len Minimum prefix length.
+ * @param max_len Maximum prefix length.
+ * @param socket The rtr_socket that received this record.
+ */
+struct pfx_record {
+ uint32_t asn;
+ struct lrtr_ip_addr prefix;
+ uint8_t min_len;
+ uint8_t max_len;
+ const struct rtr_socket *socket;
+};
+
+/**
+ * @brief A function pointer that is called if an record was added to the pfx_table or was removed from the pfx_table.
+ * @param pfx_table which was updated.
+ * @param record pfx_record that was modified.
+ * @param added True if the record was added, false if the record was removed.
+ */
+typedef void (*pfx_update_fp)(struct pfx_table *pfx_table, const struct pfx_record record, const bool added);
+
+/**
+ * @brief pfx_table.
+ * @param ipv4
+ * @param ipv6
+ * @param update_fp
+ * @param lock
+ */
+struct pfx_table {
+ struct trie_node *ipv4;
+ struct trie_node *ipv6;
+ pfx_update_fp update_fp;
+ pthread_rwlock_t lock;
+};
+
+#endif
+/** @} */
diff --git a/rtrlib/pfx/trie/trie.c b/rtrlib/pfx/trie/trie.c
new file mode 100644
index 0000000..b359892
--- /dev/null
+++ b/rtrlib/pfx/trie/trie.c
@@ -0,0 +1,235 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "trie_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/ip_private.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+static void swap_nodes(struct trie_node *a, struct trie_node *b)
+{
+ struct trie_node tmp;
+
+ tmp.prefix = a->prefix;
+ tmp.len = a->len;
+ tmp.data = a->data;
+
+ a->prefix = b->prefix;
+ a->len = b->len;
+ a->data = b->data;
+
+ b->prefix = tmp.prefix;
+ b->len = tmp.len;
+ b->data = tmp.data;
+}
+
+enum child_node_rel { LEFT, RIGHT };
+
+static void add_child_node(struct trie_node *parent, struct trie_node *child, enum child_node_rel rel)
+{
+ assert(rel == LEFT || rel == RIGHT);
+
+ if (rel == LEFT)
+ parent->lchild = child;
+ else
+ parent->rchild = child;
+
+ child->parent = parent;
+}
+
+static inline bool is_left_child(const struct lrtr_ip_addr *addr, unsigned int lvl)
+{
+ /* A node must be inserted as left child if bit <lvl> of the IP address
+ * is 0 otherwise as right child
+ */
+ return lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits(addr, lvl, 1));
+}
+
+void trie_insert(struct trie_node *root, struct trie_node *new, const unsigned int lvl)
+{
+ if (new->len < root->len)
+ swap_nodes(root, new);
+
+ if (is_left_child(&new->prefix, lvl)) {
+ if (!root->lchild)
+ return add_child_node(root, new, LEFT);
+
+ return trie_insert(root->lchild, new, lvl + 1);
+ }
+
+ if (!root->rchild)
+ return add_child_node(root, new, RIGHT);
+
+ trie_insert(root->rchild, new, lvl + 1);
+}
+
+struct trie_node *trie_lookup(const struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len,
+ unsigned int *lvl)
+{
+ while (root) {
+ if (root->len <= mask_len && lrtr_ip_addr_equal(lrtr_ip_addr_get_bits(&root->prefix, 0, root->len),
+ lrtr_ip_addr_get_bits(prefix, 0, root->len)))
+ return (struct trie_node *)root;
+
+ if (is_left_child(prefix, *lvl))
+ root = root->lchild;
+ else
+ root = root->rchild;
+
+ (*lvl)++;
+ }
+ return NULL;
+}
+
+struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix,
+ const uint8_t mask_len, unsigned int *lvl, bool *found)
+{
+ *found = false;
+
+ while (root_node) {
+ if (*lvl > 0 && root_node->len > mask_len) {
+ (*lvl)--;
+ return root_node->parent;
+ }
+
+ if (root_node->len == mask_len && lrtr_ip_addr_equal(root_node->prefix, *prefix)) {
+ *found = true;
+ return root_node;
+ }
+
+ if (is_left_child(prefix, *lvl)) {
+ if (!root_node->lchild)
+ return root_node;
+ root_node = root_node->lchild;
+ } else {
+ if (!root_node->rchild)
+ return root_node;
+ root_node = root_node->rchild;
+ }
+
+ (*lvl)++;
+ }
+ return NULL;
+}
+
+static void deref_node(struct trie_node *n)
+{
+ if (!n->parent)
+ return;
+
+ if (n->parent->lchild == n) {
+ n->parent->lchild = NULL;
+ return;
+ }
+
+ n->parent->rchild = NULL;
+}
+
+static inline bool prefix_is_same(const struct trie_node *n, const struct lrtr_ip_addr *p, uint8_t mask_len)
+{
+ return n->len == mask_len && lrtr_ip_addr_equal(n->prefix, *p);
+}
+
+static void replace_node_data(struct trie_node *a, struct trie_node *b)
+{
+ a->prefix = b->prefix;
+ a->len = b->len;
+ a->data = b->data;
+}
+
+struct trie_node *trie_remove(struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len,
+ const unsigned int lvl)
+{
+ /* If the node has no children we can simply remove it
+ * If the node has children, we swap the node with the child that
+ * has the smaller prefix length and drop the child.
+ */
+ if (prefix_is_same(root, prefix, mask_len)) {
+ void *tmp;
+
+ if (trie_is_leaf(root)) {
+ deref_node(root);
+ return root;
+ }
+
+ /* swap with the left child and drop the child */
+ if (root->lchild && (!root->rchild || root->lchild->len < root->rchild->len)) {
+ tmp = root->data;
+ replace_node_data(root, root->lchild);
+ root->lchild->data = tmp;
+
+ return trie_remove(root->lchild, &root->lchild->prefix, root->lchild->len, lvl + 1);
+ }
+
+ /* swap with the right child and drop the child */
+ tmp = root->data;
+ replace_node_data(root, root->rchild);
+ root->rchild->data = tmp;
+
+ return trie_remove(root->rchild, &root->rchild->prefix, root->rchild->len, lvl + 1);
+ }
+
+ if (is_left_child(prefix, lvl)) {
+ if (!root->lchild)
+ return NULL;
+ return trie_remove(root->lchild, prefix, mask_len, lvl + 1);
+ }
+
+ if (!root->rchild)
+ return NULL;
+ return trie_remove(root->rchild, prefix, mask_len, lvl + 1);
+}
+
+static int append_node_to_array(struct trie_node ***ary, unsigned int *len, struct trie_node *n)
+{
+ struct trie_node **new;
+
+ new = lrtr_realloc(*ary, *len * sizeof(n));
+ if (!new)
+ return -1;
+
+ *ary = new;
+ (*ary)[*len - 1] = n;
+ return 0;
+}
+
+int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len)
+{
+ if (root_node->lchild) {
+ *len += 1;
+ if (append_node_to_array(array, len, root_node->lchild))
+ goto err;
+
+ if (trie_get_children(root_node->lchild, array, len) == -1)
+ goto err;
+ }
+
+ if (root_node->rchild) {
+ *len += 1;
+ if (append_node_to_array(array, len, root_node->rchild))
+ goto err;
+
+ if (trie_get_children(root_node->rchild, array, len) == -1)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ lrtr_free(*array);
+ return -1;
+}
+
+inline bool trie_is_leaf(const struct trie_node *node)
+{
+ return !node->lchild && !node->rchild;
+}
diff --git a/rtrlib/pfx/trie/trie_private.h b/rtrlib/pfx/trie/trie_private.h
new file mode 100644
index 0000000..8003ced
--- /dev/null
+++ b/rtrlib/pfx/trie/trie_private.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef RTR_TRIE_PRIVATE
+#define RTR_TRIE_PRIVATE
+
+#include "rtrlib/lib/ip_private.h"
+
+#include <inttypes.h>
+
+/**
+ * @brief trie_node
+ * @param prefix
+ * @param rchild
+ * @param lchild
+ * @param parent
+ * @param data
+ * @param len number of elements in data array
+ */
+struct trie_node {
+ struct lrtr_ip_addr prefix;
+ struct trie_node *rchild;
+ struct trie_node *lchild;
+ struct trie_node *parent;
+ void *data;
+ uint8_t len;
+};
+
+/**
+ * @brief Inserts new_node in the tree.
+ * @param[in] root Root node of the tree for the inserting process.
+ * @param[in] new_node Node that will be inserted.
+ * @param[in] level Level of the the root node in the tree.
+ */
+void trie_insert(struct trie_node *root, struct trie_node *new_node, const unsigned int level);
+
+/**
+ * @brief Searches for a matching node matching the passed ip
+ * prefix and prefix length. If multiple matching nodes exist, the one
+ * with the shortest prefix is returned.
+ * @param[in] root_node Node were the lookup process starts.
+ * @param[in] lrtr_ip_addr IP-Prefix.
+ * @param[in] mask_len Length of the network mask of the prefix.
+ * @param[in,out] level of the the node root in the tree. Is set to the level of
+ * the node that is returned.
+ * @returns The trie_node with the short prefix in the tree matching the
+ * passed ip prefix and prefix length.
+ * @returns NULL if no node that matches the passed prefix and prefix length
+ * could be found.
+ */
+struct trie_node *trie_lookup(const struct trie_node *root_node, const struct lrtr_ip_addr *prefix,
+ const uint8_t mask_len, unsigned int *level);
+
+/**
+ * @brief Search for a node with the same prefix and prefix length.
+ * @param[in] root_node Node were the lookup process starts.
+ * @param[in] lrtr_ip_addr IP-Prefix.
+ * @param[in] mask_len Length of the network mask of the prefix.
+ * @param[in,out] level of the the node root in the tree. Is set to the level of
+ * the node that is returned.
+ * @param[in] found Is true if a node which matches could be found else found is
+ * set to false.
+ * @return A node which matches the passed parameter (found==true).
+ * @return The parent of the node where the lookup operation
+ * stopped (found==false).
+ * @return NULL if root_node is NULL.
+ */
+struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix,
+ const uint8_t mask_len, unsigned int *level, bool *found);
+
+/**
+ * @brief Removes the node with the passed IP prefix and mask_len from the tree.
+ * @param[in] root Node were the inserting process starts.
+ * @param[in] prefix Prefix that will removed from the tree.
+ * @param[in] mask_len Length of the network mask of the prefix.
+ * @param[in] level Level of the root node in the tree.
+ * @returns Node that was removed from the tree. The caller has to free it.
+ * @returns NULL If the Prefix couldn't be found in the tree.
+ */
+struct trie_node *trie_remove(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, const uint8_t mask_len,
+ const unsigned int level);
+
+/**
+ * @brief Detects if a node is a leaf in the tree.
+ * @param[in] node
+ * @returns true if node is a leaf.
+ * @returns false if node isn't a leaf.
+ */
+bool trie_is_leaf(const struct trie_node *node);
+
+int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len);
+#endif
diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c
new file mode 100644
index 0000000..ec00779
--- /dev/null
+++ b/rtrlib/rtr/packets.c
@@ -0,0 +1,1517 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "packets_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/convert_byte_order_private.h"
+#include "rtrlib/lib/log_private.h"
+#include "rtrlib/lib/utils_private.h"
+#include "rtrlib/pfx/pfx_private.h"
+#include "rtrlib/rtr/rtr_private.h"
+#include "rtrlib/spki/hashtable/ht-spkitable_private.h"
+#include "rtrlib/transport/transport_private.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MGR_DBG1(a) lrtr_dbg("RTR_MGR: " a)
+#define TEMPORARY_PDU_STORE_INCREMENT_VALUE 100
+#define MAX_SUPPORTED_PDU_TYPE 10
+
+enum pdu_error_type {
+ CORRUPT_DATA = 0,
+ INTERNAL_ERROR = 1,
+ NO_DATA_AVAIL = 2,
+ INVALID_REQUEST = 3,
+ UNSUPPORTED_PROTOCOL_VER = 4,
+ UNSUPPORTED_PDU_TYPE = 5,
+ WITHDRAWAL_OF_UNKNOWN_RECORD = 6,
+ DUPLICATE_ANNOUNCEMENT = 7,
+ UNEXPECTED_PROTOCOL_VERSION = 8,
+ PDU_TOO_BIG = 32
+};
+
+enum pdu_type {
+ SERIAL_NOTIFY = 0,
+ SERIAL_QUERY = 1,
+ RESET_QUERY = 2,
+ CACHE_RESPONSE = 3,
+ IPV4_PREFIX = 4,
+ RESERVED = 5,
+ IPV6_PREFIX = 6,
+ EOD = 7,
+ CACHE_RESET = 8,
+ ROUTER_KEY = 9,
+ ERROR = 10
+};
+
+struct pdu_header {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+};
+
+struct pdu_cache_response {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+};
+
+struct pdu_serial_notify {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_serial_query {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_ipv4 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+ uint8_t flags;
+ uint8_t prefix_len;
+ uint8_t max_prefix_len;
+ uint8_t zero;
+ uint32_t prefix;
+ uint32_t asn;
+};
+
+struct pdu_ipv6 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t reserved;
+ uint32_t len;
+ uint8_t flags;
+ uint8_t prefix_len;
+ uint8_t max_prefix_len;
+ uint8_t zero;
+ uint32_t prefix[4];
+ uint32_t asn;
+};
+
+struct pdu_error {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t error_code;
+ uint32_t len;
+ uint32_t len_enc_pdu;
+ uint8_t rest[];
+};
+
+struct pdu_router_key {
+ uint8_t ver;
+ uint8_t type;
+ uint8_t flags;
+ uint8_t zero;
+ uint32_t len;
+ uint8_t ski[SKI_SIZE];
+ uint32_t asn;
+ uint8_t spki[SPKI_SIZE];
+} __attribute__((packed));
+
+/*
+ * 0 8 16 24 31
+ * .-------------------------------------------.
+ * | Protocol | PDU | |
+ * | Version | Type | reserved = zero |
+ * | 0 | 2 | |
+ * +-------------------------------------------+
+ * | |
+ * | Length=8 |
+ * | |
+ * `-------------------------------------------'
+ */
+struct pdu_reset_query {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t flags;
+ uint32_t len;
+};
+
+struct pdu_end_of_data_v0 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+};
+
+struct pdu_end_of_data_v1 {
+ uint8_t ver;
+ uint8_t type;
+ uint16_t session_id;
+ uint32_t len;
+ uint32_t sn;
+ uint32_t refresh_interval;
+ uint32_t retry_interval;
+ uint32_t expire_interval;
+};
+
+struct recv_loop_cleanup_args {
+ struct pdu_ipv4 *ipv4_pdus;
+ struct pdu_ipv6 *ipv6_pdus;
+ struct pdu_router_key *router_key_pdus;
+};
+
+static void recv_loop_cleanup(void *p);
+
+static int rtr_send_error_pdu_from_network(const struct rtr_socket *rtr_socket, const void *erroneous_pdu,
+ const uint32_t erroneous_pdu_len, const enum pdu_error_type error,
+ const char *err_text, const uint32_t err_text_len);
+
+static int rtr_send_error_pdu_from_host(const struct rtr_socket *rtr_socket, const void *erroneous_pdu,
+ const uint32_t erroneous_pdu_len, const enum pdu_error_type error,
+ const char *err_text, const uint32_t err_text_len);
+
+static int interval_send_error_pdu(struct rtr_socket *rtr_socket, void *pdu, uint32_t interval, uint16_t minimum,
+ uint32_t maximum);
+
+static inline enum pdu_type rtr_get_pdu_type(const void *pdu)
+{
+ return *((char *)pdu + 1);
+}
+
+static int rtr_set_last_update(struct rtr_socket *rtr_socket)
+{
+ if (lrtr_get_monotonic_time(&(rtr_socket->last_update)) == -1) {
+ RTR_DBG1("get_monotonic_time(..) failed ");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ return RTR_SUCCESS;
+}
+
+int rtr_check_interval_range(uint32_t interval, uint32_t minimum, uint32_t maximum)
+{
+ if (interval < minimum)
+ return RTR_BELOW_INTERVAL_RANGE;
+ else if (interval > maximum)
+ return RTR_ABOVE_INTERVAL_RANGE;
+
+ return RTR_INSIDE_INTERVAL_RANGE;
+}
+
+void apply_interval_value(struct rtr_socket *rtr_socket, uint32_t interval, enum rtr_interval_type type)
+{
+ if (type == RTR_INTERVAL_TYPE_EXPIRATION)
+ rtr_socket->expire_interval = interval;
+ else if (type == RTR_INTERVAL_TYPE_REFRESH)
+ rtr_socket->refresh_interval = interval;
+ else if (type == RTR_INTERVAL_TYPE_RETRY)
+ rtr_socket->retry_interval = interval;
+}
+
+int rtr_check_interval_option(struct rtr_socket *rtr_socket, int interval_mode, uint32_t interval,
+ enum rtr_interval_type type)
+{
+ uint16_t minimum;
+ uint32_t maximum;
+
+ int interv_retval;
+
+ switch (type) {
+ case RTR_INTERVAL_TYPE_EXPIRATION:
+ minimum = RTR_EXPIRATION_MIN;
+ maximum = RTR_EXPIRATION_MAX;
+ interv_retval = rtr_check_interval_range(interval, minimum, maximum);
+ break;
+ case RTR_INTERVAL_TYPE_REFRESH:
+ minimum = RTR_REFRESH_MIN;
+ maximum = RTR_REFRESH_MAX;
+ interv_retval = rtr_check_interval_range(interval, minimum, maximum);
+ break;
+ case RTR_INTERVAL_TYPE_RETRY:
+ minimum = RTR_RETRY_MIN;
+ maximum = RTR_RETRY_MAX;
+ interv_retval = rtr_check_interval_range(interval, minimum, maximum);
+ break;
+ default:
+ RTR_DBG("Invalid interval type: %u.", type);
+ return RTR_ERROR;
+ }
+
+ if (interv_retval == RTR_INSIDE_INTERVAL_RANGE || interval_mode == RTR_INTERVAL_MODE_ACCEPT_ANY) {
+ apply_interval_value(rtr_socket, interval, type);
+ } else if (interval_mode == RTR_INTERVAL_MODE_DEFAULT_MIN_MAX) {
+ if (interv_retval == RTR_BELOW_INTERVAL_RANGE)
+ apply_interval_value(rtr_socket, minimum, type);
+ else
+ apply_interval_value(rtr_socket, maximum, type);
+ } else {
+ RTR_DBG("Received expiration value out of range. Was %u. It will be ignored.",
+ interval);
+ }
+
+ return RTR_SUCCESS;
+}
+
+void rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state)
+{
+ if (rtr_socket->state == new_state)
+ return;
+
+ // RTR_SHUTDOWN state is final,struct rtr_socket will be shutdowned can't be switched to any other state
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ return;
+
+ rtr_socket->state = new_state;
+ if (new_state == RTR_SHUTDOWN)
+ MGR_DBG1("Calling rtr_mgr_cb with RTR_SHUTDOWN");
+
+ if (rtr_socket->connection_state_fp)
+ rtr_socket->connection_state_fp(rtr_socket, new_state, rtr_socket->connection_state_fp_param_config,
+ rtr_socket->connection_state_fp_param_group);
+}
+
+static void rtr_pdu_convert_header_byte_order(void *pdu, const enum target_byte_order target_byte_order)
+{
+ struct pdu_header *header = pdu;
+
+ // The ROUTER_KEY PDU has two 1 Byte fields instead of the 2 Byte reserved field.
+ if (header->type != ROUTER_KEY)
+ header->reserved = lrtr_convert_short(target_byte_order, header->reserved);
+
+ header->len = lrtr_convert_long(target_byte_order, header->len);
+}
+
+static void rtr_pdu_convert_footer_byte_order(void *pdu, const enum target_byte_order target_byte_order)
+{
+ struct pdu_error *err_pdu;
+ struct pdu_header *header = pdu;
+ uint32_t addr6[4];
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ switch (type) {
+ case SERIAL_QUERY:
+ ((struct pdu_serial_query *)pdu)->sn =
+ lrtr_convert_long(target_byte_order, ((struct pdu_serial_query *)pdu)->sn);
+ break;
+
+ case ERROR:
+ err_pdu = (struct pdu_error *)pdu;
+ if (target_byte_order == TO_NETWORK_BYTE_ORDER) {
+ *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)) = lrtr_convert_long(
+ target_byte_order, *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)));
+ err_pdu->len_enc_pdu = lrtr_convert_long(target_byte_order, err_pdu->len_enc_pdu);
+ } else {
+ err_pdu->len_enc_pdu = lrtr_convert_long(target_byte_order, err_pdu->len_enc_pdu);
+ *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)) = lrtr_convert_long(
+ target_byte_order, *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)));
+ }
+ break;
+
+ case SERIAL_NOTIFY:
+ ((struct pdu_serial_notify *)pdu)->sn =
+ lrtr_convert_long(target_byte_order, ((struct pdu_serial_notify *)pdu)->sn);
+ break;
+
+ case EOD:
+ if (header->ver == RTR_PROTOCOL_VERSION_1) {
+ ((struct pdu_end_of_data_v1 *)pdu)->expire_interval = lrtr_convert_long(
+ target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->expire_interval);
+
+ ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval = lrtr_convert_long(
+ target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval);
+
+ ((struct pdu_end_of_data_v1 *)pdu)->retry_interval = lrtr_convert_long(
+ target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->retry_interval);
+
+ ((struct pdu_end_of_data_v1 *)pdu)->sn =
+ lrtr_convert_long(target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->sn);
+ } else {
+ ((struct pdu_end_of_data_v0 *)pdu)->sn =
+ lrtr_convert_long(target_byte_order, ((struct pdu_end_of_data_v0 *)pdu)->sn);
+ }
+ break;
+
+ case IPV4_PREFIX:
+ lrtr_ipv4_addr_convert_byte_order(((struct pdu_ipv4 *)pdu)->prefix, &((struct pdu_ipv4 *)pdu)->prefix,
+ target_byte_order);
+ ((struct pdu_ipv4 *)pdu)->asn = lrtr_convert_long(target_byte_order, ((struct pdu_ipv4 *)pdu)->asn);
+ break;
+
+ case IPV6_PREFIX:
+ lrtr_ipv6_addr_convert_byte_order(((struct pdu_ipv6 *)pdu)->prefix, addr6, target_byte_order);
+ memcpy(((struct pdu_ipv6 *)pdu)->prefix, addr6, sizeof(addr6));
+ ((struct pdu_ipv6 *)pdu)->asn = lrtr_convert_long(target_byte_order, ((struct pdu_ipv6 *)pdu)->asn);
+ break;
+
+ case ROUTER_KEY:
+ ((struct pdu_router_key *)pdu)->asn =
+ lrtr_convert_long(target_byte_order, ((struct pdu_router_key *)pdu)->asn);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void rtr_pdu_header_to_network_byte_order(void *pdu)
+{
+ rtr_pdu_convert_header_byte_order(pdu, TO_NETWORK_BYTE_ORDER);
+}
+
+static void rtr_pdu_footer_to_network_byte_order(void *pdu)
+{
+ rtr_pdu_convert_footer_byte_order(pdu, TO_NETWORK_BYTE_ORDER);
+}
+
+static void rtr_pdu_to_network_byte_order(void *pdu)
+{
+ rtr_pdu_footer_to_network_byte_order(pdu);
+ rtr_pdu_header_to_network_byte_order(pdu);
+}
+
+static void rtr_pdu_footer_to_host_byte_order(void *pdu)
+{
+ rtr_pdu_convert_footer_byte_order(pdu, TO_HOST_HOST_BYTE_ORDER);
+}
+
+static void rtr_pdu_header_to_host_byte_order(void *pdu)
+{
+ rtr_pdu_convert_header_byte_order(pdu, TO_HOST_HOST_BYTE_ORDER);
+}
+
+/*
+ * Check if the PDU is big enough for the PDU type it
+ * pretend to be.
+ * @param pdu A pointer to a PDU that is at least pdu->len byte large.
+ * @return False if the check fails, else true
+ */
+static bool rtr_pdu_check_size(const struct pdu_header *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+ const struct pdu_error *err_pdu = NULL;
+ bool retval = false;
+ uint64_t min_size = 0;
+
+ switch (type) {
+ case SERIAL_NOTIFY:
+ if (sizeof(struct pdu_serial_notify) == pdu->len)
+ retval = true;
+ break;
+ case CACHE_RESPONSE:
+ if (sizeof(struct pdu_cache_response) == pdu->len)
+ retval = true;
+ break;
+ case IPV4_PREFIX:
+ if (sizeof(struct pdu_ipv4) == pdu->len)
+ retval = true;
+ break;
+ case IPV6_PREFIX:
+ if (sizeof(struct pdu_ipv6) == pdu->len)
+ retval = true;
+ break;
+ case EOD:
+ if ((pdu->ver == RTR_PROTOCOL_VERSION_0 && (sizeof(struct pdu_end_of_data_v0) == pdu->len)) ||
+ (pdu->ver == RTR_PROTOCOL_VERSION_1 && (sizeof(struct pdu_end_of_data_v1) == pdu->len))) {
+ retval = true;
+ }
+ break;
+ case CACHE_RESET:
+ if (sizeof(struct pdu_header) == pdu->len)
+ retval = true;
+ break;
+ case ROUTER_KEY:
+ if (sizeof(struct pdu_router_key) == pdu->len)
+ retval = true;
+ break;
+ case ERROR:
+ err_pdu = (const struct pdu_error *)pdu;
+ // +4 because of the "Length of Error Text" field
+ min_size = 4 + sizeof(struct pdu_error);
+ if (err_pdu->len < min_size) {
+ RTR_DBG1("PDU is too small to contain \"Length of Error Text\" field!");
+ break;
+ }
+
+ // Check if the PDU really contains the error PDU
+ uint32_t enc_pdu_len = ntohl(err_pdu->len_enc_pdu);
+
+ RTR_DBG("enc_pdu_len: %u", enc_pdu_len);
+ min_size += enc_pdu_len;
+ if (err_pdu->len < min_size) {
+ RTR_DBG1("PDU is too small to contain erroneous PDU!");
+ break;
+ }
+
+ // Check if the the PDU really contains the error msg
+ uint32_t err_msg_len = ntohl(*((uint32_t *)(err_pdu->rest + enc_pdu_len)));
+
+ RTR_DBG("err_msg_len: %u", err_msg_len);
+ min_size += err_msg_len;
+ if (err_pdu->len != min_size) {
+ RTR_DBG1("PDU is too small to contain error_msg!");
+ break;
+ }
+
+ retval = true;
+ break;
+ case SERIAL_QUERY:
+ if (sizeof(struct pdu_serial_query) == pdu->len)
+ retval = true;
+ break;
+ case RESET_QUERY:
+ if (sizeof(struct pdu_reset_query) == pdu->len)
+ retval = true;
+ break;
+ case RESERVED:
+ default:
+ RTR_DBG1("PDU type is unknown or reserved!");
+ retval = false;
+ break;
+ }
+
+#ifndef NDEBUG
+ if (!retval)
+ RTR_DBG1("Received malformed PDU!");
+#endif
+
+ return retval;
+}
+
+static int rtr_send_pdu(const struct rtr_socket *rtr_socket, const void *pdu, const unsigned int len)
+{
+ char pdu_converted[len];
+
+ memcpy(pdu_converted, pdu, len);
+ rtr_pdu_to_network_byte_order(pdu_converted);
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ return RTR_ERROR;
+ const int rtval = tr_send_all(rtr_socket->tr_socket, pdu_converted, len, RTR_SEND_TIMEOUT);
+
+ if (rtval > 0)
+ return RTR_SUCCESS;
+ if (rtval == TR_WOULDBLOCK) {
+ RTR_DBG1("send would block");
+ return RTR_ERROR;
+ }
+
+ RTR_DBG1("Error sending PDU");
+ return RTR_ERROR;
+}
+
+/*
+ * if RTR_ERROR was returned a error PDU was sent, and the socket state changed
+ * @param pdu_len must >= RTR_MAX_PDU_LEN Bytes
+ * @return RTR_SUCCESS
+ * @return RTR_ERROR, error pdu was sent and socket_state changed
+ * @return TR_WOULDBLOCK
+ * \post
+ * If RTR_SUCCESS is returned PDU points to a well formed PDU that has
+ * the appropriate size for the PDU type it pretend to be. Thus, casting it to
+ * the PDU type struct and using it is save. Furthermore all PDU field are
+ * in host-byte-order.
+ */
+static int rtr_receive_pdu(struct rtr_socket *rtr_socket, void *pdu, const size_t pdu_len, const time_t timeout)
+{
+ int error = RTR_SUCCESS;
+
+ assert(pdu_len >= RTR_MAX_PDU_LEN);
+
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ return RTR_ERROR;
+ // receive packet header
+ error = tr_recv_all(rtr_socket->tr_socket, pdu, sizeof(struct pdu_header), timeout);
+ if (error < 0)
+ goto error;
+ else
+ error = RTR_SUCCESS;
+
+ // header in hostbyte order, retain original received pdu, in case we need to detach it to an error pdu
+ struct pdu_header header;
+
+ memcpy(&header, pdu, sizeof(header));
+ rtr_pdu_header_to_host_byte_order(&header);
+
+ // if header->len is < packet_header = corrupt data received
+ if (header.len < sizeof(header)) {
+ error = CORRUPT_DATA;
+ goto error;
+ } else if (header.len > RTR_MAX_PDU_LEN) { // PDU too big, > than MAX_PDU_LEN Bytes
+ error = PDU_TOO_BIG;
+ goto error;
+ }
+
+ // Handle live downgrading
+ if (!rtr_socket->has_received_pdus) {
+ if (rtr_socket->version == RTR_PROTOCOL_VERSION_1 && header.ver == RTR_PROTOCOL_VERSION_0 &&
+ header.type != ERROR) {
+ RTR_DBG("First received PDU is a version 0 PDU, downgrading to %u", RTR_PROTOCOL_VERSION_0);
+ rtr_socket->version = RTR_PROTOCOL_VERSION_0;
+ }
+ rtr_socket->has_received_pdus = true;
+ }
+
+ // Handle wrong protocol version
+ // If it is a error PDU, it will be handled by rtr_handle_error_pdu
+ if (header.ver != rtr_socket->version && header.type != ERROR) {
+ error = UNEXPECTED_PROTOCOL_VERSION;
+ goto error;
+ }
+
+ // receive packet payload
+ const unsigned int remaining_len = header.len - sizeof(header);
+
+ if (remaining_len > 0) {
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ return RTR_ERROR;
+ error = tr_recv_all(rtr_socket->tr_socket, (((char *)pdu) + sizeof(header)), remaining_len,
+ RTR_RECV_TIMEOUT);
+ if (error < 0)
+ goto error;
+ else
+ error = RTR_SUCCESS;
+ }
+ // copy header in host_byte_order to pdu
+ memcpy(pdu, &header, sizeof(header));
+
+ // Check if the header len value is valid
+ if (rtr_pdu_check_size(pdu) == false) {
+ // TODO Restore byteorder for sending error PDU
+ error = CORRUPT_DATA;
+ goto error;
+ }
+ // At this point it is save to cast and use the PDU
+
+ rtr_pdu_footer_to_host_byte_order(pdu);
+
+ // Here we should handle error PDUs instead of doing it in
+ // several other places...
+
+ if (header.type == IPV4_PREFIX || header.type == IPV6_PREFIX) {
+ if (((struct pdu_ipv4 *)pdu)->zero != 0)
+ RTR_DBG1("Warning: Zero field of received Prefix PDU doesn't contain 0");
+ }
+ if (header.type == ROUTER_KEY && ((struct pdu_router_key *)pdu)->zero != 0)
+ RTR_DBG1("Warning: ROUTER_KEY_PDU zero field is != 0");
+
+ return RTR_SUCCESS;
+
+error:
+ // send error msg to server, including unmodified pdu header(pdu variable instead header)
+ if (error == -1) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ } else if (error == TR_WOULDBLOCK) {
+ RTR_DBG1("receive timeout expired");
+ return TR_WOULDBLOCK;
+ } else if (error == TR_INTR) {
+ RTR_DBG1("receive call interrupted");
+ return TR_INTR;
+ } else if (error == CORRUPT_DATA) {
+ RTR_DBG1("corrupt PDU received");
+ const char txt[] = "corrupt data received, length value in PDU is too small";
+
+ rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt));
+ } else if (error == PDU_TOO_BIG) {
+ RTR_DBG1("PDU too big");
+ char txt[42];
+
+ snprintf(txt, sizeof(txt), "PDU too big, max. PDU size is: %u bytes", RTR_MAX_PDU_LEN);
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt));
+ } else if (error == UNSUPPORTED_PDU_TYPE) {
+ RTR_DBG("Unsupported PDU type (%u) received", header.type);
+ rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNSUPPORTED_PDU_TYPE, NULL, 0);
+ } else if (error == UNSUPPORTED_PROTOCOL_VER) {
+ RTR_DBG("PDU with unsupported Protocol version (%u) received", header.ver);
+ rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNSUPPORTED_PROTOCOL_VER, NULL, 0);
+ return RTR_ERROR;
+ } else if (error == UNEXPECTED_PROTOCOL_VERSION) {
+ RTR_DBG("PDU with unexpected Protocol version (%u) received", header.ver);
+ rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNEXPECTED_PROTOCOL_VERSION, NULL, 0);
+ return RTR_ERROR;
+ }
+
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+}
+
+static int rtr_handle_error_pdu(struct rtr_socket *rtr_socket, const void *buf)
+{
+ RTR_DBG1("Error PDU received"); // TODO: append server ip & port
+ const struct pdu_error *pdu = buf;
+
+ switch (pdu->error_code) {
+ case CORRUPT_DATA:
+ RTR_DBG1("Corrupt data received");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ case INTERNAL_ERROR:
+ RTR_DBG1("Internal error on server-side");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ case NO_DATA_AVAIL:
+ RTR_DBG1("No data available");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_DATA_AVAIL);
+ break;
+ case INVALID_REQUEST:
+ RTR_DBG1("Invalid request from client");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ case UNSUPPORTED_PROTOCOL_VER:
+ RTR_DBG1("Client uses unsupported protocol version");
+ if (pdu->ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION && pdu->ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION &&
+ pdu->ver < rtr_socket->version) {
+ RTR_DBG("Downgrading from %i to version %i", rtr_socket->version, pdu->ver);
+ rtr_socket->version = pdu->ver;
+ rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT);
+ } else {
+ RTR_DBG("Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, current version:%i, PDU version:%i",
+ rtr_socket->version, pdu->ver);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+ break;
+ case UNSUPPORTED_PDU_TYPE:
+ RTR_DBG1("Client set unsupported PDU type");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ default:
+ RTR_DBG("error unknown, server sent unsupported error code %u", pdu->error_code);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ break;
+ }
+
+ const uint32_t len_err_txt = *((uint32_t *)(pdu->rest + pdu->len_enc_pdu));
+
+ if (len_err_txt > 0) {
+ if ((sizeof(pdu->ver) + sizeof(pdu->type) + sizeof(pdu->error_code) + sizeof(pdu->len) +
+ sizeof(pdu->len_enc_pdu) + pdu->len_enc_pdu + 4 + len_err_txt) != pdu->len) {
+ RTR_DBG1("error: Length of error text contains an incorrect value");
+ } else {
+ char *pdu_txt = (char *)pdu->rest + pdu->len_enc_pdu + sizeof(len_err_txt);
+
+ RTR_DBG("Error PDU included the following error msg: \'%.*s\'", len_err_txt, pdu_txt);
+ }
+ }
+
+ return RTR_SUCCESS;
+}
+
+static int rtr_handle_cache_response_pdu(struct rtr_socket *rtr_socket, char *pdu)
+{
+ RTR_DBG1("Cache Response PDU received");
+ struct pdu_cache_response *cr_pdu = (struct pdu_cache_response *)pdu;
+ // set connection session_id
+ if (rtr_socket->request_session_id) {
+ if (rtr_socket->last_update != 0) {
+ RTR_DBG1("Resetting Socket.");
+
+ rtr_socket->last_update = 0;
+ rtr_socket->is_resetting = true;
+ }
+ rtr_socket->session_id = cr_pdu->session_id;
+ } else {
+ if (rtr_socket->session_id != cr_pdu->session_id) {
+ const char txt[] =
+ "Wrong session_id in Cache Response PDU"; //TODO: Appendrtr_socket->session_id to string
+ rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, CORRUPT_DATA, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ }
+ return RTR_SUCCESS;
+}
+
+static void rtr_key_pdu_2_spki_record(const struct rtr_socket *rtr_socket, const struct pdu_router_key *pdu,
+ struct spki_record *entry, const enum pdu_type type)
+{
+ assert(type == ROUTER_KEY);
+ entry->asn = pdu->asn;
+ memcpy(entry->ski, pdu->ski, SKI_SIZE);
+ memcpy(entry->spki, pdu->spki, SPKI_SIZE);
+ entry->socket = rtr_socket;
+}
+
+static void rtr_prefix_pdu_2_pfx_record(const struct rtr_socket *rtr_socket, const void *pdu, struct pfx_record *pfxr,
+ const enum pdu_type type)
+{
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+ if (type == IPV4_PREFIX) {
+ const struct pdu_ipv4 *ipv4 = pdu;
+
+ pfxr->prefix.u.addr4.addr = ipv4->prefix;
+ pfxr->asn = ipv4->asn;
+ pfxr->prefix.ver = LRTR_IPV4;
+ pfxr->min_len = ipv4->prefix_len;
+ pfxr->max_len = ipv4->max_prefix_len;
+ pfxr->socket = rtr_socket;
+ } else if (type == IPV6_PREFIX) {
+ const struct pdu_ipv6 *ipv6 = pdu;
+
+ pfxr->asn = ipv6->asn;
+ pfxr->prefix.ver = LRTR_IPV6;
+ memcpy(pfxr->prefix.u.addr6.addr, ipv6->prefix, sizeof(pfxr->prefix.u.addr6.addr));
+ pfxr->min_len = ipv6->prefix_len;
+ pfxr->max_len = ipv6->max_prefix_len;
+ pfxr->socket = rtr_socket;
+ }
+}
+
+/*
+ * @brief Removes all Prefix from the pfx_table with flag field == ADD, ADDs all Prefix PDU to the pfx_table with flag
+ * field == REMOVE.
+ */
+static int rtr_undo_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, void *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+ struct pfx_record pfxr;
+
+ rtr_prefix_pdu_2_pfx_record(rtr_socket, pdu, &pfxr, type);
+
+ int rtval = RTR_ERROR;
+ // invert add/remove operation
+ if (((struct pdu_ipv4 *)pdu)->flags == 1)
+ rtval = pfx_table_remove(pfx_table, &pfxr);
+ else if (((struct pdu_ipv4 *)pdu)->flags == 0)
+ rtval = pfx_table_add(pfx_table, &pfxr);
+ return rtval;
+}
+
+/*
+ * @brief Removes router_key from the spki_table with flag field == ADD, ADDs router_key PDU to the spki_table with flag
+ * field == REMOVE.
+ */
+static int rtr_undo_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, void *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ assert(type == ROUTER_KEY);
+
+ struct spki_record entry;
+
+ rtr_key_pdu_2_spki_record(rtr_socket, pdu, &entry, type);
+
+ int rtval = RTR_ERROR;
+ // invert add/remove operation
+ if (((struct pdu_router_key *)pdu)->flags == 1)
+ rtval = spki_table_remove_entry(spki_table, &entry);
+ else if (((struct pdu_router_key *)pdu)->flags == 0)
+ rtval = spki_table_add_entry(spki_table, &entry);
+ return rtval;
+}
+
+/*
+ * @brief Appends the Prefix PDU pdu to ary.
+ *
+ * @return RTR_SUCCESS On success
+ * @return RTR_ERROR On realloc failure
+ * @attention ary is not freed in this case, because it might contain data that is still needed
+ */
+static int rtr_store_prefix_pdu(struct rtr_socket *rtr_socket, const void *pdu, const unsigned int pdu_size, void **ary,
+ unsigned int *ind, unsigned int *size)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+ if ((*ind) >= *size) {
+ *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE;
+ void *tmp = lrtr_realloc(*ary, *size * pdu_size);
+
+ if (!tmp) {
+ const char txt[] = "Realloc failed";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ *ary = tmp;
+ }
+ if (type == IPV4_PREFIX) {
+ struct pdu_ipv4 *ary_ipv4 = *ary;
+
+ memcpy(ary_ipv4 + *ind, pdu, pdu_size);
+ } else if (type == IPV6_PREFIX) {
+ struct pdu_ipv6 *ary_ipv6 = *ary;
+
+ memcpy(ary_ipv6 + *ind, pdu, pdu_size);
+ }
+ (*ind)++;
+ return RTR_SUCCESS;
+}
+
+/*
+ * @brief Appends the router key to ary.
+ *
+ * @return RTR_SUCCESS On success
+ * @return RTR_ERROR On realloc failure
+ * @attention ary is not freed in this case, because it might contain data that is still needed
+ */
+static int rtr_store_router_key_pdu(struct rtr_socket *rtr_socket, const void *pdu, const unsigned int pdu_size,
+ struct pdu_router_key **ary, unsigned int *ind, unsigned int *size)
+{
+ assert(rtr_get_pdu_type(pdu) == ROUTER_KEY);
+
+ if ((*ind) >= *size) {
+ *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE;
+ void *tmp = lrtr_realloc(*ary, *size * pdu_size);
+
+ if (!tmp) {
+ const char txt[] = "Realloc failed";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+ *ary = tmp;
+ }
+
+ memcpy((struct pdu_router_key *)*ary + *ind, pdu, pdu_size);
+ (*ind)++;
+ return RTR_SUCCESS;
+}
+
+static int rtr_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, const void *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ assert(type == IPV4_PREFIX || type == IPV6_PREFIX);
+
+ struct pfx_record pfxr;
+ size_t pdu_size = (type == IPV4_PREFIX ? sizeof(struct pdu_ipv4) : sizeof(struct pdu_ipv6));
+
+ rtr_prefix_pdu_2_pfx_record(rtr_socket, pdu, &pfxr, type);
+
+ int rtval;
+
+ if (((struct pdu_ipv4 *)pdu)->flags == 1) {
+ rtval = pfx_table_add(pfx_table, &pfxr);
+ } else if (((struct pdu_ipv4 *)pdu)->flags == 0) {
+ rtval = pfx_table_remove(pfx_table, &pfxr);
+ } else {
+ const char txt[] = "Prefix PDU with invalid flags value received";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt));
+ return RTR_ERROR;
+ }
+
+ if (rtval == PFX_DUPLICATE_RECORD) {
+ char ip[INET6_ADDRSTRLEN];
+
+ lrtr_ip_addr_to_str(&(pfxr.prefix), ip, INET6_ADDRSTRLEN);
+ RTR_DBG("Duplicate Announcement for record: %s/%u-%u, ASN: %u, received", ip, pfxr.min_len,
+ pfxr.max_len, pfxr.asn);
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, DUPLICATE_ANNOUNCEMENT, NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == PFX_RECORD_NOT_FOUND) {
+ RTR_DBG1("Withdrawal of unknown record");
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, WITHDRAWAL_OF_UNKNOWN_RECORD, NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == PFX_ERROR) {
+ const char txt[] = "PFX_TABLE Error";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+
+ return RTR_SUCCESS;
+}
+
+static int rtr_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, const void *pdu)
+{
+ const enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ assert(type == ROUTER_KEY);
+
+ struct spki_record entry;
+
+ size_t pdu_size = sizeof(struct pdu_router_key);
+
+ rtr_key_pdu_2_spki_record(rtr_socket, pdu, &entry, type);
+
+ int rtval;
+
+ if (((struct pdu_router_key *)pdu)->flags == 1) {
+ rtval = spki_table_add_entry(spki_table, &entry);
+
+ } else if (((struct pdu_router_key *)pdu)->flags == 0) {
+ rtval = spki_table_remove_entry(spki_table, &entry);
+
+ } else {
+ const char txt[] = "Router Key PDU with invalid flags value received";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt));
+ return RTR_ERROR;
+ }
+
+ if (rtval == SPKI_DUPLICATE_RECORD) {
+ // TODO: This debug message isn't working yet, how to display SKI/SPKI without %x?
+ RTR_DBG("Duplicate Announcement for router key: ASN: %u received", entry.asn);
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, DUPLICATE_ANNOUNCEMENT, NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == SPKI_RECORD_NOT_FOUND) {
+ RTR_DBG1("Withdrawal of unknown router key");
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, WITHDRAWAL_OF_UNKNOWN_RECORD, NULL, 0);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ } else if (rtval == SPKI_ERROR) {
+ const char txt[] = "spki_table Error";
+
+ RTR_DBG("%s", txt);
+ rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt));
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ return RTR_ERROR;
+ }
+
+ return RTR_SUCCESS;
+}
+
+void recv_loop_cleanup(void *p)
+{
+ struct recv_loop_cleanup_args *args = p;
+
+ lrtr_free(args->ipv4_pdus);
+ lrtr_free(args->ipv6_pdus);
+ lrtr_free(args->router_key_pdus);
+}
+
+/* WARNING: This Function has cancelable sections*/
+static int rtr_sync_receive_and_store_pdus(struct rtr_socket *rtr_socket)
+{
+ char pdu[RTR_MAX_PDU_LEN];
+ enum pdu_type type;
+ int retval = RTR_SUCCESS;
+
+ struct pdu_ipv6 *ipv6_pdus = NULL;
+ unsigned int ipv6_pdus_nindex = 0; // next free index in ipv6_pdus
+ unsigned int ipv6_pdus_size = 0;
+
+ struct pdu_ipv4 *ipv4_pdus = NULL;
+ unsigned int ipv4_pdus_size = 0;
+ unsigned int ipv4_pdus_nindex = 0; // next free index in ipv4_pdus
+
+ struct pdu_router_key *router_key_pdus = NULL;
+ unsigned int router_key_pdus_size = 0;
+ unsigned int router_key_pdus_nindex = 0;
+
+ struct pfx_table *pfx_shadow_table = NULL;
+ struct spki_table *spki_shadow_table = NULL;
+
+ int oldcancelstate;
+ struct recv_loop_cleanup_args cleanup_args = {
+ .ipv4_pdus = ipv4_pdus, .ipv6_pdus = ipv6_pdus, .router_key_pdus = router_key_pdus};
+
+ // receive LRTR_IPV4/IPV6 PDUs till EOD
+ do {
+ pthread_cleanup_push(recv_loop_cleanup, &cleanup_args);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ retval = rtr_receive_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, RTR_RECV_TIMEOUT);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+ pthread_cleanup_pop(0);
+
+ if (retval == TR_WOULDBLOCK) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ retval = RTR_ERROR;
+ goto cleanup;
+ } else if (retval < 0) {
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ type = rtr_get_pdu_type(pdu);
+ if (type == IPV4_PREFIX) {
+ if (rtr_store_prefix_pdu(rtr_socket, pdu, sizeof(*ipv4_pdus), (void **)&ipv4_pdus,
+ &ipv4_pdus_nindex, &ipv4_pdus_size) == RTR_ERROR) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ } else if (type == IPV6_PREFIX) {
+ if (rtr_store_prefix_pdu(rtr_socket, pdu, sizeof(*ipv6_pdus), (void **)&ipv6_pdus,
+ &ipv6_pdus_nindex, &ipv6_pdus_size) == RTR_ERROR) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ } else if (type == ROUTER_KEY) {
+ if (rtr_store_router_key_pdu(rtr_socket, pdu, sizeof(*router_key_pdus), &router_key_pdus,
+ &router_key_pdus_nindex, &router_key_pdus_size) == RTR_ERROR) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ } else if (type == EOD) {
+ RTR_DBG1("EOD PDU received.");
+ struct pdu_end_of_data_v0 *eod_pdu = (struct pdu_end_of_data_v0 *)pdu;
+
+ if (eod_pdu->session_id != rtr_socket->session_id) {
+ char txt[67];
+
+ snprintf(txt, sizeof(txt),
+ "Expected session_id: %u, received session_id. %u in EOD PDU",
+ rtr_socket->session_id, eod_pdu->session_id);
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, RTR_MAX_PDU_LEN, CORRUPT_DATA, txt,
+ strlen(txt) + 1);
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ if (eod_pdu->ver == RTR_PROTOCOL_VERSION_1 &&
+ rtr_socket->iv_mode != RTR_INTERVAL_MODE_IGNORE_ANY) {
+ int interv_retval;
+
+ interv_retval =
+ rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode,
+ ((struct pdu_end_of_data_v1 *)pdu)->expire_interval,
+ RTR_INTERVAL_TYPE_EXPIRATION);
+
+ if (interv_retval == RTR_ERROR) {
+ interval_send_error_pdu(rtr_socket, pdu,
+ ((struct pdu_end_of_data_v1 *)pdu)->expire_interval,
+ RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ interv_retval =
+ rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode,
+ ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval,
+ RTR_INTERVAL_TYPE_REFRESH);
+
+ if (interv_retval == RTR_ERROR) {
+ interval_send_error_pdu(rtr_socket, pdu,
+ ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval,
+ RTR_REFRESH_MIN, RTR_REFRESH_MAX);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ interv_retval = rtr_check_interval_option(
+ rtr_socket, rtr_socket->iv_mode,
+ ((struct pdu_end_of_data_v1 *)pdu)->retry_interval, RTR_INTERVAL_TYPE_RETRY);
+
+ if (interv_retval == RTR_ERROR) {
+ interval_send_error_pdu(rtr_socket, pdu,
+ ((struct pdu_end_of_data_v1 *)pdu)->retry_interval,
+ RTR_RETRY_MIN, RTR_RETRY_MAX);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ RTR_DBG("New interval values: expire_interval:%u, refresh_interval:%u, retry_interval:%u",
+ rtr_socket->expire_interval, rtr_socket->refresh_interval,
+ rtr_socket->retry_interval);
+ }
+
+ struct pfx_table *pfx_update_table;
+ struct spki_table *spki_update_table;
+
+ if (rtr_socket->is_resetting) {
+ RTR_DBG1("Reset in progress creating shadow table for atomic reset");
+ pfx_shadow_table = lrtr_malloc(sizeof(struct pfx_table));
+ if (!pfx_shadow_table) {
+ RTR_DBG1("Memory allocation for pfx shadow table failed");
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ pfx_table_init(pfx_shadow_table, NULL);
+ pfx_update_table = pfx_shadow_table;
+ if (pfx_table_copy_except_socket(rtr_socket->pfx_table, pfx_update_table, rtr_socket)) {
+ RTR_DBG1("Creation of pfx shadow table failed");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ spki_shadow_table = lrtr_malloc(sizeof(struct spki_table));
+ if (!spki_shadow_table) {
+ RTR_DBG1("Memory allocation for spki shadow table failed");
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ spki_table_init(spki_shadow_table, NULL);
+ spki_update_table = spki_shadow_table;
+ if (spki_table_copy_except_socket(rtr_socket->spki_table, spki_update_table,
+ rtr_socket) != SPKI_SUCCESS) {
+ RTR_DBG1("Creation of spki shadow table failed");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+
+ RTR_DBG1("Shadow table created");
+ } else {
+ pfx_update_table = rtr_socket->pfx_table;
+ spki_update_table = rtr_socket->spki_table;
+ }
+
+ retval = PFX_SUCCESS;
+ // add all IPv4 prefix pdu to the pfx_table
+ for (unsigned int i = 0; i < ipv4_pdus_nindex; i++) {
+ if (rtr_update_pfx_table(rtr_socket, pfx_update_table, &(ipv4_pdus[i])) == PFX_ERROR) {
+ // undo all record updates, except the last which produced the error
+ RTR_DBG("Error during data synchronisation, recovering Serial Nr. %u state",
+ rtr_socket->serial_number);
+ for (unsigned int j = 0; j < i && retval == PFX_SUCCESS; j++)
+ retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table,
+ &(ipv4_pdus[j]));
+ if (retval == RTR_ERROR) {
+ RTR_DBG1(
+ "Couldn't undo all update operations from failed data synchronisation: Purging all records");
+ pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket);
+ rtr_socket->request_session_id = true;
+ }
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ }
+ RTR_DBG1("v4 prefixes added");
+ // add all IPv6 prefix pdu to the pfx_table
+ for (unsigned int i = 0; i < ipv6_pdus_nindex; i++) {
+ if (rtr_update_pfx_table(rtr_socket, pfx_update_table, &(ipv6_pdus[i])) == PFX_ERROR) {
+ // undo all record updates if error occurred
+ RTR_DBG("Error during data synchronisation, recovering Serial Nr. %u state",
+ rtr_socket->serial_number);
+ for (unsigned int j = 0; j < ipv4_pdus_nindex && retval == PFX_SUCCESS; j++)
+ retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table,
+ &(ipv4_pdus[j]));
+ for (unsigned int j = 0; j < i && retval == PFX_SUCCESS; j++)
+ retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table,
+ &(ipv6_pdus[j]));
+ if (retval == PFX_ERROR) {
+ RTR_DBG1(
+ "Couldn't undo all update operations from failed data synchronisation: Purging all records");
+ pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket);
+ rtr_socket->request_session_id = true;
+ }
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ }
+
+ RTR_DBG1("v6 prefixes added");
+ // add all router key pdu to the spki_table
+ for (unsigned int i = 0; i < router_key_pdus_nindex; i++) {
+ if (rtr_update_spki_table(rtr_socket, spki_update_table, &(router_key_pdus[i])) ==
+ SPKI_ERROR) {
+ RTR_DBG("Error during router key data synchronisation, recovering Serial Nr. %u state",
+ rtr_socket->serial_number);
+ for (unsigned int j = 0; j < ipv4_pdus_nindex && retval == PFX_SUCCESS; j++)
+ retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table,
+ &(ipv4_pdus[j]));
+ for (unsigned int j = 0; j < ipv6_pdus_nindex && retval == PFX_SUCCESS; j++)
+ retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table,
+ &(ipv6_pdus[j]));
+ for (unsigned int j = 0;
+ // cppcheck-suppress duplicateExpression
+ j < i && (retval == PFX_SUCCESS || retval == SPKI_SUCCESS); j++)
+ retval = rtr_undo_update_spki_table(rtr_socket, spki_update_table,
+ &(router_key_pdus[j]));
+ // cppcheck-suppress duplicateExpression
+ if (retval == RTR_ERROR || retval == SPKI_ERROR) {
+ RTR_DBG1(
+ "Couldn't undo all update operations from failed data synchronisation: Purging all key entries");
+ spki_table_src_remove(spki_update_table, rtr_socket);
+ rtr_socket->request_session_id = true;
+ }
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ }
+ RTR_DBG1("spki data added");
+ if (rtr_socket->is_resetting) {
+ RTR_DBG1("Reset finished. Swapping new table in.");
+ pfx_table_swap(rtr_socket->pfx_table, pfx_shadow_table);
+ spki_table_swap(rtr_socket->spki_table, spki_shadow_table);
+
+ if (rtr_socket->pfx_table->update_fp) {
+ RTR_DBG1("Calculating and notifying pfx diff");
+ pfx_table_notify_diff(rtr_socket->pfx_table, pfx_shadow_table, rtr_socket);
+ } else {
+ RTR_DBG1("No pfx update callback. Skipping diff");
+ }
+
+ if (rtr_socket->spki_table->update_fp) {
+ RTR_DBG1("Calculating and notifying spki diff");
+ spki_table_notify_diff(rtr_socket->spki_table, spki_shadow_table, rtr_socket);
+ } else {
+ RTR_DBG1("No spki update callback. Skipping diff");
+ }
+ }
+
+ rtr_socket->serial_number = eod_pdu->sn;
+ RTR_DBG("Sync successful, received %u Prefix PDUs, %u Router Key PDUs, session_id: %u, SN: %u",
+ (ipv4_pdus_nindex + ipv6_pdus_nindex), router_key_pdus_nindex, rtr_socket->session_id,
+ rtr_socket->serial_number);
+ goto cleanup;
+ } else if (type == ERROR) {
+ rtr_handle_error_pdu(rtr_socket, pdu);
+ retval = RTR_ERROR;
+ goto cleanup;
+ } else if (type == SERIAL_NOTIFY) {
+ RTR_DBG1("Ignoring Serial Notify");
+ } else {
+ RTR_DBG("Received unexpected PDU (Type: %u)", ((struct pdu_header *)pdu)->type);
+ const char txt[] = "Unexpected PDU received during data synchronisation";
+
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, sizeof(struct pdu_header), CORRUPT_DATA, txt,
+ sizeof(txt));
+ retval = RTR_ERROR;
+ goto cleanup;
+ }
+ } while (type != EOD);
+
+cleanup:
+
+ if (rtr_socket->is_resetting) {
+ RTR_DBG1("Freeing shadow tables.");
+ if (pfx_shadow_table) {
+ pfx_table_free_without_notify(pfx_shadow_table);
+ lrtr_free(pfx_shadow_table);
+ }
+
+ if (spki_shadow_table) {
+ spki_table_free_without_notify(spki_shadow_table);
+ lrtr_free(spki_shadow_table);
+ }
+ rtr_socket->is_resetting = false;
+ }
+
+ lrtr_free(router_key_pdus);
+ lrtr_free(ipv6_pdus);
+ lrtr_free(ipv4_pdus);
+ return retval;
+}
+
+/* WARNING: This Function has cancelable sections */
+int rtr_sync(struct rtr_socket *rtr_socket)
+{
+ char pdu[RTR_MAX_PDU_LEN];
+ enum pdu_type type;
+
+ int oldcancelstate;
+
+ do {
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ int rtval = rtr_receive_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, RTR_RECV_TIMEOUT);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+ // If the cache has closed the connection and we don't have a
+ // session_id (no packages where exchanged) we should downgrade.
+ if (rtval == TR_CLOSED && rtr_socket->request_session_id) {
+ RTR_DBG1("The cache server closed the connection and we have no session_id!");
+ if (rtr_socket->version > RTR_PROTOCOL_MIN_SUPPORTED_VERSION) {
+ RTR_DBG("Downgrading from %i to version %i", rtr_socket->version,
+ rtr_socket->version - 1);
+ rtr_socket->version = rtr_socket->version - 1;
+ rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT);
+ return RTR_ERROR;
+ }
+ }
+
+ if (rtval == TR_WOULDBLOCK) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ } else if (rtval < 0) {
+ return RTR_ERROR;
+ }
+
+ type = rtr_get_pdu_type(pdu);
+ if (type == SERIAL_NOTIFY)
+ RTR_DBG1("Ignoring Serial Notify");
+
+ } while (type == SERIAL_NOTIFY);
+
+ switch (type) {
+ case ERROR:
+ rtr_handle_error_pdu(rtr_socket, pdu);
+ return RTR_ERROR;
+ case CACHE_RESET:
+ RTR_DBG1("Cache Reset PDU received");
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_INCR_UPDATE_AVAIL);
+ return RTR_ERROR;
+ case CACHE_RESPONSE:
+ rtr_handle_cache_response_pdu(rtr_socket, pdu);
+ break;
+ default:
+ RTR_DBG("Expected Cache Response PDU but received PDU Type (Type: %u)",
+ ((struct pdu_header *)pdu)->type);
+ const char txt[] = "Unexpected PDU received in data synchronisation";
+
+ rtr_send_error_pdu_from_host(rtr_socket, pdu, sizeof(struct pdu_header), CORRUPT_DATA, txt,
+ sizeof(txt));
+ return RTR_ERROR;
+ }
+
+ // Receive all PDUs until EOD PDU
+ if (rtr_sync_receive_and_store_pdus(rtr_socket) == RTR_ERROR)
+ return RTR_ERROR;
+
+ rtr_socket->request_session_id = false;
+ if (rtr_set_last_update(rtr_socket) == RTR_ERROR)
+ return RTR_ERROR;
+
+ return RTR_SUCCESS;
+}
+
+int rtr_wait_for_sync(struct rtr_socket *rtr_socket)
+{
+ char pdu[RTR_MAX_PDU_LEN];
+
+ time_t cur_time;
+
+ lrtr_get_monotonic_time(&cur_time);
+ time_t wait = (rtr_socket->last_update + rtr_socket->refresh_interval) - cur_time;
+
+ if (wait < 0)
+ wait = 0;
+
+ RTR_DBG("waiting %jd sec. till next sync", (intmax_t)wait);
+ const int rtval = rtr_receive_pdu(rtr_socket, pdu, sizeof(pdu), wait);
+
+ if (rtval >= 0) {
+ enum pdu_type type = rtr_get_pdu_type(pdu);
+
+ if (type == SERIAL_NOTIFY) {
+ RTR_DBG("Serial Notify received (%u)", ((struct pdu_serial_notify *)pdu)->sn);
+ return RTR_SUCCESS;
+ }
+ } else if (rtval == TR_WOULDBLOCK) {
+ RTR_DBG1("Refresh interval expired");
+ return RTR_SUCCESS;
+ }
+ return RTR_ERROR;
+}
+
+static int rtr_send_error_pdu(const struct rtr_socket *rtr_socket, const void *erroneous_pdu,
+ const uint32_t erroneous_pdu_len, const enum pdu_error_type error, const char *err_text,
+ const uint32_t err_text_len)
+{
+ struct pdu_error *err_pdu;
+ unsigned int msg_size = sizeof(struct pdu_error) + 4 + erroneous_pdu_len + err_text_len;
+ uint8_t msg[msg_size];
+
+ // don't send errors for erroneous error PDUs
+ if (erroneous_pdu_len >= 2) {
+ if (rtr_get_pdu_type(erroneous_pdu) == ERROR) {
+ RTR_DBG1("Don't send errors for erroneous error PDUs");
+ return RTR_SUCCESS;
+ }
+ }
+
+ err_pdu = (struct pdu_error *)msg;
+ err_pdu->ver = rtr_socket->version;
+ err_pdu->type = ERROR;
+ err_pdu->error_code = error;
+ err_pdu->len = msg_size;
+
+ err_pdu->len_enc_pdu = erroneous_pdu_len;
+ if (erroneous_pdu_len > 0)
+ memcpy(err_pdu->rest, erroneous_pdu, erroneous_pdu_len);
+
+ *((uint32_t *)(err_pdu->rest + erroneous_pdu_len)) = err_text_len;
+ if (err_text_len > 0)
+ memcpy(err_pdu->rest + erroneous_pdu_len + 4, err_text, err_text_len);
+
+ return rtr_send_pdu(rtr_socket, msg, msg_size);
+}
+
+static int interval_send_error_pdu(struct rtr_socket *rtr_socket, void *pdu, uint32_t interval, uint16_t minimum,
+ uint32_t maximum)
+{
+ RTR_DBG("Received expiration value out of range. Was %u, must be between %u and %u.", interval, minimum,
+ maximum);
+ const char txt[] = "Interval value out of range";
+
+ return rtr_send_error_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, CORRUPT_DATA, txt, strlen(txt) + 1);
+}
+
+static int rtr_send_error_pdu_from_network(const struct rtr_socket *rtr_socket, const void *erroneous_pdu,
+ const uint32_t erroneous_pdu_len, const enum pdu_error_type error,
+ const char *err_text, const uint32_t err_text_len)
+{
+ return rtr_send_error_pdu(rtr_socket, erroneous_pdu, erroneous_pdu_len, error, err_text, err_text_len);
+}
+
+static int rtr_send_error_pdu_from_host(const struct rtr_socket *rtr_socket, const void *erroneous_pdu,
+ const uint32_t erroneous_pdu_len, const enum pdu_error_type error,
+ const char *err_text, const uint32_t err_text_len)
+{
+ char pdu[erroneous_pdu_len];
+
+ memcpy(&pdu, erroneous_pdu, erroneous_pdu_len);
+
+ if (erroneous_pdu_len == sizeof(struct pdu_header))
+ rtr_pdu_header_to_network_byte_order(&pdu);
+ else if (erroneous_pdu_len > sizeof(struct pdu_header))
+ rtr_pdu_to_network_byte_order(&pdu);
+ else
+ return RTR_ERROR;
+
+ return rtr_send_error_pdu(rtr_socket, &pdu, erroneous_pdu_len, error, err_text, err_text_len);
+}
+
+int rtr_send_serial_query(struct rtr_socket *rtr_socket)
+{
+ struct pdu_serial_query pdu;
+
+ pdu.ver = rtr_socket->version;
+ pdu.type = SERIAL_QUERY;
+ pdu.session_id = rtr_socket->session_id;
+ pdu.len = sizeof(pdu);
+ pdu.sn = rtr_socket->serial_number;
+
+ RTR_DBG("sending serial query, SN: %u", rtr_socket->serial_number);
+ if (rtr_send_pdu(rtr_socket, &pdu, sizeof(pdu)) != RTR_SUCCESS) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ }
+ return RTR_SUCCESS;
+}
+
+int rtr_send_reset_query(struct rtr_socket *rtr_socket)
+{
+ RTR_DBG1("Sending reset query");
+ struct pdu_reset_query pdu;
+
+ pdu.ver = rtr_socket->version;
+ pdu.type = 2;
+ pdu.flags = 0;
+ pdu.len = 8;
+
+ if (rtr_send_pdu(rtr_socket, &pdu, sizeof(pdu)) != RTR_SUCCESS) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ return RTR_ERROR;
+ }
+ return RTR_SUCCESS;
+}
diff --git a/rtrlib/rtr/packets_private.h b/rtrlib/rtr/packets_private.h
new file mode 100644
index 0000000..c8d5093
--- /dev/null
+++ b/rtrlib/rtr/packets_private.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef RTR_PACKETS_PRIVATE_H
+#define RTR_PACKETS_PRIVATE_H
+
+#include "rtrlib/rtr/rtr_private.h"
+
+#include <arpa/inet.h>
+
+// error pdu: header(8) + len(4) + ipv6_pdu(32) + len(4) + 400*8 (400 char text)
+static const unsigned int RTR_MAX_PDU_LEN = 3248;
+static const unsigned int RTR_RECV_TIMEOUT = 60;
+static const unsigned int RTR_SEND_TIMEOUT = 60;
+
+void __attribute__((weak))
+rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state);
+int rtr_sync(struct rtr_socket *rtr_socket);
+int rtr_wait_for_sync(struct rtr_socket *rtr_socket);
+int rtr_send_serial_query(struct rtr_socket *rtr_socket);
+int rtr_send_reset_query(struct rtr_socket *rtr_socket);
+int rtr_check_interval_range(uint32_t interval, uint32_t minimum, uint32_t maximum);
+void apply_interval_value(struct rtr_socket *rtr_socket, uint32_t interval, enum rtr_interval_type type);
+int rtr_check_interval_option(struct rtr_socket *rtr_socket, int interval_mode, uint32_t interval,
+ enum rtr_interval_type type);
+#endif
diff --git a/rtrlib/rtr/rtr.c b/rtrlib/rtr/rtr.c
new file mode 100644
index 0000000..086c953
--- /dev/null
+++ b/rtrlib/rtr/rtr.c
@@ -0,0 +1,272 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "rtr_private.h"
+
+#include "rtrlib/lib/log_private.h"
+#include "rtrlib/lib/utils_private.h"
+#include "rtrlib/pfx/pfx_private.h"
+#include "rtrlib/rtr/packets_private.h"
+#include "rtrlib/rtrlib_export_private.h"
+#include "rtrlib/spki/hashtable/ht-spkitable_private.h"
+#include "rtrlib/transport/transport_private.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+#include <unistd.h>
+
+static void rtr_purge_outdated_records(struct rtr_socket *rtr_socket);
+static void *rtr_fsm_start(struct rtr_socket *rtr_socket);
+
+static const char *socket_str_states[] = {[RTR_CONNECTING] = "RTR_CONNECTING",
+ [RTR_ESTABLISHED] = "RTR_ESTABLISHED",
+ [RTR_RESET] = "RTR_RESET",
+ [RTR_SYNC] = "RTR_SYNC",
+ [RTR_FAST_RECONNECT] = "RTR_FAST_RECONNECT",
+ [RTR_ERROR_NO_DATA_AVAIL] = "RTR_ERROR_NO_DATA_AVAIL",
+ [RTR_ERROR_NO_INCR_UPDATE_AVAIL] = "RTR_ERROR_NO_INCR_UPDATE_AVAIL",
+ [RTR_ERROR_FATAL] = "RTR_ERROR_FATAL",
+ [RTR_ERROR_TRANSPORT] = "RTR_ERROR_TRANSPORT",
+ [RTR_SHUTDOWN] = "RTR_SHUTDOWN"};
+
+int rtr_init(struct rtr_socket *rtr_socket, struct tr_socket *tr, struct pfx_table *pfx_table,
+ struct spki_table *spki_table, const unsigned int refresh_interval, const unsigned int expire_interval,
+ const unsigned int retry_interval, enum rtr_interval_mode iv_mode, rtr_connection_state_fp fp,
+ void *fp_param_config, void *fp_param_group)
+{
+ if (tr)
+ rtr_socket->tr_socket = tr;
+
+ // Check if one of the intervals is not in range of the predefined values.
+ if (rtr_check_interval_range(refresh_interval, RTR_REFRESH_MIN, RTR_REFRESH_MAX) != RTR_INSIDE_INTERVAL_RANGE ||
+ rtr_check_interval_range(expire_interval, RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX) !=
+ RTR_INSIDE_INTERVAL_RANGE ||
+ rtr_check_interval_range(retry_interval, RTR_RETRY_MIN, RTR_RETRY_MAX) != RTR_INSIDE_INTERVAL_RANGE) {
+ RTR_DBG("Interval value not in range.");
+ return RTR_INVALID_PARAM;
+ }
+ rtr_socket->refresh_interval = refresh_interval;
+ rtr_socket->expire_interval = expire_interval;
+ rtr_socket->retry_interval = retry_interval;
+ rtr_socket->iv_mode = iv_mode;
+
+ rtr_socket->state = RTR_CLOSED;
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ rtr_socket->pfx_table = pfx_table;
+ rtr_socket->spki_table = spki_table;
+ rtr_socket->connection_state_fp = fp;
+ rtr_socket->connection_state_fp_param_config = fp_param_config;
+ rtr_socket->connection_state_fp_param_group = fp_param_group;
+ rtr_socket->thread_id = 0;
+ rtr_socket->version = RTR_PROTOCOL_MAX_SUPPORTED_VERSION;
+ rtr_socket->has_received_pdus = false;
+ rtr_socket->is_resetting = false;
+ return RTR_SUCCESS;
+}
+
+int rtr_start(struct rtr_socket *rtr_socket)
+{
+ if (rtr_socket->thread_id)
+ return RTR_ERROR;
+
+ int rtval = pthread_create(&(rtr_socket->thread_id), NULL, (void *(*)(void *)) &rtr_fsm_start, rtr_socket);
+
+ if (rtval == 0)
+ return RTR_SUCCESS;
+ return RTR_ERROR;
+}
+
+void rtr_purge_outdated_records(struct rtr_socket *rtr_socket)
+{
+ if (rtr_socket->last_update == 0)
+ return;
+ time_t cur_time;
+ int rtval = lrtr_get_monotonic_time(&cur_time);
+
+ if (rtval == -1 || (rtr_socket->last_update + rtr_socket->expire_interval) < cur_time) {
+ if (rtval == -1)
+ RTR_DBG1("get_monotic_time(..) failed");
+ pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket);
+ RTR_DBG1("Removed outdated records from pfx_table");
+ spki_table_src_remove(rtr_socket->spki_table, rtr_socket);
+ RTR_DBG1("Removed outdated router keys from spki_table");
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ rtr_socket->is_resetting = true;
+ }
+}
+
+/* WARNING: This Function has cancelable sections*/
+void *rtr_fsm_start(struct rtr_socket *rtr_socket)
+{
+ if (rtr_socket->state == RTR_SHUTDOWN)
+ return NULL;
+
+ // We don't care about the old state, but POSIX demands a non null value for setcancelstate
+ int oldcancelstate;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+
+ rtr_socket->state = RTR_CONNECTING;
+ while (1) {
+ if (rtr_socket->state == RTR_CONNECTING) {
+ RTR_DBG1("State: RTR_CONNECTING");
+ rtr_socket->has_received_pdus = false;
+
+ // old pfx_record could exists in the pfx_table, check if they are too old and must be removed
+ // old key_entry could exists in the spki_table, check if they are too old and must be removed
+ rtr_purge_outdated_records(rtr_socket);
+
+ if (tr_open(rtr_socket->tr_socket) == TR_ERROR) {
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT);
+ } else if (rtr_socket->request_session_id) {
+ // change to state RESET, if socket doesn't have a session_id
+ rtr_change_socket_state(rtr_socket, RTR_RESET);
+ } else {
+ // if we already have a session_id, send a serial query and start to sync
+ if (rtr_send_serial_query(rtr_socket) == RTR_SUCCESS)
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ else
+ rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL);
+ }
+ }
+
+ else if (rtr_socket->state == RTR_RESET) {
+ RTR_DBG1("State: RTR_RESET");
+ if (rtr_send_reset_query(rtr_socket) == RTR_SUCCESS) {
+ RTR_DBG1("rtr_start: reset pdu sent");
+ rtr_change_socket_state(rtr_socket,
+ RTR_SYNC); // start to sync after connection is established
+ }
+ }
+
+ else if (rtr_socket->state == RTR_SYNC) {
+ RTR_DBG1("State: RTR_SYNC");
+ if (rtr_sync(rtr_socket) == RTR_SUCCESS)
+ rtr_change_socket_state(
+ rtr_socket,
+ RTR_ESTABLISHED); // wait for next sync after first successful sync
+ }
+
+ else if (rtr_socket->state == RTR_ESTABLISHED) {
+ RTR_DBG1("State: RTR_ESTABLISHED");
+
+ // Allow thread cancellation for recv code path only.
+ // This should be enough since we spend most of the time blocking on recv
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ int ret = rtr_wait_for_sync(
+ rtr_socket); // blocks till expire_interval is expired or PDU was received
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+
+ if (ret == RTR_SUCCESS) { // send serial query
+ if (rtr_send_serial_query(rtr_socket) == RTR_SUCCESS)
+ rtr_change_socket_state(rtr_socket, RTR_SYNC);
+ }
+ }
+
+ else if (rtr_socket->state == RTR_FAST_RECONNECT) {
+ RTR_DBG1("State: RTR_FAST_RECONNECT");
+ tr_close(rtr_socket->tr_socket);
+ rtr_change_socket_state(rtr_socket, RTR_CONNECTING);
+ }
+
+ else if (rtr_socket->state == RTR_ERROR_NO_DATA_AVAIL) {
+ RTR_DBG1("State: RTR_ERROR_NO_DATA_AVAIL");
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_change_socket_state(rtr_socket, RTR_RESET);
+ sleep(rtr_socket->retry_interval);
+ rtr_purge_outdated_records(rtr_socket);
+ }
+
+ else if (rtr_socket->state == RTR_ERROR_NO_INCR_UPDATE_AVAIL) {
+ RTR_DBG1("State: RTR_ERROR_NO_INCR_UPDATE_AVAIL");
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_change_socket_state(rtr_socket, RTR_RESET);
+ rtr_purge_outdated_records(rtr_socket);
+ }
+
+ else if (rtr_socket->state == RTR_ERROR_TRANSPORT) {
+ RTR_DBG1("State: RTR_ERROR_TRANSPORT");
+ tr_close(rtr_socket->tr_socket);
+ rtr_change_socket_state(rtr_socket, RTR_CONNECTING);
+ RTR_DBG("Waiting %u", rtr_socket->retry_interval);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ sleep(rtr_socket->retry_interval);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+ }
+
+ else if (rtr_socket->state == RTR_ERROR_FATAL) {
+ RTR_DBG1("State: RTR_ERROR_FATAL");
+ tr_close(rtr_socket->tr_socket);
+ rtr_change_socket_state(rtr_socket, RTR_CONNECTING);
+ RTR_DBG("Waiting %u", rtr_socket->retry_interval);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ sleep(rtr_socket->retry_interval);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+ }
+
+ else if (rtr_socket->state == RTR_SHUTDOWN) {
+ RTR_DBG1("State: RTR_SHUTDOWN");
+ pthread_exit(NULL);
+ }
+ }
+}
+
+void rtr_stop(struct rtr_socket *rtr_socket)
+{
+ RTR_DBG("%s()", __func__);
+ rtr_change_socket_state(rtr_socket, RTR_SHUTDOWN);
+ if (rtr_socket->thread_id != 0) {
+ RTR_DBG1("pthread_cancel()");
+ pthread_cancel(rtr_socket->thread_id);
+ RTR_DBG1("pthread_join()");
+ pthread_join(rtr_socket->thread_id, NULL);
+
+ tr_close(rtr_socket->tr_socket);
+ rtr_socket->request_session_id = true;
+ rtr_socket->serial_number = 0;
+ rtr_socket->last_update = 0;
+ pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket);
+ spki_table_src_remove(rtr_socket->spki_table, rtr_socket);
+ rtr_socket->thread_id = 0;
+ }
+ RTR_DBG1("Socket shut down");
+}
+
+RTRLIB_EXPORT const char *rtr_state_to_str(enum rtr_socket_state state)
+{
+ return socket_str_states[state];
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT enum rtr_interval_mode rtr_get_interval_mode(struct rtr_socket *rtr_socket)
+{
+ return rtr_socket->iv_mode;
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT void rtr_set_interval_mode(struct rtr_socket *rtr_socket, enum rtr_interval_mode option)
+{
+ switch (option) {
+ case RTR_INTERVAL_MODE_IGNORE_ANY:
+ case RTR_INTERVAL_MODE_ACCEPT_ANY:
+ case RTR_INTERVAL_MODE_DEFAULT_MIN_MAX:
+ case RTR_INTERVAL_MODE_IGNORE_ON_FAILURE:
+ rtr_socket->iv_mode = option;
+ break;
+ default:
+ RTR_DBG1("Invalid interval mode. Mode remains unchanged.");
+ }
+}
diff --git a/rtrlib/rtr/rtr.h b/rtrlib/rtr/rtr.h
new file mode 100644
index 0000000..162dadc
--- /dev/null
+++ b/rtrlib/rtr/rtr.h
@@ -0,0 +1,161 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_rtr_h RTR socket
+ * @brief An RTR socket implements the RPKI-RTR protocol scheme.
+ * @details One rtr_socket communicates with a single RPKI-RTR server.
+ * @{
+ */
+
+#ifndef RTR_H
+#define RTR_H
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+enum rtr_rtvals { RTR_SUCCESS = 0, RTR_ERROR = -1, RTR_INVALID_PARAM = -2 };
+
+/**
+ * @brief These modes let the user configure how received intervals should be handled.
+ */
+enum rtr_interval_mode {
+ /** Ignore appliance of interval values at all. */
+ RTR_INTERVAL_MODE_IGNORE_ANY,
+
+ /** Accept any interval values, even if outside of range. */
+ RTR_INTERVAL_MODE_ACCEPT_ANY,
+
+ /** If interval value is outside of range, apply min (if below range) or max (if above range). */
+ RTR_INTERVAL_MODE_DEFAULT_MIN_MAX,
+
+ /** Ignore any interval values that are outside of range. */
+ RTR_INTERVAL_MODE_IGNORE_ON_FAILURE
+};
+
+/**
+ * @brief States of the RTR socket.
+ */
+enum rtr_socket_state {
+ /** Socket is establishing the transport connection. */
+ RTR_CONNECTING,
+
+ /** Connection is established,
+ * socket is waiting for a Serial Notify or expiration of the refresh_interval timer
+ */
+ RTR_ESTABLISHED,
+
+ /** Resetting RTR connection. */
+ RTR_RESET,
+
+ /** Receiving validation records from the RTR server. */
+ RTR_SYNC,
+
+ /** Reconnect without any waiting period */
+ RTR_FAST_RECONNECT,
+
+ /** No validation records are available on the RTR server. */
+ RTR_ERROR_NO_DATA_AVAIL,
+
+ /** Server was unable to answer the last serial or reset query. */
+ RTR_ERROR_NO_INCR_UPDATE_AVAIL,
+
+ /** Fatal protocol error occurred. */
+ RTR_ERROR_FATAL,
+
+ /** Error on the transport socket occurred. */
+ RTR_ERROR_TRANSPORT,
+
+ /** RTR Socket was started, but now has shut down. */
+ RTR_SHUTDOWN,
+
+ /** RTR Socket has not been started yet. Initial state after rtr_init */
+ RTR_CLOSED,
+};
+
+struct rtr_socket;
+
+/**
+ * @brief A function pointer that is called if the state of the rtr socket has changed.
+ */
+typedef void (*rtr_connection_state_fp)(const struct rtr_socket *rtr_socket, const enum rtr_socket_state state,
+ void *connection_state_fp_param_config, void *connection_state_fp_param_group);
+
+/**
+ * @brief A RTR socket.
+ * @param tr_socket Pointer to an initialized tr_socket that will be used to communicate with the RTR server.
+ * @param refresh_interval Time period in seconds. Tells the router how long to wait before next attempting
+ * to poll the cache, using a Serial Query or Reset Query PDU.
+ * @param last_update Timestamp of the last validation record update. Is 0 if the pfx_table doesn't store any
+ * validation records from this rtr_socket.
+ * @param expire_interval Time period in seconds. Received records are deleted if the client was unable to refresh data
+ * for this time period. If 0 is specified, the expire_interval is twice the refresh_interval.
+ * @param retry_interval Time period in seconds between a failed query and the next attempt.
+ * @param iv_mode Defines handling of incoming intervals.
+ * @param state Current state of the socket.
+ * @param session_id session_id of the RTR session.
+ * @param request_session_id True, if the rtr_client have to request a new none from the server.
+ * @param serial_number Last serial number of the obtained validation records.
+ * @param pfx_table pfx_table that stores the validation records obtained from the connected rtr server.
+ * @param thread_id Handle of the thread this socket is running in.
+ * @param connection_state_fp A callback function that is executed when the state of the socket changes.
+ * @param connection_state_fp_param_config Parameter that is passed to the connection_state_fp callback.
+ * Expects a pointer to a rtr_mgr_config struct.
+ * @param connection_state_fp_param_group Parameter that is passed to the connection_state_fp callback.
+ * Expects a pointer to the rtr_mgr_group this socket belongs to.
+ * @param version Protocol version used by this socket
+ * @param has_received_pdus True, if this socket has already received PDUs
+ * @param spki_table spki_table that stores the router keys obtained from the connected rtr server
+ */
+struct rtr_socket {
+ struct tr_socket *tr_socket;
+ unsigned int refresh_interval;
+ time_t last_update;
+ unsigned int expire_interval;
+ unsigned int retry_interval;
+ enum rtr_interval_mode iv_mode;
+ enum rtr_socket_state state;
+ uint32_t session_id;
+ bool request_session_id;
+ uint32_t serial_number;
+ struct pfx_table *pfx_table;
+ pthread_t thread_id;
+ rtr_connection_state_fp connection_state_fp;
+ void *connection_state_fp_param_config;
+ void *connection_state_fp_param_group;
+ unsigned int version;
+ bool has_received_pdus;
+ struct spki_table *spki_table;
+ bool is_resetting;
+};
+
+/**
+ * @brief Converts a rtr_socket_state to a String.
+ * @param[in] state state to convert to a string
+ * @return NULL If state isn't a valid rtr_socket_state
+ * @return !=NULL The rtr_socket_state as String.
+ */
+const char *rtr_state_to_str(enum rtr_socket_state state);
+
+/**
+ * @brief Set the interval option to the desired one. It's either RTR_INTERVAL_MODE_IGNORE_ANY,
+ * RTR_INTERVAL_MODE_APPLY_ANY, RTR_INTERVAL_MODE_DEFAULT_MIN_MAX or RTR_INTERVAL_MODE_IGNORE_ON_FAILURE.
+ * @param[in] rtr_socket The target socket.
+ * @param[in] option The new interval option that should be applied.
+ */
+void rtr_set_interval_mode(struct rtr_socket *rtr_socket, enum rtr_interval_mode option);
+
+/**
+ * @brief Get the current interval mode.
+ * @param[in] rtr_socket The target socket.
+ * @return The value of the interval_option variable.
+ */
+enum rtr_interval_mode rtr_get_interval_mode(struct rtr_socket *rtr_socket);
+#endif
+/** @} */
diff --git a/rtrlib/rtr/rtr_private.h b/rtrlib/rtr/rtr_private.h
new file mode 100644
index 0000000..3e49215
--- /dev/null
+++ b/rtrlib/rtr/rtr_private.h
@@ -0,0 +1,88 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef RTR_PRIVATE_H
+#define RTR_PRIVATE_H
+#include "rtrlib/rtr/rtr.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define RTR_DBG(fmt, ...) lrtr_dbg("RTR Socket: " fmt, ##__VA_ARGS__)
+#define RTR_DBG1(a) lrtr_dbg("RTR Socket: " a)
+
+static const uint32_t RTR_EXPIRATION_MIN = 600; // ten minutes
+static const uint32_t RTR_EXPIRATION_MAX = 172800; // two days
+static const uint32_t RTR_EXPIRATION_DEFAULT = 7200; // two hours
+
+static const uint32_t RTR_REFRESH_MIN = 1; // one second
+static const uint32_t RTR_REFRESH_MAX = 86400; // one day
+static const uint32_t RTR_REFRESH_DEFAULT = 3600; // one hour
+
+static const uint32_t RTR_RETRY_MIN = 1; // one second
+static const uint32_t RTR_RETRY_MAX = 7200; // two hours
+static const uint32_t RTR_RETRY_DEFAULT = 600; // ten minutes
+
+static const uint8_t RTR_PROTOCOL_VERSION_0; // = 0
+static const uint8_t RTR_PROTOCOL_VERSION_1 = 1;
+
+static const uint8_t RTR_PROTOCOL_MIN_SUPPORTED_VERSION; // = 0
+static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 1;
+
+enum rtr_interval_range { RTR_BELOW_INTERVAL_RANGE = -1, RTR_INSIDE_INTERVAL_RANGE = 0, RTR_ABOVE_INTERVAL_RANGE = 1 };
+
+enum rtr_interval_type { RTR_INTERVAL_TYPE_EXPIRATION, RTR_INTERVAL_TYPE_REFRESH, RTR_INTERVAL_TYPE_RETRY };
+
+/**
+ * @brief Initializes a rtr_socket.
+ * @param[out] rtr_socket Pointer to the allocated rtr_socket that will be initialized.
+ * @param[in] tr_socket Pointer to a tr_socket that will be used for the transport connection.
+ * If NULL the tr_socket element of the rtr_socket won't be changed.
+ * @param[in] pfx_table pfx_table that stores the validation records obtained from the connected rtr server.
+ * @param[in] spki_table spki_table that stores the router keys obtained from the connected rtr server.
+ * @param[in] refresh_interval Interval in seconds between serial queries that are sent to the server.
+ * Must be >= 1 and <= 86400 (one day), recommended default is 3600s (one hour).
+ * @param[in] expire_interval Stored validation records will be deleted
+ * if cache was unable to refresh data for this period.
+ * The value should be twice the refresh_interval. The value must be >= 600 (ten minutes) and <= 172800 (two days).
+ * The recommended default is 7200s (two hours).
+ * @param[in] retry_interval This parameter tells the router how long to wait (in seconds) before retrying
+ * a failed Serial Query or Reset Query. The value must be >= 1s and <= 7200s (two hours).
+ * The recommended default is 600 seconds (ten minutes).
+ * @param[in] iv_mode The interval mode that controls how new interval values are applied.
+ * @param[in] fp A callback function that is executed when the state of the socket changes.
+ * @param[in] fp_data_config Parameter that is passed to the connection_state_fp callback.
+ * Expects rtr_mgr_config.
+ * @param[in] fp_data_group Parameter that is passed to the connection_state_fp callback.
+ * Expects rtr_mgr_group.
+ * @return RTR_INVALID_PARAM If the refresh_interval or the expire_interval is not valid.
+ * @return RTR_SUCCESS On success.
+ */
+int rtr_init(struct rtr_socket *rtr_socket, struct tr_socket *tr_socket, struct pfx_table *pfx_table,
+ struct spki_table *spki_table, const unsigned int refresh_interval, const unsigned int expire_interval,
+ const unsigned int retry_interval, enum rtr_interval_mode iv_mode, rtr_connection_state_fp fp,
+ void *fp_data_config, void *fp_data_group);
+
+/**
+ * @brief Starts the RTR protocol state machine in a pthread. Connection to the rtr_server will be established and the
+ * pfx_records will be synced.
+ * @param[in] rtr_socket rtr_socket that will be used.
+ * @return RTR_ERROR On error.
+ * @return RTR_SUCCESS On success.
+ */
+int rtr_start(struct rtr_socket *rtr_socket);
+
+/**
+ * @brief Stops the RTR connection and terminate the transport connection.
+ * @param[in] rtr_socket rtr_socket that will be used.
+ */
+void rtr_stop(struct rtr_socket *rtr_socket);
+
+#endif
diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c
new file mode 100644
index 0000000..cfd1de6
--- /dev/null
+++ b/rtrlib/rtr_mgr.c
@@ -0,0 +1,675 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "rtr_mgr_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/log_private.h"
+#include "rtrlib/pfx/pfx_private.h"
+#include "rtrlib/rtr/rtr_private.h"
+#include "rtrlib/rtrlib_export_private.h"
+#include "rtrlib/spki/hashtable/ht-spkitable_private.h"
+#include "rtrlib/transport/transport_private.h"
+
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MGR_DBG(fmt, ...) lrtr_dbg("RTR_MGR: " fmt, ##__VA_ARGS__)
+#define MGR_DBG1(a) lrtr_dbg("RTR_MGR: " a)
+
+static const char *const mgr_str_status[] = {
+ [RTR_MGR_CLOSED] = "RTR_MGR_CLOSED",
+ [RTR_MGR_CONNECTING] = "RTR_MGR_CONNECTING",
+ [RTR_MGR_ESTABLISHED] = "RTR_MGR_ESTABLISHED",
+ [RTR_MGR_ERROR] = "RTR_MGR_ERROR",
+};
+
+static int rtr_mgr_config_cmp(const void *a, const void *b);
+static int rtr_mgr_config_cmp_tommy(const void *a, const void *b);
+static bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group);
+static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config,
+ void *data_group);
+
+static void set_status(const struct rtr_mgr_config *conf, struct rtr_mgr_group *group, enum rtr_mgr_status mgr_status,
+ const struct rtr_socket *rtr_sock)
+{
+ MGR_DBG("Group(%u) status changed to: %s", group->preference, rtr_mgr_status_to_str(mgr_status));
+
+ group->status = mgr_status;
+ if (conf->status_fp)
+ conf->status_fp(group, mgr_status, rtr_sock, conf->status_fp_data);
+}
+
+static int rtr_mgr_start_sockets(struct rtr_mgr_group *group)
+{
+ for (unsigned int i = 0; i < group->sockets_len; i++) {
+ if (rtr_start(group->sockets[i]) != 0) {
+ MGR_DBG1("rtr_mgr: Error starting rtr_socket pthread");
+ return RTR_ERROR;
+ }
+ }
+ group->status = RTR_MGR_CONNECTING;
+ return RTR_SUCCESS;
+}
+
+static int rtr_mgr_init_sockets(struct rtr_mgr_group *group, struct rtr_mgr_config *config,
+ const unsigned int refresh_interval, const unsigned int expire_interval,
+ const unsigned int retry_interval, enum rtr_interval_mode iv_mode)
+{
+ for (unsigned int i = 0; i < group->sockets_len; i++) {
+ enum rtr_rtvals err_code = rtr_init(group->sockets[i], NULL, config->pfx_table, config->spki_table,
+ refresh_interval, expire_interval, retry_interval, iv_mode,
+ rtr_mgr_cb, config, group);
+ if (err_code)
+ return err_code;
+ }
+ return RTR_SUCCESS;
+}
+
+bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group)
+{
+ for (unsigned int i = 0; i < group->sockets_len; i++) {
+ enum rtr_socket_state state = group->sockets[i]->state;
+
+ if ((group->sockets[i]->last_update == 0) ||
+ ((state != RTR_ESTABLISHED) && (state != RTR_RESET) && (state != RTR_SYNC)))
+ return false;
+ }
+ return true;
+}
+
+static void rtr_mgr_close_less_preferable_groups(const struct rtr_socket *sock, struct rtr_mgr_config *config,
+ struct rtr_mgr_group *group)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+ struct rtr_mgr_group *current_group = group_node->group;
+
+ if ((current_group->status != RTR_MGR_CLOSED) && (current_group != group) &&
+ (current_group->preference > group->preference)) {
+ for (unsigned int j = 0; j < current_group->sockets_len; j++)
+ rtr_stop(current_group->sockets[j]);
+ set_status(config, current_group, RTR_MGR_CLOSED, sock);
+ }
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+}
+
+static struct rtr_mgr_group *get_best_inactive_rtr_mgr_group(struct rtr_mgr_config *config, struct rtr_mgr_group *group)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+ struct rtr_mgr_group *current_group = group_node->group;
+
+ if ((current_group != group) && (current_group->status == RTR_MGR_CLOSED)) {
+ pthread_mutex_unlock(&config->mutex);
+ return current_group;
+ }
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+ return NULL;
+}
+
+static bool is_some_rtr_mgr_group_established(struct rtr_mgr_config *config)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+
+ if (group_node->group->status == RTR_MGR_ESTABLISHED) {
+ pthread_mutex_unlock(&config->mutex);
+ return true;
+ }
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+ return false;
+}
+
+static inline void _rtr_mgr_cb_state_shutdown(const struct rtr_socket *sock, struct rtr_mgr_config *config,
+ struct rtr_mgr_group *group)
+{
+ bool all_down = true;
+
+ for (unsigned int i = 0; i < group->sockets_len; i++) {
+ if (group->sockets[i]->state != RTR_SHUTDOWN) {
+ all_down = false;
+ break;
+ }
+ }
+ if (all_down)
+ set_status(config, group, RTR_MGR_CLOSED, sock);
+ else
+ set_status(config, group, group->status, sock);
+}
+
+static inline void _rtr_mgr_cb_state_established(const struct rtr_socket *sock, struct rtr_mgr_config *config,
+ struct rtr_mgr_group *group)
+{
+ /* socket established a connection to the rtr_server */
+ if (group->status == RTR_MGR_CONNECTING) {
+ /*
+ * if previous state was CONNECTING, check if all
+ * other sockets in the group also have a established
+ * connection, if yes change group state to ESTABLISHED
+ */
+ if (rtr_mgr_config_status_is_synced(group)) {
+ set_status(config, group, RTR_MGR_ESTABLISHED, sock);
+ rtr_mgr_close_less_preferable_groups(sock, config, group);
+ } else {
+ set_status(config, group, RTR_MGR_CONNECTING, sock);
+ }
+ } else if (group->status == RTR_MGR_ERROR) {
+ /* if previous state was ERROR, only change state to
+ * ESTABLISHED if all other more preferable socket
+ * groups are also in ERROR or SHUTDOWN state
+ */
+ bool all_error = true;
+
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+ struct rtr_mgr_group *current_group = group_node->group;
+
+ if ((current_group != group) && (current_group->status != RTR_MGR_ERROR) &&
+ (current_group->status != RTR_MGR_CLOSED) &&
+ (current_group->preference < group->preference)) {
+ all_error = false;
+ }
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+
+ if (all_error && rtr_mgr_config_status_is_synced(group)) {
+ set_status(config, group, RTR_MGR_ESTABLISHED, sock);
+ rtr_mgr_close_less_preferable_groups(sock, config, group);
+ } else {
+ set_status(config, group, RTR_MGR_ERROR, sock);
+ }
+ }
+}
+
+static inline void _rtr_mgr_cb_state_connecting(const struct rtr_socket *sock, struct rtr_mgr_config *config,
+ struct rtr_mgr_group *group)
+{
+ if (group->status == RTR_MGR_ERROR)
+ set_status(config, group, RTR_MGR_ERROR, sock);
+ else
+ set_status(config, group, RTR_MGR_CONNECTING, sock);
+}
+
+static inline void _rtr_mgr_cb_state_error(const struct rtr_socket *sock, struct rtr_mgr_config *config,
+ struct rtr_mgr_group *group)
+{
+ set_status(config, group, RTR_MGR_ERROR, sock);
+
+ if (!is_some_rtr_mgr_group_established(config)) {
+ struct rtr_mgr_group *next_group = get_best_inactive_rtr_mgr_group(config, group);
+
+ if (next_group)
+ rtr_mgr_start_sockets(next_group);
+ else
+ MGR_DBG1("No other inactive groups found");
+ }
+}
+
+static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config,
+ void *data_group)
+{
+ if (state == RTR_SHUTDOWN)
+ MGR_DBG1("Received RTR_SHUTDOWN callback");
+
+ struct rtr_mgr_config *config = data_config;
+ struct rtr_mgr_group *group = data_group;
+
+ if (!group) {
+ MGR_DBG1("ERROR: Socket has no group");
+ return;
+ }
+
+ switch (state) {
+ case RTR_SHUTDOWN:
+ _rtr_mgr_cb_state_shutdown(sock, config, group);
+ break;
+ case RTR_ESTABLISHED:
+ _rtr_mgr_cb_state_established(sock, config, group);
+ break;
+ case RTR_CONNECTING:
+ _rtr_mgr_cb_state_connecting(sock, config, group);
+ break;
+ case RTR_ERROR_FATAL:
+ case RTR_ERROR_TRANSPORT:
+ case RTR_ERROR_NO_DATA_AVAIL:
+ _rtr_mgr_cb_state_error(sock, config, group);
+ break;
+ default:
+ set_status(config, group, group->status, sock);
+ }
+}
+
+int rtr_mgr_config_cmp(const void *a, const void *b)
+{
+ const struct rtr_mgr_group *ar = a;
+ const struct rtr_mgr_group *br = b;
+
+ if (ar->preference > br->preference)
+ return 1;
+ else if (ar->preference < br->preference)
+ return -1;
+ return 0;
+}
+
+int rtr_mgr_config_cmp_tommy(const void *a, const void *b)
+{
+ const struct rtr_mgr_group_node *ar = a;
+ const struct rtr_mgr_group_node *br = b;
+
+ return rtr_mgr_config_cmp(ar->group, br->group);
+}
+
+RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[],
+ const unsigned int groups_len, const unsigned int refresh_interval,
+ const unsigned int expire_interval, const unsigned int retry_interval,
+ const pfx_update_fp update_fp, const spki_update_fp spki_update_fp,
+ const rtr_mgr_status_fp status_fp, void *status_fp_data)
+{
+ enum rtr_rtvals err_code = RTR_ERROR;
+ enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX;
+ struct pfx_table *pfxt = NULL;
+ struct spki_table *spki_table = NULL;
+ struct rtr_mgr_config *config = NULL;
+ struct rtr_mgr_group *cg = NULL;
+ struct rtr_mgr_group_node *group_node;
+ uint8_t last_preference = UINT8_MAX;
+
+ *config_out = NULL;
+
+ if (groups_len == 0) {
+ MGR_DBG1("Error Empty rtr_mgr_group array");
+ return RTR_ERROR;
+ }
+
+ *config_out = config = lrtr_malloc(sizeof(*config));
+ if (!config)
+ return RTR_ERROR;
+
+ config->len = groups_len;
+
+ if (pthread_mutex_init(&config->mutex, NULL) != 0) {
+ MGR_DBG1("Mutex initialization failed");
+ goto err;
+ }
+ /* sort array in asc order, so we can check for dupl. pref */
+ qsort(groups, groups_len, sizeof(struct rtr_mgr_group), &rtr_mgr_config_cmp);
+
+ /* Check that the groups have unique pref and at least one socket */
+ for (unsigned int i = 0; i < config->len; i++) {
+ if ((i > 0) && (groups[i].preference == last_preference)) {
+ MGR_DBG1("Error Same preference for 2 socket groups!");
+ goto err;
+ }
+ if (groups[i].sockets_len == 0) {
+ MGR_DBG1("Error Empty sockets array in socket group!");
+ goto err;
+ }
+
+ last_preference = groups[i].preference;
+ }
+
+ /* Init data structures that we need to pass to the sockets */
+ pfxt = lrtr_malloc(sizeof(*pfxt));
+ if (!pfxt)
+ goto err;
+ pfx_table_init(pfxt, update_fp);
+
+ spki_table = lrtr_malloc(sizeof(*spki_table));
+ if (!spki_table)
+ goto err;
+ spki_table_init(spki_table, spki_update_fp);
+
+ config->pfx_table = pfxt;
+ config->spki_table = spki_table;
+
+ /* Copy the groups from the array into linked list config->groups */
+ config->len = groups_len;
+ config->groups = lrtr_malloc(sizeof(*config->groups));
+ if (!config->groups)
+ goto err;
+ config->groups->list = NULL;
+
+ for (unsigned int i = 0; i < groups_len; i++) {
+ cg = lrtr_malloc(sizeof(struct rtr_mgr_group));
+ if (!cg)
+ goto err;
+
+ memcpy(cg, &groups[i], sizeof(struct rtr_mgr_group));
+
+ cg->status = RTR_MGR_CLOSED;
+ err_code = rtr_mgr_init_sockets(cg, config, refresh_interval, expire_interval, retry_interval, iv_mode);
+ if (err_code)
+ goto err;
+
+ group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node));
+ if (!group_node)
+ goto err;
+
+ group_node->group = cg;
+ tommy_list_insert_tail(&config->groups->list, &group_node->node, group_node);
+ }
+ /* Our linked list should be sorted already, since the groups array was
+ * sorted. However, for safety reasons we sort again.
+ */
+ tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy);
+
+ config->status_fp_data = status_fp_data;
+ config->status_fp = status_fp;
+ return RTR_SUCCESS;
+
+err:
+ if (spki_table)
+ spki_table_free(spki_table);
+ if (pfxt)
+ pfx_table_free(pfxt);
+ lrtr_free(pfxt);
+ lrtr_free(spki_table);
+
+ lrtr_free(cg);
+
+ lrtr_free(config->groups);
+ lrtr_free(config);
+ config = NULL;
+ *config_out = NULL;
+
+ return err_code;
+}
+
+RTRLIB_EXPORT struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config)
+{
+ tommy_node *head = tommy_list_head(&config->groups->list);
+ struct rtr_mgr_group_node *group_node = head->data;
+
+ return group_node->group;
+}
+
+RTRLIB_EXPORT int rtr_mgr_start(struct rtr_mgr_config *config)
+{
+ MGR_DBG("%s()", __func__);
+ struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config);
+
+ return rtr_mgr_start_sockets(best_group);
+}
+
+RTRLIB_EXPORT bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ bool all_sync = true;
+ struct rtr_mgr_group_node *group_node = node->data;
+
+ for (unsigned int j = 0; all_sync && (j < group_node->group->sockets_len); j++) {
+ if (group_node->group->sockets[j]->last_update == 0)
+ all_sync = false;
+ }
+ if (all_sync) {
+ pthread_mutex_unlock(&config->mutex);
+ return true;
+ }
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+ return false;
+}
+
+RTRLIB_EXPORT void rtr_mgr_free(struct rtr_mgr_config *config)
+{
+ MGR_DBG("%s()", __func__);
+ pthread_mutex_lock(&config->mutex);
+
+ pfx_table_free(config->pfx_table);
+ spki_table_free(config->spki_table);
+ lrtr_free(config->spki_table);
+ lrtr_free(config->pfx_table);
+
+ /* Free linked list */
+ tommy_node *head = tommy_list_head(&config->groups->list);
+
+ while (head) {
+ tommy_node *tmp = head;
+ struct rtr_mgr_group_node *group_node = tmp->data;
+
+ head = head->next;
+ for (unsigned int j = 0; j < group_node->group->sockets_len; j++)
+ tr_free(group_node->group->sockets[j]->tr_socket);
+
+ lrtr_free(group_node->group);
+ lrtr_free(group_node);
+ }
+
+ lrtr_free(config->groups);
+
+ pthread_mutex_unlock(&config->mutex);
+ pthread_mutex_destroy(&config->mutex);
+ lrtr_free(config);
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT inline int rtr_mgr_validate(struct rtr_mgr_config *config, const uint32_t asn,
+ const struct lrtr_ip_addr *prefix, const uint8_t mask_len,
+ enum pfxv_state *result)
+{
+ return pfx_table_validate(config->pfx_table, asn, prefix, mask_len, result);
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT inline int rtr_mgr_get_spki(struct rtr_mgr_config *config, const uint32_t asn, uint8_t *ski,
+ struct spki_record **result, unsigned int *result_count)
+{
+ return spki_table_get_all(config->spki_table, asn, ski, result, result_count);
+}
+
+RTRLIB_EXPORT void rtr_mgr_stop(struct rtr_mgr_config *config)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ MGR_DBG("%s()", __func__);
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+
+ for (unsigned int j = 0; j < group_node->group->sockets_len; j++)
+ rtr_stop(group_node->group->sockets[j]);
+ node = node->next;
+ }
+ pthread_mutex_unlock(&config->mutex);
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct rtr_mgr_group *group)
+{
+ unsigned int refresh_iv = 3600;
+ unsigned int retry_iv = 600;
+ unsigned int expire_iv = 7200;
+ enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX;
+ enum rtr_rtvals err_code = RTR_ERROR;
+ struct rtr_mgr_group_node *new_group_node = NULL;
+ struct rtr_mgr_group *new_group = NULL;
+ struct rtr_mgr_group_node *gnode;
+
+ pthread_mutex_lock(&config->mutex);
+
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ gnode = node->data;
+ if (gnode->group->preference == group->preference) {
+ MGR_DBG1("Group with preference value already exists!");
+ err_code = RTR_INVALID_PARAM;
+ goto err;
+ }
+
+ // TODO This is not pretty. It wants to get the same intervals
+ // that are being used by other groups. Maybe intervals should
+ // be store globally/per-group/per-socket?
+ if (gnode->group->sockets[0]->refresh_interval)
+ refresh_iv = gnode->group->sockets[0]->refresh_interval;
+ if (gnode->group->sockets[0]->retry_interval)
+ retry_iv = gnode->group->sockets[0]->retry_interval;
+ if (gnode->group->sockets[0]->expire_interval)
+ expire_iv = gnode->group->sockets[0]->expire_interval;
+ node = node->next;
+ }
+ new_group = lrtr_malloc(sizeof(struct rtr_mgr_group));
+
+ if (!new_group)
+ goto err;
+
+ memcpy(new_group, group, sizeof(struct rtr_mgr_group));
+ new_group->status = RTR_MGR_CLOSED;
+
+ err_code = rtr_mgr_init_sockets(new_group, config, refresh_iv, expire_iv, retry_iv, iv_mode);
+ if (err_code)
+ goto err;
+
+ new_group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node));
+ if (!new_group_node)
+ goto err;
+
+ new_group_node->group = new_group;
+ tommy_list_insert_tail(&config->groups->list, &new_group_node->node, new_group_node);
+ config->len++;
+
+ MGR_DBG("Group with preference %d successfully added!", new_group->preference);
+
+ tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy);
+
+ struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config);
+
+ if (best_group->status == RTR_MGR_CLOSED)
+ rtr_mgr_start_sockets(best_group);
+
+ pthread_mutex_unlock(&config->mutex);
+ return RTR_SUCCESS;
+
+err:
+ pthread_mutex_unlock(&config->mutex);
+
+ if (new_group)
+ lrtr_free(new_group);
+
+ return err_code;
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference)
+{
+ pthread_mutex_lock(&config->mutex);
+ tommy_node *remove_node = NULL;
+ tommy_node *node = tommy_list_head(&config->groups->list);
+ struct rtr_mgr_group_node *group_node;
+ struct rtr_mgr_group *remove_group;
+
+ if (config->len == 1) {
+ MGR_DBG1("Cannot remove last remaining group!");
+ pthread_mutex_unlock(&config->mutex);
+ return RTR_ERROR;
+ }
+
+ // Find the node of the group we want to remove
+ while (node && !remove_node) {
+ group_node = node->data;
+ if (group_node->group->preference == preference)
+ remove_node = node;
+ node = node->next;
+ }
+
+ if (!remove_node) {
+ MGR_DBG1("The group that should be removed does not exist!");
+ pthread_mutex_unlock(&config->mutex);
+ return RTR_ERROR;
+ }
+
+ group_node = remove_node->data;
+ remove_group = group_node->group;
+ tommy_list_remove_existing(&config->groups->list, remove_node);
+ config->len--;
+ MGR_DBG("Group with preference %d successfully removed!", preference);
+ // tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp);
+ pthread_mutex_unlock(&config->mutex);
+
+ // If group isn't closed, make it so!
+ if (remove_group->status != RTR_MGR_CLOSED) {
+ for (unsigned int j = 0; j < remove_group->sockets_len; j++) {
+ rtr_stop(remove_group->sockets[j]);
+ tr_free(remove_group->sockets[j]->tr_socket);
+ }
+ set_status(config, remove_group, RTR_MGR_CLOSED, NULL);
+ }
+
+ struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config);
+
+ if (best_group->status == RTR_MGR_CLOSED)
+ rtr_mgr_start_sockets(best_group);
+
+ lrtr_free(group_node->group);
+ lrtr_free(group_node);
+ return RTR_SUCCESS;
+}
+
+// TODO: write test for this function.
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT int rtr_mgr_for_each_group(struct rtr_mgr_config *config,
+ void(fp)(const struct rtr_mgr_group *group, void *data), void *data)
+{
+ tommy_node *node = tommy_list_head(&config->groups->list);
+
+ while (node) {
+ struct rtr_mgr_group_node *group_node = node->data;
+
+ fp(group_node->group, data);
+ node = node->next;
+ }
+
+ return RTR_SUCCESS;
+}
+
+RTRLIB_EXPORT const char *rtr_mgr_status_to_str(enum rtr_mgr_status status)
+{
+ return mgr_str_status[status];
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv4_record(struct rtr_mgr_config *config,
+ void(fp)(const struct pfx_record *, void *data), void *data)
+{
+ pfx_table_for_each_ipv4_record(config->pfx_table, fp, data);
+}
+
+/* cppcheck-suppress unusedFunction */
+RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *config,
+ void(fp)(const struct pfx_record *, void *data), void *data)
+{
+ pfx_table_for_each_ipv6_record(config->pfx_table, fp, data);
+}
diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h
new file mode 100644
index 0000000..b267723
--- /dev/null
+++ b/rtrlib/rtr_mgr.h
@@ -0,0 +1,261 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_rtr_mgr_h RTR connection manager
+ * @brief The RTR connection manager maintains multiple groups of @ref
+ * rtr_socket "RTR sockets".
+ * @details The RTR connection manager is initialized with one or multiple
+ * groups of rtr_sockets. Each group is configured with a preference
+ * value and contains a set of rtr_socket RTR sockets. It connects to all
+ * sockets of the group with the lowest preference value.\n
+ * In case of failures, the connection manager establishes connections to
+ * RTR servers of another group with the next lowest preference value (see
+ * <a href="https://tools.ietf.org/html/rfc6810">IETF
+ * RFC 6810</a> for details about error handling).\n
+ * RTRlib also supports a Retry Interval (see
+ * <a href="https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis">
+ * draft-ietf-sidr-rpki-rtr-rfc6810-bis</a>).
+ * If a more preferred group is online again, the RTR connection manager
+ * will switch back and close connections to the caches of the less
+ * preferred group.
+ *
+ * @{
+ * @example rtr_mgr.c
+ * Usage example of the RTR connection manager.
+ */
+#ifndef RTR_MGR
+#define RTR_MGR
+
+#include "rtrlib/pfx/pfx.h"
+#include "rtrlib/spki/spkitable.h"
+
+#include <pthread.h>
+#include <stdint.h>
+
+/**
+ * @brief Status of a rtr_mgr_group.
+ */
+enum rtr_mgr_status {
+ /** RTR sockets are disconnected */
+ RTR_MGR_CLOSED,
+ /** RTR sockets trying to establish a connection. */
+ RTR_MGR_CONNECTING,
+ /** All RTR sockets of the group are synchronized with rtr servers. */
+ RTR_MGR_ESTABLISHED,
+ /** Error occurred on at least one RTR socket. */
+ RTR_MGR_ERROR,
+};
+
+/**
+ * @brief A set of RTR sockets.
+ * @param sockets Array of rtr_socket pointer. The tr_socket element of
+ * the rtr_socket must be associated with an initialized #
+ * transport socket.
+ * @param sockets_len Number of elements in the sockets array.
+ * @param preference The preference value of this group.
+ * Groups with lower preference values are preferred.
+ * @param status Status of the group.
+ */
+struct rtr_mgr_group {
+ struct rtr_socket **sockets;
+ unsigned int sockets_len;
+ uint8_t preference;
+ enum rtr_mgr_status status;
+};
+
+typedef void (*rtr_mgr_status_fp)(const struct rtr_mgr_group *, enum rtr_mgr_status, const struct rtr_socket *, void *);
+
+struct tommy_list_wrapper;
+
+// TODO Add refresh, expire, and retry intervals to config for easier access.
+struct rtr_mgr_config {
+ struct tommy_list_wrapper *groups;
+ unsigned int len;
+ pthread_mutex_t mutex;
+ rtr_mgr_status_fp status_fp;
+ void *status_fp_data;
+ struct pfx_table *pfx_table;
+ struct spki_table *spki_table;
+};
+
+/**
+ * @brief Initializes a rtr_mgr_config.
+ * @param[out] config_out The rtr_mgr_config that will be initialized by this
+ * function. On error, *config_out will be NULL!
+ * @param[in] groups Linked list of rtr_mgr_group. Every RTR socket in an
+ * rtr_mgr_group must be assoziated with an initialized
+ * transport socket. A Transport socket is only allowed to be
+ * associated with one rtr socket. The preference values must
+ * be unique in the linked list. More than one rtr_mgr_group
+ * with the same preference value isn't allowed.
+ * @param[in] groups_len Number of elements in the groups array. Must be >= 1.
+ * @param[in] refresh_interval Interval in seconds between serial queries that
+ * are sent to the server. Must be >= 1 and <=
+ * 86400s (1d), recommended default is 3600s (1h).
+ * @param[in] expire_interval Stored validation records will be deleted if
+ * cache was unable to refresh data for this period.
+ * The value should be twice the refresh_interval
+ * and must be >= 600s (10min) and <= 172800s (2d).
+ * The recommended default is 7200s (2h).
+ * @param[in] retry_interval This parameter tells the router how long to wait
+ * (in seconds) before retrying a failed Serial Query
+ * or Reset Query.
+ * The value must be >= 1s and <= 7200s (2h).
+ * The recommended default is 600s (10min).
+ * @param[in] update_fp Pointer to pfx_update_fp callback, that is executed for
+ every added and removed pfx_record.
+ * @param[in] spki_update_fp Pointer to spki_update_fp callback, that is
+ executed for every added and removed spki_record.
+ * @param[in] status_fp Pointer to a function that is called if the connection
+ * status from one of the socket groups is changed.
+ * @param[in] status_fp_data Pointer to a memory area that is passed to the
+ * status_fp function. Memory area can be freely used
+ * to pass user-defined data to the status_fp
+ * callback.
+ * @return RTR_ERROR If an error occurred
+ * @return RTR_INVALID_PARAM If refresh_interval or expire_interval is invalid.
+ * @return RTR_SUCCESS On success.
+ */
+int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[], const unsigned int groups_len,
+ const unsigned int refresh_interval, const unsigned int expire_interval,
+ const unsigned int retry_interval, const pfx_update_fp update_fp, const spki_update_fp spki_update_fp,
+ const rtr_mgr_status_fp status_fp, void *status_fp_data);
+
+/**
+ * @brief Adds a new rtr_mgr_group to the linked list of a initialized config.
+ * @details A new group must have at least one rtr_socket associated
+ * with it. This socket must have at least one initialized
+ * transport socket associated with it. The new group must
+ * have a preference value that is none of the already present
+ * groups have. More than one rtr_mgr_group with the same
+ * preference is not allowed.
+ * @param config A rtr_mgr_config struct that has been initialized
+ * previously with rtr_mgr_init
+ * @param group A rtr_mgr_group with at least one rtr_socket and a
+ * preference value that no existing group has.
+ * @return RTR_INVALID_PARAM If a group with the same preference value already
+ * exists.
+ * @return RTR_ERROR If an error occurred while adding the group.
+ * @return RTR_SUCCESS If the group was successfully added.
+ *
+ */
+int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct rtr_mgr_group *group);
+/**
+ * @brief Removes an existing rtr_mgr_group from the linked list of config.
+ * @details The group to be removed is identified by its preference value.
+ * Should the group to be removed be currently active, it will be
+ * shut down and the next best group will be spun up.
+ * @param config A rtr_mgr_config struct that has been initialized previously
+ * with rtr_mgr_init
+ * @param preference The preference value of the group to be removed.
+ * @return RTR_ERROR If no group with this preference value exists.
+ * @return RTR_SUCCESS If group was successfully removed.
+ *
+ */
+int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference);
+/**
+ * @brief Frees all resources that were allocated from the rtr_mgr.
+ * @details rtr_mgr_stop must be called before, to shutdown all rtr_sockets.
+ * @param[in] config rtr_mgr_config.
+ */
+void rtr_mgr_free(struct rtr_mgr_config *config);
+
+/**
+ * @brief Establishes rtr_socket connections
+ * @details Establishes the connection with the rtr_sockets of the group
+ * with the lowest preference value and handles errors as defined in the
+ * RPKI-RTR protocol.
+ * @param[in] config Pointer to an initialized rtr_mgr_config.
+ * @return RTR_SUCCESS On success
+ * @return RTR_ERROR On error
+ */
+int rtr_mgr_start(struct rtr_mgr_config *config);
+
+/**
+ * @brief Terminates rtr_socket connections
+ * @details Terminates all rtr_socket connections defined in the config.
+ * All pfx_records received from these sockets will be purged.
+ * @param[in] config The rtr_mgr_config struct
+ */
+void rtr_mgr_stop(struct rtr_mgr_config *config);
+
+/**
+ * @brief Check if rtr_mgr_group is fully synchronized with at least one group.
+ * @param[in] config The rtr_mgr_config.
+ * @return true If pfx_table stores non-outdated pfx_records
+ * @return false If pfx_table isn't fully synchronized with at least one group.
+ */
+bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config);
+
+/**
+ * @brief Validates the origin of a BGP-Route.
+ * @param[in] config The rtr_mgr_config
+ * @param[in] asn Autonomous system number of the Origin-AS of the prefix
+ * @param[in] prefix Announced network prefix
+ * @param[in] mask_len Length of the network mask of the announced prefix
+ * @param[out] result Outcome of the validation
+ * @return PFX_SUCCESS On success.
+ * @return PFX_ERROR If an error occurred.
+ */
+int rtr_mgr_validate(struct rtr_mgr_config *config, const uint32_t asn, const struct lrtr_ip_addr *prefix,
+ const uint8_t mask_len, enum pfxv_state *result);
+
+/**
+ * @brief Returns all SPKI records which match the given ASN and SKI.
+ * @param[in] config
+ * @param[in] asn Autonomous system number of the Origin-AS
+ * @param[in] ski the SKI to search for
+ * @param[out] result a array of all matching spki_records
+ * @param[out] result_count number of returned spki_records
+ * @return SPKI_SUCCESS On success
+ * @return SPKI_ERROR If an error occurred
+ */
+int rtr_mgr_get_spki(struct rtr_mgr_config *config, const uint32_t asn, uint8_t *ski, struct spki_record **result,
+ unsigned int *result_count);
+
+/**
+ * @brief Converts a rtr_mgr_status to a String.
+ * @param[in] status state to convert to a string.
+ * @return NULL If status isn't a valid rtr_mgr_status.
+ * @return !=NULL The rtr_rtr_mgr_status as String.
+ */
+const char *rtr_mgr_status_to_str(enum rtr_mgr_status status);
+
+/**
+ * @brief Iterates over all IPv4 records in the pfx_table.
+ * @details For every pfx_record the function fp is called. The pfx_record and
+ * the data pointer is passed to the fp.
+ * @param[in] config rtr_mgr_config
+ * @param[in] fp Pointer to callback function with signature \c pfx_for_each_fp.
+ * @param[in] data This parameter is forwarded to the callback function.
+ */
+void rtr_mgr_for_each_ipv4_record(struct rtr_mgr_config *config, pfx_for_each_fp fp, void *data);
+
+/**
+ * @brief Iterates over all IPv6 records in the pfx_table.
+ * @details For every pfx_record the function fp is called. The pfx_record and
+ * the data pointer is passed to the fp.
+ * @param[in] config rtr_mgr_config
+ * @param[in] fp Pointer to callback function with signature \c pfx_for_each_fp.
+ * @param[in] data This parameter is forwarded to the callback function.
+ */
+void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *config, pfx_for_each_fp fp, void *data);
+
+/**
+ * @brief Returns the first, thus active group.
+ * @param[in] config The rtr_mgr_config
+ * @return rtr_mgr_group The head of the linked list.
+ */
+struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config);
+
+int rtr_mgr_for_each_group(struct rtr_mgr_config *config, void (*fp)(const struct rtr_mgr_group *group, void *data),
+ void *data);
+#endif
+/** @} */
diff --git a/rtrlib/rtr_mgr_private.h b/rtrlib/rtr_mgr_private.h
new file mode 100644
index 0000000..d654820
--- /dev/null
+++ b/rtrlib/rtr_mgr_private.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef RTR_MGR_PRIVATE
+#define RTR_MGR_PRIVATE
+
+#include "rtrlib/rtr_mgr.h"
+
+#include "third-party/tommyds/tommylist.h"
+
+struct tommy_list_wrapper {
+ tommy_list list;
+};
+
+// TODO Find a nicer way todo a linked list (without writing our own)
+struct rtr_mgr_group_node {
+ tommy_node node;
+ struct rtr_mgr_group *group;
+};
+
+#endif
diff --git a/rtrlib/rtrlib.h.cmake b/rtrlib/rtrlib.h.cmake
new file mode 100644
index 0000000..de8f612
--- /dev/null
+++ b/rtrlib/rtrlib.h.cmake
@@ -0,0 +1,40 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef RTRLIB_H
+#define RTRLIB_H
+
+#cmakedefine RTRLIB_HAVE_LIBSSH
+#define RTRLIB_VERSION_MAJOR @RTRLIB_VERSION_MAJOR@
+#define RTRLIB_VERSION_MINOR @RTRLIB_VERSION_MINOR@
+#define RTRLIB_VERSION_PATCH @RTRLIB_VERSION_PATCH@
+
+#include "lib/alloc_utils.h"
+#include "lib/ip.h"
+#include "lib/ipv4.h"
+#include "lib/ipv6.h"
+#include "pfx/pfx.h"
+#include "rtr/rtr.h"
+#include "rtr_mgr.h"
+#include "spki/spkitable.h"
+#include "transport/tcp/tcp_transport.h"
+#include "transport/transport.h"
+#ifdef RTRLIB_HAVE_LIBSSH
+#include "rtrlib/transport/ssh/ssh_transport.h"
+#endif
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/rtrlib/rtrlib_export_private.h b/rtrlib/rtrlib_export_private.h
new file mode 100644
index 0000000..c5ed4b3
--- /dev/null
+++ b/rtrlib/rtrlib_export_private.h
@@ -0,0 +1,16 @@
+
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef RTRLIB_EXPORT_H
+#define RTRLIB_EXPORT_H
+
+#define RTRLIB_EXPORT __attribute__((visibility("default")))
+
+#endif
diff --git a/rtrlib/spki/hashtable/ht-spkitable.c b/rtrlib/spki/hashtable/ht-spkitable.c
new file mode 100644
index 0000000..047ab3a
--- /dev/null
+++ b/rtrlib/spki/hashtable/ht-spkitable.c
@@ -0,0 +1,368 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "ht-spkitable_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+
+struct key_entry {
+ uint8_t ski[SKI_SIZE];
+ uint32_t asn;
+ uint8_t spki[SPKI_SIZE];
+ const struct rtr_socket *socket;
+ tommy_node hash_node;
+ tommy_node list_node;
+};
+
+/**
+ * @brief Compares two key_entries by comparing ASN, SKI and SPKI
+ * @param[in] arg Pointer to first key_entry
+ * @param[in] obj Pointer to second key_entry
+ * @return 1 if not equal
+ * @return 0 if equal
+ */
+static int key_entry_cmp(const void *arg, const void *obj)
+{
+ const struct key_entry *param = arg;
+ const struct key_entry *entry = obj;
+
+ if (param->asn != entry->asn)
+ return 1;
+ if (memcmp(param->ski, entry->ski, sizeof(entry->ski)))
+ return 1;
+ if (memcmp(param->spki, entry->spki, sizeof(entry->spki)))
+ return 1;
+ if (param->socket != entry->socket)
+ return 1;
+
+ return 0;
+}
+
+/* Copying the content, target struct must already be allocated */
+static void key_entry_to_spki_record(struct key_entry *key_e, struct spki_record *spki_r)
+{
+ spki_r->asn = key_e->asn;
+ spki_r->socket = key_e->socket;
+ memcpy(spki_r->ski, key_e->ski, sizeof(key_e->ski));
+ memcpy(spki_r->spki, key_e->spki, sizeof(key_e->spki));
+}
+
+/* Copying the content, target struct must already be allocated */
+static void spki_record_to_key_entry(struct spki_record *spki_r, struct key_entry *key_e)
+{
+ key_e->asn = spki_r->asn;
+ key_e->socket = spki_r->socket;
+ memcpy(key_e->ski, spki_r->ski, sizeof(spki_r->ski));
+ memcpy(key_e->spki, spki_r->spki, sizeof(spki_r->spki));
+}
+
+/**
+ * @brief Calls the spki_table update function.
+ * @param[in] spki_table spki_table to use.
+ * @param[in] record spki_record that was added/removed.
+ * @param[in] added True means record was added, False means removed
+ */
+static void spki_table_notify_clients(struct spki_table *spki_table, const struct spki_record *record, const bool added)
+{
+ if (spki_table->update_fp)
+ spki_table->update_fp(spki_table, *record, added);
+}
+
+void spki_table_init(struct spki_table *spki_table, spki_update_fp update_fp)
+{
+ tommy_hashlin_init(&spki_table->hashtable);
+ tommy_list_init(&spki_table->list);
+ pthread_rwlock_init(&spki_table->lock, NULL);
+ spki_table->cmp_fp = key_entry_cmp;
+ spki_table->update_fp = update_fp;
+}
+
+void spki_table_free(struct spki_table *spki_table)
+{
+ pthread_rwlock_wrlock(&spki_table->lock);
+
+ tommy_list_foreach(&spki_table->list, free);
+ tommy_hashlin_done(&spki_table->hashtable);
+
+ pthread_rwlock_unlock(&spki_table->lock);
+ pthread_rwlock_destroy(&spki_table->lock);
+}
+
+void spki_table_free_without_notify(struct spki_table *spki_table)
+{
+ pthread_rwlock_wrlock(&spki_table->lock);
+
+ spki_table->update_fp = NULL;
+ tommy_list_foreach(&spki_table->list, free);
+ tommy_hashlin_done(&spki_table->hashtable);
+
+ pthread_rwlock_unlock(&spki_table->lock);
+ pthread_rwlock_destroy(&spki_table->lock);
+}
+
+int spki_table_add_entry(struct spki_table *spki_table, struct spki_record *spki_record)
+{
+ uint32_t hash;
+ struct key_entry *entry;
+
+ entry = lrtr_malloc(sizeof(*entry));
+ if (!entry)
+ return SPKI_ERROR;
+
+ spki_record_to_key_entry(spki_record, entry);
+ hash = tommy_inthash_u32(spki_record->asn);
+
+ pthread_rwlock_wrlock(&spki_table->lock);
+ if (tommy_hashlin_search(&spki_table->hashtable, spki_table->cmp_fp, entry, hash)) {
+ lrtr_free(entry);
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_DUPLICATE_RECORD;
+ }
+
+ /* Insert into hashtable and list */
+ tommy_hashlin_insert(&spki_table->hashtable, &entry->hash_node, entry, hash);
+ tommy_list_insert_tail(&spki_table->list, &entry->list_node, entry);
+ pthread_rwlock_unlock(&spki_table->lock);
+ spki_table_notify_clients(spki_table, spki_record, true);
+ return SPKI_SUCCESS;
+}
+
+int spki_table_get_all(struct spki_table *spki_table, uint32_t asn, uint8_t *ski, struct spki_record **result,
+ unsigned int *result_size)
+{
+ uint32_t hash = tommy_inthash_u32(asn);
+ tommy_node *result_bucket;
+ void *tmp;
+
+ *result = NULL;
+ *result_size = 0;
+
+ pthread_rwlock_rdlock(&spki_table->lock);
+
+ /**
+ * A tommy node contains its storing key_entry (->data) as well as
+ * next and prev pointer to accommodate multiple results.
+ * The bucket is guaranteed to contain ALL the elements
+ * with the specified hash, but it can contain also others.
+ */
+ result_bucket = tommy_hashlin_bucket(&spki_table->hashtable, hash);
+
+ if (!result_bucket) {
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_SUCCESS;
+ }
+
+ /* Build the result array */
+ while (result_bucket) {
+ struct key_entry *element;
+
+ element = result_bucket->data;
+ if (element->asn == asn && memcmp(element->ski, ski, sizeof(element->ski)) == 0) {
+ (*result_size)++;
+ tmp = lrtr_realloc(*result, *result_size * sizeof(**result));
+ if (!tmp) {
+ lrtr_free(*result);
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_ERROR;
+ }
+ *result = tmp;
+ key_entry_to_spki_record(element, *result + *result_size - 1);
+ }
+ result_bucket = result_bucket->next;
+ }
+
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_SUCCESS;
+}
+
+// cppcheck-suppress unusedFunction
+int spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result,
+ unsigned int *result_size)
+{
+ tommy_node *current_node;
+ void *tmp;
+ *result = NULL;
+ *result_size = 0;
+
+ pthread_rwlock_rdlock(&spki_table->lock);
+
+ current_node = tommy_list_head(&spki_table->list);
+ while (current_node) {
+ struct key_entry *current_entry;
+
+ current_entry = (struct key_entry *)current_node->data;
+
+ if (memcmp(current_entry->ski, ski, sizeof(current_entry->ski)) == 0) {
+ (*result_size)++;
+ tmp = lrtr_realloc(*result, sizeof(**result) * (*result_size));
+ if (!tmp) {
+ lrtr_free(*result);
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_ERROR;
+ }
+ *result = tmp;
+ key_entry_to_spki_record(current_entry, *result + (*result_size - 1));
+ }
+ current_node = current_node->next;
+ }
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_SUCCESS;
+}
+
+int spki_table_remove_entry(struct spki_table *spki_table, struct spki_record *spki_record)
+{
+ uint32_t hash;
+ struct key_entry entry;
+ struct key_entry *rmv_elem;
+ int rtval = SPKI_ERROR;
+
+ spki_record_to_key_entry(spki_record, &entry);
+ hash = tommy_inthash_u32(spki_record->asn);
+
+ pthread_rwlock_wrlock(&spki_table->lock);
+
+ if (!tommy_hashlin_search(&spki_table->hashtable, spki_table->cmp_fp, &entry, hash)) {
+ rtval = SPKI_RECORD_NOT_FOUND;
+ } else {
+ /* Remove from hashtable and list */
+ rmv_elem = tommy_hashlin_remove(&spki_table->hashtable, spki_table->cmp_fp, &entry, hash);
+ if (rmv_elem && tommy_list_remove_existing(&spki_table->list, &rmv_elem->list_node)) {
+ lrtr_free(rmv_elem);
+ spki_table_notify_clients(spki_table, spki_record, false);
+ rtval = SPKI_SUCCESS;
+ }
+ }
+ pthread_rwlock_unlock(&spki_table->lock);
+ return rtval;
+}
+
+int spki_table_src_remove(struct spki_table *spki_table, const struct rtr_socket *socket)
+{
+ struct key_entry *entry;
+ tommy_node *current_node;
+
+ pthread_rwlock_wrlock(&spki_table->lock);
+
+ current_node = tommy_list_head(&spki_table->list);
+ while (current_node) {
+ entry = current_node->data;
+ if (entry->socket == socket) {
+ current_node = current_node->next;
+ if (!tommy_list_remove_existing(&spki_table->list, &entry->list_node)) {
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_ERROR;
+ }
+ if (!tommy_hashlin_remove_existing(&spki_table->hashtable, &entry->hash_node)) {
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_ERROR;
+ }
+ lrtr_free(entry);
+ } else {
+ current_node = current_node->next;
+ }
+ }
+ pthread_rwlock_unlock(&spki_table->lock);
+ return SPKI_SUCCESS;
+}
+
+int spki_table_copy_except_socket(struct spki_table *src, struct spki_table *dst, struct rtr_socket *socket)
+{
+ tommy_node *current_node;
+ int ret = SPKI_SUCCESS;
+
+ pthread_rwlock_rdlock(&src->lock);
+ current_node = tommy_list_head(&src->list);
+ while (current_node) {
+ struct key_entry *entry;
+ struct spki_record record;
+
+ entry = (struct key_entry *)current_node->data;
+ key_entry_to_spki_record(entry, &record);
+
+ if (entry->socket != socket) {
+ if (spki_table_add_entry(dst, &record) != SPKI_SUCCESS) {
+ ret = SPKI_ERROR;
+ break;
+ }
+ }
+ current_node = current_node->next;
+ }
+
+ pthread_rwlock_unlock(&src->lock);
+
+ return ret;
+}
+
+void spki_table_notify_diff(struct spki_table *new_table, struct spki_table *old_table, const struct rtr_socket *socket)
+{
+ spki_update_fp old_table_fp;
+
+ // Disable update callback for old_table
+ old_table_fp = old_table->update_fp;
+ old_table->update_fp = NULL;
+
+ // Iterate new_table and try to delete every entry from the given socket
+ // in old_table If the prefix could not be removed it was added in
+ // new_table and the update cb must be called
+ for (tommy_node *current_node = tommy_list_head(&new_table->list); current_node;
+ current_node = current_node->next) {
+ struct key_entry *entry = (struct key_entry *)current_node->data;
+
+ if (entry->socket == socket) {
+ struct spki_record record;
+
+ key_entry_to_spki_record(entry, &record);
+
+ if (spki_table_remove_entry(old_table, &record) == SPKI_RECORD_NOT_FOUND)
+ spki_table_notify_clients(new_table, &record, true);
+ }
+ }
+
+ // Iterate old_table and call cb for every remianing entry from the
+ // given socket with added false because it is not present in new_table
+ for (tommy_node *current_node = tommy_list_head(&old_table->list); current_node;
+ current_node = current_node->next) {
+ struct key_entry *entry = (struct key_entry *)current_node->data;
+
+ if (entry->socket == socket) {
+ struct spki_record record;
+
+ key_entry_to_spki_record(entry, &record);
+ spki_table_notify_clients(new_table, &record, false);
+ }
+ }
+
+ // Restore original state of old_tables update_fp
+ old_table->update_fp = old_table_fp;
+}
+
+void spki_table_swap(struct spki_table *a, struct spki_table *b)
+{
+ tommy_hashlin tmp_hashtable;
+ tommy_list tmp_list;
+
+ pthread_rwlock_wrlock(&a->lock);
+ pthread_rwlock_wrlock(&b->lock);
+
+ memcpy(&tmp_hashtable, &a->hashtable, sizeof(tmp_hashtable));
+ memcpy(&tmp_list, &a->list, sizeof(tmp_list));
+
+ memcpy(&a->hashtable, &b->hashtable, sizeof(tmp_hashtable));
+ memcpy(&a->list, &b->list, sizeof(tmp_list));
+
+ memcpy(&b->hashtable, &tmp_hashtable, sizeof(tmp_hashtable));
+ memcpy(&b->list, &tmp_list, sizeof(tmp_list));
+
+ pthread_rwlock_unlock(&a->lock);
+ pthread_rwlock_unlock(&b->lock);
+}
diff --git a/rtrlib/spki/hashtable/ht-spkitable_private.h b/rtrlib/spki/hashtable/ht-spkitable_private.h
new file mode 100644
index 0000000..05b0a48
--- /dev/null
+++ b/rtrlib/spki/hashtable/ht-spkitable_private.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+#ifndef RTR_HT_SPKITABLE_PRIVATE_H
+#define RTR_HT_SPKITABLE_PRIVATE_H
+
+#include "rtrlib/spki/spkitable_private.h"
+
+#include "third-party/tommyds/tommyhashlin.h"
+#include "third-party/tommyds/tommylist.h"
+
+typedef int (*hash_cmp_fp)(const void *arg, const void *obj);
+
+/**
+ * @brief spki_table.
+ * @param hashtable Linear hashtable
+ * @param list List that holds the same entries as hashtable, used to iterate.
+ * @param cmp_fp Compare function used to find entries in the hashtable
+ * @param update_fp Update function, called when the hashtable changes
+ * @param lock Read-Write lock to prevent data races
+ */
+struct spki_table {
+ tommy_hashlin hashtable;
+ tommy_list list;
+ hash_cmp_fp cmp_fp;
+ spki_update_fp update_fp;
+ pthread_rwlock_t lock;
+};
+
+#endif
diff --git a/rtrlib/spki/spkitable.h b/rtrlib/spki/spkitable.h
new file mode 100644
index 0000000..4158c58
--- /dev/null
+++ b/rtrlib/spki/spkitable.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_spki_h Subject Public Key Info table
+ * @brief The spki_table is an abstract data structure to organize the received Router Key PDUs
+ * from a RPKI-RTR cache server.
+ *
+ * @{
+ */
+
+#ifndef RTR_SPKI_H
+#define RTR_SPKI_H
+
+#include "rtrlib/rtr/rtr.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define SKI_SIZE 20
+#define SPKI_SIZE 91
+
+struct spki_table;
+
+/**
+ * @brief spki_record.
+ * @param ski Subject Key Identifier
+ * @param asn Origin AS number
+ * @param spki Subject public key info
+ * @param socket Pointer to the rtr_socket this spki_record was received in
+ */
+struct spki_record {
+ uint8_t ski[SKI_SIZE];
+ uint32_t asn;
+ uint8_t spki[SPKI_SIZE];
+ const struct rtr_socket *socket;
+};
+
+/**
+ * @brief A function pointer that is called if an record was added
+ * to the spki_table or was removed from the spki_table.
+ * @param spki_table which was updated.
+ * @param record spki_record that was modified.
+ * @param added True if the record was added, false if the record was removed.
+ */
+typedef void (*spki_update_fp)(struct spki_table *spki_table, const struct spki_record record, const bool added);
+#endif
+/** @} */
diff --git a/rtrlib/spki/spkitable_private.h b/rtrlib/spki/spkitable_private.h
new file mode 100644
index 0000000..65c3481
--- /dev/null
+++ b/rtrlib/spki/spkitable_private.h
@@ -0,0 +1,143 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_spki_h Subject Public Key Info table
+ * @brief The spki_table is an abstract data structure to organize the received Router Key PDUs
+ * from a RPKI-RTR cache server.
+ *
+ * @{
+ */
+
+#ifndef RTR_SPKI_PRIVATE_H
+#define RTR_SPKI_PRIVATE_H
+
+#include "rtrlib/spki/spkitable.h"
+
+#include <stdint.h>
+
+/**
+ * @brief Possible return values for some spki_table_ functions.
+ */
+enum spki_rtvals {
+ /** Operation was successful. */
+ SPKI_SUCCESS = 0,
+
+ /** Error occurred. */
+ SPKI_ERROR = -1,
+
+ /** The supplied spki_record already exists in the spki_table. */
+ SPKI_DUPLICATE_RECORD = -2,
+
+ /** spki_record wasn't found in the spki_table. */
+ SPKI_RECORD_NOT_FOUND = -3
+};
+
+/**
+ * @brief Initializes the spki_table struct.
+ * @param[in] spki_table spki_table that will be initialized.
+ * @param[in] update_fp Pointer to update function
+ */
+void spki_table_init(struct spki_table *spki_table, spki_update_fp update_fp);
+
+/**
+ * @brief Frees the memory associated with the spki_table.
+ * @param[in] spki_table spki_table that will be initialized.
+ */
+void spki_table_free(struct spki_table *spki_table);
+
+/**
+ * @brief Frees the memory associated with the spki_table without calling the update callback.
+ * @param[in] spki_table spki_table that will be initialized.
+ */
+void spki_table_free_without_notify(struct spki_table *spki_table);
+
+/**
+ * @brief Adds a spki_record to a spki_table.
+ * @param[in] spki_table spki_table to use.
+ * @param[in] spki_record spki_record that will be added.
+ * @return SPKI_SUCCESS On success.
+ * @return SPKI_ERROR On error.
+ * @return SPKI_DUPLICATE_RECORD If an identical spki_record already exists
+ */
+int spki_table_add_entry(struct spki_table *spki_table, struct spki_record *spki_record);
+
+/**
+ * @brief Returns all spki_record whose ASN and SKI matches.
+ * @param[in] spki_table spki_table to use
+ * @param[in] asn the AS number
+ * @param[in] ski the 20 byte field which contains the SKI to search for
+ * @param[out] result the result array. NULL if no records could be found
+ * @param[out] result_size elment count of the result array
+ * @return SPKI_SUCCESS On success
+ * @return SPKI_ERROR On error
+ */
+int spki_table_get_all(struct spki_table *spki_table, uint32_t asn, uint8_t *ski, struct spki_record **result,
+ unsigned int *result_size);
+
+/**
+ * @brief Returns all spki_record whose SKI number matches the given one.
+ * @param[in] spki_table spki_table to use
+ * @param[in] ski the 20 byte field which contains the SKI to search for
+ * @param[out] result the result array. NULL if no records could be found
+ * @param[out] result_size elment count of the result array
+ * @return SPKI_SUCCESS On success
+ * @return SPKI_ERROR On error
+ */
+int spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result,
+ unsigned int *result_size);
+
+/**
+ * @brief Removes spki_record from spki_table
+ * @param spki_table spki_table to use
+ * @param spki_record spki_record to remove;
+ * @return SPKI_SUCCESS On success
+ * @return SPKI_ERROR On error
+ * @return SPKI_RECORD_NOT_FOUND On record not found
+ */
+int spki_table_remove_entry(struct spki_table *spki_table, struct spki_record *spki_record);
+
+/**
+ * @brief Removes all entries in the spki_table that match the passed socket_id.
+ * @param[in] spki_table spki_table to use.
+ * @param[in] socket origin socket of the record
+ * @return SPKI_SUCCESS On success.
+ * @return SPKI_ERROR On error.
+ */
+int spki_table_src_remove(struct spki_table *spki_table, const struct rtr_socket *socket);
+
+/**
+ * @brief Copy spki table except entries from the given socket
+ * @param[in] src source table
+ * @param[in] dest target table
+ * @param[in] socket socket which entries should not be copied
+ * @return SPKI_SUCCESS On success.
+ * @return SPKI_ERROR On error.
+ */
+int spki_table_copy_except_socket(struct spki_table *src, struct spki_table *dest, struct rtr_socket *socket);
+
+/**
+ * @brief Notify client about changes between two spki tables regarding one specific socket
+ * @details old_table will be modified and should probebly be freed after calling this function
+ * @param[in] new_table
+ * @param[in] old_table
+ * @param[in] socket socket which entries should be diffed
+ */
+void spki_table_notify_diff(struct spki_table *new_table, struct spki_table *old_table,
+ const struct rtr_socket *socket);
+
+/**
+ * @brief tommy_hashlin and tommy_list of the argument tables
+ * @param[in] a
+ * @param[in] b
+ */
+void spki_table_swap(struct spki_table *a, struct spki_table *b);
+
+#endif
+/** @} */
diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c
new file mode 100644
index 0000000..222ea25
--- /dev/null
+++ b/rtrlib/transport/ssh/ssh_transport.c
@@ -0,0 +1,392 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "ssh_transport_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/log_private.h"
+#include "rtrlib/lib/utils_private.h"
+#include "rtrlib/rtrlib_export_private.h"
+#include "rtrlib/transport/transport_private.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <libssh/libssh.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#define SSH_DBG(fmt, sock, ...) \
+ do { \
+ const struct tr_ssh_socket *tmp = sock; \
+ lrtr_dbg("SSH Transport(%s@%s:%u): " fmt, tmp->config.username, tmp->config.host, tmp->config.port, \
+ ##__VA_ARGS__); \
+ } while (0)
+#define SSH_DBG1(a, sock) SSH_DBG(a, sock)
+
+struct tr_ssh_socket {
+ ssh_session session;
+ ssh_channel channel;
+ struct tr_ssh_config config;
+ char *ident;
+};
+
+static int tr_ssh_open(void *tr_ssh_sock);
+static void tr_ssh_close(void *tr_ssh_sock);
+static void tr_ssh_free(struct tr_socket *tr_sock);
+static int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout);
+static int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, const time_t timeout);
+static int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len);
+static const char *tr_ssh_ident(void *tr_ssh_sock);
+
+/* WARNING: This function has cancelable sections! */
+int tr_ssh_open(void *socket)
+{
+ struct tr_ssh_socket *ssh_socket = socket;
+ const struct tr_ssh_config *config = &ssh_socket->config;
+
+ assert(!ssh_socket->channel);
+ assert(!ssh_socket->session);
+
+ ssh_socket->session = ssh_new();
+ if (!ssh_socket->session) {
+ SSH_DBG("%s: can't create ssh_session", ssh_socket, __func__);
+ goto error;
+ }
+
+ const int verbosity = SSH_LOG_NOLOG;
+
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_HOST, config->host);
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_PORT, &(config->port));
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_BINDADDR, config->bindaddr);
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_USER, config->username);
+
+ if (config->server_hostkey_path)
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_KNOWNHOSTS, config->server_hostkey_path);
+
+ if (config->client_privkey_path)
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path);
+ if (config->new_socket) {
+ int fd;
+
+ fd = config->new_socket(config->data);
+ if (fd >= 0) {
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_FD, &fd);
+ } else {
+ SSH_DBG1("tr_ssh_init: opening SSH connection failed", ssh_socket);
+ goto error;
+ }
+ }
+
+ ssh_set_blocking(ssh_socket->session, 0);
+ int ret;
+
+ do {
+ ret = ssh_connect(ssh_socket->session);
+
+ if (ret == SSH_ERROR) {
+ SSH_DBG("%s: opening SSH connection failed", ssh_socket, __func__);
+ goto error;
+ } else if (ret == SSH_AGAIN) {
+ socket_t fd = ssh_get_fd(ssh_socket->session);
+
+ fd_set rfds;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ struct timeval timeout = {.tv_sec = ssh_socket->config.connect_timeout, .tv_usec = 0};
+ int oldcancelstate;
+
+ /* Enable cancellability for the select call
+ * to prevent rtr_stop from blocking for a long time.
+ * It must be the only blocking call in this function.
+ * Since local resources have all been freed this should be safe.
+ */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ int sret = select(fd + 1, &rfds, NULL, NULL, &timeout);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+
+ if (sret < 0) {
+ SSH_DBG("could not select ssh socket, %s", ssh_socket, strerror(errno));
+
+ } else if (sret == 0) {
+ SSH_DBG1("connection attempt timed out", ssh_socket);
+ goto error;
+ }
+ }
+
+ } while (ret != SSH_OK);
+
+ ssh_set_blocking(ssh_socket->session, 1);
+
+ // check server identity
+#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 8
+ if ((config->server_hostkey_path) && (ssh_session_is_known_server(ssh_socket->session) != SSH_KNOWN_HOSTS_OK)) {
+#else
+ if ((config->server_hostkey_path) && (ssh_is_server_known(ssh_socket->session) != SSH_SERVER_KNOWN_OK)) {
+#endif
+ SSH_DBG("%s: Wrong hostkey", ssh_socket, __func__);
+ goto error;
+ }
+
+ if (config->client_privkey_path) {
+ SSH_DBG("%s: Trying publickey authentication", ssh_socket, __func__);
+ ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path);
+
+ int rtval;
+
+#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 5
+ rtval = ssh_userauth_publickey_auto(ssh_socket->session, NULL, NULL);
+#else /* else use libSSH version 0.5.0 */
+ rtval = ssh_userauth_autopubkey(ssh_socket->session, NULL);
+#endif
+ if (rtval != SSH_AUTH_SUCCESS) {
+ SSH_DBG("%s: Publickey authentication failed", ssh_socket, __func__);
+ goto error;
+ }
+
+ } else {
+ SSH_DBG("%s: Trying password authentication", ssh_socket, __func__);
+
+ if (ssh_userauth_password(ssh_socket->session, NULL, config->password) != SSH_AUTH_SUCCESS) {
+ SSH_DBG("%s: Password authentication failed", ssh_socket, __func__);
+ goto error;
+ }
+ }
+
+ ssh_socket->channel = ssh_channel_new(ssh_socket->session);
+ if (!ssh_socket->channel)
+ goto error;
+
+ if (ssh_channel_open_session(ssh_socket->channel) == SSH_ERROR)
+ goto error;
+
+ if (ssh_channel_request_subsystem(ssh_socket->channel, "rpki-rtr") == SSH_ERROR) {
+ SSH_DBG("%s: Error requesting subsystem rpki-rtr", ssh_socket, __func__);
+ goto error;
+ }
+ SSH_DBG1("Connection established", ssh_socket);
+
+ return TR_SUCCESS;
+
+error:
+ tr_ssh_close(ssh_socket);
+ return TR_ERROR;
+}
+
+void tr_ssh_close(void *tr_ssh_sock)
+{
+ struct tr_ssh_socket *socket = tr_ssh_sock;
+
+ if (socket->channel) {
+ if (ssh_channel_is_open(socket->channel))
+ ssh_channel_close(socket->channel);
+ ssh_channel_free(socket->channel);
+ socket->channel = NULL;
+ }
+ if (socket->session) {
+ ssh_disconnect(socket->session);
+ ssh_free(socket->session);
+ socket->session = NULL;
+ }
+ SSH_DBG1("Socket closed", socket);
+}
+
+void tr_ssh_free(struct tr_socket *tr_sock)
+{
+ struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket;
+
+ assert(!tr_ssh_sock->channel);
+ assert(!tr_ssh_sock->session);
+
+ SSH_DBG1("Freeing socket", tr_ssh_sock);
+
+ lrtr_free(tr_ssh_sock->config.host);
+ lrtr_free(tr_ssh_sock->config.bindaddr);
+ lrtr_free(tr_ssh_sock->config.username);
+ lrtr_free(tr_ssh_sock->config.client_privkey_path);
+ lrtr_free(tr_ssh_sock->config.server_hostkey_path);
+
+ if (tr_ssh_sock->ident)
+ lrtr_free(tr_ssh_sock->ident);
+ lrtr_free(tr_ssh_sock);
+ tr_sock->socket = NULL;
+}
+
+int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len)
+{
+ const int rtval = ssh_channel_read_nonblocking(tr_ssh_sock->channel, buf, buf_len, false);
+
+ if (rtval == 0) {
+ if (ssh_channel_is_eof(tr_ssh_sock->channel) != 0) {
+ SSH_DBG1("remote has sent EOF", tr_ssh_sock);
+ return TR_CLOSED;
+ } else {
+ return TR_WOULDBLOCK;
+ }
+ } else if (rtval == SSH_ERROR) {
+ SSH_DBG1("recv(..) error", tr_ssh_sock);
+ return TR_ERROR;
+ }
+ return rtval;
+}
+
+int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout)
+{
+ ssh_channel rchans[2] = {((struct tr_ssh_socket *)tr_ssh_sock)->channel, NULL};
+ struct timeval timev = {timeout, 0};
+ int ret;
+
+ ret = ssh_channel_select(rchans, NULL, NULL, &timev);
+ if (ret == SSH_EINTR)
+ return TR_INTR;
+ else if (ret == SSH_ERROR)
+ return TR_ERROR;
+
+ if (ssh_channel_is_eof(((struct tr_ssh_socket *)tr_ssh_sock)->channel) != 0)
+ return TR_ERROR;
+
+ if (!rchans[0])
+ return TR_WOULDBLOCK;
+
+ return tr_ssh_recv_async(tr_ssh_sock, buf, buf_len);
+}
+
+int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len,
+ const time_t timeout __attribute__((unused)))
+{
+ int ret = ssh_channel_write(((struct tr_ssh_socket *)tr_ssh_sock)->channel, pdu, len);
+
+ if (ret == SSH_ERROR)
+ return TR_ERROR;
+
+ return ret;
+}
+
+const char *tr_ssh_ident(void *tr_ssh_sock)
+{
+ size_t len;
+ struct tr_ssh_socket *sock = tr_ssh_sock;
+
+ assert(sock);
+
+ if (sock->ident)
+ return sock->ident;
+
+ len = strlen(sock->config.username) + 1 + strlen(sock->config.host) + 1 + 5 + 1;
+ sock->ident = lrtr_malloc(len);
+ if (!sock->ident)
+ return NULL;
+ snprintf(sock->ident, len, "%s@%s:%u", sock->config.username, sock->config.host, sock->config.port);
+ return sock->ident;
+}
+
+RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket)
+{
+ socket->close_fp = &tr_ssh_close;
+ socket->free_fp = &tr_ssh_free;
+ socket->open_fp = &tr_ssh_open;
+ socket->recv_fp = &tr_ssh_recv;
+ socket->send_fp = &tr_ssh_send;
+ socket->ident_fp = &tr_ssh_ident;
+
+ socket->socket = lrtr_calloc(1, sizeof(struct tr_ssh_socket));
+ struct tr_ssh_socket *ssh_socket = socket->socket;
+
+ ssh_socket->channel = NULL;
+ ssh_socket->session = NULL;
+ ssh_socket->config.host = lrtr_strdup(config->host);
+ if (!ssh_socket->config.host)
+ goto error;
+ ssh_socket->config.port = config->port;
+
+ ssh_socket->config.username = lrtr_strdup(config->username);
+ if (!ssh_socket->config.username)
+ goto error;
+
+ if ((config->password && config->client_privkey_path) || (!config->password && !config->client_privkey_path))
+ return TR_ERROR;
+
+ if (config->bindaddr) {
+ ssh_socket->config.bindaddr = lrtr_strdup(config->bindaddr);
+
+ if (!ssh_socket->config.bindaddr)
+ goto error;
+
+ } else {
+ ssh_socket->config.bindaddr = NULL;
+ }
+
+ if (config->client_privkey_path) {
+ ssh_socket->config.client_privkey_path = lrtr_strdup(config->client_privkey_path);
+ if (!ssh_socket->config.client_privkey_path)
+ goto error;
+
+ } else {
+ ssh_socket->config.client_privkey_path = NULL;
+ }
+
+ if (config->server_hostkey_path) {
+ ssh_socket->config.server_hostkey_path = lrtr_strdup(config->server_hostkey_path);
+
+ if (!ssh_socket->config.client_privkey_path)
+ goto error;
+
+ } else {
+ ssh_socket->config.server_hostkey_path = NULL;
+ }
+
+ if (config->connect_timeout == 0)
+ ssh_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT;
+ else
+ ssh_socket->config.connect_timeout = config->connect_timeout;
+
+ if (config->password) {
+ ssh_socket->config.password = lrtr_strdup(config->password);
+
+ if (!ssh_socket->config.password)
+ goto error;
+
+ } else {
+ ssh_socket->config.password = NULL;
+ }
+
+ ssh_socket->ident = NULL;
+ ssh_socket->config.data = config->data;
+ ssh_socket->config.new_socket = config->new_socket;
+
+ return TR_SUCCESS;
+
+error:
+ if (ssh_socket->config.host)
+ free(ssh_socket->config.host);
+
+ if (ssh_socket->config.username)
+ free(ssh_socket->config.username);
+
+ if (ssh_socket->config.bindaddr)
+ free(ssh_socket->config.bindaddr);
+
+ if (ssh_socket->config.client_privkey_path)
+ free(ssh_socket->config.client_privkey_path);
+
+ if (ssh_socket->config.server_hostkey_path)
+ free(ssh_socket->config.server_hostkey_path);
+
+ if (ssh_socket->config.password)
+ free(ssh_socket->config.password);
+
+ return TR_ERROR;
+}
diff --git a/rtrlib/transport/ssh/ssh_transport.h b/rtrlib/transport/ssh/ssh_transport.h
new file mode 100644
index 0000000..188e256
--- /dev/null
+++ b/rtrlib/transport/ssh/ssh_transport.h
@@ -0,0 +1,74 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_ssh_transport_h SSH transport socket
+ * @ingroup mod_transport_h
+ * @brief An implementation of the SSH protocol for the RTR transport.
+ * @details This transport implementation uses libssh
+ * (http://www.libssh.org/) for all ssh specific operations.\n
+ * See @ref mod_transport_h "transport interface" for a list of supported
+ * operations.
+ *
+ * @{
+ *
+ * @example ssh_tr.c
+ * Example of how to open a SSH transport connection.
+ */
+
+#ifndef SSH_TRANSPORT_H
+#define SSH_TRANSPORT_H
+
+#include "rtrlib/transport/transport.h"
+
+/**
+ * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket.
+ * @param host Hostname or IP address to connect to.
+ * @param port Port to connect to.
+ * @param bindaddr Hostname or IP address to connect from. NULL for
+ * determination by OS.
+ * @param username Username for authentication.
+ * @param server_hostkey_path Path to public SSH key of the server or NULL to
+ * don't verify host authenticity.
+ * @param client_privkey_path Path to private key of the authentication keypair
+ * or NULL to use ~/.ssh/id_rsa.
+ * @param data Information to pass to callback function
+ * in charge of retrieving socket
+ * @param new_socket(void *opaque_info) callback routine, that
+ * Pointer to the function that is called every time a new connection
+ * is made. The returned socket is expected to be ready for use (e.g.
+ * in state established), and must use a reliably stream-oriented transport.
+ * When new_socket() is used, host, port, and bindaddr are not used.
+ * @param connect_timeout Time in seconds to wait for a successful connection.
+ * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT
+ */
+struct tr_ssh_config {
+ char *host;
+ unsigned int port;
+ char *bindaddr;
+ char *username;
+ char *server_hostkey_path;
+ char *client_privkey_path;
+ void *data;
+ int (*new_socket)(void *data);
+ unsigned int connect_timeout;
+ char *password;
+};
+
+/**
+ * @brief Initializes the tr_socket struct for a SSH connection.
+ * @param[in] config SSH configuration for the connection.
+ * @param[out] socket Initialized transport socket.
+ * @returns TR_SUCCESS On success.
+ * @returns TR_ERROR On error.
+ */
+int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket);
+
+#endif
+/** @} */
diff --git a/rtrlib/transport/ssh/ssh_transport_private.h b/rtrlib/transport/ssh/ssh_transport_private.h
new file mode 100644
index 0000000..b574863
--- /dev/null
+++ b/rtrlib/transport/ssh/ssh_transport_private.h
@@ -0,0 +1,13 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#ifndef SSH_TRANSPORT_PRIVATE_H
+#define SSH_TRANSPORT_PRIVATE_H
+#include "ssh_transport.h"
+#endif
diff --git a/rtrlib/transport/tcp/tcp_transport.c b/rtrlib/transport/tcp/tcp_transport.c
new file mode 100644
index 0000000..1dbc903
--- /dev/null
+++ b/rtrlib/transport/tcp/tcp_transport.c
@@ -0,0 +1,368 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "tcp_transport_private.h"
+
+#include "rtrlib/lib/alloc_utils_private.h"
+#include "rtrlib/lib/log_private.h"
+#include "rtrlib/rtrlib_export_private.h"
+#include "rtrlib/transport/transport_private.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define TCP_DBG(fmt, sock, ...) \
+ do { \
+ const struct tr_tcp_socket *tmp = sock; \
+ lrtr_dbg("TCP Transport(%s:%s): " fmt, tmp->config.host, tmp->config.port, ##__VA_ARGS__); \
+ } while (0)
+#define TCP_DBG1(a, sock) TCP_DBG(a, sock)
+
+struct tr_tcp_socket {
+ int socket;
+ struct tr_tcp_config config;
+ char *ident;
+};
+
+static int tr_tcp_open(void *tr_tcp_sock);
+static void tr_tcp_close(void *tr_tcp_sock);
+static void tr_tcp_free(struct tr_socket *tr_sock);
+static int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout);
+static int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout);
+static const char *tr_tcp_ident(void *socket);
+
+static int set_socket_blocking(int socket)
+{
+ int flags = fcntl(socket, F_GETFL);
+
+ if (flags == -1)
+ return TR_ERROR;
+
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(socket, F_SETFL, flags) == -1)
+ return TR_ERROR;
+
+ return TR_SUCCESS;
+}
+
+static int set_socket_non_blocking(int socket)
+{
+ int flags = fcntl(socket, F_GETFL);
+
+ if (flags == -1)
+ return TR_ERROR;
+
+ flags |= O_NONBLOCK;
+
+ if (fcntl(socket, F_SETFL, flags) == -1)
+ return TR_ERROR;
+
+ return TR_SUCCESS;
+}
+
+static int get_socket_error(int socket)
+{
+ int result;
+ socklen_t result_len = sizeof(result);
+
+ if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0)
+ return TR_ERROR;
+
+ return result;
+}
+
+/* WARNING: This function has cancelable sections! */
+int tr_tcp_open(void *tr_socket)
+{
+ int rtval = TR_ERROR;
+ int tcp_rtval = 0;
+ struct tr_tcp_socket *tcp_socket = tr_socket;
+ const struct tr_tcp_config *config = &tcp_socket->config;
+
+ assert(tcp_socket->socket == -1);
+
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ struct addrinfo *bind_addrinfo = NULL;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ if (config->new_socket) {
+ tcp_socket->socket = (*config->new_socket)(config->data);
+ if (tcp_socket->socket <= 0) {
+ TCP_DBG("Couldn't establish TCP connection, %s",
+ tcp_socket, strerror(errno));
+ goto end;
+ }
+ }
+ if (tcp_socket->socket < 0) {
+ tcp_rtval = getaddrinfo(config->host, config->port, &hints, &res);
+ if (tcp_rtval != 0) {
+ if (tcp_rtval == EAI_SYSTEM) {
+ TCP_DBG("getaddrinfo error, %s", tcp_socket,
+ strerror(errno));
+ } else {
+ TCP_DBG("getaddrinfo error, %s", tcp_socket,
+ gai_strerror(tcp_rtval));
+ }
+ return TR_ERROR;
+ }
+
+ tcp_socket->socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (tcp_socket->socket == -1) {
+ TCP_DBG("Socket creation failed, %s", tcp_socket, strerror(errno));
+ goto end;
+ }
+
+ if (tcp_socket->config.bindaddr) {
+ tcp_rtval = getaddrinfo(tcp_socket->config.bindaddr, 0, &hints, &bind_addrinfo);
+ if (tcp_rtval != 0) {
+ if (tcp_rtval == EAI_SYSTEM) {
+ TCP_DBG("getaddrinfo error, %s", tcp_socket,
+ strerror(errno));
+ } else {
+ TCP_DBG("getaddrinfo error, %s", tcp_socket,
+ gai_strerror(tcp_rtval));
+ }
+ goto end;
+ }
+ if (bind(tcp_socket->socket, bind_addrinfo->ai_addr, bind_addrinfo->ai_addrlen) != 0) {
+ TCP_DBG("Socket bind failed, %s", tcp_socket, strerror(errno));
+ goto end;
+ }
+
+ freeaddrinfo(bind_addrinfo);
+ bind_addrinfo = NULL;
+ }
+
+ if (set_socket_non_blocking(tcp_socket->socket) == TR_ERROR) {
+ TCP_DBG("Could not set socket to non blocking, %s", tcp_socket, strerror(errno));
+ goto end;
+ }
+
+ if (connect(tcp_socket->socket, res->ai_addr, res->ai_addrlen) == -1 && errno != EINPROGRESS) {
+ TCP_DBG("Couldn't establish TCP connection, %s",
+ tcp_socket, strerror(errno));
+ goto end;
+ }
+
+ freeaddrinfo(res);
+ res = NULL;
+
+ fd_set wfds;
+
+ FD_ZERO(&wfds);
+ FD_SET(tcp_socket->socket, &wfds);
+
+ struct timeval timeout = {.tv_sec = tcp_socket->config.connect_timeout, .tv_usec = 0};
+ int oldcancelstate;
+
+ /* Enable cancellability for the select call
+ * to prevent rtr_stop from blocking for a long time.
+ * It must be the only blocking call in this function.
+ * Since local resources have all been freed this should be safe.
+ */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
+ int ret = select(tcp_socket->socket + 1, NULL, &wfds, NULL, &timeout);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate);
+
+ if (ret < 0) {
+ TCP_DBG("Could not select tcp socket, %s", tcp_socket, strerror(errno));
+ goto end;
+
+ } else if (ret == 0) {
+ TCP_DBG("Could not establish TCP connection in time", tcp_socket);
+ goto end;
+ }
+
+ int socket_error = get_socket_error(tcp_socket->socket);
+
+ if (socket_error == TR_ERROR) {
+ TCP_DBG("Could not get socket error, %s", tcp_socket, strerror(errno));
+ goto end;
+
+ } else if (socket_error > 0) {
+ TCP_DBG("Could not establish TCP connection, %s", tcp_socket, strerror(socket_error));
+ goto end;
+ }
+
+ if (set_socket_blocking(tcp_socket->socket) == TR_ERROR) {
+ TCP_DBG("Could not set socket to blocking, %s", tcp_socket, strerror(errno));
+ goto end;
+ }
+ }
+
+ TCP_DBG1("Connection established", tcp_socket);
+ rtval = TR_SUCCESS;
+
+end:
+ if (res)
+ freeaddrinfo(res);
+
+ if (bind_addrinfo)
+ freeaddrinfo(bind_addrinfo);
+ if (rtval == -1)
+ tr_tcp_close(tr_socket);
+ return rtval;
+}
+
+void tr_tcp_close(void *tr_tcp_sock)
+{
+ struct tr_tcp_socket *tcp_socket = tr_tcp_sock;
+
+ if (tcp_socket->socket != -1)
+ close(tcp_socket->socket);
+ TCP_DBG1("Socket closed", tcp_socket);
+ tcp_socket->socket = -1;
+}
+
+void tr_tcp_free(struct tr_socket *tr_sock)
+{
+ struct tr_tcp_socket *tcp_sock = tr_sock->socket;
+
+ assert(tcp_sock);
+ assert(tcp_sock->socket == -1);
+
+ TCP_DBG1("Freeing socket", tcp_sock);
+
+ lrtr_free(tcp_sock->config.host);
+ lrtr_free(tcp_sock->config.port);
+ lrtr_free(tcp_sock->config.bindaddr);
+
+ if (tcp_sock->ident)
+ lrtr_free(tcp_sock->ident);
+ tr_sock->socket = NULL;
+ lrtr_free(tcp_sock);
+}
+
+int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout)
+{
+ const struct tr_tcp_socket *tcp_socket = tr_tcp_sock;
+ int rtval;
+
+ if (timeout == 0) {
+ rtval = recv(tcp_socket->socket, pdu, len, MSG_DONTWAIT);
+ } else {
+ struct timeval t = {timeout, 0};
+
+ if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) == -1) {
+ TCP_DBG("setting SO_RCVTIMEO failed, %s", tcp_socket, strerror(errno));
+ return TR_ERROR;
+ }
+ rtval = recv(tcp_socket->socket, pdu, len, 0);
+ }
+
+ if (rtval == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return TR_WOULDBLOCK;
+ if (errno == EINTR)
+ return TR_INTR;
+ TCP_DBG("recv(..) error: %s", tcp_socket, strerror(errno));
+ return TR_ERROR;
+ }
+ if (rtval == 0)
+ return TR_CLOSED;
+ return rtval;
+}
+
+int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout)
+{
+ const struct tr_tcp_socket *tcp_socket = tr_tcp_sock;
+ int rtval;
+
+ if (timeout == 0) {
+ rtval = send(tcp_socket->socket, pdu, len, MSG_DONTWAIT);
+ } else {
+ struct timeval t = {timeout, 0};
+
+ if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) == -1) {
+ TCP_DBG("setting SO_SNDTIMEO failed, %s", tcp_socket, strerror(errno));
+ return TR_ERROR;
+ }
+ rtval = send(tcp_socket->socket, pdu, len, 0);
+ }
+
+ if (rtval == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return TR_WOULDBLOCK;
+ if (errno == EINTR)
+ return TR_INTR;
+ TCP_DBG("send(..) error: %s", tcp_socket, strerror(errno));
+ return TR_ERROR;
+ }
+ if (rtval == 0)
+ return TR_ERROR;
+ return rtval;
+}
+
+const char *tr_tcp_ident(void *socket)
+{
+ size_t len;
+ struct tr_tcp_socket *sock = socket;
+
+ assert(sock);
+
+ if (sock->ident)
+ return sock->ident;
+
+ len = strlen(sock->config.port) + strlen(sock->config.host) + 2;
+ sock->ident = lrtr_malloc(len);
+ if (!sock->ident)
+ return NULL;
+ snprintf(sock->ident, len, "%s:%s", sock->config.host, sock->config.port);
+
+ return sock->ident;
+}
+
+RTRLIB_EXPORT int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket)
+{
+ socket->close_fp = &tr_tcp_close;
+ socket->free_fp = &tr_tcp_free;
+ socket->open_fp = &tr_tcp_open;
+ socket->recv_fp = &tr_tcp_recv;
+ socket->send_fp = &tr_tcp_send;
+ socket->ident_fp = &tr_tcp_ident;
+
+ socket->socket = lrtr_malloc(sizeof(struct tr_tcp_socket));
+ struct tr_tcp_socket *tcp_socket = socket->socket;
+
+ tcp_socket->socket = -1;
+ tcp_socket->config.host = lrtr_strdup(config->host);
+ tcp_socket->config.port = lrtr_strdup(config->port);
+ if (config->bindaddr)
+ tcp_socket->config.bindaddr = lrtr_strdup(config->bindaddr);
+ else
+ tcp_socket->config.bindaddr = NULL;
+
+ if (config->connect_timeout == 0)
+ tcp_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT;
+ else
+ tcp_socket->config.connect_timeout = config->connect_timeout;
+
+ tcp_socket->ident = NULL;
+ tcp_socket->config.data = config->data;
+ tcp_socket->config.new_socket = config->new_socket;
+
+ return TR_SUCCESS;
+}
diff --git a/rtrlib/transport/tcp/tcp_transport.h b/rtrlib/transport/tcp/tcp_transport.h
new file mode 100644
index 0000000..3198050
--- /dev/null
+++ b/rtrlib/transport/tcp/tcp_transport.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_tcp_transport_h TCP transport socket
+ * @ingroup mod_transport_h
+ * @brief An implementation of the TCP protocol for the RTR transport.
+ * See @ref mod_transport_h "transport interface" for a list of supported operations.
+ *
+ * @{
+ */
+
+#ifndef RTR_TCP_TRANSPORT_H
+#define RTR_TCP_TRANSPORT_H
+
+#include "rtrlib/transport/transport.h"
+
+/**
+ * @brief A tr_tcp_config struct holds configuration for a TCP connection.
+ * @param host Hostname or IP address to connect to.
+ * @param port Port to connect to.
+ * @param bindaddr Hostname or IP address to connect from. NULL for
+ * determination by OS.
+ * to use the source address of the system's default route to the server
+ * @param data Information to pass to callback function
+ * in charge of retrieving socket
+ * @param new_socket(void *opaque_info) callback routine, that
+ * Pointer to the function that is called every time a new connection
+ * is made. The returned socket is expected to be ready for use (e.g.
+ * in state established), and must use a reliably stream-oriented transport.
+ * When new_socket() is used, host, port, and bindaddr are not used.
+ * @param connect_timeout Time in seconds to wait for a successful connection.
+ * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT
+ */
+struct tr_tcp_config {
+ char *host;
+ char *port;
+ char *bindaddr;
+ void *data;
+ int (*new_socket)(void *data);
+ unsigned int connect_timeout;
+};
+
+/**
+ * @brief Initializes the tr_socket struct for a TCP connection.
+ * @param[in] config TCP configuration for the connection.
+ * @param[out] socket Initialized transport socket.
+ * @returns TR_SUCCESS On success.
+ * @returns TR_ERROR On error.
+ */
+int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket);
+#endif
+/** @} */
diff --git a/rtrlib/transport/tcp/tcp_transport_private.h b/rtrlib/transport/tcp/tcp_transport_private.h
new file mode 100644
index 0000000..ef41a32
--- /dev/null
+++ b/rtrlib/transport/tcp/tcp_transport_private.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_tcp_transport_h TCP transport socket
+ * @ingroup mod_transport_h
+ * @brief An implementation of the TCP protocol for the RTR transport.
+ * See @ref mod_transport_h "transport interface" for a list of supported operations.
+ *
+ * @{
+ */
+
+#ifndef RTR_TCP_TRANSPORT_PRIVATE_H
+#define RTR_TCP_TRANSPORT_PRIVATE_H
+#include "tcp_transport.h"
+#endif
+/** @} */
diff --git a/rtrlib/transport/transport.c b/rtrlib/transport/transport.c
new file mode 100644
index 0000000..d1bdfc5
--- /dev/null
+++ b/rtrlib/transport/transport.c
@@ -0,0 +1,87 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "transport_private.h"
+
+#include "rtrlib/lib/utils_private.h"
+
+inline int tr_open(struct tr_socket *socket)
+{
+ return socket->open_fp(socket->socket);
+}
+
+inline void tr_close(struct tr_socket *socket)
+{
+ socket->close_fp(socket->socket);
+}
+
+inline void tr_free(struct tr_socket *socket)
+{
+ socket->free_fp(socket);
+}
+
+inline int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout)
+{
+ return socket->send_fp(socket->socket, pdu, len, timeout);
+}
+
+inline int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout)
+{
+ return socket->recv_fp(socket->socket, buf, len, timeout);
+}
+
+/* cppcheck-suppress unusedFunction */
+inline const char *tr_ident(struct tr_socket *sock)
+{
+ return sock->ident_fp(sock->socket);
+}
+
+int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout)
+{
+ unsigned int total_send = 0;
+ time_t end_time;
+
+ lrtr_get_monotonic_time(&end_time);
+ end_time = end_time + timeout;
+
+ while (total_send < len) {
+ time_t cur_time;
+ int rtval;
+
+ lrtr_get_monotonic_time(&cur_time);
+
+ rtval = tr_send(socket, ((char *)pdu) + total_send, (len - total_send), (end_time - cur_time));
+ if (rtval < 0)
+ return rtval;
+ total_send += rtval;
+ }
+ return total_send;
+}
+
+int tr_recv_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout)
+{
+ size_t total_recv = 0;
+ time_t end_time;
+
+ lrtr_get_monotonic_time(&end_time);
+ end_time += timeout;
+
+ while (total_recv < len) {
+ time_t cur_time;
+ int rtval;
+
+ lrtr_get_monotonic_time(&cur_time);
+
+ rtval = tr_recv(socket, ((char *)pdu) + total_recv, (len - total_recv), end_time - cur_time);
+ if (rtval < 0)
+ return rtval;
+ total_recv += rtval;
+ }
+ return total_recv;
+}
diff --git a/rtrlib/transport/transport.h b/rtrlib/transport/transport.h
new file mode 100644
index 0000000..c092a2f
--- /dev/null
+++ b/rtrlib/transport/transport.h
@@ -0,0 +1,113 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_transport_h Transport sockets
+ * @brief The RTR transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RTR server and client.
+ * @details Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * tr_tcp_init()).\n
+ * The tr_* functions call the corresponding function pointers, which are
+ * passed in the tr_socket struct, and forward the remaining arguments.
+ *
+ * @{
+ */
+
+#ifndef RTR_TRANSPORT_H
+#define RTR_TRANSPORT_H
+
+#include <time.h>
+
+/**
+ * @brief Default connect timeout
+ */
+#define RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT 30
+
+/**
+ * @brief The return values for tr_ functions.
+ */
+enum tr_rtvals {
+ /** @brief Operation was successful. */
+ TR_SUCCESS = 0,
+
+ /** Error occurred. */
+ TR_ERROR = -1,
+
+ /** No data is available on the socket. */
+ TR_WOULDBLOCK = -2,
+
+ /** Call was interrupted from a signal */
+ TR_INTR = -3,
+
+ /** Connection closed */
+ TR_CLOSED = -4
+};
+
+struct tr_socket;
+
+/**
+ * @brief A function pointer to a technology specific close function.
+ * \sa tr_close
+ */
+typedef void (*tr_close_fp)(void *socket);
+
+/**
+ * @brief A function pointer to a technology specific open function.
+ * \sa tr_open
+ */
+typedef int (*tr_open_fp)(void *socket);
+
+/**
+ * @brief A function pointer to a technology specific free function.
+ * All memory associated with the tr_socket will be freed.
+ * \sa tr_free
+ */
+typedef void (*tr_free_fp)(struct tr_socket *tr_sock);
+
+/**
+ * @brief A function pointer to a technology specific recv function.
+ * \sa tr_recv
+ */
+typedef int (*tr_recv_fp)(const void *socket, void *pdu, const size_t len, const time_t timeout);
+
+/**
+ * @brief A function pointer to a technology specific send function.
+ * \sa tr_send
+ */
+typedef int (*tr_send_fp)(const void *socket, const void *pdu, const size_t len, const time_t timeout);
+
+/**
+ * @brief A function pointer to a technology specific info function.
+ * \sa tr_send
+ */
+typedef const char *(*tr_ident_fp)(void *socket);
+
+/**
+ * @brief A transport socket datastructure.
+ *
+ * @param socket A pointer to a technology specific socket.
+ * @param open_fp Pointer to a function that establishes the socket connection.
+ * @param close_fp Pointer to a function that closes the socket.
+ * @param free_fp Pointer to a function that frees all memory allocated with this socket.
+ * @param send_fp Pointer to a function that sends data through this socket.
+ * @param recv_fp Pointer to a function that receives data from this socket.
+ */
+struct tr_socket {
+ void *socket;
+ tr_open_fp open_fp;
+ tr_close_fp close_fp;
+ tr_free_fp free_fp;
+ tr_send_fp send_fp;
+ tr_recv_fp recv_fp;
+ tr_ident_fp ident_fp;
+};
+
+#endif
+/** @} */
diff --git a/rtrlib/transport/transport_private.h b/rtrlib/transport/transport_private.h
new file mode 100644
index 0000000..7fc2e19
--- /dev/null
+++ b/rtrlib/transport/transport_private.h
@@ -0,0 +1,107 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+/**
+ * @defgroup mod_transport_h Transport sockets
+ * @brief The RTR transport sockets implement the communication channel
+ * (e.g., SSH, TCP, TCP-AO) between an RTR server and client.
+ * @details Before using the transport socket, a tr_socket must be
+ * initialized based on a protocol-dependent init function (e.g.,
+ * tr_tcp_init()).\n
+ * The tr_* functions call the corresponding function pointers, which are
+ * passed in the tr_socket struct, and forward the remaining arguments.
+ *
+ * @{
+ */
+
+#ifndef RTR_TRANSPORT_PRIVATE_H
+#define RTR_TRANSPORT_PRIVATE_H
+
+#include "transport.h"
+
+#include <time.h>
+
+/**
+ * @brief Establish the connection.
+ * @param[in] socket Socket that will be used.
+ * @return TR_SUCCESS On success.
+ * @return TR_ERROR On error.
+ */
+int tr_open(struct tr_socket *socket);
+
+/**
+ * @brief Close the socket connection.
+ * @param[in] socket Socket that will be closed.
+ */
+void tr_close(struct tr_socket *socket);
+
+/**
+ * @brief Deallocates all memory that the passed socket uses.
+ * Socket have to be closed before.
+ * @param[in] socket which will be freed.
+ */
+void tr_free(struct tr_socket *socket);
+
+/**
+ * @brief Receives <= len Bytes data from the socket.
+ * @param[in] socket Socket that will be used.
+ * @param[out] buf Received data, must be an allocated memory area of >=pdu_len bytes.
+ * @param[in] len Size of pdu in Bytes.
+ * @param[in] timeout Max. seconds the function will block till len data was received.
+ * @return >0 Number of Bytes read.
+ * @return TR_ERROR On error.
+ * @return TR_WOULDBLOCK If no data was available at the socket before the timeout expired.
+ */
+int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout);
+
+/**
+ * @brief Send <= len Bytes data over the socket.
+ * @param[in] socket Socket that will be used.
+ * @param[out] pdu Data that will be be sent.
+ * @param[in] len Size of pdu in Bytes.
+ * @param[in] timeout Max. seconds the function should try to send the data till it returns.
+ * @return >0 Number of Bytes sent.
+ * @return TR_ERROR On error.
+ */
+int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout);
+
+/**
+ * Repeatedly calls tr_send(..) till len Bytes were sent, the timeout expired or an error occurred.
+ * @param[in] socket Socket that will be used.
+ * @param[out] pdu Data that will be be sent.
+ * @param[in] len Size of pdu in Bytes.
+ * @param[in] timeout Max. seconds the functions should try to send pdu till it returns.
+ * @return >0 Number of Bytes sent.
+ * @return TR_ERROR On Error.
+ * @return TR_WOULDBLOCK If send would block.
+ */
+int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout);
+
+/**
+ * Repeatedly calls tr_recv(..) till len Bytes were received, the timeout expired or an error occurred.
+ * @param[in] socket Socket that will be used.
+ * @param[out] buf Received data, must be an allocated memory area of >=len bytes.
+ * @param[in] len Size of pdu in Bytes.
+ * @param[in] timeout Max. seconds the functions should try to receive len data till it returns.
+ * @return >0 Number of Bytes received.
+ * @return TR_ERROR On error.
+ * @return TR_WOULDBLOCK If send would block.
+ */
+int tr_recv_all(const struct tr_socket *socket, const void *buf, const size_t len, const time_t timeout);
+
+/**
+ * Returns an identifier for the socket endpoint, eg host:port.
+ * @param[in] socket
+ * return Pointer to a \0 terminated String
+ * return NULL on error
+ */
+const char *tr_ident(struct tr_socket *socket);
+
+#endif
+/** @} */