summaryrefslogtreecommitdiffstats
path: root/rtrlib/rtr
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:54:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:54:46 +0000
commitcd7b005519ade8ab6c97fcb21590b71b7d1be6e3 (patch)
treec611a8d0cd5e8f68f41b8c2d16ba580e0f40a38d /rtrlib/rtr
parentInitial commit. (diff)
downloadlibrtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.tar.xz
librtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.zip
Adding upstream version 0.8.0.upstream/0.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rtrlib/rtr')
-rw-r--r--rtrlib/rtr/packets.c1517
-rw-r--r--rtrlib/rtr/packets_private.h32
-rw-r--r--rtrlib/rtr/rtr.c272
-rw-r--r--rtrlib/rtr/rtr.h161
-rw-r--r--rtrlib/rtr/rtr_private.h88
5 files changed, 2070 insertions, 0 deletions
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