diff options
Diffstat (limited to '')
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 +/** @} */ |